<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
		<id>https://wiki.openstack.org/w/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Mike+Bayer</id>
		<title>OpenStack - User contributions [en]</title>
		<link rel="self" type="application/atom+xml" href="https://wiki.openstack.org/w/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Mike+Bayer"/>
		<link rel="alternate" type="text/html" href="https://wiki.openstack.org/wiki/Special:Contributions/Mike_Bayer"/>
		<updated>2026-07-13T10:01:47Z</updated>
		<subtitle>User contributions</subtitle>
		<generator>MediaWiki 1.28.2</generator>

	<entry>
		<id>https://wiki.openstack.org/w/index.php?title=Eventlet-removal&amp;diff=185432</id>
		<title>Eventlet-removal</title>
		<link rel="alternate" type="text/html" href="https://wiki.openstack.org/w/index.php?title=Eventlet-removal&amp;diff=185432"/>
				<updated>2024-10-07T22:49:00Z</updated>
		
		<summary type="html">&lt;p&gt;Mike Bayer: /* 5. Use of `eventlet.wsgi` */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Why Eventlet Needs to Be Removed ==&lt;br /&gt;
&lt;br /&gt;
Eventlet, though once popular for concurrent execution using &amp;quot;greenthreads,&amp;quot; now presents several limitations and issues that make it an outdated solution for modern projects like OpenStack:&lt;br /&gt;
&lt;br /&gt;
1. '''Incompatibility with Recent Versions of Python:'''  &lt;br /&gt;
&lt;br /&gt;
Eventlet struggles to keep up with the evolution of Python, particularly with recent versions (such as Python 3.12 and beyond). Some of its internal functionalities, such as &amp;quot;monkey patching&amp;quot; standard libraries, no longer work correctly or cause unexpected behavior. This limits projects from adopting the latest improvements in Python, including critical security patches.&lt;br /&gt;
&lt;br /&gt;
2. '''Inefficient Resource and Thread Management:'''  &lt;br /&gt;
&lt;br /&gt;
The greenthreads used by Eventlet can lead to excessive memory consumption and poor resource management, especially under heavy loads or in large-scale applications. Given that OpenStack operates in a distributed and highly parallel environment, this can become a significant bottleneck for overall performance.&lt;br /&gt;
&lt;br /&gt;
3. '''Maintenance and Security Issues:'''  &lt;br /&gt;
&lt;br /&gt;
Eventlet suffers from a lack of active maintenance and does not receive regular updates to address security vulnerabilities and compatibility issues. This exposes OpenStack to potential security risks if flaws in Eventlet are not addressed promptly.&lt;br /&gt;
&lt;br /&gt;
4. '''Outdated Approach to Concurrency Programming:'''  &lt;br /&gt;
&lt;br /&gt;
With the emergence of more modern solutions like `asyncio` or `concurrent.futures`, which are natively integrated into Python, using Eventlet feels outdated. These newer libraries offer better concurrency management, improved performance, and tighter integration with the current Python ecosystem.&lt;br /&gt;
&lt;br /&gt;
5. '''Inability to Adopt New Linux Distributions:'''  &lt;br /&gt;
&lt;br /&gt;
Many modern Linux distributions, such as Ubuntu, RHEL, and others, now ship with newer versions of Python that are not supported by Eventlet. Without migrating away from Eventlet, OpenStack will face challenges in adopting these newer distributions. This will limit the ability to stay current with the latest infrastructure improvements and security enhancements provided by modern operating systems.&lt;br /&gt;
&lt;br /&gt;
== Potential Impacts if the Migration is Not Completed ==&lt;br /&gt;
&lt;br /&gt;
Failure to migrate away from Eventlet could have significant consequences for OpenStack-based projects:&lt;br /&gt;
&lt;br /&gt;
1. '''Blockage of Python Updates:'''  &lt;br /&gt;
&lt;br /&gt;
Continuing to use Eventlet may cause OpenStack to be stuck on older versions of Python. This prevents the adoption of new features, performance improvements, and —most importantly—security patches offered by newer versions of Python.&lt;br /&gt;
&lt;br /&gt;
2. '''Security Vulnerabilities Risk:'''  &lt;br /&gt;
&lt;br /&gt;
Given Eventlet's lack of maintenance, unresolved security flaws could leave OpenStack systems vulnerable to attacks. Failing to migrate to modern solutions exposes the infrastructure to major security risks.&lt;br /&gt;
&lt;br /&gt;
3. '''Degradation of Performance and Scalability:'''  &lt;br /&gt;
&lt;br /&gt;
OpenStack handles large workloads in distributed environments, and Eventlet’s inefficient resource management can cause slowdowns and failures. Without migration, this could limit OpenStack's ability to scale smoothly and handle increasingly larger data volumes.&lt;br /&gt;
&lt;br /&gt;
4. '''Inability to Adopt New Linux Distributions:'''  &lt;br /&gt;
&lt;br /&gt;
If the migration from Eventlet is not completed, OpenStack will struggle to support modern Linux distributions that come with newer versions of Python. Distributions like Ubuntu and RHEL now ship with versions of Python that are incompatible with Eventlet. This will hinder the ability to upgrade to these distributions, which are critical for staying up-to-date with the latest security fixes and performance improvements.&lt;br /&gt;
&lt;br /&gt;
5. '''Increased Maintenance Costs:'''  &lt;br /&gt;
&lt;br /&gt;
Continuing to rely on an outdated technology will increase maintenance costs as it becomes harder to find fixes or workarounds for technical issues. This will lead to a growing technical debt that will be difficult to manage in the long term.&lt;br /&gt;
&lt;br /&gt;
To ensure the longevity, stability, and security of OpenStack, it is crucial to migrate to modern solutions like `asyncio` or `concurrent.futures`. This will not only overcome Eventlet's current limitations but also ensure that OpenStack remains capable of meeting performance, security, and scalability demands. Moreover, migrating is essential for supporting the latest Linux distributions like Ubuntu and RHEL, which include newer versions of Python that are not compatible with Eventlet.&lt;br /&gt;
&lt;br /&gt;
The following sections aims to guide you during your migration journey.&lt;br /&gt;
&lt;br /&gt;
== Different Kind of Eventlet Usages ==&lt;br /&gt;
&lt;br /&gt;
In OpenStack, there exist two kinds of deliverables based on Eventlet:&lt;br /&gt;
* deliverables for whom Eventlet is optional and can be disabled&lt;br /&gt;
* deliverables that cannot live without Eventlet&lt;br /&gt;
&lt;br /&gt;
Both deliverables won't be migrated the same way.&lt;br /&gt;
&lt;br /&gt;
For the first scenario - optional Eventlet - we suggest completely disabling Eventlet first, and then from there, start migrating your deliverables. This kind of deliverables often allows switching from one kind of execution model to another, e.g., Eventlet or futurist. We encourage migrating all your occurrences over the models non-based on Eventlet.&lt;br /&gt;
&lt;br /&gt;
==== Steps to Disable Eventlet in Optional Deliverables ====&lt;br /&gt;
&lt;br /&gt;
===== 1. Analyze Dependencies and Usage of Eventlet =====&lt;br /&gt;
&lt;br /&gt;
* Identify all parts of the code that depend on Eventlet. This includes direct imports, the use of greenthreads, and specific methods such as `monkey_patch()`, `Timeout`, or `eventlet.wsgi`.&lt;br /&gt;
* Perform a search across the codebase to locate these Eventlet elements. Use search tools like `grep`, or more specialized tools like `beagle`, to get an overview of Eventlet occurrences.&lt;br /&gt;
&lt;br /&gt;
===== 2. Selective Eventlet Deactivation =====&lt;br /&gt;
&lt;br /&gt;
* In some cases, it’s possible to disable Eventlet via configuration settings. Check if your project or framework offers this capability.&lt;br /&gt;
* If configuration doesn’t explicitly allow for disabling, start by commenting out or removing the Eventlet import lines and test the execution of the code in a local or test environment.&lt;br /&gt;
* Use native alternatives to replace functionalities dependent on Eventlet. For instance, replace greenthreads with standard threads or asynchronous processes using `asyncio`.&lt;br /&gt;
&lt;br /&gt;
===== 3. Progressive Refactoring of Eventlet Entry Points =====&lt;br /&gt;
&lt;br /&gt;
* '''Substitute `monkey_patch()`''' : Replace all occurrences of `eventlet.monkey_patch()` with explicitly defined alternatives (e.g., use `asyncio.sleep` for delays or standard libraries for threads and sockets).&lt;br /&gt;
* '''Timeout Management''' : Replace the usage of `eventlet.timeout.Timeout` with `asyncio.wait_for()` or equivalent exception handlers.&lt;br /&gt;
* '''Greenthreads and Thread Pools''' : Replace calls to `eventlet.spawn()` or `GreenPool()` with `asyncio.create_task()` or `concurrent.futures.ThreadPoolExecutor()` for concurrency management.&lt;br /&gt;
&lt;br /&gt;
===== 4. Unit and Functional Testing =====&lt;br /&gt;
&lt;br /&gt;
* After disabling Eventlet and refactoring critical elements, run existing unit and functional tests to ensure the code remains stable.&lt;br /&gt;
* Use `mock` to simulate asynchronous behaviors in your tests (e.g., patching `asyncio` instead of Eventlet).&lt;br /&gt;
&lt;br /&gt;
===== 5. Performance and Stability Validation =====&lt;br /&gt;
&lt;br /&gt;
* Assess the application's performance after disabling Eventlet, particularly in high-load scenarios. The goal is to ensure that using `asyncio` or standard threads provides equivalent or better performance.&lt;br /&gt;
* Also, monitor memory consumption and exception handling, as Eventlet’s greenthread implementation may differ from standard threads or `asyncio`.&lt;br /&gt;
&lt;br /&gt;
===== 6. Document the Changes =====&lt;br /&gt;
&lt;br /&gt;
* Once Eventlet is successfully disabled, clearly document the changes made and the new dependency requirements (e.g., `asyncio` or `concurrent.futures`).&lt;br /&gt;
* Outline the new execution models so that other developers are aware of the modifications and the new behavior of the code.&lt;br /&gt;
&lt;br /&gt;
===== 7. Complete Migration Phase =====&lt;br /&gt;
&lt;br /&gt;
* Once Eventlet is disabled and alternatives are in place, you can begin the full migration to a modern solution like `asyncio` by more extensively refactoring code that still depends on the old Eventlet-based architecture.&lt;br /&gt;
&lt;br /&gt;
The sections below aims to give you a way to identify the Eventlet patterns, and aims to give refactoring guidance.&lt;br /&gt;
&lt;br /&gt;
== Save the Date ==&lt;br /&gt;
&lt;br /&gt;
List of the coming events related to the Eventlet removal that could interest the reader:&lt;br /&gt;
&lt;br /&gt;
* 2024-10-21: PTG cross team session - The goal is to give you an overview of the Eventlet removal intiative and to present you the pop-up team;&lt;br /&gt;
* 2024-10-23: PTG cross team session - Open discussion and Q&amp;amp;A session.&lt;br /&gt;
&lt;br /&gt;
== Migration's Milestones ==&lt;br /&gt;
&lt;br /&gt;
* Adapting common libraries (Oslo, etc...) - e.g: introducing an Async Engine Facade to oslo.db, adapting oslo.service to not relying on Eventlet;&lt;br /&gt;
* Services migration - removing Eventlet usages from OpenStack services;&lt;br /&gt;
* Common libraries migration - common libraries depends on Eventlet because at a higher level - at the services level - Eventlet is required, we cannot remove this kind of Eventlet occurence from the common libraries while services are not fully migrated;&lt;br /&gt;
* Removing Eventlet from our global requirements;&lt;br /&gt;
* Abandoning Eventlet.&lt;br /&gt;
&lt;br /&gt;
== Targeted Releases ==&lt;br /&gt;
&lt;br /&gt;
Fully removing Eventlet from OpenStack will surely take several series, we target, at least, the H or I series.&lt;br /&gt;
&lt;br /&gt;
== Want to Discuss? ==&lt;br /&gt;
&lt;br /&gt;
=== #eventlet-removal ===&lt;br /&gt;
&lt;br /&gt;
Follow the '''#eventlet-removal''' tag. It will be used everywhere to tag all the related things to this topic:&lt;br /&gt;
&lt;br /&gt;
* [https://review.opendev.org/q/topic:%22eventlet-removal%22 gerrit patches]&lt;br /&gt;
* mailing list threads&lt;br /&gt;
* OpenStack irc channel&lt;br /&gt;
* RedHat slack channel&lt;br /&gt;
* ...&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
If you want to discuss about the Eventlet removal then '''dial #eventlet-removal'''. &lt;br /&gt;
You can join one of these channels or [https://review.opendev.org/q/topic:%22eventlet-removal%22 submit a new patch with a topic set to #eventlet-removal] or open a new thread with the #eventlet-removal tag.&lt;br /&gt;
&lt;br /&gt;
=== The Pop-up team ===&lt;br /&gt;
&lt;br /&gt;
A Pop-up team is constituted to manage this initiative, do not hesitate to contact this team to get feedback or answers.&lt;br /&gt;
&lt;br /&gt;
== Getting Started ==&lt;br /&gt;
&lt;br /&gt;
=== Available Alternatives to Eventlet ===&lt;br /&gt;
&lt;br /&gt;
We identified the following alternatives to replace Eventlet:&lt;br /&gt;
&lt;br /&gt;
* [https://docs.python.org/3/library/asyncio.html Asyncio];&lt;br /&gt;
* [https://awaitlet.sqlalchemy.org/en/latest/ Awaitlet];&lt;br /&gt;
* [https://docs.aiohttp.org/en/stable/ Aiohttp];&lt;br /&gt;
* [https://www.uvicorn.org/ Uvicorn];&lt;br /&gt;
* ...&lt;br /&gt;
&lt;br /&gt;
Do not hesitate to take a look to the following resources:&lt;br /&gt;
&lt;br /&gt;
* [https://eventlet.readthedocs.io/en/latest/asyncio/asyncio.html#alternatives-tips alternatives officially supported by Eventlet];&lt;br /&gt;
* [https://governance.openstack.org/tc/goals/proposed/remove-eventlet.html#the-available-alternatives-to-eventlet the alternatives proposed into the OpenStack community goal];&lt;br /&gt;
* [https://github.com/timofurrer/awesome-asyncio a curated list of Python asyncio frameworks, libraries, software and resources].&lt;br /&gt;
&lt;br /&gt;
=== How to Analyze Your Deliverables ===&lt;br /&gt;
&lt;br /&gt;
Teams are invited to analyze their deliverables to see their kind of Eventlet usages, here is an example with Oslo:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ pip install beagle&lt;br /&gt;
$ beagle search --ignore-comments -f link --repo-pattern &amp;quot;openstack/oslo*&amp;quot; 'eventlet'&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The following sections aims to give you guidance to identify your usages and use cases.&lt;br /&gt;
&lt;br /&gt;
=== Identifying Common Pattern ===&lt;br /&gt;
&lt;br /&gt;
This section aims to help you identify the various patterns that you can find on your deliverables.&lt;br /&gt;
&lt;br /&gt;
==== 1. Importing Eventlet ====&lt;br /&gt;
&lt;br /&gt;
Eventlet is imported in two main ways:&lt;br /&gt;
&lt;br /&gt;
* '''Direct import:'''&lt;br /&gt;
  &amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  import eventlet&lt;br /&gt;
  &amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
  &lt;br /&gt;
* '''Specific import of Eventlet submodules:'''&lt;br /&gt;
  &amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  from eventlet.timeout import Timeout&lt;br /&gt;
  from eventlet import greenthread&lt;br /&gt;
  from eventlet import hubs&lt;br /&gt;
  &amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 2. Use of `monkey_patch()` ====&lt;br /&gt;
&lt;br /&gt;
The use of the `eventlet.monkey_patch()` method is frequent and aims to replace some standard libraries (such as threads or sockets) with their asynchronous equivalents compatible with Eventlet's model.&lt;br /&gt;
&lt;br /&gt;
* '''Explicit calls to `monkey_patch`:'''&lt;br /&gt;
  &amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  eventlet.monkey_patch()&lt;br /&gt;
  eventlet.monkey_patch(all=True)&lt;br /&gt;
  eventlet.monkey_patch(time=True)&lt;br /&gt;
  eventlet.patcher.monkey_patch(all=False, socket=True)&lt;br /&gt;
  &amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 3. Timeout and Deadline Management ====&lt;br /&gt;
&lt;br /&gt;
The use of `eventlet.timeout.Timeout` is common to manage waiting periods or deadlines for blocking operations.&lt;br /&gt;
&lt;br /&gt;
* '''Use of `Timeout` in a `with` or `try-except` block:'''&lt;br /&gt;
  &amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  with eventlet.Timeout(timeout, True):&lt;br /&gt;
      # Do something&lt;br /&gt;
  except eventlet.timeout.Timeout:&lt;br /&gt;
      # Handle the timeout&lt;br /&gt;
  &amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 4. Green Threads and GreenPool ====&lt;br /&gt;
&lt;br /&gt;
Eventlet provides &amp;quot;greenthread&amp;quot; versions of classic threads as well as thread pools adapted to its asynchronous model.&lt;br /&gt;
&lt;br /&gt;
* '''Use of GreenThread and GreenPool:'''&lt;br /&gt;
  &amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  self.pool = eventlet.GreenPool(threads)&lt;br /&gt;
  eventlet.spawn(func)&lt;br /&gt;
  eventlet.spawn_n(func)&lt;br /&gt;
  &amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 5. Use of `eventlet.wsgi` ====&lt;br /&gt;
&lt;br /&gt;
Eventlet is sometimes used to handle WSGI (Web Server Gateway Interface) servers, particularly via `eventlet.wsgi`.&lt;br /&gt;
&lt;br /&gt;
* '''Example of use with `eventlet.wsgi`:'''&lt;br /&gt;
  &amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  import eventlet.wsgi&lt;br /&gt;
  eventlet.wsgi.server(socket, application, custom_pool=self.pool)&lt;br /&gt;
  &amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 6. Deferred Tasks and Scheduling ====&lt;br /&gt;
&lt;br /&gt;
The use of `eventlet.spawn_after()` to execute tasks after a delay is also a recurring pattern.&lt;br /&gt;
&lt;br /&gt;
* '''Example of `spawn_after`:'''&lt;br /&gt;
  &amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  eventlet.spawn_after(3.5, update_db)&lt;br /&gt;
  &amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 7. Use in Tests with `mock` ====&lt;br /&gt;
&lt;br /&gt;
In tests, Eventlet is sometimes patched or mocked to simulate its behavior or to control asynchronous tests.&lt;br /&gt;
&lt;br /&gt;
* '''Examples of use in tests:'''&lt;br /&gt;
  &amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  @mock.patch.object(eventlet, 'monkey_patch')&lt;br /&gt;
  @mock.patch('eventlet.spawn')&lt;br /&gt;
  &amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 8. Presence in Configuration Files and Dependencies ====&lt;br /&gt;
&lt;br /&gt;
Eventlet is explicitly mentioned in configuration files as a dependency in `requirements.txt` and `setup.cfg` files.&lt;br /&gt;
&lt;br /&gt;
* '''Dependencies:'''&lt;br /&gt;
  &amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
  eventlet&amp;gt;=0.18.2&lt;br /&gt;
  eventlet!=0.20.1,&amp;gt;=0.18.2 # MIT&lt;br /&gt;
  &amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 9. References in Documentation ====&lt;br /&gt;
&lt;br /&gt;
Some parts of the documentation or code comments refer to the use of Eventlet, either explaining why it is used or mentioning alternatives.&lt;br /&gt;
&lt;br /&gt;
* '''Example in documentation:'''&lt;br /&gt;
  &amp;lt;syntaxhighlight lang=&amp;quot;rst&amp;quot;&amp;gt;&lt;br /&gt;
  If eventlet is used, then this engine will not block other threads&lt;br /&gt;
  &amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Migration Plan ===&lt;br /&gt;
&lt;br /&gt;
Here’s a proposed migration plan to replace Eventlet with a more modern and efficient solution like asyncio or concurrent.futures, while considering each of the identified patterns.&lt;br /&gt;
This plan should be used in addition of the official migration guide provided into the Eventlet documentation:&lt;br /&gt;
&lt;br /&gt;
https://eventlet.readthedocs.io/en/latest/asyncio/asyncio.html&lt;br /&gt;
&lt;br /&gt;
==== 1. Direct Import of Eventlet ====&lt;br /&gt;
&lt;br /&gt;
'''Migration:'''&lt;br /&gt;
* Replace `import eventlet` and specific Eventlet submodules with asyncio, or other libraries such as `concurrent.futures`.&lt;br /&gt;
&lt;br /&gt;
'''Steps:'''&lt;br /&gt;
* Review the code for all occurrences of `eventlet` imports.&lt;br /&gt;
* Replace with equivalent asyncio or standard Python concurrency libraries.&lt;br /&gt;
&lt;br /&gt;
'''Example:'''&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
# Before&lt;br /&gt;
import eventlet&lt;br /&gt;
&lt;br /&gt;
# After&lt;br /&gt;
import asyncio&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 2. `monkey_patch()` ====&lt;br /&gt;
&lt;br /&gt;
'''Migration:'''&lt;br /&gt;
* Monkey-patching should be avoided because it changes standard library behavior, which can cause unexpected side effects.&lt;br /&gt;
* Refactor the code to use explicit asyncio or threading-based code.&lt;br /&gt;
&lt;br /&gt;
'''Steps:'''&lt;br /&gt;
* Identify all places where `eventlet.monkey_patch()` is used.&lt;br /&gt;
* Replace patched calls (e.g., `time.sleep()`, `threading`) with non-blocking alternatives (e.g., `asyncio.sleep()`, `concurrent.futures.ThreadPoolExecutor`).&lt;br /&gt;
&lt;br /&gt;
'''Example:'''&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
# Before&lt;br /&gt;
eventlet.monkey_patch()&lt;br /&gt;
&lt;br /&gt;
# After&lt;br /&gt;
# Use asyncio or threading where applicable without monkey-patching.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 3. Timeout and Deadline Management ====&lt;br /&gt;
&lt;br /&gt;
'''Migration:'''&lt;br /&gt;
* Use `asyncio.TimeoutError` or `asyncio.wait_for()` to handle timeout operations.&lt;br /&gt;
&lt;br /&gt;
'''Steps:'''&lt;br /&gt;
* Replace `eventlet.Timeout()` with `asyncio.wait_for()`.&lt;br /&gt;
* Ensure that any blocking operations are handled asynchronously.&lt;br /&gt;
&lt;br /&gt;
'''Example:'''&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
# Before&lt;br /&gt;
with eventlet.Timeout(timeout, True):&lt;br /&gt;
    # Do something&lt;br /&gt;
&lt;br /&gt;
# After&lt;br /&gt;
async def main():&lt;br /&gt;
    try:&lt;br /&gt;
        await asyncio.wait_for(task, timeout)&lt;br /&gt;
    except asyncio.TimeoutError:&lt;br /&gt;
        # Handle timeout&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 4. Green Threads and GreenPool ====&lt;br /&gt;
&lt;br /&gt;
'''Migration:'''&lt;br /&gt;
* Replace Eventlet's green threads with `asyncio` tasks or `concurrent.futures` for multithreading or multiprocessing.&lt;br /&gt;
&lt;br /&gt;
'''Steps:'''&lt;br /&gt;
* Replace `eventlet.GreenPool` with `asyncio.gather()` or `concurrent.futures.ThreadPoolExecutor`.&lt;br /&gt;
* Replace `eventlet.spawn()` and `eventlet.spawn_n()` with `asyncio.create_task()` or `executor.submit()` from concurrent.futures.&lt;br /&gt;
&lt;br /&gt;
'''Example:'''&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
# Before&lt;br /&gt;
pool = eventlet.GreenPool(threads)&lt;br /&gt;
pool.spawn(func)&lt;br /&gt;
&lt;br /&gt;
# After&lt;br /&gt;
async def func():&lt;br /&gt;
    # Function implementation&lt;br /&gt;
&lt;br /&gt;
asyncio.create_task(func())&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 5. Use of `eventlet.wsgi` ====&lt;br /&gt;
&lt;br /&gt;
'''Migration:'''&lt;br /&gt;
* Replace Eventlet's WSGI handling with a modern asynchronous web framework, such as `aiohttp`, `FastAPI`, or `uvicorn` (requires all code be ported to asyncio)&lt;br /&gt;
&lt;br /&gt;
* Replace Eventlet's WSGI handling with a plain wsgi framework such as `gunicorn`  (does not require porting to asyncio)&lt;br /&gt;
&lt;br /&gt;
zzzeek:   why not a plain WSGI framework?    There is no need for code that transparently uses eventlet for wsgi to explicitly use asyncio and convert all functions to use `async`; it should use standard wsgi patterns directly which can be run in a threaded container like mod_wsgi (we use mod_wsgi right now for web services).   see masakari for example.   gunicorn is currently present in  [https://github.com/openstack/requirements/blob/master/upper-constraints.txt requirements] as well&lt;br /&gt;
&lt;br /&gt;
'''Steps:'''&lt;br /&gt;
* Replace `eventlet.wsgi` server code with an asyncio-compatible web server like `uvicorn`.&lt;br /&gt;
* Update the application to be compatible with asynchronous HTTP handling.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
zzzeek:  How can the above be achieved for projects based on oslo.service?    will an alternative to oslo.service be introduced or will we modify oslo.service to include traditional wsgi support not based on eventlet ?    &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Example:'''&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
# Before&lt;br /&gt;
import eventlet.wsgi&lt;br /&gt;
eventlet.wsgi.server(socket, application, custom_pool=self.pool)&lt;br /&gt;
&lt;br /&gt;
# After&lt;br /&gt;
# Use an asynchronous web server like Uvicorn&lt;br /&gt;
import uvicorn&lt;br /&gt;
uvicorn.run(&amp;quot;module_name:app&amp;quot;, host=&amp;quot;0.0.0.0&amp;quot;, port=8000)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 6. Deferred Tasks and Scheduling (`spawn_after`) ====&lt;br /&gt;
&lt;br /&gt;
'''Migration:'''&lt;br /&gt;
* Use `asyncio.sleep()` or `asyncio.call_later()` for scheduling delayed tasks.&lt;br /&gt;
&lt;br /&gt;
'''Steps:'''&lt;br /&gt;
* Replace `eventlet.spawn_after()` with `asyncio.create_task()` combined with `asyncio.sleep()`.&lt;br /&gt;
&lt;br /&gt;
'''Example:'''&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
# Before&lt;br /&gt;
eventlet.spawn_after(3.5, update_db)&lt;br /&gt;
&lt;br /&gt;
# After&lt;br /&gt;
async def update_db():&lt;br /&gt;
    await asyncio.sleep(3.5)&lt;br /&gt;
    # Perform database update&lt;br /&gt;
&lt;br /&gt;
asyncio.create_task(update_db())&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 7. Use in Tests with `mock` ====&lt;br /&gt;
&lt;br /&gt;
'''Migration:'''&lt;br /&gt;
* Ensure that tests are updated to mock the corresponding asyncio or threading features instead of Eventlet’s patched functions.&lt;br /&gt;
&lt;br /&gt;
'''Steps:'''&lt;br /&gt;
* Replace Eventlet mock patches with mocks for asyncio or threading functions.&lt;br /&gt;
* Use `unittest.mock` or `pytest` to mock async tasks.&lt;br /&gt;
&lt;br /&gt;
'''Example:'''&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
# Before&lt;br /&gt;
@mock.patch.object(eventlet, 'monkey_patch')&lt;br /&gt;
&lt;br /&gt;
# After&lt;br /&gt;
@mock.patch('asyncio.sleep')&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 8. Presence in Configuration Files and Dependencies ====&lt;br /&gt;
&lt;br /&gt;
'''Migration:'''&lt;br /&gt;
* Remove Eventlet from `requirements.txt` and `setup.cfg` files.&lt;br /&gt;
* Ensure that new dependencies (such as `asyncio` or `uvicorn`) are added where needed.&lt;br /&gt;
&lt;br /&gt;
'''Steps:'''&lt;br /&gt;
* Remove all references to Eventlet in the configuration files.&lt;br /&gt;
* Replace them with modern asynchronous libraries.&lt;br /&gt;
&lt;br /&gt;
'''Example:'''&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
# Before&lt;br /&gt;
eventlet&amp;gt;=0.18.2&lt;br /&gt;
&lt;br /&gt;
# After&lt;br /&gt;
# Remove Eventlet, add necessary asyncio libraries&lt;br /&gt;
aiohttp&amp;gt;=3.7.0&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 9. References in Documentation ====&lt;br /&gt;
&lt;br /&gt;
'''Migration:'''&lt;br /&gt;
* Update all documentation and code comments to reflect the switch from Eventlet to asyncio or other modern libraries.&lt;br /&gt;
&lt;br /&gt;
'''Steps:'''&lt;br /&gt;
* Review the documentation for all mentions of Eventlet.&lt;br /&gt;
* Update examples and explanations to refer to the new concurrency model.&lt;br /&gt;
&lt;br /&gt;
'''Example:'''&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;rst&amp;quot;&amp;gt;&lt;br /&gt;
# Before&lt;br /&gt;
If eventlet is used, the engine will not block other threads&lt;br /&gt;
&lt;br /&gt;
# After&lt;br /&gt;
If asyncio is used, tasks will run concurrently without blocking each other.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Migration Plan Summary ===&lt;br /&gt;
&lt;br /&gt;
1. '''Preparation:'''&lt;br /&gt;
&lt;br /&gt;
* Audit code for Eventlet usage.&lt;br /&gt;
* Identify areas where direct migration (e.g., replacing `monkey_patch()`) will be challenging.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
2. '''Refactor Core Logic:'''&lt;br /&gt;
&lt;br /&gt;
* Refactor blocking I/O operations with asyncio’s non-blocking equivalents or Awaitlet.&lt;br /&gt;
* Replace timeouts, task scheduling, and WSGI servers with modern async solutions.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
3. '''Testing and Validation:'''&lt;br /&gt;
   &lt;br /&gt;
* Refactor test cases to mock asyncio or threading instead of Eventlet.&lt;br /&gt;
* Ensure comprehensive testing of the new concurrency model, especially for timeouts and green threads.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
4. '''Documentation and Cleanup:'''&lt;br /&gt;
&lt;br /&gt;
* Update all related documentation.&lt;br /&gt;
* Remove Eventlet from dependencies and configuration files.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
5. '''Deployment and Monitoring:'''&lt;br /&gt;
&lt;br /&gt;
* Deploy the refactored code and closely monitor for performance or concurrency issues.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
This plan ensures that the migration from Eventlet to modern asynchronous libraries like `asyncio` (or other alternatives) happens incrementally, reducing risks and ensuring code stability.&lt;br /&gt;
You can apply this plan into your team by using the following checklist.&lt;br /&gt;
&lt;br /&gt;
=== Proposed Checklist for Migrating Each Deliverable ===&lt;br /&gt;
&lt;br /&gt;
'''1. Identification of Eventlet Occurrences:'''&lt;br /&gt;
&lt;br /&gt;
* '''Objective''': List all occurrences of Eventlet in the deliverable, including imports, usage of `monkey_patch()`, `Timeout`, greenthreads, etc.&lt;br /&gt;
* '''Action''': Use search tools (such as `beagle`) to locate all instances of Eventlet in the source code.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''2. Context of Usage:'''&lt;br /&gt;
&lt;br /&gt;
* '''Objective''': Document the context of each Eventlet occurrence, including the impacted components (WSGI, threads, timeout management, etc.) and the potential impact of its removal.&lt;br /&gt;
* '''Action''': For each identified occurrence, add a note explaining its role and importance in the functionality of the deliverable.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''3. Replacement Plan:'''&lt;br /&gt;
&lt;br /&gt;
* '''Objective''': Define how each occurrence of Eventlet will be replaced with a modern alternative (e.g., `asyncio`, `concurrent.futures`, `threading`).&lt;br /&gt;
* '''Action''': Describe the specific steps for refactoring or replacing each component.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''4. Progress Tracking:'''&lt;br /&gt;
&lt;br /&gt;
* '''Objective''': Implement a progress tracker for the migration of each component within the deliverable.&lt;br /&gt;
* '''Action''': Create a dedicated section in the checklist to track the status of each task (in progress, completed, awaiting testing, etc.).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''5. Testing and Validation:'''&lt;br /&gt;
&lt;br /&gt;
* '''Objective''': Ensure that the deliverable works correctly after the migration by running specific unit and functional tests.&lt;br /&gt;
* '''Action''': Document test results after each phase of the migration and validate the stability of the deliverable.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''6. Bug and Incident Tracking:'''&lt;br /&gt;
&lt;br /&gt;
* '''Objective''': Keep track of issues encountered during or after the migration and ensure they are resolved.&lt;br /&gt;
* '''Action''': Log bugs or incidents related to the migration, with a resolution status for each issue.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''7. Documentation of Changes:'''&lt;br /&gt;
&lt;br /&gt;
* '''Objective''': Update technical documentation to reflect the changes made during the migration.&lt;br /&gt;
* '''Action''': Provide a summary of modifications in the deliverable and describe the new concurrency management practices (e.g., adopting `asyncio`).&lt;br /&gt;
&lt;br /&gt;
=== Roles and Responsibilities ===&lt;br /&gt;
&lt;br /&gt;
To help teams to manage the organisational aspects of the migration, we propose the following roles and responsibilities. OpenStack is maintained by several companies and organisations, the following structure aims to provide a minimal template for teams organisation to ensure that everyone is on the same page and have a clear understanding on how we work.&lt;br /&gt;
&lt;br /&gt;
The Eventlet migration is complex, without a bit of structure your team might suffer of that migration. The future of your deliverables depends on it.&lt;br /&gt;
&lt;br /&gt;
'''We strongly encourage teams to adopt this structure.''' &lt;br /&gt;
&lt;br /&gt;
==== Technical Lead Role ====&lt;br /&gt;
&lt;br /&gt;
The technical lead will be responsible for:&lt;br /&gt;
&lt;br /&gt;
* '''Supervision:''' Monitoring the technical progress of the migration within the team.&lt;br /&gt;
* '''Support:''' Providing assistance to other developers and troubleshooting any technical issues related to the migration.&lt;br /&gt;
* '''Reporting:''' Submitting regular progress reports, listing impacted deliverables, patterns of Eventlet identified, and participating in monthly meetings with the team product manager.&lt;br /&gt;
* '''Technology Updates:''' Staying up-to-date on the latest technical news and updates related to the migration, and sharing this information with the team.&lt;br /&gt;
* '''Coordination:''' Working closely with the product manager to ensure that the team has the necessary resources to complete the migration.&lt;br /&gt;
* '''Communication:''' Acting as the main point of contact for questions or issues within the team regarding the migration process.&lt;br /&gt;
&lt;br /&gt;
==== Product Manager’s Role ====&lt;br /&gt;
&lt;br /&gt;
The product manager is responsible for coordinating and facilitating the Eventlet removal within their team. They ensure that the necessary resources and time are allocated for the transition and that progress is tracked regularly. Key Responsibilities:&lt;br /&gt;
&lt;br /&gt;
* '''Prioritization and Planning:''' Ensure the migration is prioritized in the team's roadmap and schedules. Set clear objectives and deadlines for each stage of the migration.&lt;br /&gt;
* '''Support for the Team:''' Provide the technical lead and the team with the necessary resources and time to complete the migration. Address any organizational or logistical obstacles that might slow down progress.&lt;br /&gt;
* '''Progress Monitoring:''' Review and monitor progress reports submitted by the technical lead. Ensure the team meets the deadlines for audits, migrations, and report submissions.&lt;br /&gt;
* '''Coordination:''' Collaborate with other product managers to ensure a smooth and coordinated transition across all teams. Facilitate knowledge sharing between teams to optimize the migration process.&lt;br /&gt;
* '''Communication:''' Attend monthly meetings with the technical lead to discuss progress, challenges, and any resource needs. Provide regular updates to stakeholders about the state of the migration.&lt;br /&gt;
&lt;br /&gt;
==== Responsibilities For Developers ====&lt;br /&gt;
&lt;br /&gt;
* '''Task 1''': Identify which Eventlet version is currently in use in your deliverables.You can simply inspect your requirements. You can also use beagle on all your deliverables, see the related sections.&lt;br /&gt;
* '''Task 2''': If the version is lower than 0.36.1, prepare the Eventlet requirement upgrade.&lt;br /&gt;
* '''Task 3''': Audit your code to identify all dependencies related to Eventlet within the next three weeks.&lt;br /&gt;
* '''Task 4''': Prepare a progress report that includes:&lt;br /&gt;
# A list of impacted deliverables;&lt;br /&gt;
# Eventlet patterns used within these deliverables (based on the patterns defined above).&lt;br /&gt;
* '''Task 5''': Join us at the PTG sessions and then lets discuss together about your use cases.&lt;br /&gt;
* '''Task 6''': Start migrating identified Eventlet components to available alternatives after the audit is complete. The target is to have this migration started within the current quarter.&lt;br /&gt;
&lt;br /&gt;
==== Responsibilities For Product Managers ====&lt;br /&gt;
&lt;br /&gt;
* '''Task 1''': Prioritize this migration in your team's roadmap. From easiest to most complex deliverables to migrate.&lt;br /&gt;
* '''Task 2''': Within the next two weeks, identify a technical lead (preferably a senior developer or principal senior developer) who will oversee the migration.&lt;br /&gt;
* '''Task 3''': Ensure that your teams have the necessary resources and time to complete the migration.&lt;br /&gt;
* '''Task 4''': Attend the coming PTG sessions and monthly follow-up meetings with the technical lead to discuss progress, blockers, and next steps.&lt;br /&gt;
&lt;br /&gt;
== Tracking Progress ==&lt;br /&gt;
&lt;br /&gt;
=== Proposed Key Performance Indicators (KPI) ===&lt;br /&gt;
&lt;br /&gt;
These KPIs aim to provide managers (product managers) with a way to track and measure progress, both from a team-based perspective and from a global overview.&lt;br /&gt;
&lt;br /&gt;
'''1. Eventlet Deactivation Rate in Deliverables:'''&lt;br /&gt;
&lt;br /&gt;
* '''Objective:''' Track the progress of the number of deliverables where Eventlet has been completely disabled and replaced.&lt;br /&gt;
* '''KPI:''' Percentage of deliverables where Eventlet is replaced by alternative solutions (`asyncio`, `awaitlet`, `concurrent.futures`, etc.).&lt;br /&gt;
* '''Calculation:''' (Number of migrated deliverables / Total number of identified deliverables) x 100.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''2. Reduction in the Number of `monkey_patch()` Occurrences:'''&lt;br /&gt;
&lt;br /&gt;
* '''Objective:''' Assess the gradual removal of calls to `eventlet.monkey_patch()`, which can cause unwanted behaviors.&lt;br /&gt;
* '''KPI:''' Number of `monkey_patch()` occurrences still present in the code.&lt;br /&gt;
* '''Calculation:''' Total number of `monkey_patch()` occurrences identified before and after each migration phase.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''3. Response Time and Performance Post-Migration:'''&lt;br /&gt;
&lt;br /&gt;
* '''Objective:''' Verify whether the performance of the deliverables improves or remains stable after migrating to `asyncio` or other solutions.&lt;br /&gt;
* '''KPI:''' Average response time of services post-migration (in ms).&lt;br /&gt;
* '''Calculation:''' Measure the response time before and after migration to check if performance improves.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''4. Percentage of Automated Tests Passed After Migration:'''&lt;br /&gt;
&lt;br /&gt;
* '''Objective:''' Ensure the stability and quality of the migrated code by verifying that unit and functional tests pass successfully.&lt;br /&gt;
* '''KPI:''' Percentage of automated tests passed after migration.&lt;br /&gt;
* '''Calculation:''' (Number of tests passed post-migration / Total number of tests executed) x 100.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''5. Number of Bugs or Incidents Post-Migration:'''&lt;br /&gt;
&lt;br /&gt;
* '''Objective:''' Track the stability of the project post-migration by monitoring the number of bugs or incidents related to the migration.&lt;br /&gt;
* '''KPI:''' Number of bugs or incidents identified related to the Eventlet migration.&lt;br /&gt;
* '''Calculation:''' Total number of bugs/incidents opened during and after migration.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''6. Adoption of New Python Versions:'''&lt;br /&gt;
&lt;br /&gt;
* '''Objective:''' Verify if the teams are able to adopt new Python versions in the migrated deliverables.&lt;br /&gt;
* '''KPI:''' Percentage of deliverables using a recent version of Python (e.g., Python 3.12).&lt;br /&gt;
* '''Calculation:''' (Number of deliverables using the latest Python version / Total number of deliverables) x 100.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''7. Meeting Project Migration Deadlines:'''&lt;br /&gt;
&lt;br /&gt;
* '''Objective:''' Ensure that the migration project adheres to the initial schedule.&lt;br /&gt;
* '''KPI:''' Number of deadlines met compared to the migration plan.&lt;br /&gt;
* '''Calculation:''' (Number of deliverables migrated within the deadline / Total number of planned deliverables) x 100.&lt;br /&gt;
&lt;br /&gt;
== Useful Links ==&lt;br /&gt;
&lt;br /&gt;
* [https://eventlet.readthedocs.io/en/latest/asyncio/asyncio.html Eventlet's official migration guide]&lt;br /&gt;
* [https://review.opendev.org/q/topic:%22eventlet-removal%22 The gerrit patches related to the migration]&lt;br /&gt;
* [https://awaitlet.sqlalchemy.org/en/latest/ The Awaitlet documentation]&lt;br /&gt;
* [https://governance.openstack.org/tc/goals/proposed/remove-eventlet.html The related community goal (governance/TC)]&lt;br /&gt;
* [https://lists.openstack.org/archives/list/openstack-discuss@lists.openstack.org/thread/YO5CZDVAJ6QSF734ALWSGNOQDDAIOXKI/#RZFUTBFTUCSHKVA6SOCWWIXEH3QJHMYT The origin of this whole story]&lt;br /&gt;
* [https://github.com/eventlet/eventlet/issues/824 Getting the Eventlet maintenance under our umbrella]&lt;br /&gt;
&lt;br /&gt;
== FAQ ==&lt;br /&gt;
&lt;br /&gt;
'''Is this initiative is an official initiative?'''&lt;br /&gt;
&lt;br /&gt;
Yes, this initiative is supported by the [https://governance.openstack.org/tc/ OpenStack TC]. This initiative is an [https://governance.openstack.org/tc/goals/proposed/remove-eventlet.html official OpenStack community goal]. This goal [https://review.opendev.org/c/openstack/governance/+/902585 has been accepted months ago] and [https://review.opendev.org/c/openstack/governance/+/931254 is now going to be a selected goal].&lt;br /&gt;
&lt;br /&gt;
'''what will happen to eventlet once the openstack migration is complete?'''&lt;br /&gt;
&lt;br /&gt;
Eventlet will be simply abandoned officially. No more maintenance will be given to the official Eventlet repository. The official Eventlet repository will be archived.&lt;br /&gt;
&lt;br /&gt;
== Map of the Impacted Deliverables ==&lt;br /&gt;
&lt;br /&gt;
Here is a curated list of the Openstack deliverables currently (as of 2024 Oct. 4th) using Eventlet and which must be migrated:&lt;br /&gt;
&lt;br /&gt;
* https://opendev.org/openstack/aodh/&lt;br /&gt;
* https://opendev.org/openstack/auto-scaling-sig/&lt;br /&gt;
* https://opendev.org/openstack/barbican/&lt;br /&gt;
* https://opendev.org/openstack/blazar/&lt;br /&gt;
* https://opendev.org/openstack/ceilometer/&lt;br /&gt;
* https://opendev.org/openstack/charm-manila/&lt;br /&gt;
* https://opendev.org/openstack/charm-manila-ganesha/&lt;br /&gt;
* https://opendev.org/openstack/cinder/&lt;br /&gt;
* https://opendev.org/openstack/cinder-specs/&lt;br /&gt;
* https://opendev.org/openstack/cloudkitty/&lt;br /&gt;
* https://opendev.org/openstack/cloudkitty-specs/&lt;br /&gt;
* https://opendev.org/openstack/cyborg/&lt;br /&gt;
* https://opendev.org/openstack/designate/&lt;br /&gt;
* https://opendev.org/openstack/designate-specs/&lt;br /&gt;
* https://opendev.org/openstack/devstack/&lt;br /&gt;
* https://opendev.org/openstack/election/&lt;br /&gt;
* https://opendev.org/openstack/etcd3gw/&lt;br /&gt;
* https://opendev.org/openstack/futurist/&lt;br /&gt;
* https://opendev.org/openstack/glance/&lt;br /&gt;
* https://opendev.org/openstack/glance-specs/&lt;br /&gt;
* https://opendev.org/openstack/glance_store/&lt;br /&gt;
* https://opendev.org/openstack/governance/&lt;br /&gt;
* https://opendev.org/openstack/hacking/&lt;br /&gt;
* https://opendev.org/openstack/heat/&lt;br /&gt;
* https://opendev.org/openstack/heat-specs/&lt;br /&gt;
* https://opendev.org/openstack/heat-templates/&lt;br /&gt;
* https://opendev.org/openstack/ironic/&lt;br /&gt;
* https://opendev.org/openstack/ironic-inspector/&lt;br /&gt;
* https://opendev.org/openstack/ironic-python-agent/&lt;br /&gt;
* https://opendev.org/openstack/ironic-specs/&lt;br /&gt;
* https://opendev.org/openstack/keystone/&lt;br /&gt;
* https://opendev.org/openstack/keystone-specs/&lt;br /&gt;
* https://opendev.org/openstack/keystonemiddleware/&lt;br /&gt;
* https://opendev.org/openstack/kolla/&lt;br /&gt;
* https://opendev.org/openstack/kolla-ansible/&lt;br /&gt;
* https://opendev.org/openstack/magnum/&lt;br /&gt;
* https://opendev.org/openstack/manila/&lt;br /&gt;
* https://opendev.org/openstack/masakari/&lt;br /&gt;
* https://opendev.org/openstack/masakari-monitors/&lt;br /&gt;
* https://opendev.org/openstack/mistral/&lt;br /&gt;
* https://opendev.org/openstack/mistral-extra/&lt;br /&gt;
* https://opendev.org/openstack/mistral-lib/&lt;br /&gt;
* https://opendev.org/openstack/monasca-agent/&lt;br /&gt;
* https://opendev.org/openstack/monasca-api/&lt;br /&gt;
* https://opendev.org/openstack/monasca-events-api/&lt;br /&gt;
* https://opendev.org/openstack/networking-baremetal/&lt;br /&gt;
* https://opendev.org/openstack/networking-generic-switch/&lt;br /&gt;
* https://opendev.org/openstack/networking-sfc/&lt;br /&gt;
* https://opendev.org/openstack/neutron/&lt;br /&gt;
* https://opendev.org/openstack/neutron-dynamic-routing/&lt;br /&gt;
* https://opendev.org/openstack/neutron-fwaas/&lt;br /&gt;
* https://opendev.org/openstack/neutron-lib/&lt;br /&gt;
* https://opendev.org/openstack/neutron-specs/&lt;br /&gt;
* https://opendev.org/openstack/neutron-tempest-plugin/&lt;br /&gt;
* https://opendev.org/openstack/neutron-vpnaas/&lt;br /&gt;
* https://opendev.org/openstack/nova/&lt;br /&gt;
* https://opendev.org/openstack/nova-specs/&lt;br /&gt;
* https://opendev.org/openstack/octavia/&lt;br /&gt;
* https://opendev.org/openstack/octavia-lib/&lt;br /&gt;
* https://opendev.org/openstack/octavia-tempest-plugin/&lt;br /&gt;
* https://opendev.org/openstack/openstack-ansible-ops/&lt;br /&gt;
* https://opendev.org/openstack/openstack-ansible-os_neutron/&lt;br /&gt;
* https://opendev.org/openstack/openstack-helm/&lt;br /&gt;
* https://opendev.org/openstack/openstack-manuals/&lt;br /&gt;
* https://opendev.org/openstack/os-brick/&lt;br /&gt;
* https://opendev.org/openstack/os-ken/&lt;br /&gt;
* https://opendev.org/openstack/os-vif/&lt;br /&gt;
* https://opendev.org/openstack/oslo-specs/&lt;br /&gt;
* https://opendev.org/openstack/oslo.cache/&lt;br /&gt;
* https://opendev.org/openstack/oslo.concurrency/&lt;br /&gt;
* https://opendev.org/openstack/oslo.config/&lt;br /&gt;
* https://opendev.org/openstack/oslo.db/&lt;br /&gt;
* https://opendev.org/openstack/oslo.log/&lt;br /&gt;
* https://opendev.org/openstack/oslo.messaging/&lt;br /&gt;
* https://opendev.org/openstack/oslo.privsep/&lt;br /&gt;
* https://opendev.org/openstack/oslo.reports/&lt;br /&gt;
* https://opendev.org/openstack/oslo.rootwrap/&lt;br /&gt;
* https://opendev.org/openstack/oslo.service/&lt;br /&gt;
* https://opendev.org/openstack/oslo.utils/&lt;br /&gt;
* https://opendev.org/openstack/oslo.versionedobjects/&lt;br /&gt;
* https://opendev.org/openstack/oslo.vmware/&lt;br /&gt;
* https://opendev.org/openstack/osops/&lt;br /&gt;
* https://opendev.org/openstack/ossa/&lt;br /&gt;
* https://opendev.org/openstack/ovn-bgp-agent/&lt;br /&gt;
* https://opendev.org/openstack/ovsdbapp/&lt;br /&gt;
* https://opendev.org/openstack/performance-docs/&lt;br /&gt;
* https://opendev.org/openstack/project-config/&lt;br /&gt;
* https://opendev.org/openstack/puppet-barbican/&lt;br /&gt;
* https://opendev.org/openstack/puppet-ceilometer/&lt;br /&gt;
* https://opendev.org/openstack/puppet-keystone/&lt;br /&gt;
* https://opendev.org/openstack/puppet-manila/&lt;br /&gt;
* https://opendev.org/openstack/puppet-neutron/&lt;br /&gt;
* https://opendev.org/openstack/puppet-openstacklib/&lt;br /&gt;
* https://opendev.org/openstack/python-cinderclient/&lt;br /&gt;
* https://opendev.org/openstack/python-glanceclient/&lt;br /&gt;
* https://opendev.org/openstack/python-ironic-inspector-client/&lt;br /&gt;
* https://opendev.org/openstack/python-ironicclient/&lt;br /&gt;
* https://opendev.org/openstack/python-keystoneclient/&lt;br /&gt;
* https://opendev.org/openstack/python-manilaclient/&lt;br /&gt;
* https://opendev.org/openstack/python-mistralclient/&lt;br /&gt;
* https://opendev.org/openstack/python-octaviaclient/&lt;br /&gt;
* https://opendev.org/openstack/python-swiftclient/&lt;br /&gt;
* https://opendev.org/openstack/python-troveclient/&lt;br /&gt;
* https://opendev.org/openstack/python-zaqarclient/&lt;br /&gt;
* https://opendev.org/openstack/rally/&lt;br /&gt;
* https://opendev.org/openstack/releases/&lt;br /&gt;
* https://opendev.org/openstack/requirements/&lt;br /&gt;
* https://opendev.org/openstack/rpm-packaging/&lt;br /&gt;
* https://opendev.org/openstack/security-doc/&lt;br /&gt;
* https://opendev.org/openstack/storlets/&lt;br /&gt;
* https://opendev.org/openstack/swift/&lt;br /&gt;
* https://opendev.org/openstack/swift-bench/&lt;br /&gt;
* https://opendev.org/openstack/tacker/&lt;br /&gt;
* https://opendev.org/openstack/taskflow/&lt;br /&gt;
* https://opendev.org/openstack/telemetry-specs/&lt;br /&gt;
* https://opendev.org/openstack/tooz/&lt;br /&gt;
* https://opendev.org/openstack/trove/&lt;br /&gt;
* https://opendev.org/openstack/upstream-institute-virtual-environment/&lt;br /&gt;
* https://opendev.org/openstack/venus/&lt;br /&gt;
* https://opendev.org/openstack/vitrage/&lt;br /&gt;
* https://opendev.org/openstack/watcher/&lt;br /&gt;
* https://opendev.org/openstack/zun/&lt;/div&gt;</summary>
		<author><name>Mike Bayer</name></author>	</entry>

	<entry>
		<id>https://wiki.openstack.org/w/index.php?title=Eventlet-removal&amp;diff=185431</id>
		<title>Eventlet-removal</title>
		<link rel="alternate" type="text/html" href="https://wiki.openstack.org/w/index.php?title=Eventlet-removal&amp;diff=185431"/>
				<updated>2024-10-07T22:47:23Z</updated>
		
		<summary type="html">&lt;p&gt;Mike Bayer: /* 5. Use of `eventlet.wsgi` */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Why Eventlet Needs to Be Removed ==&lt;br /&gt;
&lt;br /&gt;
Eventlet, though once popular for concurrent execution using &amp;quot;greenthreads,&amp;quot; now presents several limitations and issues that make it an outdated solution for modern projects like OpenStack:&lt;br /&gt;
&lt;br /&gt;
1. '''Incompatibility with Recent Versions of Python:'''  &lt;br /&gt;
&lt;br /&gt;
Eventlet struggles to keep up with the evolution of Python, particularly with recent versions (such as Python 3.12 and beyond). Some of its internal functionalities, such as &amp;quot;monkey patching&amp;quot; standard libraries, no longer work correctly or cause unexpected behavior. This limits projects from adopting the latest improvements in Python, including critical security patches.&lt;br /&gt;
&lt;br /&gt;
2. '''Inefficient Resource and Thread Management:'''  &lt;br /&gt;
&lt;br /&gt;
The greenthreads used by Eventlet can lead to excessive memory consumption and poor resource management, especially under heavy loads or in large-scale applications. Given that OpenStack operates in a distributed and highly parallel environment, this can become a significant bottleneck for overall performance.&lt;br /&gt;
&lt;br /&gt;
3. '''Maintenance and Security Issues:'''  &lt;br /&gt;
&lt;br /&gt;
Eventlet suffers from a lack of active maintenance and does not receive regular updates to address security vulnerabilities and compatibility issues. This exposes OpenStack to potential security risks if flaws in Eventlet are not addressed promptly.&lt;br /&gt;
&lt;br /&gt;
4. '''Outdated Approach to Concurrency Programming:'''  &lt;br /&gt;
&lt;br /&gt;
With the emergence of more modern solutions like `asyncio` or `concurrent.futures`, which are natively integrated into Python, using Eventlet feels outdated. These newer libraries offer better concurrency management, improved performance, and tighter integration with the current Python ecosystem.&lt;br /&gt;
&lt;br /&gt;
5. '''Inability to Adopt New Linux Distributions:'''  &lt;br /&gt;
&lt;br /&gt;
Many modern Linux distributions, such as Ubuntu, RHEL, and others, now ship with newer versions of Python that are not supported by Eventlet. Without migrating away from Eventlet, OpenStack will face challenges in adopting these newer distributions. This will limit the ability to stay current with the latest infrastructure improvements and security enhancements provided by modern operating systems.&lt;br /&gt;
&lt;br /&gt;
== Potential Impacts if the Migration is Not Completed ==&lt;br /&gt;
&lt;br /&gt;
Failure to migrate away from Eventlet could have significant consequences for OpenStack-based projects:&lt;br /&gt;
&lt;br /&gt;
1. '''Blockage of Python Updates:'''  &lt;br /&gt;
&lt;br /&gt;
Continuing to use Eventlet may cause OpenStack to be stuck on older versions of Python. This prevents the adoption of new features, performance improvements, and —most importantly—security patches offered by newer versions of Python.&lt;br /&gt;
&lt;br /&gt;
2. '''Security Vulnerabilities Risk:'''  &lt;br /&gt;
&lt;br /&gt;
Given Eventlet's lack of maintenance, unresolved security flaws could leave OpenStack systems vulnerable to attacks. Failing to migrate to modern solutions exposes the infrastructure to major security risks.&lt;br /&gt;
&lt;br /&gt;
3. '''Degradation of Performance and Scalability:'''  &lt;br /&gt;
&lt;br /&gt;
OpenStack handles large workloads in distributed environments, and Eventlet’s inefficient resource management can cause slowdowns and failures. Without migration, this could limit OpenStack's ability to scale smoothly and handle increasingly larger data volumes.&lt;br /&gt;
&lt;br /&gt;
4. '''Inability to Adopt New Linux Distributions:'''  &lt;br /&gt;
&lt;br /&gt;
If the migration from Eventlet is not completed, OpenStack will struggle to support modern Linux distributions that come with newer versions of Python. Distributions like Ubuntu and RHEL now ship with versions of Python that are incompatible with Eventlet. This will hinder the ability to upgrade to these distributions, which are critical for staying up-to-date with the latest security fixes and performance improvements.&lt;br /&gt;
&lt;br /&gt;
5. '''Increased Maintenance Costs:'''  &lt;br /&gt;
&lt;br /&gt;
Continuing to rely on an outdated technology will increase maintenance costs as it becomes harder to find fixes or workarounds for technical issues. This will lead to a growing technical debt that will be difficult to manage in the long term.&lt;br /&gt;
&lt;br /&gt;
To ensure the longevity, stability, and security of OpenStack, it is crucial to migrate to modern solutions like `asyncio` or `concurrent.futures`. This will not only overcome Eventlet's current limitations but also ensure that OpenStack remains capable of meeting performance, security, and scalability demands. Moreover, migrating is essential for supporting the latest Linux distributions like Ubuntu and RHEL, which include newer versions of Python that are not compatible with Eventlet.&lt;br /&gt;
&lt;br /&gt;
The following sections aims to guide you during your migration journey.&lt;br /&gt;
&lt;br /&gt;
== Different Kind of Eventlet Usages ==&lt;br /&gt;
&lt;br /&gt;
In OpenStack, there exist two kinds of deliverables based on Eventlet:&lt;br /&gt;
* deliverables for whom Eventlet is optional and can be disabled&lt;br /&gt;
* deliverables that cannot live without Eventlet&lt;br /&gt;
&lt;br /&gt;
Both deliverables won't be migrated the same way.&lt;br /&gt;
&lt;br /&gt;
For the first scenario - optional Eventlet - we suggest completely disabling Eventlet first, and then from there, start migrating your deliverables. This kind of deliverables often allows switching from one kind of execution model to another, e.g., Eventlet or futurist. We encourage migrating all your occurrences over the models non-based on Eventlet.&lt;br /&gt;
&lt;br /&gt;
==== Steps to Disable Eventlet in Optional Deliverables ====&lt;br /&gt;
&lt;br /&gt;
===== 1. Analyze Dependencies and Usage of Eventlet =====&lt;br /&gt;
&lt;br /&gt;
* Identify all parts of the code that depend on Eventlet. This includes direct imports, the use of greenthreads, and specific methods such as `monkey_patch()`, `Timeout`, or `eventlet.wsgi`.&lt;br /&gt;
* Perform a search across the codebase to locate these Eventlet elements. Use search tools like `grep`, or more specialized tools like `beagle`, to get an overview of Eventlet occurrences.&lt;br /&gt;
&lt;br /&gt;
===== 2. Selective Eventlet Deactivation =====&lt;br /&gt;
&lt;br /&gt;
* In some cases, it’s possible to disable Eventlet via configuration settings. Check if your project or framework offers this capability.&lt;br /&gt;
* If configuration doesn’t explicitly allow for disabling, start by commenting out or removing the Eventlet import lines and test the execution of the code in a local or test environment.&lt;br /&gt;
* Use native alternatives to replace functionalities dependent on Eventlet. For instance, replace greenthreads with standard threads or asynchronous processes using `asyncio`.&lt;br /&gt;
&lt;br /&gt;
===== 3. Progressive Refactoring of Eventlet Entry Points =====&lt;br /&gt;
&lt;br /&gt;
* '''Substitute `monkey_patch()`''' : Replace all occurrences of `eventlet.monkey_patch()` with explicitly defined alternatives (e.g., use `asyncio.sleep` for delays or standard libraries for threads and sockets).&lt;br /&gt;
* '''Timeout Management''' : Replace the usage of `eventlet.timeout.Timeout` with `asyncio.wait_for()` or equivalent exception handlers.&lt;br /&gt;
* '''Greenthreads and Thread Pools''' : Replace calls to `eventlet.spawn()` or `GreenPool()` with `asyncio.create_task()` or `concurrent.futures.ThreadPoolExecutor()` for concurrency management.&lt;br /&gt;
&lt;br /&gt;
===== 4. Unit and Functional Testing =====&lt;br /&gt;
&lt;br /&gt;
* After disabling Eventlet and refactoring critical elements, run existing unit and functional tests to ensure the code remains stable.&lt;br /&gt;
* Use `mock` to simulate asynchronous behaviors in your tests (e.g., patching `asyncio` instead of Eventlet).&lt;br /&gt;
&lt;br /&gt;
===== 5. Performance and Stability Validation =====&lt;br /&gt;
&lt;br /&gt;
* Assess the application's performance after disabling Eventlet, particularly in high-load scenarios. The goal is to ensure that using `asyncio` or standard threads provides equivalent or better performance.&lt;br /&gt;
* Also, monitor memory consumption and exception handling, as Eventlet’s greenthread implementation may differ from standard threads or `asyncio`.&lt;br /&gt;
&lt;br /&gt;
===== 6. Document the Changes =====&lt;br /&gt;
&lt;br /&gt;
* Once Eventlet is successfully disabled, clearly document the changes made and the new dependency requirements (e.g., `asyncio` or `concurrent.futures`).&lt;br /&gt;
* Outline the new execution models so that other developers are aware of the modifications and the new behavior of the code.&lt;br /&gt;
&lt;br /&gt;
===== 7. Complete Migration Phase =====&lt;br /&gt;
&lt;br /&gt;
* Once Eventlet is disabled and alternatives are in place, you can begin the full migration to a modern solution like `asyncio` by more extensively refactoring code that still depends on the old Eventlet-based architecture.&lt;br /&gt;
&lt;br /&gt;
The sections below aims to give you a way to identify the Eventlet patterns, and aims to give refactoring guidance.&lt;br /&gt;
&lt;br /&gt;
== Save the Date ==&lt;br /&gt;
&lt;br /&gt;
List of the coming events related to the Eventlet removal that could interest the reader:&lt;br /&gt;
&lt;br /&gt;
* 2024-10-21: PTG cross team session - The goal is to give you an overview of the Eventlet removal intiative and to present you the pop-up team;&lt;br /&gt;
* 2024-10-23: PTG cross team session - Open discussion and Q&amp;amp;A session.&lt;br /&gt;
&lt;br /&gt;
== Migration's Milestones ==&lt;br /&gt;
&lt;br /&gt;
* Adapting common libraries (Oslo, etc...) - e.g: introducing an Async Engine Facade to oslo.db, adapting oslo.service to not relying on Eventlet;&lt;br /&gt;
* Services migration - removing Eventlet usages from OpenStack services;&lt;br /&gt;
* Common libraries migration - common libraries depends on Eventlet because at a higher level - at the services level - Eventlet is required, we cannot remove this kind of Eventlet occurence from the common libraries while services are not fully migrated;&lt;br /&gt;
* Removing Eventlet from our global requirements;&lt;br /&gt;
* Abandoning Eventlet.&lt;br /&gt;
&lt;br /&gt;
== Targeted Releases ==&lt;br /&gt;
&lt;br /&gt;
Fully removing Eventlet from OpenStack will surely take several series, we target, at least, the H or I series.&lt;br /&gt;
&lt;br /&gt;
== Want to Discuss? ==&lt;br /&gt;
&lt;br /&gt;
=== #eventlet-removal ===&lt;br /&gt;
&lt;br /&gt;
Follow the '''#eventlet-removal''' tag. It will be used everywhere to tag all the related things to this topic:&lt;br /&gt;
&lt;br /&gt;
* [https://review.opendev.org/q/topic:%22eventlet-removal%22 gerrit patches]&lt;br /&gt;
* mailing list threads&lt;br /&gt;
* OpenStack irc channel&lt;br /&gt;
* RedHat slack channel&lt;br /&gt;
* ...&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
If you want to discuss about the Eventlet removal then '''dial #eventlet-removal'''. &lt;br /&gt;
You can join one of these channels or [https://review.opendev.org/q/topic:%22eventlet-removal%22 submit a new patch with a topic set to #eventlet-removal] or open a new thread with the #eventlet-removal tag.&lt;br /&gt;
&lt;br /&gt;
=== The Pop-up team ===&lt;br /&gt;
&lt;br /&gt;
A Pop-up team is constituted to manage this initiative, do not hesitate to contact this team to get feedback or answers.&lt;br /&gt;
&lt;br /&gt;
== Getting Started ==&lt;br /&gt;
&lt;br /&gt;
=== Available Alternatives to Eventlet ===&lt;br /&gt;
&lt;br /&gt;
We identified the following alternatives to replace Eventlet:&lt;br /&gt;
&lt;br /&gt;
* [https://docs.python.org/3/library/asyncio.html Asyncio];&lt;br /&gt;
* [https://awaitlet.sqlalchemy.org/en/latest/ Awaitlet];&lt;br /&gt;
* [https://docs.aiohttp.org/en/stable/ Aiohttp];&lt;br /&gt;
* [https://www.uvicorn.org/ Uvicorn];&lt;br /&gt;
* ...&lt;br /&gt;
&lt;br /&gt;
Do not hesitate to take a look to the following resources:&lt;br /&gt;
&lt;br /&gt;
* [https://eventlet.readthedocs.io/en/latest/asyncio/asyncio.html#alternatives-tips alternatives officially supported by Eventlet];&lt;br /&gt;
* [https://governance.openstack.org/tc/goals/proposed/remove-eventlet.html#the-available-alternatives-to-eventlet the alternatives proposed into the OpenStack community goal];&lt;br /&gt;
* [https://github.com/timofurrer/awesome-asyncio a curated list of Python asyncio frameworks, libraries, software and resources].&lt;br /&gt;
&lt;br /&gt;
=== How to Analyze Your Deliverables ===&lt;br /&gt;
&lt;br /&gt;
Teams are invited to analyze their deliverables to see their kind of Eventlet usages, here is an example with Oslo:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ pip install beagle&lt;br /&gt;
$ beagle search --ignore-comments -f link --repo-pattern &amp;quot;openstack/oslo*&amp;quot; 'eventlet'&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The following sections aims to give you guidance to identify your usages and use cases.&lt;br /&gt;
&lt;br /&gt;
=== Identifying Common Pattern ===&lt;br /&gt;
&lt;br /&gt;
This section aims to help you identify the various patterns that you can find on your deliverables.&lt;br /&gt;
&lt;br /&gt;
==== 1. Importing Eventlet ====&lt;br /&gt;
&lt;br /&gt;
Eventlet is imported in two main ways:&lt;br /&gt;
&lt;br /&gt;
* '''Direct import:'''&lt;br /&gt;
  &amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  import eventlet&lt;br /&gt;
  &amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
  &lt;br /&gt;
* '''Specific import of Eventlet submodules:'''&lt;br /&gt;
  &amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  from eventlet.timeout import Timeout&lt;br /&gt;
  from eventlet import greenthread&lt;br /&gt;
  from eventlet import hubs&lt;br /&gt;
  &amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 2. Use of `monkey_patch()` ====&lt;br /&gt;
&lt;br /&gt;
The use of the `eventlet.monkey_patch()` method is frequent and aims to replace some standard libraries (such as threads or sockets) with their asynchronous equivalents compatible with Eventlet's model.&lt;br /&gt;
&lt;br /&gt;
* '''Explicit calls to `monkey_patch`:'''&lt;br /&gt;
  &amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  eventlet.monkey_patch()&lt;br /&gt;
  eventlet.monkey_patch(all=True)&lt;br /&gt;
  eventlet.monkey_patch(time=True)&lt;br /&gt;
  eventlet.patcher.monkey_patch(all=False, socket=True)&lt;br /&gt;
  &amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 3. Timeout and Deadline Management ====&lt;br /&gt;
&lt;br /&gt;
The use of `eventlet.timeout.Timeout` is common to manage waiting periods or deadlines for blocking operations.&lt;br /&gt;
&lt;br /&gt;
* '''Use of `Timeout` in a `with` or `try-except` block:'''&lt;br /&gt;
  &amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  with eventlet.Timeout(timeout, True):&lt;br /&gt;
      # Do something&lt;br /&gt;
  except eventlet.timeout.Timeout:&lt;br /&gt;
      # Handle the timeout&lt;br /&gt;
  &amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 4. Green Threads and GreenPool ====&lt;br /&gt;
&lt;br /&gt;
Eventlet provides &amp;quot;greenthread&amp;quot; versions of classic threads as well as thread pools adapted to its asynchronous model.&lt;br /&gt;
&lt;br /&gt;
* '''Use of GreenThread and GreenPool:'''&lt;br /&gt;
  &amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  self.pool = eventlet.GreenPool(threads)&lt;br /&gt;
  eventlet.spawn(func)&lt;br /&gt;
  eventlet.spawn_n(func)&lt;br /&gt;
  &amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 5. Use of `eventlet.wsgi` ====&lt;br /&gt;
&lt;br /&gt;
Eventlet is sometimes used to handle WSGI (Web Server Gateway Interface) servers, particularly via `eventlet.wsgi`.&lt;br /&gt;
&lt;br /&gt;
* '''Example of use with `eventlet.wsgi`:'''&lt;br /&gt;
  &amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  import eventlet.wsgi&lt;br /&gt;
  eventlet.wsgi.server(socket, application, custom_pool=self.pool)&lt;br /&gt;
  &amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 6. Deferred Tasks and Scheduling ====&lt;br /&gt;
&lt;br /&gt;
The use of `eventlet.spawn_after()` to execute tasks after a delay is also a recurring pattern.&lt;br /&gt;
&lt;br /&gt;
* '''Example of `spawn_after`:'''&lt;br /&gt;
  &amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  eventlet.spawn_after(3.5, update_db)&lt;br /&gt;
  &amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 7. Use in Tests with `mock` ====&lt;br /&gt;
&lt;br /&gt;
In tests, Eventlet is sometimes patched or mocked to simulate its behavior or to control asynchronous tests.&lt;br /&gt;
&lt;br /&gt;
* '''Examples of use in tests:'''&lt;br /&gt;
  &amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  @mock.patch.object(eventlet, 'monkey_patch')&lt;br /&gt;
  @mock.patch('eventlet.spawn')&lt;br /&gt;
  &amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 8. Presence in Configuration Files and Dependencies ====&lt;br /&gt;
&lt;br /&gt;
Eventlet is explicitly mentioned in configuration files as a dependency in `requirements.txt` and `setup.cfg` files.&lt;br /&gt;
&lt;br /&gt;
* '''Dependencies:'''&lt;br /&gt;
  &amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
  eventlet&amp;gt;=0.18.2&lt;br /&gt;
  eventlet!=0.20.1,&amp;gt;=0.18.2 # MIT&lt;br /&gt;
  &amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 9. References in Documentation ====&lt;br /&gt;
&lt;br /&gt;
Some parts of the documentation or code comments refer to the use of Eventlet, either explaining why it is used or mentioning alternatives.&lt;br /&gt;
&lt;br /&gt;
* '''Example in documentation:'''&lt;br /&gt;
  &amp;lt;syntaxhighlight lang=&amp;quot;rst&amp;quot;&amp;gt;&lt;br /&gt;
  If eventlet is used, then this engine will not block other threads&lt;br /&gt;
  &amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Migration Plan ===&lt;br /&gt;
&lt;br /&gt;
Here’s a proposed migration plan to replace Eventlet with a more modern and efficient solution like asyncio or concurrent.futures, while considering each of the identified patterns.&lt;br /&gt;
This plan should be used in addition of the official migration guide provided into the Eventlet documentation:&lt;br /&gt;
&lt;br /&gt;
https://eventlet.readthedocs.io/en/latest/asyncio/asyncio.html&lt;br /&gt;
&lt;br /&gt;
==== 1. Direct Import of Eventlet ====&lt;br /&gt;
&lt;br /&gt;
'''Migration:'''&lt;br /&gt;
* Replace `import eventlet` and specific Eventlet submodules with asyncio, or other libraries such as `concurrent.futures`.&lt;br /&gt;
&lt;br /&gt;
'''Steps:'''&lt;br /&gt;
* Review the code for all occurrences of `eventlet` imports.&lt;br /&gt;
* Replace with equivalent asyncio or standard Python concurrency libraries.&lt;br /&gt;
&lt;br /&gt;
'''Example:'''&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
# Before&lt;br /&gt;
import eventlet&lt;br /&gt;
&lt;br /&gt;
# After&lt;br /&gt;
import asyncio&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 2. `monkey_patch()` ====&lt;br /&gt;
&lt;br /&gt;
'''Migration:'''&lt;br /&gt;
* Monkey-patching should be avoided because it changes standard library behavior, which can cause unexpected side effects.&lt;br /&gt;
* Refactor the code to use explicit asyncio or threading-based code.&lt;br /&gt;
&lt;br /&gt;
'''Steps:'''&lt;br /&gt;
* Identify all places where `eventlet.monkey_patch()` is used.&lt;br /&gt;
* Replace patched calls (e.g., `time.sleep()`, `threading`) with non-blocking alternatives (e.g., `asyncio.sleep()`, `concurrent.futures.ThreadPoolExecutor`).&lt;br /&gt;
&lt;br /&gt;
'''Example:'''&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
# Before&lt;br /&gt;
eventlet.monkey_patch()&lt;br /&gt;
&lt;br /&gt;
# After&lt;br /&gt;
# Use asyncio or threading where applicable without monkey-patching.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 3. Timeout and Deadline Management ====&lt;br /&gt;
&lt;br /&gt;
'''Migration:'''&lt;br /&gt;
* Use `asyncio.TimeoutError` or `asyncio.wait_for()` to handle timeout operations.&lt;br /&gt;
&lt;br /&gt;
'''Steps:'''&lt;br /&gt;
* Replace `eventlet.Timeout()` with `asyncio.wait_for()`.&lt;br /&gt;
* Ensure that any blocking operations are handled asynchronously.&lt;br /&gt;
&lt;br /&gt;
'''Example:'''&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
# Before&lt;br /&gt;
with eventlet.Timeout(timeout, True):&lt;br /&gt;
    # Do something&lt;br /&gt;
&lt;br /&gt;
# After&lt;br /&gt;
async def main():&lt;br /&gt;
    try:&lt;br /&gt;
        await asyncio.wait_for(task, timeout)&lt;br /&gt;
    except asyncio.TimeoutError:&lt;br /&gt;
        # Handle timeout&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 4. Green Threads and GreenPool ====&lt;br /&gt;
&lt;br /&gt;
'''Migration:'''&lt;br /&gt;
* Replace Eventlet's green threads with `asyncio` tasks or `concurrent.futures` for multithreading or multiprocessing.&lt;br /&gt;
&lt;br /&gt;
'''Steps:'''&lt;br /&gt;
* Replace `eventlet.GreenPool` with `asyncio.gather()` or `concurrent.futures.ThreadPoolExecutor`.&lt;br /&gt;
* Replace `eventlet.spawn()` and `eventlet.spawn_n()` with `asyncio.create_task()` or `executor.submit()` from concurrent.futures.&lt;br /&gt;
&lt;br /&gt;
'''Example:'''&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
# Before&lt;br /&gt;
pool = eventlet.GreenPool(threads)&lt;br /&gt;
pool.spawn(func)&lt;br /&gt;
&lt;br /&gt;
# After&lt;br /&gt;
async def func():&lt;br /&gt;
    # Function implementation&lt;br /&gt;
&lt;br /&gt;
asyncio.create_task(func())&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 5. Use of `eventlet.wsgi` ====&lt;br /&gt;
&lt;br /&gt;
'''Migration:'''&lt;br /&gt;
* Replace Eventlet's WSGI handling with a modern asynchronous web framework, such as `aiohttp`, `FastAPI`, or `uvicorn` (requires all code be ported to asyncio)&lt;br /&gt;
&lt;br /&gt;
* Replace Eventlet's WSGI handling with a plain wsgi framework such as `gunicorn`  (does not require porting to asyncio)&lt;br /&gt;
&lt;br /&gt;
zzzeek:   why not a plain WSGI framework?    There is no need for code that transparently uses eventlet for wsgi to explicitly use asyncio and convert all functions to use `async`; it should use standard wsgi patterns directly which can be run in a threaded container like mod_wsgi (we use mod_wsgi right now for web services).   see masakari for example&lt;br /&gt;
&lt;br /&gt;
'''Steps:'''&lt;br /&gt;
* Replace `eventlet.wsgi` server code with an asyncio-compatible web server like `uvicorn`.&lt;br /&gt;
* Update the application to be compatible with asynchronous HTTP handling.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
zzzeek:  How can the above be achieved for projects based on oslo.service?    will an alternative to oslo.service be introduced or will we modify oslo.service to include traditional wsgi support not based on eventlet ?    &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Example:'''&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
# Before&lt;br /&gt;
import eventlet.wsgi&lt;br /&gt;
eventlet.wsgi.server(socket, application, custom_pool=self.pool)&lt;br /&gt;
&lt;br /&gt;
# After&lt;br /&gt;
# Use an asynchronous web server like Uvicorn&lt;br /&gt;
import uvicorn&lt;br /&gt;
uvicorn.run(&amp;quot;module_name:app&amp;quot;, host=&amp;quot;0.0.0.0&amp;quot;, port=8000)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 6. Deferred Tasks and Scheduling (`spawn_after`) ====&lt;br /&gt;
&lt;br /&gt;
'''Migration:'''&lt;br /&gt;
* Use `asyncio.sleep()` or `asyncio.call_later()` for scheduling delayed tasks.&lt;br /&gt;
&lt;br /&gt;
'''Steps:'''&lt;br /&gt;
* Replace `eventlet.spawn_after()` with `asyncio.create_task()` combined with `asyncio.sleep()`.&lt;br /&gt;
&lt;br /&gt;
'''Example:'''&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
# Before&lt;br /&gt;
eventlet.spawn_after(3.5, update_db)&lt;br /&gt;
&lt;br /&gt;
# After&lt;br /&gt;
async def update_db():&lt;br /&gt;
    await asyncio.sleep(3.5)&lt;br /&gt;
    # Perform database update&lt;br /&gt;
&lt;br /&gt;
asyncio.create_task(update_db())&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 7. Use in Tests with `mock` ====&lt;br /&gt;
&lt;br /&gt;
'''Migration:'''&lt;br /&gt;
* Ensure that tests are updated to mock the corresponding asyncio or threading features instead of Eventlet’s patched functions.&lt;br /&gt;
&lt;br /&gt;
'''Steps:'''&lt;br /&gt;
* Replace Eventlet mock patches with mocks for asyncio or threading functions.&lt;br /&gt;
* Use `unittest.mock` or `pytest` to mock async tasks.&lt;br /&gt;
&lt;br /&gt;
'''Example:'''&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
# Before&lt;br /&gt;
@mock.patch.object(eventlet, 'monkey_patch')&lt;br /&gt;
&lt;br /&gt;
# After&lt;br /&gt;
@mock.patch('asyncio.sleep')&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 8. Presence in Configuration Files and Dependencies ====&lt;br /&gt;
&lt;br /&gt;
'''Migration:'''&lt;br /&gt;
* Remove Eventlet from `requirements.txt` and `setup.cfg` files.&lt;br /&gt;
* Ensure that new dependencies (such as `asyncio` or `uvicorn`) are added where needed.&lt;br /&gt;
&lt;br /&gt;
'''Steps:'''&lt;br /&gt;
* Remove all references to Eventlet in the configuration files.&lt;br /&gt;
* Replace them with modern asynchronous libraries.&lt;br /&gt;
&lt;br /&gt;
'''Example:'''&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
# Before&lt;br /&gt;
eventlet&amp;gt;=0.18.2&lt;br /&gt;
&lt;br /&gt;
# After&lt;br /&gt;
# Remove Eventlet, add necessary asyncio libraries&lt;br /&gt;
aiohttp&amp;gt;=3.7.0&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 9. References in Documentation ====&lt;br /&gt;
&lt;br /&gt;
'''Migration:'''&lt;br /&gt;
* Update all documentation and code comments to reflect the switch from Eventlet to asyncio or other modern libraries.&lt;br /&gt;
&lt;br /&gt;
'''Steps:'''&lt;br /&gt;
* Review the documentation for all mentions of Eventlet.&lt;br /&gt;
* Update examples and explanations to refer to the new concurrency model.&lt;br /&gt;
&lt;br /&gt;
'''Example:'''&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;rst&amp;quot;&amp;gt;&lt;br /&gt;
# Before&lt;br /&gt;
If eventlet is used, the engine will not block other threads&lt;br /&gt;
&lt;br /&gt;
# After&lt;br /&gt;
If asyncio is used, tasks will run concurrently without blocking each other.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Migration Plan Summary ===&lt;br /&gt;
&lt;br /&gt;
1. '''Preparation:'''&lt;br /&gt;
&lt;br /&gt;
* Audit code for Eventlet usage.&lt;br /&gt;
* Identify areas where direct migration (e.g., replacing `monkey_patch()`) will be challenging.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
2. '''Refactor Core Logic:'''&lt;br /&gt;
&lt;br /&gt;
* Refactor blocking I/O operations with asyncio’s non-blocking equivalents or Awaitlet.&lt;br /&gt;
* Replace timeouts, task scheduling, and WSGI servers with modern async solutions.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
3. '''Testing and Validation:'''&lt;br /&gt;
   &lt;br /&gt;
* Refactor test cases to mock asyncio or threading instead of Eventlet.&lt;br /&gt;
* Ensure comprehensive testing of the new concurrency model, especially for timeouts and green threads.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
4. '''Documentation and Cleanup:'''&lt;br /&gt;
&lt;br /&gt;
* Update all related documentation.&lt;br /&gt;
* Remove Eventlet from dependencies and configuration files.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
5. '''Deployment and Monitoring:'''&lt;br /&gt;
&lt;br /&gt;
* Deploy the refactored code and closely monitor for performance or concurrency issues.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
This plan ensures that the migration from Eventlet to modern asynchronous libraries like `asyncio` (or other alternatives) happens incrementally, reducing risks and ensuring code stability.&lt;br /&gt;
You can apply this plan into your team by using the following checklist.&lt;br /&gt;
&lt;br /&gt;
=== Proposed Checklist for Migrating Each Deliverable ===&lt;br /&gt;
&lt;br /&gt;
'''1. Identification of Eventlet Occurrences:'''&lt;br /&gt;
&lt;br /&gt;
* '''Objective''': List all occurrences of Eventlet in the deliverable, including imports, usage of `monkey_patch()`, `Timeout`, greenthreads, etc.&lt;br /&gt;
* '''Action''': Use search tools (such as `beagle`) to locate all instances of Eventlet in the source code.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''2. Context of Usage:'''&lt;br /&gt;
&lt;br /&gt;
* '''Objective''': Document the context of each Eventlet occurrence, including the impacted components (WSGI, threads, timeout management, etc.) and the potential impact of its removal.&lt;br /&gt;
* '''Action''': For each identified occurrence, add a note explaining its role and importance in the functionality of the deliverable.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''3. Replacement Plan:'''&lt;br /&gt;
&lt;br /&gt;
* '''Objective''': Define how each occurrence of Eventlet will be replaced with a modern alternative (e.g., `asyncio`, `concurrent.futures`, `threading`).&lt;br /&gt;
* '''Action''': Describe the specific steps for refactoring or replacing each component.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''4. Progress Tracking:'''&lt;br /&gt;
&lt;br /&gt;
* '''Objective''': Implement a progress tracker for the migration of each component within the deliverable.&lt;br /&gt;
* '''Action''': Create a dedicated section in the checklist to track the status of each task (in progress, completed, awaiting testing, etc.).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''5. Testing and Validation:'''&lt;br /&gt;
&lt;br /&gt;
* '''Objective''': Ensure that the deliverable works correctly after the migration by running specific unit and functional tests.&lt;br /&gt;
* '''Action''': Document test results after each phase of the migration and validate the stability of the deliverable.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''6. Bug and Incident Tracking:'''&lt;br /&gt;
&lt;br /&gt;
* '''Objective''': Keep track of issues encountered during or after the migration and ensure they are resolved.&lt;br /&gt;
* '''Action''': Log bugs or incidents related to the migration, with a resolution status for each issue.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''7. Documentation of Changes:'''&lt;br /&gt;
&lt;br /&gt;
* '''Objective''': Update technical documentation to reflect the changes made during the migration.&lt;br /&gt;
* '''Action''': Provide a summary of modifications in the deliverable and describe the new concurrency management practices (e.g., adopting `asyncio`).&lt;br /&gt;
&lt;br /&gt;
=== Roles and Responsibilities ===&lt;br /&gt;
&lt;br /&gt;
To help teams to manage the organisational aspects of the migration, we propose the following roles and responsibilities. OpenStack is maintained by several companies and organisations, the following structure aims to provide a minimal template for teams organisation to ensure that everyone is on the same page and have a clear understanding on how we work.&lt;br /&gt;
&lt;br /&gt;
The Eventlet migration is complex, without a bit of structure your team might suffer of that migration. The future of your deliverables depends on it.&lt;br /&gt;
&lt;br /&gt;
'''We strongly encourage teams to adopt this structure.''' &lt;br /&gt;
&lt;br /&gt;
==== Technical Lead Role ====&lt;br /&gt;
&lt;br /&gt;
The technical lead will be responsible for:&lt;br /&gt;
&lt;br /&gt;
* '''Supervision:''' Monitoring the technical progress of the migration within the team.&lt;br /&gt;
* '''Support:''' Providing assistance to other developers and troubleshooting any technical issues related to the migration.&lt;br /&gt;
* '''Reporting:''' Submitting regular progress reports, listing impacted deliverables, patterns of Eventlet identified, and participating in monthly meetings with the team product manager.&lt;br /&gt;
* '''Technology Updates:''' Staying up-to-date on the latest technical news and updates related to the migration, and sharing this information with the team.&lt;br /&gt;
* '''Coordination:''' Working closely with the product manager to ensure that the team has the necessary resources to complete the migration.&lt;br /&gt;
* '''Communication:''' Acting as the main point of contact for questions or issues within the team regarding the migration process.&lt;br /&gt;
&lt;br /&gt;
==== Product Manager’s Role ====&lt;br /&gt;
&lt;br /&gt;
The product manager is responsible for coordinating and facilitating the Eventlet removal within their team. They ensure that the necessary resources and time are allocated for the transition and that progress is tracked regularly. Key Responsibilities:&lt;br /&gt;
&lt;br /&gt;
* '''Prioritization and Planning:''' Ensure the migration is prioritized in the team's roadmap and schedules. Set clear objectives and deadlines for each stage of the migration.&lt;br /&gt;
* '''Support for the Team:''' Provide the technical lead and the team with the necessary resources and time to complete the migration. Address any organizational or logistical obstacles that might slow down progress.&lt;br /&gt;
* '''Progress Monitoring:''' Review and monitor progress reports submitted by the technical lead. Ensure the team meets the deadlines for audits, migrations, and report submissions.&lt;br /&gt;
* '''Coordination:''' Collaborate with other product managers to ensure a smooth and coordinated transition across all teams. Facilitate knowledge sharing between teams to optimize the migration process.&lt;br /&gt;
* '''Communication:''' Attend monthly meetings with the technical lead to discuss progress, challenges, and any resource needs. Provide regular updates to stakeholders about the state of the migration.&lt;br /&gt;
&lt;br /&gt;
==== Responsibilities For Developers ====&lt;br /&gt;
&lt;br /&gt;
* '''Task 1''': Identify which Eventlet version is currently in use in your deliverables.You can simply inspect your requirements. You can also use beagle on all your deliverables, see the related sections.&lt;br /&gt;
* '''Task 2''': If the version is lower than 0.36.1, prepare the Eventlet requirement upgrade.&lt;br /&gt;
* '''Task 3''': Audit your code to identify all dependencies related to Eventlet within the next three weeks.&lt;br /&gt;
* '''Task 4''': Prepare a progress report that includes:&lt;br /&gt;
# A list of impacted deliverables;&lt;br /&gt;
# Eventlet patterns used within these deliverables (based on the patterns defined above).&lt;br /&gt;
* '''Task 5''': Join us at the PTG sessions and then lets discuss together about your use cases.&lt;br /&gt;
* '''Task 6''': Start migrating identified Eventlet components to available alternatives after the audit is complete. The target is to have this migration started within the current quarter.&lt;br /&gt;
&lt;br /&gt;
==== Responsibilities For Product Managers ====&lt;br /&gt;
&lt;br /&gt;
* '''Task 1''': Prioritize this migration in your team's roadmap. From easiest to most complex deliverables to migrate.&lt;br /&gt;
* '''Task 2''': Within the next two weeks, identify a technical lead (preferably a senior developer or principal senior developer) who will oversee the migration.&lt;br /&gt;
* '''Task 3''': Ensure that your teams have the necessary resources and time to complete the migration.&lt;br /&gt;
* '''Task 4''': Attend the coming PTG sessions and monthly follow-up meetings with the technical lead to discuss progress, blockers, and next steps.&lt;br /&gt;
&lt;br /&gt;
== Tracking Progress ==&lt;br /&gt;
&lt;br /&gt;
=== Proposed Key Performance Indicators (KPI) ===&lt;br /&gt;
&lt;br /&gt;
These KPIs aim to provide managers (product managers) with a way to track and measure progress, both from a team-based perspective and from a global overview.&lt;br /&gt;
&lt;br /&gt;
'''1. Eventlet Deactivation Rate in Deliverables:'''&lt;br /&gt;
&lt;br /&gt;
* '''Objective:''' Track the progress of the number of deliverables where Eventlet has been completely disabled and replaced.&lt;br /&gt;
* '''KPI:''' Percentage of deliverables where Eventlet is replaced by alternative solutions (`asyncio`, `awaitlet`, `concurrent.futures`, etc.).&lt;br /&gt;
* '''Calculation:''' (Number of migrated deliverables / Total number of identified deliverables) x 100.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''2. Reduction in the Number of `monkey_patch()` Occurrences:'''&lt;br /&gt;
&lt;br /&gt;
* '''Objective:''' Assess the gradual removal of calls to `eventlet.monkey_patch()`, which can cause unwanted behaviors.&lt;br /&gt;
* '''KPI:''' Number of `monkey_patch()` occurrences still present in the code.&lt;br /&gt;
* '''Calculation:''' Total number of `monkey_patch()` occurrences identified before and after each migration phase.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''3. Response Time and Performance Post-Migration:'''&lt;br /&gt;
&lt;br /&gt;
* '''Objective:''' Verify whether the performance of the deliverables improves or remains stable after migrating to `asyncio` or other solutions.&lt;br /&gt;
* '''KPI:''' Average response time of services post-migration (in ms).&lt;br /&gt;
* '''Calculation:''' Measure the response time before and after migration to check if performance improves.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''4. Percentage of Automated Tests Passed After Migration:'''&lt;br /&gt;
&lt;br /&gt;
* '''Objective:''' Ensure the stability and quality of the migrated code by verifying that unit and functional tests pass successfully.&lt;br /&gt;
* '''KPI:''' Percentage of automated tests passed after migration.&lt;br /&gt;
* '''Calculation:''' (Number of tests passed post-migration / Total number of tests executed) x 100.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''5. Number of Bugs or Incidents Post-Migration:'''&lt;br /&gt;
&lt;br /&gt;
* '''Objective:''' Track the stability of the project post-migration by monitoring the number of bugs or incidents related to the migration.&lt;br /&gt;
* '''KPI:''' Number of bugs or incidents identified related to the Eventlet migration.&lt;br /&gt;
* '''Calculation:''' Total number of bugs/incidents opened during and after migration.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''6. Adoption of New Python Versions:'''&lt;br /&gt;
&lt;br /&gt;
* '''Objective:''' Verify if the teams are able to adopt new Python versions in the migrated deliverables.&lt;br /&gt;
* '''KPI:''' Percentage of deliverables using a recent version of Python (e.g., Python 3.12).&lt;br /&gt;
* '''Calculation:''' (Number of deliverables using the latest Python version / Total number of deliverables) x 100.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''7. Meeting Project Migration Deadlines:'''&lt;br /&gt;
&lt;br /&gt;
* '''Objective:''' Ensure that the migration project adheres to the initial schedule.&lt;br /&gt;
* '''KPI:''' Number of deadlines met compared to the migration plan.&lt;br /&gt;
* '''Calculation:''' (Number of deliverables migrated within the deadline / Total number of planned deliverables) x 100.&lt;br /&gt;
&lt;br /&gt;
== Useful Links ==&lt;br /&gt;
&lt;br /&gt;
* [https://eventlet.readthedocs.io/en/latest/asyncio/asyncio.html Eventlet's official migration guide]&lt;br /&gt;
* [https://review.opendev.org/q/topic:%22eventlet-removal%22 The gerrit patches related to the migration]&lt;br /&gt;
* [https://awaitlet.sqlalchemy.org/en/latest/ The Awaitlet documentation]&lt;br /&gt;
* [https://governance.openstack.org/tc/goals/proposed/remove-eventlet.html The related community goal (governance/TC)]&lt;br /&gt;
* [https://lists.openstack.org/archives/list/openstack-discuss@lists.openstack.org/thread/YO5CZDVAJ6QSF734ALWSGNOQDDAIOXKI/#RZFUTBFTUCSHKVA6SOCWWIXEH3QJHMYT The origin of this whole story]&lt;br /&gt;
* [https://github.com/eventlet/eventlet/issues/824 Getting the Eventlet maintenance under our umbrella]&lt;br /&gt;
&lt;br /&gt;
== FAQ ==&lt;br /&gt;
&lt;br /&gt;
'''Is this initiative is an official initiative?'''&lt;br /&gt;
&lt;br /&gt;
Yes, this initiative is supported by the [https://governance.openstack.org/tc/ OpenStack TC]. This initiative is an [https://governance.openstack.org/tc/goals/proposed/remove-eventlet.html official OpenStack community goal]. This goal [https://review.opendev.org/c/openstack/governance/+/902585 has been accepted months ago] and [https://review.opendev.org/c/openstack/governance/+/931254 is now going to be a selected goal].&lt;br /&gt;
&lt;br /&gt;
'''what will happen to eventlet once the openstack migration is complete?'''&lt;br /&gt;
&lt;br /&gt;
Eventlet will be simply abandoned officially. No more maintenance will be given to the official Eventlet repository. The official Eventlet repository will be archived.&lt;br /&gt;
&lt;br /&gt;
== Map of the Impacted Deliverables ==&lt;br /&gt;
&lt;br /&gt;
Here is a curated list of the Openstack deliverables currently (as of 2024 Oct. 4th) using Eventlet and which must be migrated:&lt;br /&gt;
&lt;br /&gt;
* https://opendev.org/openstack/aodh/&lt;br /&gt;
* https://opendev.org/openstack/auto-scaling-sig/&lt;br /&gt;
* https://opendev.org/openstack/barbican/&lt;br /&gt;
* https://opendev.org/openstack/blazar/&lt;br /&gt;
* https://opendev.org/openstack/ceilometer/&lt;br /&gt;
* https://opendev.org/openstack/charm-manila/&lt;br /&gt;
* https://opendev.org/openstack/charm-manila-ganesha/&lt;br /&gt;
* https://opendev.org/openstack/cinder/&lt;br /&gt;
* https://opendev.org/openstack/cinder-specs/&lt;br /&gt;
* https://opendev.org/openstack/cloudkitty/&lt;br /&gt;
* https://opendev.org/openstack/cloudkitty-specs/&lt;br /&gt;
* https://opendev.org/openstack/cyborg/&lt;br /&gt;
* https://opendev.org/openstack/designate/&lt;br /&gt;
* https://opendev.org/openstack/designate-specs/&lt;br /&gt;
* https://opendev.org/openstack/devstack/&lt;br /&gt;
* https://opendev.org/openstack/election/&lt;br /&gt;
* https://opendev.org/openstack/etcd3gw/&lt;br /&gt;
* https://opendev.org/openstack/futurist/&lt;br /&gt;
* https://opendev.org/openstack/glance/&lt;br /&gt;
* https://opendev.org/openstack/glance-specs/&lt;br /&gt;
* https://opendev.org/openstack/glance_store/&lt;br /&gt;
* https://opendev.org/openstack/governance/&lt;br /&gt;
* https://opendev.org/openstack/hacking/&lt;br /&gt;
* https://opendev.org/openstack/heat/&lt;br /&gt;
* https://opendev.org/openstack/heat-specs/&lt;br /&gt;
* https://opendev.org/openstack/heat-templates/&lt;br /&gt;
* https://opendev.org/openstack/ironic/&lt;br /&gt;
* https://opendev.org/openstack/ironic-inspector/&lt;br /&gt;
* https://opendev.org/openstack/ironic-python-agent/&lt;br /&gt;
* https://opendev.org/openstack/ironic-specs/&lt;br /&gt;
* https://opendev.org/openstack/keystone/&lt;br /&gt;
* https://opendev.org/openstack/keystone-specs/&lt;br /&gt;
* https://opendev.org/openstack/keystonemiddleware/&lt;br /&gt;
* https://opendev.org/openstack/kolla/&lt;br /&gt;
* https://opendev.org/openstack/kolla-ansible/&lt;br /&gt;
* https://opendev.org/openstack/magnum/&lt;br /&gt;
* https://opendev.org/openstack/manila/&lt;br /&gt;
* https://opendev.org/openstack/masakari/&lt;br /&gt;
* https://opendev.org/openstack/masakari-monitors/&lt;br /&gt;
* https://opendev.org/openstack/mistral/&lt;br /&gt;
* https://opendev.org/openstack/mistral-extra/&lt;br /&gt;
* https://opendev.org/openstack/mistral-lib/&lt;br /&gt;
* https://opendev.org/openstack/monasca-agent/&lt;br /&gt;
* https://opendev.org/openstack/monasca-api/&lt;br /&gt;
* https://opendev.org/openstack/monasca-events-api/&lt;br /&gt;
* https://opendev.org/openstack/networking-baremetal/&lt;br /&gt;
* https://opendev.org/openstack/networking-generic-switch/&lt;br /&gt;
* https://opendev.org/openstack/networking-sfc/&lt;br /&gt;
* https://opendev.org/openstack/neutron/&lt;br /&gt;
* https://opendev.org/openstack/neutron-dynamic-routing/&lt;br /&gt;
* https://opendev.org/openstack/neutron-fwaas/&lt;br /&gt;
* https://opendev.org/openstack/neutron-lib/&lt;br /&gt;
* https://opendev.org/openstack/neutron-specs/&lt;br /&gt;
* https://opendev.org/openstack/neutron-tempest-plugin/&lt;br /&gt;
* https://opendev.org/openstack/neutron-vpnaas/&lt;br /&gt;
* https://opendev.org/openstack/nova/&lt;br /&gt;
* https://opendev.org/openstack/nova-specs/&lt;br /&gt;
* https://opendev.org/openstack/octavia/&lt;br /&gt;
* https://opendev.org/openstack/octavia-lib/&lt;br /&gt;
* https://opendev.org/openstack/octavia-tempest-plugin/&lt;br /&gt;
* https://opendev.org/openstack/openstack-ansible-ops/&lt;br /&gt;
* https://opendev.org/openstack/openstack-ansible-os_neutron/&lt;br /&gt;
* https://opendev.org/openstack/openstack-helm/&lt;br /&gt;
* https://opendev.org/openstack/openstack-manuals/&lt;br /&gt;
* https://opendev.org/openstack/os-brick/&lt;br /&gt;
* https://opendev.org/openstack/os-ken/&lt;br /&gt;
* https://opendev.org/openstack/os-vif/&lt;br /&gt;
* https://opendev.org/openstack/oslo-specs/&lt;br /&gt;
* https://opendev.org/openstack/oslo.cache/&lt;br /&gt;
* https://opendev.org/openstack/oslo.concurrency/&lt;br /&gt;
* https://opendev.org/openstack/oslo.config/&lt;br /&gt;
* https://opendev.org/openstack/oslo.db/&lt;br /&gt;
* https://opendev.org/openstack/oslo.log/&lt;br /&gt;
* https://opendev.org/openstack/oslo.messaging/&lt;br /&gt;
* https://opendev.org/openstack/oslo.privsep/&lt;br /&gt;
* https://opendev.org/openstack/oslo.reports/&lt;br /&gt;
* https://opendev.org/openstack/oslo.rootwrap/&lt;br /&gt;
* https://opendev.org/openstack/oslo.service/&lt;br /&gt;
* https://opendev.org/openstack/oslo.utils/&lt;br /&gt;
* https://opendev.org/openstack/oslo.versionedobjects/&lt;br /&gt;
* https://opendev.org/openstack/oslo.vmware/&lt;br /&gt;
* https://opendev.org/openstack/osops/&lt;br /&gt;
* https://opendev.org/openstack/ossa/&lt;br /&gt;
* https://opendev.org/openstack/ovn-bgp-agent/&lt;br /&gt;
* https://opendev.org/openstack/ovsdbapp/&lt;br /&gt;
* https://opendev.org/openstack/performance-docs/&lt;br /&gt;
* https://opendev.org/openstack/project-config/&lt;br /&gt;
* https://opendev.org/openstack/puppet-barbican/&lt;br /&gt;
* https://opendev.org/openstack/puppet-ceilometer/&lt;br /&gt;
* https://opendev.org/openstack/puppet-keystone/&lt;br /&gt;
* https://opendev.org/openstack/puppet-manila/&lt;br /&gt;
* https://opendev.org/openstack/puppet-neutron/&lt;br /&gt;
* https://opendev.org/openstack/puppet-openstacklib/&lt;br /&gt;
* https://opendev.org/openstack/python-cinderclient/&lt;br /&gt;
* https://opendev.org/openstack/python-glanceclient/&lt;br /&gt;
* https://opendev.org/openstack/python-ironic-inspector-client/&lt;br /&gt;
* https://opendev.org/openstack/python-ironicclient/&lt;br /&gt;
* https://opendev.org/openstack/python-keystoneclient/&lt;br /&gt;
* https://opendev.org/openstack/python-manilaclient/&lt;br /&gt;
* https://opendev.org/openstack/python-mistralclient/&lt;br /&gt;
* https://opendev.org/openstack/python-octaviaclient/&lt;br /&gt;
* https://opendev.org/openstack/python-swiftclient/&lt;br /&gt;
* https://opendev.org/openstack/python-troveclient/&lt;br /&gt;
* https://opendev.org/openstack/python-zaqarclient/&lt;br /&gt;
* https://opendev.org/openstack/rally/&lt;br /&gt;
* https://opendev.org/openstack/releases/&lt;br /&gt;
* https://opendev.org/openstack/requirements/&lt;br /&gt;
* https://opendev.org/openstack/rpm-packaging/&lt;br /&gt;
* https://opendev.org/openstack/security-doc/&lt;br /&gt;
* https://opendev.org/openstack/storlets/&lt;br /&gt;
* https://opendev.org/openstack/swift/&lt;br /&gt;
* https://opendev.org/openstack/swift-bench/&lt;br /&gt;
* https://opendev.org/openstack/tacker/&lt;br /&gt;
* https://opendev.org/openstack/taskflow/&lt;br /&gt;
* https://opendev.org/openstack/telemetry-specs/&lt;br /&gt;
* https://opendev.org/openstack/tooz/&lt;br /&gt;
* https://opendev.org/openstack/trove/&lt;br /&gt;
* https://opendev.org/openstack/upstream-institute-virtual-environment/&lt;br /&gt;
* https://opendev.org/openstack/venus/&lt;br /&gt;
* https://opendev.org/openstack/vitrage/&lt;br /&gt;
* https://opendev.org/openstack/watcher/&lt;br /&gt;
* https://opendev.org/openstack/zun/&lt;/div&gt;</summary>
		<author><name>Mike Bayer</name></author>	</entry>

	<entry>
		<id>https://wiki.openstack.org/w/index.php?title=Eventlet-removal&amp;diff=185427</id>
		<title>Eventlet-removal</title>
		<link rel="alternate" type="text/html" href="https://wiki.openstack.org/w/index.php?title=Eventlet-removal&amp;diff=185427"/>
				<updated>2024-10-07T17:45:37Z</updated>
		
		<summary type="html">&lt;p&gt;Mike Bayer: /* 5. Use of `eventlet.wsgi` */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Why Eventlet Needs to Be Removed ==&lt;br /&gt;
&lt;br /&gt;
Eventlet, though once popular for concurrent execution using &amp;quot;greenthreads,&amp;quot; now presents several limitations and issues that make it an outdated solution for modern projects like OpenStack:&lt;br /&gt;
&lt;br /&gt;
1. '''Incompatibility with Recent Versions of Python:'''  &lt;br /&gt;
&lt;br /&gt;
Eventlet struggles to keep up with the evolution of Python, particularly with recent versions (such as Python 3.12 and beyond). Some of its internal functionalities, such as &amp;quot;monkey patching&amp;quot; standard libraries, no longer work correctly or cause unexpected behavior. This limits projects from adopting the latest improvements in Python, including critical security patches.&lt;br /&gt;
&lt;br /&gt;
2. '''Inefficient Resource and Thread Management:'''  &lt;br /&gt;
&lt;br /&gt;
The greenthreads used by Eventlet can lead to excessive memory consumption and poor resource management, especially under heavy loads or in large-scale applications. Given that OpenStack operates in a distributed and highly parallel environment, this can become a significant bottleneck for overall performance.&lt;br /&gt;
&lt;br /&gt;
3. '''Maintenance and Security Issues:'''  &lt;br /&gt;
&lt;br /&gt;
Eventlet suffers from a lack of active maintenance and does not receive regular updates to address security vulnerabilities and compatibility issues. This exposes OpenStack to potential security risks if flaws in Eventlet are not addressed promptly.&lt;br /&gt;
&lt;br /&gt;
4. '''Outdated Approach to Concurrency Programming:'''  &lt;br /&gt;
&lt;br /&gt;
With the emergence of more modern solutions like `asyncio` or `concurrent.futures`, which are natively integrated into Python, using Eventlet feels outdated. These newer libraries offer better concurrency management, improved performance, and tighter integration with the current Python ecosystem.&lt;br /&gt;
&lt;br /&gt;
5. '''Inability to Adopt New Linux Distributions:'''  &lt;br /&gt;
&lt;br /&gt;
Many modern Linux distributions, such as Ubuntu, RHEL, and others, now ship with newer versions of Python that are not supported by Eventlet. Without migrating away from Eventlet, OpenStack will face challenges in adopting these newer distributions. This will limit the ability to stay current with the latest infrastructure improvements and security enhancements provided by modern operating systems.&lt;br /&gt;
&lt;br /&gt;
== Potential Impacts if the Migration is Not Completed ==&lt;br /&gt;
&lt;br /&gt;
Failure to migrate away from Eventlet could have significant consequences for OpenStack-based projects:&lt;br /&gt;
&lt;br /&gt;
1. '''Blockage of Python Updates:'''  &lt;br /&gt;
&lt;br /&gt;
Continuing to use Eventlet may cause OpenStack to be stuck on older versions of Python. This prevents the adoption of new features, performance improvements, and —most importantly—security patches offered by newer versions of Python.&lt;br /&gt;
&lt;br /&gt;
2. '''Security Vulnerabilities Risk:'''  &lt;br /&gt;
&lt;br /&gt;
Given Eventlet's lack of maintenance, unresolved security flaws could leave OpenStack systems vulnerable to attacks. Failing to migrate to modern solutions exposes the infrastructure to major security risks.&lt;br /&gt;
&lt;br /&gt;
3. '''Degradation of Performance and Scalability:'''  &lt;br /&gt;
&lt;br /&gt;
OpenStack handles large workloads in distributed environments, and Eventlet’s inefficient resource management can cause slowdowns and failures. Without migration, this could limit OpenStack's ability to scale smoothly and handle increasingly larger data volumes.&lt;br /&gt;
&lt;br /&gt;
4. '''Inability to Adopt New Linux Distributions:'''  &lt;br /&gt;
&lt;br /&gt;
If the migration from Eventlet is not completed, OpenStack will struggle to support modern Linux distributions that come with newer versions of Python. Distributions like Ubuntu and RHEL now ship with versions of Python that are incompatible with Eventlet. This will hinder the ability to upgrade to these distributions, which are critical for staying up-to-date with the latest security fixes and performance improvements.&lt;br /&gt;
&lt;br /&gt;
5. '''Increased Maintenance Costs:'''  &lt;br /&gt;
&lt;br /&gt;
Continuing to rely on an outdated technology will increase maintenance costs as it becomes harder to find fixes or workarounds for technical issues. This will lead to a growing technical debt that will be difficult to manage in the long term.&lt;br /&gt;
&lt;br /&gt;
To ensure the longevity, stability, and security of OpenStack, it is crucial to migrate to modern solutions like `asyncio` or `concurrent.futures`. This will not only overcome Eventlet's current limitations but also ensure that OpenStack remains capable of meeting performance, security, and scalability demands. Moreover, migrating is essential for supporting the latest Linux distributions like Ubuntu and RHEL, which include newer versions of Python that are not compatible with Eventlet.&lt;br /&gt;
&lt;br /&gt;
The following sections aims to guide you during your migration journey.&lt;br /&gt;
&lt;br /&gt;
== Different Kind of Eventlet Usages ==&lt;br /&gt;
&lt;br /&gt;
In OpenStack, there exist two kinds of deliverables based on Eventlet:&lt;br /&gt;
* deliverables for whom Eventlet is optional and can be disabled&lt;br /&gt;
* deliverables that cannot live without Eventlet&lt;br /&gt;
&lt;br /&gt;
Both deliverables won't be migrated the same way.&lt;br /&gt;
&lt;br /&gt;
For the first scenario - optional Eventlet - we suggest completely disabling Eventlet first, and then from there, start migrating your deliverables. This kind of deliverables often allows switching from one kind of execution model to another, e.g., Eventlet or futurist. We encourage migrating all your occurrences over the models non-based on Eventlet.&lt;br /&gt;
&lt;br /&gt;
==== Steps to Disable Eventlet in Optional Deliverables ====&lt;br /&gt;
&lt;br /&gt;
===== 1. Analyze Dependencies and Usage of Eventlet =====&lt;br /&gt;
&lt;br /&gt;
* Identify all parts of the code that depend on Eventlet. This includes direct imports, the use of greenthreads, and specific methods such as `monkey_patch()`, `Timeout`, or `eventlet.wsgi`.&lt;br /&gt;
* Perform a search across the codebase to locate these Eventlet elements. Use search tools like `grep`, or more specialized tools like `beagle`, to get an overview of Eventlet occurrences.&lt;br /&gt;
&lt;br /&gt;
===== 2. Selective Eventlet Deactivation =====&lt;br /&gt;
&lt;br /&gt;
* In some cases, it’s possible to disable Eventlet via configuration settings. Check if your project or framework offers this capability.&lt;br /&gt;
* If configuration doesn’t explicitly allow for disabling, start by commenting out or removing the Eventlet import lines and test the execution of the code in a local or test environment.&lt;br /&gt;
* Use native alternatives to replace functionalities dependent on Eventlet. For instance, replace greenthreads with standard threads or asynchronous processes using `asyncio`.&lt;br /&gt;
&lt;br /&gt;
===== 3. Progressive Refactoring of Eventlet Entry Points =====&lt;br /&gt;
&lt;br /&gt;
* '''Substitute `monkey_patch()`''' : Replace all occurrences of `eventlet.monkey_patch()` with explicitly defined alternatives (e.g., use `asyncio.sleep` for delays or standard libraries for threads and sockets).&lt;br /&gt;
* '''Timeout Management''' : Replace the usage of `eventlet.timeout.Timeout` with `asyncio.wait_for()` or equivalent exception handlers.&lt;br /&gt;
* '''Greenthreads and Thread Pools''' : Replace calls to `eventlet.spawn()` or `GreenPool()` with `asyncio.create_task()` or `concurrent.futures.ThreadPoolExecutor()` for concurrency management.&lt;br /&gt;
&lt;br /&gt;
===== 4. Unit and Functional Testing =====&lt;br /&gt;
&lt;br /&gt;
* After disabling Eventlet and refactoring critical elements, run existing unit and functional tests to ensure the code remains stable.&lt;br /&gt;
* Use `mock` to simulate asynchronous behaviors in your tests (e.g., patching `asyncio` instead of Eventlet).&lt;br /&gt;
&lt;br /&gt;
===== 5. Performance and Stability Validation =====&lt;br /&gt;
&lt;br /&gt;
* Assess the application's performance after disabling Eventlet, particularly in high-load scenarios. The goal is to ensure that using `asyncio` or standard threads provides equivalent or better performance.&lt;br /&gt;
* Also, monitor memory consumption and exception handling, as Eventlet’s greenthread implementation may differ from standard threads or `asyncio`.&lt;br /&gt;
&lt;br /&gt;
===== 6. Document the Changes =====&lt;br /&gt;
&lt;br /&gt;
* Once Eventlet is successfully disabled, clearly document the changes made and the new dependency requirements (e.g., `asyncio` or `concurrent.futures`).&lt;br /&gt;
* Outline the new execution models so that other developers are aware of the modifications and the new behavior of the code.&lt;br /&gt;
&lt;br /&gt;
===== 7. Complete Migration Phase =====&lt;br /&gt;
&lt;br /&gt;
* Once Eventlet is disabled and alternatives are in place, you can begin the full migration to a modern solution like `asyncio` by more extensively refactoring code that still depends on the old Eventlet-based architecture.&lt;br /&gt;
&lt;br /&gt;
The sections below aims to give you a way to identify the Eventlet patterns, and aims to give refactoring guidance.&lt;br /&gt;
&lt;br /&gt;
== Save the Date ==&lt;br /&gt;
&lt;br /&gt;
List of the coming events related to the Eventlet removal that could interest the reader:&lt;br /&gt;
&lt;br /&gt;
* 2024-10-21: PTG cross team session - The goal is to give you an overview of the Eventlet removal intiative and to present you the pop-up team;&lt;br /&gt;
* 2024-10-23: PTG cross team session - Open discussion and Q&amp;amp;A session.&lt;br /&gt;
&lt;br /&gt;
== Migration's Milestones ==&lt;br /&gt;
&lt;br /&gt;
* Adapting common libraries (Oslo, etc...) - e.g: introducing an Async Engine Facade to oslo.db, adapting oslo.service to not relying on Eventlet;&lt;br /&gt;
* Services migration - removing Eventlet usages from OpenStack services;&lt;br /&gt;
* Common libraries migration - common libraries depends on Eventlet because at a higher level - at the services level - Eventlet is required, we cannot remove this kind of Eventlet occurence from the common libraries while services are not fully migrated;&lt;br /&gt;
* Removing Eventlet from our global requirements;&lt;br /&gt;
* Abandoning Eventlet.&lt;br /&gt;
&lt;br /&gt;
== Targeted Releases ==&lt;br /&gt;
&lt;br /&gt;
Fully removing Eventlet from OpenStack will surely take several series, we target, at least, the H or I series.&lt;br /&gt;
&lt;br /&gt;
== Want to Discuss? ==&lt;br /&gt;
&lt;br /&gt;
=== #eventlet-removal ===&lt;br /&gt;
&lt;br /&gt;
Follow the '''#eventlet-removal''' tag. It will be used everywhere to tag all the related things to this topic:&lt;br /&gt;
&lt;br /&gt;
* [https://review.opendev.org/q/topic:%22eventlet-removal%22 gerrit patches]&lt;br /&gt;
* mailing list threads&lt;br /&gt;
* OpenStack irc channel&lt;br /&gt;
* RedHat slack channel&lt;br /&gt;
* ...&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
If you want to discuss about the Eventlet removal then '''dial #eventlet-removal'''. &lt;br /&gt;
You can join one of these channels or [https://review.opendev.org/q/topic:%22eventlet-removal%22 submit a new patch with a topic set to #eventlet-removal] or open a new thread with the #eventlet-removal tag.&lt;br /&gt;
&lt;br /&gt;
=== The Pop-up team ===&lt;br /&gt;
&lt;br /&gt;
A Pop-up team is constituted to manage this initiative, do not hesitate to contact this team to get feedback or answers.&lt;br /&gt;
&lt;br /&gt;
== Getting Started ==&lt;br /&gt;
&lt;br /&gt;
=== Available Alternatives to Eventlet ===&lt;br /&gt;
&lt;br /&gt;
We identified the following alternatives to replace Eventlet:&lt;br /&gt;
&lt;br /&gt;
* [https://docs.python.org/3/library/asyncio.html Asyncio];&lt;br /&gt;
* [https://awaitlet.sqlalchemy.org/en/latest/ Awaitlet];&lt;br /&gt;
* [https://docs.aiohttp.org/en/stable/ Aiohttp];&lt;br /&gt;
* [https://www.uvicorn.org/ Uvicorn];&lt;br /&gt;
* ...&lt;br /&gt;
&lt;br /&gt;
Do not hesitate to take a look to the following resources:&lt;br /&gt;
&lt;br /&gt;
* [https://eventlet.readthedocs.io/en/latest/asyncio/asyncio.html#alternatives-tips alternatives officially supported by Eventlet];&lt;br /&gt;
* [https://governance.openstack.org/tc/goals/proposed/remove-eventlet.html#the-available-alternatives-to-eventlet the alternatives proposed into the OpenStack community goal];&lt;br /&gt;
* [https://github.com/timofurrer/awesome-asyncio a curated list of Python asyncio frameworks, libraries, software and resources].&lt;br /&gt;
&lt;br /&gt;
=== How to Analyze Your Deliverables ===&lt;br /&gt;
&lt;br /&gt;
Teams are invited to analyze their deliverables to see their kind of Eventlet usages, here is an example with Oslo:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ pip install beagle&lt;br /&gt;
$ beagle search --ignore-comments -f link --repo-pattern &amp;quot;openstack/oslo*&amp;quot; 'eventlet'&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The following sections aims to give you guidance to identify your usages and use cases.&lt;br /&gt;
&lt;br /&gt;
=== Identifying Common Pattern ===&lt;br /&gt;
&lt;br /&gt;
This section aims to help you identify the various patterns that you can find on your deliverables.&lt;br /&gt;
&lt;br /&gt;
==== 1. Importing Eventlet ====&lt;br /&gt;
&lt;br /&gt;
Eventlet is imported in two main ways:&lt;br /&gt;
&lt;br /&gt;
* '''Direct import:'''&lt;br /&gt;
  &amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  import eventlet&lt;br /&gt;
  &amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
  &lt;br /&gt;
* '''Specific import of Eventlet submodules:'''&lt;br /&gt;
  &amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  from eventlet.timeout import Timeout&lt;br /&gt;
  from eventlet import greenthread&lt;br /&gt;
  from eventlet import hubs&lt;br /&gt;
  &amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 2. Use of `monkey_patch()` ====&lt;br /&gt;
&lt;br /&gt;
The use of the `eventlet.monkey_patch()` method is frequent and aims to replace some standard libraries (such as threads or sockets) with their asynchronous equivalents compatible with Eventlet's model.&lt;br /&gt;
&lt;br /&gt;
* '''Explicit calls to `monkey_patch`:'''&lt;br /&gt;
  &amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  eventlet.monkey_patch()&lt;br /&gt;
  eventlet.monkey_patch(all=True)&lt;br /&gt;
  eventlet.monkey_patch(time=True)&lt;br /&gt;
  eventlet.patcher.monkey_patch(all=False, socket=True)&lt;br /&gt;
  &amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 3. Timeout and Deadline Management ====&lt;br /&gt;
&lt;br /&gt;
The use of `eventlet.timeout.Timeout` is common to manage waiting periods or deadlines for blocking operations.&lt;br /&gt;
&lt;br /&gt;
* '''Use of `Timeout` in a `with` or `try-except` block:'''&lt;br /&gt;
  &amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  with eventlet.Timeout(timeout, True):&lt;br /&gt;
      # Do something&lt;br /&gt;
  except eventlet.timeout.Timeout:&lt;br /&gt;
      # Handle the timeout&lt;br /&gt;
  &amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 4. Green Threads and GreenPool ====&lt;br /&gt;
&lt;br /&gt;
Eventlet provides &amp;quot;greenthread&amp;quot; versions of classic threads as well as thread pools adapted to its asynchronous model.&lt;br /&gt;
&lt;br /&gt;
* '''Use of GreenThread and GreenPool:'''&lt;br /&gt;
  &amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  self.pool = eventlet.GreenPool(threads)&lt;br /&gt;
  eventlet.spawn(func)&lt;br /&gt;
  eventlet.spawn_n(func)&lt;br /&gt;
  &amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 5. Use of `eventlet.wsgi` ====&lt;br /&gt;
&lt;br /&gt;
Eventlet is sometimes used to handle WSGI (Web Server Gateway Interface) servers, particularly via `eventlet.wsgi`.&lt;br /&gt;
&lt;br /&gt;
* '''Example of use with `eventlet.wsgi`:'''&lt;br /&gt;
  &amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  import eventlet.wsgi&lt;br /&gt;
  eventlet.wsgi.server(socket, application, custom_pool=self.pool)&lt;br /&gt;
  &amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 6. Deferred Tasks and Scheduling ====&lt;br /&gt;
&lt;br /&gt;
The use of `eventlet.spawn_after()` to execute tasks after a delay is also a recurring pattern.&lt;br /&gt;
&lt;br /&gt;
* '''Example of `spawn_after`:'''&lt;br /&gt;
  &amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  eventlet.spawn_after(3.5, update_db)&lt;br /&gt;
  &amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 7. Use in Tests with `mock` ====&lt;br /&gt;
&lt;br /&gt;
In tests, Eventlet is sometimes patched or mocked to simulate its behavior or to control asynchronous tests.&lt;br /&gt;
&lt;br /&gt;
* '''Examples of use in tests:'''&lt;br /&gt;
  &amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  @mock.patch.object(eventlet, 'monkey_patch')&lt;br /&gt;
  @mock.patch('eventlet.spawn')&lt;br /&gt;
  &amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 8. Presence in Configuration Files and Dependencies ====&lt;br /&gt;
&lt;br /&gt;
Eventlet is explicitly mentioned in configuration files as a dependency in `requirements.txt` and `setup.cfg` files.&lt;br /&gt;
&lt;br /&gt;
* '''Dependencies:'''&lt;br /&gt;
  &amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
  eventlet&amp;gt;=0.18.2&lt;br /&gt;
  eventlet!=0.20.1,&amp;gt;=0.18.2 # MIT&lt;br /&gt;
  &amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 9. References in Documentation ====&lt;br /&gt;
&lt;br /&gt;
Some parts of the documentation or code comments refer to the use of Eventlet, either explaining why it is used or mentioning alternatives.&lt;br /&gt;
&lt;br /&gt;
* '''Example in documentation:'''&lt;br /&gt;
  &amp;lt;syntaxhighlight lang=&amp;quot;rst&amp;quot;&amp;gt;&lt;br /&gt;
  If eventlet is used, then this engine will not block other threads&lt;br /&gt;
  &amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Migration Plan ===&lt;br /&gt;
&lt;br /&gt;
Here’s a proposed migration plan to replace Eventlet with a more modern and efficient solution like asyncio or concurrent.futures, while considering each of the identified patterns.&lt;br /&gt;
This plan should be used in addition of the official migration guide provided into the Eventlet documentation:&lt;br /&gt;
&lt;br /&gt;
https://eventlet.readthedocs.io/en/latest/asyncio/asyncio.html&lt;br /&gt;
&lt;br /&gt;
==== 1. Direct Import of Eventlet ====&lt;br /&gt;
&lt;br /&gt;
'''Migration:'''&lt;br /&gt;
* Replace `import eventlet` and specific Eventlet submodules with asyncio, or other libraries such as `concurrent.futures`.&lt;br /&gt;
&lt;br /&gt;
'''Steps:'''&lt;br /&gt;
* Review the code for all occurrences of `eventlet` imports.&lt;br /&gt;
* Replace with equivalent asyncio or standard Python concurrency libraries.&lt;br /&gt;
&lt;br /&gt;
'''Example:'''&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
# Before&lt;br /&gt;
import eventlet&lt;br /&gt;
&lt;br /&gt;
# After&lt;br /&gt;
import asyncio&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 2. `monkey_patch()` ====&lt;br /&gt;
&lt;br /&gt;
'''Migration:'''&lt;br /&gt;
* Monkey-patching should be avoided because it changes standard library behavior, which can cause unexpected side effects.&lt;br /&gt;
* Refactor the code to use explicit asyncio or threading-based code.&lt;br /&gt;
&lt;br /&gt;
'''Steps:'''&lt;br /&gt;
* Identify all places where `eventlet.monkey_patch()` is used.&lt;br /&gt;
* Replace patched calls (e.g., `time.sleep()`, `threading`) with non-blocking alternatives (e.g., `asyncio.sleep()`, `concurrent.futures.ThreadPoolExecutor`).&lt;br /&gt;
&lt;br /&gt;
'''Example:'''&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
# Before&lt;br /&gt;
eventlet.monkey_patch()&lt;br /&gt;
&lt;br /&gt;
# After&lt;br /&gt;
# Use asyncio or threading where applicable without monkey-patching.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 3. Timeout and Deadline Management ====&lt;br /&gt;
&lt;br /&gt;
'''Migration:'''&lt;br /&gt;
* Use `asyncio.TimeoutError` or `asyncio.wait_for()` to handle timeout operations.&lt;br /&gt;
&lt;br /&gt;
'''Steps:'''&lt;br /&gt;
* Replace `eventlet.Timeout()` with `asyncio.wait_for()`.&lt;br /&gt;
* Ensure that any blocking operations are handled asynchronously.&lt;br /&gt;
&lt;br /&gt;
'''Example:'''&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
# Before&lt;br /&gt;
with eventlet.Timeout(timeout, True):&lt;br /&gt;
    # Do something&lt;br /&gt;
&lt;br /&gt;
# After&lt;br /&gt;
async def main():&lt;br /&gt;
    try:&lt;br /&gt;
        await asyncio.wait_for(task, timeout)&lt;br /&gt;
    except asyncio.TimeoutError:&lt;br /&gt;
        # Handle timeout&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 4. Green Threads and GreenPool ====&lt;br /&gt;
&lt;br /&gt;
'''Migration:'''&lt;br /&gt;
* Replace Eventlet's green threads with `asyncio` tasks or `concurrent.futures` for multithreading or multiprocessing.&lt;br /&gt;
&lt;br /&gt;
'''Steps:'''&lt;br /&gt;
* Replace `eventlet.GreenPool` with `asyncio.gather()` or `concurrent.futures.ThreadPoolExecutor`.&lt;br /&gt;
* Replace `eventlet.spawn()` and `eventlet.spawn_n()` with `asyncio.create_task()` or `executor.submit()` from concurrent.futures.&lt;br /&gt;
&lt;br /&gt;
'''Example:'''&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
# Before&lt;br /&gt;
pool = eventlet.GreenPool(threads)&lt;br /&gt;
pool.spawn(func)&lt;br /&gt;
&lt;br /&gt;
# After&lt;br /&gt;
async def func():&lt;br /&gt;
    # Function implementation&lt;br /&gt;
&lt;br /&gt;
asyncio.create_task(func())&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 5. Use of `eventlet.wsgi` ====&lt;br /&gt;
&lt;br /&gt;
'''Migration:'''&lt;br /&gt;
* Replace Eventlet's WSGI handling with a modern asynchronous web framework, such as `aiohttp`, `FastAPI`, or `uvicorn`.&lt;br /&gt;
&lt;br /&gt;
zzzeek:   why not a plain WSGI framework?    There is no need for code that transparently uses eventlet for wsgi to explicitly use asyncio and convert all functions to use `async`; it should use standard wsgi patterns directly which can be run in a threaded container like mod_wsgi (we use mod_wsgi right now for web services).   see masakari for example&lt;br /&gt;
&lt;br /&gt;
'''Steps:'''&lt;br /&gt;
* Replace `eventlet.wsgi` server code with an asyncio-compatible web server like `uvicorn`.&lt;br /&gt;
* Update the application to be compatible with asynchronous HTTP handling.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
zzzeek:  How can the above be achieved for projects based on oslo.service?    will an alternative to oslo.service be introduced or will we modify oslo.service to include traditional wsgi support not based on eventlet ?    &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Example:'''&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
# Before&lt;br /&gt;
import eventlet.wsgi&lt;br /&gt;
eventlet.wsgi.server(socket, application, custom_pool=self.pool)&lt;br /&gt;
&lt;br /&gt;
# After&lt;br /&gt;
# Use an asynchronous web server like Uvicorn&lt;br /&gt;
import uvicorn&lt;br /&gt;
uvicorn.run(&amp;quot;module_name:app&amp;quot;, host=&amp;quot;0.0.0.0&amp;quot;, port=8000)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 6. Deferred Tasks and Scheduling (`spawn_after`) ====&lt;br /&gt;
&lt;br /&gt;
'''Migration:'''&lt;br /&gt;
* Use `asyncio.sleep()` or `asyncio.call_later()` for scheduling delayed tasks.&lt;br /&gt;
&lt;br /&gt;
'''Steps:'''&lt;br /&gt;
* Replace `eventlet.spawn_after()` with `asyncio.create_task()` combined with `asyncio.sleep()`.&lt;br /&gt;
&lt;br /&gt;
'''Example:'''&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
# Before&lt;br /&gt;
eventlet.spawn_after(3.5, update_db)&lt;br /&gt;
&lt;br /&gt;
# After&lt;br /&gt;
async def update_db():&lt;br /&gt;
    await asyncio.sleep(3.5)&lt;br /&gt;
    # Perform database update&lt;br /&gt;
&lt;br /&gt;
asyncio.create_task(update_db())&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 7. Use in Tests with `mock` ====&lt;br /&gt;
&lt;br /&gt;
'''Migration:'''&lt;br /&gt;
* Ensure that tests are updated to mock the corresponding asyncio or threading features instead of Eventlet’s patched functions.&lt;br /&gt;
&lt;br /&gt;
'''Steps:'''&lt;br /&gt;
* Replace Eventlet mock patches with mocks for asyncio or threading functions.&lt;br /&gt;
* Use `unittest.mock` or `pytest` to mock async tasks.&lt;br /&gt;
&lt;br /&gt;
'''Example:'''&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
# Before&lt;br /&gt;
@mock.patch.object(eventlet, 'monkey_patch')&lt;br /&gt;
&lt;br /&gt;
# After&lt;br /&gt;
@mock.patch('asyncio.sleep')&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 8. Presence in Configuration Files and Dependencies ====&lt;br /&gt;
&lt;br /&gt;
'''Migration:'''&lt;br /&gt;
* Remove Eventlet from `requirements.txt` and `setup.cfg` files.&lt;br /&gt;
* Ensure that new dependencies (such as `asyncio` or `uvicorn`) are added where needed.&lt;br /&gt;
&lt;br /&gt;
'''Steps:'''&lt;br /&gt;
* Remove all references to Eventlet in the configuration files.&lt;br /&gt;
* Replace them with modern asynchronous libraries.&lt;br /&gt;
&lt;br /&gt;
'''Example:'''&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
# Before&lt;br /&gt;
eventlet&amp;gt;=0.18.2&lt;br /&gt;
&lt;br /&gt;
# After&lt;br /&gt;
# Remove Eventlet, add necessary asyncio libraries&lt;br /&gt;
aiohttp&amp;gt;=3.7.0&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 9. References in Documentation ====&lt;br /&gt;
&lt;br /&gt;
'''Migration:'''&lt;br /&gt;
* Update all documentation and code comments to reflect the switch from Eventlet to asyncio or other modern libraries.&lt;br /&gt;
&lt;br /&gt;
'''Steps:'''&lt;br /&gt;
* Review the documentation for all mentions of Eventlet.&lt;br /&gt;
* Update examples and explanations to refer to the new concurrency model.&lt;br /&gt;
&lt;br /&gt;
'''Example:'''&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;rst&amp;quot;&amp;gt;&lt;br /&gt;
# Before&lt;br /&gt;
If eventlet is used, the engine will not block other threads&lt;br /&gt;
&lt;br /&gt;
# After&lt;br /&gt;
If asyncio is used, tasks will run concurrently without blocking each other.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Migration Plan Summary ===&lt;br /&gt;
&lt;br /&gt;
1. '''Preparation:'''&lt;br /&gt;
&lt;br /&gt;
* Audit code for Eventlet usage.&lt;br /&gt;
* Identify areas where direct migration (e.g., replacing `monkey_patch()`) will be challenging.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
2. '''Refactor Core Logic:'''&lt;br /&gt;
&lt;br /&gt;
* Refactor blocking I/O operations with asyncio’s non-blocking equivalents or Awaitlet.&lt;br /&gt;
* Replace timeouts, task scheduling, and WSGI servers with modern async solutions.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
3. '''Testing and Validation:'''&lt;br /&gt;
   &lt;br /&gt;
* Refactor test cases to mock asyncio or threading instead of Eventlet.&lt;br /&gt;
* Ensure comprehensive testing of the new concurrency model, especially for timeouts and green threads.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
4. '''Documentation and Cleanup:'''&lt;br /&gt;
&lt;br /&gt;
* Update all related documentation.&lt;br /&gt;
* Remove Eventlet from dependencies and configuration files.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
5. '''Deployment and Monitoring:'''&lt;br /&gt;
&lt;br /&gt;
* Deploy the refactored code and closely monitor for performance or concurrency issues.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
This plan ensures that the migration from Eventlet to modern asynchronous libraries like `asyncio` (or other alternatives) happens incrementally, reducing risks and ensuring code stability.&lt;br /&gt;
You can apply this plan into your team by using the following checklist.&lt;br /&gt;
&lt;br /&gt;
=== Proposed Checklist for Migrating Each Deliverable ===&lt;br /&gt;
&lt;br /&gt;
'''1. Identification of Eventlet Occurrences:'''&lt;br /&gt;
&lt;br /&gt;
* '''Objective''': List all occurrences of Eventlet in the deliverable, including imports, usage of `monkey_patch()`, `Timeout`, greenthreads, etc.&lt;br /&gt;
* '''Action''': Use search tools (such as `beagle`) to locate all instances of Eventlet in the source code.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''2. Context of Usage:'''&lt;br /&gt;
&lt;br /&gt;
* '''Objective''': Document the context of each Eventlet occurrence, including the impacted components (WSGI, threads, timeout management, etc.) and the potential impact of its removal.&lt;br /&gt;
* '''Action''': For each identified occurrence, add a note explaining its role and importance in the functionality of the deliverable.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''3. Replacement Plan:'''&lt;br /&gt;
&lt;br /&gt;
* '''Objective''': Define how each occurrence of Eventlet will be replaced with a modern alternative (e.g., `asyncio`, `concurrent.futures`, `threading`).&lt;br /&gt;
* '''Action''': Describe the specific steps for refactoring or replacing each component.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''4. Progress Tracking:'''&lt;br /&gt;
&lt;br /&gt;
* '''Objective''': Implement a progress tracker for the migration of each component within the deliverable.&lt;br /&gt;
* '''Action''': Create a dedicated section in the checklist to track the status of each task (in progress, completed, awaiting testing, etc.).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''5. Testing and Validation:'''&lt;br /&gt;
&lt;br /&gt;
* '''Objective''': Ensure that the deliverable works correctly after the migration by running specific unit and functional tests.&lt;br /&gt;
* '''Action''': Document test results after each phase of the migration and validate the stability of the deliverable.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''6. Bug and Incident Tracking:'''&lt;br /&gt;
&lt;br /&gt;
* '''Objective''': Keep track of issues encountered during or after the migration and ensure they are resolved.&lt;br /&gt;
* '''Action''': Log bugs or incidents related to the migration, with a resolution status for each issue.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''7. Documentation of Changes:'''&lt;br /&gt;
&lt;br /&gt;
* '''Objective''': Update technical documentation to reflect the changes made during the migration.&lt;br /&gt;
* '''Action''': Provide a summary of modifications in the deliverable and describe the new concurrency management practices (e.g., adopting `asyncio`).&lt;br /&gt;
&lt;br /&gt;
=== Roles and Responsibilities ===&lt;br /&gt;
&lt;br /&gt;
To help teams to manage the organisational aspects of the migration, we propose the following roles and responsibilities. OpenStack is maintained by several companies and organisations, the following structure aims to provide a minimal template for teams organisation to ensure that everyone is on the same page and have a clear understanding on how we work.&lt;br /&gt;
&lt;br /&gt;
The Eventlet migration is complex, without a bit of structure your team might suffer of that migration. The future of your deliverables depends on it.&lt;br /&gt;
&lt;br /&gt;
'''We strongly encourage teams to adopt this structure.''' &lt;br /&gt;
&lt;br /&gt;
==== Technical Lead Role ====&lt;br /&gt;
&lt;br /&gt;
The technical lead will be responsible for:&lt;br /&gt;
&lt;br /&gt;
* '''Supervision:''' Monitoring the technical progress of the migration within the team.&lt;br /&gt;
* '''Support:''' Providing assistance to other developers and troubleshooting any technical issues related to the migration.&lt;br /&gt;
* '''Reporting:''' Submitting regular progress reports, listing impacted deliverables, patterns of Eventlet identified, and participating in monthly meetings with the team product manager.&lt;br /&gt;
* '''Technology Updates:''' Staying up-to-date on the latest technical news and updates related to the migration, and sharing this information with the team.&lt;br /&gt;
* '''Coordination:''' Working closely with the product manager to ensure that the team has the necessary resources to complete the migration.&lt;br /&gt;
* '''Communication:''' Acting as the main point of contact for questions or issues within the team regarding the migration process.&lt;br /&gt;
&lt;br /&gt;
==== Product Manager’s Role ====&lt;br /&gt;
&lt;br /&gt;
The product manager is responsible for coordinating and facilitating the Eventlet removal within their team. They ensure that the necessary resources and time are allocated for the transition and that progress is tracked regularly. Key Responsibilities:&lt;br /&gt;
&lt;br /&gt;
* '''Prioritization and Planning:''' Ensure the migration is prioritized in the team's roadmap and schedules. Set clear objectives and deadlines for each stage of the migration.&lt;br /&gt;
* '''Support for the Team:''' Provide the technical lead and the team with the necessary resources and time to complete the migration. Address any organizational or logistical obstacles that might slow down progress.&lt;br /&gt;
* '''Progress Monitoring:''' Review and monitor progress reports submitted by the technical lead. Ensure the team meets the deadlines for audits, migrations, and report submissions.&lt;br /&gt;
* '''Coordination:''' Collaborate with other product managers to ensure a smooth and coordinated transition across all teams. Facilitate knowledge sharing between teams to optimize the migration process.&lt;br /&gt;
* '''Communication:''' Attend monthly meetings with the technical lead to discuss progress, challenges, and any resource needs. Provide regular updates to stakeholders about the state of the migration.&lt;br /&gt;
&lt;br /&gt;
==== Responsibilities For Developers ====&lt;br /&gt;
&lt;br /&gt;
* '''Task 1''': Identify which Eventlet version is currently in use in your deliverables.You can simply inspect your requirements. You can also use beagle on all your deliverables, see the related sections.&lt;br /&gt;
* '''Task 2''': If the version is lower than 0.36.1, prepare the Eventlet requirement upgrade.&lt;br /&gt;
* '''Task 3''': Audit your code to identify all dependencies related to Eventlet within the next three weeks.&lt;br /&gt;
* '''Task 4''': Prepare a progress report that includes:&lt;br /&gt;
# A list of impacted deliverables;&lt;br /&gt;
# Eventlet patterns used within these deliverables (based on the patterns defined above).&lt;br /&gt;
* '''Task 5''': Join us at the PTG sessions and then lets discuss together about your use cases.&lt;br /&gt;
* '''Task 6''': Start migrating identified Eventlet components to available alternatives after the audit is complete. The target is to have this migration started within the current quarter.&lt;br /&gt;
&lt;br /&gt;
==== Responsibilities For Product Managers ====&lt;br /&gt;
&lt;br /&gt;
* '''Task 1''': Prioritize this migration in your team's roadmap. From easiest to most complex deliverables to migrate.&lt;br /&gt;
* '''Task 2''': Within the next two weeks, identify a technical lead (preferably a senior developer or principal senior developer) who will oversee the migration.&lt;br /&gt;
* '''Task 3''': Ensure that your teams have the necessary resources and time to complete the migration.&lt;br /&gt;
* '''Task 4''': Attend the coming PTG sessions and monthly follow-up meetings with the technical lead to discuss progress, blockers, and next steps.&lt;br /&gt;
&lt;br /&gt;
== Tracking Progress ==&lt;br /&gt;
&lt;br /&gt;
=== Proposed Key Performance Indicators (KPI) ===&lt;br /&gt;
&lt;br /&gt;
These KPIs aim to provide managers (product managers) with a way to track and measure progress, both from a team-based perspective and from a global overview.&lt;br /&gt;
&lt;br /&gt;
'''1. Eventlet Deactivation Rate in Deliverables:'''&lt;br /&gt;
&lt;br /&gt;
* '''Objective:''' Track the progress of the number of deliverables where Eventlet has been completely disabled and replaced.&lt;br /&gt;
* '''KPI:''' Percentage of deliverables where Eventlet is replaced by alternative solutions (`asyncio`, `awaitlet`, `concurrent.futures`, etc.).&lt;br /&gt;
* '''Calculation:''' (Number of migrated deliverables / Total number of identified deliverables) x 100.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''2. Reduction in the Number of `monkey_patch()` Occurrences:'''&lt;br /&gt;
&lt;br /&gt;
* '''Objective:''' Assess the gradual removal of calls to `eventlet.monkey_patch()`, which can cause unwanted behaviors.&lt;br /&gt;
* '''KPI:''' Number of `monkey_patch()` occurrences still present in the code.&lt;br /&gt;
* '''Calculation:''' Total number of `monkey_patch()` occurrences identified before and after each migration phase.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''3. Response Time and Performance Post-Migration:'''&lt;br /&gt;
&lt;br /&gt;
* '''Objective:''' Verify whether the performance of the deliverables improves or remains stable after migrating to `asyncio` or other solutions.&lt;br /&gt;
* '''KPI:''' Average response time of services post-migration (in ms).&lt;br /&gt;
* '''Calculation:''' Measure the response time before and after migration to check if performance improves.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''4. Percentage of Automated Tests Passed After Migration:'''&lt;br /&gt;
&lt;br /&gt;
* '''Objective:''' Ensure the stability and quality of the migrated code by verifying that unit and functional tests pass successfully.&lt;br /&gt;
* '''KPI:''' Percentage of automated tests passed after migration.&lt;br /&gt;
* '''Calculation:''' (Number of tests passed post-migration / Total number of tests executed) x 100.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''5. Number of Bugs or Incidents Post-Migration:'''&lt;br /&gt;
&lt;br /&gt;
* '''Objective:''' Track the stability of the project post-migration by monitoring the number of bugs or incidents related to the migration.&lt;br /&gt;
* '''KPI:''' Number of bugs or incidents identified related to the Eventlet migration.&lt;br /&gt;
* '''Calculation:''' Total number of bugs/incidents opened during and after migration.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''6. Adoption of New Python Versions:'''&lt;br /&gt;
&lt;br /&gt;
* '''Objective:''' Verify if the teams are able to adopt new Python versions in the migrated deliverables.&lt;br /&gt;
* '''KPI:''' Percentage of deliverables using a recent version of Python (e.g., Python 3.12).&lt;br /&gt;
* '''Calculation:''' (Number of deliverables using the latest Python version / Total number of deliverables) x 100.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''7. Meeting Project Migration Deadlines:'''&lt;br /&gt;
&lt;br /&gt;
* '''Objective:''' Ensure that the migration project adheres to the initial schedule.&lt;br /&gt;
* '''KPI:''' Number of deadlines met compared to the migration plan.&lt;br /&gt;
* '''Calculation:''' (Number of deliverables migrated within the deadline / Total number of planned deliverables) x 100.&lt;br /&gt;
&lt;br /&gt;
== Useful Links ==&lt;br /&gt;
&lt;br /&gt;
* [https://eventlet.readthedocs.io/en/latest/asyncio/asyncio.html Eventlet's official migration guide]&lt;br /&gt;
* [https://review.opendev.org/q/topic:%22eventlet-removal%22 The gerrit patches related to the migration]&lt;br /&gt;
* [https://awaitlet.sqlalchemy.org/en/latest/ The Awaitlet documentation]&lt;br /&gt;
* [https://governance.openstack.org/tc/goals/proposed/remove-eventlet.html The related community goal (governance/TC)]&lt;br /&gt;
* [https://lists.openstack.org/archives/list/openstack-discuss@lists.openstack.org/thread/YO5CZDVAJ6QSF734ALWSGNOQDDAIOXKI/#RZFUTBFTUCSHKVA6SOCWWIXEH3QJHMYT The origin of this whole story]&lt;br /&gt;
* [https://github.com/eventlet/eventlet/issues/824 Getting the Eventlet maintenance under our umbrella]&lt;br /&gt;
&lt;br /&gt;
== FAQ ==&lt;br /&gt;
&lt;br /&gt;
'''Is this initiative is an official initiative?'''&lt;br /&gt;
&lt;br /&gt;
Yes, this initiative is supported by the [https://governance.openstack.org/tc/ OpenStack TC]. This initiative is an [https://governance.openstack.org/tc/goals/proposed/remove-eventlet.html official OpenStack community goal]. This goal [https://review.opendev.org/c/openstack/governance/+/902585 has been accepted months ago] and [https://review.opendev.org/c/openstack/governance/+/931254 is now going to be a selected goal].&lt;br /&gt;
&lt;br /&gt;
'''what will happen to eventlet once the openstack migration is complete?'''&lt;br /&gt;
&lt;br /&gt;
Eventlet will be simply abandoned officially. No more maintenance will be given to the official Eventlet repository. The official Eventlet repository will be archived.&lt;br /&gt;
&lt;br /&gt;
== Map of the Impacted Deliverables ==&lt;br /&gt;
&lt;br /&gt;
Here is a curated list of the Openstack deliverables currently (as of 2024 Oct. 4th) using Eventlet and which must be migrated:&lt;br /&gt;
&lt;br /&gt;
* https://opendev.org/openstack/aodh/&lt;br /&gt;
* https://opendev.org/openstack/auto-scaling-sig/&lt;br /&gt;
* https://opendev.org/openstack/barbican/&lt;br /&gt;
* https://opendev.org/openstack/blazar/&lt;br /&gt;
* https://opendev.org/openstack/ceilometer/&lt;br /&gt;
* https://opendev.org/openstack/charm-manila/&lt;br /&gt;
* https://opendev.org/openstack/charm-manila-ganesha/&lt;br /&gt;
* https://opendev.org/openstack/cinder/&lt;br /&gt;
* https://opendev.org/openstack/cinder-specs/&lt;br /&gt;
* https://opendev.org/openstack/cloudkitty/&lt;br /&gt;
* https://opendev.org/openstack/cloudkitty-specs/&lt;br /&gt;
* https://opendev.org/openstack/cyborg/&lt;br /&gt;
* https://opendev.org/openstack/designate/&lt;br /&gt;
* https://opendev.org/openstack/designate-specs/&lt;br /&gt;
* https://opendev.org/openstack/devstack/&lt;br /&gt;
* https://opendev.org/openstack/election/&lt;br /&gt;
* https://opendev.org/openstack/etcd3gw/&lt;br /&gt;
* https://opendev.org/openstack/futurist/&lt;br /&gt;
* https://opendev.org/openstack/glance/&lt;br /&gt;
* https://opendev.org/openstack/glance-specs/&lt;br /&gt;
* https://opendev.org/openstack/glance_store/&lt;br /&gt;
* https://opendev.org/openstack/governance/&lt;br /&gt;
* https://opendev.org/openstack/hacking/&lt;br /&gt;
* https://opendev.org/openstack/heat/&lt;br /&gt;
* https://opendev.org/openstack/heat-specs/&lt;br /&gt;
* https://opendev.org/openstack/heat-templates/&lt;br /&gt;
* https://opendev.org/openstack/ironic/&lt;br /&gt;
* https://opendev.org/openstack/ironic-inspector/&lt;br /&gt;
* https://opendev.org/openstack/ironic-python-agent/&lt;br /&gt;
* https://opendev.org/openstack/ironic-specs/&lt;br /&gt;
* https://opendev.org/openstack/keystone/&lt;br /&gt;
* https://opendev.org/openstack/keystone-specs/&lt;br /&gt;
* https://opendev.org/openstack/keystonemiddleware/&lt;br /&gt;
* https://opendev.org/openstack/kolla/&lt;br /&gt;
* https://opendev.org/openstack/kolla-ansible/&lt;br /&gt;
* https://opendev.org/openstack/magnum/&lt;br /&gt;
* https://opendev.org/openstack/manila/&lt;br /&gt;
* https://opendev.org/openstack/masakari/&lt;br /&gt;
* https://opendev.org/openstack/masakari-monitors/&lt;br /&gt;
* https://opendev.org/openstack/mistral/&lt;br /&gt;
* https://opendev.org/openstack/mistral-extra/&lt;br /&gt;
* https://opendev.org/openstack/mistral-lib/&lt;br /&gt;
* https://opendev.org/openstack/monasca-agent/&lt;br /&gt;
* https://opendev.org/openstack/monasca-api/&lt;br /&gt;
* https://opendev.org/openstack/monasca-events-api/&lt;br /&gt;
* https://opendev.org/openstack/networking-baremetal/&lt;br /&gt;
* https://opendev.org/openstack/networking-generic-switch/&lt;br /&gt;
* https://opendev.org/openstack/networking-sfc/&lt;br /&gt;
* https://opendev.org/openstack/neutron/&lt;br /&gt;
* https://opendev.org/openstack/neutron-dynamic-routing/&lt;br /&gt;
* https://opendev.org/openstack/neutron-fwaas/&lt;br /&gt;
* https://opendev.org/openstack/neutron-lib/&lt;br /&gt;
* https://opendev.org/openstack/neutron-specs/&lt;br /&gt;
* https://opendev.org/openstack/neutron-tempest-plugin/&lt;br /&gt;
* https://opendev.org/openstack/neutron-vpnaas/&lt;br /&gt;
* https://opendev.org/openstack/nova/&lt;br /&gt;
* https://opendev.org/openstack/nova-specs/&lt;br /&gt;
* https://opendev.org/openstack/octavia/&lt;br /&gt;
* https://opendev.org/openstack/octavia-lib/&lt;br /&gt;
* https://opendev.org/openstack/octavia-tempest-plugin/&lt;br /&gt;
* https://opendev.org/openstack/openstack-ansible-ops/&lt;br /&gt;
* https://opendev.org/openstack/openstack-ansible-os_neutron/&lt;br /&gt;
* https://opendev.org/openstack/openstack-helm/&lt;br /&gt;
* https://opendev.org/openstack/openstack-manuals/&lt;br /&gt;
* https://opendev.org/openstack/os-brick/&lt;br /&gt;
* https://opendev.org/openstack/os-ken/&lt;br /&gt;
* https://opendev.org/openstack/os-vif/&lt;br /&gt;
* https://opendev.org/openstack/oslo-specs/&lt;br /&gt;
* https://opendev.org/openstack/oslo.cache/&lt;br /&gt;
* https://opendev.org/openstack/oslo.concurrency/&lt;br /&gt;
* https://opendev.org/openstack/oslo.config/&lt;br /&gt;
* https://opendev.org/openstack/oslo.db/&lt;br /&gt;
* https://opendev.org/openstack/oslo.log/&lt;br /&gt;
* https://opendev.org/openstack/oslo.messaging/&lt;br /&gt;
* https://opendev.org/openstack/oslo.privsep/&lt;br /&gt;
* https://opendev.org/openstack/oslo.reports/&lt;br /&gt;
* https://opendev.org/openstack/oslo.rootwrap/&lt;br /&gt;
* https://opendev.org/openstack/oslo.service/&lt;br /&gt;
* https://opendev.org/openstack/oslo.utils/&lt;br /&gt;
* https://opendev.org/openstack/oslo.versionedobjects/&lt;br /&gt;
* https://opendev.org/openstack/oslo.vmware/&lt;br /&gt;
* https://opendev.org/openstack/osops/&lt;br /&gt;
* https://opendev.org/openstack/ossa/&lt;br /&gt;
* https://opendev.org/openstack/ovn-bgp-agent/&lt;br /&gt;
* https://opendev.org/openstack/ovsdbapp/&lt;br /&gt;
* https://opendev.org/openstack/performance-docs/&lt;br /&gt;
* https://opendev.org/openstack/project-config/&lt;br /&gt;
* https://opendev.org/openstack/puppet-barbican/&lt;br /&gt;
* https://opendev.org/openstack/puppet-ceilometer/&lt;br /&gt;
* https://opendev.org/openstack/puppet-keystone/&lt;br /&gt;
* https://opendev.org/openstack/puppet-manila/&lt;br /&gt;
* https://opendev.org/openstack/puppet-neutron/&lt;br /&gt;
* https://opendev.org/openstack/puppet-openstacklib/&lt;br /&gt;
* https://opendev.org/openstack/python-cinderclient/&lt;br /&gt;
* https://opendev.org/openstack/python-glanceclient/&lt;br /&gt;
* https://opendev.org/openstack/python-ironic-inspector-client/&lt;br /&gt;
* https://opendev.org/openstack/python-ironicclient/&lt;br /&gt;
* https://opendev.org/openstack/python-keystoneclient/&lt;br /&gt;
* https://opendev.org/openstack/python-manilaclient/&lt;br /&gt;
* https://opendev.org/openstack/python-mistralclient/&lt;br /&gt;
* https://opendev.org/openstack/python-octaviaclient/&lt;br /&gt;
* https://opendev.org/openstack/python-swiftclient/&lt;br /&gt;
* https://opendev.org/openstack/python-troveclient/&lt;br /&gt;
* https://opendev.org/openstack/python-zaqarclient/&lt;br /&gt;
* https://opendev.org/openstack/rally/&lt;br /&gt;
* https://opendev.org/openstack/releases/&lt;br /&gt;
* https://opendev.org/openstack/requirements/&lt;br /&gt;
* https://opendev.org/openstack/rpm-packaging/&lt;br /&gt;
* https://opendev.org/openstack/security-doc/&lt;br /&gt;
* https://opendev.org/openstack/storlets/&lt;br /&gt;
* https://opendev.org/openstack/swift/&lt;br /&gt;
* https://opendev.org/openstack/swift-bench/&lt;br /&gt;
* https://opendev.org/openstack/tacker/&lt;br /&gt;
* https://opendev.org/openstack/taskflow/&lt;br /&gt;
* https://opendev.org/openstack/telemetry-specs/&lt;br /&gt;
* https://opendev.org/openstack/tooz/&lt;br /&gt;
* https://opendev.org/openstack/trove/&lt;br /&gt;
* https://opendev.org/openstack/upstream-institute-virtual-environment/&lt;br /&gt;
* https://opendev.org/openstack/venus/&lt;br /&gt;
* https://opendev.org/openstack/vitrage/&lt;br /&gt;
* https://opendev.org/openstack/watcher/&lt;br /&gt;
* https://opendev.org/openstack/zun/&lt;/div&gt;</summary>
		<author><name>Mike Bayer</name></author>	</entry>

	<entry>
		<id>https://wiki.openstack.org/w/index.php?title=Eventlet-removal&amp;diff=185426</id>
		<title>Eventlet-removal</title>
		<link rel="alternate" type="text/html" href="https://wiki.openstack.org/w/index.php?title=Eventlet-removal&amp;diff=185426"/>
				<updated>2024-10-07T17:24:36Z</updated>
		
		<summary type="html">&lt;p&gt;Mike Bayer: /* 5. Use of `eventlet.wsgi` */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Why Eventlet Needs to Be Removed ==&lt;br /&gt;
&lt;br /&gt;
Eventlet, though once popular for concurrent execution using &amp;quot;greenthreads,&amp;quot; now presents several limitations and issues that make it an outdated solution for modern projects like OpenStack:&lt;br /&gt;
&lt;br /&gt;
1. '''Incompatibility with Recent Versions of Python:'''  &lt;br /&gt;
&lt;br /&gt;
Eventlet struggles to keep up with the evolution of Python, particularly with recent versions (such as Python 3.12 and beyond). Some of its internal functionalities, such as &amp;quot;monkey patching&amp;quot; standard libraries, no longer work correctly or cause unexpected behavior. This limits projects from adopting the latest improvements in Python, including critical security patches.&lt;br /&gt;
&lt;br /&gt;
2. '''Inefficient Resource and Thread Management:'''  &lt;br /&gt;
&lt;br /&gt;
The greenthreads used by Eventlet can lead to excessive memory consumption and poor resource management, especially under heavy loads or in large-scale applications. Given that OpenStack operates in a distributed and highly parallel environment, this can become a significant bottleneck for overall performance.&lt;br /&gt;
&lt;br /&gt;
3. '''Maintenance and Security Issues:'''  &lt;br /&gt;
&lt;br /&gt;
Eventlet suffers from a lack of active maintenance and does not receive regular updates to address security vulnerabilities and compatibility issues. This exposes OpenStack to potential security risks if flaws in Eventlet are not addressed promptly.&lt;br /&gt;
&lt;br /&gt;
4. '''Outdated Approach to Concurrency Programming:'''  &lt;br /&gt;
&lt;br /&gt;
With the emergence of more modern solutions like `asyncio` or `concurrent.futures`, which are natively integrated into Python, using Eventlet feels outdated. These newer libraries offer better concurrency management, improved performance, and tighter integration with the current Python ecosystem.&lt;br /&gt;
&lt;br /&gt;
5. '''Inability to Adopt New Linux Distributions:'''  &lt;br /&gt;
&lt;br /&gt;
Many modern Linux distributions, such as Ubuntu, RHEL, and others, now ship with newer versions of Python that are not supported by Eventlet. Without migrating away from Eventlet, OpenStack will face challenges in adopting these newer distributions. This will limit the ability to stay current with the latest infrastructure improvements and security enhancements provided by modern operating systems.&lt;br /&gt;
&lt;br /&gt;
== Potential Impacts if the Migration is Not Completed ==&lt;br /&gt;
&lt;br /&gt;
Failure to migrate away from Eventlet could have significant consequences for OpenStack-based projects:&lt;br /&gt;
&lt;br /&gt;
1. '''Blockage of Python Updates:'''  &lt;br /&gt;
&lt;br /&gt;
Continuing to use Eventlet may cause OpenStack to be stuck on older versions of Python. This prevents the adoption of new features, performance improvements, and —most importantly—security patches offered by newer versions of Python.&lt;br /&gt;
&lt;br /&gt;
2. '''Security Vulnerabilities Risk:'''  &lt;br /&gt;
&lt;br /&gt;
Given Eventlet's lack of maintenance, unresolved security flaws could leave OpenStack systems vulnerable to attacks. Failing to migrate to modern solutions exposes the infrastructure to major security risks.&lt;br /&gt;
&lt;br /&gt;
3. '''Degradation of Performance and Scalability:'''  &lt;br /&gt;
&lt;br /&gt;
OpenStack handles large workloads in distributed environments, and Eventlet’s inefficient resource management can cause slowdowns and failures. Without migration, this could limit OpenStack's ability to scale smoothly and handle increasingly larger data volumes.&lt;br /&gt;
&lt;br /&gt;
4. '''Inability to Adopt New Linux Distributions:'''  &lt;br /&gt;
&lt;br /&gt;
If the migration from Eventlet is not completed, OpenStack will struggle to support modern Linux distributions that come with newer versions of Python. Distributions like Ubuntu and RHEL now ship with versions of Python that are incompatible with Eventlet. This will hinder the ability to upgrade to these distributions, which are critical for staying up-to-date with the latest security fixes and performance improvements.&lt;br /&gt;
&lt;br /&gt;
5. '''Increased Maintenance Costs:'''  &lt;br /&gt;
&lt;br /&gt;
Continuing to rely on an outdated technology will increase maintenance costs as it becomes harder to find fixes or workarounds for technical issues. This will lead to a growing technical debt that will be difficult to manage in the long term.&lt;br /&gt;
&lt;br /&gt;
To ensure the longevity, stability, and security of OpenStack, it is crucial to migrate to modern solutions like `asyncio` or `concurrent.futures`. This will not only overcome Eventlet's current limitations but also ensure that OpenStack remains capable of meeting performance, security, and scalability demands. Moreover, migrating is essential for supporting the latest Linux distributions like Ubuntu and RHEL, which include newer versions of Python that are not compatible with Eventlet.&lt;br /&gt;
&lt;br /&gt;
The following sections aims to guide you during your migration journey.&lt;br /&gt;
&lt;br /&gt;
== Different Kind of Eventlet Usages ==&lt;br /&gt;
&lt;br /&gt;
In OpenStack, there exist two kinds of deliverables based on Eventlet:&lt;br /&gt;
* deliverables for whom Eventlet is optional and can be disabled&lt;br /&gt;
* deliverables that cannot live without Eventlet&lt;br /&gt;
&lt;br /&gt;
Both deliverables won't be migrated the same way.&lt;br /&gt;
&lt;br /&gt;
For the first scenario - optional Eventlet - we suggest completely disabling Eventlet first, and then from there, start migrating your deliverables. This kind of deliverables often allows switching from one kind of execution model to another, e.g., Eventlet or futurist. We encourage migrating all your occurrences over the models non-based on Eventlet.&lt;br /&gt;
&lt;br /&gt;
==== Steps to Disable Eventlet in Optional Deliverables ====&lt;br /&gt;
&lt;br /&gt;
===== 1. Analyze Dependencies and Usage of Eventlet =====&lt;br /&gt;
&lt;br /&gt;
* Identify all parts of the code that depend on Eventlet. This includes direct imports, the use of greenthreads, and specific methods such as `monkey_patch()`, `Timeout`, or `eventlet.wsgi`.&lt;br /&gt;
* Perform a search across the codebase to locate these Eventlet elements. Use search tools like `grep`, or more specialized tools like `beagle`, to get an overview of Eventlet occurrences.&lt;br /&gt;
&lt;br /&gt;
===== 2. Selective Eventlet Deactivation =====&lt;br /&gt;
&lt;br /&gt;
* In some cases, it’s possible to disable Eventlet via configuration settings. Check if your project or framework offers this capability.&lt;br /&gt;
* If configuration doesn’t explicitly allow for disabling, start by commenting out or removing the Eventlet import lines and test the execution of the code in a local or test environment.&lt;br /&gt;
* Use native alternatives to replace functionalities dependent on Eventlet. For instance, replace greenthreads with standard threads or asynchronous processes using `asyncio`.&lt;br /&gt;
&lt;br /&gt;
===== 3. Progressive Refactoring of Eventlet Entry Points =====&lt;br /&gt;
&lt;br /&gt;
* '''Substitute `monkey_patch()`''' : Replace all occurrences of `eventlet.monkey_patch()` with explicitly defined alternatives (e.g., use `asyncio.sleep` for delays or standard libraries for threads and sockets).&lt;br /&gt;
* '''Timeout Management''' : Replace the usage of `eventlet.timeout.Timeout` with `asyncio.wait_for()` or equivalent exception handlers.&lt;br /&gt;
* '''Greenthreads and Thread Pools''' : Replace calls to `eventlet.spawn()` or `GreenPool()` with `asyncio.create_task()` or `concurrent.futures.ThreadPoolExecutor()` for concurrency management.&lt;br /&gt;
&lt;br /&gt;
===== 4. Unit and Functional Testing =====&lt;br /&gt;
&lt;br /&gt;
* After disabling Eventlet and refactoring critical elements, run existing unit and functional tests to ensure the code remains stable.&lt;br /&gt;
* Use `mock` to simulate asynchronous behaviors in your tests (e.g., patching `asyncio` instead of Eventlet).&lt;br /&gt;
&lt;br /&gt;
===== 5. Performance and Stability Validation =====&lt;br /&gt;
&lt;br /&gt;
* Assess the application's performance after disabling Eventlet, particularly in high-load scenarios. The goal is to ensure that using `asyncio` or standard threads provides equivalent or better performance.&lt;br /&gt;
* Also, monitor memory consumption and exception handling, as Eventlet’s greenthread implementation may differ from standard threads or `asyncio`.&lt;br /&gt;
&lt;br /&gt;
===== 6. Document the Changes =====&lt;br /&gt;
&lt;br /&gt;
* Once Eventlet is successfully disabled, clearly document the changes made and the new dependency requirements (e.g., `asyncio` or `concurrent.futures`).&lt;br /&gt;
* Outline the new execution models so that other developers are aware of the modifications and the new behavior of the code.&lt;br /&gt;
&lt;br /&gt;
===== 7. Complete Migration Phase =====&lt;br /&gt;
&lt;br /&gt;
* Once Eventlet is disabled and alternatives are in place, you can begin the full migration to a modern solution like `asyncio` by more extensively refactoring code that still depends on the old Eventlet-based architecture.&lt;br /&gt;
&lt;br /&gt;
The sections below aims to give you a way to identify the Eventlet patterns, and aims to give refactoring guidance.&lt;br /&gt;
&lt;br /&gt;
== Save the Date ==&lt;br /&gt;
&lt;br /&gt;
List of the coming events related to the Eventlet removal that could interest the reader:&lt;br /&gt;
&lt;br /&gt;
* 2024-10-21: PTG cross team session - The goal is to give you an overview of the Eventlet removal intiative and to present you the pop-up team;&lt;br /&gt;
* 2024-10-23: PTG cross team session - Open discussion and Q&amp;amp;A session.&lt;br /&gt;
&lt;br /&gt;
== Migration's Milestones ==&lt;br /&gt;
&lt;br /&gt;
* Adapting common libraries (Oslo, etc...) - e.g: introducing an Async Engine Facade to oslo.db, adapting oslo.service to not relying on Eventlet;&lt;br /&gt;
* Services migration - removing Eventlet usages from OpenStack services;&lt;br /&gt;
* Common libraries migration - common libraries depends on Eventlet because at a higher level - at the services level - Eventlet is required, we cannot remove this kind of Eventlet occurence from the common libraries while services are not fully migrated;&lt;br /&gt;
* Removing Eventlet from our global requirements;&lt;br /&gt;
* Abandoning Eventlet.&lt;br /&gt;
&lt;br /&gt;
== Targeted Releases ==&lt;br /&gt;
&lt;br /&gt;
Fully removing Eventlet from OpenStack will surely take several series, we target, at least, the H or I series.&lt;br /&gt;
&lt;br /&gt;
== Want to Discuss? ==&lt;br /&gt;
&lt;br /&gt;
=== #eventlet-removal ===&lt;br /&gt;
&lt;br /&gt;
Follow the '''#eventlet-removal''' tag. It will be used everywhere to tag all the related things to this topic:&lt;br /&gt;
&lt;br /&gt;
* [https://review.opendev.org/q/topic:%22eventlet-removal%22 gerrit patches]&lt;br /&gt;
* mailing list threads&lt;br /&gt;
* OpenStack irc channel&lt;br /&gt;
* RedHat slack channel&lt;br /&gt;
* ...&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
If you want to discuss about the Eventlet removal then '''dial #eventlet-removal'''. &lt;br /&gt;
You can join one of these channels or [https://review.opendev.org/q/topic:%22eventlet-removal%22 submit a new patch with a topic set to #eventlet-removal] or open a new thread with the #eventlet-removal tag.&lt;br /&gt;
&lt;br /&gt;
=== The Pop-up team ===&lt;br /&gt;
&lt;br /&gt;
A Pop-up team is constituted to manage this initiative, do not hesitate to contact this team to get feedback or answers.&lt;br /&gt;
&lt;br /&gt;
== Getting Started ==&lt;br /&gt;
&lt;br /&gt;
=== Available Alternatives to Eventlet ===&lt;br /&gt;
&lt;br /&gt;
We identified the following alternatives to replace Eventlet:&lt;br /&gt;
&lt;br /&gt;
* [https://docs.python.org/3/library/asyncio.html Asyncio];&lt;br /&gt;
* [https://awaitlet.sqlalchemy.org/en/latest/ Awaitlet];&lt;br /&gt;
* [https://docs.aiohttp.org/en/stable/ Aiohttp];&lt;br /&gt;
* [https://www.uvicorn.org/ Uvicorn];&lt;br /&gt;
* ...&lt;br /&gt;
&lt;br /&gt;
Do not hesitate to take a look to the following resources:&lt;br /&gt;
&lt;br /&gt;
* [https://eventlet.readthedocs.io/en/latest/asyncio/asyncio.html#alternatives-tips alternatives officially supported by Eventlet];&lt;br /&gt;
* [https://governance.openstack.org/tc/goals/proposed/remove-eventlet.html#the-available-alternatives-to-eventlet the alternatives proposed into the OpenStack community goal];&lt;br /&gt;
* [https://github.com/timofurrer/awesome-asyncio a curated list of Python asyncio frameworks, libraries, software and resources].&lt;br /&gt;
&lt;br /&gt;
=== How to Analyze Your Deliverables ===&lt;br /&gt;
&lt;br /&gt;
Teams are invited to analyze their deliverables to see their kind of Eventlet usages, here is an example with Oslo:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
$ pip install beagle&lt;br /&gt;
$ beagle search --ignore-comments -f link --repo-pattern &amp;quot;openstack/oslo*&amp;quot; 'eventlet'&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The following sections aims to give you guidance to identify your usages and use cases.&lt;br /&gt;
&lt;br /&gt;
=== Identifying Common Pattern ===&lt;br /&gt;
&lt;br /&gt;
This section aims to help you identify the various patterns that you can find on your deliverables.&lt;br /&gt;
&lt;br /&gt;
==== 1. Importing Eventlet ====&lt;br /&gt;
&lt;br /&gt;
Eventlet is imported in two main ways:&lt;br /&gt;
&lt;br /&gt;
* '''Direct import:'''&lt;br /&gt;
  &amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  import eventlet&lt;br /&gt;
  &amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
  &lt;br /&gt;
* '''Specific import of Eventlet submodules:'''&lt;br /&gt;
  &amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  from eventlet.timeout import Timeout&lt;br /&gt;
  from eventlet import greenthread&lt;br /&gt;
  from eventlet import hubs&lt;br /&gt;
  &amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 2. Use of `monkey_patch()` ====&lt;br /&gt;
&lt;br /&gt;
The use of the `eventlet.monkey_patch()` method is frequent and aims to replace some standard libraries (such as threads or sockets) with their asynchronous equivalents compatible with Eventlet's model.&lt;br /&gt;
&lt;br /&gt;
* '''Explicit calls to `monkey_patch`:'''&lt;br /&gt;
  &amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  eventlet.monkey_patch()&lt;br /&gt;
  eventlet.monkey_patch(all=True)&lt;br /&gt;
  eventlet.monkey_patch(time=True)&lt;br /&gt;
  eventlet.patcher.monkey_patch(all=False, socket=True)&lt;br /&gt;
  &amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 3. Timeout and Deadline Management ====&lt;br /&gt;
&lt;br /&gt;
The use of `eventlet.timeout.Timeout` is common to manage waiting periods or deadlines for blocking operations.&lt;br /&gt;
&lt;br /&gt;
* '''Use of `Timeout` in a `with` or `try-except` block:'''&lt;br /&gt;
  &amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  with eventlet.Timeout(timeout, True):&lt;br /&gt;
      # Do something&lt;br /&gt;
  except eventlet.timeout.Timeout:&lt;br /&gt;
      # Handle the timeout&lt;br /&gt;
  &amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 4. Green Threads and GreenPool ====&lt;br /&gt;
&lt;br /&gt;
Eventlet provides &amp;quot;greenthread&amp;quot; versions of classic threads as well as thread pools adapted to its asynchronous model.&lt;br /&gt;
&lt;br /&gt;
* '''Use of GreenThread and GreenPool:'''&lt;br /&gt;
  &amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  self.pool = eventlet.GreenPool(threads)&lt;br /&gt;
  eventlet.spawn(func)&lt;br /&gt;
  eventlet.spawn_n(func)&lt;br /&gt;
  &amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 5. Use of `eventlet.wsgi` ====&lt;br /&gt;
&lt;br /&gt;
Eventlet is sometimes used to handle WSGI (Web Server Gateway Interface) servers, particularly via `eventlet.wsgi`.&lt;br /&gt;
&lt;br /&gt;
* '''Example of use with `eventlet.wsgi`:'''&lt;br /&gt;
  &amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  import eventlet.wsgi&lt;br /&gt;
  eventlet.wsgi.server(socket, application, custom_pool=self.pool)&lt;br /&gt;
  &amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 6. Deferred Tasks and Scheduling ====&lt;br /&gt;
&lt;br /&gt;
The use of `eventlet.spawn_after()` to execute tasks after a delay is also a recurring pattern.&lt;br /&gt;
&lt;br /&gt;
* '''Example of `spawn_after`:'''&lt;br /&gt;
  &amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  eventlet.spawn_after(3.5, update_db)&lt;br /&gt;
  &amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 7. Use in Tests with `mock` ====&lt;br /&gt;
&lt;br /&gt;
In tests, Eventlet is sometimes patched or mocked to simulate its behavior or to control asynchronous tests.&lt;br /&gt;
&lt;br /&gt;
* '''Examples of use in tests:'''&lt;br /&gt;
  &amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  @mock.patch.object(eventlet, 'monkey_patch')&lt;br /&gt;
  @mock.patch('eventlet.spawn')&lt;br /&gt;
  &amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 8. Presence in Configuration Files and Dependencies ====&lt;br /&gt;
&lt;br /&gt;
Eventlet is explicitly mentioned in configuration files as a dependency in `requirements.txt` and `setup.cfg` files.&lt;br /&gt;
&lt;br /&gt;
* '''Dependencies:'''&lt;br /&gt;
  &amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
  eventlet&amp;gt;=0.18.2&lt;br /&gt;
  eventlet!=0.20.1,&amp;gt;=0.18.2 # MIT&lt;br /&gt;
  &amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 9. References in Documentation ====&lt;br /&gt;
&lt;br /&gt;
Some parts of the documentation or code comments refer to the use of Eventlet, either explaining why it is used or mentioning alternatives.&lt;br /&gt;
&lt;br /&gt;
* '''Example in documentation:'''&lt;br /&gt;
  &amp;lt;syntaxhighlight lang=&amp;quot;rst&amp;quot;&amp;gt;&lt;br /&gt;
  If eventlet is used, then this engine will not block other threads&lt;br /&gt;
  &amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Migration Plan ===&lt;br /&gt;
&lt;br /&gt;
Here’s a proposed migration plan to replace Eventlet with a more modern and efficient solution like asyncio or concurrent.futures, while considering each of the identified patterns.&lt;br /&gt;
This plan should be used in addition of the official migration guide provided into the Eventlet documentation:&lt;br /&gt;
&lt;br /&gt;
https://eventlet.readthedocs.io/en/latest/asyncio/asyncio.html&lt;br /&gt;
&lt;br /&gt;
==== 1. Direct Import of Eventlet ====&lt;br /&gt;
&lt;br /&gt;
'''Migration:'''&lt;br /&gt;
* Replace `import eventlet` and specific Eventlet submodules with asyncio, or other libraries such as `concurrent.futures`.&lt;br /&gt;
&lt;br /&gt;
'''Steps:'''&lt;br /&gt;
* Review the code for all occurrences of `eventlet` imports.&lt;br /&gt;
* Replace with equivalent asyncio or standard Python concurrency libraries.&lt;br /&gt;
&lt;br /&gt;
'''Example:'''&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
# Before&lt;br /&gt;
import eventlet&lt;br /&gt;
&lt;br /&gt;
# After&lt;br /&gt;
import asyncio&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 2. `monkey_patch()` ====&lt;br /&gt;
&lt;br /&gt;
'''Migration:'''&lt;br /&gt;
* Monkey-patching should be avoided because it changes standard library behavior, which can cause unexpected side effects.&lt;br /&gt;
* Refactor the code to use explicit asyncio or threading-based code.&lt;br /&gt;
&lt;br /&gt;
'''Steps:'''&lt;br /&gt;
* Identify all places where `eventlet.monkey_patch()` is used.&lt;br /&gt;
* Replace patched calls (e.g., `time.sleep()`, `threading`) with non-blocking alternatives (e.g., `asyncio.sleep()`, `concurrent.futures.ThreadPoolExecutor`).&lt;br /&gt;
&lt;br /&gt;
'''Example:'''&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
# Before&lt;br /&gt;
eventlet.monkey_patch()&lt;br /&gt;
&lt;br /&gt;
# After&lt;br /&gt;
# Use asyncio or threading where applicable without monkey-patching.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 3. Timeout and Deadline Management ====&lt;br /&gt;
&lt;br /&gt;
'''Migration:'''&lt;br /&gt;
* Use `asyncio.TimeoutError` or `asyncio.wait_for()` to handle timeout operations.&lt;br /&gt;
&lt;br /&gt;
'''Steps:'''&lt;br /&gt;
* Replace `eventlet.Timeout()` with `asyncio.wait_for()`.&lt;br /&gt;
* Ensure that any blocking operations are handled asynchronously.&lt;br /&gt;
&lt;br /&gt;
'''Example:'''&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
# Before&lt;br /&gt;
with eventlet.Timeout(timeout, True):&lt;br /&gt;
    # Do something&lt;br /&gt;
&lt;br /&gt;
# After&lt;br /&gt;
async def main():&lt;br /&gt;
    try:&lt;br /&gt;
        await asyncio.wait_for(task, timeout)&lt;br /&gt;
    except asyncio.TimeoutError:&lt;br /&gt;
        # Handle timeout&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 4. Green Threads and GreenPool ====&lt;br /&gt;
&lt;br /&gt;
'''Migration:'''&lt;br /&gt;
* Replace Eventlet's green threads with `asyncio` tasks or `concurrent.futures` for multithreading or multiprocessing.&lt;br /&gt;
&lt;br /&gt;
'''Steps:'''&lt;br /&gt;
* Replace `eventlet.GreenPool` with `asyncio.gather()` or `concurrent.futures.ThreadPoolExecutor`.&lt;br /&gt;
* Replace `eventlet.spawn()` and `eventlet.spawn_n()` with `asyncio.create_task()` or `executor.submit()` from concurrent.futures.&lt;br /&gt;
&lt;br /&gt;
'''Example:'''&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
# Before&lt;br /&gt;
pool = eventlet.GreenPool(threads)&lt;br /&gt;
pool.spawn(func)&lt;br /&gt;
&lt;br /&gt;
# After&lt;br /&gt;
async def func():&lt;br /&gt;
    # Function implementation&lt;br /&gt;
&lt;br /&gt;
asyncio.create_task(func())&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 5. Use of `eventlet.wsgi` ====&lt;br /&gt;
&lt;br /&gt;
'''Migration:'''&lt;br /&gt;
* Replace Eventlet's WSGI handling with a modern asynchronous web framework, such as `aiohttp`, `FastAPI`, or `uvicorn`.&lt;br /&gt;
&lt;br /&gt;
zzzeek:   why not a plain WSGI framework?    There is no need for code that transparently uses eventlet for wsgi to use asyncio; it should use threads directly&lt;br /&gt;
&lt;br /&gt;
'''Steps:'''&lt;br /&gt;
* Replace `eventlet.wsgi` server code with an asyncio-compatible web server like `uvicorn`.&lt;br /&gt;
* Update the application to be compatible with asynchronous HTTP handling.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
zzzeek:  How can the above be achieved for projects based on oslo.service?    will an alternative to oslo.service be introduced or will we modify oslo.service to include traditional wsgi support not based on eventlet ?    &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Example:'''&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
# Before&lt;br /&gt;
import eventlet.wsgi&lt;br /&gt;
eventlet.wsgi.server(socket, application, custom_pool=self.pool)&lt;br /&gt;
&lt;br /&gt;
# After&lt;br /&gt;
# Use an asynchronous web server like Uvicorn&lt;br /&gt;
import uvicorn&lt;br /&gt;
uvicorn.run(&amp;quot;module_name:app&amp;quot;, host=&amp;quot;0.0.0.0&amp;quot;, port=8000)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 6. Deferred Tasks and Scheduling (`spawn_after`) ====&lt;br /&gt;
&lt;br /&gt;
'''Migration:'''&lt;br /&gt;
* Use `asyncio.sleep()` or `asyncio.call_later()` for scheduling delayed tasks.&lt;br /&gt;
&lt;br /&gt;
'''Steps:'''&lt;br /&gt;
* Replace `eventlet.spawn_after()` with `asyncio.create_task()` combined with `asyncio.sleep()`.&lt;br /&gt;
&lt;br /&gt;
'''Example:'''&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
# Before&lt;br /&gt;
eventlet.spawn_after(3.5, update_db)&lt;br /&gt;
&lt;br /&gt;
# After&lt;br /&gt;
async def update_db():&lt;br /&gt;
    await asyncio.sleep(3.5)&lt;br /&gt;
    # Perform database update&lt;br /&gt;
&lt;br /&gt;
asyncio.create_task(update_db())&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 7. Use in Tests with `mock` ====&lt;br /&gt;
&lt;br /&gt;
'''Migration:'''&lt;br /&gt;
* Ensure that tests are updated to mock the corresponding asyncio or threading features instead of Eventlet’s patched functions.&lt;br /&gt;
&lt;br /&gt;
'''Steps:'''&lt;br /&gt;
* Replace Eventlet mock patches with mocks for asyncio or threading functions.&lt;br /&gt;
* Use `unittest.mock` or `pytest` to mock async tasks.&lt;br /&gt;
&lt;br /&gt;
'''Example:'''&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
# Before&lt;br /&gt;
@mock.patch.object(eventlet, 'monkey_patch')&lt;br /&gt;
&lt;br /&gt;
# After&lt;br /&gt;
@mock.patch('asyncio.sleep')&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 8. Presence in Configuration Files and Dependencies ====&lt;br /&gt;
&lt;br /&gt;
'''Migration:'''&lt;br /&gt;
* Remove Eventlet from `requirements.txt` and `setup.cfg` files.&lt;br /&gt;
* Ensure that new dependencies (such as `asyncio` or `uvicorn`) are added where needed.&lt;br /&gt;
&lt;br /&gt;
'''Steps:'''&lt;br /&gt;
* Remove all references to Eventlet in the configuration files.&lt;br /&gt;
* Replace them with modern asynchronous libraries.&lt;br /&gt;
&lt;br /&gt;
'''Example:'''&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
# Before&lt;br /&gt;
eventlet&amp;gt;=0.18.2&lt;br /&gt;
&lt;br /&gt;
# After&lt;br /&gt;
# Remove Eventlet, add necessary asyncio libraries&lt;br /&gt;
aiohttp&amp;gt;=3.7.0&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 9. References in Documentation ====&lt;br /&gt;
&lt;br /&gt;
'''Migration:'''&lt;br /&gt;
* Update all documentation and code comments to reflect the switch from Eventlet to asyncio or other modern libraries.&lt;br /&gt;
&lt;br /&gt;
'''Steps:'''&lt;br /&gt;
* Review the documentation for all mentions of Eventlet.&lt;br /&gt;
* Update examples and explanations to refer to the new concurrency model.&lt;br /&gt;
&lt;br /&gt;
'''Example:'''&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;rst&amp;quot;&amp;gt;&lt;br /&gt;
# Before&lt;br /&gt;
If eventlet is used, the engine will not block other threads&lt;br /&gt;
&lt;br /&gt;
# After&lt;br /&gt;
If asyncio is used, tasks will run concurrently without blocking each other.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Migration Plan Summary ===&lt;br /&gt;
&lt;br /&gt;
1. '''Preparation:'''&lt;br /&gt;
&lt;br /&gt;
* Audit code for Eventlet usage.&lt;br /&gt;
* Identify areas where direct migration (e.g., replacing `monkey_patch()`) will be challenging.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
2. '''Refactor Core Logic:'''&lt;br /&gt;
&lt;br /&gt;
* Refactor blocking I/O operations with asyncio’s non-blocking equivalents or Awaitlet.&lt;br /&gt;
* Replace timeouts, task scheduling, and WSGI servers with modern async solutions.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
3. '''Testing and Validation:'''&lt;br /&gt;
   &lt;br /&gt;
* Refactor test cases to mock asyncio or threading instead of Eventlet.&lt;br /&gt;
* Ensure comprehensive testing of the new concurrency model, especially for timeouts and green threads.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
4. '''Documentation and Cleanup:'''&lt;br /&gt;
&lt;br /&gt;
* Update all related documentation.&lt;br /&gt;
* Remove Eventlet from dependencies and configuration files.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
5. '''Deployment and Monitoring:'''&lt;br /&gt;
&lt;br /&gt;
* Deploy the refactored code and closely monitor for performance or concurrency issues.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
This plan ensures that the migration from Eventlet to modern asynchronous libraries like `asyncio` (or other alternatives) happens incrementally, reducing risks and ensuring code stability.&lt;br /&gt;
You can apply this plan into your team by using the following checklist.&lt;br /&gt;
&lt;br /&gt;
=== Proposed Checklist for Migrating Each Deliverable ===&lt;br /&gt;
&lt;br /&gt;
'''1. Identification of Eventlet Occurrences:'''&lt;br /&gt;
&lt;br /&gt;
* '''Objective''': List all occurrences of Eventlet in the deliverable, including imports, usage of `monkey_patch()`, `Timeout`, greenthreads, etc.&lt;br /&gt;
* '''Action''': Use search tools (such as `beagle`) to locate all instances of Eventlet in the source code.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''2. Context of Usage:'''&lt;br /&gt;
&lt;br /&gt;
* '''Objective''': Document the context of each Eventlet occurrence, including the impacted components (WSGI, threads, timeout management, etc.) and the potential impact of its removal.&lt;br /&gt;
* '''Action''': For each identified occurrence, add a note explaining its role and importance in the functionality of the deliverable.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''3. Replacement Plan:'''&lt;br /&gt;
&lt;br /&gt;
* '''Objective''': Define how each occurrence of Eventlet will be replaced with a modern alternative (e.g., `asyncio`, `concurrent.futures`, `threading`).&lt;br /&gt;
* '''Action''': Describe the specific steps for refactoring or replacing each component.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''4. Progress Tracking:'''&lt;br /&gt;
&lt;br /&gt;
* '''Objective''': Implement a progress tracker for the migration of each component within the deliverable.&lt;br /&gt;
* '''Action''': Create a dedicated section in the checklist to track the status of each task (in progress, completed, awaiting testing, etc.).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''5. Testing and Validation:'''&lt;br /&gt;
&lt;br /&gt;
* '''Objective''': Ensure that the deliverable works correctly after the migration by running specific unit and functional tests.&lt;br /&gt;
* '''Action''': Document test results after each phase of the migration and validate the stability of the deliverable.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''6. Bug and Incident Tracking:'''&lt;br /&gt;
&lt;br /&gt;
* '''Objective''': Keep track of issues encountered during or after the migration and ensure they are resolved.&lt;br /&gt;
* '''Action''': Log bugs or incidents related to the migration, with a resolution status for each issue.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''7. Documentation of Changes:'''&lt;br /&gt;
&lt;br /&gt;
* '''Objective''': Update technical documentation to reflect the changes made during the migration.&lt;br /&gt;
* '''Action''': Provide a summary of modifications in the deliverable and describe the new concurrency management practices (e.g., adopting `asyncio`).&lt;br /&gt;
&lt;br /&gt;
=== Roles and Responsibilities ===&lt;br /&gt;
&lt;br /&gt;
To help teams to manage the organisational aspects of the migration, we propose the following roles and responsibilities. OpenStack is maintained by several companies and organisations, the following structure aims to provide a minimal template for teams organisation to ensure that everyone is on the same page and have a clear understanding on how we work.&lt;br /&gt;
&lt;br /&gt;
The Eventlet migration is complex, without a bit of structure your team might suffer of that migration. The future of your deliverables depends on it.&lt;br /&gt;
&lt;br /&gt;
'''We strongly encourage teams to adopt this structure.''' &lt;br /&gt;
&lt;br /&gt;
==== Technical Lead Role ====&lt;br /&gt;
&lt;br /&gt;
The technical lead will be responsible for:&lt;br /&gt;
&lt;br /&gt;
* '''Supervision:''' Monitoring the technical progress of the migration within the team.&lt;br /&gt;
* '''Support:''' Providing assistance to other developers and troubleshooting any technical issues related to the migration.&lt;br /&gt;
* '''Reporting:''' Submitting regular progress reports, listing impacted deliverables, patterns of Eventlet identified, and participating in monthly meetings with the team product manager.&lt;br /&gt;
* '''Technology Updates:''' Staying up-to-date on the latest technical news and updates related to the migration, and sharing this information with the team.&lt;br /&gt;
* '''Coordination:''' Working closely with the product manager to ensure that the team has the necessary resources to complete the migration.&lt;br /&gt;
* '''Communication:''' Acting as the main point of contact for questions or issues within the team regarding the migration process.&lt;br /&gt;
&lt;br /&gt;
==== Product Manager’s Role ====&lt;br /&gt;
&lt;br /&gt;
The product manager is responsible for coordinating and facilitating the Eventlet removal within their team. They ensure that the necessary resources and time are allocated for the transition and that progress is tracked regularly. Key Responsibilities:&lt;br /&gt;
&lt;br /&gt;
* '''Prioritization and Planning:''' Ensure the migration is prioritized in the team's roadmap and schedules. Set clear objectives and deadlines for each stage of the migration.&lt;br /&gt;
* '''Support for the Team:''' Provide the technical lead and the team with the necessary resources and time to complete the migration. Address any organizational or logistical obstacles that might slow down progress.&lt;br /&gt;
* '''Progress Monitoring:''' Review and monitor progress reports submitted by the technical lead. Ensure the team meets the deadlines for audits, migrations, and report submissions.&lt;br /&gt;
* '''Coordination:''' Collaborate with other product managers to ensure a smooth and coordinated transition across all teams. Facilitate knowledge sharing between teams to optimize the migration process.&lt;br /&gt;
* '''Communication:''' Attend monthly meetings with the technical lead to discuss progress, challenges, and any resource needs. Provide regular updates to stakeholders about the state of the migration.&lt;br /&gt;
&lt;br /&gt;
==== Responsibilities For Developers ====&lt;br /&gt;
&lt;br /&gt;
* '''Task 1''': Identify which Eventlet version is currently in use in your deliverables.You can simply inspect your requirements. You can also use beagle on all your deliverables, see the related sections.&lt;br /&gt;
* '''Task 2''': If the version is lower than 0.36.1, prepare the Eventlet requirement upgrade.&lt;br /&gt;
* '''Task 3''': Audit your code to identify all dependencies related to Eventlet within the next three weeks.&lt;br /&gt;
* '''Task 4''': Prepare a progress report that includes:&lt;br /&gt;
# A list of impacted deliverables;&lt;br /&gt;
# Eventlet patterns used within these deliverables (based on the patterns defined above).&lt;br /&gt;
* '''Task 5''': Join us at the PTG sessions and then lets discuss together about your use cases.&lt;br /&gt;
* '''Task 6''': Start migrating identified Eventlet components to available alternatives after the audit is complete. The target is to have this migration started within the current quarter.&lt;br /&gt;
&lt;br /&gt;
==== Responsibilities For Product Managers ====&lt;br /&gt;
&lt;br /&gt;
* '''Task 1''': Prioritize this migration in your team's roadmap. From easiest to most complex deliverables to migrate.&lt;br /&gt;
* '''Task 2''': Within the next two weeks, identify a technical lead (preferably a senior developer or principal senior developer) who will oversee the migration.&lt;br /&gt;
* '''Task 3''': Ensure that your teams have the necessary resources and time to complete the migration.&lt;br /&gt;
* '''Task 4''': Attend the coming PTG sessions and monthly follow-up meetings with the technical lead to discuss progress, blockers, and next steps.&lt;br /&gt;
&lt;br /&gt;
== Tracking Progress ==&lt;br /&gt;
&lt;br /&gt;
=== Proposed Key Performance Indicators (KPI) ===&lt;br /&gt;
&lt;br /&gt;
These KPIs aim to provide managers (product managers) with a way to track and measure progress, both from a team-based perspective and from a global overview.&lt;br /&gt;
&lt;br /&gt;
'''1. Eventlet Deactivation Rate in Deliverables:'''&lt;br /&gt;
&lt;br /&gt;
* '''Objective:''' Track the progress of the number of deliverables where Eventlet has been completely disabled and replaced.&lt;br /&gt;
* '''KPI:''' Percentage of deliverables where Eventlet is replaced by alternative solutions (`asyncio`, `awaitlet`, `concurrent.futures`, etc.).&lt;br /&gt;
* '''Calculation:''' (Number of migrated deliverables / Total number of identified deliverables) x 100.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''2. Reduction in the Number of `monkey_patch()` Occurrences:'''&lt;br /&gt;
&lt;br /&gt;
* '''Objective:''' Assess the gradual removal of calls to `eventlet.monkey_patch()`, which can cause unwanted behaviors.&lt;br /&gt;
* '''KPI:''' Number of `monkey_patch()` occurrences still present in the code.&lt;br /&gt;
* '''Calculation:''' Total number of `monkey_patch()` occurrences identified before and after each migration phase.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''3. Response Time and Performance Post-Migration:'''&lt;br /&gt;
&lt;br /&gt;
* '''Objective:''' Verify whether the performance of the deliverables improves or remains stable after migrating to `asyncio` or other solutions.&lt;br /&gt;
* '''KPI:''' Average response time of services post-migration (in ms).&lt;br /&gt;
* '''Calculation:''' Measure the response time before and after migration to check if performance improves.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''4. Percentage of Automated Tests Passed After Migration:'''&lt;br /&gt;
&lt;br /&gt;
* '''Objective:''' Ensure the stability and quality of the migrated code by verifying that unit and functional tests pass successfully.&lt;br /&gt;
* '''KPI:''' Percentage of automated tests passed after migration.&lt;br /&gt;
* '''Calculation:''' (Number of tests passed post-migration / Total number of tests executed) x 100.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''5. Number of Bugs or Incidents Post-Migration:'''&lt;br /&gt;
&lt;br /&gt;
* '''Objective:''' Track the stability of the project post-migration by monitoring the number of bugs or incidents related to the migration.&lt;br /&gt;
* '''KPI:''' Number of bugs or incidents identified related to the Eventlet migration.&lt;br /&gt;
* '''Calculation:''' Total number of bugs/incidents opened during and after migration.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''6. Adoption of New Python Versions:'''&lt;br /&gt;
&lt;br /&gt;
* '''Objective:''' Verify if the teams are able to adopt new Python versions in the migrated deliverables.&lt;br /&gt;
* '''KPI:''' Percentage of deliverables using a recent version of Python (e.g., Python 3.12).&lt;br /&gt;
* '''Calculation:''' (Number of deliverables using the latest Python version / Total number of deliverables) x 100.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''7. Meeting Project Migration Deadlines:'''&lt;br /&gt;
&lt;br /&gt;
* '''Objective:''' Ensure that the migration project adheres to the initial schedule.&lt;br /&gt;
* '''KPI:''' Number of deadlines met compared to the migration plan.&lt;br /&gt;
* '''Calculation:''' (Number of deliverables migrated within the deadline / Total number of planned deliverables) x 100.&lt;br /&gt;
&lt;br /&gt;
== Useful Links ==&lt;br /&gt;
&lt;br /&gt;
* [https://eventlet.readthedocs.io/en/latest/asyncio/asyncio.html Eventlet's official migration guide]&lt;br /&gt;
* [https://review.opendev.org/q/topic:%22eventlet-removal%22 The gerrit patches related to the migration]&lt;br /&gt;
* [https://awaitlet.sqlalchemy.org/en/latest/ The Awaitlet documentation]&lt;br /&gt;
* [https://governance.openstack.org/tc/goals/proposed/remove-eventlet.html The related community goal (governance/TC)]&lt;br /&gt;
* [https://lists.openstack.org/archives/list/openstack-discuss@lists.openstack.org/thread/YO5CZDVAJ6QSF734ALWSGNOQDDAIOXKI/#RZFUTBFTUCSHKVA6SOCWWIXEH3QJHMYT The origin of this whole story]&lt;br /&gt;
* [https://github.com/eventlet/eventlet/issues/824 Getting the Eventlet maintenance under our umbrella]&lt;br /&gt;
&lt;br /&gt;
== FAQ ==&lt;br /&gt;
&lt;br /&gt;
'''Is this initiative is an official initiative?'''&lt;br /&gt;
&lt;br /&gt;
Yes, this initiative is supported by the [https://governance.openstack.org/tc/ OpenStack TC]. This initiative is an [https://governance.openstack.org/tc/goals/proposed/remove-eventlet.html official OpenStack community goal]. This goal [https://review.opendev.org/c/openstack/governance/+/902585 has been accepted months ago] and [https://review.opendev.org/c/openstack/governance/+/931254 is now going to be a selected goal].&lt;br /&gt;
&lt;br /&gt;
'''what will happen to eventlet once the openstack migration is complete?'''&lt;br /&gt;
&lt;br /&gt;
Eventlet will be simply abandoned officially. No more maintenance will be given to the official Eventlet repository. The official Eventlet repository will be archived.&lt;br /&gt;
&lt;br /&gt;
== Map of the Impacted Deliverables ==&lt;br /&gt;
&lt;br /&gt;
Here is a curated list of the Openstack deliverables currently (as of 2024 Oct. 4th) using Eventlet and which must be migrated:&lt;br /&gt;
&lt;br /&gt;
* https://opendev.org/openstack/aodh/&lt;br /&gt;
* https://opendev.org/openstack/auto-scaling-sig/&lt;br /&gt;
* https://opendev.org/openstack/barbican/&lt;br /&gt;
* https://opendev.org/openstack/blazar/&lt;br /&gt;
* https://opendev.org/openstack/ceilometer/&lt;br /&gt;
* https://opendev.org/openstack/charm-manila/&lt;br /&gt;
* https://opendev.org/openstack/charm-manila-ganesha/&lt;br /&gt;
* https://opendev.org/openstack/cinder/&lt;br /&gt;
* https://opendev.org/openstack/cinder-specs/&lt;br /&gt;
* https://opendev.org/openstack/cloudkitty/&lt;br /&gt;
* https://opendev.org/openstack/cloudkitty-specs/&lt;br /&gt;
* https://opendev.org/openstack/cyborg/&lt;br /&gt;
* https://opendev.org/openstack/designate/&lt;br /&gt;
* https://opendev.org/openstack/designate-specs/&lt;br /&gt;
* https://opendev.org/openstack/devstack/&lt;br /&gt;
* https://opendev.org/openstack/election/&lt;br /&gt;
* https://opendev.org/openstack/etcd3gw/&lt;br /&gt;
* https://opendev.org/openstack/futurist/&lt;br /&gt;
* https://opendev.org/openstack/glance/&lt;br /&gt;
* https://opendev.org/openstack/glance-specs/&lt;br /&gt;
* https://opendev.org/openstack/glance_store/&lt;br /&gt;
* https://opendev.org/openstack/governance/&lt;br /&gt;
* https://opendev.org/openstack/hacking/&lt;br /&gt;
* https://opendev.org/openstack/heat/&lt;br /&gt;
* https://opendev.org/openstack/heat-specs/&lt;br /&gt;
* https://opendev.org/openstack/heat-templates/&lt;br /&gt;
* https://opendev.org/openstack/ironic/&lt;br /&gt;
* https://opendev.org/openstack/ironic-inspector/&lt;br /&gt;
* https://opendev.org/openstack/ironic-python-agent/&lt;br /&gt;
* https://opendev.org/openstack/ironic-specs/&lt;br /&gt;
* https://opendev.org/openstack/keystone/&lt;br /&gt;
* https://opendev.org/openstack/keystone-specs/&lt;br /&gt;
* https://opendev.org/openstack/keystonemiddleware/&lt;br /&gt;
* https://opendev.org/openstack/kolla/&lt;br /&gt;
* https://opendev.org/openstack/kolla-ansible/&lt;br /&gt;
* https://opendev.org/openstack/magnum/&lt;br /&gt;
* https://opendev.org/openstack/manila/&lt;br /&gt;
* https://opendev.org/openstack/masakari/&lt;br /&gt;
* https://opendev.org/openstack/masakari-monitors/&lt;br /&gt;
* https://opendev.org/openstack/mistral/&lt;br /&gt;
* https://opendev.org/openstack/mistral-extra/&lt;br /&gt;
* https://opendev.org/openstack/mistral-lib/&lt;br /&gt;
* https://opendev.org/openstack/monasca-agent/&lt;br /&gt;
* https://opendev.org/openstack/monasca-api/&lt;br /&gt;
* https://opendev.org/openstack/monasca-events-api/&lt;br /&gt;
* https://opendev.org/openstack/networking-baremetal/&lt;br /&gt;
* https://opendev.org/openstack/networking-generic-switch/&lt;br /&gt;
* https://opendev.org/openstack/networking-sfc/&lt;br /&gt;
* https://opendev.org/openstack/neutron/&lt;br /&gt;
* https://opendev.org/openstack/neutron-dynamic-routing/&lt;br /&gt;
* https://opendev.org/openstack/neutron-fwaas/&lt;br /&gt;
* https://opendev.org/openstack/neutron-lib/&lt;br /&gt;
* https://opendev.org/openstack/neutron-specs/&lt;br /&gt;
* https://opendev.org/openstack/neutron-tempest-plugin/&lt;br /&gt;
* https://opendev.org/openstack/neutron-vpnaas/&lt;br /&gt;
* https://opendev.org/openstack/nova/&lt;br /&gt;
* https://opendev.org/openstack/nova-specs/&lt;br /&gt;
* https://opendev.org/openstack/octavia/&lt;br /&gt;
* https://opendev.org/openstack/octavia-lib/&lt;br /&gt;
* https://opendev.org/openstack/octavia-tempest-plugin/&lt;br /&gt;
* https://opendev.org/openstack/openstack-ansible-ops/&lt;br /&gt;
* https://opendev.org/openstack/openstack-ansible-os_neutron/&lt;br /&gt;
* https://opendev.org/openstack/openstack-helm/&lt;br /&gt;
* https://opendev.org/openstack/openstack-manuals/&lt;br /&gt;
* https://opendev.org/openstack/os-brick/&lt;br /&gt;
* https://opendev.org/openstack/os-ken/&lt;br /&gt;
* https://opendev.org/openstack/os-vif/&lt;br /&gt;
* https://opendev.org/openstack/oslo-specs/&lt;br /&gt;
* https://opendev.org/openstack/oslo.cache/&lt;br /&gt;
* https://opendev.org/openstack/oslo.concurrency/&lt;br /&gt;
* https://opendev.org/openstack/oslo.config/&lt;br /&gt;
* https://opendev.org/openstack/oslo.db/&lt;br /&gt;
* https://opendev.org/openstack/oslo.log/&lt;br /&gt;
* https://opendev.org/openstack/oslo.messaging/&lt;br /&gt;
* https://opendev.org/openstack/oslo.privsep/&lt;br /&gt;
* https://opendev.org/openstack/oslo.reports/&lt;br /&gt;
* https://opendev.org/openstack/oslo.rootwrap/&lt;br /&gt;
* https://opendev.org/openstack/oslo.service/&lt;br /&gt;
* https://opendev.org/openstack/oslo.utils/&lt;br /&gt;
* https://opendev.org/openstack/oslo.versionedobjects/&lt;br /&gt;
* https://opendev.org/openstack/oslo.vmware/&lt;br /&gt;
* https://opendev.org/openstack/osops/&lt;br /&gt;
* https://opendev.org/openstack/ossa/&lt;br /&gt;
* https://opendev.org/openstack/ovn-bgp-agent/&lt;br /&gt;
* https://opendev.org/openstack/ovsdbapp/&lt;br /&gt;
* https://opendev.org/openstack/performance-docs/&lt;br /&gt;
* https://opendev.org/openstack/project-config/&lt;br /&gt;
* https://opendev.org/openstack/puppet-barbican/&lt;br /&gt;
* https://opendev.org/openstack/puppet-ceilometer/&lt;br /&gt;
* https://opendev.org/openstack/puppet-keystone/&lt;br /&gt;
* https://opendev.org/openstack/puppet-manila/&lt;br /&gt;
* https://opendev.org/openstack/puppet-neutron/&lt;br /&gt;
* https://opendev.org/openstack/puppet-openstacklib/&lt;br /&gt;
* https://opendev.org/openstack/python-cinderclient/&lt;br /&gt;
* https://opendev.org/openstack/python-glanceclient/&lt;br /&gt;
* https://opendev.org/openstack/python-ironic-inspector-client/&lt;br /&gt;
* https://opendev.org/openstack/python-ironicclient/&lt;br /&gt;
* https://opendev.org/openstack/python-keystoneclient/&lt;br /&gt;
* https://opendev.org/openstack/python-manilaclient/&lt;br /&gt;
* https://opendev.org/openstack/python-mistralclient/&lt;br /&gt;
* https://opendev.org/openstack/python-octaviaclient/&lt;br /&gt;
* https://opendev.org/openstack/python-swiftclient/&lt;br /&gt;
* https://opendev.org/openstack/python-troveclient/&lt;br /&gt;
* https://opendev.org/openstack/python-zaqarclient/&lt;br /&gt;
* https://opendev.org/openstack/rally/&lt;br /&gt;
* https://opendev.org/openstack/releases/&lt;br /&gt;
* https://opendev.org/openstack/requirements/&lt;br /&gt;
* https://opendev.org/openstack/rpm-packaging/&lt;br /&gt;
* https://opendev.org/openstack/security-doc/&lt;br /&gt;
* https://opendev.org/openstack/storlets/&lt;br /&gt;
* https://opendev.org/openstack/swift/&lt;br /&gt;
* https://opendev.org/openstack/swift-bench/&lt;br /&gt;
* https://opendev.org/openstack/tacker/&lt;br /&gt;
* https://opendev.org/openstack/taskflow/&lt;br /&gt;
* https://opendev.org/openstack/telemetry-specs/&lt;br /&gt;
* https://opendev.org/openstack/tooz/&lt;br /&gt;
* https://opendev.org/openstack/trove/&lt;br /&gt;
* https://opendev.org/openstack/upstream-institute-virtual-environment/&lt;br /&gt;
* https://opendev.org/openstack/venus/&lt;br /&gt;
* https://opendev.org/openstack/vitrage/&lt;br /&gt;
* https://opendev.org/openstack/watcher/&lt;br /&gt;
* https://opendev.org/openstack/zun/&lt;/div&gt;</summary>
		<author><name>Mike Bayer</name></author>	</entry>

	<entry>
		<id>https://wiki.openstack.org/w/index.php?title=Keystone_edge_architectures&amp;diff=163575</id>
		<title>Keystone edge architectures</title>
		<link rel="alternate" type="text/html" href="https://wiki.openstack.org/w/index.php?title=Keystone_edge_architectures&amp;diff=163575"/>
				<updated>2018-08-09T13:54:01Z</updated>
		
		<summary type="html">&lt;p&gt;Mike Bayer: /* Keystone database replication */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This page contains a summary of the Vancouver Forum discussions about the topic. Full notes of the discussion are in [https://etherpad.openstack.org/p/YVR-edge-keystone-brainstorming here]. The features and requirements for edge cloud infrastructure are described in [https://wiki.openstack.org/wiki/OpenStack_Edge_Discussions_Dublin_PTG OpenStack_Edge_Discussions_Dublin_PTG].&lt;br /&gt;
&lt;br /&gt;
= Concerns to be addressed =&lt;br /&gt;
== Usability ==&lt;br /&gt;
* Some data may be modified locally and must persist when changed&lt;br /&gt;
== Functionality ==&lt;br /&gt;
* There may be significant times with no connectivity and all functions (e.g. autoscaling) must continue to function&lt;br /&gt;
== Security ==&lt;br /&gt;
* Some data should NOT be synchornized to some sites, if the site is compromised, it should only hold relevant local data&lt;br /&gt;
* Centralized &amp;quot;view&amp;quot; to synch status of edge clouds would be needed for audit / compliance&lt;br /&gt;
* Centralized Management (of some sort) required.&lt;br /&gt;
== Scalability ==&lt;br /&gt;
* Edge sites may be very limited hardware (eg, may be single-node infrastructure)&lt;br /&gt;
&lt;br /&gt;
= Architecture options =&lt;br /&gt;
== Several keystone instances with federation and API synchronsation ==&lt;br /&gt;
Every edge cloud instance runs its own keystone instances. These keystone instances are federated where each keystone node is a &amp;quot;service provider&amp;quot; accepting and validating SAML assertions from a trusted identity provider (this is not the same as k2k federation). Each keystone maintains a mapping to control access depending on who needs what (this is going to be a lot of mappings, since there can be multiple for each deployment).&amp;lt;br/&amp;gt;&lt;br /&gt;
Basic flow: &amp;lt;br/&amp;gt;&lt;br /&gt;
# A user presents a SAML assertion to prove their idenitty&lt;br /&gt;
# The mapping processes their attributes, creates a shadow user, etc..&lt;br /&gt;
# From there the user creates an application credential with their shadow user&lt;br /&gt;
# A user generates tokens with their application credential to do things with that specific keystone deployment&amp;lt;br /&amp;gt;&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
=== More info ===&lt;br /&gt;
* [http://www.websequencediagrams.com/cgi-bin/cdraw?lz=dGl0bGUgRmVkZXJhdGVkIEF1dGhlbnRpY2F0aW9uIFNlcXVlbmNlCgoAGQpVc2VyLT5Ib3Jpem9uOmYAMglhAC0OYW5kIHVzZXIgc3BlY2lmaWVzIElkUCBpZAoANActPktleXN0b25lIFNlcnZpY2UgUHJvdmlkZXI6cGFzACYJAA0ZLS0-AIEIDjogMzAyIHJlZGlyZWN0IHRvIGlkZW50aXR5IHAAUwcAgTgQLT5JABsIAHQJAIE6EHJlcXVlc3QKABkRAGEgd2l0aCBTQU1MIGFzc2VydGlvbgBuEgCBaxpleGNoYW5nZQA9BmZvciB1bnNjb3BlZCB0b2tlbgoAgX4dAIJDGnZhbGlkYXQAUgYAgjMscmV0dXJuAHcPIGluIHdlYmZvcm0AgnIJaW5nIHRvIACEHQcAgmISAIQ1CAA7DwCDdiQgbGlzdCBhdmFpbGFibGUgcHJvamVjdHMAhAwbAIUjCSAAgToHACsTAIULCQCEQw9wcmVzZW50AF4JIHRvAIVRBQCFdxlzZWxlYwAnCSB0byAAgy4FIHRvAIVUJAAmBgCCUg90bwCBXggAgUEkAIMeBwB5CACEJA0&amp;amp;s=napkin Keystone federation seguence]&lt;br /&gt;
* [http://www.websequencediagrams.com/cgi-bin/cdraw?lz=dGl0bGUgRmVkZXJhdGVkIEF1dGhlbnRpY2F0aW9uIFNlcXVlbmNlClVzZXItPkNva2UgRGFzaGJvYXJkOigxKSB1c2VyIHdhbnRzIHRvIGYAQgcKABwOLS0-VXNlcjogKDIpIDMwMiBZb3UncmUgbm90IGxvZ2dlZCBpbiBwbGVhc2Ugc2VlIEtleXN0b24AcAgABwggKG1vZF9zaGliKTooMykgAAYIIGludGVyY2VwdHMgL3YzL09TLUZFREVSQVRJT04vaWRlbnRpdHlfcHJvdmlkZXJzL3tpZHBfaXB9L3Byb3RvY29sL3sAAghfaWR9L3dlYnNzbwoAZRMAgUEKNACBRQZ0aGUAgX8GdG8ABwVhY3R1YWwgSWRQAIIuB0kAeAcgUAB4BzogKDUpIFQAMAhsb2dpbnMgaW4gc3VjY2Vzc2Z1bGx5CgAmEQCCOwo2AII4DWFsbCBnb29kIHRvIGdvOyBoZXJlJ3Mgc29tZSB1Z2x5IFNBTUwAgi4bICg3KSBWYWxpZGF0ZXMAgUsFAC0FAIFpFACDAApTZXJ2aWNlAIFJDDgpIEdldCB1bnNjb3BlZCB0b2tlbgCCOgoAIhAAhAsKOSkgVQAoDSAAgyMHAIQSBWEgaGlkZGVuIGZvcm0gd2l0aCBhIEpTIFBPU1Qgb25sb2FkCgCFCBgwKSByZXR1cm4AgQcPIGluIHdlYgBNBXZpAEUKcmVkaXJlY3RpbmcgdG8gAIViDgCFRRAAgXIbKDExKSBsaXN0IGF2YWlsYWJsZSBwcm9qZWN0cwCBdR0AhlERMgCBOQkANxMAgQEQAIZeBzEzKSBwcmVzZW50AG0JIHRvAIcnBQCHRAYAhzoRICgxNCkgc2VsZWMAKwkgdG8gd29yayBvbgCBTi01KSAAg30FAIJkEHRvAIF8CACBVS42AIM8CQCBEQgAhFUM&amp;amp;s=napkin Keystone federation seguence]&lt;br /&gt;
* [https://docs.openstack.org/keystone/latest/advanced-topics/federation/federated_identity.html#auto-provisioning Federation auto provisioning]&lt;br /&gt;
* [https://docs.openstack.org/keystone/queens/user/application_credentials.html Application credentials]&lt;br /&gt;
&lt;br /&gt;
=== Questions ===&lt;br /&gt;
* Can a Keystone in VIO act as an Identity provider for K2K federation?&lt;br /&gt;
* Do we need further synchronisation of RBAC data on top of what we have in K2K federation?&lt;br /&gt;
&lt;br /&gt;
== Keystone database replication ==&lt;br /&gt;
Every edge cloud instance runs its own keystone instances. The database of these instances are syncronised and the data is syncronised between the edge cloud instances by the standard replication mechanism of the database. &lt;br /&gt;
=== Related materials ===&lt;br /&gt;
* [https://www.researchgate.net/publication/4284466_Enhancing_Edge_Computing_with_Database_Replication Enhancing Edge Computing with Database Replication] from 2007&lt;br /&gt;
* Galera Multi-master replication: [https://review.openstack.org/#/c/566448/ Region Support for Keystone with TripleO]  [https://github.com/zzzeek/stretch_cluster Ansible / TripleO proof of concept]&lt;br /&gt;
&lt;br /&gt;
== Distributed LDAP database as Keystone backend ==&lt;br /&gt;
Keystones in the edge cloud instances are using an LDAP database as a backend and the LDAP is configured to synchronize the data. &amp;lt;/br&amp;gt;&lt;br /&gt;
LDAP can be set up only as the auth realm and keystone RDB will provide identity service database. But Keystone can also handle both authentication and identity service which would imply there is no keystone relational database needed in this scenario. &lt;br /&gt;
=== Related materials ===&lt;br /&gt;
* [https://docs.openstack.org/keystone/latest/admin/identity-integrate-with-ldap.html Keystone documentation] about LDAP integration&lt;br /&gt;
&lt;br /&gt;
=== Questions ===&lt;br /&gt;
* Is it possible to store and synchronize all Keystone related data in this way?&lt;br /&gt;
&lt;br /&gt;
== Isolated Domains Per Edge and Localized Authority to Change data within isolated domain(s) ==&lt;br /&gt;
* &amp;quot;Spoke/Hub Model&amp;quot;-ish&lt;br /&gt;
* &amp;quot;Local DB for local &amp;quot;data&amp;quot; and pending writes&lt;br /&gt;
* Local data is send up to central hub once connectivity is restored&lt;br /&gt;
* Sites are authoritative for it's domain(s) no other &amp;quot;remote&amp;quot; domains are aurhoritative&lt;br /&gt;
* Central Hub is authoritative to write to any domain&lt;br /&gt;
* &amp;quot;Code/Service&amp;quot; written to handle bundling local changes and ship to central for distribution/synchonization down when/if connictivity is restored&lt;br /&gt;
** This must be allowed to do things that normal Keystone-API work cannot do (create project in the database with a specific UUID)&lt;br /&gt;
&lt;br /&gt;
== Keystone API Synchronization &amp;amp; Fernet Key Synchronization ==&lt;br /&gt;
* Every Edge Cloud instance runs its own keystone instance,&lt;br /&gt;
* Keystone resources are replicated from central site to edge clouds using API-based Synchronization, &lt;br /&gt;
** i.e. projects, users, groups, domains, ...&lt;br /&gt;
* Also supporting Fernet Key synchronization and management across Edge Clouds in order to enable Tokens created at any Edge / Central cloud being able to be used (and authenticated) in any other clouds. &lt;br /&gt;
&lt;br /&gt;
= Replicated data =&lt;br /&gt;
This is the list of data what is syncronised by StarlingX&lt;br /&gt;
* Keystone&lt;br /&gt;
** Users&lt;br /&gt;
** Projects&lt;br /&gt;
** Roles&lt;br /&gt;
** Assignments&lt;br /&gt;
** Groups (not yet implemented)&lt;br /&gt;
** Domains (not yet implemented)&lt;br /&gt;
** Fernet keys (not yet implemented)&lt;br /&gt;
* Nova&lt;br /&gt;
** Flavors&lt;br /&gt;
** Flavor extra specs&lt;br /&gt;
** Keypairs&lt;br /&gt;
** Quotas (should be managed dynamically in edge cloud infrastructure level I.e. a project that has a quota of 10 instances, can only create 10 instances across ALL Edge Clouds; NOT 10 instances per Edge Cloud.)&lt;br /&gt;
* Neutron&lt;br /&gt;
** Security Groups&lt;br /&gt;
** Security Group Rules&lt;br /&gt;
* Cinder&lt;br /&gt;
** Quotas&lt;/div&gt;</summary>
		<author><name>Mike Bayer</name></author>	</entry>

	<entry>
		<id>https://wiki.openstack.org/w/index.php?title=PyMySQL_evaluation&amp;diff=77212</id>
		<title>PyMySQL evaluation</title>
		<link rel="alternate" type="text/html" href="https://wiki.openstack.org/w/index.php?title=PyMySQL_evaluation&amp;diff=77212"/>
				<updated>2015-04-08T21:23:09Z</updated>
		
		<summary type="html">&lt;p&gt;Mike Bayer: /* MySQL DB Drivers Comparison */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= PyMySQL Evaluation =&lt;br /&gt;
&lt;br /&gt;
This page will capture issues related to Openstack moving to the [https://github.com/PyMySQL/PyMySQL/ PyMySQL driver] for MySQL/MariaDB dbapi access.&lt;br /&gt;
&lt;br /&gt;
== Rationale ==&lt;br /&gt;
&lt;br /&gt;
While the [https://github.com/farcepest/MySQLdb1 MySQL-Python] driver is a very mature and stable driver, it does not provide compatibility with either Python 3 or with [http://eventlet.net/doc/patching.html eventlet monkeypatching] (MySQL-Python  [https://github.com/eventlet/eventlet/blob/v0.16.1/eventlet/patcher.py#L225-L237 can be monkeypatched] with eventlet, but this feature disabled by default and missed in documentation). So OpenStack's usage of MySQL-Python, combined with the fact that concurrency is provided by eventlet, means that we currently have fully serialized database access within a single process, that is, only one database command occurs at a time within an Openstack Python process.&lt;br /&gt;
&lt;br /&gt;
== Drivers Under Consideration ==&lt;br /&gt;
&lt;br /&gt;
The two drivers that are known to provide eventlet-monkeypatch compatibility are MySQL-Connector and PyMySQL, as they are written in pure Python.   The two other well-known drivers for MySQL are MySQL-Python and OurSQL, both of which are written in C and offer no explicit support for eventlet or async.  They can reportedly be built to support gevent using a system called [https://github.com/CMGS/greenify Greenify], but the maturity and/or stabiltiy of this system is unknown.&lt;br /&gt;
&lt;br /&gt;
A comparison at http://www.diamondtin.com/2014/sqlalchemy-gevent-mysql-python-drivers-comparison/ illustrates performance metrics observed with all four of these drivers, with MySQL-python built both without and with the &amp;quot;greenify&amp;quot; system.&lt;br /&gt;
&lt;br /&gt;
A summary of the status of all four drivers is as follows.&lt;br /&gt;
&lt;br /&gt;
=== MySQL-Python ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/farcepest/MySQLdb1 MySQL-Python] is the most widely used Python driver.  However, it does not support Python 3, and it does not support async systems unless used within a thread pool.   While a system known as Greenify can potentially resolve the latter situation, the Python 3 limitation is still a deal-breaker - pull request with Python 3 support [https://github.com/farcepest/MySQLdb1/pull/62 was proposed] to MySQL-python in April 2014 and still not merged.&lt;br /&gt;
&lt;br /&gt;
=== mysqlclient ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/PyMySQL/mysqlclient-python mysqlclient] is a fork of MySQL-python which works in Python 3.3.  It is fully functional and passes all SQLAlchemy unit tests, and is also maintained by the same people that maintain PyMySQL.&lt;br /&gt;
&lt;br /&gt;
=== OurSQL ===&lt;br /&gt;
&lt;br /&gt;
[https://pythonhosted.org/oursql/ OurSQL] is an alternative MySQL driver that is also written in C.  It features fast performance and uses a different execution model than MySQL-python, using prepared statements.    OurSQL does have a Python 3 port hosted on [https://launchpad.net/oursql/py3k/ launchpad] but is not integrated with OurSQL itself nor is it published on Pypi.     Because it is written in C, it does not offer compatibility with async systems including eventlet-style monkeypatching.&lt;br /&gt;
&lt;br /&gt;
Besides the shaky Python 3 support and lack of eventlet support, OurSQL is also not maintained, seeing its last master commit on [https://code.launchpad.net/~jerub/oursql/master 2012-06-05].&lt;br /&gt;
&lt;br /&gt;
=== MySQL-Connector-Python ===&lt;br /&gt;
&lt;br /&gt;
[https://pypi.python.org/pypi/mysql-connector-python MySQL-connector-Python] is a pure Python MySQL driver, and is now released under the auspices of the MySQL project itself, as owned by Oracle.  MySQL-connector-Python supports both Python 3 as well as eventlet monkeypatching, and is well maintained.  It is endorsed by Oracle as the official Oracle-supported driver for MySQL, so to that extent, it is in most ways Openstack's first choice in driver.  However, Oracle refuses to publish MySQL-connector-Python on Pypi, which is critical to the Openstack infrastructure.  Repeated attempts to communicate with Oracle in order to resolve this issue have not made any progress.    Therefore, for this one unfortunate reason, MySQL-connector-Python will not have a place in the Openstack ecosystem unless this issue is resolved.&lt;br /&gt;
&lt;br /&gt;
=== PyMySQL ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/PyMySQL/PyMySQL PyMySQL] is a pure Python MySQL&lt;br /&gt;
driver, first written as a rough port of the MySQL-Python driver.&lt;br /&gt;
PyMySQL meets all of Openstack's criterion for a driver: it is fully&lt;br /&gt;
open source, hosted on Github, released on Pypi, is actively&lt;br /&gt;
maintained, is written in pure Python so is eventlet-monkeypatch&lt;br /&gt;
compatible, and is fully Python 3 compatible.&lt;br /&gt;
&lt;br /&gt;
As this document is named &amp;quot;PyMySQL Evaluation&amp;quot;, it should be apparent that&lt;br /&gt;
this is the driver Openstack is currently leaning towards; because it is the only&lt;br /&gt;
one that meets all criteria fully, it is already most likely the &amp;quot;winner&amp;quot;.  However, it does&lt;br /&gt;
have some minor code quality issues which hopefully can be addressed in some&lt;br /&gt;
way; the section below titled &amp;quot;PyMySQL Code Review&amp;quot; will summarize the&lt;br /&gt;
current state of the code.&lt;br /&gt;
&lt;br /&gt;
===MySQL DB Drivers Comparison===&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Project !! PyPi hosted !! Eventlet friendly !! Python 3 compatibility !! Maturity and/or stability  !! Comment&lt;br /&gt;
|-&lt;br /&gt;
| MySQL-Python || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: orange;&amp;quot; | Partial || style=&amp;quot;background-color: red;&amp;quot; | No  || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || Can be monkeypatched by eventlet, but only to enable thread pooling&lt;br /&gt;
|-&lt;br /&gt;
| mysqlclient || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: orange;&amp;quot; | Partial || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes  || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || Initial testing shows that this is a promising DBAPI if eventlet requirement can be dropped&lt;br /&gt;
|-&lt;br /&gt;
| OurSQL || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: red;&amp;quot; | No || style=&amp;quot;background-color: orange;&amp;quot; | Yes, but not Pypi hosted  || style=&amp;quot;background-color: red;&amp;quot; | No || Development halted fairly early on, and has not seen commits/releases in two years&lt;br /&gt;
|-&lt;br /&gt;
| MySQL-Connector-Python || style=&amp;quot;background-color: red;&amp;quot; | No || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes  || style=&amp;quot;background-color: orange;&amp;quot; | Yes, though the driver is still fairly new || The official Oracle-supported driver for MySQL&lt;br /&gt;
|-&lt;br /&gt;
| PyMySQL || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes  || style=&amp;quot;background-color: orange;&amp;quot; | Yes, however see notes below. || Actively maintained and popular.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== PyMySQL Code Review ==&lt;br /&gt;
&lt;br /&gt;
PyMySQL started roughly as a pure Python port of MySQL-Python.   The sections below will&lt;br /&gt;
detail various aspects of the driver and the status of the code.   Overall, the general&lt;br /&gt;
tone of PyMySQL is one of code that was written in the spirit of pragmatism and immediate&lt;br /&gt;
need; it is straightforward, free of serious antipatterns and performs&lt;br /&gt;
its task in a matter-of-fact way.  However,&lt;br /&gt;
it lacks polish and completeness in many areas that would normally be in better&lt;br /&gt;
condition for a more mature project.  These areas are all highly fixable, and it is&lt;br /&gt;
hoped that these items can be shared with the current PyMySQL developers, through&lt;br /&gt;
a combination of this document itself as well as new issues and pull requests&lt;br /&gt;
on the PyMySQL tracker, so that this fairly promising library can be pushed&lt;br /&gt;
to the next level.   This would require that the developers are amenable to&lt;br /&gt;
these improvements.&lt;br /&gt;
&lt;br /&gt;
=== Coding Style / Pep8 ===&lt;br /&gt;
&lt;br /&gt;
PyMySQL's coding style is pretty good.  The majority of it passes all&lt;br /&gt;
flake8 tests, save for a few dozen whitespace issues and&lt;br /&gt;
some long lines here and there within the core&lt;br /&gt;
modules, and moreso within the tests which appear to be a bit more&lt;br /&gt;
crufty than the core modules.  A full run of flake8 produces only 128&lt;br /&gt;
errors, and with a default run of autopep8 against E1,E2,E3, we can&lt;br /&gt;
fix all of them in one pass except for 38 remaining long line warnings.&lt;br /&gt;
The code layout is mostly idiomatic and reasonable.&lt;br /&gt;
&lt;br /&gt;
The codebase is probably short on docstrings.  Those methods that are&lt;br /&gt;
part of the public DBAPI do have docstrings, but tend to be extremely&lt;br /&gt;
terse (example, docstring for cursor.execute(): &amp;quot;'Execute a query'&amp;quot;).&lt;br /&gt;
Other methods that aren't public tend to not have any docstrings, but&lt;br /&gt;
the codebase is not at all hard to read and the purpose of methods and&lt;br /&gt;
functions is pretty easy to discern just by their names and implementations,&lt;br /&gt;
however those who seek inline documentation as a means to understand a codebase&lt;br /&gt;
will be disappointed.   The terseness of public API docstrings is more of&lt;br /&gt;
an issue in terms of the overall lack of documentation for PyMySQL,&lt;br /&gt;
see the section &amp;quot;Documentation&amp;quot; below.&lt;br /&gt;
&lt;br /&gt;
=== Test Coverage ===&lt;br /&gt;
&lt;br /&gt;
The test coverage for PyMySQL is definitely lacking.   From a visual&lt;br /&gt;
inspection alone, it's apparent that of the dozen or so test modules,&lt;br /&gt;
most of them have less then ten tests each and the modules are very short.&lt;br /&gt;
For a full test run, there's only 126 tests; contrast this to&lt;br /&gt;
MySQL-Connector-Python which has 531 tests.&lt;br /&gt;
&lt;br /&gt;
Additionally, all of the tests are live round-trip tests against a&lt;br /&gt;
real database.  There are no non-live unit tests of any kind,&lt;br /&gt;
leaving codepaths that are not easily exercised&lt;br /&gt;
on a generic MySQL database out in the cold.  This means that features designed&lt;br /&gt;
for particular versions of MySQL, particular datatypes, particular error&lt;br /&gt;
conditions, and particular kinds of message packets that aren't created for&lt;br /&gt;
real are simply not tested.&lt;br /&gt;
&lt;br /&gt;
The lack of tests is apparent when run with coverage.  The test suite itself&lt;br /&gt;
does not appear to have any signs that it is normally run with coverage&lt;br /&gt;
turned on, with no directives in tox.ini or similar.   With coverage,&lt;br /&gt;
the overall coverage is 85%, but the modules most lacking in coverage&lt;br /&gt;
are also the most critical to PyMySQL's core functionality,&lt;br /&gt;
connections.py, converters.py and cursors.py:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Name                                                              Stmts   Miss  Cover&lt;br /&gt;
-------------------------------------------------------------------------------------&lt;br /&gt;
pymysql/__init__                                                     51      3    94%&lt;br /&gt;
pymysql/_compat                                                      14      4    71%&lt;br /&gt;
pymysql/_socketio                                                    72     30    58%&lt;br /&gt;
pymysql/charset                                                     228      3    99%&lt;br /&gt;
pymysql/connections                                                 827    188    77%&lt;br /&gt;
pymysql/constants/CLIENT                                             18      0   100%&lt;br /&gt;
pymysql/constants/COMMAND                                            32      0   100%&lt;br /&gt;
pymysql/constants/ER                                                471      0   100%&lt;br /&gt;
pymysql/constants/FIELD_TYPE                                         29      0   100%&lt;br /&gt;
pymysql/constants/FLAG                                               15      0   100%&lt;br /&gt;
pymysql/constants/SERVER_STATUS                                      10      0   100%&lt;br /&gt;
pymysql/constants/__init__                                            0      0   100%&lt;br /&gt;
pymysql/converters                                                  148     51    66%&lt;br /&gt;
pymysql/cursors                                                     273     37    86%&lt;br /&gt;
pymysql/err                                                          40      1    98%&lt;br /&gt;
pymysql/tests/__init__                                               14      5    64%&lt;br /&gt;
pymysql/tests/base                                                   25      2    92%&lt;br /&gt;
pymysql/tests/test_DictCursor                                        73      2    97%&lt;br /&gt;
pymysql/tests/test_SSCursor                                          56      6    89%&lt;br /&gt;
pymysql/tests/test_basic                                            186      4    98%&lt;br /&gt;
pymysql/tests/test_connection                                        57      8    86%&lt;br /&gt;
pymysql/tests/test_example                                           17      2    88%&lt;br /&gt;
pymysql/tests/test_issues                                           300     48    84%&lt;br /&gt;
pymysql/tests/test_load_local                                        39      2    95%&lt;br /&gt;
pymysql/tests/test_nextset                                           43      4    91%&lt;br /&gt;
pymysql/tests/thirdparty/__init__                                     7      5    29%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/__init__                        6      2    67%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/capabilities                  196     23    88%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/dbapi20                       423    121    71%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_capabilities      73      6    92%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_dbapi20          100      2    98%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_nonstandard       57      3    95%&lt;br /&gt;
pymysql/times                                                        12      0   100%&lt;br /&gt;
pymysql/util                                                         14      7    50%&lt;br /&gt;
-------------------------------------------------------------------------------------&lt;br /&gt;
TOTAL                                                              3926    569    86%&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The lack of coverage in converters.py refers to functions related to handling&lt;br /&gt;
incoming bound values as well as outgoing result values.    Key data escaping&lt;br /&gt;
features such as escaping of dictionaries, sets, some unicode objects and&lt;br /&gt;
byte objects as well as many pathways into date and time-processing functions are not&lt;br /&gt;
covered.&lt;br /&gt;
&lt;br /&gt;
In connections.py, uncovered features include various SSL and&lt;br /&gt;
very legacy (e.g. version 3.23) MySQL features, but also some protocol parsing&lt;br /&gt;
features.&lt;br /&gt;
&lt;br /&gt;
In cursors.py, there is coverage for an elaborate and mostly undocumented&lt;br /&gt;
performance-related feature that rewrites an INSERT statement to have an&lt;br /&gt;
extended VALUES clause (_do_execute_many), however it fails to cover the very&lt;br /&gt;
likely case where the function will need to break the input set into&lt;br /&gt;
multiple chunks:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;gt;         for arg in args:&lt;br /&gt;
&amp;gt;             v = values % escape(arg, conn)&lt;br /&gt;
&amp;gt;             if isinstance(v, text_type):&lt;br /&gt;
&amp;gt;                 v = v.encode(encoding)&lt;br /&gt;
&amp;gt;             if len(sql) + len(v) + 1 &amp;gt; max_stmt_length:&lt;br /&gt;
!                 rows += self.execute(sql)&lt;br /&gt;
!                 sql = bytearray(prefix)&lt;br /&gt;
&amp;gt;             else:&lt;br /&gt;
&amp;gt;                 sql += b','&lt;br /&gt;
&amp;gt;             sql += v&lt;br /&gt;
&amp;gt;         rows += self.execute(sql)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The most prominent missing coverage in cursors.py seems to be&lt;br /&gt;
for its &amp;quot;scrollable cursor&amp;quot; support.&lt;br /&gt;
&lt;br /&gt;
Beyond the lack of completeness in testing, many of the tests themselves&lt;br /&gt;
are written in an expedient &amp;quot;one giant test&amp;quot; style where dozens of&lt;br /&gt;
assertions and individual behaviors are lumped into one big test&lt;br /&gt;
case (see test_SSCursor.py, test_basic.py-&amp;gt;test_datatypes for examples).&lt;br /&gt;
Test cases like these are difficult to work with when debugging&lt;br /&gt;
regressions as well as when tests for new features need to be added, and ideally&lt;br /&gt;
these should be broken out into clean single-feature tests with consistent&lt;br /&gt;
fixtures.&lt;br /&gt;
&lt;br /&gt;
There is also a suite called &amp;quot;test_issues&amp;quot;, which intends to&lt;br /&gt;
accumulate tests against specific issues that have been reported. This&lt;br /&gt;
isn't a bad idea, though it would be nice to see an effort made into&lt;br /&gt;
categorizing these tests into the actual features they are testing,&lt;br /&gt;
rather than a meaningless list of issue numbers, which could then form&lt;br /&gt;
as the basis for new suites centered around those areas of&lt;br /&gt;
functionality.  The issues reported should be used as the inspiration&lt;br /&gt;
for improvement of the test suite, rather than just another line-item&lt;br /&gt;
to be filed away.&lt;br /&gt;
&lt;br /&gt;
=== Library Documentation ===&lt;br /&gt;
&lt;br /&gt;
As the Python DBAPI is already a well-documented API, and the users of&lt;br /&gt;
PyMySQL are typically coming from MySQL-Python which is documented&lt;br /&gt;
to some degree and for which the use contract is widely known, it seems&lt;br /&gt;
unlikely that users of PyMySQL are eager for comprehensive documentation&lt;br /&gt;
specific to this library.&lt;br /&gt;
&lt;br /&gt;
That being said, PyMySQL does not appear to have any real library&lt;br /&gt;
documentation at all.  There's a README which&lt;br /&gt;
essentially refers to pep-249, an example.py in the root which shows a&lt;br /&gt;
very basic connection / round trip, and that's it. In&lt;br /&gt;
order to know anything about the API, specific parameters, optimization&lt;br /&gt;
strategies, behaviors, etc., one either has to go off of the MySQL-Python documentation and assume&lt;br /&gt;
PyMySQL also features the same parameter, or read the source code.&lt;br /&gt;
&lt;br /&gt;
PyMySQL probably hasn't had much urgent need for real documentation thus&lt;br /&gt;
far.  However, relying upon being a port of MySQL-Python is fast becoming&lt;br /&gt;
something that can no longer be relied upon, as MySQL-Python's development&lt;br /&gt;
is quite stalled, and PyMySQL should aspire to move into the future&lt;br /&gt;
of MySQL and Python with its own featureset and behaviors.  To that end, it would be a &lt;br /&gt;
great idea if it at least made the start&lt;br /&gt;
of a rudimentary Sphinx build including autodoc for module documentation&lt;br /&gt;
which could then be published up to RTD.   This would be very easy to get&lt;br /&gt;
started, and once present, new documentation sections can be added&lt;br /&gt;
iteratively.&lt;br /&gt;
&lt;br /&gt;
=== Architecture and Performance ===&lt;br /&gt;
&lt;br /&gt;
As stated before, PyMySQL has a really matter-of-fact and straightforward&lt;br /&gt;
implementation.   The code is very readable and it's easy to discern even&lt;br /&gt;
unusual features such as the INSERT..VALUES rewriting.  As far as the design,&lt;br /&gt;
there are lots of areas where performance suffers a bit at the Python level;&lt;br /&gt;
these areas could be greatly improved very easily with a little bit of&lt;br /&gt;
attention.&lt;br /&gt;
&lt;br /&gt;
As we are dealing with a driver that is communicating with a low level&lt;br /&gt;
protocol over a socket, it's critical that sending and receiving messages&lt;br /&gt;
is done as efficiently as possible.  The mechanisms used in PyMySQL rely&lt;br /&gt;
heavily on standard Python objects, without the use of __slots__; this means&lt;br /&gt;
that for individual messages on a socket, we are creating a heavyweight object,&lt;br /&gt;
calling its __init__() method and a creating a new __dict__ each time;&lt;br /&gt;
the most common object, OKPacketWrapper(), already wraps a MysqlPacket()&lt;br /&gt;
object, so we're doing two __init__()s in this common case.  OKPacketWrapper&lt;br /&gt;
also implements a `__getattr__` scheme&lt;br /&gt;
to proxy attribute access from itself to the internal MysqlPacket; this is a&lt;br /&gt;
an inefficient mode of operation in Python as it means a lookup must first&lt;br /&gt;
fail on the OKPacketWrapper before it invokes the `__getattr__()` method&lt;br /&gt;
and does another attribute lookup.   A reorg of the packet classes to produce&lt;br /&gt;
far fewer objects for messages as well as to use __slots__ would be&lt;br /&gt;
recommended.&lt;br /&gt;
&lt;br /&gt;
There are also at least some areas where function call overhead is unnecessarily&lt;br /&gt;
high due to the organization of calls.  For example, when calling upon&lt;br /&gt;
cursor.execute(), for parameters passed as a dictionary under Python 2K,&lt;br /&gt;
the isinstance() builtin function will be called on the incoming arguments&lt;br /&gt;
four times at a minimum, with two of each call against the exact same&lt;br /&gt;
criteria.  Additional logic will then call isinstance() on every key of&lt;br /&gt;
the dictionary as well as at least twice on every value within the dictionary,&lt;br /&gt;
in order to determine if the value needs to be converted to bytes as well&lt;br /&gt;
as for some top-level escaping logic checks; the escaping logic then&lt;br /&gt;
goes further with more checks for type() to match the object to a final&lt;br /&gt;
converter function.   While the flow of this logic does appear to be&lt;br /&gt;
reasonable and straightforward, it isn't ideal for a low-level database&lt;br /&gt;
driver library where speed is a high priority; the various wrappers and&lt;br /&gt;
processors can easily be reorganized here to reduce the number of isinstance()&lt;br /&gt;
and type() calls significantly, which would save a lot on function call&lt;br /&gt;
overhead.  It is likely there are many other areas in the codebase where&lt;br /&gt;
similar reductions in call counts can be made without too much disruption.&lt;br /&gt;
&lt;br /&gt;
In Appendix A, a short performance suite is illustrated.  This&lt;br /&gt;
is run against PyMySQL, Mysql-Connector-Python, and MySQL-Python.&lt;br /&gt;
PyMySQL does in fact demonstrate the&lt;br /&gt;
highest call-count behavior, logging 26,851,003 function calls&lt;br /&gt;
compared to MySQL-connector's 11,674,074 for the same operations.  To PyMySQL's&lt;br /&gt;
credit, it somehow completes the test 25-50% faster than MySQL-connector&lt;br /&gt;
despite having more than twice as many function calls; this probably indicates&lt;br /&gt;
the use of something very slow within MySQL-connector, such as catching&lt;br /&gt;
an exception within a tight loop.  However, the real bar we're comparing to&lt;br /&gt;
is MySQL-Python, which is written&lt;br /&gt;
in C and does the whole test with only 47,503 Python function calls; while&lt;br /&gt;
PyMySQL and MySQL-connector-Python compete for time within the 10-15 second&lt;br /&gt;
range without profiling, MySQL-Python completes the whole test in **1.03 seconds**, a 1000%&lt;br /&gt;
improvement over PyMySQL.   This speed difference is not PyMySQL's fault,&lt;br /&gt;
as pure Python is known to be extremely slow compared to C code.&lt;br /&gt;
However, there's a lot of easy wins in PyMySQL's codebase where the function&lt;br /&gt;
call count could probably be reduced dramatically to be even better than that&lt;br /&gt;
of MySQL-connector-Python; ideally, the driver would be able to run this&lt;br /&gt;
test possibly 5-6x slower than MySQL-Python rather than 10x.&lt;br /&gt;
&lt;br /&gt;
=== History Documentation ===&lt;br /&gt;
&lt;br /&gt;
PyMySQL includes a CHANGELOG file in its root which describes the bug fixes&lt;br /&gt;
and features of each release.  The construction of this file is definitely&lt;br /&gt;
within the &amp;quot;expedient&amp;quot; style noted for PyMySQL overall, and for it to be&lt;br /&gt;
genuinely useful for enterprise-level software, it would need to be&lt;br /&gt;
much more complete in its detail.    Each bug fix mentioned is only referred to&lt;br /&gt;
tersely without any link to a pull request or bug report.  While some can&lt;br /&gt;
be linked to a specific change through detective work&lt;br /&gt;
(&amp;quot;Cursor.fetchall() and .fetchmany now return list, not tuple&amp;quot;), it would&lt;br /&gt;
be very difficult for many&lt;br /&gt;
(&amp;quot;Fixed BIT type handling&amp;quot;, &amp;quot;Fixed GC errors&amp;quot; - what was the issue?)&lt;br /&gt;
and in many cases impossible (&amp;quot;Improved Py3k support&amp;quot;).  The file also&lt;br /&gt;
includes no dates so one has to look at the git tags to figure this&lt;br /&gt;
part out (fortunately, releases to seem to be git tagged at least).&lt;br /&gt;
For a user trying to ascertain if particular issues are&lt;br /&gt;
fixed or if behaviors have changed, this file is at best highly frustrating.&lt;br /&gt;
&lt;br /&gt;
A listing of release dates illustrates an average of two releases per year,&lt;br /&gt;
excluding 2012 when PyMySQL was seeking a new maintainer:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
2014-12-02 12:05:10 -0200  (tag: pymysql-0.6.3)&lt;br /&gt;
2014-04-21 14:28:47 -0300  (tag: pymysql-0.6.2)&lt;br /&gt;
2013-10-11 13:36:56 -0300  (tag: pymysql-0.6.1)&lt;br /&gt;
2013-10-04 14:25:14 -0300  (tag: pymysql-0.6)&lt;br /&gt;
2011-11-08 10:27:41 -0800  (tag: pymysql-0.5)&lt;br /&gt;
2010-12-27 17:28:16 +0000  (tag: pymysql-0.4)&lt;br /&gt;
2010-09-03 00:55:29 +0000  (tag: pymysql-0.3)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Community Involvement ===&lt;br /&gt;
&lt;br /&gt;
This is an area where PyMySQL seems to shine, and might even be an advantage&lt;br /&gt;
as compared to MySQL-Connector-Python, which remains under the cloak&lt;br /&gt;
of MySQL's vastly overstuffed bugtracker under the ownership of&lt;br /&gt;
the famously-opaque Oracle.   For a small, low key library without&lt;br /&gt;
any real documentation and only 126 tests, it features 33 contributors on&lt;br /&gt;
Github and is up to pull request #285, which is quite high.  Because&lt;br /&gt;
PyMySQL is small and straightforward, it does lend itself to contribution&lt;br /&gt;
and seems to do very well in this area.   The developers so far have been&lt;br /&gt;
responsive to issues and pull requests made by the author.&lt;br /&gt;
&lt;br /&gt;
Based on github contributor graphs it appears that PyMySQL had a lull&lt;br /&gt;
in 2012 but was picked up again afterwards in 2013.  As of 2012, its most&lt;br /&gt;
prominent developer Pete Hunt stopped development, and on July 25, 2013&lt;br /&gt;
added a note to the README that the project was looking for a new maintainer.&lt;br /&gt;
As of 2013 it was picked up by the current most prominent developers&lt;br /&gt;
Marcel Rodrigues (github username lecram) and INADA Naoki&lt;br /&gt;
(github username methane).&lt;br /&gt;
&lt;br /&gt;
Most issues that have been noted thus far within the areas of testing,&lt;br /&gt;
documentation and architecture are all very fixable; if the Openstack community&lt;br /&gt;
can provide resources to address some of these areas, and if the PyMySQL&lt;br /&gt;
developers are reasonably open to accepting new changes and producing new&lt;br /&gt;
releases in a timely fashion, the PyMySQL driver&lt;br /&gt;
could rapidly become a fully formidable contender in the MySQL driver field.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Appendix A - Performance Tests ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
import cProfile&lt;br /&gt;
import StringIO&lt;br /&gt;
import pstats&lt;br /&gt;
import contextlib&lt;br /&gt;
import time&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextlib.contextmanager&lt;br /&gt;
def profiled(dbapi):&lt;br /&gt;
    pr = cProfile.Profile()&lt;br /&gt;
    pr.enable()&lt;br /&gt;
    yield&lt;br /&gt;
    pr.disable()&lt;br /&gt;
    s = StringIO.StringIO()&lt;br /&gt;
    ps = pstats.Stats(pr, stream=s).sort_stats('cumulative')&lt;br /&gt;
    ps.print_stats()&lt;br /&gt;
    print &amp;quot;DBAPI:  %s&amp;quot; % dbapi&lt;br /&gt;
    print s.getvalue()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextlib.contextmanager&lt;br /&gt;
def timeonly(dbapi):&lt;br /&gt;
    now = time.time()&lt;br /&gt;
    try:&lt;br /&gt;
        yield&lt;br /&gt;
    finally:&lt;br /&gt;
        total = time.time() - now&lt;br /&gt;
        print &amp;quot;DBAPI:  %s, total seconds %f&amp;quot; % (dbapi, total)&lt;br /&gt;
&lt;br /&gt;
import MySQLdb&lt;br /&gt;
import pymysql&lt;br /&gt;
from mysql import connector as mysqlconnector&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def go(dbapi, ctx):&lt;br /&gt;
    conn = dbapi.connect(&lt;br /&gt;
        user='scott', passwd='tiger', host='localhost', db='test')&lt;br /&gt;
    cursor = conn.cursor()&lt;br /&gt;
    cursor.execute(&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
CREATE TABLE IF NOT EXISTS test_things (&lt;br /&gt;
    x INTEGER,&lt;br /&gt;
    y VARCHAR(255),&lt;br /&gt;
    z FLOAT&lt;br /&gt;
) engine=InnoDB&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;)&lt;br /&gt;
    cursor.execute(&amp;quot;DELETE from test_things&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    with ctx(dbapi):&lt;br /&gt;
        for row in xrange(1000):&lt;br /&gt;
            cursor.execute(&lt;br /&gt;
                &amp;quot;INSERT INTO test_things (x, y, z) &amp;quot;&lt;br /&gt;
                &amp;quot;VALUES (%(x)s, %(y)s, %(z)s)&amp;quot;,&lt;br /&gt;
                {&amp;quot;x&amp;quot;: row, &amp;quot;y&amp;quot;: &amp;quot;row number %d&amp;quot; % row,&lt;br /&gt;
                 &amp;quot;z&amp;quot;: row * 4.57292, }&lt;br /&gt;
            )&lt;br /&gt;
&lt;br /&gt;
        for x in xrange(500):&lt;br /&gt;
            cursor.execute(&lt;br /&gt;
                &amp;quot;select * from test_things&amp;quot;)&lt;br /&gt;
            for row in cursor.fetchall():&lt;br /&gt;
                row[0], row[1], row[2]&lt;br /&gt;
&lt;br /&gt;
    cursor.close()&lt;br /&gt;
    conn.close()&lt;br /&gt;
&lt;br /&gt;
go(pymysql, profiled)&lt;br /&gt;
go(mysqlconnector, profiled)&lt;br /&gt;
go(MySQLdb, profiled)&lt;br /&gt;
&lt;br /&gt;
go(pymysql, timeonly)&lt;br /&gt;
go(mysqlconnector, timeonly)&lt;br /&gt;
go(MySQLdb, timeonly)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DBAPI:  &amp;lt;module 'pymysql' from '/Users/classic/dev/PyMySQL/pymysql/__init__.pyc'&amp;gt;&lt;br /&gt;
         26851003 function calls in 15.924 seconds&lt;br /&gt;
&lt;br /&gt;
   Ordered by: cumulative time&lt;br /&gt;
&lt;br /&gt;
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;
     1500    0.010    0.000   15.923    0.011 /Users/classic/dev/PyMySQL/pymysql/cursors.py:105(execute)&lt;br /&gt;
     1500    0.003    0.000   15.883    0.011 /Users/classic/dev/PyMySQL/pymysql/cursors.py:269(_query)&lt;br /&gt;
     1500    0.004    0.000   15.859    0.011 /Users/classic/dev/PyMySQL/pymysql/connections.py:746(query)&lt;br /&gt;
     1500    0.003    0.000   15.819    0.011 /Users/classic/dev/PyMySQL/pymysql/connections.py:892(_read_query_result)&lt;br /&gt;
     1500    0.004    0.000   15.812    0.011 /Users/classic/dev/PyMySQL/pymysql/connections.py:1097(read)&lt;br /&gt;
      500    0.003    0.000   15.559    0.031 /Users/classic/dev/PyMySQL/pymysql/connections.py:1154(_read_result_packet)&lt;br /&gt;
      500    0.900    0.002   15.429    0.031 /Users/classic/dev/PyMySQL/pymysql/connections.py:1187(_read_rowdata_packet)&lt;br /&gt;
   500000    3.417    0.000    9.173    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1200(_read_row_from_packet)&lt;br /&gt;
  2012000    1.157    0.000    5.307    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:311(read_length_coded_string)&lt;br /&gt;
   504500    1.495    0.000    5.209    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:845(_read_packet)&lt;br /&gt;
  2014500    1.248    0.000    3.035    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:293(read_length_encoded_integer)&lt;br /&gt;
  3539500    2.439    0.000    2.750    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:245(read)&lt;br /&gt;
  1009000    0.779    0.000    2.307    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:870(_read_bytes)&lt;br /&gt;
  1009000    1.197    0.000    1.423    0.000 {method 'read' of '_io.BufferedReader' objects}&lt;br /&gt;
   508500    0.379    0.000    0.695    0.000 /Users/classic/dev/PyMySQL/pymysql/util.py:3(byte2int)&lt;br /&gt;
  5058000    0.465    0.000    0.465    0.000 {len}&lt;br /&gt;
   500500    0.222    0.000    0.455    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1146(_check_packet_is_eof)&lt;br /&gt;
   504500    0.205    0.000    0.375    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:342(check_error)&lt;br /&gt;
  2504000    0.275    0.000    0.275    0.000 {method 'append' of 'list' objects}&lt;br /&gt;
   501500    0.182    0.000    0.230    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:326(is_eof_packet)&lt;br /&gt;
     3500    0.009    0.000    0.226    0.000 /Users/classic/dev/PyMySQL/pymysql/_socketio.py:45(readinto)&lt;br /&gt;
     3500    0.207    0.000    0.207    0.000 {method 'recv_into' of '_socket.socket' objects}&lt;br /&gt;
  1022000    0.203    0.000    0.203    0.000 {_struct.unpack}&lt;br /&gt;
   526000    0.200    0.000    0.200    0.000 {isinstance}&lt;br /&gt;
  1501000    0.195    0.000    0.195    0.000 {method 'get' of 'dict' objects}&lt;br /&gt;
   504500    0.176    0.000    0.176    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:238(__init__)&lt;br /&gt;
   504500    0.170    0.000    0.170    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:339(is_error_packet)&lt;br /&gt;
  2014500    0.167    0.000    0.167    0.000 {ord}&lt;br /&gt;
      500    0.006    0.000    0.125    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1224(_get_descriptions)&lt;br /&gt;
     2000    0.003    0.000    0.087    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:361(__init__)&lt;br /&gt;
     2000    0.019    0.000    0.084    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:365(__parse_field_descriptor)&lt;br /&gt;
   500000    0.056    0.000    0.056    0.000 /Users/classic/dev/PyMySQL/pymysql/converters.py:301(through)&lt;br /&gt;
     1500    0.005    0.000    0.037    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:915(_execute_command)&lt;br /&gt;
     1500    0.003    0.000    0.027    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:886(_write_bytes)&lt;br /&gt;
     1500    0.002    0.000    0.024    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/socket.py:223(meth)&lt;br /&gt;
     1500    0.021    0.000    0.021    0.000 {method 'sendall' of '_socket.socket' objects}&lt;br /&gt;
     1500    0.020    0.000    0.020    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:276(_do_get_result)&lt;br /&gt;
     1000    0.003    0.000    0.019    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:95(_escape_args)&lt;br /&gt;
     4000    0.002    0.000    0.015    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:99(&amp;lt;genexpr&amp;gt;)&lt;br /&gt;
     1000    0.002    0.000    0.014    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1127(_read_ok_packet)&lt;br /&gt;
     3000    0.003    0.000    0.013    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:712(escape)&lt;br /&gt;
     1000    0.005    0.000    0.012    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:416(__init__)&lt;br /&gt;
     8000    0.011    0.000    0.011    0.000 {method 'decode' of 'str' objects}&lt;br /&gt;
     3500    0.003    0.000    0.007    0.000 {method '_checkReadable' of '_io._IOBase' objects}&lt;br /&gt;
     2000    0.003    0.000    0.006    0.000 /Users/classic/dev/PyMySQL/pymysql/converters.py:19(escape_item)&lt;br /&gt;
     4000    0.002    0.000    0.006    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:126(&amp;lt;genexpr&amp;gt;)&lt;br /&gt;
     7500    0.003    0.000    0.005    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:115(ensure_bytes)&lt;br /&gt;
     2000    0.003    0.000    0.005    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:386(description)&lt;br /&gt;
     5500    0.004    0.000    0.004    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:268(advance)&lt;br /&gt;
     1000    0.001    0.000    0.004    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:722(escape_string)&lt;br /&gt;
     3500    0.004    0.000    0.004    0.000 /Users/classic/dev/PyMySQL/pymysql/_socketio.py:87(readable)&lt;br /&gt;
      500    0.002    0.000    0.004    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:442(__init__)&lt;br /&gt;
     3500    0.003    0.000    0.003    0.000 {method '_checkClosed' of '_io._IOBase' objects}&lt;br /&gt;
     1000    0.001    0.000    0.003    0.000 /Users/classic/dev/PyMySQL/pymysql/converters.py:59(escape_string)&lt;br /&gt;
     1500    0.003    0.000    0.003    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1080(__init__)&lt;br /&gt;
     1500    0.001    0.000    0.003    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:92(nextset)&lt;br /&gt;
     4000    0.002    0.000    0.002    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:397(get_column_length)&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 /Users/classic/dev/PyMySQL/pymysql/converters.py:56(escape_float)&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 {method 'sub' of '_sre.SRE_Pattern' objects}&lt;br /&gt;
     3000    0.002    0.000    0.002    0.000 {_struct.pack}&lt;br /&gt;
     6000    0.002    0.000    0.002    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:62(_get_db)&lt;br /&gt;
     1500    0.001    0.000    0.002    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:80(_nextset)&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 /Users/classic/dev/PyMySQL/pymysql/util.py:9(int2byte)&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:244(fetchall)&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {min}&lt;br /&gt;
     2500    0.001    0.000    0.001    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:323(is_ok_packet)&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Users/classic/dev/PyMySQL/pymysql/converters.py:52(escape_int)&lt;br /&gt;
     2000    0.001    0.000    0.001    0.000 {method 'items' of 'dict' objects}&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {getattr}&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1093(__del__)&lt;br /&gt;
     1000    0.000    0.000    0.000    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:259(read_all)&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:67(_check_executed)&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:336(is_load_local_packet)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/contextlib.py:21(__exit__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 test.py:8(profiled)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DBAPI:  &amp;lt;module 'mysql.connector' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/__init__.pyc'&amp;gt;&lt;br /&gt;
         11674074 function calls in 19.049 seconds&lt;br /&gt;
&lt;br /&gt;
   Ordered by: cumulative time&lt;br /&gt;
&lt;br /&gt;
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;
      500    0.640    0.001   18.556    0.037 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:820(fetchall)&lt;br /&gt;
      500    0.003    0.000   11.196    0.022 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:655(get_rows)&lt;br /&gt;
      500    1.561    0.003   11.191    0.022 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:292(read_text_result)&lt;br /&gt;
   504500    3.303    0.000    7.306    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/network.py:219(recv_plain)&lt;br /&gt;
   500000    3.572    0.000    6.625    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:359(row_to_python)&lt;br /&gt;
  1009000    3.155    0.000    3.155    0.000 {method 'recv_into' of '_socket.socket' objects}&lt;br /&gt;
   500000    2.049    0.000    2.329    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:220(read_lc_string_list)&lt;br /&gt;
   500000    0.653    0.000    2.116    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:531(_STRING_to_python)&lt;br /&gt;
   502000    0.378    0.000    1.329    0.000 {method 'decode' of 'bytearray' objects}&lt;br /&gt;
   502000    0.221    0.000    0.951    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/utf_8.py:15(decode)&lt;br /&gt;
   502000    0.730    0.000    0.730    0.000 {_codecs.utf_8_decode}&lt;br /&gt;
   500000    0.514    0.000    0.514    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:413(_INT_to_python)&lt;br /&gt;
   509500    0.510    0.000    0.510    0.000 {_struct.unpack_from}&lt;br /&gt;
     1500    0.009    0.000    0.492    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:452(execute)&lt;br /&gt;
     1500    0.004    0.000    0.437    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:705(cmd_query)&lt;br /&gt;
   500000    0.367    0.000    0.367    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:405(_FLOAT_to_python)&lt;br /&gt;
   504500    0.343    0.000    0.343    0.000 {method 'extend' of 'bytearray' objects}&lt;br /&gt;
     1500    0.007    0.000    0.315    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:481(_send_cmd)&lt;br /&gt;
  2501500    0.275    0.000    0.275    0.000 {method 'append' of 'list' objects}&lt;br /&gt;
   500500    0.246    0.000    0.246    0.000 {method 'startswith' of 'bytearray' objects}&lt;br /&gt;
   524002    0.150    0.000    0.150    0.000 {isinstance}&lt;br /&gt;
     1500    0.011    0.000    0.118    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:613(_handle_result)&lt;br /&gt;
  1004500    0.112    0.000    0.112    0.000 {len}&lt;br /&gt;
   500000    0.092    0.000    0.092    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:204(description)&lt;br /&gt;
     1500    0.007    0.000    0.042    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/network.py:118(send_plain)&lt;br /&gt;
     2000    0.013    0.000    0.041    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:226(parse_column)&lt;br /&gt;
     1000    0.008    0.000    0.034    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:346(_process_params_dict)&lt;br /&gt;
     1500    0.002    0.000    0.029    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/socket.py:223(meth)&lt;br /&gt;
     1500    0.026    0.000    0.026    0.000 {method 'sendall' of '_socket.socket' objects}&lt;br /&gt;
     5000    0.007    0.000    0.014    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/catch23.py:79(struct_unpack)&lt;br /&gt;
     1000    0.001    0.000    0.013    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:562(_handle_ok)&lt;br /&gt;
    12000    0.011    0.000    0.011    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:167(read_lc_string)&lt;br /&gt;
     1000    0.004    0.000    0.010    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:199(parse_ok)&lt;br /&gt;
     3000    0.005    0.000    0.009    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:152(to_mysql)&lt;br /&gt;
     1500    0.003    0.000    0.007    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:118(make_command)&lt;br /&gt;
     3000    0.003    0.000    0.007    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:130(quote)&lt;br /&gt;
     1500    0.005    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/network.py:53(_prepare_packets)&lt;br /&gt;
     1500    0.003    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:406(_handle_result)&lt;br /&gt;
     3000    0.003    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:102(escape)&lt;br /&gt;
     1000    0.002    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:252(parse_eof)&lt;br /&gt;
      500    0.001    0.000    0.005    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:579(_handle_eof)&lt;br /&gt;
     2000    0.003    0.000    0.005    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:545(_handle_server_status)&lt;br /&gt;
     1500    0.003    0.000    0.004    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:53(int1store)&lt;br /&gt;
     3000    0.003    0.000    0.003    0.000 {method 'encode' of 'str' objects}&lt;br /&gt;
     9001    0.003    0.000    0.003    0.000 {method 'replace' of 'str' objects}&lt;br /&gt;
     1500    0.003    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:295(_reset_result)&lt;br /&gt;
     4500    0.003    0.000    0.003    0.000 {_struct.pack}&lt;br /&gt;
     2500    0.003    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:296(read_lc_int)&lt;br /&gt;
      500    0.002    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:757(_handle_eof)&lt;br /&gt;
     6027    0.003    0.000    0.003    0.000 {method 'format' of 'str' objects}&lt;br /&gt;
     3000    0.002    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:1221(_set_unread_result)&lt;br /&gt;
      500    0.001    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:219(parse_column_count)&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 {repr}&lt;br /&gt;
     4527    0.002    0.000    0.002    0.000 {getattr}&lt;br /&gt;
     4000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/constants.py:34(flag_is_set)&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:383(_handle_noresultset)&lt;br /&gt;
     4000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:1233(_get_unread_result)&lt;br /&gt;
     2001    0.001    0.000    0.001    0.000 {method 'items' of 'dict' objects}&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:307(_have_unread_result)&lt;br /&gt;
     3000    0.001    0.000    0.001    0.000 {method 'lower' of 'str' objects}&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 {range}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:173(_str_to_mysql)&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:169(_float_to_mysql)&lt;br /&gt;
     1000    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:161(_int_to_mysql)&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:1313(_get_getwarnings)&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:200(reset)&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:397(_handle_resultset)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/__init__.py:71(search_function)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/ascii.py:41(getregentry)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/contextlib.py:21(__exit__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/__init__.py:49(normalize_encoding)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 test.py:8(profiled)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/codecs.py:77(__new__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {__import__}&lt;br /&gt;
        3    0.000    0.000    0.000    0.000 {method 'get' of 'dict' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'split' of 'str' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {built-in method __new__ of type object at 0x1001534e8}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'translate' of 'str' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'join' of 'str' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {hasattr}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DBAPI:  &amp;lt;module 'MySQLdb' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/__init__.pyc'&amp;gt;&lt;br /&gt;
         47503 function calls in 0.918 seconds&lt;br /&gt;
&lt;br /&gt;
   Ordered by: cumulative time&lt;br /&gt;
&lt;br /&gt;
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;
     1500    0.009    0.000    0.917    0.001 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:164(execute)&lt;br /&gt;
     1500    0.002    0.000    0.890    0.001 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:353(_query)&lt;br /&gt;
     1500    0.004    0.000    0.498    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:315(_do_query)&lt;br /&gt;
     1500    0.016    0.000    0.391    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:358(_post_get_result)&lt;br /&gt;
     1500    0.001    0.000    0.375    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:324(_fetch_row)&lt;br /&gt;
      500    0.374    0.001    0.374    0.001 {built-in method fetch_row}&lt;br /&gt;
     1500    0.007    0.000    0.362    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:142(_do_get_result)&lt;br /&gt;
     1500    0.002    0.000    0.351    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:351(_get_result)&lt;br /&gt;
     1500    0.349    0.000    0.349    0.000 {method 'store_result' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.131    0.000    0.131    0.000 {method 'query' of '_mysql.connection' objects}&lt;br /&gt;
     4000    0.002    0.000    0.010    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:184(&amp;lt;genexpr&amp;gt;)&lt;br /&gt;
     3000    0.002    0.000    0.008    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/connections.py:267(literal)&lt;br /&gt;
     1500    0.007    0.000    0.007    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:107(_warning_check)&lt;br /&gt;
     3000    0.002    0.000    0.006    0.000 {method 'escape' of '_mysql.connection' objects}&lt;br /&gt;
     6000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:159(_get_db)&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/converters.py:81(Float2Str)&lt;br /&gt;
     2500    0.001    0.000    0.001    0.000 {isinstance}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/connections.py:202(string_literal)&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:380(fetchall)&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 {built-in method describe}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 {method 'string_literal' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {method 'insert_id' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {method 'affected_rows' of '_mysql.connection' objects}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/converters.py:69(Thing2Str)&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 {method 'warning_count' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 {method 'info' of '_mysql.connection' objects}&lt;br /&gt;
     1000    0.000    0.000    0.000    0.000 {method 'iteritems' of 'dict' objects}&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:103(_check_executed)&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 {len}&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 {built-in method field_flags}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/contextlib.py:21(__exit__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 test.py:8(profiled)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DBAPI:  &amp;lt;module 'pymysql' from '/Users/classic/dev/PyMySQL/pymysql/__init__.pyc'&amp;gt;, total seconds 10.046012&lt;br /&gt;
DBAPI:  &amp;lt;module 'mysql.connector' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/__init__.pyc'&amp;gt;, total seconds 15.403260&lt;br /&gt;
DBAPI:  &amp;lt;module 'MySQLdb' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/__init__.pyc'&amp;gt;, total seconds 1.028737&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mike Bayer</name></author>	</entry>

	<entry>
		<id>https://wiki.openstack.org/w/index.php?title=PyMySQL_evaluation&amp;diff=77211</id>
		<title>PyMySQL evaluation</title>
		<link rel="alternate" type="text/html" href="https://wiki.openstack.org/w/index.php?title=PyMySQL_evaluation&amp;diff=77211"/>
				<updated>2015-04-08T21:22:27Z</updated>
		
		<summary type="html">&lt;p&gt;Mike Bayer: /* mysqlclient */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= PyMySQL Evaluation =&lt;br /&gt;
&lt;br /&gt;
This page will capture issues related to Openstack moving to the [https://github.com/PyMySQL/PyMySQL/ PyMySQL driver] for MySQL/MariaDB dbapi access.&lt;br /&gt;
&lt;br /&gt;
== Rationale ==&lt;br /&gt;
&lt;br /&gt;
While the [https://github.com/farcepest/MySQLdb1 MySQL-Python] driver is a very mature and stable driver, it does not provide compatibility with either Python 3 or with [http://eventlet.net/doc/patching.html eventlet monkeypatching] (MySQL-Python  [https://github.com/eventlet/eventlet/blob/v0.16.1/eventlet/patcher.py#L225-L237 can be monkeypatched] with eventlet, but this feature disabled by default and missed in documentation). So OpenStack's usage of MySQL-Python, combined with the fact that concurrency is provided by eventlet, means that we currently have fully serialized database access within a single process, that is, only one database command occurs at a time within an Openstack Python process.&lt;br /&gt;
&lt;br /&gt;
== Drivers Under Consideration ==&lt;br /&gt;
&lt;br /&gt;
The two drivers that are known to provide eventlet-monkeypatch compatibility are MySQL-Connector and PyMySQL, as they are written in pure Python.   The two other well-known drivers for MySQL are MySQL-Python and OurSQL, both of which are written in C and offer no explicit support for eventlet or async.  They can reportedly be built to support gevent using a system called [https://github.com/CMGS/greenify Greenify], but the maturity and/or stabiltiy of this system is unknown.&lt;br /&gt;
&lt;br /&gt;
A comparison at http://www.diamondtin.com/2014/sqlalchemy-gevent-mysql-python-drivers-comparison/ illustrates performance metrics observed with all four of these drivers, with MySQL-python built both without and with the &amp;quot;greenify&amp;quot; system.&lt;br /&gt;
&lt;br /&gt;
A summary of the status of all four drivers is as follows.&lt;br /&gt;
&lt;br /&gt;
=== MySQL-Python ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/farcepest/MySQLdb1 MySQL-Python] is the most widely used Python driver.  However, it does not support Python 3, and it does not support async systems unless used within a thread pool.   While a system known as Greenify can potentially resolve the latter situation, the Python 3 limitation is still a deal-breaker - pull request with Python 3 support [https://github.com/farcepest/MySQLdb1/pull/62 was proposed] to MySQL-python in April 2014 and still not merged.&lt;br /&gt;
&lt;br /&gt;
=== mysqlclient ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/PyMySQL/mysqlclient-python mysqlclient] is a fork of MySQL-python which works in Python 3.3.  It is fully functional and passes all SQLAlchemy unit tests, and is also maintained by the same people that maintain PyMySQL.&lt;br /&gt;
&lt;br /&gt;
=== OurSQL ===&lt;br /&gt;
&lt;br /&gt;
[https://pythonhosted.org/oursql/ OurSQL] is an alternative MySQL driver that is also written in C.  It features fast performance and uses a different execution model than MySQL-python, using prepared statements.    OurSQL does have a Python 3 port hosted on [https://launchpad.net/oursql/py3k/ launchpad] but is not integrated with OurSQL itself nor is it published on Pypi.     Because it is written in C, it does not offer compatibility with async systems including eventlet-style monkeypatching.&lt;br /&gt;
&lt;br /&gt;
Besides the shaky Python 3 support and lack of eventlet support, OurSQL is also not maintained, seeing its last master commit on [https://code.launchpad.net/~jerub/oursql/master 2012-06-05].&lt;br /&gt;
&lt;br /&gt;
=== MySQL-Connector-Python ===&lt;br /&gt;
&lt;br /&gt;
[https://pypi.python.org/pypi/mysql-connector-python MySQL-connector-Python] is a pure Python MySQL driver, and is now released under the auspices of the MySQL project itself, as owned by Oracle.  MySQL-connector-Python supports both Python 3 as well as eventlet monkeypatching, and is well maintained.  It is endorsed by Oracle as the official Oracle-supported driver for MySQL, so to that extent, it is in most ways Openstack's first choice in driver.  However, Oracle refuses to publish MySQL-connector-Python on Pypi, which is critical to the Openstack infrastructure.  Repeated attempts to communicate with Oracle in order to resolve this issue have not made any progress.    Therefore, for this one unfortunate reason, MySQL-connector-Python will not have a place in the Openstack ecosystem unless this issue is resolved.&lt;br /&gt;
&lt;br /&gt;
=== PyMySQL ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/PyMySQL/PyMySQL PyMySQL] is a pure Python MySQL&lt;br /&gt;
driver, first written as a rough port of the MySQL-Python driver.&lt;br /&gt;
PyMySQL meets all of Openstack's criterion for a driver: it is fully&lt;br /&gt;
open source, hosted on Github, released on Pypi, is actively&lt;br /&gt;
maintained, is written in pure Python so is eventlet-monkeypatch&lt;br /&gt;
compatible, and is fully Python 3 compatible.&lt;br /&gt;
&lt;br /&gt;
As this document is named &amp;quot;PyMySQL Evaluation&amp;quot;, it should be apparent that&lt;br /&gt;
this is the driver Openstack is currently leaning towards; because it is the only&lt;br /&gt;
one that meets all criteria fully, it is already most likely the &amp;quot;winner&amp;quot;.  However, it does&lt;br /&gt;
have some minor code quality issues which hopefully can be addressed in some&lt;br /&gt;
way; the section below titled &amp;quot;PyMySQL Code Review&amp;quot; will summarize the&lt;br /&gt;
current state of the code.&lt;br /&gt;
&lt;br /&gt;
===MySQL DB Drivers Comparison===&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Project !! PyPi hosted !! Eventlet friendly !! Python 3 compatibility !! Maturity and/or stability  !! Comment&lt;br /&gt;
|-&lt;br /&gt;
| MySQL-Python || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: orange;&amp;quot; | Partial || style=&amp;quot;background-color: red;&amp;quot; | No  || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || Can be monkeypatched by eventlet, but only to enable thread pooling&lt;br /&gt;
|-&lt;br /&gt;
| mysqlclient || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: orange;&amp;quot; | Partial || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes  || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || MySQL-python fork, so should be similar to it&lt;br /&gt;
|-&lt;br /&gt;
| OurSQL || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: red;&amp;quot; | No || style=&amp;quot;background-color: orange;&amp;quot; | Yes, but not Pypi hosted  || style=&amp;quot;background-color: red;&amp;quot; | No || Development halted fairly early on, and has not seen commits/releases in two years&lt;br /&gt;
|-&lt;br /&gt;
| MySQL-Connector-Python || style=&amp;quot;background-color: red;&amp;quot; | No || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes  || style=&amp;quot;background-color: orange;&amp;quot; | Yes, though the driver is still fairly new || The official Oracle-supported driver for MySQL&lt;br /&gt;
|-&lt;br /&gt;
| PyMySQL || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes  || style=&amp;quot;background-color: orange;&amp;quot; | Yes, however see notes below. || Actively maintained and popular.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== PyMySQL Code Review ==&lt;br /&gt;
&lt;br /&gt;
PyMySQL started roughly as a pure Python port of MySQL-Python.   The sections below will&lt;br /&gt;
detail various aspects of the driver and the status of the code.   Overall, the general&lt;br /&gt;
tone of PyMySQL is one of code that was written in the spirit of pragmatism and immediate&lt;br /&gt;
need; it is straightforward, free of serious antipatterns and performs&lt;br /&gt;
its task in a matter-of-fact way.  However,&lt;br /&gt;
it lacks polish and completeness in many areas that would normally be in better&lt;br /&gt;
condition for a more mature project.  These areas are all highly fixable, and it is&lt;br /&gt;
hoped that these items can be shared with the current PyMySQL developers, through&lt;br /&gt;
a combination of this document itself as well as new issues and pull requests&lt;br /&gt;
on the PyMySQL tracker, so that this fairly promising library can be pushed&lt;br /&gt;
to the next level.   This would require that the developers are amenable to&lt;br /&gt;
these improvements.&lt;br /&gt;
&lt;br /&gt;
=== Coding Style / Pep8 ===&lt;br /&gt;
&lt;br /&gt;
PyMySQL's coding style is pretty good.  The majority of it passes all&lt;br /&gt;
flake8 tests, save for a few dozen whitespace issues and&lt;br /&gt;
some long lines here and there within the core&lt;br /&gt;
modules, and moreso within the tests which appear to be a bit more&lt;br /&gt;
crufty than the core modules.  A full run of flake8 produces only 128&lt;br /&gt;
errors, and with a default run of autopep8 against E1,E2,E3, we can&lt;br /&gt;
fix all of them in one pass except for 38 remaining long line warnings.&lt;br /&gt;
The code layout is mostly idiomatic and reasonable.&lt;br /&gt;
&lt;br /&gt;
The codebase is probably short on docstrings.  Those methods that are&lt;br /&gt;
part of the public DBAPI do have docstrings, but tend to be extremely&lt;br /&gt;
terse (example, docstring for cursor.execute(): &amp;quot;'Execute a query'&amp;quot;).&lt;br /&gt;
Other methods that aren't public tend to not have any docstrings, but&lt;br /&gt;
the codebase is not at all hard to read and the purpose of methods and&lt;br /&gt;
functions is pretty easy to discern just by their names and implementations,&lt;br /&gt;
however those who seek inline documentation as a means to understand a codebase&lt;br /&gt;
will be disappointed.   The terseness of public API docstrings is more of&lt;br /&gt;
an issue in terms of the overall lack of documentation for PyMySQL,&lt;br /&gt;
see the section &amp;quot;Documentation&amp;quot; below.&lt;br /&gt;
&lt;br /&gt;
=== Test Coverage ===&lt;br /&gt;
&lt;br /&gt;
The test coverage for PyMySQL is definitely lacking.   From a visual&lt;br /&gt;
inspection alone, it's apparent that of the dozen or so test modules,&lt;br /&gt;
most of them have less then ten tests each and the modules are very short.&lt;br /&gt;
For a full test run, there's only 126 tests; contrast this to&lt;br /&gt;
MySQL-Connector-Python which has 531 tests.&lt;br /&gt;
&lt;br /&gt;
Additionally, all of the tests are live round-trip tests against a&lt;br /&gt;
real database.  There are no non-live unit tests of any kind,&lt;br /&gt;
leaving codepaths that are not easily exercised&lt;br /&gt;
on a generic MySQL database out in the cold.  This means that features designed&lt;br /&gt;
for particular versions of MySQL, particular datatypes, particular error&lt;br /&gt;
conditions, and particular kinds of message packets that aren't created for&lt;br /&gt;
real are simply not tested.&lt;br /&gt;
&lt;br /&gt;
The lack of tests is apparent when run with coverage.  The test suite itself&lt;br /&gt;
does not appear to have any signs that it is normally run with coverage&lt;br /&gt;
turned on, with no directives in tox.ini or similar.   With coverage,&lt;br /&gt;
the overall coverage is 85%, but the modules most lacking in coverage&lt;br /&gt;
are also the most critical to PyMySQL's core functionality,&lt;br /&gt;
connections.py, converters.py and cursors.py:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Name                                                              Stmts   Miss  Cover&lt;br /&gt;
-------------------------------------------------------------------------------------&lt;br /&gt;
pymysql/__init__                                                     51      3    94%&lt;br /&gt;
pymysql/_compat                                                      14      4    71%&lt;br /&gt;
pymysql/_socketio                                                    72     30    58%&lt;br /&gt;
pymysql/charset                                                     228      3    99%&lt;br /&gt;
pymysql/connections                                                 827    188    77%&lt;br /&gt;
pymysql/constants/CLIENT                                             18      0   100%&lt;br /&gt;
pymysql/constants/COMMAND                                            32      0   100%&lt;br /&gt;
pymysql/constants/ER                                                471      0   100%&lt;br /&gt;
pymysql/constants/FIELD_TYPE                                         29      0   100%&lt;br /&gt;
pymysql/constants/FLAG                                               15      0   100%&lt;br /&gt;
pymysql/constants/SERVER_STATUS                                      10      0   100%&lt;br /&gt;
pymysql/constants/__init__                                            0      0   100%&lt;br /&gt;
pymysql/converters                                                  148     51    66%&lt;br /&gt;
pymysql/cursors                                                     273     37    86%&lt;br /&gt;
pymysql/err                                                          40      1    98%&lt;br /&gt;
pymysql/tests/__init__                                               14      5    64%&lt;br /&gt;
pymysql/tests/base                                                   25      2    92%&lt;br /&gt;
pymysql/tests/test_DictCursor                                        73      2    97%&lt;br /&gt;
pymysql/tests/test_SSCursor                                          56      6    89%&lt;br /&gt;
pymysql/tests/test_basic                                            186      4    98%&lt;br /&gt;
pymysql/tests/test_connection                                        57      8    86%&lt;br /&gt;
pymysql/tests/test_example                                           17      2    88%&lt;br /&gt;
pymysql/tests/test_issues                                           300     48    84%&lt;br /&gt;
pymysql/tests/test_load_local                                        39      2    95%&lt;br /&gt;
pymysql/tests/test_nextset                                           43      4    91%&lt;br /&gt;
pymysql/tests/thirdparty/__init__                                     7      5    29%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/__init__                        6      2    67%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/capabilities                  196     23    88%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/dbapi20                       423    121    71%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_capabilities      73      6    92%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_dbapi20          100      2    98%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_nonstandard       57      3    95%&lt;br /&gt;
pymysql/times                                                        12      0   100%&lt;br /&gt;
pymysql/util                                                         14      7    50%&lt;br /&gt;
-------------------------------------------------------------------------------------&lt;br /&gt;
TOTAL                                                              3926    569    86%&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The lack of coverage in converters.py refers to functions related to handling&lt;br /&gt;
incoming bound values as well as outgoing result values.    Key data escaping&lt;br /&gt;
features such as escaping of dictionaries, sets, some unicode objects and&lt;br /&gt;
byte objects as well as many pathways into date and time-processing functions are not&lt;br /&gt;
covered.&lt;br /&gt;
&lt;br /&gt;
In connections.py, uncovered features include various SSL and&lt;br /&gt;
very legacy (e.g. version 3.23) MySQL features, but also some protocol parsing&lt;br /&gt;
features.&lt;br /&gt;
&lt;br /&gt;
In cursors.py, there is coverage for an elaborate and mostly undocumented&lt;br /&gt;
performance-related feature that rewrites an INSERT statement to have an&lt;br /&gt;
extended VALUES clause (_do_execute_many), however it fails to cover the very&lt;br /&gt;
likely case where the function will need to break the input set into&lt;br /&gt;
multiple chunks:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;gt;         for arg in args:&lt;br /&gt;
&amp;gt;             v = values % escape(arg, conn)&lt;br /&gt;
&amp;gt;             if isinstance(v, text_type):&lt;br /&gt;
&amp;gt;                 v = v.encode(encoding)&lt;br /&gt;
&amp;gt;             if len(sql) + len(v) + 1 &amp;gt; max_stmt_length:&lt;br /&gt;
!                 rows += self.execute(sql)&lt;br /&gt;
!                 sql = bytearray(prefix)&lt;br /&gt;
&amp;gt;             else:&lt;br /&gt;
&amp;gt;                 sql += b','&lt;br /&gt;
&amp;gt;             sql += v&lt;br /&gt;
&amp;gt;         rows += self.execute(sql)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The most prominent missing coverage in cursors.py seems to be&lt;br /&gt;
for its &amp;quot;scrollable cursor&amp;quot; support.&lt;br /&gt;
&lt;br /&gt;
Beyond the lack of completeness in testing, many of the tests themselves&lt;br /&gt;
are written in an expedient &amp;quot;one giant test&amp;quot; style where dozens of&lt;br /&gt;
assertions and individual behaviors are lumped into one big test&lt;br /&gt;
case (see test_SSCursor.py, test_basic.py-&amp;gt;test_datatypes for examples).&lt;br /&gt;
Test cases like these are difficult to work with when debugging&lt;br /&gt;
regressions as well as when tests for new features need to be added, and ideally&lt;br /&gt;
these should be broken out into clean single-feature tests with consistent&lt;br /&gt;
fixtures.&lt;br /&gt;
&lt;br /&gt;
There is also a suite called &amp;quot;test_issues&amp;quot;, which intends to&lt;br /&gt;
accumulate tests against specific issues that have been reported. This&lt;br /&gt;
isn't a bad idea, though it would be nice to see an effort made into&lt;br /&gt;
categorizing these tests into the actual features they are testing,&lt;br /&gt;
rather than a meaningless list of issue numbers, which could then form&lt;br /&gt;
as the basis for new suites centered around those areas of&lt;br /&gt;
functionality.  The issues reported should be used as the inspiration&lt;br /&gt;
for improvement of the test suite, rather than just another line-item&lt;br /&gt;
to be filed away.&lt;br /&gt;
&lt;br /&gt;
=== Library Documentation ===&lt;br /&gt;
&lt;br /&gt;
As the Python DBAPI is already a well-documented API, and the users of&lt;br /&gt;
PyMySQL are typically coming from MySQL-Python which is documented&lt;br /&gt;
to some degree and for which the use contract is widely known, it seems&lt;br /&gt;
unlikely that users of PyMySQL are eager for comprehensive documentation&lt;br /&gt;
specific to this library.&lt;br /&gt;
&lt;br /&gt;
That being said, PyMySQL does not appear to have any real library&lt;br /&gt;
documentation at all.  There's a README which&lt;br /&gt;
essentially refers to pep-249, an example.py in the root which shows a&lt;br /&gt;
very basic connection / round trip, and that's it. In&lt;br /&gt;
order to know anything about the API, specific parameters, optimization&lt;br /&gt;
strategies, behaviors, etc., one either has to go off of the MySQL-Python documentation and assume&lt;br /&gt;
PyMySQL also features the same parameter, or read the source code.&lt;br /&gt;
&lt;br /&gt;
PyMySQL probably hasn't had much urgent need for real documentation thus&lt;br /&gt;
far.  However, relying upon being a port of MySQL-Python is fast becoming&lt;br /&gt;
something that can no longer be relied upon, as MySQL-Python's development&lt;br /&gt;
is quite stalled, and PyMySQL should aspire to move into the future&lt;br /&gt;
of MySQL and Python with its own featureset and behaviors.  To that end, it would be a &lt;br /&gt;
great idea if it at least made the start&lt;br /&gt;
of a rudimentary Sphinx build including autodoc for module documentation&lt;br /&gt;
which could then be published up to RTD.   This would be very easy to get&lt;br /&gt;
started, and once present, new documentation sections can be added&lt;br /&gt;
iteratively.&lt;br /&gt;
&lt;br /&gt;
=== Architecture and Performance ===&lt;br /&gt;
&lt;br /&gt;
As stated before, PyMySQL has a really matter-of-fact and straightforward&lt;br /&gt;
implementation.   The code is very readable and it's easy to discern even&lt;br /&gt;
unusual features such as the INSERT..VALUES rewriting.  As far as the design,&lt;br /&gt;
there are lots of areas where performance suffers a bit at the Python level;&lt;br /&gt;
these areas could be greatly improved very easily with a little bit of&lt;br /&gt;
attention.&lt;br /&gt;
&lt;br /&gt;
As we are dealing with a driver that is communicating with a low level&lt;br /&gt;
protocol over a socket, it's critical that sending and receiving messages&lt;br /&gt;
is done as efficiently as possible.  The mechanisms used in PyMySQL rely&lt;br /&gt;
heavily on standard Python objects, without the use of __slots__; this means&lt;br /&gt;
that for individual messages on a socket, we are creating a heavyweight object,&lt;br /&gt;
calling its __init__() method and a creating a new __dict__ each time;&lt;br /&gt;
the most common object, OKPacketWrapper(), already wraps a MysqlPacket()&lt;br /&gt;
object, so we're doing two __init__()s in this common case.  OKPacketWrapper&lt;br /&gt;
also implements a `__getattr__` scheme&lt;br /&gt;
to proxy attribute access from itself to the internal MysqlPacket; this is a&lt;br /&gt;
an inefficient mode of operation in Python as it means a lookup must first&lt;br /&gt;
fail on the OKPacketWrapper before it invokes the `__getattr__()` method&lt;br /&gt;
and does another attribute lookup.   A reorg of the packet classes to produce&lt;br /&gt;
far fewer objects for messages as well as to use __slots__ would be&lt;br /&gt;
recommended.&lt;br /&gt;
&lt;br /&gt;
There are also at least some areas where function call overhead is unnecessarily&lt;br /&gt;
high due to the organization of calls.  For example, when calling upon&lt;br /&gt;
cursor.execute(), for parameters passed as a dictionary under Python 2K,&lt;br /&gt;
the isinstance() builtin function will be called on the incoming arguments&lt;br /&gt;
four times at a minimum, with two of each call against the exact same&lt;br /&gt;
criteria.  Additional logic will then call isinstance() on every key of&lt;br /&gt;
the dictionary as well as at least twice on every value within the dictionary,&lt;br /&gt;
in order to determine if the value needs to be converted to bytes as well&lt;br /&gt;
as for some top-level escaping logic checks; the escaping logic then&lt;br /&gt;
goes further with more checks for type() to match the object to a final&lt;br /&gt;
converter function.   While the flow of this logic does appear to be&lt;br /&gt;
reasonable and straightforward, it isn't ideal for a low-level database&lt;br /&gt;
driver library where speed is a high priority; the various wrappers and&lt;br /&gt;
processors can easily be reorganized here to reduce the number of isinstance()&lt;br /&gt;
and type() calls significantly, which would save a lot on function call&lt;br /&gt;
overhead.  It is likely there are many other areas in the codebase where&lt;br /&gt;
similar reductions in call counts can be made without too much disruption.&lt;br /&gt;
&lt;br /&gt;
In Appendix A, a short performance suite is illustrated.  This&lt;br /&gt;
is run against PyMySQL, Mysql-Connector-Python, and MySQL-Python.&lt;br /&gt;
PyMySQL does in fact demonstrate the&lt;br /&gt;
highest call-count behavior, logging 26,851,003 function calls&lt;br /&gt;
compared to MySQL-connector's 11,674,074 for the same operations.  To PyMySQL's&lt;br /&gt;
credit, it somehow completes the test 25-50% faster than MySQL-connector&lt;br /&gt;
despite having more than twice as many function calls; this probably indicates&lt;br /&gt;
the use of something very slow within MySQL-connector, such as catching&lt;br /&gt;
an exception within a tight loop.  However, the real bar we're comparing to&lt;br /&gt;
is MySQL-Python, which is written&lt;br /&gt;
in C and does the whole test with only 47,503 Python function calls; while&lt;br /&gt;
PyMySQL and MySQL-connector-Python compete for time within the 10-15 second&lt;br /&gt;
range without profiling, MySQL-Python completes the whole test in **1.03 seconds**, a 1000%&lt;br /&gt;
improvement over PyMySQL.   This speed difference is not PyMySQL's fault,&lt;br /&gt;
as pure Python is known to be extremely slow compared to C code.&lt;br /&gt;
However, there's a lot of easy wins in PyMySQL's codebase where the function&lt;br /&gt;
call count could probably be reduced dramatically to be even better than that&lt;br /&gt;
of MySQL-connector-Python; ideally, the driver would be able to run this&lt;br /&gt;
test possibly 5-6x slower than MySQL-Python rather than 10x.&lt;br /&gt;
&lt;br /&gt;
=== History Documentation ===&lt;br /&gt;
&lt;br /&gt;
PyMySQL includes a CHANGELOG file in its root which describes the bug fixes&lt;br /&gt;
and features of each release.  The construction of this file is definitely&lt;br /&gt;
within the &amp;quot;expedient&amp;quot; style noted for PyMySQL overall, and for it to be&lt;br /&gt;
genuinely useful for enterprise-level software, it would need to be&lt;br /&gt;
much more complete in its detail.    Each bug fix mentioned is only referred to&lt;br /&gt;
tersely without any link to a pull request or bug report.  While some can&lt;br /&gt;
be linked to a specific change through detective work&lt;br /&gt;
(&amp;quot;Cursor.fetchall() and .fetchmany now return list, not tuple&amp;quot;), it would&lt;br /&gt;
be very difficult for many&lt;br /&gt;
(&amp;quot;Fixed BIT type handling&amp;quot;, &amp;quot;Fixed GC errors&amp;quot; - what was the issue?)&lt;br /&gt;
and in many cases impossible (&amp;quot;Improved Py3k support&amp;quot;).  The file also&lt;br /&gt;
includes no dates so one has to look at the git tags to figure this&lt;br /&gt;
part out (fortunately, releases to seem to be git tagged at least).&lt;br /&gt;
For a user trying to ascertain if particular issues are&lt;br /&gt;
fixed or if behaviors have changed, this file is at best highly frustrating.&lt;br /&gt;
&lt;br /&gt;
A listing of release dates illustrates an average of two releases per year,&lt;br /&gt;
excluding 2012 when PyMySQL was seeking a new maintainer:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
2014-12-02 12:05:10 -0200  (tag: pymysql-0.6.3)&lt;br /&gt;
2014-04-21 14:28:47 -0300  (tag: pymysql-0.6.2)&lt;br /&gt;
2013-10-11 13:36:56 -0300  (tag: pymysql-0.6.1)&lt;br /&gt;
2013-10-04 14:25:14 -0300  (tag: pymysql-0.6)&lt;br /&gt;
2011-11-08 10:27:41 -0800  (tag: pymysql-0.5)&lt;br /&gt;
2010-12-27 17:28:16 +0000  (tag: pymysql-0.4)&lt;br /&gt;
2010-09-03 00:55:29 +0000  (tag: pymysql-0.3)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Community Involvement ===&lt;br /&gt;
&lt;br /&gt;
This is an area where PyMySQL seems to shine, and might even be an advantage&lt;br /&gt;
as compared to MySQL-Connector-Python, which remains under the cloak&lt;br /&gt;
of MySQL's vastly overstuffed bugtracker under the ownership of&lt;br /&gt;
the famously-opaque Oracle.   For a small, low key library without&lt;br /&gt;
any real documentation and only 126 tests, it features 33 contributors on&lt;br /&gt;
Github and is up to pull request #285, which is quite high.  Because&lt;br /&gt;
PyMySQL is small and straightforward, it does lend itself to contribution&lt;br /&gt;
and seems to do very well in this area.   The developers so far have been&lt;br /&gt;
responsive to issues and pull requests made by the author.&lt;br /&gt;
&lt;br /&gt;
Based on github contributor graphs it appears that PyMySQL had a lull&lt;br /&gt;
in 2012 but was picked up again afterwards in 2013.  As of 2012, its most&lt;br /&gt;
prominent developer Pete Hunt stopped development, and on July 25, 2013&lt;br /&gt;
added a note to the README that the project was looking for a new maintainer.&lt;br /&gt;
As of 2013 it was picked up by the current most prominent developers&lt;br /&gt;
Marcel Rodrigues (github username lecram) and INADA Naoki&lt;br /&gt;
(github username methane).&lt;br /&gt;
&lt;br /&gt;
Most issues that have been noted thus far within the areas of testing,&lt;br /&gt;
documentation and architecture are all very fixable; if the Openstack community&lt;br /&gt;
can provide resources to address some of these areas, and if the PyMySQL&lt;br /&gt;
developers are reasonably open to accepting new changes and producing new&lt;br /&gt;
releases in a timely fashion, the PyMySQL driver&lt;br /&gt;
could rapidly become a fully formidable contender in the MySQL driver field.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Appendix A - Performance Tests ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
import cProfile&lt;br /&gt;
import StringIO&lt;br /&gt;
import pstats&lt;br /&gt;
import contextlib&lt;br /&gt;
import time&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextlib.contextmanager&lt;br /&gt;
def profiled(dbapi):&lt;br /&gt;
    pr = cProfile.Profile()&lt;br /&gt;
    pr.enable()&lt;br /&gt;
    yield&lt;br /&gt;
    pr.disable()&lt;br /&gt;
    s = StringIO.StringIO()&lt;br /&gt;
    ps = pstats.Stats(pr, stream=s).sort_stats('cumulative')&lt;br /&gt;
    ps.print_stats()&lt;br /&gt;
    print &amp;quot;DBAPI:  %s&amp;quot; % dbapi&lt;br /&gt;
    print s.getvalue()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextlib.contextmanager&lt;br /&gt;
def timeonly(dbapi):&lt;br /&gt;
    now = time.time()&lt;br /&gt;
    try:&lt;br /&gt;
        yield&lt;br /&gt;
    finally:&lt;br /&gt;
        total = time.time() - now&lt;br /&gt;
        print &amp;quot;DBAPI:  %s, total seconds %f&amp;quot; % (dbapi, total)&lt;br /&gt;
&lt;br /&gt;
import MySQLdb&lt;br /&gt;
import pymysql&lt;br /&gt;
from mysql import connector as mysqlconnector&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def go(dbapi, ctx):&lt;br /&gt;
    conn = dbapi.connect(&lt;br /&gt;
        user='scott', passwd='tiger', host='localhost', db='test')&lt;br /&gt;
    cursor = conn.cursor()&lt;br /&gt;
    cursor.execute(&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
CREATE TABLE IF NOT EXISTS test_things (&lt;br /&gt;
    x INTEGER,&lt;br /&gt;
    y VARCHAR(255),&lt;br /&gt;
    z FLOAT&lt;br /&gt;
) engine=InnoDB&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;)&lt;br /&gt;
    cursor.execute(&amp;quot;DELETE from test_things&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    with ctx(dbapi):&lt;br /&gt;
        for row in xrange(1000):&lt;br /&gt;
            cursor.execute(&lt;br /&gt;
                &amp;quot;INSERT INTO test_things (x, y, z) &amp;quot;&lt;br /&gt;
                &amp;quot;VALUES (%(x)s, %(y)s, %(z)s)&amp;quot;,&lt;br /&gt;
                {&amp;quot;x&amp;quot;: row, &amp;quot;y&amp;quot;: &amp;quot;row number %d&amp;quot; % row,&lt;br /&gt;
                 &amp;quot;z&amp;quot;: row * 4.57292, }&lt;br /&gt;
            )&lt;br /&gt;
&lt;br /&gt;
        for x in xrange(500):&lt;br /&gt;
            cursor.execute(&lt;br /&gt;
                &amp;quot;select * from test_things&amp;quot;)&lt;br /&gt;
            for row in cursor.fetchall():&lt;br /&gt;
                row[0], row[1], row[2]&lt;br /&gt;
&lt;br /&gt;
    cursor.close()&lt;br /&gt;
    conn.close()&lt;br /&gt;
&lt;br /&gt;
go(pymysql, profiled)&lt;br /&gt;
go(mysqlconnector, profiled)&lt;br /&gt;
go(MySQLdb, profiled)&lt;br /&gt;
&lt;br /&gt;
go(pymysql, timeonly)&lt;br /&gt;
go(mysqlconnector, timeonly)&lt;br /&gt;
go(MySQLdb, timeonly)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DBAPI:  &amp;lt;module 'pymysql' from '/Users/classic/dev/PyMySQL/pymysql/__init__.pyc'&amp;gt;&lt;br /&gt;
         26851003 function calls in 15.924 seconds&lt;br /&gt;
&lt;br /&gt;
   Ordered by: cumulative time&lt;br /&gt;
&lt;br /&gt;
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;
     1500    0.010    0.000   15.923    0.011 /Users/classic/dev/PyMySQL/pymysql/cursors.py:105(execute)&lt;br /&gt;
     1500    0.003    0.000   15.883    0.011 /Users/classic/dev/PyMySQL/pymysql/cursors.py:269(_query)&lt;br /&gt;
     1500    0.004    0.000   15.859    0.011 /Users/classic/dev/PyMySQL/pymysql/connections.py:746(query)&lt;br /&gt;
     1500    0.003    0.000   15.819    0.011 /Users/classic/dev/PyMySQL/pymysql/connections.py:892(_read_query_result)&lt;br /&gt;
     1500    0.004    0.000   15.812    0.011 /Users/classic/dev/PyMySQL/pymysql/connections.py:1097(read)&lt;br /&gt;
      500    0.003    0.000   15.559    0.031 /Users/classic/dev/PyMySQL/pymysql/connections.py:1154(_read_result_packet)&lt;br /&gt;
      500    0.900    0.002   15.429    0.031 /Users/classic/dev/PyMySQL/pymysql/connections.py:1187(_read_rowdata_packet)&lt;br /&gt;
   500000    3.417    0.000    9.173    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1200(_read_row_from_packet)&lt;br /&gt;
  2012000    1.157    0.000    5.307    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:311(read_length_coded_string)&lt;br /&gt;
   504500    1.495    0.000    5.209    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:845(_read_packet)&lt;br /&gt;
  2014500    1.248    0.000    3.035    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:293(read_length_encoded_integer)&lt;br /&gt;
  3539500    2.439    0.000    2.750    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:245(read)&lt;br /&gt;
  1009000    0.779    0.000    2.307    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:870(_read_bytes)&lt;br /&gt;
  1009000    1.197    0.000    1.423    0.000 {method 'read' of '_io.BufferedReader' objects}&lt;br /&gt;
   508500    0.379    0.000    0.695    0.000 /Users/classic/dev/PyMySQL/pymysql/util.py:3(byte2int)&lt;br /&gt;
  5058000    0.465    0.000    0.465    0.000 {len}&lt;br /&gt;
   500500    0.222    0.000    0.455    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1146(_check_packet_is_eof)&lt;br /&gt;
   504500    0.205    0.000    0.375    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:342(check_error)&lt;br /&gt;
  2504000    0.275    0.000    0.275    0.000 {method 'append' of 'list' objects}&lt;br /&gt;
   501500    0.182    0.000    0.230    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:326(is_eof_packet)&lt;br /&gt;
     3500    0.009    0.000    0.226    0.000 /Users/classic/dev/PyMySQL/pymysql/_socketio.py:45(readinto)&lt;br /&gt;
     3500    0.207    0.000    0.207    0.000 {method 'recv_into' of '_socket.socket' objects}&lt;br /&gt;
  1022000    0.203    0.000    0.203    0.000 {_struct.unpack}&lt;br /&gt;
   526000    0.200    0.000    0.200    0.000 {isinstance}&lt;br /&gt;
  1501000    0.195    0.000    0.195    0.000 {method 'get' of 'dict' objects}&lt;br /&gt;
   504500    0.176    0.000    0.176    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:238(__init__)&lt;br /&gt;
   504500    0.170    0.000    0.170    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:339(is_error_packet)&lt;br /&gt;
  2014500    0.167    0.000    0.167    0.000 {ord}&lt;br /&gt;
      500    0.006    0.000    0.125    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1224(_get_descriptions)&lt;br /&gt;
     2000    0.003    0.000    0.087    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:361(__init__)&lt;br /&gt;
     2000    0.019    0.000    0.084    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:365(__parse_field_descriptor)&lt;br /&gt;
   500000    0.056    0.000    0.056    0.000 /Users/classic/dev/PyMySQL/pymysql/converters.py:301(through)&lt;br /&gt;
     1500    0.005    0.000    0.037    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:915(_execute_command)&lt;br /&gt;
     1500    0.003    0.000    0.027    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:886(_write_bytes)&lt;br /&gt;
     1500    0.002    0.000    0.024    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/socket.py:223(meth)&lt;br /&gt;
     1500    0.021    0.000    0.021    0.000 {method 'sendall' of '_socket.socket' objects}&lt;br /&gt;
     1500    0.020    0.000    0.020    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:276(_do_get_result)&lt;br /&gt;
     1000    0.003    0.000    0.019    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:95(_escape_args)&lt;br /&gt;
     4000    0.002    0.000    0.015    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:99(&amp;lt;genexpr&amp;gt;)&lt;br /&gt;
     1000    0.002    0.000    0.014    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1127(_read_ok_packet)&lt;br /&gt;
     3000    0.003    0.000    0.013    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:712(escape)&lt;br /&gt;
     1000    0.005    0.000    0.012    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:416(__init__)&lt;br /&gt;
     8000    0.011    0.000    0.011    0.000 {method 'decode' of 'str' objects}&lt;br /&gt;
     3500    0.003    0.000    0.007    0.000 {method '_checkReadable' of '_io._IOBase' objects}&lt;br /&gt;
     2000    0.003    0.000    0.006    0.000 /Users/classic/dev/PyMySQL/pymysql/converters.py:19(escape_item)&lt;br /&gt;
     4000    0.002    0.000    0.006    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:126(&amp;lt;genexpr&amp;gt;)&lt;br /&gt;
     7500    0.003    0.000    0.005    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:115(ensure_bytes)&lt;br /&gt;
     2000    0.003    0.000    0.005    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:386(description)&lt;br /&gt;
     5500    0.004    0.000    0.004    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:268(advance)&lt;br /&gt;
     1000    0.001    0.000    0.004    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:722(escape_string)&lt;br /&gt;
     3500    0.004    0.000    0.004    0.000 /Users/classic/dev/PyMySQL/pymysql/_socketio.py:87(readable)&lt;br /&gt;
      500    0.002    0.000    0.004    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:442(__init__)&lt;br /&gt;
     3500    0.003    0.000    0.003    0.000 {method '_checkClosed' of '_io._IOBase' objects}&lt;br /&gt;
     1000    0.001    0.000    0.003    0.000 /Users/classic/dev/PyMySQL/pymysql/converters.py:59(escape_string)&lt;br /&gt;
     1500    0.003    0.000    0.003    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1080(__init__)&lt;br /&gt;
     1500    0.001    0.000    0.003    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:92(nextset)&lt;br /&gt;
     4000    0.002    0.000    0.002    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:397(get_column_length)&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 /Users/classic/dev/PyMySQL/pymysql/converters.py:56(escape_float)&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 {method 'sub' of '_sre.SRE_Pattern' objects}&lt;br /&gt;
     3000    0.002    0.000    0.002    0.000 {_struct.pack}&lt;br /&gt;
     6000    0.002    0.000    0.002    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:62(_get_db)&lt;br /&gt;
     1500    0.001    0.000    0.002    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:80(_nextset)&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 /Users/classic/dev/PyMySQL/pymysql/util.py:9(int2byte)&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:244(fetchall)&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {min}&lt;br /&gt;
     2500    0.001    0.000    0.001    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:323(is_ok_packet)&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Users/classic/dev/PyMySQL/pymysql/converters.py:52(escape_int)&lt;br /&gt;
     2000    0.001    0.000    0.001    0.000 {method 'items' of 'dict' objects}&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {getattr}&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1093(__del__)&lt;br /&gt;
     1000    0.000    0.000    0.000    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:259(read_all)&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:67(_check_executed)&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:336(is_load_local_packet)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/contextlib.py:21(__exit__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 test.py:8(profiled)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DBAPI:  &amp;lt;module 'mysql.connector' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/__init__.pyc'&amp;gt;&lt;br /&gt;
         11674074 function calls in 19.049 seconds&lt;br /&gt;
&lt;br /&gt;
   Ordered by: cumulative time&lt;br /&gt;
&lt;br /&gt;
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;
      500    0.640    0.001   18.556    0.037 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:820(fetchall)&lt;br /&gt;
      500    0.003    0.000   11.196    0.022 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:655(get_rows)&lt;br /&gt;
      500    1.561    0.003   11.191    0.022 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:292(read_text_result)&lt;br /&gt;
   504500    3.303    0.000    7.306    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/network.py:219(recv_plain)&lt;br /&gt;
   500000    3.572    0.000    6.625    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:359(row_to_python)&lt;br /&gt;
  1009000    3.155    0.000    3.155    0.000 {method 'recv_into' of '_socket.socket' objects}&lt;br /&gt;
   500000    2.049    0.000    2.329    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:220(read_lc_string_list)&lt;br /&gt;
   500000    0.653    0.000    2.116    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:531(_STRING_to_python)&lt;br /&gt;
   502000    0.378    0.000    1.329    0.000 {method 'decode' of 'bytearray' objects}&lt;br /&gt;
   502000    0.221    0.000    0.951    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/utf_8.py:15(decode)&lt;br /&gt;
   502000    0.730    0.000    0.730    0.000 {_codecs.utf_8_decode}&lt;br /&gt;
   500000    0.514    0.000    0.514    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:413(_INT_to_python)&lt;br /&gt;
   509500    0.510    0.000    0.510    0.000 {_struct.unpack_from}&lt;br /&gt;
     1500    0.009    0.000    0.492    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:452(execute)&lt;br /&gt;
     1500    0.004    0.000    0.437    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:705(cmd_query)&lt;br /&gt;
   500000    0.367    0.000    0.367    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:405(_FLOAT_to_python)&lt;br /&gt;
   504500    0.343    0.000    0.343    0.000 {method 'extend' of 'bytearray' objects}&lt;br /&gt;
     1500    0.007    0.000    0.315    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:481(_send_cmd)&lt;br /&gt;
  2501500    0.275    0.000    0.275    0.000 {method 'append' of 'list' objects}&lt;br /&gt;
   500500    0.246    0.000    0.246    0.000 {method 'startswith' of 'bytearray' objects}&lt;br /&gt;
   524002    0.150    0.000    0.150    0.000 {isinstance}&lt;br /&gt;
     1500    0.011    0.000    0.118    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:613(_handle_result)&lt;br /&gt;
  1004500    0.112    0.000    0.112    0.000 {len}&lt;br /&gt;
   500000    0.092    0.000    0.092    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:204(description)&lt;br /&gt;
     1500    0.007    0.000    0.042    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/network.py:118(send_plain)&lt;br /&gt;
     2000    0.013    0.000    0.041    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:226(parse_column)&lt;br /&gt;
     1000    0.008    0.000    0.034    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:346(_process_params_dict)&lt;br /&gt;
     1500    0.002    0.000    0.029    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/socket.py:223(meth)&lt;br /&gt;
     1500    0.026    0.000    0.026    0.000 {method 'sendall' of '_socket.socket' objects}&lt;br /&gt;
     5000    0.007    0.000    0.014    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/catch23.py:79(struct_unpack)&lt;br /&gt;
     1000    0.001    0.000    0.013    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:562(_handle_ok)&lt;br /&gt;
    12000    0.011    0.000    0.011    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:167(read_lc_string)&lt;br /&gt;
     1000    0.004    0.000    0.010    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:199(parse_ok)&lt;br /&gt;
     3000    0.005    0.000    0.009    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:152(to_mysql)&lt;br /&gt;
     1500    0.003    0.000    0.007    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:118(make_command)&lt;br /&gt;
     3000    0.003    0.000    0.007    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:130(quote)&lt;br /&gt;
     1500    0.005    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/network.py:53(_prepare_packets)&lt;br /&gt;
     1500    0.003    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:406(_handle_result)&lt;br /&gt;
     3000    0.003    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:102(escape)&lt;br /&gt;
     1000    0.002    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:252(parse_eof)&lt;br /&gt;
      500    0.001    0.000    0.005    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:579(_handle_eof)&lt;br /&gt;
     2000    0.003    0.000    0.005    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:545(_handle_server_status)&lt;br /&gt;
     1500    0.003    0.000    0.004    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:53(int1store)&lt;br /&gt;
     3000    0.003    0.000    0.003    0.000 {method 'encode' of 'str' objects}&lt;br /&gt;
     9001    0.003    0.000    0.003    0.000 {method 'replace' of 'str' objects}&lt;br /&gt;
     1500    0.003    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:295(_reset_result)&lt;br /&gt;
     4500    0.003    0.000    0.003    0.000 {_struct.pack}&lt;br /&gt;
     2500    0.003    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:296(read_lc_int)&lt;br /&gt;
      500    0.002    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:757(_handle_eof)&lt;br /&gt;
     6027    0.003    0.000    0.003    0.000 {method 'format' of 'str' objects}&lt;br /&gt;
     3000    0.002    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:1221(_set_unread_result)&lt;br /&gt;
      500    0.001    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:219(parse_column_count)&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 {repr}&lt;br /&gt;
     4527    0.002    0.000    0.002    0.000 {getattr}&lt;br /&gt;
     4000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/constants.py:34(flag_is_set)&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:383(_handle_noresultset)&lt;br /&gt;
     4000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:1233(_get_unread_result)&lt;br /&gt;
     2001    0.001    0.000    0.001    0.000 {method 'items' of 'dict' objects}&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:307(_have_unread_result)&lt;br /&gt;
     3000    0.001    0.000    0.001    0.000 {method 'lower' of 'str' objects}&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 {range}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:173(_str_to_mysql)&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:169(_float_to_mysql)&lt;br /&gt;
     1000    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:161(_int_to_mysql)&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:1313(_get_getwarnings)&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:200(reset)&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:397(_handle_resultset)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/__init__.py:71(search_function)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/ascii.py:41(getregentry)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/contextlib.py:21(__exit__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/__init__.py:49(normalize_encoding)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 test.py:8(profiled)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/codecs.py:77(__new__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {__import__}&lt;br /&gt;
        3    0.000    0.000    0.000    0.000 {method 'get' of 'dict' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'split' of 'str' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {built-in method __new__ of type object at 0x1001534e8}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'translate' of 'str' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'join' of 'str' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {hasattr}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DBAPI:  &amp;lt;module 'MySQLdb' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/__init__.pyc'&amp;gt;&lt;br /&gt;
         47503 function calls in 0.918 seconds&lt;br /&gt;
&lt;br /&gt;
   Ordered by: cumulative time&lt;br /&gt;
&lt;br /&gt;
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;
     1500    0.009    0.000    0.917    0.001 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:164(execute)&lt;br /&gt;
     1500    0.002    0.000    0.890    0.001 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:353(_query)&lt;br /&gt;
     1500    0.004    0.000    0.498    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:315(_do_query)&lt;br /&gt;
     1500    0.016    0.000    0.391    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:358(_post_get_result)&lt;br /&gt;
     1500    0.001    0.000    0.375    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:324(_fetch_row)&lt;br /&gt;
      500    0.374    0.001    0.374    0.001 {built-in method fetch_row}&lt;br /&gt;
     1500    0.007    0.000    0.362    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:142(_do_get_result)&lt;br /&gt;
     1500    0.002    0.000    0.351    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:351(_get_result)&lt;br /&gt;
     1500    0.349    0.000    0.349    0.000 {method 'store_result' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.131    0.000    0.131    0.000 {method 'query' of '_mysql.connection' objects}&lt;br /&gt;
     4000    0.002    0.000    0.010    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:184(&amp;lt;genexpr&amp;gt;)&lt;br /&gt;
     3000    0.002    0.000    0.008    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/connections.py:267(literal)&lt;br /&gt;
     1500    0.007    0.000    0.007    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:107(_warning_check)&lt;br /&gt;
     3000    0.002    0.000    0.006    0.000 {method 'escape' of '_mysql.connection' objects}&lt;br /&gt;
     6000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:159(_get_db)&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/converters.py:81(Float2Str)&lt;br /&gt;
     2500    0.001    0.000    0.001    0.000 {isinstance}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/connections.py:202(string_literal)&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:380(fetchall)&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 {built-in method describe}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 {method 'string_literal' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {method 'insert_id' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {method 'affected_rows' of '_mysql.connection' objects}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/converters.py:69(Thing2Str)&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 {method 'warning_count' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 {method 'info' of '_mysql.connection' objects}&lt;br /&gt;
     1000    0.000    0.000    0.000    0.000 {method 'iteritems' of 'dict' objects}&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:103(_check_executed)&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 {len}&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 {built-in method field_flags}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/contextlib.py:21(__exit__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 test.py:8(profiled)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DBAPI:  &amp;lt;module 'pymysql' from '/Users/classic/dev/PyMySQL/pymysql/__init__.pyc'&amp;gt;, total seconds 10.046012&lt;br /&gt;
DBAPI:  &amp;lt;module 'mysql.connector' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/__init__.pyc'&amp;gt;, total seconds 15.403260&lt;br /&gt;
DBAPI:  &amp;lt;module 'MySQLdb' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/__init__.pyc'&amp;gt;, total seconds 1.028737&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mike Bayer</name></author>	</entry>

	<entry>
		<id>https://wiki.openstack.org/w/index.php?title=PyMySQL_evaluation&amp;diff=72563</id>
		<title>PyMySQL evaluation</title>
		<link rel="alternate" type="text/html" href="https://wiki.openstack.org/w/index.php?title=PyMySQL_evaluation&amp;diff=72563"/>
				<updated>2015-01-28T20:33:00Z</updated>
		
		<summary type="html">&lt;p&gt;Mike Bayer: /* Architecture and Performance */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= PyMySQL Evaluation =&lt;br /&gt;
&lt;br /&gt;
This page will capture issues related to Openstack moving to the [https://github.com/PyMySQL/PyMySQL/ PyMySQL driver] for MySQL/MariaDB dbapi access.&lt;br /&gt;
&lt;br /&gt;
== Rationale ==&lt;br /&gt;
&lt;br /&gt;
While the [https://github.com/farcepest/MySQLdb1 MySQL-Python] driver is a very mature and stable driver, it does not provide compatibility with either Python 3 or with [http://eventlet.net/doc/patching.html eventlet monkeypatching] (MySQL-Python  [https://github.com/eventlet/eventlet/blob/v0.16.1/eventlet/patcher.py#L225-L237 can be monkeypatched] with eventlet, but this feature disabled by default and missed in documentation). So OpenStack's usage of MySQL-Python, combined with the fact that concurrency is provided by eventlet, means that we currently have fully serialized database access within a single process, that is, only one database command occurs at a time within an Openstack Python process.&lt;br /&gt;
&lt;br /&gt;
== Drivers Under Consideration ==&lt;br /&gt;
&lt;br /&gt;
The two drivers that are known to provide eventlet-monkeypatch compatibility are MySQL-Connector and PyMySQL, as they are written in pure Python.   The two other well-known drivers for MySQL are MySQL-Python and OurSQL, both of which are written in C and offer no explicit support for eventlet or async.  They can reportedly be built to support gevent using a system called [https://github.com/CMGS/greenify Greenify], but the maturity and/or stabiltiy of this system is unknown.&lt;br /&gt;
&lt;br /&gt;
A comparison at http://www.diamondtin.com/2014/sqlalchemy-gevent-mysql-python-drivers-comparison/ illustrates performance metrics observed with all four of these drivers, with MySQL-python built both without and with the &amp;quot;greenify&amp;quot; system.&lt;br /&gt;
&lt;br /&gt;
A summary of the status of all four drivers is as follows.&lt;br /&gt;
&lt;br /&gt;
=== MySQL-Python ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/farcepest/MySQLdb1 MySQL-Python] is the most widely used Python driver.  However, it does not support Python 3, and it does not support async systems unless used within a thread pool.   While a system known as Greenify can potentially resolve the latter situation, the Python 3 limitation is still a deal-breaker - pull request with Python 3 support [https://github.com/farcepest/MySQLdb1/pull/62 was proposed] to MySQL-python in April 2014 and still not merged.&lt;br /&gt;
&lt;br /&gt;
=== mysqlclient ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/PyMySQL/mysqlclient-python mysqlclient] is a fork of MySQL-python. It add Python 3.3 support and merges some pull requests.&lt;br /&gt;
&lt;br /&gt;
=== OurSQL ===&lt;br /&gt;
&lt;br /&gt;
[https://pythonhosted.org/oursql/ OurSQL] is an alternative MySQL driver that is also written in C.  It features fast performance and uses a different execution model than MySQL-python, using prepared statements.    OurSQL does have a Python 3 port hosted on [https://launchpad.net/oursql/py3k/ launchpad] but is not integrated with OurSQL itself nor is it published on Pypi.     Because it is written in C, it does not offer compatibility with async systems including eventlet-style monkeypatching.&lt;br /&gt;
&lt;br /&gt;
Besides the shaky Python 3 support and lack of eventlet support, OurSQL is also not maintained, seeing its last master commit on [https://code.launchpad.net/~jerub/oursql/master 2012-06-05].&lt;br /&gt;
&lt;br /&gt;
=== MySQL-Connector-Python ===&lt;br /&gt;
&lt;br /&gt;
[https://pypi.python.org/pypi/mysql-connector-python MySQL-connector-Python] is a pure Python MySQL driver, and is now released under the auspices of the MySQL project itself, as owned by Oracle.  MySQL-connector-Python supports both Python 3 as well as eventlet monkeypatching, and is well maintained.  It is endorsed by Oracle as the official Oracle-supported driver for MySQL, so to that extent, it is in most ways Openstack's first choice in driver.  However, Oracle refuses to publish MySQL-connector-Python on Pypi, which is critical to the Openstack infrastructure.  Repeated attempts to communicate with Oracle in order to resolve this issue have not made any progress.    Therefore, for this one unfortunate reason, MySQL-connector-Python will not have a place in the Openstack ecosystem unless this issue is resolved.&lt;br /&gt;
&lt;br /&gt;
=== PyMySQL ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/PyMySQL/PyMySQL PyMySQL] is a pure Python MySQL&lt;br /&gt;
driver, first written as a rough port of the MySQL-Python driver.&lt;br /&gt;
PyMySQL meets all of Openstack's criterion for a driver: it is fully&lt;br /&gt;
open source, hosted on Github, released on Pypi, is actively&lt;br /&gt;
maintained, is written in pure Python so is eventlet-monkeypatch&lt;br /&gt;
compatible, and is fully Python 3 compatible.&lt;br /&gt;
&lt;br /&gt;
As this document is named &amp;quot;PyMySQL Evaluation&amp;quot;, it should be apparent that&lt;br /&gt;
this is the driver Openstack is currently leaning towards; because it is the only&lt;br /&gt;
one that meets all criteria fully, it is already most likely the &amp;quot;winner&amp;quot;.  However, it does&lt;br /&gt;
have some minor code quality issues which hopefully can be addressed in some&lt;br /&gt;
way; the section below titled &amp;quot;PyMySQL Code Review&amp;quot; will summarize the&lt;br /&gt;
current state of the code.&lt;br /&gt;
&lt;br /&gt;
===MySQL DB Drivers Comparison===&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Project !! PyPi hosted !! Eventlet friendly !! Python 3 compatibility !! Maturity and/or stability  !! Comment&lt;br /&gt;
|-&lt;br /&gt;
| MySQL-Python || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: orange;&amp;quot; | Partial || style=&amp;quot;background-color: red;&amp;quot; | No  || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || Can be monkeypatched by eventlet, but only to enable thread pooling&lt;br /&gt;
|-&lt;br /&gt;
| mysqlclient || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: orange;&amp;quot; | Partial || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes  || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || MySQL-python fork, so should be similar to it&lt;br /&gt;
|-&lt;br /&gt;
| OurSQL || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: red;&amp;quot; | No || style=&amp;quot;background-color: orange;&amp;quot; | Yes, but not Pypi hosted  || style=&amp;quot;background-color: red;&amp;quot; | No || Development halted fairly early on, and has not seen commits/releases in two years&lt;br /&gt;
|-&lt;br /&gt;
| MySQL-Connector-Python || style=&amp;quot;background-color: red;&amp;quot; | No || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes  || style=&amp;quot;background-color: orange;&amp;quot; | Yes, though the driver is still fairly new || The official Oracle-supported driver for MySQL&lt;br /&gt;
|-&lt;br /&gt;
| PyMySQL || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes  || style=&amp;quot;background-color: orange;&amp;quot; | Yes, however see notes below. || Actively maintained and popular.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== PyMySQL Code Review ==&lt;br /&gt;
&lt;br /&gt;
PyMySQL started roughly as a pure Python port of MySQL-Python.   The sections below will&lt;br /&gt;
detail various aspects of the driver and the status of the code.   Overall, the general&lt;br /&gt;
tone of PyMySQL is one of code that was written in the spirit of pragmatism and immediate&lt;br /&gt;
need; it is straightforward, free of serious antipatterns and performs&lt;br /&gt;
its task in a matter-of-fact way.  However,&lt;br /&gt;
it lacks polish and completeness in many areas that would normally be in better&lt;br /&gt;
condition for a more mature project.  These areas are all highly fixable, and it is&lt;br /&gt;
hoped that these items can be shared with the current PyMySQL developers, through&lt;br /&gt;
a combination of this document itself as well as new issues and pull requests&lt;br /&gt;
on the PyMySQL tracker, so that this fairly promising library can be pushed&lt;br /&gt;
to the next level.   This would require that the developers are amenable to&lt;br /&gt;
these improvements.&lt;br /&gt;
&lt;br /&gt;
=== Coding Style / Pep8 ===&lt;br /&gt;
&lt;br /&gt;
PyMySQL's coding style is pretty good.  The majority of it passes all&lt;br /&gt;
flake8 tests, save for a few dozen whitespace issues and&lt;br /&gt;
some long lines here and there within the core&lt;br /&gt;
modules, and moreso within the tests which appear to be a bit more&lt;br /&gt;
crufty than the core modules.  A full run of flake8 produces only 128&lt;br /&gt;
errors, and with a default run of autopep8 against E1,E2,E3, we can&lt;br /&gt;
fix all of them in one pass except for 38 remaining long line warnings.&lt;br /&gt;
The code layout is mostly idiomatic and reasonable.&lt;br /&gt;
&lt;br /&gt;
The codebase is probably short on docstrings.  Those methods that are&lt;br /&gt;
part of the public DBAPI do have docstrings, but tend to be extremely&lt;br /&gt;
terse (example, docstring for cursor.execute(): &amp;quot;'Execute a query'&amp;quot;).&lt;br /&gt;
Other methods that aren't public tend to not have any docstrings, but&lt;br /&gt;
the codebase is not at all hard to read and the purpose of methods and&lt;br /&gt;
functions is pretty easy to discern just by their names and implementations,&lt;br /&gt;
however those who seek inline documentation as a means to understand a codebase&lt;br /&gt;
will be disappointed.   The terseness of public API docstrings is more of&lt;br /&gt;
an issue in terms of the overall lack of documentation for PyMySQL,&lt;br /&gt;
see the section &amp;quot;Documentation&amp;quot; below.&lt;br /&gt;
&lt;br /&gt;
=== Test Coverage ===&lt;br /&gt;
&lt;br /&gt;
The test coverage for PyMySQL is definitely lacking.   From a visual&lt;br /&gt;
inspection alone, it's apparent that of the dozen or so test modules,&lt;br /&gt;
most of them have less then ten tests each and the modules are very short.&lt;br /&gt;
For a full test run, there's only 126 tests; contrast this to&lt;br /&gt;
MySQL-Connector-Python which has 531 tests.&lt;br /&gt;
&lt;br /&gt;
Additionally, all of the tests are live round-trip tests against a&lt;br /&gt;
real database.  There are no non-live unit tests of any kind,&lt;br /&gt;
leaving codepaths that are not easily exercised&lt;br /&gt;
on a generic MySQL database out in the cold.  This means that features designed&lt;br /&gt;
for particular versions of MySQL, particular datatypes, particular error&lt;br /&gt;
conditions, and particular kinds of message packets that aren't created for&lt;br /&gt;
real are simply not tested.&lt;br /&gt;
&lt;br /&gt;
The lack of tests is apparent when run with coverage.  The test suite itself&lt;br /&gt;
does not appear to have any signs that it is normally run with coverage&lt;br /&gt;
turned on, with no directives in tox.ini or similar.   With coverage,&lt;br /&gt;
the overall coverage is 85%, but the modules most lacking in coverage&lt;br /&gt;
are also the most critical to PyMySQL's core functionality,&lt;br /&gt;
connections.py, converters.py and cursors.py:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Name                                                              Stmts   Miss  Cover&lt;br /&gt;
-------------------------------------------------------------------------------------&lt;br /&gt;
pymysql/__init__                                                     51      3    94%&lt;br /&gt;
pymysql/_compat                                                      14      4    71%&lt;br /&gt;
pymysql/_socketio                                                    72     30    58%&lt;br /&gt;
pymysql/charset                                                     228      3    99%&lt;br /&gt;
pymysql/connections                                                 827    188    77%&lt;br /&gt;
pymysql/constants/CLIENT                                             18      0   100%&lt;br /&gt;
pymysql/constants/COMMAND                                            32      0   100%&lt;br /&gt;
pymysql/constants/ER                                                471      0   100%&lt;br /&gt;
pymysql/constants/FIELD_TYPE                                         29      0   100%&lt;br /&gt;
pymysql/constants/FLAG                                               15      0   100%&lt;br /&gt;
pymysql/constants/SERVER_STATUS                                      10      0   100%&lt;br /&gt;
pymysql/constants/__init__                                            0      0   100%&lt;br /&gt;
pymysql/converters                                                  148     51    66%&lt;br /&gt;
pymysql/cursors                                                     273     37    86%&lt;br /&gt;
pymysql/err                                                          40      1    98%&lt;br /&gt;
pymysql/tests/__init__                                               14      5    64%&lt;br /&gt;
pymysql/tests/base                                                   25      2    92%&lt;br /&gt;
pymysql/tests/test_DictCursor                                        73      2    97%&lt;br /&gt;
pymysql/tests/test_SSCursor                                          56      6    89%&lt;br /&gt;
pymysql/tests/test_basic                                            186      4    98%&lt;br /&gt;
pymysql/tests/test_connection                                        57      8    86%&lt;br /&gt;
pymysql/tests/test_example                                           17      2    88%&lt;br /&gt;
pymysql/tests/test_issues                                           300     48    84%&lt;br /&gt;
pymysql/tests/test_load_local                                        39      2    95%&lt;br /&gt;
pymysql/tests/test_nextset                                           43      4    91%&lt;br /&gt;
pymysql/tests/thirdparty/__init__                                     7      5    29%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/__init__                        6      2    67%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/capabilities                  196     23    88%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/dbapi20                       423    121    71%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_capabilities      73      6    92%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_dbapi20          100      2    98%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_nonstandard       57      3    95%&lt;br /&gt;
pymysql/times                                                        12      0   100%&lt;br /&gt;
pymysql/util                                                         14      7    50%&lt;br /&gt;
-------------------------------------------------------------------------------------&lt;br /&gt;
TOTAL                                                              3926    569    86%&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The lack of coverage in converters.py refers to functions related to handling&lt;br /&gt;
incoming bound values as well as outgoing result values.    Key data escaping&lt;br /&gt;
features such as escaping of dictionaries, sets, some unicode objects and&lt;br /&gt;
byte objects as well as many pathways into date and time-processing functions are not&lt;br /&gt;
covered.&lt;br /&gt;
&lt;br /&gt;
In connections.py, uncovered features include various SSL and&lt;br /&gt;
very legacy (e.g. version 3.23) MySQL features, but also some protocol parsing&lt;br /&gt;
features.&lt;br /&gt;
&lt;br /&gt;
In cursors.py, there is coverage for an elaborate and mostly undocumented&lt;br /&gt;
performance-related feature that rewrites an INSERT statement to have an&lt;br /&gt;
extended VALUES clause (_do_execute_many), however it fails to cover the very&lt;br /&gt;
likely case where the function will need to break the input set into&lt;br /&gt;
multiple chunks:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;gt;         for arg in args:&lt;br /&gt;
&amp;gt;             v = values % escape(arg, conn)&lt;br /&gt;
&amp;gt;             if isinstance(v, text_type):&lt;br /&gt;
&amp;gt;                 v = v.encode(encoding)&lt;br /&gt;
&amp;gt;             if len(sql) + len(v) + 1 &amp;gt; max_stmt_length:&lt;br /&gt;
!                 rows += self.execute(sql)&lt;br /&gt;
!                 sql = bytearray(prefix)&lt;br /&gt;
&amp;gt;             else:&lt;br /&gt;
&amp;gt;                 sql += b','&lt;br /&gt;
&amp;gt;             sql += v&lt;br /&gt;
&amp;gt;         rows += self.execute(sql)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The most prominent missing coverage in cursors.py seems to be&lt;br /&gt;
for its &amp;quot;scrollable cursor&amp;quot; support.&lt;br /&gt;
&lt;br /&gt;
Beyond the lack of completeness in testing, many of the tests themselves&lt;br /&gt;
are written in an expedient &amp;quot;one giant test&amp;quot; style where dozens of&lt;br /&gt;
assertions and individual behaviors are lumped into one big test&lt;br /&gt;
case (see test_SSCursor.py, test_basic.py-&amp;gt;test_datatypes for examples).&lt;br /&gt;
Test cases like these are difficult to work with when debugging&lt;br /&gt;
regressions as well as when tests for new features need to be added, and ideally&lt;br /&gt;
these should be broken out into clean single-feature tests with consistent&lt;br /&gt;
fixtures.&lt;br /&gt;
&lt;br /&gt;
There is also a suite called &amp;quot;test_issues&amp;quot;, which intends to&lt;br /&gt;
accumulate tests against specific issues that have been reported. This&lt;br /&gt;
isn't a bad idea, though it would be nice to see an effort made into&lt;br /&gt;
categorizing these tests into the actual features they are testing,&lt;br /&gt;
rather than a meaningless list of issue numbers, which could then form&lt;br /&gt;
as the basis for new suites centered around those areas of&lt;br /&gt;
functionality.  The issues reported should be used as the inspiration&lt;br /&gt;
for improvement of the test suite, rather than just another line-item&lt;br /&gt;
to be filed away.&lt;br /&gt;
&lt;br /&gt;
=== Library Documentation ===&lt;br /&gt;
&lt;br /&gt;
As the Python DBAPI is already a well-documented API, and the users of&lt;br /&gt;
PyMySQL are typically coming from MySQL-Python which is documented&lt;br /&gt;
to some degree and for which the use contract is widely known, it seems&lt;br /&gt;
unlikely that users of PyMySQL are eager for comprehensive documentation&lt;br /&gt;
specific to this library.&lt;br /&gt;
&lt;br /&gt;
That being said, PyMySQL does not appear to have any real library&lt;br /&gt;
documentation at all.  There's a README which&lt;br /&gt;
essentially refers to pep-249, an example.py in the root which shows a&lt;br /&gt;
very basic connection / round trip, and that's it. In&lt;br /&gt;
order to know anything about the API, specific parameters, optimization&lt;br /&gt;
strategies, behaviors, etc., one either has to go off of the MySQL-Python documentation and assume&lt;br /&gt;
PyMySQL also features the same parameter, or read the source code.&lt;br /&gt;
&lt;br /&gt;
PyMySQL probably hasn't had much urgent need for real documentation thus&lt;br /&gt;
far.  However, relying upon being a port of MySQL-Python is fast becoming&lt;br /&gt;
something that can no longer be relied upon, as MySQL-Python's development&lt;br /&gt;
is quite stalled, and PyMySQL should aspire to move into the future&lt;br /&gt;
of MySQL and Python with its own featureset and behaviors.  To that end, it would be a &lt;br /&gt;
great idea if it at least made the start&lt;br /&gt;
of a rudimentary Sphinx build including autodoc for module documentation&lt;br /&gt;
which could then be published up to RTD.   This would be very easy to get&lt;br /&gt;
started, and once present, new documentation sections can be added&lt;br /&gt;
iteratively.&lt;br /&gt;
&lt;br /&gt;
=== Architecture and Performance ===&lt;br /&gt;
&lt;br /&gt;
As stated before, PyMySQL has a really matter-of-fact and straightforward&lt;br /&gt;
implementation.   The code is very readable and it's easy to discern even&lt;br /&gt;
unusual features such as the INSERT..VALUES rewriting.  As far as the design,&lt;br /&gt;
there are lots of areas where performance suffers a bit at the Python level;&lt;br /&gt;
these areas could be greatly improved very easily with a little bit of&lt;br /&gt;
attention.&lt;br /&gt;
&lt;br /&gt;
As we are dealing with a driver that is communicating with a low level&lt;br /&gt;
protocol over a socket, it's critical that sending and receiving messages&lt;br /&gt;
is done as efficiently as possible.  The mechanisms used in PyMySQL rely&lt;br /&gt;
heavily on standard Python objects, without the use of __slots__; this means&lt;br /&gt;
that for individual messages on a socket, we are creating a heavyweight object,&lt;br /&gt;
calling its __init__() method and a creating a new __dict__ each time;&lt;br /&gt;
the most common object, OKPacketWrapper(), already wraps a MysqlPacket()&lt;br /&gt;
object, so we're doing two __init__()s in this common case.  OKPacketWrapper&lt;br /&gt;
also implements a `__getattr__` scheme&lt;br /&gt;
to proxy attribute access from itself to the internal MysqlPacket; this is a&lt;br /&gt;
an inefficient mode of operation in Python as it means a lookup must first&lt;br /&gt;
fail on the OKPacketWrapper before it invokes the `__getattr__()` method&lt;br /&gt;
and does another attribute lookup.   A reorg of the packet classes to produce&lt;br /&gt;
far fewer objects for messages as well as to use __slots__ would be&lt;br /&gt;
recommended.&lt;br /&gt;
&lt;br /&gt;
There are also at least some areas where function call overhead is unnecessarily&lt;br /&gt;
high due to the organization of calls.  For example, when calling upon&lt;br /&gt;
cursor.execute(), for parameters passed as a dictionary under Python 2K,&lt;br /&gt;
the isinstance() builtin function will be called on the incoming arguments&lt;br /&gt;
four times at a minimum, with two of each call against the exact same&lt;br /&gt;
criteria.  Additional logic will then call isinstance() on every key of&lt;br /&gt;
the dictionary as well as at least twice on every value within the dictionary,&lt;br /&gt;
in order to determine if the value needs to be converted to bytes as well&lt;br /&gt;
as for some top-level escaping logic checks; the escaping logic then&lt;br /&gt;
goes further with more checks for type() to match the object to a final&lt;br /&gt;
converter function.   While the flow of this logic does appear to be&lt;br /&gt;
reasonable and straightforward, it isn't ideal for a low-level database&lt;br /&gt;
driver library where speed is a high priority; the various wrappers and&lt;br /&gt;
processors can easily be reorganized here to reduce the number of isinstance()&lt;br /&gt;
and type() calls significantly, which would save a lot on function call&lt;br /&gt;
overhead.  It is likely there are many other areas in the codebase where&lt;br /&gt;
similar reductions in call counts can be made without too much disruption.&lt;br /&gt;
&lt;br /&gt;
In Appendix A, a short performance suite is illustrated.  This&lt;br /&gt;
is run against PyMySQL, Mysql-Connector-Python, and MySQL-Python.&lt;br /&gt;
PyMySQL does in fact demonstrate the&lt;br /&gt;
highest call-count behavior, logging 26,851,003 function calls&lt;br /&gt;
compared to MySQL-connector's 11,674,074 for the same operations.  To PyMySQL's&lt;br /&gt;
credit, it somehow completes the test 25-50% faster than MySQL-connector&lt;br /&gt;
despite having more than twice as many function calls; this probably indicates&lt;br /&gt;
the use of something very slow within MySQL-connector, such as catching&lt;br /&gt;
an exception within a tight loop.  However, the real bar we're comparing to&lt;br /&gt;
is MySQL-Python, which is written&lt;br /&gt;
in C and does the whole test with only 47,503 Python function calls; while&lt;br /&gt;
PyMySQL and MySQL-connector-Python compete for time within the 10-15 second&lt;br /&gt;
range without profiling, MySQL-Python completes the whole test in **1.03 seconds**, a 1000%&lt;br /&gt;
improvement over PyMySQL.   This speed difference is not PyMySQL's fault,&lt;br /&gt;
as pure Python is known to be extremely slow compared to C code.&lt;br /&gt;
However, there's a lot of easy wins in PyMySQL's codebase where the function&lt;br /&gt;
call count could probably be reduced dramatically to be even better than that&lt;br /&gt;
of MySQL-connector-Python; ideally, the driver would be able to run this&lt;br /&gt;
test possibly 5-6x slower than MySQL-Python rather than 10x.&lt;br /&gt;
&lt;br /&gt;
=== History Documentation ===&lt;br /&gt;
&lt;br /&gt;
PyMySQL includes a CHANGELOG file in its root which describes the bug fixes&lt;br /&gt;
and features of each release.  The construction of this file is definitely&lt;br /&gt;
within the &amp;quot;expedient&amp;quot; style noted for PyMySQL overall, and for it to be&lt;br /&gt;
genuinely useful for enterprise-level software, it would need to be&lt;br /&gt;
much more complete in its detail.    Each bug fix mentioned is only referred to&lt;br /&gt;
tersely without any link to a pull request or bug report.  While some can&lt;br /&gt;
be linked to a specific change through detective work&lt;br /&gt;
(&amp;quot;Cursor.fetchall() and .fetchmany now return list, not tuple&amp;quot;), it would&lt;br /&gt;
be very difficult for many&lt;br /&gt;
(&amp;quot;Fixed BIT type handling&amp;quot;, &amp;quot;Fixed GC errors&amp;quot; - what was the issue?)&lt;br /&gt;
and in many cases impossible (&amp;quot;Improved Py3k support&amp;quot;).  The file also&lt;br /&gt;
includes no dates so one has to look at the git tags to figure this&lt;br /&gt;
part out (fortunately, releases to seem to be git tagged at least).&lt;br /&gt;
For a user trying to ascertain if particular issues are&lt;br /&gt;
fixed or if behaviors have changed, this file is at best highly frustrating.&lt;br /&gt;
&lt;br /&gt;
A listing of release dates illustrates an average of two releases per year,&lt;br /&gt;
excluding 2012 when PyMySQL was seeking a new maintainer:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
2014-12-02 12:05:10 -0200  (tag: pymysql-0.6.3)&lt;br /&gt;
2014-04-21 14:28:47 -0300  (tag: pymysql-0.6.2)&lt;br /&gt;
2013-10-11 13:36:56 -0300  (tag: pymysql-0.6.1)&lt;br /&gt;
2013-10-04 14:25:14 -0300  (tag: pymysql-0.6)&lt;br /&gt;
2011-11-08 10:27:41 -0800  (tag: pymysql-0.5)&lt;br /&gt;
2010-12-27 17:28:16 +0000  (tag: pymysql-0.4)&lt;br /&gt;
2010-09-03 00:55:29 +0000  (tag: pymysql-0.3)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Community Involvement ===&lt;br /&gt;
&lt;br /&gt;
This is an area where PyMySQL seems to shine, and might even be an advantage&lt;br /&gt;
as compared to MySQL-Connector-Python, which remains under the cloak&lt;br /&gt;
of MySQL's vastly overstuffed bugtracker under the ownership of&lt;br /&gt;
the famously-opaque Oracle.   For a small, low key library without&lt;br /&gt;
any real documentation and only 126 tests, it features 33 contributors on&lt;br /&gt;
Github and is up to pull request #285, which is quite high.  Because&lt;br /&gt;
PyMySQL is small and straightforward, it does lend itself to contribution&lt;br /&gt;
and seems to do very well in this area.   The developers so far have been&lt;br /&gt;
responsive to issues and pull requests made by the author.&lt;br /&gt;
&lt;br /&gt;
Based on github contributor graphs it appears that PyMySQL had a lull&lt;br /&gt;
in 2012 but was picked up again afterwards in 2013.  As of 2012, its most&lt;br /&gt;
prominent developer Pete Hunt stopped development, and on July 25, 2013&lt;br /&gt;
added a note to the README that the project was looking for a new maintainer.&lt;br /&gt;
As of 2013 it was picked up by the current most prominent developers&lt;br /&gt;
Marcel Rodrigues (github username lecram) and INADA Naoki&lt;br /&gt;
(github username methane).&lt;br /&gt;
&lt;br /&gt;
Most issues that have been noted thus far within the areas of testing,&lt;br /&gt;
documentation and architecture are all very fixable; if the Openstack community&lt;br /&gt;
can provide resources to address some of these areas, and if the PyMySQL&lt;br /&gt;
developers are reasonably open to accepting new changes and producing new&lt;br /&gt;
releases in a timely fashion, the PyMySQL driver&lt;br /&gt;
could rapidly become a fully formidable contender in the MySQL driver field.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Appendix A - Performance Tests ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
import cProfile&lt;br /&gt;
import StringIO&lt;br /&gt;
import pstats&lt;br /&gt;
import contextlib&lt;br /&gt;
import time&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextlib.contextmanager&lt;br /&gt;
def profiled(dbapi):&lt;br /&gt;
    pr = cProfile.Profile()&lt;br /&gt;
    pr.enable()&lt;br /&gt;
    yield&lt;br /&gt;
    pr.disable()&lt;br /&gt;
    s = StringIO.StringIO()&lt;br /&gt;
    ps = pstats.Stats(pr, stream=s).sort_stats('cumulative')&lt;br /&gt;
    ps.print_stats()&lt;br /&gt;
    print &amp;quot;DBAPI:  %s&amp;quot; % dbapi&lt;br /&gt;
    print s.getvalue()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextlib.contextmanager&lt;br /&gt;
def timeonly(dbapi):&lt;br /&gt;
    now = time.time()&lt;br /&gt;
    try:&lt;br /&gt;
        yield&lt;br /&gt;
    finally:&lt;br /&gt;
        total = time.time() - now&lt;br /&gt;
        print &amp;quot;DBAPI:  %s, total seconds %f&amp;quot; % (dbapi, total)&lt;br /&gt;
&lt;br /&gt;
import MySQLdb&lt;br /&gt;
import pymysql&lt;br /&gt;
from mysql import connector as mysqlconnector&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def go(dbapi, ctx):&lt;br /&gt;
    conn = dbapi.connect(&lt;br /&gt;
        user='scott', passwd='tiger', host='localhost', db='test')&lt;br /&gt;
    cursor = conn.cursor()&lt;br /&gt;
    cursor.execute(&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
CREATE TABLE IF NOT EXISTS test_things (&lt;br /&gt;
    x INTEGER,&lt;br /&gt;
    y VARCHAR(255),&lt;br /&gt;
    z FLOAT&lt;br /&gt;
) engine=InnoDB&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;)&lt;br /&gt;
    cursor.execute(&amp;quot;DELETE from test_things&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    with ctx(dbapi):&lt;br /&gt;
        for row in xrange(1000):&lt;br /&gt;
            cursor.execute(&lt;br /&gt;
                &amp;quot;INSERT INTO test_things (x, y, z) &amp;quot;&lt;br /&gt;
                &amp;quot;VALUES (%(x)s, %(y)s, %(z)s)&amp;quot;,&lt;br /&gt;
                {&amp;quot;x&amp;quot;: row, &amp;quot;y&amp;quot;: &amp;quot;row number %d&amp;quot; % row,&lt;br /&gt;
                 &amp;quot;z&amp;quot;: row * 4.57292, }&lt;br /&gt;
            )&lt;br /&gt;
&lt;br /&gt;
        for x in xrange(500):&lt;br /&gt;
            cursor.execute(&lt;br /&gt;
                &amp;quot;select * from test_things&amp;quot;)&lt;br /&gt;
            for row in cursor.fetchall():&lt;br /&gt;
                row[0], row[1], row[2]&lt;br /&gt;
&lt;br /&gt;
    cursor.close()&lt;br /&gt;
    conn.close()&lt;br /&gt;
&lt;br /&gt;
go(pymysql, profiled)&lt;br /&gt;
go(mysqlconnector, profiled)&lt;br /&gt;
go(MySQLdb, profiled)&lt;br /&gt;
&lt;br /&gt;
go(pymysql, timeonly)&lt;br /&gt;
go(mysqlconnector, timeonly)&lt;br /&gt;
go(MySQLdb, timeonly)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DBAPI:  &amp;lt;module 'pymysql' from '/Users/classic/dev/PyMySQL/pymysql/__init__.pyc'&amp;gt;&lt;br /&gt;
         26851003 function calls in 15.924 seconds&lt;br /&gt;
&lt;br /&gt;
   Ordered by: cumulative time&lt;br /&gt;
&lt;br /&gt;
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;
     1500    0.010    0.000   15.923    0.011 /Users/classic/dev/PyMySQL/pymysql/cursors.py:105(execute)&lt;br /&gt;
     1500    0.003    0.000   15.883    0.011 /Users/classic/dev/PyMySQL/pymysql/cursors.py:269(_query)&lt;br /&gt;
     1500    0.004    0.000   15.859    0.011 /Users/classic/dev/PyMySQL/pymysql/connections.py:746(query)&lt;br /&gt;
     1500    0.003    0.000   15.819    0.011 /Users/classic/dev/PyMySQL/pymysql/connections.py:892(_read_query_result)&lt;br /&gt;
     1500    0.004    0.000   15.812    0.011 /Users/classic/dev/PyMySQL/pymysql/connections.py:1097(read)&lt;br /&gt;
      500    0.003    0.000   15.559    0.031 /Users/classic/dev/PyMySQL/pymysql/connections.py:1154(_read_result_packet)&lt;br /&gt;
      500    0.900    0.002   15.429    0.031 /Users/classic/dev/PyMySQL/pymysql/connections.py:1187(_read_rowdata_packet)&lt;br /&gt;
   500000    3.417    0.000    9.173    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1200(_read_row_from_packet)&lt;br /&gt;
  2012000    1.157    0.000    5.307    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:311(read_length_coded_string)&lt;br /&gt;
   504500    1.495    0.000    5.209    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:845(_read_packet)&lt;br /&gt;
  2014500    1.248    0.000    3.035    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:293(read_length_encoded_integer)&lt;br /&gt;
  3539500    2.439    0.000    2.750    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:245(read)&lt;br /&gt;
  1009000    0.779    0.000    2.307    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:870(_read_bytes)&lt;br /&gt;
  1009000    1.197    0.000    1.423    0.000 {method 'read' of '_io.BufferedReader' objects}&lt;br /&gt;
   508500    0.379    0.000    0.695    0.000 /Users/classic/dev/PyMySQL/pymysql/util.py:3(byte2int)&lt;br /&gt;
  5058000    0.465    0.000    0.465    0.000 {len}&lt;br /&gt;
   500500    0.222    0.000    0.455    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1146(_check_packet_is_eof)&lt;br /&gt;
   504500    0.205    0.000    0.375    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:342(check_error)&lt;br /&gt;
  2504000    0.275    0.000    0.275    0.000 {method 'append' of 'list' objects}&lt;br /&gt;
   501500    0.182    0.000    0.230    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:326(is_eof_packet)&lt;br /&gt;
     3500    0.009    0.000    0.226    0.000 /Users/classic/dev/PyMySQL/pymysql/_socketio.py:45(readinto)&lt;br /&gt;
     3500    0.207    0.000    0.207    0.000 {method 'recv_into' of '_socket.socket' objects}&lt;br /&gt;
  1022000    0.203    0.000    0.203    0.000 {_struct.unpack}&lt;br /&gt;
   526000    0.200    0.000    0.200    0.000 {isinstance}&lt;br /&gt;
  1501000    0.195    0.000    0.195    0.000 {method 'get' of 'dict' objects}&lt;br /&gt;
   504500    0.176    0.000    0.176    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:238(__init__)&lt;br /&gt;
   504500    0.170    0.000    0.170    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:339(is_error_packet)&lt;br /&gt;
  2014500    0.167    0.000    0.167    0.000 {ord}&lt;br /&gt;
      500    0.006    0.000    0.125    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1224(_get_descriptions)&lt;br /&gt;
     2000    0.003    0.000    0.087    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:361(__init__)&lt;br /&gt;
     2000    0.019    0.000    0.084    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:365(__parse_field_descriptor)&lt;br /&gt;
   500000    0.056    0.000    0.056    0.000 /Users/classic/dev/PyMySQL/pymysql/converters.py:301(through)&lt;br /&gt;
     1500    0.005    0.000    0.037    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:915(_execute_command)&lt;br /&gt;
     1500    0.003    0.000    0.027    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:886(_write_bytes)&lt;br /&gt;
     1500    0.002    0.000    0.024    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/socket.py:223(meth)&lt;br /&gt;
     1500    0.021    0.000    0.021    0.000 {method 'sendall' of '_socket.socket' objects}&lt;br /&gt;
     1500    0.020    0.000    0.020    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:276(_do_get_result)&lt;br /&gt;
     1000    0.003    0.000    0.019    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:95(_escape_args)&lt;br /&gt;
     4000    0.002    0.000    0.015    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:99(&amp;lt;genexpr&amp;gt;)&lt;br /&gt;
     1000    0.002    0.000    0.014    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1127(_read_ok_packet)&lt;br /&gt;
     3000    0.003    0.000    0.013    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:712(escape)&lt;br /&gt;
     1000    0.005    0.000    0.012    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:416(__init__)&lt;br /&gt;
     8000    0.011    0.000    0.011    0.000 {method 'decode' of 'str' objects}&lt;br /&gt;
     3500    0.003    0.000    0.007    0.000 {method '_checkReadable' of '_io._IOBase' objects}&lt;br /&gt;
     2000    0.003    0.000    0.006    0.000 /Users/classic/dev/PyMySQL/pymysql/converters.py:19(escape_item)&lt;br /&gt;
     4000    0.002    0.000    0.006    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:126(&amp;lt;genexpr&amp;gt;)&lt;br /&gt;
     7500    0.003    0.000    0.005    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:115(ensure_bytes)&lt;br /&gt;
     2000    0.003    0.000    0.005    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:386(description)&lt;br /&gt;
     5500    0.004    0.000    0.004    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:268(advance)&lt;br /&gt;
     1000    0.001    0.000    0.004    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:722(escape_string)&lt;br /&gt;
     3500    0.004    0.000    0.004    0.000 /Users/classic/dev/PyMySQL/pymysql/_socketio.py:87(readable)&lt;br /&gt;
      500    0.002    0.000    0.004    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:442(__init__)&lt;br /&gt;
     3500    0.003    0.000    0.003    0.000 {method '_checkClosed' of '_io._IOBase' objects}&lt;br /&gt;
     1000    0.001    0.000    0.003    0.000 /Users/classic/dev/PyMySQL/pymysql/converters.py:59(escape_string)&lt;br /&gt;
     1500    0.003    0.000    0.003    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1080(__init__)&lt;br /&gt;
     1500    0.001    0.000    0.003    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:92(nextset)&lt;br /&gt;
     4000    0.002    0.000    0.002    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:397(get_column_length)&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 /Users/classic/dev/PyMySQL/pymysql/converters.py:56(escape_float)&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 {method 'sub' of '_sre.SRE_Pattern' objects}&lt;br /&gt;
     3000    0.002    0.000    0.002    0.000 {_struct.pack}&lt;br /&gt;
     6000    0.002    0.000    0.002    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:62(_get_db)&lt;br /&gt;
     1500    0.001    0.000    0.002    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:80(_nextset)&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 /Users/classic/dev/PyMySQL/pymysql/util.py:9(int2byte)&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:244(fetchall)&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {min}&lt;br /&gt;
     2500    0.001    0.000    0.001    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:323(is_ok_packet)&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Users/classic/dev/PyMySQL/pymysql/converters.py:52(escape_int)&lt;br /&gt;
     2000    0.001    0.000    0.001    0.000 {method 'items' of 'dict' objects}&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {getattr}&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1093(__del__)&lt;br /&gt;
     1000    0.000    0.000    0.000    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:259(read_all)&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:67(_check_executed)&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:336(is_load_local_packet)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/contextlib.py:21(__exit__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 test.py:8(profiled)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DBAPI:  &amp;lt;module 'mysql.connector' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/__init__.pyc'&amp;gt;&lt;br /&gt;
         11674074 function calls in 19.049 seconds&lt;br /&gt;
&lt;br /&gt;
   Ordered by: cumulative time&lt;br /&gt;
&lt;br /&gt;
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;
      500    0.640    0.001   18.556    0.037 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:820(fetchall)&lt;br /&gt;
      500    0.003    0.000   11.196    0.022 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:655(get_rows)&lt;br /&gt;
      500    1.561    0.003   11.191    0.022 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:292(read_text_result)&lt;br /&gt;
   504500    3.303    0.000    7.306    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/network.py:219(recv_plain)&lt;br /&gt;
   500000    3.572    0.000    6.625    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:359(row_to_python)&lt;br /&gt;
  1009000    3.155    0.000    3.155    0.000 {method 'recv_into' of '_socket.socket' objects}&lt;br /&gt;
   500000    2.049    0.000    2.329    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:220(read_lc_string_list)&lt;br /&gt;
   500000    0.653    0.000    2.116    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:531(_STRING_to_python)&lt;br /&gt;
   502000    0.378    0.000    1.329    0.000 {method 'decode' of 'bytearray' objects}&lt;br /&gt;
   502000    0.221    0.000    0.951    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/utf_8.py:15(decode)&lt;br /&gt;
   502000    0.730    0.000    0.730    0.000 {_codecs.utf_8_decode}&lt;br /&gt;
   500000    0.514    0.000    0.514    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:413(_INT_to_python)&lt;br /&gt;
   509500    0.510    0.000    0.510    0.000 {_struct.unpack_from}&lt;br /&gt;
     1500    0.009    0.000    0.492    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:452(execute)&lt;br /&gt;
     1500    0.004    0.000    0.437    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:705(cmd_query)&lt;br /&gt;
   500000    0.367    0.000    0.367    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:405(_FLOAT_to_python)&lt;br /&gt;
   504500    0.343    0.000    0.343    0.000 {method 'extend' of 'bytearray' objects}&lt;br /&gt;
     1500    0.007    0.000    0.315    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:481(_send_cmd)&lt;br /&gt;
  2501500    0.275    0.000    0.275    0.000 {method 'append' of 'list' objects}&lt;br /&gt;
   500500    0.246    0.000    0.246    0.000 {method 'startswith' of 'bytearray' objects}&lt;br /&gt;
   524002    0.150    0.000    0.150    0.000 {isinstance}&lt;br /&gt;
     1500    0.011    0.000    0.118    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:613(_handle_result)&lt;br /&gt;
  1004500    0.112    0.000    0.112    0.000 {len}&lt;br /&gt;
   500000    0.092    0.000    0.092    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:204(description)&lt;br /&gt;
     1500    0.007    0.000    0.042    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/network.py:118(send_plain)&lt;br /&gt;
     2000    0.013    0.000    0.041    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:226(parse_column)&lt;br /&gt;
     1000    0.008    0.000    0.034    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:346(_process_params_dict)&lt;br /&gt;
     1500    0.002    0.000    0.029    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/socket.py:223(meth)&lt;br /&gt;
     1500    0.026    0.000    0.026    0.000 {method 'sendall' of '_socket.socket' objects}&lt;br /&gt;
     5000    0.007    0.000    0.014    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/catch23.py:79(struct_unpack)&lt;br /&gt;
     1000    0.001    0.000    0.013    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:562(_handle_ok)&lt;br /&gt;
    12000    0.011    0.000    0.011    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:167(read_lc_string)&lt;br /&gt;
     1000    0.004    0.000    0.010    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:199(parse_ok)&lt;br /&gt;
     3000    0.005    0.000    0.009    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:152(to_mysql)&lt;br /&gt;
     1500    0.003    0.000    0.007    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:118(make_command)&lt;br /&gt;
     3000    0.003    0.000    0.007    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:130(quote)&lt;br /&gt;
     1500    0.005    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/network.py:53(_prepare_packets)&lt;br /&gt;
     1500    0.003    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:406(_handle_result)&lt;br /&gt;
     3000    0.003    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:102(escape)&lt;br /&gt;
     1000    0.002    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:252(parse_eof)&lt;br /&gt;
      500    0.001    0.000    0.005    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:579(_handle_eof)&lt;br /&gt;
     2000    0.003    0.000    0.005    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:545(_handle_server_status)&lt;br /&gt;
     1500    0.003    0.000    0.004    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:53(int1store)&lt;br /&gt;
     3000    0.003    0.000    0.003    0.000 {method 'encode' of 'str' objects}&lt;br /&gt;
     9001    0.003    0.000    0.003    0.000 {method 'replace' of 'str' objects}&lt;br /&gt;
     1500    0.003    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:295(_reset_result)&lt;br /&gt;
     4500    0.003    0.000    0.003    0.000 {_struct.pack}&lt;br /&gt;
     2500    0.003    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:296(read_lc_int)&lt;br /&gt;
      500    0.002    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:757(_handle_eof)&lt;br /&gt;
     6027    0.003    0.000    0.003    0.000 {method 'format' of 'str' objects}&lt;br /&gt;
     3000    0.002    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:1221(_set_unread_result)&lt;br /&gt;
      500    0.001    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:219(parse_column_count)&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 {repr}&lt;br /&gt;
     4527    0.002    0.000    0.002    0.000 {getattr}&lt;br /&gt;
     4000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/constants.py:34(flag_is_set)&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:383(_handle_noresultset)&lt;br /&gt;
     4000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:1233(_get_unread_result)&lt;br /&gt;
     2001    0.001    0.000    0.001    0.000 {method 'items' of 'dict' objects}&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:307(_have_unread_result)&lt;br /&gt;
     3000    0.001    0.000    0.001    0.000 {method 'lower' of 'str' objects}&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 {range}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:173(_str_to_mysql)&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:169(_float_to_mysql)&lt;br /&gt;
     1000    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:161(_int_to_mysql)&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:1313(_get_getwarnings)&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:200(reset)&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:397(_handle_resultset)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/__init__.py:71(search_function)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/ascii.py:41(getregentry)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/contextlib.py:21(__exit__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/__init__.py:49(normalize_encoding)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 test.py:8(profiled)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/codecs.py:77(__new__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {__import__}&lt;br /&gt;
        3    0.000    0.000    0.000    0.000 {method 'get' of 'dict' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'split' of 'str' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {built-in method __new__ of type object at 0x1001534e8}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'translate' of 'str' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'join' of 'str' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {hasattr}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DBAPI:  &amp;lt;module 'MySQLdb' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/__init__.pyc'&amp;gt;&lt;br /&gt;
         47503 function calls in 0.918 seconds&lt;br /&gt;
&lt;br /&gt;
   Ordered by: cumulative time&lt;br /&gt;
&lt;br /&gt;
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;
     1500    0.009    0.000    0.917    0.001 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:164(execute)&lt;br /&gt;
     1500    0.002    0.000    0.890    0.001 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:353(_query)&lt;br /&gt;
     1500    0.004    0.000    0.498    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:315(_do_query)&lt;br /&gt;
     1500    0.016    0.000    0.391    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:358(_post_get_result)&lt;br /&gt;
     1500    0.001    0.000    0.375    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:324(_fetch_row)&lt;br /&gt;
      500    0.374    0.001    0.374    0.001 {built-in method fetch_row}&lt;br /&gt;
     1500    0.007    0.000    0.362    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:142(_do_get_result)&lt;br /&gt;
     1500    0.002    0.000    0.351    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:351(_get_result)&lt;br /&gt;
     1500    0.349    0.000    0.349    0.000 {method 'store_result' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.131    0.000    0.131    0.000 {method 'query' of '_mysql.connection' objects}&lt;br /&gt;
     4000    0.002    0.000    0.010    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:184(&amp;lt;genexpr&amp;gt;)&lt;br /&gt;
     3000    0.002    0.000    0.008    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/connections.py:267(literal)&lt;br /&gt;
     1500    0.007    0.000    0.007    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:107(_warning_check)&lt;br /&gt;
     3000    0.002    0.000    0.006    0.000 {method 'escape' of '_mysql.connection' objects}&lt;br /&gt;
     6000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:159(_get_db)&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/converters.py:81(Float2Str)&lt;br /&gt;
     2500    0.001    0.000    0.001    0.000 {isinstance}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/connections.py:202(string_literal)&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:380(fetchall)&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 {built-in method describe}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 {method 'string_literal' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {method 'insert_id' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {method 'affected_rows' of '_mysql.connection' objects}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/converters.py:69(Thing2Str)&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 {method 'warning_count' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 {method 'info' of '_mysql.connection' objects}&lt;br /&gt;
     1000    0.000    0.000    0.000    0.000 {method 'iteritems' of 'dict' objects}&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:103(_check_executed)&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 {len}&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 {built-in method field_flags}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/contextlib.py:21(__exit__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 test.py:8(profiled)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DBAPI:  &amp;lt;module 'pymysql' from '/Users/classic/dev/PyMySQL/pymysql/__init__.pyc'&amp;gt;, total seconds 10.046012&lt;br /&gt;
DBAPI:  &amp;lt;module 'mysql.connector' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/__init__.pyc'&amp;gt;, total seconds 15.403260&lt;br /&gt;
DBAPI:  &amp;lt;module 'MySQLdb' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/__init__.pyc'&amp;gt;, total seconds 1.028737&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mike Bayer</name></author>	</entry>

	<entry>
		<id>https://wiki.openstack.org/w/index.php?title=PyMySQL_evaluation&amp;diff=72562</id>
		<title>PyMySQL evaluation</title>
		<link rel="alternate" type="text/html" href="https://wiki.openstack.org/w/index.php?title=PyMySQL_evaluation&amp;diff=72562"/>
				<updated>2015-01-28T20:30:04Z</updated>
		
		<summary type="html">&lt;p&gt;Mike Bayer: /* PyMySQL Code Review */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= PyMySQL Evaluation =&lt;br /&gt;
&lt;br /&gt;
This page will capture issues related to Openstack moving to the [https://github.com/PyMySQL/PyMySQL/ PyMySQL driver] for MySQL/MariaDB dbapi access.&lt;br /&gt;
&lt;br /&gt;
== Rationale ==&lt;br /&gt;
&lt;br /&gt;
While the [https://github.com/farcepest/MySQLdb1 MySQL-Python] driver is a very mature and stable driver, it does not provide compatibility with either Python 3 or with [http://eventlet.net/doc/patching.html eventlet monkeypatching] (MySQL-Python  [https://github.com/eventlet/eventlet/blob/v0.16.1/eventlet/patcher.py#L225-L237 can be monkeypatched] with eventlet, but this feature disabled by default and missed in documentation). So OpenStack's usage of MySQL-Python, combined with the fact that concurrency is provided by eventlet, means that we currently have fully serialized database access within a single process, that is, only one database command occurs at a time within an Openstack Python process.&lt;br /&gt;
&lt;br /&gt;
== Drivers Under Consideration ==&lt;br /&gt;
&lt;br /&gt;
The two drivers that are known to provide eventlet-monkeypatch compatibility are MySQL-Connector and PyMySQL, as they are written in pure Python.   The two other well-known drivers for MySQL are MySQL-Python and OurSQL, both of which are written in C and offer no explicit support for eventlet or async.  They can reportedly be built to support gevent using a system called [https://github.com/CMGS/greenify Greenify], but the maturity and/or stabiltiy of this system is unknown.&lt;br /&gt;
&lt;br /&gt;
A comparison at http://www.diamondtin.com/2014/sqlalchemy-gevent-mysql-python-drivers-comparison/ illustrates performance metrics observed with all four of these drivers, with MySQL-python built both without and with the &amp;quot;greenify&amp;quot; system.&lt;br /&gt;
&lt;br /&gt;
A summary of the status of all four drivers is as follows.&lt;br /&gt;
&lt;br /&gt;
=== MySQL-Python ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/farcepest/MySQLdb1 MySQL-Python] is the most widely used Python driver.  However, it does not support Python 3, and it does not support async systems unless used within a thread pool.   While a system known as Greenify can potentially resolve the latter situation, the Python 3 limitation is still a deal-breaker - pull request with Python 3 support [https://github.com/farcepest/MySQLdb1/pull/62 was proposed] to MySQL-python in April 2014 and still not merged.&lt;br /&gt;
&lt;br /&gt;
=== mysqlclient ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/PyMySQL/mysqlclient-python mysqlclient] is a fork of MySQL-python. It add Python 3.3 support and merges some pull requests.&lt;br /&gt;
&lt;br /&gt;
=== OurSQL ===&lt;br /&gt;
&lt;br /&gt;
[https://pythonhosted.org/oursql/ OurSQL] is an alternative MySQL driver that is also written in C.  It features fast performance and uses a different execution model than MySQL-python, using prepared statements.    OurSQL does have a Python 3 port hosted on [https://launchpad.net/oursql/py3k/ launchpad] but is not integrated with OurSQL itself nor is it published on Pypi.     Because it is written in C, it does not offer compatibility with async systems including eventlet-style monkeypatching.&lt;br /&gt;
&lt;br /&gt;
Besides the shaky Python 3 support and lack of eventlet support, OurSQL is also not maintained, seeing its last master commit on [https://code.launchpad.net/~jerub/oursql/master 2012-06-05].&lt;br /&gt;
&lt;br /&gt;
=== MySQL-Connector-Python ===&lt;br /&gt;
&lt;br /&gt;
[https://pypi.python.org/pypi/mysql-connector-python MySQL-connector-Python] is a pure Python MySQL driver, and is now released under the auspices of the MySQL project itself, as owned by Oracle.  MySQL-connector-Python supports both Python 3 as well as eventlet monkeypatching, and is well maintained.  It is endorsed by Oracle as the official Oracle-supported driver for MySQL, so to that extent, it is in most ways Openstack's first choice in driver.  However, Oracle refuses to publish MySQL-connector-Python on Pypi, which is critical to the Openstack infrastructure.  Repeated attempts to communicate with Oracle in order to resolve this issue have not made any progress.    Therefore, for this one unfortunate reason, MySQL-connector-Python will not have a place in the Openstack ecosystem unless this issue is resolved.&lt;br /&gt;
&lt;br /&gt;
=== PyMySQL ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/PyMySQL/PyMySQL PyMySQL] is a pure Python MySQL&lt;br /&gt;
driver, first written as a rough port of the MySQL-Python driver.&lt;br /&gt;
PyMySQL meets all of Openstack's criterion for a driver: it is fully&lt;br /&gt;
open source, hosted on Github, released on Pypi, is actively&lt;br /&gt;
maintained, is written in pure Python so is eventlet-monkeypatch&lt;br /&gt;
compatible, and is fully Python 3 compatible.&lt;br /&gt;
&lt;br /&gt;
As this document is named &amp;quot;PyMySQL Evaluation&amp;quot;, it should be apparent that&lt;br /&gt;
this is the driver Openstack is currently leaning towards; because it is the only&lt;br /&gt;
one that meets all criteria fully, it is already most likely the &amp;quot;winner&amp;quot;.  However, it does&lt;br /&gt;
have some minor code quality issues which hopefully can be addressed in some&lt;br /&gt;
way; the section below titled &amp;quot;PyMySQL Code Review&amp;quot; will summarize the&lt;br /&gt;
current state of the code.&lt;br /&gt;
&lt;br /&gt;
===MySQL DB Drivers Comparison===&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Project !! PyPi hosted !! Eventlet friendly !! Python 3 compatibility !! Maturity and/or stability  !! Comment&lt;br /&gt;
|-&lt;br /&gt;
| MySQL-Python || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: orange;&amp;quot; | Partial || style=&amp;quot;background-color: red;&amp;quot; | No  || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || Can be monkeypatched by eventlet, but only to enable thread pooling&lt;br /&gt;
|-&lt;br /&gt;
| mysqlclient || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: orange;&amp;quot; | Partial || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes  || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || MySQL-python fork, so should be similar to it&lt;br /&gt;
|-&lt;br /&gt;
| OurSQL || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: red;&amp;quot; | No || style=&amp;quot;background-color: orange;&amp;quot; | Yes, but not Pypi hosted  || style=&amp;quot;background-color: red;&amp;quot; | No || Development halted fairly early on, and has not seen commits/releases in two years&lt;br /&gt;
|-&lt;br /&gt;
| MySQL-Connector-Python || style=&amp;quot;background-color: red;&amp;quot; | No || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes  || style=&amp;quot;background-color: orange;&amp;quot; | Yes, though the driver is still fairly new || The official Oracle-supported driver for MySQL&lt;br /&gt;
|-&lt;br /&gt;
| PyMySQL || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes  || style=&amp;quot;background-color: orange;&amp;quot; | Yes, however see notes below. || Actively maintained and popular.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== PyMySQL Code Review ==&lt;br /&gt;
&lt;br /&gt;
PyMySQL started roughly as a pure Python port of MySQL-Python.   The sections below will&lt;br /&gt;
detail various aspects of the driver and the status of the code.   Overall, the general&lt;br /&gt;
tone of PyMySQL is one of code that was written in the spirit of pragmatism and immediate&lt;br /&gt;
need; it is straightforward, free of serious antipatterns and performs&lt;br /&gt;
its task in a matter-of-fact way.  However,&lt;br /&gt;
it lacks polish and completeness in many areas that would normally be in better&lt;br /&gt;
condition for a more mature project.  These areas are all highly fixable, and it is&lt;br /&gt;
hoped that these items can be shared with the current PyMySQL developers, through&lt;br /&gt;
a combination of this document itself as well as new issues and pull requests&lt;br /&gt;
on the PyMySQL tracker, so that this fairly promising library can be pushed&lt;br /&gt;
to the next level.   This would require that the developers are amenable to&lt;br /&gt;
these improvements.&lt;br /&gt;
&lt;br /&gt;
=== Coding Style / Pep8 ===&lt;br /&gt;
&lt;br /&gt;
PyMySQL's coding style is pretty good.  The majority of it passes all&lt;br /&gt;
flake8 tests, save for a few dozen whitespace issues and&lt;br /&gt;
some long lines here and there within the core&lt;br /&gt;
modules, and moreso within the tests which appear to be a bit more&lt;br /&gt;
crufty than the core modules.  A full run of flake8 produces only 128&lt;br /&gt;
errors, and with a default run of autopep8 against E1,E2,E3, we can&lt;br /&gt;
fix all of them in one pass except for 38 remaining long line warnings.&lt;br /&gt;
The code layout is mostly idiomatic and reasonable.&lt;br /&gt;
&lt;br /&gt;
The codebase is probably short on docstrings.  Those methods that are&lt;br /&gt;
part of the public DBAPI do have docstrings, but tend to be extremely&lt;br /&gt;
terse (example, docstring for cursor.execute(): &amp;quot;'Execute a query'&amp;quot;).&lt;br /&gt;
Other methods that aren't public tend to not have any docstrings, but&lt;br /&gt;
the codebase is not at all hard to read and the purpose of methods and&lt;br /&gt;
functions is pretty easy to discern just by their names and implementations,&lt;br /&gt;
however those who seek inline documentation as a means to understand a codebase&lt;br /&gt;
will be disappointed.   The terseness of public API docstrings is more of&lt;br /&gt;
an issue in terms of the overall lack of documentation for PyMySQL,&lt;br /&gt;
see the section &amp;quot;Documentation&amp;quot; below.&lt;br /&gt;
&lt;br /&gt;
=== Test Coverage ===&lt;br /&gt;
&lt;br /&gt;
The test coverage for PyMySQL is definitely lacking.   From a visual&lt;br /&gt;
inspection alone, it's apparent that of the dozen or so test modules,&lt;br /&gt;
most of them have less then ten tests each and the modules are very short.&lt;br /&gt;
For a full test run, there's only 126 tests; contrast this to&lt;br /&gt;
MySQL-Connector-Python which has 531 tests.&lt;br /&gt;
&lt;br /&gt;
Additionally, all of the tests are live round-trip tests against a&lt;br /&gt;
real database.  There are no non-live unit tests of any kind,&lt;br /&gt;
leaving codepaths that are not easily exercised&lt;br /&gt;
on a generic MySQL database out in the cold.  This means that features designed&lt;br /&gt;
for particular versions of MySQL, particular datatypes, particular error&lt;br /&gt;
conditions, and particular kinds of message packets that aren't created for&lt;br /&gt;
real are simply not tested.&lt;br /&gt;
&lt;br /&gt;
The lack of tests is apparent when run with coverage.  The test suite itself&lt;br /&gt;
does not appear to have any signs that it is normally run with coverage&lt;br /&gt;
turned on, with no directives in tox.ini or similar.   With coverage,&lt;br /&gt;
the overall coverage is 85%, but the modules most lacking in coverage&lt;br /&gt;
are also the most critical to PyMySQL's core functionality,&lt;br /&gt;
connections.py, converters.py and cursors.py:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Name                                                              Stmts   Miss  Cover&lt;br /&gt;
-------------------------------------------------------------------------------------&lt;br /&gt;
pymysql/__init__                                                     51      3    94%&lt;br /&gt;
pymysql/_compat                                                      14      4    71%&lt;br /&gt;
pymysql/_socketio                                                    72     30    58%&lt;br /&gt;
pymysql/charset                                                     228      3    99%&lt;br /&gt;
pymysql/connections                                                 827    188    77%&lt;br /&gt;
pymysql/constants/CLIENT                                             18      0   100%&lt;br /&gt;
pymysql/constants/COMMAND                                            32      0   100%&lt;br /&gt;
pymysql/constants/ER                                                471      0   100%&lt;br /&gt;
pymysql/constants/FIELD_TYPE                                         29      0   100%&lt;br /&gt;
pymysql/constants/FLAG                                               15      0   100%&lt;br /&gt;
pymysql/constants/SERVER_STATUS                                      10      0   100%&lt;br /&gt;
pymysql/constants/__init__                                            0      0   100%&lt;br /&gt;
pymysql/converters                                                  148     51    66%&lt;br /&gt;
pymysql/cursors                                                     273     37    86%&lt;br /&gt;
pymysql/err                                                          40      1    98%&lt;br /&gt;
pymysql/tests/__init__                                               14      5    64%&lt;br /&gt;
pymysql/tests/base                                                   25      2    92%&lt;br /&gt;
pymysql/tests/test_DictCursor                                        73      2    97%&lt;br /&gt;
pymysql/tests/test_SSCursor                                          56      6    89%&lt;br /&gt;
pymysql/tests/test_basic                                            186      4    98%&lt;br /&gt;
pymysql/tests/test_connection                                        57      8    86%&lt;br /&gt;
pymysql/tests/test_example                                           17      2    88%&lt;br /&gt;
pymysql/tests/test_issues                                           300     48    84%&lt;br /&gt;
pymysql/tests/test_load_local                                        39      2    95%&lt;br /&gt;
pymysql/tests/test_nextset                                           43      4    91%&lt;br /&gt;
pymysql/tests/thirdparty/__init__                                     7      5    29%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/__init__                        6      2    67%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/capabilities                  196     23    88%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/dbapi20                       423    121    71%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_capabilities      73      6    92%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_dbapi20          100      2    98%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_nonstandard       57      3    95%&lt;br /&gt;
pymysql/times                                                        12      0   100%&lt;br /&gt;
pymysql/util                                                         14      7    50%&lt;br /&gt;
-------------------------------------------------------------------------------------&lt;br /&gt;
TOTAL                                                              3926    569    86%&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The lack of coverage in converters.py refers to functions related to handling&lt;br /&gt;
incoming bound values as well as outgoing result values.    Key data escaping&lt;br /&gt;
features such as escaping of dictionaries, sets, some unicode objects and&lt;br /&gt;
byte objects as well as many pathways into date and time-processing functions are not&lt;br /&gt;
covered.&lt;br /&gt;
&lt;br /&gt;
In connections.py, uncovered features include various SSL and&lt;br /&gt;
very legacy (e.g. version 3.23) MySQL features, but also some protocol parsing&lt;br /&gt;
features.&lt;br /&gt;
&lt;br /&gt;
In cursors.py, there is coverage for an elaborate and mostly undocumented&lt;br /&gt;
performance-related feature that rewrites an INSERT statement to have an&lt;br /&gt;
extended VALUES clause (_do_execute_many), however it fails to cover the very&lt;br /&gt;
likely case where the function will need to break the input set into&lt;br /&gt;
multiple chunks:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;gt;         for arg in args:&lt;br /&gt;
&amp;gt;             v = values % escape(arg, conn)&lt;br /&gt;
&amp;gt;             if isinstance(v, text_type):&lt;br /&gt;
&amp;gt;                 v = v.encode(encoding)&lt;br /&gt;
&amp;gt;             if len(sql) + len(v) + 1 &amp;gt; max_stmt_length:&lt;br /&gt;
!                 rows += self.execute(sql)&lt;br /&gt;
!                 sql = bytearray(prefix)&lt;br /&gt;
&amp;gt;             else:&lt;br /&gt;
&amp;gt;                 sql += b','&lt;br /&gt;
&amp;gt;             sql += v&lt;br /&gt;
&amp;gt;         rows += self.execute(sql)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The most prominent missing coverage in cursors.py seems to be&lt;br /&gt;
for its &amp;quot;scrollable cursor&amp;quot; support.&lt;br /&gt;
&lt;br /&gt;
Beyond the lack of completeness in testing, many of the tests themselves&lt;br /&gt;
are written in an expedient &amp;quot;one giant test&amp;quot; style where dozens of&lt;br /&gt;
assertions and individual behaviors are lumped into one big test&lt;br /&gt;
case (see test_SSCursor.py, test_basic.py-&amp;gt;test_datatypes for examples).&lt;br /&gt;
Test cases like these are difficult to work with when debugging&lt;br /&gt;
regressions as well as when tests for new features need to be added, and ideally&lt;br /&gt;
these should be broken out into clean single-feature tests with consistent&lt;br /&gt;
fixtures.&lt;br /&gt;
&lt;br /&gt;
There is also a suite called &amp;quot;test_issues&amp;quot;, which intends to&lt;br /&gt;
accumulate tests against specific issues that have been reported. This&lt;br /&gt;
isn't a bad idea, though it would be nice to see an effort made into&lt;br /&gt;
categorizing these tests into the actual features they are testing,&lt;br /&gt;
rather than a meaningless list of issue numbers, which could then form&lt;br /&gt;
as the basis for new suites centered around those areas of&lt;br /&gt;
functionality.  The issues reported should be used as the inspiration&lt;br /&gt;
for improvement of the test suite, rather than just another line-item&lt;br /&gt;
to be filed away.&lt;br /&gt;
&lt;br /&gt;
=== Library Documentation ===&lt;br /&gt;
&lt;br /&gt;
As the Python DBAPI is already a well-documented API, and the users of&lt;br /&gt;
PyMySQL are typically coming from MySQL-Python which is documented&lt;br /&gt;
to some degree and for which the use contract is widely known, it seems&lt;br /&gt;
unlikely that users of PyMySQL are eager for comprehensive documentation&lt;br /&gt;
specific to this library.&lt;br /&gt;
&lt;br /&gt;
That being said, PyMySQL does not appear to have any real library&lt;br /&gt;
documentation at all.  There's a README which&lt;br /&gt;
essentially refers to pep-249, an example.py in the root which shows a&lt;br /&gt;
very basic connection / round trip, and that's it. In&lt;br /&gt;
order to know anything about the API, specific parameters, optimization&lt;br /&gt;
strategies, behaviors, etc., one either has to go off of the MySQL-Python documentation and assume&lt;br /&gt;
PyMySQL also features the same parameter, or read the source code.&lt;br /&gt;
&lt;br /&gt;
PyMySQL probably hasn't had much urgent need for real documentation thus&lt;br /&gt;
far.  However, relying upon being a port of MySQL-Python is fast becoming&lt;br /&gt;
something that can no longer be relied upon, as MySQL-Python's development&lt;br /&gt;
is quite stalled, and PyMySQL should aspire to move into the future&lt;br /&gt;
of MySQL and Python with its own featureset and behaviors.  To that end, it would be a &lt;br /&gt;
great idea if it at least made the start&lt;br /&gt;
of a rudimentary Sphinx build including autodoc for module documentation&lt;br /&gt;
which could then be published up to RTD.   This would be very easy to get&lt;br /&gt;
started, and once present, new documentation sections can be added&lt;br /&gt;
iteratively.&lt;br /&gt;
&lt;br /&gt;
=== Architecture and Performance ===&lt;br /&gt;
&lt;br /&gt;
As stated before, PyMySQL has a really matter-of-fact and straightforward&lt;br /&gt;
implementation.   The code is very readable and it's easy to discern even&lt;br /&gt;
unusual features such as the INSERT..VALUES rewriting.  As far as the design,&lt;br /&gt;
there are lots of areas where performance suffers a bit at the Python level;&lt;br /&gt;
these areas could be greatly improved very easily with a little bit of&lt;br /&gt;
attention.&lt;br /&gt;
&lt;br /&gt;
As we are dealing with a driver that is communicating with a low level&lt;br /&gt;
protocol over a socket, it's critical that sending and receiving messages&lt;br /&gt;
is done as efficiently as possible.  The mechanisms used in PyMySQL rely&lt;br /&gt;
heavily on standard Python objects, without the use of __slots__; this means&lt;br /&gt;
that for individual messages on a socket, we are creating a heavyweight object,&lt;br /&gt;
calling its __init__() method and a creating a new __dict__ each time;&lt;br /&gt;
the most common object, OKPacketWrapper(), already wraps a MysqlPacket()&lt;br /&gt;
object, so we're doing two __init__()s in this common case.  OKPacketWrapper&lt;br /&gt;
also implements a `__getattr__` scheme&lt;br /&gt;
to proxy attribute access from itself to the internal MysqlPacket; this is a&lt;br /&gt;
an inefficient mode of operation in Python as it means a lookup must first&lt;br /&gt;
fail on the OKPacketWrapper before it invokes the `__getattr__()` method&lt;br /&gt;
and does another attribute lookup.   A reorg of the packet classes to produce&lt;br /&gt;
far fewer objects for messages as well as to use __slots__ would be&lt;br /&gt;
recommended.&lt;br /&gt;
&lt;br /&gt;
There are also lots of areas where function call overhead is unnecessarily&lt;br /&gt;
high due to the organization of calls.  For example, when calling upon&lt;br /&gt;
cursor.execute(), for parameters passed as a dictionary under Python 2K,&lt;br /&gt;
the isinstance() builtin function will be called on the incoming arguments&lt;br /&gt;
four times at a minimum, with two of each call against the exact same&lt;br /&gt;
criteria.  Additional logic will then call isinstance() on every key of&lt;br /&gt;
the dictionary as well as at least twice on every value within the dictionary,&lt;br /&gt;
in order to determine if the value needs to be converted to bytes as well&lt;br /&gt;
as for some top-level escaping logic checks; the escaping logic then&lt;br /&gt;
goes further with more checks for type() to match the object to a final&lt;br /&gt;
converter function.   While the flow of this logic does appear to be&lt;br /&gt;
reasonable and straightforward, it isn't ideal for a low-level database&lt;br /&gt;
driver library where speed is a high priority; the various wrappers and&lt;br /&gt;
processors can easily be reorganized here to reduce the number of isinstance()&lt;br /&gt;
and type() calls significantly, which would save a lot on function call&lt;br /&gt;
overhead.  It is likely there are many other areas in the codebase where&lt;br /&gt;
similar reductions in call counts can be made without too much disruption.&lt;br /&gt;
&lt;br /&gt;
In Appendix A, a short performance suite is illustrated.  This&lt;br /&gt;
is run against PyMySQL, Mysql-Connector-Python, and MySQL-Python.&lt;br /&gt;
PyMySQL does in fact demonstrate the&lt;br /&gt;
highest call-count behavior, logging 26,851,003 function calls&lt;br /&gt;
compared to MySQL-connector's 11,674,074 for the same operations.  To PyMySQL's&lt;br /&gt;
credit, it somehow completes the test 25-50% faster than MySQL-connector&lt;br /&gt;
despite having more than twice as many function calls; this probably indicates&lt;br /&gt;
the use of something very slow within MySQL-connector, such as catching&lt;br /&gt;
an exception within a tight loop.  However, the real bar we're comparing to&lt;br /&gt;
is MySQL-Python, which is written&lt;br /&gt;
in C and does the whole test with only 47,503 Python function calls; while&lt;br /&gt;
PyMySQL and MySQL-connector-Python compete for time within the 10-15 second&lt;br /&gt;
range without profiling, MySQL-Python completes the whole test in **1.03 seconds**, a 1000%&lt;br /&gt;
improvement over PyMySQL.   This speed difference is not PyMySQL's fault,&lt;br /&gt;
as pure Python is known to be extremely slow compared to C code.&lt;br /&gt;
However, there's a lot of easy wins in PyMySQL's codebase where the function&lt;br /&gt;
call count could probably be reduced dramatically to be even better than that&lt;br /&gt;
of MySQL-connector-Python; ideally, the driver would be able to run this&lt;br /&gt;
test possibly 5-6x slower than MySQL-Python rather than 10x.&lt;br /&gt;
&lt;br /&gt;
=== History Documentation ===&lt;br /&gt;
&lt;br /&gt;
PyMySQL includes a CHANGELOG file in its root which describes the bug fixes&lt;br /&gt;
and features of each release.  The construction of this file is definitely&lt;br /&gt;
within the &amp;quot;expedient&amp;quot; style noted for PyMySQL overall, and for it to be&lt;br /&gt;
genuinely useful for enterprise-level software, it would need to be&lt;br /&gt;
much more complete in its detail.    Each bug fix mentioned is only referred to&lt;br /&gt;
tersely without any link to a pull request or bug report.  While some can&lt;br /&gt;
be linked to a specific change through detective work&lt;br /&gt;
(&amp;quot;Cursor.fetchall() and .fetchmany now return list, not tuple&amp;quot;), it would&lt;br /&gt;
be very difficult for many&lt;br /&gt;
(&amp;quot;Fixed BIT type handling&amp;quot;, &amp;quot;Fixed GC errors&amp;quot; - what was the issue?)&lt;br /&gt;
and in many cases impossible (&amp;quot;Improved Py3k support&amp;quot;).  The file also&lt;br /&gt;
includes no dates so one has to look at the git tags to figure this&lt;br /&gt;
part out (fortunately, releases to seem to be git tagged at least).&lt;br /&gt;
For a user trying to ascertain if particular issues are&lt;br /&gt;
fixed or if behaviors have changed, this file is at best highly frustrating.&lt;br /&gt;
&lt;br /&gt;
A listing of release dates illustrates an average of two releases per year,&lt;br /&gt;
excluding 2012 when PyMySQL was seeking a new maintainer:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
2014-12-02 12:05:10 -0200  (tag: pymysql-0.6.3)&lt;br /&gt;
2014-04-21 14:28:47 -0300  (tag: pymysql-0.6.2)&lt;br /&gt;
2013-10-11 13:36:56 -0300  (tag: pymysql-0.6.1)&lt;br /&gt;
2013-10-04 14:25:14 -0300  (tag: pymysql-0.6)&lt;br /&gt;
2011-11-08 10:27:41 -0800  (tag: pymysql-0.5)&lt;br /&gt;
2010-12-27 17:28:16 +0000  (tag: pymysql-0.4)&lt;br /&gt;
2010-09-03 00:55:29 +0000  (tag: pymysql-0.3)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Community Involvement ===&lt;br /&gt;
&lt;br /&gt;
This is an area where PyMySQL seems to shine, and might even be an advantage&lt;br /&gt;
as compared to MySQL-Connector-Python, which remains under the cloak&lt;br /&gt;
of MySQL's vastly overstuffed bugtracker under the ownership of&lt;br /&gt;
the famously-opaque Oracle.   For a small, low key library without&lt;br /&gt;
any real documentation and only 126 tests, it features 33 contributors on&lt;br /&gt;
Github and is up to pull request #285, which is quite high.  Because&lt;br /&gt;
PyMySQL is small and straightforward, it does lend itself to contribution&lt;br /&gt;
and seems to do very well in this area.   The developers so far have been&lt;br /&gt;
responsive to issues and pull requests made by the author.&lt;br /&gt;
&lt;br /&gt;
Based on github contributor graphs it appears that PyMySQL had a lull&lt;br /&gt;
in 2012 but was picked up again afterwards in 2013.  As of 2012, its most&lt;br /&gt;
prominent developer Pete Hunt stopped development, and on July 25, 2013&lt;br /&gt;
added a note to the README that the project was looking for a new maintainer.&lt;br /&gt;
As of 2013 it was picked up by the current most prominent developers&lt;br /&gt;
Marcel Rodrigues (github username lecram) and INADA Naoki&lt;br /&gt;
(github username methane).&lt;br /&gt;
&lt;br /&gt;
Most issues that have been noted thus far within the areas of testing,&lt;br /&gt;
documentation and architecture are all very fixable; if the Openstack community&lt;br /&gt;
can provide resources to address some of these areas, and if the PyMySQL&lt;br /&gt;
developers are reasonably open to accepting new changes and producing new&lt;br /&gt;
releases in a timely fashion, the PyMySQL driver&lt;br /&gt;
could rapidly become a fully formidable contender in the MySQL driver field.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Appendix A - Performance Tests ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
import cProfile&lt;br /&gt;
import StringIO&lt;br /&gt;
import pstats&lt;br /&gt;
import contextlib&lt;br /&gt;
import time&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextlib.contextmanager&lt;br /&gt;
def profiled(dbapi):&lt;br /&gt;
    pr = cProfile.Profile()&lt;br /&gt;
    pr.enable()&lt;br /&gt;
    yield&lt;br /&gt;
    pr.disable()&lt;br /&gt;
    s = StringIO.StringIO()&lt;br /&gt;
    ps = pstats.Stats(pr, stream=s).sort_stats('cumulative')&lt;br /&gt;
    ps.print_stats()&lt;br /&gt;
    print &amp;quot;DBAPI:  %s&amp;quot; % dbapi&lt;br /&gt;
    print s.getvalue()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextlib.contextmanager&lt;br /&gt;
def timeonly(dbapi):&lt;br /&gt;
    now = time.time()&lt;br /&gt;
    try:&lt;br /&gt;
        yield&lt;br /&gt;
    finally:&lt;br /&gt;
        total = time.time() - now&lt;br /&gt;
        print &amp;quot;DBAPI:  %s, total seconds %f&amp;quot; % (dbapi, total)&lt;br /&gt;
&lt;br /&gt;
import MySQLdb&lt;br /&gt;
import pymysql&lt;br /&gt;
from mysql import connector as mysqlconnector&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def go(dbapi, ctx):&lt;br /&gt;
    conn = dbapi.connect(&lt;br /&gt;
        user='scott', passwd='tiger', host='localhost', db='test')&lt;br /&gt;
    cursor = conn.cursor()&lt;br /&gt;
    cursor.execute(&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
CREATE TABLE IF NOT EXISTS test_things (&lt;br /&gt;
    x INTEGER,&lt;br /&gt;
    y VARCHAR(255),&lt;br /&gt;
    z FLOAT&lt;br /&gt;
) engine=InnoDB&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;)&lt;br /&gt;
    cursor.execute(&amp;quot;DELETE from test_things&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    with ctx(dbapi):&lt;br /&gt;
        for row in xrange(1000):&lt;br /&gt;
            cursor.execute(&lt;br /&gt;
                &amp;quot;INSERT INTO test_things (x, y, z) &amp;quot;&lt;br /&gt;
                &amp;quot;VALUES (%(x)s, %(y)s, %(z)s)&amp;quot;,&lt;br /&gt;
                {&amp;quot;x&amp;quot;: row, &amp;quot;y&amp;quot;: &amp;quot;row number %d&amp;quot; % row,&lt;br /&gt;
                 &amp;quot;z&amp;quot;: row * 4.57292, }&lt;br /&gt;
            )&lt;br /&gt;
&lt;br /&gt;
        for x in xrange(500):&lt;br /&gt;
            cursor.execute(&lt;br /&gt;
                &amp;quot;select * from test_things&amp;quot;)&lt;br /&gt;
            for row in cursor.fetchall():&lt;br /&gt;
                row[0], row[1], row[2]&lt;br /&gt;
&lt;br /&gt;
    cursor.close()&lt;br /&gt;
    conn.close()&lt;br /&gt;
&lt;br /&gt;
go(pymysql, profiled)&lt;br /&gt;
go(mysqlconnector, profiled)&lt;br /&gt;
go(MySQLdb, profiled)&lt;br /&gt;
&lt;br /&gt;
go(pymysql, timeonly)&lt;br /&gt;
go(mysqlconnector, timeonly)&lt;br /&gt;
go(MySQLdb, timeonly)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DBAPI:  &amp;lt;module 'pymysql' from '/Users/classic/dev/PyMySQL/pymysql/__init__.pyc'&amp;gt;&lt;br /&gt;
         26851003 function calls in 15.924 seconds&lt;br /&gt;
&lt;br /&gt;
   Ordered by: cumulative time&lt;br /&gt;
&lt;br /&gt;
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;
     1500    0.010    0.000   15.923    0.011 /Users/classic/dev/PyMySQL/pymysql/cursors.py:105(execute)&lt;br /&gt;
     1500    0.003    0.000   15.883    0.011 /Users/classic/dev/PyMySQL/pymysql/cursors.py:269(_query)&lt;br /&gt;
     1500    0.004    0.000   15.859    0.011 /Users/classic/dev/PyMySQL/pymysql/connections.py:746(query)&lt;br /&gt;
     1500    0.003    0.000   15.819    0.011 /Users/classic/dev/PyMySQL/pymysql/connections.py:892(_read_query_result)&lt;br /&gt;
     1500    0.004    0.000   15.812    0.011 /Users/classic/dev/PyMySQL/pymysql/connections.py:1097(read)&lt;br /&gt;
      500    0.003    0.000   15.559    0.031 /Users/classic/dev/PyMySQL/pymysql/connections.py:1154(_read_result_packet)&lt;br /&gt;
      500    0.900    0.002   15.429    0.031 /Users/classic/dev/PyMySQL/pymysql/connections.py:1187(_read_rowdata_packet)&lt;br /&gt;
   500000    3.417    0.000    9.173    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1200(_read_row_from_packet)&lt;br /&gt;
  2012000    1.157    0.000    5.307    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:311(read_length_coded_string)&lt;br /&gt;
   504500    1.495    0.000    5.209    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:845(_read_packet)&lt;br /&gt;
  2014500    1.248    0.000    3.035    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:293(read_length_encoded_integer)&lt;br /&gt;
  3539500    2.439    0.000    2.750    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:245(read)&lt;br /&gt;
  1009000    0.779    0.000    2.307    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:870(_read_bytes)&lt;br /&gt;
  1009000    1.197    0.000    1.423    0.000 {method 'read' of '_io.BufferedReader' objects}&lt;br /&gt;
   508500    0.379    0.000    0.695    0.000 /Users/classic/dev/PyMySQL/pymysql/util.py:3(byte2int)&lt;br /&gt;
  5058000    0.465    0.000    0.465    0.000 {len}&lt;br /&gt;
   500500    0.222    0.000    0.455    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1146(_check_packet_is_eof)&lt;br /&gt;
   504500    0.205    0.000    0.375    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:342(check_error)&lt;br /&gt;
  2504000    0.275    0.000    0.275    0.000 {method 'append' of 'list' objects}&lt;br /&gt;
   501500    0.182    0.000    0.230    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:326(is_eof_packet)&lt;br /&gt;
     3500    0.009    0.000    0.226    0.000 /Users/classic/dev/PyMySQL/pymysql/_socketio.py:45(readinto)&lt;br /&gt;
     3500    0.207    0.000    0.207    0.000 {method 'recv_into' of '_socket.socket' objects}&lt;br /&gt;
  1022000    0.203    0.000    0.203    0.000 {_struct.unpack}&lt;br /&gt;
   526000    0.200    0.000    0.200    0.000 {isinstance}&lt;br /&gt;
  1501000    0.195    0.000    0.195    0.000 {method 'get' of 'dict' objects}&lt;br /&gt;
   504500    0.176    0.000    0.176    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:238(__init__)&lt;br /&gt;
   504500    0.170    0.000    0.170    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:339(is_error_packet)&lt;br /&gt;
  2014500    0.167    0.000    0.167    0.000 {ord}&lt;br /&gt;
      500    0.006    0.000    0.125    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1224(_get_descriptions)&lt;br /&gt;
     2000    0.003    0.000    0.087    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:361(__init__)&lt;br /&gt;
     2000    0.019    0.000    0.084    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:365(__parse_field_descriptor)&lt;br /&gt;
   500000    0.056    0.000    0.056    0.000 /Users/classic/dev/PyMySQL/pymysql/converters.py:301(through)&lt;br /&gt;
     1500    0.005    0.000    0.037    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:915(_execute_command)&lt;br /&gt;
     1500    0.003    0.000    0.027    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:886(_write_bytes)&lt;br /&gt;
     1500    0.002    0.000    0.024    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/socket.py:223(meth)&lt;br /&gt;
     1500    0.021    0.000    0.021    0.000 {method 'sendall' of '_socket.socket' objects}&lt;br /&gt;
     1500    0.020    0.000    0.020    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:276(_do_get_result)&lt;br /&gt;
     1000    0.003    0.000    0.019    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:95(_escape_args)&lt;br /&gt;
     4000    0.002    0.000    0.015    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:99(&amp;lt;genexpr&amp;gt;)&lt;br /&gt;
     1000    0.002    0.000    0.014    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1127(_read_ok_packet)&lt;br /&gt;
     3000    0.003    0.000    0.013    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:712(escape)&lt;br /&gt;
     1000    0.005    0.000    0.012    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:416(__init__)&lt;br /&gt;
     8000    0.011    0.000    0.011    0.000 {method 'decode' of 'str' objects}&lt;br /&gt;
     3500    0.003    0.000    0.007    0.000 {method '_checkReadable' of '_io._IOBase' objects}&lt;br /&gt;
     2000    0.003    0.000    0.006    0.000 /Users/classic/dev/PyMySQL/pymysql/converters.py:19(escape_item)&lt;br /&gt;
     4000    0.002    0.000    0.006    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:126(&amp;lt;genexpr&amp;gt;)&lt;br /&gt;
     7500    0.003    0.000    0.005    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:115(ensure_bytes)&lt;br /&gt;
     2000    0.003    0.000    0.005    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:386(description)&lt;br /&gt;
     5500    0.004    0.000    0.004    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:268(advance)&lt;br /&gt;
     1000    0.001    0.000    0.004    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:722(escape_string)&lt;br /&gt;
     3500    0.004    0.000    0.004    0.000 /Users/classic/dev/PyMySQL/pymysql/_socketio.py:87(readable)&lt;br /&gt;
      500    0.002    0.000    0.004    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:442(__init__)&lt;br /&gt;
     3500    0.003    0.000    0.003    0.000 {method '_checkClosed' of '_io._IOBase' objects}&lt;br /&gt;
     1000    0.001    0.000    0.003    0.000 /Users/classic/dev/PyMySQL/pymysql/converters.py:59(escape_string)&lt;br /&gt;
     1500    0.003    0.000    0.003    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1080(__init__)&lt;br /&gt;
     1500    0.001    0.000    0.003    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:92(nextset)&lt;br /&gt;
     4000    0.002    0.000    0.002    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:397(get_column_length)&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 /Users/classic/dev/PyMySQL/pymysql/converters.py:56(escape_float)&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 {method 'sub' of '_sre.SRE_Pattern' objects}&lt;br /&gt;
     3000    0.002    0.000    0.002    0.000 {_struct.pack}&lt;br /&gt;
     6000    0.002    0.000    0.002    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:62(_get_db)&lt;br /&gt;
     1500    0.001    0.000    0.002    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:80(_nextset)&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 /Users/classic/dev/PyMySQL/pymysql/util.py:9(int2byte)&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:244(fetchall)&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {min}&lt;br /&gt;
     2500    0.001    0.000    0.001    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:323(is_ok_packet)&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Users/classic/dev/PyMySQL/pymysql/converters.py:52(escape_int)&lt;br /&gt;
     2000    0.001    0.000    0.001    0.000 {method 'items' of 'dict' objects}&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {getattr}&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1093(__del__)&lt;br /&gt;
     1000    0.000    0.000    0.000    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:259(read_all)&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:67(_check_executed)&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:336(is_load_local_packet)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/contextlib.py:21(__exit__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 test.py:8(profiled)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DBAPI:  &amp;lt;module 'mysql.connector' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/__init__.pyc'&amp;gt;&lt;br /&gt;
         11674074 function calls in 19.049 seconds&lt;br /&gt;
&lt;br /&gt;
   Ordered by: cumulative time&lt;br /&gt;
&lt;br /&gt;
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;
      500    0.640    0.001   18.556    0.037 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:820(fetchall)&lt;br /&gt;
      500    0.003    0.000   11.196    0.022 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:655(get_rows)&lt;br /&gt;
      500    1.561    0.003   11.191    0.022 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:292(read_text_result)&lt;br /&gt;
   504500    3.303    0.000    7.306    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/network.py:219(recv_plain)&lt;br /&gt;
   500000    3.572    0.000    6.625    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:359(row_to_python)&lt;br /&gt;
  1009000    3.155    0.000    3.155    0.000 {method 'recv_into' of '_socket.socket' objects}&lt;br /&gt;
   500000    2.049    0.000    2.329    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:220(read_lc_string_list)&lt;br /&gt;
   500000    0.653    0.000    2.116    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:531(_STRING_to_python)&lt;br /&gt;
   502000    0.378    0.000    1.329    0.000 {method 'decode' of 'bytearray' objects}&lt;br /&gt;
   502000    0.221    0.000    0.951    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/utf_8.py:15(decode)&lt;br /&gt;
   502000    0.730    0.000    0.730    0.000 {_codecs.utf_8_decode}&lt;br /&gt;
   500000    0.514    0.000    0.514    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:413(_INT_to_python)&lt;br /&gt;
   509500    0.510    0.000    0.510    0.000 {_struct.unpack_from}&lt;br /&gt;
     1500    0.009    0.000    0.492    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:452(execute)&lt;br /&gt;
     1500    0.004    0.000    0.437    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:705(cmd_query)&lt;br /&gt;
   500000    0.367    0.000    0.367    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:405(_FLOAT_to_python)&lt;br /&gt;
   504500    0.343    0.000    0.343    0.000 {method 'extend' of 'bytearray' objects}&lt;br /&gt;
     1500    0.007    0.000    0.315    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:481(_send_cmd)&lt;br /&gt;
  2501500    0.275    0.000    0.275    0.000 {method 'append' of 'list' objects}&lt;br /&gt;
   500500    0.246    0.000    0.246    0.000 {method 'startswith' of 'bytearray' objects}&lt;br /&gt;
   524002    0.150    0.000    0.150    0.000 {isinstance}&lt;br /&gt;
     1500    0.011    0.000    0.118    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:613(_handle_result)&lt;br /&gt;
  1004500    0.112    0.000    0.112    0.000 {len}&lt;br /&gt;
   500000    0.092    0.000    0.092    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:204(description)&lt;br /&gt;
     1500    0.007    0.000    0.042    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/network.py:118(send_plain)&lt;br /&gt;
     2000    0.013    0.000    0.041    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:226(parse_column)&lt;br /&gt;
     1000    0.008    0.000    0.034    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:346(_process_params_dict)&lt;br /&gt;
     1500    0.002    0.000    0.029    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/socket.py:223(meth)&lt;br /&gt;
     1500    0.026    0.000    0.026    0.000 {method 'sendall' of '_socket.socket' objects}&lt;br /&gt;
     5000    0.007    0.000    0.014    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/catch23.py:79(struct_unpack)&lt;br /&gt;
     1000    0.001    0.000    0.013    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:562(_handle_ok)&lt;br /&gt;
    12000    0.011    0.000    0.011    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:167(read_lc_string)&lt;br /&gt;
     1000    0.004    0.000    0.010    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:199(parse_ok)&lt;br /&gt;
     3000    0.005    0.000    0.009    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:152(to_mysql)&lt;br /&gt;
     1500    0.003    0.000    0.007    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:118(make_command)&lt;br /&gt;
     3000    0.003    0.000    0.007    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:130(quote)&lt;br /&gt;
     1500    0.005    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/network.py:53(_prepare_packets)&lt;br /&gt;
     1500    0.003    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:406(_handle_result)&lt;br /&gt;
     3000    0.003    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:102(escape)&lt;br /&gt;
     1000    0.002    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:252(parse_eof)&lt;br /&gt;
      500    0.001    0.000    0.005    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:579(_handle_eof)&lt;br /&gt;
     2000    0.003    0.000    0.005    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:545(_handle_server_status)&lt;br /&gt;
     1500    0.003    0.000    0.004    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:53(int1store)&lt;br /&gt;
     3000    0.003    0.000    0.003    0.000 {method 'encode' of 'str' objects}&lt;br /&gt;
     9001    0.003    0.000    0.003    0.000 {method 'replace' of 'str' objects}&lt;br /&gt;
     1500    0.003    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:295(_reset_result)&lt;br /&gt;
     4500    0.003    0.000    0.003    0.000 {_struct.pack}&lt;br /&gt;
     2500    0.003    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:296(read_lc_int)&lt;br /&gt;
      500    0.002    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:757(_handle_eof)&lt;br /&gt;
     6027    0.003    0.000    0.003    0.000 {method 'format' of 'str' objects}&lt;br /&gt;
     3000    0.002    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:1221(_set_unread_result)&lt;br /&gt;
      500    0.001    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:219(parse_column_count)&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 {repr}&lt;br /&gt;
     4527    0.002    0.000    0.002    0.000 {getattr}&lt;br /&gt;
     4000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/constants.py:34(flag_is_set)&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:383(_handle_noresultset)&lt;br /&gt;
     4000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:1233(_get_unread_result)&lt;br /&gt;
     2001    0.001    0.000    0.001    0.000 {method 'items' of 'dict' objects}&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:307(_have_unread_result)&lt;br /&gt;
     3000    0.001    0.000    0.001    0.000 {method 'lower' of 'str' objects}&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 {range}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:173(_str_to_mysql)&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:169(_float_to_mysql)&lt;br /&gt;
     1000    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:161(_int_to_mysql)&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:1313(_get_getwarnings)&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:200(reset)&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:397(_handle_resultset)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/__init__.py:71(search_function)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/ascii.py:41(getregentry)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/contextlib.py:21(__exit__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/__init__.py:49(normalize_encoding)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 test.py:8(profiled)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/codecs.py:77(__new__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {__import__}&lt;br /&gt;
        3    0.000    0.000    0.000    0.000 {method 'get' of 'dict' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'split' of 'str' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {built-in method __new__ of type object at 0x1001534e8}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'translate' of 'str' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'join' of 'str' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {hasattr}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DBAPI:  &amp;lt;module 'MySQLdb' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/__init__.pyc'&amp;gt;&lt;br /&gt;
         47503 function calls in 0.918 seconds&lt;br /&gt;
&lt;br /&gt;
   Ordered by: cumulative time&lt;br /&gt;
&lt;br /&gt;
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;
     1500    0.009    0.000    0.917    0.001 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:164(execute)&lt;br /&gt;
     1500    0.002    0.000    0.890    0.001 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:353(_query)&lt;br /&gt;
     1500    0.004    0.000    0.498    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:315(_do_query)&lt;br /&gt;
     1500    0.016    0.000    0.391    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:358(_post_get_result)&lt;br /&gt;
     1500    0.001    0.000    0.375    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:324(_fetch_row)&lt;br /&gt;
      500    0.374    0.001    0.374    0.001 {built-in method fetch_row}&lt;br /&gt;
     1500    0.007    0.000    0.362    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:142(_do_get_result)&lt;br /&gt;
     1500    0.002    0.000    0.351    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:351(_get_result)&lt;br /&gt;
     1500    0.349    0.000    0.349    0.000 {method 'store_result' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.131    0.000    0.131    0.000 {method 'query' of '_mysql.connection' objects}&lt;br /&gt;
     4000    0.002    0.000    0.010    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:184(&amp;lt;genexpr&amp;gt;)&lt;br /&gt;
     3000    0.002    0.000    0.008    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/connections.py:267(literal)&lt;br /&gt;
     1500    0.007    0.000    0.007    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:107(_warning_check)&lt;br /&gt;
     3000    0.002    0.000    0.006    0.000 {method 'escape' of '_mysql.connection' objects}&lt;br /&gt;
     6000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:159(_get_db)&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/converters.py:81(Float2Str)&lt;br /&gt;
     2500    0.001    0.000    0.001    0.000 {isinstance}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/connections.py:202(string_literal)&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:380(fetchall)&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 {built-in method describe}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 {method 'string_literal' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {method 'insert_id' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {method 'affected_rows' of '_mysql.connection' objects}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/converters.py:69(Thing2Str)&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 {method 'warning_count' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 {method 'info' of '_mysql.connection' objects}&lt;br /&gt;
     1000    0.000    0.000    0.000    0.000 {method 'iteritems' of 'dict' objects}&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:103(_check_executed)&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 {len}&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 {built-in method field_flags}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/contextlib.py:21(__exit__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 test.py:8(profiled)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DBAPI:  &amp;lt;module 'pymysql' from '/Users/classic/dev/PyMySQL/pymysql/__init__.pyc'&amp;gt;, total seconds 10.046012&lt;br /&gt;
DBAPI:  &amp;lt;module 'mysql.connector' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/__init__.pyc'&amp;gt;, total seconds 15.403260&lt;br /&gt;
DBAPI:  &amp;lt;module 'MySQLdb' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/__init__.pyc'&amp;gt;, total seconds 1.028737&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mike Bayer</name></author>	</entry>

	<entry>
		<id>https://wiki.openstack.org/w/index.php?title=PyMySQL_evaluation&amp;diff=72560</id>
		<title>PyMySQL evaluation</title>
		<link rel="alternate" type="text/html" href="https://wiki.openstack.org/w/index.php?title=PyMySQL_evaluation&amp;diff=72560"/>
				<updated>2015-01-28T20:29:03Z</updated>
		
		<summary type="html">&lt;p&gt;Mike Bayer: /* MySQL DB Drivers Comparison */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= PyMySQL Evaluation =&lt;br /&gt;
&lt;br /&gt;
This page will capture issues related to Openstack moving to the [https://github.com/PyMySQL/PyMySQL/ PyMySQL driver] for MySQL/MariaDB dbapi access.&lt;br /&gt;
&lt;br /&gt;
== Rationale ==&lt;br /&gt;
&lt;br /&gt;
While the [https://github.com/farcepest/MySQLdb1 MySQL-Python] driver is a very mature and stable driver, it does not provide compatibility with either Python 3 or with [http://eventlet.net/doc/patching.html eventlet monkeypatching] (MySQL-Python  [https://github.com/eventlet/eventlet/blob/v0.16.1/eventlet/patcher.py#L225-L237 can be monkeypatched] with eventlet, but this feature disabled by default and missed in documentation). So OpenStack's usage of MySQL-Python, combined with the fact that concurrency is provided by eventlet, means that we currently have fully serialized database access within a single process, that is, only one database command occurs at a time within an Openstack Python process.&lt;br /&gt;
&lt;br /&gt;
== Drivers Under Consideration ==&lt;br /&gt;
&lt;br /&gt;
The two drivers that are known to provide eventlet-monkeypatch compatibility are MySQL-Connector and PyMySQL, as they are written in pure Python.   The two other well-known drivers for MySQL are MySQL-Python and OurSQL, both of which are written in C and offer no explicit support for eventlet or async.  They can reportedly be built to support gevent using a system called [https://github.com/CMGS/greenify Greenify], but the maturity and/or stabiltiy of this system is unknown.&lt;br /&gt;
&lt;br /&gt;
A comparison at http://www.diamondtin.com/2014/sqlalchemy-gevent-mysql-python-drivers-comparison/ illustrates performance metrics observed with all four of these drivers, with MySQL-python built both without and with the &amp;quot;greenify&amp;quot; system.&lt;br /&gt;
&lt;br /&gt;
A summary of the status of all four drivers is as follows.&lt;br /&gt;
&lt;br /&gt;
=== MySQL-Python ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/farcepest/MySQLdb1 MySQL-Python] is the most widely used Python driver.  However, it does not support Python 3, and it does not support async systems unless used within a thread pool.   While a system known as Greenify can potentially resolve the latter situation, the Python 3 limitation is still a deal-breaker - pull request with Python 3 support [https://github.com/farcepest/MySQLdb1/pull/62 was proposed] to MySQL-python in April 2014 and still not merged.&lt;br /&gt;
&lt;br /&gt;
=== mysqlclient ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/PyMySQL/mysqlclient-python mysqlclient] is a fork of MySQL-python. It add Python 3.3 support and merges some pull requests.&lt;br /&gt;
&lt;br /&gt;
=== OurSQL ===&lt;br /&gt;
&lt;br /&gt;
[https://pythonhosted.org/oursql/ OurSQL] is an alternative MySQL driver that is also written in C.  It features fast performance and uses a different execution model than MySQL-python, using prepared statements.    OurSQL does have a Python 3 port hosted on [https://launchpad.net/oursql/py3k/ launchpad] but is not integrated with OurSQL itself nor is it published on Pypi.     Because it is written in C, it does not offer compatibility with async systems including eventlet-style monkeypatching.&lt;br /&gt;
&lt;br /&gt;
Besides the shaky Python 3 support and lack of eventlet support, OurSQL is also not maintained, seeing its last master commit on [https://code.launchpad.net/~jerub/oursql/master 2012-06-05].&lt;br /&gt;
&lt;br /&gt;
=== MySQL-Connector-Python ===&lt;br /&gt;
&lt;br /&gt;
[https://pypi.python.org/pypi/mysql-connector-python MySQL-connector-Python] is a pure Python MySQL driver, and is now released under the auspices of the MySQL project itself, as owned by Oracle.  MySQL-connector-Python supports both Python 3 as well as eventlet monkeypatching, and is well maintained.  It is endorsed by Oracle as the official Oracle-supported driver for MySQL, so to that extent, it is in most ways Openstack's first choice in driver.  However, Oracle refuses to publish MySQL-connector-Python on Pypi, which is critical to the Openstack infrastructure.  Repeated attempts to communicate with Oracle in order to resolve this issue have not made any progress.    Therefore, for this one unfortunate reason, MySQL-connector-Python will not have a place in the Openstack ecosystem unless this issue is resolved.&lt;br /&gt;
&lt;br /&gt;
=== PyMySQL ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/PyMySQL/PyMySQL PyMySQL] is a pure Python MySQL&lt;br /&gt;
driver, first written as a rough port of the MySQL-Python driver.&lt;br /&gt;
PyMySQL meets all of Openstack's criterion for a driver: it is fully&lt;br /&gt;
open source, hosted on Github, released on Pypi, is actively&lt;br /&gt;
maintained, is written in pure Python so is eventlet-monkeypatch&lt;br /&gt;
compatible, and is fully Python 3 compatible.&lt;br /&gt;
&lt;br /&gt;
As this document is named &amp;quot;PyMySQL Evaluation&amp;quot;, it should be apparent that&lt;br /&gt;
this is the driver Openstack is currently leaning towards; because it is the only&lt;br /&gt;
one that meets all criteria fully, it is already most likely the &amp;quot;winner&amp;quot;.  However, it does&lt;br /&gt;
have some minor code quality issues which hopefully can be addressed in some&lt;br /&gt;
way; the section below titled &amp;quot;PyMySQL Code Review&amp;quot; will summarize the&lt;br /&gt;
current state of the code.&lt;br /&gt;
&lt;br /&gt;
===MySQL DB Drivers Comparison===&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Project !! PyPi hosted !! Eventlet friendly !! Python 3 compatibility !! Maturity and/or stability  !! Comment&lt;br /&gt;
|-&lt;br /&gt;
| MySQL-Python || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: orange;&amp;quot; | Partial || style=&amp;quot;background-color: red;&amp;quot; | No  || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || Can be monkeypatched by eventlet, but only to enable thread pooling&lt;br /&gt;
|-&lt;br /&gt;
| mysqlclient || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: orange;&amp;quot; | Partial || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes  || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || MySQL-python fork, so should be similar to it&lt;br /&gt;
|-&lt;br /&gt;
| OurSQL || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: red;&amp;quot; | No || style=&amp;quot;background-color: orange;&amp;quot; | Yes, but not Pypi hosted  || style=&amp;quot;background-color: red;&amp;quot; | No || Development halted fairly early on, and has not seen commits/releases in two years&lt;br /&gt;
|-&lt;br /&gt;
| MySQL-Connector-Python || style=&amp;quot;background-color: red;&amp;quot; | No || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes  || style=&amp;quot;background-color: orange;&amp;quot; | Yes, though the driver is still fairly new || The official Oracle-supported driver for MySQL&lt;br /&gt;
|-&lt;br /&gt;
| PyMySQL || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes  || style=&amp;quot;background-color: orange;&amp;quot; | Yes, however see notes below. || Actively maintained and popular.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== PyMySQL Code Review ==&lt;br /&gt;
&lt;br /&gt;
PyMySQL started roughly as a pure Python port of MySQL-Python.   The sections below will&lt;br /&gt;
detail various aspects of the driver and the status of the code.   Overall, the general&lt;br /&gt;
tone of PyMySQL is one of code that was written in the spirit of pragmatism and immediate&lt;br /&gt;
need; it is straightforward, relatively free of serious antipatterns and performs&lt;br /&gt;
its task in a matter-of-fact way.  However,&lt;br /&gt;
it lacks polish and completeness in many areas that would normally be in better&lt;br /&gt;
condition for a more mature project.  These areas are all highly fixable, and it is&lt;br /&gt;
hoped that these items can be shared with the current PyMySQL developers, through&lt;br /&gt;
a combination of this document itself as well as new issues and pull requests&lt;br /&gt;
on the PyMySQL tracker, so that this fairly promising library can be pushed&lt;br /&gt;
to the next level.   This would require that the developers are amenable to&lt;br /&gt;
these improvements.&lt;br /&gt;
&lt;br /&gt;
=== Coding Style / Pep8 ===&lt;br /&gt;
&lt;br /&gt;
PyMySQL's coding style is pretty good.  The majority of it passes all&lt;br /&gt;
flake8 tests, save for a few dozen whitespace issues and&lt;br /&gt;
some long lines here and there within the core&lt;br /&gt;
modules, and moreso within the tests which appear to be a bit more&lt;br /&gt;
crufty than the core modules.  A full run of flake8 produces only 128&lt;br /&gt;
errors, and with a default run of autopep8 against E1,E2,E3, we can&lt;br /&gt;
fix all of them in one pass except for 38 remaining long line warnings.&lt;br /&gt;
The code layout is mostly idiomatic and reasonable.&lt;br /&gt;
&lt;br /&gt;
The codebase is probably short on docstrings.  Those methods that are&lt;br /&gt;
part of the public DBAPI do have docstrings, but tend to be extremely&lt;br /&gt;
terse (example, docstring for cursor.execute(): &amp;quot;'Execute a query'&amp;quot;).&lt;br /&gt;
Other methods that aren't public tend to not have any docstrings, but&lt;br /&gt;
the codebase is not at all hard to read and the purpose of methods and&lt;br /&gt;
functions is pretty easy to discern just by their names and implementations,&lt;br /&gt;
however those who seek inline documentation as a means to understand a codebase&lt;br /&gt;
will be disappointed.   The terseness of public API docstrings is more of&lt;br /&gt;
an issue in terms of the overall lack of documentation for PyMySQL,&lt;br /&gt;
see the section &amp;quot;Documentation&amp;quot; below.&lt;br /&gt;
&lt;br /&gt;
=== Test Coverage ===&lt;br /&gt;
&lt;br /&gt;
The test coverage for PyMySQL is definitely lacking.   From a visual&lt;br /&gt;
inspection alone, it's apparent that of the dozen or so test modules,&lt;br /&gt;
most of them have less then ten tests each and the modules are very short.&lt;br /&gt;
For a full test run, there's only 126 tests; contrast this to&lt;br /&gt;
MySQL-Connector-Python which has 531 tests.&lt;br /&gt;
&lt;br /&gt;
Additionally, all of the tests are live round-trip tests against a&lt;br /&gt;
real database.  There are no non-live unit tests of any kind,&lt;br /&gt;
leaving codepaths that are not easily exercised&lt;br /&gt;
on a generic MySQL database out in the cold.  This means that features designed&lt;br /&gt;
for particular versions of MySQL, particular datatypes, particular error&lt;br /&gt;
conditions, and particular kinds of message packets that aren't created for&lt;br /&gt;
real are simply not tested.&lt;br /&gt;
&lt;br /&gt;
The lack of tests is apparent when run with coverage.  The test suite itself&lt;br /&gt;
does not appear to have any signs that it is normally run with coverage&lt;br /&gt;
turned on, with no directives in tox.ini or similar.   With coverage,&lt;br /&gt;
the overall coverage is 85%, but the modules most lacking in coverage&lt;br /&gt;
are also the most critical to PyMySQL's core functionality,&lt;br /&gt;
connections.py, converters.py and cursors.py:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Name                                                              Stmts   Miss  Cover&lt;br /&gt;
-------------------------------------------------------------------------------------&lt;br /&gt;
pymysql/__init__                                                     51      3    94%&lt;br /&gt;
pymysql/_compat                                                      14      4    71%&lt;br /&gt;
pymysql/_socketio                                                    72     30    58%&lt;br /&gt;
pymysql/charset                                                     228      3    99%&lt;br /&gt;
pymysql/connections                                                 827    188    77%&lt;br /&gt;
pymysql/constants/CLIENT                                             18      0   100%&lt;br /&gt;
pymysql/constants/COMMAND                                            32      0   100%&lt;br /&gt;
pymysql/constants/ER                                                471      0   100%&lt;br /&gt;
pymysql/constants/FIELD_TYPE                                         29      0   100%&lt;br /&gt;
pymysql/constants/FLAG                                               15      0   100%&lt;br /&gt;
pymysql/constants/SERVER_STATUS                                      10      0   100%&lt;br /&gt;
pymysql/constants/__init__                                            0      0   100%&lt;br /&gt;
pymysql/converters                                                  148     51    66%&lt;br /&gt;
pymysql/cursors                                                     273     37    86%&lt;br /&gt;
pymysql/err                                                          40      1    98%&lt;br /&gt;
pymysql/tests/__init__                                               14      5    64%&lt;br /&gt;
pymysql/tests/base                                                   25      2    92%&lt;br /&gt;
pymysql/tests/test_DictCursor                                        73      2    97%&lt;br /&gt;
pymysql/tests/test_SSCursor                                          56      6    89%&lt;br /&gt;
pymysql/tests/test_basic                                            186      4    98%&lt;br /&gt;
pymysql/tests/test_connection                                        57      8    86%&lt;br /&gt;
pymysql/tests/test_example                                           17      2    88%&lt;br /&gt;
pymysql/tests/test_issues                                           300     48    84%&lt;br /&gt;
pymysql/tests/test_load_local                                        39      2    95%&lt;br /&gt;
pymysql/tests/test_nextset                                           43      4    91%&lt;br /&gt;
pymysql/tests/thirdparty/__init__                                     7      5    29%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/__init__                        6      2    67%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/capabilities                  196     23    88%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/dbapi20                       423    121    71%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_capabilities      73      6    92%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_dbapi20          100      2    98%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_nonstandard       57      3    95%&lt;br /&gt;
pymysql/times                                                        12      0   100%&lt;br /&gt;
pymysql/util                                                         14      7    50%&lt;br /&gt;
-------------------------------------------------------------------------------------&lt;br /&gt;
TOTAL                                                              3926    569    86%&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The lack of coverage in converters.py refers to functions related to handling&lt;br /&gt;
incoming bound values as well as outgoing result values.    Key data escaping&lt;br /&gt;
features such as escaping of dictionaries, sets, some unicode objects and&lt;br /&gt;
byte objects as well as many pathways into date and time-processing functions are not&lt;br /&gt;
covered.&lt;br /&gt;
&lt;br /&gt;
In connections.py, uncovered features include various SSL and&lt;br /&gt;
very legacy (e.g. version 3.23) MySQL features, but also some protocol parsing&lt;br /&gt;
features.&lt;br /&gt;
&lt;br /&gt;
In cursors.py, there is coverage for an elaborate and mostly undocumented&lt;br /&gt;
performance-related feature that rewrites an INSERT statement to have an&lt;br /&gt;
extended VALUES clause (_do_execute_many), however it fails to cover the very&lt;br /&gt;
likely case where the function will need to break the input set into&lt;br /&gt;
multiple chunks:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;gt;         for arg in args:&lt;br /&gt;
&amp;gt;             v = values % escape(arg, conn)&lt;br /&gt;
&amp;gt;             if isinstance(v, text_type):&lt;br /&gt;
&amp;gt;                 v = v.encode(encoding)&lt;br /&gt;
&amp;gt;             if len(sql) + len(v) + 1 &amp;gt; max_stmt_length:&lt;br /&gt;
!                 rows += self.execute(sql)&lt;br /&gt;
!                 sql = bytearray(prefix)&lt;br /&gt;
&amp;gt;             else:&lt;br /&gt;
&amp;gt;                 sql += b','&lt;br /&gt;
&amp;gt;             sql += v&lt;br /&gt;
&amp;gt;         rows += self.execute(sql)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The most prominent missing coverage in cursors.py seems to be&lt;br /&gt;
for its &amp;quot;scrollable cursor&amp;quot; support.&lt;br /&gt;
&lt;br /&gt;
Beyond the lack of completeness in testing, many of the tests themselves&lt;br /&gt;
are written in an expedient &amp;quot;one giant test&amp;quot; style where dozens of&lt;br /&gt;
assertions and individual behaviors are lumped into one big test&lt;br /&gt;
case (see test_SSCursor.py, test_basic.py-&amp;gt;test_datatypes for examples).&lt;br /&gt;
Test cases like these are difficult to work with when debugging&lt;br /&gt;
regressions as well as when tests for new features need to be added, and ideally&lt;br /&gt;
these should be broken out into clean single-feature tests with consistent&lt;br /&gt;
fixtures.&lt;br /&gt;
&lt;br /&gt;
There is also a suite called &amp;quot;test_issues&amp;quot;, which intends to&lt;br /&gt;
accumulate tests against specific issues that have been reported. This&lt;br /&gt;
isn't a bad idea, though it would be nice to see an effort made into&lt;br /&gt;
categorizing these tests into the actual features they are testing,&lt;br /&gt;
rather than a meaningless list of issue numbers, which could then form&lt;br /&gt;
as the basis for new suites centered around those areas of&lt;br /&gt;
functionality.  The issues reported should be used as the inspiration&lt;br /&gt;
for improvement of the test suite, rather than just another line-item&lt;br /&gt;
to be filed away.&lt;br /&gt;
&lt;br /&gt;
=== Library Documentation ===&lt;br /&gt;
&lt;br /&gt;
As the Python DBAPI is already a well-documented API, and the users of&lt;br /&gt;
PyMySQL are typically coming from MySQL-Python which is documented&lt;br /&gt;
to some degree and for which the use contract is widely known, it seems&lt;br /&gt;
unlikely that users of PyMySQL are eager for comprehensive documentation&lt;br /&gt;
specific to this library.&lt;br /&gt;
&lt;br /&gt;
That being said, PyMySQL does not appear to have any real library&lt;br /&gt;
documentation at all.  There's a README which&lt;br /&gt;
essentially refers to pep-249, an example.py in the root which shows a&lt;br /&gt;
very basic connection / round trip, and that's it. In&lt;br /&gt;
order to know anything about the API, specific parameters, optimization&lt;br /&gt;
strategies, behaviors, etc., one either has to go off of the MySQL-Python documentation and assume&lt;br /&gt;
PyMySQL also features the same parameter, or read the source code.&lt;br /&gt;
&lt;br /&gt;
PyMySQL probably hasn't had much urgent need for real documentation thus&lt;br /&gt;
far.  However, relying upon being a port of MySQL-Python is fast becoming&lt;br /&gt;
something that can no longer be relied upon, as MySQL-Python's development&lt;br /&gt;
is quite stalled, and PyMySQL should aspire to move into the future&lt;br /&gt;
of MySQL and Python with its own featureset and behaviors.  To that end, it would be a &lt;br /&gt;
great idea if it at least made the start&lt;br /&gt;
of a rudimentary Sphinx build including autodoc for module documentation&lt;br /&gt;
which could then be published up to RTD.   This would be very easy to get&lt;br /&gt;
started, and once present, new documentation sections can be added&lt;br /&gt;
iteratively.&lt;br /&gt;
&lt;br /&gt;
=== Architecture and Performance ===&lt;br /&gt;
&lt;br /&gt;
As stated before, PyMySQL has a really matter-of-fact and straightforward&lt;br /&gt;
implementation.   The code is very readable and it's easy to discern even&lt;br /&gt;
unusual features such as the INSERT..VALUES rewriting.  As far as the design,&lt;br /&gt;
there are lots of areas where performance suffers a bit at the Python level;&lt;br /&gt;
these areas could be greatly improved very easily with a little bit of&lt;br /&gt;
attention.&lt;br /&gt;
&lt;br /&gt;
As we are dealing with a driver that is communicating with a low level&lt;br /&gt;
protocol over a socket, it's critical that sending and receiving messages&lt;br /&gt;
is done as efficiently as possible.  The mechanisms used in PyMySQL rely&lt;br /&gt;
heavily on standard Python objects, without the use of __slots__; this means&lt;br /&gt;
that for individual messages on a socket, we are creating a heavyweight object,&lt;br /&gt;
calling its __init__() method and a creating a new __dict__ each time;&lt;br /&gt;
the most common object, OKPacketWrapper(), already wraps a MysqlPacket()&lt;br /&gt;
object, so we're doing two __init__()s in this common case.  OKPacketWrapper&lt;br /&gt;
also implements a `__getattr__` scheme&lt;br /&gt;
to proxy attribute access from itself to the internal MysqlPacket; this is a&lt;br /&gt;
an inefficient mode of operation in Python as it means a lookup must first&lt;br /&gt;
fail on the OKPacketWrapper before it invokes the `__getattr__()` method&lt;br /&gt;
and does another attribute lookup.   A reorg of the packet classes to produce&lt;br /&gt;
far fewer objects for messages as well as to use __slots__ would be&lt;br /&gt;
recommended.&lt;br /&gt;
&lt;br /&gt;
There are also lots of areas where function call overhead is unnecessarily&lt;br /&gt;
high due to the organization of calls.  For example, when calling upon&lt;br /&gt;
cursor.execute(), for parameters passed as a dictionary under Python 2K,&lt;br /&gt;
the isinstance() builtin function will be called on the incoming arguments&lt;br /&gt;
four times at a minimum, with two of each call against the exact same&lt;br /&gt;
criteria.  Additional logic will then call isinstance() on every key of&lt;br /&gt;
the dictionary as well as at least twice on every value within the dictionary,&lt;br /&gt;
in order to determine if the value needs to be converted to bytes as well&lt;br /&gt;
as for some top-level escaping logic checks; the escaping logic then&lt;br /&gt;
goes further with more checks for type() to match the object to a final&lt;br /&gt;
converter function.   While the flow of this logic does appear to be&lt;br /&gt;
reasonable and straightforward, it isn't ideal for a low-level database&lt;br /&gt;
driver library where speed is a high priority; the various wrappers and&lt;br /&gt;
processors can easily be reorganized here to reduce the number of isinstance()&lt;br /&gt;
and type() calls significantly, which would save a lot on function call&lt;br /&gt;
overhead.  It is likely there are many other areas in the codebase where&lt;br /&gt;
similar reductions in call counts can be made without too much disruption.&lt;br /&gt;
&lt;br /&gt;
In Appendix A, a short performance suite is illustrated.  This&lt;br /&gt;
is run against PyMySQL, Mysql-Connector-Python, and MySQL-Python.&lt;br /&gt;
PyMySQL does in fact demonstrate the&lt;br /&gt;
highest call-count behavior, logging 26,851,003 function calls&lt;br /&gt;
compared to MySQL-connector's 11,674,074 for the same operations.  To PyMySQL's&lt;br /&gt;
credit, it somehow completes the test 25-50% faster than MySQL-connector&lt;br /&gt;
despite having more than twice as many function calls; this probably indicates&lt;br /&gt;
the use of something very slow within MySQL-connector, such as catching&lt;br /&gt;
an exception within a tight loop.  However, the real bar we're comparing to&lt;br /&gt;
is MySQL-Python, which is written&lt;br /&gt;
in C and does the whole test with only 47,503 Python function calls; while&lt;br /&gt;
PyMySQL and MySQL-connector-Python compete for time within the 10-15 second&lt;br /&gt;
range without profiling, MySQL-Python completes the whole test in **1.03 seconds**, a 1000%&lt;br /&gt;
improvement over PyMySQL.   This speed difference is not PyMySQL's fault,&lt;br /&gt;
as pure Python is known to be extremely slow compared to C code.&lt;br /&gt;
However, there's a lot of easy wins in PyMySQL's codebase where the function&lt;br /&gt;
call count could probably be reduced dramatically to be even better than that&lt;br /&gt;
of MySQL-connector-Python; ideally, the driver would be able to run this&lt;br /&gt;
test possibly 5-6x slower than MySQL-Python rather than 10x.&lt;br /&gt;
&lt;br /&gt;
=== History Documentation ===&lt;br /&gt;
&lt;br /&gt;
PyMySQL includes a CHANGELOG file in its root which describes the bug fixes&lt;br /&gt;
and features of each release.  The construction of this file is definitely&lt;br /&gt;
within the &amp;quot;expedient&amp;quot; style noted for PyMySQL overall, and for it to be&lt;br /&gt;
genuinely useful for enterprise-level software, it would need to be&lt;br /&gt;
much more complete in its detail.    Each bug fix mentioned is only referred to&lt;br /&gt;
tersely without any link to a pull request or bug report.  While some can&lt;br /&gt;
be linked to a specific change through detective work&lt;br /&gt;
(&amp;quot;Cursor.fetchall() and .fetchmany now return list, not tuple&amp;quot;), it would&lt;br /&gt;
be very difficult for many&lt;br /&gt;
(&amp;quot;Fixed BIT type handling&amp;quot;, &amp;quot;Fixed GC errors&amp;quot; - what was the issue?)&lt;br /&gt;
and in many cases impossible (&amp;quot;Improved Py3k support&amp;quot;).  The file also&lt;br /&gt;
includes no dates so one has to look at the git tags to figure this&lt;br /&gt;
part out (fortunately, releases to seem to be git tagged at least).&lt;br /&gt;
For a user trying to ascertain if particular issues are&lt;br /&gt;
fixed or if behaviors have changed, this file is at best highly frustrating.&lt;br /&gt;
&lt;br /&gt;
A listing of release dates illustrates an average of two releases per year,&lt;br /&gt;
excluding 2012 when PyMySQL was seeking a new maintainer:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
2014-12-02 12:05:10 -0200  (tag: pymysql-0.6.3)&lt;br /&gt;
2014-04-21 14:28:47 -0300  (tag: pymysql-0.6.2)&lt;br /&gt;
2013-10-11 13:36:56 -0300  (tag: pymysql-0.6.1)&lt;br /&gt;
2013-10-04 14:25:14 -0300  (tag: pymysql-0.6)&lt;br /&gt;
2011-11-08 10:27:41 -0800  (tag: pymysql-0.5)&lt;br /&gt;
2010-12-27 17:28:16 +0000  (tag: pymysql-0.4)&lt;br /&gt;
2010-09-03 00:55:29 +0000  (tag: pymysql-0.3)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Community Involvement ===&lt;br /&gt;
&lt;br /&gt;
This is an area where PyMySQL seems to shine, and might even be an advantage&lt;br /&gt;
as compared to MySQL-Connector-Python, which remains under the cloak&lt;br /&gt;
of MySQL's vastly overstuffed bugtracker under the ownership of&lt;br /&gt;
the famously-opaque Oracle.   For a small, low key library without&lt;br /&gt;
any real documentation and only 126 tests, it features 33 contributors on&lt;br /&gt;
Github and is up to pull request #285, which is quite high.  Because&lt;br /&gt;
PyMySQL is small and straightforward, it does lend itself to contribution&lt;br /&gt;
and seems to do very well in this area.   The developers so far have been&lt;br /&gt;
responsive to issues and pull requests made by the author.&lt;br /&gt;
&lt;br /&gt;
Based on github contributor graphs it appears that PyMySQL had a lull&lt;br /&gt;
in 2012 but was picked up again afterwards in 2013.  As of 2012, its most&lt;br /&gt;
prominent developer Pete Hunt stopped development, and on July 25, 2013&lt;br /&gt;
added a note to the README that the project was looking for a new maintainer.&lt;br /&gt;
As of 2013 it was picked up by the current most prominent developers&lt;br /&gt;
Marcel Rodrigues (github username lecram) and INADA Naoki&lt;br /&gt;
(github username methane).&lt;br /&gt;
&lt;br /&gt;
Most issues that have been noted thus far within the areas of testing,&lt;br /&gt;
documentation and architecture are all very fixable; if the Openstack community&lt;br /&gt;
can provide resources to address some of these areas, and if the PyMySQL&lt;br /&gt;
developers are reasonably open to accepting new changes and producing new&lt;br /&gt;
releases in a timely fashion, the PyMySQL driver&lt;br /&gt;
could rapidly become a fully formidable contender in the MySQL driver field.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Appendix A - Performance Tests ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
import cProfile&lt;br /&gt;
import StringIO&lt;br /&gt;
import pstats&lt;br /&gt;
import contextlib&lt;br /&gt;
import time&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextlib.contextmanager&lt;br /&gt;
def profiled(dbapi):&lt;br /&gt;
    pr = cProfile.Profile()&lt;br /&gt;
    pr.enable()&lt;br /&gt;
    yield&lt;br /&gt;
    pr.disable()&lt;br /&gt;
    s = StringIO.StringIO()&lt;br /&gt;
    ps = pstats.Stats(pr, stream=s).sort_stats('cumulative')&lt;br /&gt;
    ps.print_stats()&lt;br /&gt;
    print &amp;quot;DBAPI:  %s&amp;quot; % dbapi&lt;br /&gt;
    print s.getvalue()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextlib.contextmanager&lt;br /&gt;
def timeonly(dbapi):&lt;br /&gt;
    now = time.time()&lt;br /&gt;
    try:&lt;br /&gt;
        yield&lt;br /&gt;
    finally:&lt;br /&gt;
        total = time.time() - now&lt;br /&gt;
        print &amp;quot;DBAPI:  %s, total seconds %f&amp;quot; % (dbapi, total)&lt;br /&gt;
&lt;br /&gt;
import MySQLdb&lt;br /&gt;
import pymysql&lt;br /&gt;
from mysql import connector as mysqlconnector&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def go(dbapi, ctx):&lt;br /&gt;
    conn = dbapi.connect(&lt;br /&gt;
        user='scott', passwd='tiger', host='localhost', db='test')&lt;br /&gt;
    cursor = conn.cursor()&lt;br /&gt;
    cursor.execute(&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
CREATE TABLE IF NOT EXISTS test_things (&lt;br /&gt;
    x INTEGER,&lt;br /&gt;
    y VARCHAR(255),&lt;br /&gt;
    z FLOAT&lt;br /&gt;
) engine=InnoDB&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;)&lt;br /&gt;
    cursor.execute(&amp;quot;DELETE from test_things&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    with ctx(dbapi):&lt;br /&gt;
        for row in xrange(1000):&lt;br /&gt;
            cursor.execute(&lt;br /&gt;
                &amp;quot;INSERT INTO test_things (x, y, z) &amp;quot;&lt;br /&gt;
                &amp;quot;VALUES (%(x)s, %(y)s, %(z)s)&amp;quot;,&lt;br /&gt;
                {&amp;quot;x&amp;quot;: row, &amp;quot;y&amp;quot;: &amp;quot;row number %d&amp;quot; % row,&lt;br /&gt;
                 &amp;quot;z&amp;quot;: row * 4.57292, }&lt;br /&gt;
            )&lt;br /&gt;
&lt;br /&gt;
        for x in xrange(500):&lt;br /&gt;
            cursor.execute(&lt;br /&gt;
                &amp;quot;select * from test_things&amp;quot;)&lt;br /&gt;
            for row in cursor.fetchall():&lt;br /&gt;
                row[0], row[1], row[2]&lt;br /&gt;
&lt;br /&gt;
    cursor.close()&lt;br /&gt;
    conn.close()&lt;br /&gt;
&lt;br /&gt;
go(pymysql, profiled)&lt;br /&gt;
go(mysqlconnector, profiled)&lt;br /&gt;
go(MySQLdb, profiled)&lt;br /&gt;
&lt;br /&gt;
go(pymysql, timeonly)&lt;br /&gt;
go(mysqlconnector, timeonly)&lt;br /&gt;
go(MySQLdb, timeonly)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DBAPI:  &amp;lt;module 'pymysql' from '/Users/classic/dev/PyMySQL/pymysql/__init__.pyc'&amp;gt;&lt;br /&gt;
         26851003 function calls in 15.924 seconds&lt;br /&gt;
&lt;br /&gt;
   Ordered by: cumulative time&lt;br /&gt;
&lt;br /&gt;
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;
     1500    0.010    0.000   15.923    0.011 /Users/classic/dev/PyMySQL/pymysql/cursors.py:105(execute)&lt;br /&gt;
     1500    0.003    0.000   15.883    0.011 /Users/classic/dev/PyMySQL/pymysql/cursors.py:269(_query)&lt;br /&gt;
     1500    0.004    0.000   15.859    0.011 /Users/classic/dev/PyMySQL/pymysql/connections.py:746(query)&lt;br /&gt;
     1500    0.003    0.000   15.819    0.011 /Users/classic/dev/PyMySQL/pymysql/connections.py:892(_read_query_result)&lt;br /&gt;
     1500    0.004    0.000   15.812    0.011 /Users/classic/dev/PyMySQL/pymysql/connections.py:1097(read)&lt;br /&gt;
      500    0.003    0.000   15.559    0.031 /Users/classic/dev/PyMySQL/pymysql/connections.py:1154(_read_result_packet)&lt;br /&gt;
      500    0.900    0.002   15.429    0.031 /Users/classic/dev/PyMySQL/pymysql/connections.py:1187(_read_rowdata_packet)&lt;br /&gt;
   500000    3.417    0.000    9.173    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1200(_read_row_from_packet)&lt;br /&gt;
  2012000    1.157    0.000    5.307    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:311(read_length_coded_string)&lt;br /&gt;
   504500    1.495    0.000    5.209    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:845(_read_packet)&lt;br /&gt;
  2014500    1.248    0.000    3.035    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:293(read_length_encoded_integer)&lt;br /&gt;
  3539500    2.439    0.000    2.750    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:245(read)&lt;br /&gt;
  1009000    0.779    0.000    2.307    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:870(_read_bytes)&lt;br /&gt;
  1009000    1.197    0.000    1.423    0.000 {method 'read' of '_io.BufferedReader' objects}&lt;br /&gt;
   508500    0.379    0.000    0.695    0.000 /Users/classic/dev/PyMySQL/pymysql/util.py:3(byte2int)&lt;br /&gt;
  5058000    0.465    0.000    0.465    0.000 {len}&lt;br /&gt;
   500500    0.222    0.000    0.455    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1146(_check_packet_is_eof)&lt;br /&gt;
   504500    0.205    0.000    0.375    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:342(check_error)&lt;br /&gt;
  2504000    0.275    0.000    0.275    0.000 {method 'append' of 'list' objects}&lt;br /&gt;
   501500    0.182    0.000    0.230    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:326(is_eof_packet)&lt;br /&gt;
     3500    0.009    0.000    0.226    0.000 /Users/classic/dev/PyMySQL/pymysql/_socketio.py:45(readinto)&lt;br /&gt;
     3500    0.207    0.000    0.207    0.000 {method 'recv_into' of '_socket.socket' objects}&lt;br /&gt;
  1022000    0.203    0.000    0.203    0.000 {_struct.unpack}&lt;br /&gt;
   526000    0.200    0.000    0.200    0.000 {isinstance}&lt;br /&gt;
  1501000    0.195    0.000    0.195    0.000 {method 'get' of 'dict' objects}&lt;br /&gt;
   504500    0.176    0.000    0.176    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:238(__init__)&lt;br /&gt;
   504500    0.170    0.000    0.170    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:339(is_error_packet)&lt;br /&gt;
  2014500    0.167    0.000    0.167    0.000 {ord}&lt;br /&gt;
      500    0.006    0.000    0.125    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1224(_get_descriptions)&lt;br /&gt;
     2000    0.003    0.000    0.087    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:361(__init__)&lt;br /&gt;
     2000    0.019    0.000    0.084    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:365(__parse_field_descriptor)&lt;br /&gt;
   500000    0.056    0.000    0.056    0.000 /Users/classic/dev/PyMySQL/pymysql/converters.py:301(through)&lt;br /&gt;
     1500    0.005    0.000    0.037    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:915(_execute_command)&lt;br /&gt;
     1500    0.003    0.000    0.027    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:886(_write_bytes)&lt;br /&gt;
     1500    0.002    0.000    0.024    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/socket.py:223(meth)&lt;br /&gt;
     1500    0.021    0.000    0.021    0.000 {method 'sendall' of '_socket.socket' objects}&lt;br /&gt;
     1500    0.020    0.000    0.020    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:276(_do_get_result)&lt;br /&gt;
     1000    0.003    0.000    0.019    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:95(_escape_args)&lt;br /&gt;
     4000    0.002    0.000    0.015    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:99(&amp;lt;genexpr&amp;gt;)&lt;br /&gt;
     1000    0.002    0.000    0.014    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1127(_read_ok_packet)&lt;br /&gt;
     3000    0.003    0.000    0.013    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:712(escape)&lt;br /&gt;
     1000    0.005    0.000    0.012    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:416(__init__)&lt;br /&gt;
     8000    0.011    0.000    0.011    0.000 {method 'decode' of 'str' objects}&lt;br /&gt;
     3500    0.003    0.000    0.007    0.000 {method '_checkReadable' of '_io._IOBase' objects}&lt;br /&gt;
     2000    0.003    0.000    0.006    0.000 /Users/classic/dev/PyMySQL/pymysql/converters.py:19(escape_item)&lt;br /&gt;
     4000    0.002    0.000    0.006    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:126(&amp;lt;genexpr&amp;gt;)&lt;br /&gt;
     7500    0.003    0.000    0.005    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:115(ensure_bytes)&lt;br /&gt;
     2000    0.003    0.000    0.005    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:386(description)&lt;br /&gt;
     5500    0.004    0.000    0.004    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:268(advance)&lt;br /&gt;
     1000    0.001    0.000    0.004    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:722(escape_string)&lt;br /&gt;
     3500    0.004    0.000    0.004    0.000 /Users/classic/dev/PyMySQL/pymysql/_socketio.py:87(readable)&lt;br /&gt;
      500    0.002    0.000    0.004    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:442(__init__)&lt;br /&gt;
     3500    0.003    0.000    0.003    0.000 {method '_checkClosed' of '_io._IOBase' objects}&lt;br /&gt;
     1000    0.001    0.000    0.003    0.000 /Users/classic/dev/PyMySQL/pymysql/converters.py:59(escape_string)&lt;br /&gt;
     1500    0.003    0.000    0.003    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1080(__init__)&lt;br /&gt;
     1500    0.001    0.000    0.003    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:92(nextset)&lt;br /&gt;
     4000    0.002    0.000    0.002    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:397(get_column_length)&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 /Users/classic/dev/PyMySQL/pymysql/converters.py:56(escape_float)&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 {method 'sub' of '_sre.SRE_Pattern' objects}&lt;br /&gt;
     3000    0.002    0.000    0.002    0.000 {_struct.pack}&lt;br /&gt;
     6000    0.002    0.000    0.002    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:62(_get_db)&lt;br /&gt;
     1500    0.001    0.000    0.002    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:80(_nextset)&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 /Users/classic/dev/PyMySQL/pymysql/util.py:9(int2byte)&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:244(fetchall)&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {min}&lt;br /&gt;
     2500    0.001    0.000    0.001    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:323(is_ok_packet)&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Users/classic/dev/PyMySQL/pymysql/converters.py:52(escape_int)&lt;br /&gt;
     2000    0.001    0.000    0.001    0.000 {method 'items' of 'dict' objects}&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {getattr}&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1093(__del__)&lt;br /&gt;
     1000    0.000    0.000    0.000    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:259(read_all)&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:67(_check_executed)&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:336(is_load_local_packet)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/contextlib.py:21(__exit__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 test.py:8(profiled)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DBAPI:  &amp;lt;module 'mysql.connector' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/__init__.pyc'&amp;gt;&lt;br /&gt;
         11674074 function calls in 19.049 seconds&lt;br /&gt;
&lt;br /&gt;
   Ordered by: cumulative time&lt;br /&gt;
&lt;br /&gt;
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;
      500    0.640    0.001   18.556    0.037 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:820(fetchall)&lt;br /&gt;
      500    0.003    0.000   11.196    0.022 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:655(get_rows)&lt;br /&gt;
      500    1.561    0.003   11.191    0.022 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:292(read_text_result)&lt;br /&gt;
   504500    3.303    0.000    7.306    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/network.py:219(recv_plain)&lt;br /&gt;
   500000    3.572    0.000    6.625    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:359(row_to_python)&lt;br /&gt;
  1009000    3.155    0.000    3.155    0.000 {method 'recv_into' of '_socket.socket' objects}&lt;br /&gt;
   500000    2.049    0.000    2.329    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:220(read_lc_string_list)&lt;br /&gt;
   500000    0.653    0.000    2.116    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:531(_STRING_to_python)&lt;br /&gt;
   502000    0.378    0.000    1.329    0.000 {method 'decode' of 'bytearray' objects}&lt;br /&gt;
   502000    0.221    0.000    0.951    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/utf_8.py:15(decode)&lt;br /&gt;
   502000    0.730    0.000    0.730    0.000 {_codecs.utf_8_decode}&lt;br /&gt;
   500000    0.514    0.000    0.514    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:413(_INT_to_python)&lt;br /&gt;
   509500    0.510    0.000    0.510    0.000 {_struct.unpack_from}&lt;br /&gt;
     1500    0.009    0.000    0.492    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:452(execute)&lt;br /&gt;
     1500    0.004    0.000    0.437    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:705(cmd_query)&lt;br /&gt;
   500000    0.367    0.000    0.367    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:405(_FLOAT_to_python)&lt;br /&gt;
   504500    0.343    0.000    0.343    0.000 {method 'extend' of 'bytearray' objects}&lt;br /&gt;
     1500    0.007    0.000    0.315    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:481(_send_cmd)&lt;br /&gt;
  2501500    0.275    0.000    0.275    0.000 {method 'append' of 'list' objects}&lt;br /&gt;
   500500    0.246    0.000    0.246    0.000 {method 'startswith' of 'bytearray' objects}&lt;br /&gt;
   524002    0.150    0.000    0.150    0.000 {isinstance}&lt;br /&gt;
     1500    0.011    0.000    0.118    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:613(_handle_result)&lt;br /&gt;
  1004500    0.112    0.000    0.112    0.000 {len}&lt;br /&gt;
   500000    0.092    0.000    0.092    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:204(description)&lt;br /&gt;
     1500    0.007    0.000    0.042    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/network.py:118(send_plain)&lt;br /&gt;
     2000    0.013    0.000    0.041    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:226(parse_column)&lt;br /&gt;
     1000    0.008    0.000    0.034    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:346(_process_params_dict)&lt;br /&gt;
     1500    0.002    0.000    0.029    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/socket.py:223(meth)&lt;br /&gt;
     1500    0.026    0.000    0.026    0.000 {method 'sendall' of '_socket.socket' objects}&lt;br /&gt;
     5000    0.007    0.000    0.014    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/catch23.py:79(struct_unpack)&lt;br /&gt;
     1000    0.001    0.000    0.013    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:562(_handle_ok)&lt;br /&gt;
    12000    0.011    0.000    0.011    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:167(read_lc_string)&lt;br /&gt;
     1000    0.004    0.000    0.010    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:199(parse_ok)&lt;br /&gt;
     3000    0.005    0.000    0.009    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:152(to_mysql)&lt;br /&gt;
     1500    0.003    0.000    0.007    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:118(make_command)&lt;br /&gt;
     3000    0.003    0.000    0.007    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:130(quote)&lt;br /&gt;
     1500    0.005    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/network.py:53(_prepare_packets)&lt;br /&gt;
     1500    0.003    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:406(_handle_result)&lt;br /&gt;
     3000    0.003    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:102(escape)&lt;br /&gt;
     1000    0.002    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:252(parse_eof)&lt;br /&gt;
      500    0.001    0.000    0.005    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:579(_handle_eof)&lt;br /&gt;
     2000    0.003    0.000    0.005    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:545(_handle_server_status)&lt;br /&gt;
     1500    0.003    0.000    0.004    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:53(int1store)&lt;br /&gt;
     3000    0.003    0.000    0.003    0.000 {method 'encode' of 'str' objects}&lt;br /&gt;
     9001    0.003    0.000    0.003    0.000 {method 'replace' of 'str' objects}&lt;br /&gt;
     1500    0.003    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:295(_reset_result)&lt;br /&gt;
     4500    0.003    0.000    0.003    0.000 {_struct.pack}&lt;br /&gt;
     2500    0.003    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:296(read_lc_int)&lt;br /&gt;
      500    0.002    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:757(_handle_eof)&lt;br /&gt;
     6027    0.003    0.000    0.003    0.000 {method 'format' of 'str' objects}&lt;br /&gt;
     3000    0.002    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:1221(_set_unread_result)&lt;br /&gt;
      500    0.001    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:219(parse_column_count)&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 {repr}&lt;br /&gt;
     4527    0.002    0.000    0.002    0.000 {getattr}&lt;br /&gt;
     4000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/constants.py:34(flag_is_set)&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:383(_handle_noresultset)&lt;br /&gt;
     4000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:1233(_get_unread_result)&lt;br /&gt;
     2001    0.001    0.000    0.001    0.000 {method 'items' of 'dict' objects}&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:307(_have_unread_result)&lt;br /&gt;
     3000    0.001    0.000    0.001    0.000 {method 'lower' of 'str' objects}&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 {range}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:173(_str_to_mysql)&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:169(_float_to_mysql)&lt;br /&gt;
     1000    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:161(_int_to_mysql)&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:1313(_get_getwarnings)&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:200(reset)&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:397(_handle_resultset)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/__init__.py:71(search_function)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/ascii.py:41(getregentry)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/contextlib.py:21(__exit__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/__init__.py:49(normalize_encoding)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 test.py:8(profiled)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/codecs.py:77(__new__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {__import__}&lt;br /&gt;
        3    0.000    0.000    0.000    0.000 {method 'get' of 'dict' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'split' of 'str' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {built-in method __new__ of type object at 0x1001534e8}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'translate' of 'str' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'join' of 'str' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {hasattr}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DBAPI:  &amp;lt;module 'MySQLdb' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/__init__.pyc'&amp;gt;&lt;br /&gt;
         47503 function calls in 0.918 seconds&lt;br /&gt;
&lt;br /&gt;
   Ordered by: cumulative time&lt;br /&gt;
&lt;br /&gt;
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;
     1500    0.009    0.000    0.917    0.001 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:164(execute)&lt;br /&gt;
     1500    0.002    0.000    0.890    0.001 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:353(_query)&lt;br /&gt;
     1500    0.004    0.000    0.498    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:315(_do_query)&lt;br /&gt;
     1500    0.016    0.000    0.391    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:358(_post_get_result)&lt;br /&gt;
     1500    0.001    0.000    0.375    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:324(_fetch_row)&lt;br /&gt;
      500    0.374    0.001    0.374    0.001 {built-in method fetch_row}&lt;br /&gt;
     1500    0.007    0.000    0.362    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:142(_do_get_result)&lt;br /&gt;
     1500    0.002    0.000    0.351    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:351(_get_result)&lt;br /&gt;
     1500    0.349    0.000    0.349    0.000 {method 'store_result' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.131    0.000    0.131    0.000 {method 'query' of '_mysql.connection' objects}&lt;br /&gt;
     4000    0.002    0.000    0.010    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:184(&amp;lt;genexpr&amp;gt;)&lt;br /&gt;
     3000    0.002    0.000    0.008    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/connections.py:267(literal)&lt;br /&gt;
     1500    0.007    0.000    0.007    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:107(_warning_check)&lt;br /&gt;
     3000    0.002    0.000    0.006    0.000 {method 'escape' of '_mysql.connection' objects}&lt;br /&gt;
     6000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:159(_get_db)&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/converters.py:81(Float2Str)&lt;br /&gt;
     2500    0.001    0.000    0.001    0.000 {isinstance}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/connections.py:202(string_literal)&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:380(fetchall)&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 {built-in method describe}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 {method 'string_literal' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {method 'insert_id' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {method 'affected_rows' of '_mysql.connection' objects}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/converters.py:69(Thing2Str)&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 {method 'warning_count' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 {method 'info' of '_mysql.connection' objects}&lt;br /&gt;
     1000    0.000    0.000    0.000    0.000 {method 'iteritems' of 'dict' objects}&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:103(_check_executed)&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 {len}&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 {built-in method field_flags}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/contextlib.py:21(__exit__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 test.py:8(profiled)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DBAPI:  &amp;lt;module 'pymysql' from '/Users/classic/dev/PyMySQL/pymysql/__init__.pyc'&amp;gt;, total seconds 10.046012&lt;br /&gt;
DBAPI:  &amp;lt;module 'mysql.connector' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/__init__.pyc'&amp;gt;, total seconds 15.403260&lt;br /&gt;
DBAPI:  &amp;lt;module 'MySQLdb' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/__init__.pyc'&amp;gt;, total seconds 1.028737&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mike Bayer</name></author>	</entry>

	<entry>
		<id>https://wiki.openstack.org/w/index.php?title=PyMySQL_evaluation&amp;diff=72559</id>
		<title>PyMySQL evaluation</title>
		<link rel="alternate" type="text/html" href="https://wiki.openstack.org/w/index.php?title=PyMySQL_evaluation&amp;diff=72559"/>
				<updated>2015-01-28T20:28:13Z</updated>
		
		<summary type="html">&lt;p&gt;Mike Bayer: /* MySQL DB Drivers Comparison */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= PyMySQL Evaluation =&lt;br /&gt;
&lt;br /&gt;
This page will capture issues related to Openstack moving to the [https://github.com/PyMySQL/PyMySQL/ PyMySQL driver] for MySQL/MariaDB dbapi access.&lt;br /&gt;
&lt;br /&gt;
== Rationale ==&lt;br /&gt;
&lt;br /&gt;
While the [https://github.com/farcepest/MySQLdb1 MySQL-Python] driver is a very mature and stable driver, it does not provide compatibility with either Python 3 or with [http://eventlet.net/doc/patching.html eventlet monkeypatching] (MySQL-Python  [https://github.com/eventlet/eventlet/blob/v0.16.1/eventlet/patcher.py#L225-L237 can be monkeypatched] with eventlet, but this feature disabled by default and missed in documentation). So OpenStack's usage of MySQL-Python, combined with the fact that concurrency is provided by eventlet, means that we currently have fully serialized database access within a single process, that is, only one database command occurs at a time within an Openstack Python process.&lt;br /&gt;
&lt;br /&gt;
== Drivers Under Consideration ==&lt;br /&gt;
&lt;br /&gt;
The two drivers that are known to provide eventlet-monkeypatch compatibility are MySQL-Connector and PyMySQL, as they are written in pure Python.   The two other well-known drivers for MySQL are MySQL-Python and OurSQL, both of which are written in C and offer no explicit support for eventlet or async.  They can reportedly be built to support gevent using a system called [https://github.com/CMGS/greenify Greenify], but the maturity and/or stabiltiy of this system is unknown.&lt;br /&gt;
&lt;br /&gt;
A comparison at http://www.diamondtin.com/2014/sqlalchemy-gevent-mysql-python-drivers-comparison/ illustrates performance metrics observed with all four of these drivers, with MySQL-python built both without and with the &amp;quot;greenify&amp;quot; system.&lt;br /&gt;
&lt;br /&gt;
A summary of the status of all four drivers is as follows.&lt;br /&gt;
&lt;br /&gt;
=== MySQL-Python ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/farcepest/MySQLdb1 MySQL-Python] is the most widely used Python driver.  However, it does not support Python 3, and it does not support async systems unless used within a thread pool.   While a system known as Greenify can potentially resolve the latter situation, the Python 3 limitation is still a deal-breaker - pull request with Python 3 support [https://github.com/farcepest/MySQLdb1/pull/62 was proposed] to MySQL-python in April 2014 and still not merged.&lt;br /&gt;
&lt;br /&gt;
=== mysqlclient ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/PyMySQL/mysqlclient-python mysqlclient] is a fork of MySQL-python. It add Python 3.3 support and merges some pull requests.&lt;br /&gt;
&lt;br /&gt;
=== OurSQL ===&lt;br /&gt;
&lt;br /&gt;
[https://pythonhosted.org/oursql/ OurSQL] is an alternative MySQL driver that is also written in C.  It features fast performance and uses a different execution model than MySQL-python, using prepared statements.    OurSQL does have a Python 3 port hosted on [https://launchpad.net/oursql/py3k/ launchpad] but is not integrated with OurSQL itself nor is it published on Pypi.     Because it is written in C, it does not offer compatibility with async systems including eventlet-style monkeypatching.&lt;br /&gt;
&lt;br /&gt;
Besides the shaky Python 3 support and lack of eventlet support, OurSQL is also not maintained, seeing its last master commit on [https://code.launchpad.net/~jerub/oursql/master 2012-06-05].&lt;br /&gt;
&lt;br /&gt;
=== MySQL-Connector-Python ===&lt;br /&gt;
&lt;br /&gt;
[https://pypi.python.org/pypi/mysql-connector-python MySQL-connector-Python] is a pure Python MySQL driver, and is now released under the auspices of the MySQL project itself, as owned by Oracle.  MySQL-connector-Python supports both Python 3 as well as eventlet monkeypatching, and is well maintained.  It is endorsed by Oracle as the official Oracle-supported driver for MySQL, so to that extent, it is in most ways Openstack's first choice in driver.  However, Oracle refuses to publish MySQL-connector-Python on Pypi, which is critical to the Openstack infrastructure.  Repeated attempts to communicate with Oracle in order to resolve this issue have not made any progress.    Therefore, for this one unfortunate reason, MySQL-connector-Python will not have a place in the Openstack ecosystem unless this issue is resolved.&lt;br /&gt;
&lt;br /&gt;
=== PyMySQL ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/PyMySQL/PyMySQL PyMySQL] is a pure Python MySQL&lt;br /&gt;
driver, first written as a rough port of the MySQL-Python driver.&lt;br /&gt;
PyMySQL meets all of Openstack's criterion for a driver: it is fully&lt;br /&gt;
open source, hosted on Github, released on Pypi, is actively&lt;br /&gt;
maintained, is written in pure Python so is eventlet-monkeypatch&lt;br /&gt;
compatible, and is fully Python 3 compatible.&lt;br /&gt;
&lt;br /&gt;
As this document is named &amp;quot;PyMySQL Evaluation&amp;quot;, it should be apparent that&lt;br /&gt;
this is the driver Openstack is currently leaning towards; because it is the only&lt;br /&gt;
one that meets all criteria fully, it is already most likely the &amp;quot;winner&amp;quot;.  However, it does&lt;br /&gt;
have some minor code quality issues which hopefully can be addressed in some&lt;br /&gt;
way; the section below titled &amp;quot;PyMySQL Code Review&amp;quot; will summarize the&lt;br /&gt;
current state of the code.&lt;br /&gt;
&lt;br /&gt;
===MySQL DB Drivers Comparison===&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Project !! PyPi hosted !! Eventlet friendly !! Python 3 compatibility !! Maturity and/or stability  !! Comment&lt;br /&gt;
|-&lt;br /&gt;
| MySQL-Python || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: orange;&amp;quot; | Partial || style=&amp;quot;background-color: red;&amp;quot; | No  || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || Can be monkeypatched by eventlet, but only to enable thread pooling&lt;br /&gt;
|-&lt;br /&gt;
| mysqlclient || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: orange;&amp;quot; | Partial || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes  || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || MySQL-python fork, so should be similar to it&lt;br /&gt;
|-&lt;br /&gt;
| OurSQL || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: red;&amp;quot; | No || style=&amp;quot;background-color: orange;&amp;quot; | Yes, but not Pypi hosted  || style=&amp;quot;background-color: red;&amp;quot; | development halted fairly early on || Has not seen commits/releases in two years&lt;br /&gt;
|-&lt;br /&gt;
| MySQL-Connector-Python || style=&amp;quot;background-color: red;&amp;quot; | No || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes  || style=&amp;quot;background-color: orange;&amp;quot; | Yes, though the driver is still fairly new || The official Oracle-supported driver for MySQL&lt;br /&gt;
|-&lt;br /&gt;
| PyMySQL || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes  || style=&amp;quot;background-color: orange;&amp;quot; | Yes, however see notes below. || Actively maintained and popular.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== PyMySQL Code Review ==&lt;br /&gt;
&lt;br /&gt;
PyMySQL started roughly as a pure Python port of MySQL-Python.   The sections below will&lt;br /&gt;
detail various aspects of the driver and the status of the code.   Overall, the general&lt;br /&gt;
tone of PyMySQL is one of code that was written in the spirit of pragmatism and immediate&lt;br /&gt;
need; it is straightforward, relatively free of serious antipatterns and performs&lt;br /&gt;
its task in a matter-of-fact way.  However,&lt;br /&gt;
it lacks polish and completeness in many areas that would normally be in better&lt;br /&gt;
condition for a more mature project.  These areas are all highly fixable, and it is&lt;br /&gt;
hoped that these items can be shared with the current PyMySQL developers, through&lt;br /&gt;
a combination of this document itself as well as new issues and pull requests&lt;br /&gt;
on the PyMySQL tracker, so that this fairly promising library can be pushed&lt;br /&gt;
to the next level.   This would require that the developers are amenable to&lt;br /&gt;
these improvements.&lt;br /&gt;
&lt;br /&gt;
=== Coding Style / Pep8 ===&lt;br /&gt;
&lt;br /&gt;
PyMySQL's coding style is pretty good.  The majority of it passes all&lt;br /&gt;
flake8 tests, save for a few dozen whitespace issues and&lt;br /&gt;
some long lines here and there within the core&lt;br /&gt;
modules, and moreso within the tests which appear to be a bit more&lt;br /&gt;
crufty than the core modules.  A full run of flake8 produces only 128&lt;br /&gt;
errors, and with a default run of autopep8 against E1,E2,E3, we can&lt;br /&gt;
fix all of them in one pass except for 38 remaining long line warnings.&lt;br /&gt;
The code layout is mostly idiomatic and reasonable.&lt;br /&gt;
&lt;br /&gt;
The codebase is probably short on docstrings.  Those methods that are&lt;br /&gt;
part of the public DBAPI do have docstrings, but tend to be extremely&lt;br /&gt;
terse (example, docstring for cursor.execute(): &amp;quot;'Execute a query'&amp;quot;).&lt;br /&gt;
Other methods that aren't public tend to not have any docstrings, but&lt;br /&gt;
the codebase is not at all hard to read and the purpose of methods and&lt;br /&gt;
functions is pretty easy to discern just by their names and implementations,&lt;br /&gt;
however those who seek inline documentation as a means to understand a codebase&lt;br /&gt;
will be disappointed.   The terseness of public API docstrings is more of&lt;br /&gt;
an issue in terms of the overall lack of documentation for PyMySQL,&lt;br /&gt;
see the section &amp;quot;Documentation&amp;quot; below.&lt;br /&gt;
&lt;br /&gt;
=== Test Coverage ===&lt;br /&gt;
&lt;br /&gt;
The test coverage for PyMySQL is definitely lacking.   From a visual&lt;br /&gt;
inspection alone, it's apparent that of the dozen or so test modules,&lt;br /&gt;
most of them have less then ten tests each and the modules are very short.&lt;br /&gt;
For a full test run, there's only 126 tests; contrast this to&lt;br /&gt;
MySQL-Connector-Python which has 531 tests.&lt;br /&gt;
&lt;br /&gt;
Additionally, all of the tests are live round-trip tests against a&lt;br /&gt;
real database.  There are no non-live unit tests of any kind,&lt;br /&gt;
leaving codepaths that are not easily exercised&lt;br /&gt;
on a generic MySQL database out in the cold.  This means that features designed&lt;br /&gt;
for particular versions of MySQL, particular datatypes, particular error&lt;br /&gt;
conditions, and particular kinds of message packets that aren't created for&lt;br /&gt;
real are simply not tested.&lt;br /&gt;
&lt;br /&gt;
The lack of tests is apparent when run with coverage.  The test suite itself&lt;br /&gt;
does not appear to have any signs that it is normally run with coverage&lt;br /&gt;
turned on, with no directives in tox.ini or similar.   With coverage,&lt;br /&gt;
the overall coverage is 85%, but the modules most lacking in coverage&lt;br /&gt;
are also the most critical to PyMySQL's core functionality,&lt;br /&gt;
connections.py, converters.py and cursors.py:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Name                                                              Stmts   Miss  Cover&lt;br /&gt;
-------------------------------------------------------------------------------------&lt;br /&gt;
pymysql/__init__                                                     51      3    94%&lt;br /&gt;
pymysql/_compat                                                      14      4    71%&lt;br /&gt;
pymysql/_socketio                                                    72     30    58%&lt;br /&gt;
pymysql/charset                                                     228      3    99%&lt;br /&gt;
pymysql/connections                                                 827    188    77%&lt;br /&gt;
pymysql/constants/CLIENT                                             18      0   100%&lt;br /&gt;
pymysql/constants/COMMAND                                            32      0   100%&lt;br /&gt;
pymysql/constants/ER                                                471      0   100%&lt;br /&gt;
pymysql/constants/FIELD_TYPE                                         29      0   100%&lt;br /&gt;
pymysql/constants/FLAG                                               15      0   100%&lt;br /&gt;
pymysql/constants/SERVER_STATUS                                      10      0   100%&lt;br /&gt;
pymysql/constants/__init__                                            0      0   100%&lt;br /&gt;
pymysql/converters                                                  148     51    66%&lt;br /&gt;
pymysql/cursors                                                     273     37    86%&lt;br /&gt;
pymysql/err                                                          40      1    98%&lt;br /&gt;
pymysql/tests/__init__                                               14      5    64%&lt;br /&gt;
pymysql/tests/base                                                   25      2    92%&lt;br /&gt;
pymysql/tests/test_DictCursor                                        73      2    97%&lt;br /&gt;
pymysql/tests/test_SSCursor                                          56      6    89%&lt;br /&gt;
pymysql/tests/test_basic                                            186      4    98%&lt;br /&gt;
pymysql/tests/test_connection                                        57      8    86%&lt;br /&gt;
pymysql/tests/test_example                                           17      2    88%&lt;br /&gt;
pymysql/tests/test_issues                                           300     48    84%&lt;br /&gt;
pymysql/tests/test_load_local                                        39      2    95%&lt;br /&gt;
pymysql/tests/test_nextset                                           43      4    91%&lt;br /&gt;
pymysql/tests/thirdparty/__init__                                     7      5    29%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/__init__                        6      2    67%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/capabilities                  196     23    88%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/dbapi20                       423    121    71%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_capabilities      73      6    92%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_dbapi20          100      2    98%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_nonstandard       57      3    95%&lt;br /&gt;
pymysql/times                                                        12      0   100%&lt;br /&gt;
pymysql/util                                                         14      7    50%&lt;br /&gt;
-------------------------------------------------------------------------------------&lt;br /&gt;
TOTAL                                                              3926    569    86%&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The lack of coverage in converters.py refers to functions related to handling&lt;br /&gt;
incoming bound values as well as outgoing result values.    Key data escaping&lt;br /&gt;
features such as escaping of dictionaries, sets, some unicode objects and&lt;br /&gt;
byte objects as well as many pathways into date and time-processing functions are not&lt;br /&gt;
covered.&lt;br /&gt;
&lt;br /&gt;
In connections.py, uncovered features include various SSL and&lt;br /&gt;
very legacy (e.g. version 3.23) MySQL features, but also some protocol parsing&lt;br /&gt;
features.&lt;br /&gt;
&lt;br /&gt;
In cursors.py, there is coverage for an elaborate and mostly undocumented&lt;br /&gt;
performance-related feature that rewrites an INSERT statement to have an&lt;br /&gt;
extended VALUES clause (_do_execute_many), however it fails to cover the very&lt;br /&gt;
likely case where the function will need to break the input set into&lt;br /&gt;
multiple chunks:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;gt;         for arg in args:&lt;br /&gt;
&amp;gt;             v = values % escape(arg, conn)&lt;br /&gt;
&amp;gt;             if isinstance(v, text_type):&lt;br /&gt;
&amp;gt;                 v = v.encode(encoding)&lt;br /&gt;
&amp;gt;             if len(sql) + len(v) + 1 &amp;gt; max_stmt_length:&lt;br /&gt;
!                 rows += self.execute(sql)&lt;br /&gt;
!                 sql = bytearray(prefix)&lt;br /&gt;
&amp;gt;             else:&lt;br /&gt;
&amp;gt;                 sql += b','&lt;br /&gt;
&amp;gt;             sql += v&lt;br /&gt;
&amp;gt;         rows += self.execute(sql)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The most prominent missing coverage in cursors.py seems to be&lt;br /&gt;
for its &amp;quot;scrollable cursor&amp;quot; support.&lt;br /&gt;
&lt;br /&gt;
Beyond the lack of completeness in testing, many of the tests themselves&lt;br /&gt;
are written in an expedient &amp;quot;one giant test&amp;quot; style where dozens of&lt;br /&gt;
assertions and individual behaviors are lumped into one big test&lt;br /&gt;
case (see test_SSCursor.py, test_basic.py-&amp;gt;test_datatypes for examples).&lt;br /&gt;
Test cases like these are difficult to work with when debugging&lt;br /&gt;
regressions as well as when tests for new features need to be added, and ideally&lt;br /&gt;
these should be broken out into clean single-feature tests with consistent&lt;br /&gt;
fixtures.&lt;br /&gt;
&lt;br /&gt;
There is also a suite called &amp;quot;test_issues&amp;quot;, which intends to&lt;br /&gt;
accumulate tests against specific issues that have been reported. This&lt;br /&gt;
isn't a bad idea, though it would be nice to see an effort made into&lt;br /&gt;
categorizing these tests into the actual features they are testing,&lt;br /&gt;
rather than a meaningless list of issue numbers, which could then form&lt;br /&gt;
as the basis for new suites centered around those areas of&lt;br /&gt;
functionality.  The issues reported should be used as the inspiration&lt;br /&gt;
for improvement of the test suite, rather than just another line-item&lt;br /&gt;
to be filed away.&lt;br /&gt;
&lt;br /&gt;
=== Library Documentation ===&lt;br /&gt;
&lt;br /&gt;
As the Python DBAPI is already a well-documented API, and the users of&lt;br /&gt;
PyMySQL are typically coming from MySQL-Python which is documented&lt;br /&gt;
to some degree and for which the use contract is widely known, it seems&lt;br /&gt;
unlikely that users of PyMySQL are eager for comprehensive documentation&lt;br /&gt;
specific to this library.&lt;br /&gt;
&lt;br /&gt;
That being said, PyMySQL does not appear to have any real library&lt;br /&gt;
documentation at all.  There's a README which&lt;br /&gt;
essentially refers to pep-249, an example.py in the root which shows a&lt;br /&gt;
very basic connection / round trip, and that's it. In&lt;br /&gt;
order to know anything about the API, specific parameters, optimization&lt;br /&gt;
strategies, behaviors, etc., one either has to go off of the MySQL-Python documentation and assume&lt;br /&gt;
PyMySQL also features the same parameter, or read the source code.&lt;br /&gt;
&lt;br /&gt;
PyMySQL probably hasn't had much urgent need for real documentation thus&lt;br /&gt;
far.  However, relying upon being a port of MySQL-Python is fast becoming&lt;br /&gt;
something that can no longer be relied upon, as MySQL-Python's development&lt;br /&gt;
is quite stalled, and PyMySQL should aspire to move into the future&lt;br /&gt;
of MySQL and Python with its own featureset and behaviors.  To that end, it would be a &lt;br /&gt;
great idea if it at least made the start&lt;br /&gt;
of a rudimentary Sphinx build including autodoc for module documentation&lt;br /&gt;
which could then be published up to RTD.   This would be very easy to get&lt;br /&gt;
started, and once present, new documentation sections can be added&lt;br /&gt;
iteratively.&lt;br /&gt;
&lt;br /&gt;
=== Architecture and Performance ===&lt;br /&gt;
&lt;br /&gt;
As stated before, PyMySQL has a really matter-of-fact and straightforward&lt;br /&gt;
implementation.   The code is very readable and it's easy to discern even&lt;br /&gt;
unusual features such as the INSERT..VALUES rewriting.  As far as the design,&lt;br /&gt;
there are lots of areas where performance suffers a bit at the Python level;&lt;br /&gt;
these areas could be greatly improved very easily with a little bit of&lt;br /&gt;
attention.&lt;br /&gt;
&lt;br /&gt;
As we are dealing with a driver that is communicating with a low level&lt;br /&gt;
protocol over a socket, it's critical that sending and receiving messages&lt;br /&gt;
is done as efficiently as possible.  The mechanisms used in PyMySQL rely&lt;br /&gt;
heavily on standard Python objects, without the use of __slots__; this means&lt;br /&gt;
that for individual messages on a socket, we are creating a heavyweight object,&lt;br /&gt;
calling its __init__() method and a creating a new __dict__ each time;&lt;br /&gt;
the most common object, OKPacketWrapper(), already wraps a MysqlPacket()&lt;br /&gt;
object, so we're doing two __init__()s in this common case.  OKPacketWrapper&lt;br /&gt;
also implements a `__getattr__` scheme&lt;br /&gt;
to proxy attribute access from itself to the internal MysqlPacket; this is a&lt;br /&gt;
an inefficient mode of operation in Python as it means a lookup must first&lt;br /&gt;
fail on the OKPacketWrapper before it invokes the `__getattr__()` method&lt;br /&gt;
and does another attribute lookup.   A reorg of the packet classes to produce&lt;br /&gt;
far fewer objects for messages as well as to use __slots__ would be&lt;br /&gt;
recommended.&lt;br /&gt;
&lt;br /&gt;
There are also lots of areas where function call overhead is unnecessarily&lt;br /&gt;
high due to the organization of calls.  For example, when calling upon&lt;br /&gt;
cursor.execute(), for parameters passed as a dictionary under Python 2K,&lt;br /&gt;
the isinstance() builtin function will be called on the incoming arguments&lt;br /&gt;
four times at a minimum, with two of each call against the exact same&lt;br /&gt;
criteria.  Additional logic will then call isinstance() on every key of&lt;br /&gt;
the dictionary as well as at least twice on every value within the dictionary,&lt;br /&gt;
in order to determine if the value needs to be converted to bytes as well&lt;br /&gt;
as for some top-level escaping logic checks; the escaping logic then&lt;br /&gt;
goes further with more checks for type() to match the object to a final&lt;br /&gt;
converter function.   While the flow of this logic does appear to be&lt;br /&gt;
reasonable and straightforward, it isn't ideal for a low-level database&lt;br /&gt;
driver library where speed is a high priority; the various wrappers and&lt;br /&gt;
processors can easily be reorganized here to reduce the number of isinstance()&lt;br /&gt;
and type() calls significantly, which would save a lot on function call&lt;br /&gt;
overhead.  It is likely there are many other areas in the codebase where&lt;br /&gt;
similar reductions in call counts can be made without too much disruption.&lt;br /&gt;
&lt;br /&gt;
In Appendix A, a short performance suite is illustrated.  This&lt;br /&gt;
is run against PyMySQL, Mysql-Connector-Python, and MySQL-Python.&lt;br /&gt;
PyMySQL does in fact demonstrate the&lt;br /&gt;
highest call-count behavior, logging 26,851,003 function calls&lt;br /&gt;
compared to MySQL-connector's 11,674,074 for the same operations.  To PyMySQL's&lt;br /&gt;
credit, it somehow completes the test 25-50% faster than MySQL-connector&lt;br /&gt;
despite having more than twice as many function calls; this probably indicates&lt;br /&gt;
the use of something very slow within MySQL-connector, such as catching&lt;br /&gt;
an exception within a tight loop.  However, the real bar we're comparing to&lt;br /&gt;
is MySQL-Python, which is written&lt;br /&gt;
in C and does the whole test with only 47,503 Python function calls; while&lt;br /&gt;
PyMySQL and MySQL-connector-Python compete for time within the 10-15 second&lt;br /&gt;
range without profiling, MySQL-Python completes the whole test in **1.03 seconds**, a 1000%&lt;br /&gt;
improvement over PyMySQL.   This speed difference is not PyMySQL's fault,&lt;br /&gt;
as pure Python is known to be extremely slow compared to C code.&lt;br /&gt;
However, there's a lot of easy wins in PyMySQL's codebase where the function&lt;br /&gt;
call count could probably be reduced dramatically to be even better than that&lt;br /&gt;
of MySQL-connector-Python; ideally, the driver would be able to run this&lt;br /&gt;
test possibly 5-6x slower than MySQL-Python rather than 10x.&lt;br /&gt;
&lt;br /&gt;
=== History Documentation ===&lt;br /&gt;
&lt;br /&gt;
PyMySQL includes a CHANGELOG file in its root which describes the bug fixes&lt;br /&gt;
and features of each release.  The construction of this file is definitely&lt;br /&gt;
within the &amp;quot;expedient&amp;quot; style noted for PyMySQL overall, and for it to be&lt;br /&gt;
genuinely useful for enterprise-level software, it would need to be&lt;br /&gt;
much more complete in its detail.    Each bug fix mentioned is only referred to&lt;br /&gt;
tersely without any link to a pull request or bug report.  While some can&lt;br /&gt;
be linked to a specific change through detective work&lt;br /&gt;
(&amp;quot;Cursor.fetchall() and .fetchmany now return list, not tuple&amp;quot;), it would&lt;br /&gt;
be very difficult for many&lt;br /&gt;
(&amp;quot;Fixed BIT type handling&amp;quot;, &amp;quot;Fixed GC errors&amp;quot; - what was the issue?)&lt;br /&gt;
and in many cases impossible (&amp;quot;Improved Py3k support&amp;quot;).  The file also&lt;br /&gt;
includes no dates so one has to look at the git tags to figure this&lt;br /&gt;
part out (fortunately, releases to seem to be git tagged at least).&lt;br /&gt;
For a user trying to ascertain if particular issues are&lt;br /&gt;
fixed or if behaviors have changed, this file is at best highly frustrating.&lt;br /&gt;
&lt;br /&gt;
A listing of release dates illustrates an average of two releases per year,&lt;br /&gt;
excluding 2012 when PyMySQL was seeking a new maintainer:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
2014-12-02 12:05:10 -0200  (tag: pymysql-0.6.3)&lt;br /&gt;
2014-04-21 14:28:47 -0300  (tag: pymysql-0.6.2)&lt;br /&gt;
2013-10-11 13:36:56 -0300  (tag: pymysql-0.6.1)&lt;br /&gt;
2013-10-04 14:25:14 -0300  (tag: pymysql-0.6)&lt;br /&gt;
2011-11-08 10:27:41 -0800  (tag: pymysql-0.5)&lt;br /&gt;
2010-12-27 17:28:16 +0000  (tag: pymysql-0.4)&lt;br /&gt;
2010-09-03 00:55:29 +0000  (tag: pymysql-0.3)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Community Involvement ===&lt;br /&gt;
&lt;br /&gt;
This is an area where PyMySQL seems to shine, and might even be an advantage&lt;br /&gt;
as compared to MySQL-Connector-Python, which remains under the cloak&lt;br /&gt;
of MySQL's vastly overstuffed bugtracker under the ownership of&lt;br /&gt;
the famously-opaque Oracle.   For a small, low key library without&lt;br /&gt;
any real documentation and only 126 tests, it features 33 contributors on&lt;br /&gt;
Github and is up to pull request #285, which is quite high.  Because&lt;br /&gt;
PyMySQL is small and straightforward, it does lend itself to contribution&lt;br /&gt;
and seems to do very well in this area.   The developers so far have been&lt;br /&gt;
responsive to issues and pull requests made by the author.&lt;br /&gt;
&lt;br /&gt;
Based on github contributor graphs it appears that PyMySQL had a lull&lt;br /&gt;
in 2012 but was picked up again afterwards in 2013.  As of 2012, its most&lt;br /&gt;
prominent developer Pete Hunt stopped development, and on July 25, 2013&lt;br /&gt;
added a note to the README that the project was looking for a new maintainer.&lt;br /&gt;
As of 2013 it was picked up by the current most prominent developers&lt;br /&gt;
Marcel Rodrigues (github username lecram) and INADA Naoki&lt;br /&gt;
(github username methane).&lt;br /&gt;
&lt;br /&gt;
Most issues that have been noted thus far within the areas of testing,&lt;br /&gt;
documentation and architecture are all very fixable; if the Openstack community&lt;br /&gt;
can provide resources to address some of these areas, and if the PyMySQL&lt;br /&gt;
developers are reasonably open to accepting new changes and producing new&lt;br /&gt;
releases in a timely fashion, the PyMySQL driver&lt;br /&gt;
could rapidly become a fully formidable contender in the MySQL driver field.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Appendix A - Performance Tests ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
import cProfile&lt;br /&gt;
import StringIO&lt;br /&gt;
import pstats&lt;br /&gt;
import contextlib&lt;br /&gt;
import time&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextlib.contextmanager&lt;br /&gt;
def profiled(dbapi):&lt;br /&gt;
    pr = cProfile.Profile()&lt;br /&gt;
    pr.enable()&lt;br /&gt;
    yield&lt;br /&gt;
    pr.disable()&lt;br /&gt;
    s = StringIO.StringIO()&lt;br /&gt;
    ps = pstats.Stats(pr, stream=s).sort_stats('cumulative')&lt;br /&gt;
    ps.print_stats()&lt;br /&gt;
    print &amp;quot;DBAPI:  %s&amp;quot; % dbapi&lt;br /&gt;
    print s.getvalue()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextlib.contextmanager&lt;br /&gt;
def timeonly(dbapi):&lt;br /&gt;
    now = time.time()&lt;br /&gt;
    try:&lt;br /&gt;
        yield&lt;br /&gt;
    finally:&lt;br /&gt;
        total = time.time() - now&lt;br /&gt;
        print &amp;quot;DBAPI:  %s, total seconds %f&amp;quot; % (dbapi, total)&lt;br /&gt;
&lt;br /&gt;
import MySQLdb&lt;br /&gt;
import pymysql&lt;br /&gt;
from mysql import connector as mysqlconnector&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def go(dbapi, ctx):&lt;br /&gt;
    conn = dbapi.connect(&lt;br /&gt;
        user='scott', passwd='tiger', host='localhost', db='test')&lt;br /&gt;
    cursor = conn.cursor()&lt;br /&gt;
    cursor.execute(&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
CREATE TABLE IF NOT EXISTS test_things (&lt;br /&gt;
    x INTEGER,&lt;br /&gt;
    y VARCHAR(255),&lt;br /&gt;
    z FLOAT&lt;br /&gt;
) engine=InnoDB&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;)&lt;br /&gt;
    cursor.execute(&amp;quot;DELETE from test_things&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    with ctx(dbapi):&lt;br /&gt;
        for row in xrange(1000):&lt;br /&gt;
            cursor.execute(&lt;br /&gt;
                &amp;quot;INSERT INTO test_things (x, y, z) &amp;quot;&lt;br /&gt;
                &amp;quot;VALUES (%(x)s, %(y)s, %(z)s)&amp;quot;,&lt;br /&gt;
                {&amp;quot;x&amp;quot;: row, &amp;quot;y&amp;quot;: &amp;quot;row number %d&amp;quot; % row,&lt;br /&gt;
                 &amp;quot;z&amp;quot;: row * 4.57292, }&lt;br /&gt;
            )&lt;br /&gt;
&lt;br /&gt;
        for x in xrange(500):&lt;br /&gt;
            cursor.execute(&lt;br /&gt;
                &amp;quot;select * from test_things&amp;quot;)&lt;br /&gt;
            for row in cursor.fetchall():&lt;br /&gt;
                row[0], row[1], row[2]&lt;br /&gt;
&lt;br /&gt;
    cursor.close()&lt;br /&gt;
    conn.close()&lt;br /&gt;
&lt;br /&gt;
go(pymysql, profiled)&lt;br /&gt;
go(mysqlconnector, profiled)&lt;br /&gt;
go(MySQLdb, profiled)&lt;br /&gt;
&lt;br /&gt;
go(pymysql, timeonly)&lt;br /&gt;
go(mysqlconnector, timeonly)&lt;br /&gt;
go(MySQLdb, timeonly)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DBAPI:  &amp;lt;module 'pymysql' from '/Users/classic/dev/PyMySQL/pymysql/__init__.pyc'&amp;gt;&lt;br /&gt;
         26851003 function calls in 15.924 seconds&lt;br /&gt;
&lt;br /&gt;
   Ordered by: cumulative time&lt;br /&gt;
&lt;br /&gt;
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;
     1500    0.010    0.000   15.923    0.011 /Users/classic/dev/PyMySQL/pymysql/cursors.py:105(execute)&lt;br /&gt;
     1500    0.003    0.000   15.883    0.011 /Users/classic/dev/PyMySQL/pymysql/cursors.py:269(_query)&lt;br /&gt;
     1500    0.004    0.000   15.859    0.011 /Users/classic/dev/PyMySQL/pymysql/connections.py:746(query)&lt;br /&gt;
     1500    0.003    0.000   15.819    0.011 /Users/classic/dev/PyMySQL/pymysql/connections.py:892(_read_query_result)&lt;br /&gt;
     1500    0.004    0.000   15.812    0.011 /Users/classic/dev/PyMySQL/pymysql/connections.py:1097(read)&lt;br /&gt;
      500    0.003    0.000   15.559    0.031 /Users/classic/dev/PyMySQL/pymysql/connections.py:1154(_read_result_packet)&lt;br /&gt;
      500    0.900    0.002   15.429    0.031 /Users/classic/dev/PyMySQL/pymysql/connections.py:1187(_read_rowdata_packet)&lt;br /&gt;
   500000    3.417    0.000    9.173    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1200(_read_row_from_packet)&lt;br /&gt;
  2012000    1.157    0.000    5.307    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:311(read_length_coded_string)&lt;br /&gt;
   504500    1.495    0.000    5.209    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:845(_read_packet)&lt;br /&gt;
  2014500    1.248    0.000    3.035    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:293(read_length_encoded_integer)&lt;br /&gt;
  3539500    2.439    0.000    2.750    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:245(read)&lt;br /&gt;
  1009000    0.779    0.000    2.307    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:870(_read_bytes)&lt;br /&gt;
  1009000    1.197    0.000    1.423    0.000 {method 'read' of '_io.BufferedReader' objects}&lt;br /&gt;
   508500    0.379    0.000    0.695    0.000 /Users/classic/dev/PyMySQL/pymysql/util.py:3(byte2int)&lt;br /&gt;
  5058000    0.465    0.000    0.465    0.000 {len}&lt;br /&gt;
   500500    0.222    0.000    0.455    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1146(_check_packet_is_eof)&lt;br /&gt;
   504500    0.205    0.000    0.375    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:342(check_error)&lt;br /&gt;
  2504000    0.275    0.000    0.275    0.000 {method 'append' of 'list' objects}&lt;br /&gt;
   501500    0.182    0.000    0.230    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:326(is_eof_packet)&lt;br /&gt;
     3500    0.009    0.000    0.226    0.000 /Users/classic/dev/PyMySQL/pymysql/_socketio.py:45(readinto)&lt;br /&gt;
     3500    0.207    0.000    0.207    0.000 {method 'recv_into' of '_socket.socket' objects}&lt;br /&gt;
  1022000    0.203    0.000    0.203    0.000 {_struct.unpack}&lt;br /&gt;
   526000    0.200    0.000    0.200    0.000 {isinstance}&lt;br /&gt;
  1501000    0.195    0.000    0.195    0.000 {method 'get' of 'dict' objects}&lt;br /&gt;
   504500    0.176    0.000    0.176    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:238(__init__)&lt;br /&gt;
   504500    0.170    0.000    0.170    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:339(is_error_packet)&lt;br /&gt;
  2014500    0.167    0.000    0.167    0.000 {ord}&lt;br /&gt;
      500    0.006    0.000    0.125    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1224(_get_descriptions)&lt;br /&gt;
     2000    0.003    0.000    0.087    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:361(__init__)&lt;br /&gt;
     2000    0.019    0.000    0.084    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:365(__parse_field_descriptor)&lt;br /&gt;
   500000    0.056    0.000    0.056    0.000 /Users/classic/dev/PyMySQL/pymysql/converters.py:301(through)&lt;br /&gt;
     1500    0.005    0.000    0.037    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:915(_execute_command)&lt;br /&gt;
     1500    0.003    0.000    0.027    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:886(_write_bytes)&lt;br /&gt;
     1500    0.002    0.000    0.024    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/socket.py:223(meth)&lt;br /&gt;
     1500    0.021    0.000    0.021    0.000 {method 'sendall' of '_socket.socket' objects}&lt;br /&gt;
     1500    0.020    0.000    0.020    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:276(_do_get_result)&lt;br /&gt;
     1000    0.003    0.000    0.019    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:95(_escape_args)&lt;br /&gt;
     4000    0.002    0.000    0.015    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:99(&amp;lt;genexpr&amp;gt;)&lt;br /&gt;
     1000    0.002    0.000    0.014    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1127(_read_ok_packet)&lt;br /&gt;
     3000    0.003    0.000    0.013    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:712(escape)&lt;br /&gt;
     1000    0.005    0.000    0.012    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:416(__init__)&lt;br /&gt;
     8000    0.011    0.000    0.011    0.000 {method 'decode' of 'str' objects}&lt;br /&gt;
     3500    0.003    0.000    0.007    0.000 {method '_checkReadable' of '_io._IOBase' objects}&lt;br /&gt;
     2000    0.003    0.000    0.006    0.000 /Users/classic/dev/PyMySQL/pymysql/converters.py:19(escape_item)&lt;br /&gt;
     4000    0.002    0.000    0.006    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:126(&amp;lt;genexpr&amp;gt;)&lt;br /&gt;
     7500    0.003    0.000    0.005    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:115(ensure_bytes)&lt;br /&gt;
     2000    0.003    0.000    0.005    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:386(description)&lt;br /&gt;
     5500    0.004    0.000    0.004    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:268(advance)&lt;br /&gt;
     1000    0.001    0.000    0.004    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:722(escape_string)&lt;br /&gt;
     3500    0.004    0.000    0.004    0.000 /Users/classic/dev/PyMySQL/pymysql/_socketio.py:87(readable)&lt;br /&gt;
      500    0.002    0.000    0.004    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:442(__init__)&lt;br /&gt;
     3500    0.003    0.000    0.003    0.000 {method '_checkClosed' of '_io._IOBase' objects}&lt;br /&gt;
     1000    0.001    0.000    0.003    0.000 /Users/classic/dev/PyMySQL/pymysql/converters.py:59(escape_string)&lt;br /&gt;
     1500    0.003    0.000    0.003    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1080(__init__)&lt;br /&gt;
     1500    0.001    0.000    0.003    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:92(nextset)&lt;br /&gt;
     4000    0.002    0.000    0.002    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:397(get_column_length)&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 /Users/classic/dev/PyMySQL/pymysql/converters.py:56(escape_float)&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 {method 'sub' of '_sre.SRE_Pattern' objects}&lt;br /&gt;
     3000    0.002    0.000    0.002    0.000 {_struct.pack}&lt;br /&gt;
     6000    0.002    0.000    0.002    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:62(_get_db)&lt;br /&gt;
     1500    0.001    0.000    0.002    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:80(_nextset)&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 /Users/classic/dev/PyMySQL/pymysql/util.py:9(int2byte)&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:244(fetchall)&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {min}&lt;br /&gt;
     2500    0.001    0.000    0.001    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:323(is_ok_packet)&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Users/classic/dev/PyMySQL/pymysql/converters.py:52(escape_int)&lt;br /&gt;
     2000    0.001    0.000    0.001    0.000 {method 'items' of 'dict' objects}&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {getattr}&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1093(__del__)&lt;br /&gt;
     1000    0.000    0.000    0.000    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:259(read_all)&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:67(_check_executed)&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:336(is_load_local_packet)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/contextlib.py:21(__exit__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 test.py:8(profiled)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DBAPI:  &amp;lt;module 'mysql.connector' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/__init__.pyc'&amp;gt;&lt;br /&gt;
         11674074 function calls in 19.049 seconds&lt;br /&gt;
&lt;br /&gt;
   Ordered by: cumulative time&lt;br /&gt;
&lt;br /&gt;
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;
      500    0.640    0.001   18.556    0.037 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:820(fetchall)&lt;br /&gt;
      500    0.003    0.000   11.196    0.022 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:655(get_rows)&lt;br /&gt;
      500    1.561    0.003   11.191    0.022 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:292(read_text_result)&lt;br /&gt;
   504500    3.303    0.000    7.306    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/network.py:219(recv_plain)&lt;br /&gt;
   500000    3.572    0.000    6.625    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:359(row_to_python)&lt;br /&gt;
  1009000    3.155    0.000    3.155    0.000 {method 'recv_into' of '_socket.socket' objects}&lt;br /&gt;
   500000    2.049    0.000    2.329    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:220(read_lc_string_list)&lt;br /&gt;
   500000    0.653    0.000    2.116    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:531(_STRING_to_python)&lt;br /&gt;
   502000    0.378    0.000    1.329    0.000 {method 'decode' of 'bytearray' objects}&lt;br /&gt;
   502000    0.221    0.000    0.951    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/utf_8.py:15(decode)&lt;br /&gt;
   502000    0.730    0.000    0.730    0.000 {_codecs.utf_8_decode}&lt;br /&gt;
   500000    0.514    0.000    0.514    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:413(_INT_to_python)&lt;br /&gt;
   509500    0.510    0.000    0.510    0.000 {_struct.unpack_from}&lt;br /&gt;
     1500    0.009    0.000    0.492    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:452(execute)&lt;br /&gt;
     1500    0.004    0.000    0.437    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:705(cmd_query)&lt;br /&gt;
   500000    0.367    0.000    0.367    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:405(_FLOAT_to_python)&lt;br /&gt;
   504500    0.343    0.000    0.343    0.000 {method 'extend' of 'bytearray' objects}&lt;br /&gt;
     1500    0.007    0.000    0.315    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:481(_send_cmd)&lt;br /&gt;
  2501500    0.275    0.000    0.275    0.000 {method 'append' of 'list' objects}&lt;br /&gt;
   500500    0.246    0.000    0.246    0.000 {method 'startswith' of 'bytearray' objects}&lt;br /&gt;
   524002    0.150    0.000    0.150    0.000 {isinstance}&lt;br /&gt;
     1500    0.011    0.000    0.118    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:613(_handle_result)&lt;br /&gt;
  1004500    0.112    0.000    0.112    0.000 {len}&lt;br /&gt;
   500000    0.092    0.000    0.092    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:204(description)&lt;br /&gt;
     1500    0.007    0.000    0.042    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/network.py:118(send_plain)&lt;br /&gt;
     2000    0.013    0.000    0.041    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:226(parse_column)&lt;br /&gt;
     1000    0.008    0.000    0.034    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:346(_process_params_dict)&lt;br /&gt;
     1500    0.002    0.000    0.029    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/socket.py:223(meth)&lt;br /&gt;
     1500    0.026    0.000    0.026    0.000 {method 'sendall' of '_socket.socket' objects}&lt;br /&gt;
     5000    0.007    0.000    0.014    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/catch23.py:79(struct_unpack)&lt;br /&gt;
     1000    0.001    0.000    0.013    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:562(_handle_ok)&lt;br /&gt;
    12000    0.011    0.000    0.011    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:167(read_lc_string)&lt;br /&gt;
     1000    0.004    0.000    0.010    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:199(parse_ok)&lt;br /&gt;
     3000    0.005    0.000    0.009    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:152(to_mysql)&lt;br /&gt;
     1500    0.003    0.000    0.007    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:118(make_command)&lt;br /&gt;
     3000    0.003    0.000    0.007    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:130(quote)&lt;br /&gt;
     1500    0.005    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/network.py:53(_prepare_packets)&lt;br /&gt;
     1500    0.003    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:406(_handle_result)&lt;br /&gt;
     3000    0.003    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:102(escape)&lt;br /&gt;
     1000    0.002    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:252(parse_eof)&lt;br /&gt;
      500    0.001    0.000    0.005    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:579(_handle_eof)&lt;br /&gt;
     2000    0.003    0.000    0.005    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:545(_handle_server_status)&lt;br /&gt;
     1500    0.003    0.000    0.004    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:53(int1store)&lt;br /&gt;
     3000    0.003    0.000    0.003    0.000 {method 'encode' of 'str' objects}&lt;br /&gt;
     9001    0.003    0.000    0.003    0.000 {method 'replace' of 'str' objects}&lt;br /&gt;
     1500    0.003    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:295(_reset_result)&lt;br /&gt;
     4500    0.003    0.000    0.003    0.000 {_struct.pack}&lt;br /&gt;
     2500    0.003    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:296(read_lc_int)&lt;br /&gt;
      500    0.002    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:757(_handle_eof)&lt;br /&gt;
     6027    0.003    0.000    0.003    0.000 {method 'format' of 'str' objects}&lt;br /&gt;
     3000    0.002    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:1221(_set_unread_result)&lt;br /&gt;
      500    0.001    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:219(parse_column_count)&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 {repr}&lt;br /&gt;
     4527    0.002    0.000    0.002    0.000 {getattr}&lt;br /&gt;
     4000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/constants.py:34(flag_is_set)&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:383(_handle_noresultset)&lt;br /&gt;
     4000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:1233(_get_unread_result)&lt;br /&gt;
     2001    0.001    0.000    0.001    0.000 {method 'items' of 'dict' objects}&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:307(_have_unread_result)&lt;br /&gt;
     3000    0.001    0.000    0.001    0.000 {method 'lower' of 'str' objects}&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 {range}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:173(_str_to_mysql)&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:169(_float_to_mysql)&lt;br /&gt;
     1000    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:161(_int_to_mysql)&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:1313(_get_getwarnings)&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:200(reset)&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:397(_handle_resultset)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/__init__.py:71(search_function)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/ascii.py:41(getregentry)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/contextlib.py:21(__exit__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/__init__.py:49(normalize_encoding)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 test.py:8(profiled)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/codecs.py:77(__new__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {__import__}&lt;br /&gt;
        3    0.000    0.000    0.000    0.000 {method 'get' of 'dict' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'split' of 'str' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {built-in method __new__ of type object at 0x1001534e8}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'translate' of 'str' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'join' of 'str' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {hasattr}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DBAPI:  &amp;lt;module 'MySQLdb' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/__init__.pyc'&amp;gt;&lt;br /&gt;
         47503 function calls in 0.918 seconds&lt;br /&gt;
&lt;br /&gt;
   Ordered by: cumulative time&lt;br /&gt;
&lt;br /&gt;
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;
     1500    0.009    0.000    0.917    0.001 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:164(execute)&lt;br /&gt;
     1500    0.002    0.000    0.890    0.001 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:353(_query)&lt;br /&gt;
     1500    0.004    0.000    0.498    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:315(_do_query)&lt;br /&gt;
     1500    0.016    0.000    0.391    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:358(_post_get_result)&lt;br /&gt;
     1500    0.001    0.000    0.375    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:324(_fetch_row)&lt;br /&gt;
      500    0.374    0.001    0.374    0.001 {built-in method fetch_row}&lt;br /&gt;
     1500    0.007    0.000    0.362    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:142(_do_get_result)&lt;br /&gt;
     1500    0.002    0.000    0.351    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:351(_get_result)&lt;br /&gt;
     1500    0.349    0.000    0.349    0.000 {method 'store_result' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.131    0.000    0.131    0.000 {method 'query' of '_mysql.connection' objects}&lt;br /&gt;
     4000    0.002    0.000    0.010    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:184(&amp;lt;genexpr&amp;gt;)&lt;br /&gt;
     3000    0.002    0.000    0.008    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/connections.py:267(literal)&lt;br /&gt;
     1500    0.007    0.000    0.007    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:107(_warning_check)&lt;br /&gt;
     3000    0.002    0.000    0.006    0.000 {method 'escape' of '_mysql.connection' objects}&lt;br /&gt;
     6000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:159(_get_db)&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/converters.py:81(Float2Str)&lt;br /&gt;
     2500    0.001    0.000    0.001    0.000 {isinstance}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/connections.py:202(string_literal)&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:380(fetchall)&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 {built-in method describe}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 {method 'string_literal' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {method 'insert_id' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {method 'affected_rows' of '_mysql.connection' objects}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/converters.py:69(Thing2Str)&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 {method 'warning_count' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 {method 'info' of '_mysql.connection' objects}&lt;br /&gt;
     1000    0.000    0.000    0.000    0.000 {method 'iteritems' of 'dict' objects}&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:103(_check_executed)&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 {len}&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 {built-in method field_flags}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/contextlib.py:21(__exit__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 test.py:8(profiled)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DBAPI:  &amp;lt;module 'pymysql' from '/Users/classic/dev/PyMySQL/pymysql/__init__.pyc'&amp;gt;, total seconds 10.046012&lt;br /&gt;
DBAPI:  &amp;lt;module 'mysql.connector' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/__init__.pyc'&amp;gt;, total seconds 15.403260&lt;br /&gt;
DBAPI:  &amp;lt;module 'MySQLdb' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/__init__.pyc'&amp;gt;, total seconds 1.028737&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mike Bayer</name></author>	</entry>

	<entry>
		<id>https://wiki.openstack.org/w/index.php?title=PyMySQL_evaluation&amp;diff=72558</id>
		<title>PyMySQL evaluation</title>
		<link rel="alternate" type="text/html" href="https://wiki.openstack.org/w/index.php?title=PyMySQL_evaluation&amp;diff=72558"/>
				<updated>2015-01-28T20:27:50Z</updated>
		
		<summary type="html">&lt;p&gt;Mike Bayer: /* MySQL DB Drivers Comparison */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= PyMySQL Evaluation =&lt;br /&gt;
&lt;br /&gt;
This page will capture issues related to Openstack moving to the [https://github.com/PyMySQL/PyMySQL/ PyMySQL driver] for MySQL/MariaDB dbapi access.&lt;br /&gt;
&lt;br /&gt;
== Rationale ==&lt;br /&gt;
&lt;br /&gt;
While the [https://github.com/farcepest/MySQLdb1 MySQL-Python] driver is a very mature and stable driver, it does not provide compatibility with either Python 3 or with [http://eventlet.net/doc/patching.html eventlet monkeypatching] (MySQL-Python  [https://github.com/eventlet/eventlet/blob/v0.16.1/eventlet/patcher.py#L225-L237 can be monkeypatched] with eventlet, but this feature disabled by default and missed in documentation). So OpenStack's usage of MySQL-Python, combined with the fact that concurrency is provided by eventlet, means that we currently have fully serialized database access within a single process, that is, only one database command occurs at a time within an Openstack Python process.&lt;br /&gt;
&lt;br /&gt;
== Drivers Under Consideration ==&lt;br /&gt;
&lt;br /&gt;
The two drivers that are known to provide eventlet-monkeypatch compatibility are MySQL-Connector and PyMySQL, as they are written in pure Python.   The two other well-known drivers for MySQL are MySQL-Python and OurSQL, both of which are written in C and offer no explicit support for eventlet or async.  They can reportedly be built to support gevent using a system called [https://github.com/CMGS/greenify Greenify], but the maturity and/or stabiltiy of this system is unknown.&lt;br /&gt;
&lt;br /&gt;
A comparison at http://www.diamondtin.com/2014/sqlalchemy-gevent-mysql-python-drivers-comparison/ illustrates performance metrics observed with all four of these drivers, with MySQL-python built both without and with the &amp;quot;greenify&amp;quot; system.&lt;br /&gt;
&lt;br /&gt;
A summary of the status of all four drivers is as follows.&lt;br /&gt;
&lt;br /&gt;
=== MySQL-Python ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/farcepest/MySQLdb1 MySQL-Python] is the most widely used Python driver.  However, it does not support Python 3, and it does not support async systems unless used within a thread pool.   While a system known as Greenify can potentially resolve the latter situation, the Python 3 limitation is still a deal-breaker - pull request with Python 3 support [https://github.com/farcepest/MySQLdb1/pull/62 was proposed] to MySQL-python in April 2014 and still not merged.&lt;br /&gt;
&lt;br /&gt;
=== mysqlclient ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/PyMySQL/mysqlclient-python mysqlclient] is a fork of MySQL-python. It add Python 3.3 support and merges some pull requests.&lt;br /&gt;
&lt;br /&gt;
=== OurSQL ===&lt;br /&gt;
&lt;br /&gt;
[https://pythonhosted.org/oursql/ OurSQL] is an alternative MySQL driver that is also written in C.  It features fast performance and uses a different execution model than MySQL-python, using prepared statements.    OurSQL does have a Python 3 port hosted on [https://launchpad.net/oursql/py3k/ launchpad] but is not integrated with OurSQL itself nor is it published on Pypi.     Because it is written in C, it does not offer compatibility with async systems including eventlet-style monkeypatching.&lt;br /&gt;
&lt;br /&gt;
Besides the shaky Python 3 support and lack of eventlet support, OurSQL is also not maintained, seeing its last master commit on [https://code.launchpad.net/~jerub/oursql/master 2012-06-05].&lt;br /&gt;
&lt;br /&gt;
=== MySQL-Connector-Python ===&lt;br /&gt;
&lt;br /&gt;
[https://pypi.python.org/pypi/mysql-connector-python MySQL-connector-Python] is a pure Python MySQL driver, and is now released under the auspices of the MySQL project itself, as owned by Oracle.  MySQL-connector-Python supports both Python 3 as well as eventlet monkeypatching, and is well maintained.  It is endorsed by Oracle as the official Oracle-supported driver for MySQL, so to that extent, it is in most ways Openstack's first choice in driver.  However, Oracle refuses to publish MySQL-connector-Python on Pypi, which is critical to the Openstack infrastructure.  Repeated attempts to communicate with Oracle in order to resolve this issue have not made any progress.    Therefore, for this one unfortunate reason, MySQL-connector-Python will not have a place in the Openstack ecosystem unless this issue is resolved.&lt;br /&gt;
&lt;br /&gt;
=== PyMySQL ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/PyMySQL/PyMySQL PyMySQL] is a pure Python MySQL&lt;br /&gt;
driver, first written as a rough port of the MySQL-Python driver.&lt;br /&gt;
PyMySQL meets all of Openstack's criterion for a driver: it is fully&lt;br /&gt;
open source, hosted on Github, released on Pypi, is actively&lt;br /&gt;
maintained, is written in pure Python so is eventlet-monkeypatch&lt;br /&gt;
compatible, and is fully Python 3 compatible.&lt;br /&gt;
&lt;br /&gt;
As this document is named &amp;quot;PyMySQL Evaluation&amp;quot;, it should be apparent that&lt;br /&gt;
this is the driver Openstack is currently leaning towards; because it is the only&lt;br /&gt;
one that meets all criteria fully, it is already most likely the &amp;quot;winner&amp;quot;.  However, it does&lt;br /&gt;
have some minor code quality issues which hopefully can be addressed in some&lt;br /&gt;
way; the section below titled &amp;quot;PyMySQL Code Review&amp;quot; will summarize the&lt;br /&gt;
current state of the code.&lt;br /&gt;
&lt;br /&gt;
===MySQL DB Drivers Comparison===&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Project !! PyPi hosted !! Eventlet friendly !! Python 3 compatibility !! Maturity and/or stability  !! Comment&lt;br /&gt;
|-&lt;br /&gt;
| MySQL-Python || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: orange;&amp;quot; | Partial || style=&amp;quot;background-color: red;&amp;quot; | No  || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || Can be monkeypatched by eventlet, but only to enable thread pooling&lt;br /&gt;
|-&lt;br /&gt;
| mysqlclient || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: orange;&amp;quot; | Partial || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes  || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || MySQL-python fork, so should be similar to it&lt;br /&gt;
|-&lt;br /&gt;
| OurSQL || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: red;&amp;quot; | No || style=&amp;quot;background-color: orange;&amp;quot; | Yes, but not Pypi hosted  || style=&amp;quot;background-color: red;&amp;quot; | development halted fairly early on || Has not seen commits/releases in two years&lt;br /&gt;
|-&lt;br /&gt;
| MySQL-Connector-Python || style=&amp;quot;background-color: red;&amp;quot; | No || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes  || style=&amp;quot;background-color: orange;&amp;quot; | The driver is still fairly new || The official Oracle-supported driver for MySQL&lt;br /&gt;
|-&lt;br /&gt;
| PyMySQL || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes  || style=&amp;quot;background-color: orange;&amp;quot; | Yes, however see notes below. || Actively maintained and popular.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== PyMySQL Code Review ==&lt;br /&gt;
&lt;br /&gt;
PyMySQL started roughly as a pure Python port of MySQL-Python.   The sections below will&lt;br /&gt;
detail various aspects of the driver and the status of the code.   Overall, the general&lt;br /&gt;
tone of PyMySQL is one of code that was written in the spirit of pragmatism and immediate&lt;br /&gt;
need; it is straightforward, relatively free of serious antipatterns and performs&lt;br /&gt;
its task in a matter-of-fact way.  However,&lt;br /&gt;
it lacks polish and completeness in many areas that would normally be in better&lt;br /&gt;
condition for a more mature project.  These areas are all highly fixable, and it is&lt;br /&gt;
hoped that these items can be shared with the current PyMySQL developers, through&lt;br /&gt;
a combination of this document itself as well as new issues and pull requests&lt;br /&gt;
on the PyMySQL tracker, so that this fairly promising library can be pushed&lt;br /&gt;
to the next level.   This would require that the developers are amenable to&lt;br /&gt;
these improvements.&lt;br /&gt;
&lt;br /&gt;
=== Coding Style / Pep8 ===&lt;br /&gt;
&lt;br /&gt;
PyMySQL's coding style is pretty good.  The majority of it passes all&lt;br /&gt;
flake8 tests, save for a few dozen whitespace issues and&lt;br /&gt;
some long lines here and there within the core&lt;br /&gt;
modules, and moreso within the tests which appear to be a bit more&lt;br /&gt;
crufty than the core modules.  A full run of flake8 produces only 128&lt;br /&gt;
errors, and with a default run of autopep8 against E1,E2,E3, we can&lt;br /&gt;
fix all of them in one pass except for 38 remaining long line warnings.&lt;br /&gt;
The code layout is mostly idiomatic and reasonable.&lt;br /&gt;
&lt;br /&gt;
The codebase is probably short on docstrings.  Those methods that are&lt;br /&gt;
part of the public DBAPI do have docstrings, but tend to be extremely&lt;br /&gt;
terse (example, docstring for cursor.execute(): &amp;quot;'Execute a query'&amp;quot;).&lt;br /&gt;
Other methods that aren't public tend to not have any docstrings, but&lt;br /&gt;
the codebase is not at all hard to read and the purpose of methods and&lt;br /&gt;
functions is pretty easy to discern just by their names and implementations,&lt;br /&gt;
however those who seek inline documentation as a means to understand a codebase&lt;br /&gt;
will be disappointed.   The terseness of public API docstrings is more of&lt;br /&gt;
an issue in terms of the overall lack of documentation for PyMySQL,&lt;br /&gt;
see the section &amp;quot;Documentation&amp;quot; below.&lt;br /&gt;
&lt;br /&gt;
=== Test Coverage ===&lt;br /&gt;
&lt;br /&gt;
The test coverage for PyMySQL is definitely lacking.   From a visual&lt;br /&gt;
inspection alone, it's apparent that of the dozen or so test modules,&lt;br /&gt;
most of them have less then ten tests each and the modules are very short.&lt;br /&gt;
For a full test run, there's only 126 tests; contrast this to&lt;br /&gt;
MySQL-Connector-Python which has 531 tests.&lt;br /&gt;
&lt;br /&gt;
Additionally, all of the tests are live round-trip tests against a&lt;br /&gt;
real database.  There are no non-live unit tests of any kind,&lt;br /&gt;
leaving codepaths that are not easily exercised&lt;br /&gt;
on a generic MySQL database out in the cold.  This means that features designed&lt;br /&gt;
for particular versions of MySQL, particular datatypes, particular error&lt;br /&gt;
conditions, and particular kinds of message packets that aren't created for&lt;br /&gt;
real are simply not tested.&lt;br /&gt;
&lt;br /&gt;
The lack of tests is apparent when run with coverage.  The test suite itself&lt;br /&gt;
does not appear to have any signs that it is normally run with coverage&lt;br /&gt;
turned on, with no directives in tox.ini or similar.   With coverage,&lt;br /&gt;
the overall coverage is 85%, but the modules most lacking in coverage&lt;br /&gt;
are also the most critical to PyMySQL's core functionality,&lt;br /&gt;
connections.py, converters.py and cursors.py:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Name                                                              Stmts   Miss  Cover&lt;br /&gt;
-------------------------------------------------------------------------------------&lt;br /&gt;
pymysql/__init__                                                     51      3    94%&lt;br /&gt;
pymysql/_compat                                                      14      4    71%&lt;br /&gt;
pymysql/_socketio                                                    72     30    58%&lt;br /&gt;
pymysql/charset                                                     228      3    99%&lt;br /&gt;
pymysql/connections                                                 827    188    77%&lt;br /&gt;
pymysql/constants/CLIENT                                             18      0   100%&lt;br /&gt;
pymysql/constants/COMMAND                                            32      0   100%&lt;br /&gt;
pymysql/constants/ER                                                471      0   100%&lt;br /&gt;
pymysql/constants/FIELD_TYPE                                         29      0   100%&lt;br /&gt;
pymysql/constants/FLAG                                               15      0   100%&lt;br /&gt;
pymysql/constants/SERVER_STATUS                                      10      0   100%&lt;br /&gt;
pymysql/constants/__init__                                            0      0   100%&lt;br /&gt;
pymysql/converters                                                  148     51    66%&lt;br /&gt;
pymysql/cursors                                                     273     37    86%&lt;br /&gt;
pymysql/err                                                          40      1    98%&lt;br /&gt;
pymysql/tests/__init__                                               14      5    64%&lt;br /&gt;
pymysql/tests/base                                                   25      2    92%&lt;br /&gt;
pymysql/tests/test_DictCursor                                        73      2    97%&lt;br /&gt;
pymysql/tests/test_SSCursor                                          56      6    89%&lt;br /&gt;
pymysql/tests/test_basic                                            186      4    98%&lt;br /&gt;
pymysql/tests/test_connection                                        57      8    86%&lt;br /&gt;
pymysql/tests/test_example                                           17      2    88%&lt;br /&gt;
pymysql/tests/test_issues                                           300     48    84%&lt;br /&gt;
pymysql/tests/test_load_local                                        39      2    95%&lt;br /&gt;
pymysql/tests/test_nextset                                           43      4    91%&lt;br /&gt;
pymysql/tests/thirdparty/__init__                                     7      5    29%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/__init__                        6      2    67%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/capabilities                  196     23    88%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/dbapi20                       423    121    71%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_capabilities      73      6    92%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_dbapi20          100      2    98%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_nonstandard       57      3    95%&lt;br /&gt;
pymysql/times                                                        12      0   100%&lt;br /&gt;
pymysql/util                                                         14      7    50%&lt;br /&gt;
-------------------------------------------------------------------------------------&lt;br /&gt;
TOTAL                                                              3926    569    86%&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The lack of coverage in converters.py refers to functions related to handling&lt;br /&gt;
incoming bound values as well as outgoing result values.    Key data escaping&lt;br /&gt;
features such as escaping of dictionaries, sets, some unicode objects and&lt;br /&gt;
byte objects as well as many pathways into date and time-processing functions are not&lt;br /&gt;
covered.&lt;br /&gt;
&lt;br /&gt;
In connections.py, uncovered features include various SSL and&lt;br /&gt;
very legacy (e.g. version 3.23) MySQL features, but also some protocol parsing&lt;br /&gt;
features.&lt;br /&gt;
&lt;br /&gt;
In cursors.py, there is coverage for an elaborate and mostly undocumented&lt;br /&gt;
performance-related feature that rewrites an INSERT statement to have an&lt;br /&gt;
extended VALUES clause (_do_execute_many), however it fails to cover the very&lt;br /&gt;
likely case where the function will need to break the input set into&lt;br /&gt;
multiple chunks:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;gt;         for arg in args:&lt;br /&gt;
&amp;gt;             v = values % escape(arg, conn)&lt;br /&gt;
&amp;gt;             if isinstance(v, text_type):&lt;br /&gt;
&amp;gt;                 v = v.encode(encoding)&lt;br /&gt;
&amp;gt;             if len(sql) + len(v) + 1 &amp;gt; max_stmt_length:&lt;br /&gt;
!                 rows += self.execute(sql)&lt;br /&gt;
!                 sql = bytearray(prefix)&lt;br /&gt;
&amp;gt;             else:&lt;br /&gt;
&amp;gt;                 sql += b','&lt;br /&gt;
&amp;gt;             sql += v&lt;br /&gt;
&amp;gt;         rows += self.execute(sql)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The most prominent missing coverage in cursors.py seems to be&lt;br /&gt;
for its &amp;quot;scrollable cursor&amp;quot; support.&lt;br /&gt;
&lt;br /&gt;
Beyond the lack of completeness in testing, many of the tests themselves&lt;br /&gt;
are written in an expedient &amp;quot;one giant test&amp;quot; style where dozens of&lt;br /&gt;
assertions and individual behaviors are lumped into one big test&lt;br /&gt;
case (see test_SSCursor.py, test_basic.py-&amp;gt;test_datatypes for examples).&lt;br /&gt;
Test cases like these are difficult to work with when debugging&lt;br /&gt;
regressions as well as when tests for new features need to be added, and ideally&lt;br /&gt;
these should be broken out into clean single-feature tests with consistent&lt;br /&gt;
fixtures.&lt;br /&gt;
&lt;br /&gt;
There is also a suite called &amp;quot;test_issues&amp;quot;, which intends to&lt;br /&gt;
accumulate tests against specific issues that have been reported. This&lt;br /&gt;
isn't a bad idea, though it would be nice to see an effort made into&lt;br /&gt;
categorizing these tests into the actual features they are testing,&lt;br /&gt;
rather than a meaningless list of issue numbers, which could then form&lt;br /&gt;
as the basis for new suites centered around those areas of&lt;br /&gt;
functionality.  The issues reported should be used as the inspiration&lt;br /&gt;
for improvement of the test suite, rather than just another line-item&lt;br /&gt;
to be filed away.&lt;br /&gt;
&lt;br /&gt;
=== Library Documentation ===&lt;br /&gt;
&lt;br /&gt;
As the Python DBAPI is already a well-documented API, and the users of&lt;br /&gt;
PyMySQL are typically coming from MySQL-Python which is documented&lt;br /&gt;
to some degree and for which the use contract is widely known, it seems&lt;br /&gt;
unlikely that users of PyMySQL are eager for comprehensive documentation&lt;br /&gt;
specific to this library.&lt;br /&gt;
&lt;br /&gt;
That being said, PyMySQL does not appear to have any real library&lt;br /&gt;
documentation at all.  There's a README which&lt;br /&gt;
essentially refers to pep-249, an example.py in the root which shows a&lt;br /&gt;
very basic connection / round trip, and that's it. In&lt;br /&gt;
order to know anything about the API, specific parameters, optimization&lt;br /&gt;
strategies, behaviors, etc., one either has to go off of the MySQL-Python documentation and assume&lt;br /&gt;
PyMySQL also features the same parameter, or read the source code.&lt;br /&gt;
&lt;br /&gt;
PyMySQL probably hasn't had much urgent need for real documentation thus&lt;br /&gt;
far.  However, relying upon being a port of MySQL-Python is fast becoming&lt;br /&gt;
something that can no longer be relied upon, as MySQL-Python's development&lt;br /&gt;
is quite stalled, and PyMySQL should aspire to move into the future&lt;br /&gt;
of MySQL and Python with its own featureset and behaviors.  To that end, it would be a &lt;br /&gt;
great idea if it at least made the start&lt;br /&gt;
of a rudimentary Sphinx build including autodoc for module documentation&lt;br /&gt;
which could then be published up to RTD.   This would be very easy to get&lt;br /&gt;
started, and once present, new documentation sections can be added&lt;br /&gt;
iteratively.&lt;br /&gt;
&lt;br /&gt;
=== Architecture and Performance ===&lt;br /&gt;
&lt;br /&gt;
As stated before, PyMySQL has a really matter-of-fact and straightforward&lt;br /&gt;
implementation.   The code is very readable and it's easy to discern even&lt;br /&gt;
unusual features such as the INSERT..VALUES rewriting.  As far as the design,&lt;br /&gt;
there are lots of areas where performance suffers a bit at the Python level;&lt;br /&gt;
these areas could be greatly improved very easily with a little bit of&lt;br /&gt;
attention.&lt;br /&gt;
&lt;br /&gt;
As we are dealing with a driver that is communicating with a low level&lt;br /&gt;
protocol over a socket, it's critical that sending and receiving messages&lt;br /&gt;
is done as efficiently as possible.  The mechanisms used in PyMySQL rely&lt;br /&gt;
heavily on standard Python objects, without the use of __slots__; this means&lt;br /&gt;
that for individual messages on a socket, we are creating a heavyweight object,&lt;br /&gt;
calling its __init__() method and a creating a new __dict__ each time;&lt;br /&gt;
the most common object, OKPacketWrapper(), already wraps a MysqlPacket()&lt;br /&gt;
object, so we're doing two __init__()s in this common case.  OKPacketWrapper&lt;br /&gt;
also implements a `__getattr__` scheme&lt;br /&gt;
to proxy attribute access from itself to the internal MysqlPacket; this is a&lt;br /&gt;
an inefficient mode of operation in Python as it means a lookup must first&lt;br /&gt;
fail on the OKPacketWrapper before it invokes the `__getattr__()` method&lt;br /&gt;
and does another attribute lookup.   A reorg of the packet classes to produce&lt;br /&gt;
far fewer objects for messages as well as to use __slots__ would be&lt;br /&gt;
recommended.&lt;br /&gt;
&lt;br /&gt;
There are also lots of areas where function call overhead is unnecessarily&lt;br /&gt;
high due to the organization of calls.  For example, when calling upon&lt;br /&gt;
cursor.execute(), for parameters passed as a dictionary under Python 2K,&lt;br /&gt;
the isinstance() builtin function will be called on the incoming arguments&lt;br /&gt;
four times at a minimum, with two of each call against the exact same&lt;br /&gt;
criteria.  Additional logic will then call isinstance() on every key of&lt;br /&gt;
the dictionary as well as at least twice on every value within the dictionary,&lt;br /&gt;
in order to determine if the value needs to be converted to bytes as well&lt;br /&gt;
as for some top-level escaping logic checks; the escaping logic then&lt;br /&gt;
goes further with more checks for type() to match the object to a final&lt;br /&gt;
converter function.   While the flow of this logic does appear to be&lt;br /&gt;
reasonable and straightforward, it isn't ideal for a low-level database&lt;br /&gt;
driver library where speed is a high priority; the various wrappers and&lt;br /&gt;
processors can easily be reorganized here to reduce the number of isinstance()&lt;br /&gt;
and type() calls significantly, which would save a lot on function call&lt;br /&gt;
overhead.  It is likely there are many other areas in the codebase where&lt;br /&gt;
similar reductions in call counts can be made without too much disruption.&lt;br /&gt;
&lt;br /&gt;
In Appendix A, a short performance suite is illustrated.  This&lt;br /&gt;
is run against PyMySQL, Mysql-Connector-Python, and MySQL-Python.&lt;br /&gt;
PyMySQL does in fact demonstrate the&lt;br /&gt;
highest call-count behavior, logging 26,851,003 function calls&lt;br /&gt;
compared to MySQL-connector's 11,674,074 for the same operations.  To PyMySQL's&lt;br /&gt;
credit, it somehow completes the test 25-50% faster than MySQL-connector&lt;br /&gt;
despite having more than twice as many function calls; this probably indicates&lt;br /&gt;
the use of something very slow within MySQL-connector, such as catching&lt;br /&gt;
an exception within a tight loop.  However, the real bar we're comparing to&lt;br /&gt;
is MySQL-Python, which is written&lt;br /&gt;
in C and does the whole test with only 47,503 Python function calls; while&lt;br /&gt;
PyMySQL and MySQL-connector-Python compete for time within the 10-15 second&lt;br /&gt;
range without profiling, MySQL-Python completes the whole test in **1.03 seconds**, a 1000%&lt;br /&gt;
improvement over PyMySQL.   This speed difference is not PyMySQL's fault,&lt;br /&gt;
as pure Python is known to be extremely slow compared to C code.&lt;br /&gt;
However, there's a lot of easy wins in PyMySQL's codebase where the function&lt;br /&gt;
call count could probably be reduced dramatically to be even better than that&lt;br /&gt;
of MySQL-connector-Python; ideally, the driver would be able to run this&lt;br /&gt;
test possibly 5-6x slower than MySQL-Python rather than 10x.&lt;br /&gt;
&lt;br /&gt;
=== History Documentation ===&lt;br /&gt;
&lt;br /&gt;
PyMySQL includes a CHANGELOG file in its root which describes the bug fixes&lt;br /&gt;
and features of each release.  The construction of this file is definitely&lt;br /&gt;
within the &amp;quot;expedient&amp;quot; style noted for PyMySQL overall, and for it to be&lt;br /&gt;
genuinely useful for enterprise-level software, it would need to be&lt;br /&gt;
much more complete in its detail.    Each bug fix mentioned is only referred to&lt;br /&gt;
tersely without any link to a pull request or bug report.  While some can&lt;br /&gt;
be linked to a specific change through detective work&lt;br /&gt;
(&amp;quot;Cursor.fetchall() and .fetchmany now return list, not tuple&amp;quot;), it would&lt;br /&gt;
be very difficult for many&lt;br /&gt;
(&amp;quot;Fixed BIT type handling&amp;quot;, &amp;quot;Fixed GC errors&amp;quot; - what was the issue?)&lt;br /&gt;
and in many cases impossible (&amp;quot;Improved Py3k support&amp;quot;).  The file also&lt;br /&gt;
includes no dates so one has to look at the git tags to figure this&lt;br /&gt;
part out (fortunately, releases to seem to be git tagged at least).&lt;br /&gt;
For a user trying to ascertain if particular issues are&lt;br /&gt;
fixed or if behaviors have changed, this file is at best highly frustrating.&lt;br /&gt;
&lt;br /&gt;
A listing of release dates illustrates an average of two releases per year,&lt;br /&gt;
excluding 2012 when PyMySQL was seeking a new maintainer:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
2014-12-02 12:05:10 -0200  (tag: pymysql-0.6.3)&lt;br /&gt;
2014-04-21 14:28:47 -0300  (tag: pymysql-0.6.2)&lt;br /&gt;
2013-10-11 13:36:56 -0300  (tag: pymysql-0.6.1)&lt;br /&gt;
2013-10-04 14:25:14 -0300  (tag: pymysql-0.6)&lt;br /&gt;
2011-11-08 10:27:41 -0800  (tag: pymysql-0.5)&lt;br /&gt;
2010-12-27 17:28:16 +0000  (tag: pymysql-0.4)&lt;br /&gt;
2010-09-03 00:55:29 +0000  (tag: pymysql-0.3)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Community Involvement ===&lt;br /&gt;
&lt;br /&gt;
This is an area where PyMySQL seems to shine, and might even be an advantage&lt;br /&gt;
as compared to MySQL-Connector-Python, which remains under the cloak&lt;br /&gt;
of MySQL's vastly overstuffed bugtracker under the ownership of&lt;br /&gt;
the famously-opaque Oracle.   For a small, low key library without&lt;br /&gt;
any real documentation and only 126 tests, it features 33 contributors on&lt;br /&gt;
Github and is up to pull request #285, which is quite high.  Because&lt;br /&gt;
PyMySQL is small and straightforward, it does lend itself to contribution&lt;br /&gt;
and seems to do very well in this area.   The developers so far have been&lt;br /&gt;
responsive to issues and pull requests made by the author.&lt;br /&gt;
&lt;br /&gt;
Based on github contributor graphs it appears that PyMySQL had a lull&lt;br /&gt;
in 2012 but was picked up again afterwards in 2013.  As of 2012, its most&lt;br /&gt;
prominent developer Pete Hunt stopped development, and on July 25, 2013&lt;br /&gt;
added a note to the README that the project was looking for a new maintainer.&lt;br /&gt;
As of 2013 it was picked up by the current most prominent developers&lt;br /&gt;
Marcel Rodrigues (github username lecram) and INADA Naoki&lt;br /&gt;
(github username methane).&lt;br /&gt;
&lt;br /&gt;
Most issues that have been noted thus far within the areas of testing,&lt;br /&gt;
documentation and architecture are all very fixable; if the Openstack community&lt;br /&gt;
can provide resources to address some of these areas, and if the PyMySQL&lt;br /&gt;
developers are reasonably open to accepting new changes and producing new&lt;br /&gt;
releases in a timely fashion, the PyMySQL driver&lt;br /&gt;
could rapidly become a fully formidable contender in the MySQL driver field.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Appendix A - Performance Tests ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
import cProfile&lt;br /&gt;
import StringIO&lt;br /&gt;
import pstats&lt;br /&gt;
import contextlib&lt;br /&gt;
import time&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextlib.contextmanager&lt;br /&gt;
def profiled(dbapi):&lt;br /&gt;
    pr = cProfile.Profile()&lt;br /&gt;
    pr.enable()&lt;br /&gt;
    yield&lt;br /&gt;
    pr.disable()&lt;br /&gt;
    s = StringIO.StringIO()&lt;br /&gt;
    ps = pstats.Stats(pr, stream=s).sort_stats('cumulative')&lt;br /&gt;
    ps.print_stats()&lt;br /&gt;
    print &amp;quot;DBAPI:  %s&amp;quot; % dbapi&lt;br /&gt;
    print s.getvalue()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextlib.contextmanager&lt;br /&gt;
def timeonly(dbapi):&lt;br /&gt;
    now = time.time()&lt;br /&gt;
    try:&lt;br /&gt;
        yield&lt;br /&gt;
    finally:&lt;br /&gt;
        total = time.time() - now&lt;br /&gt;
        print &amp;quot;DBAPI:  %s, total seconds %f&amp;quot; % (dbapi, total)&lt;br /&gt;
&lt;br /&gt;
import MySQLdb&lt;br /&gt;
import pymysql&lt;br /&gt;
from mysql import connector as mysqlconnector&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def go(dbapi, ctx):&lt;br /&gt;
    conn = dbapi.connect(&lt;br /&gt;
        user='scott', passwd='tiger', host='localhost', db='test')&lt;br /&gt;
    cursor = conn.cursor()&lt;br /&gt;
    cursor.execute(&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
CREATE TABLE IF NOT EXISTS test_things (&lt;br /&gt;
    x INTEGER,&lt;br /&gt;
    y VARCHAR(255),&lt;br /&gt;
    z FLOAT&lt;br /&gt;
) engine=InnoDB&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;)&lt;br /&gt;
    cursor.execute(&amp;quot;DELETE from test_things&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    with ctx(dbapi):&lt;br /&gt;
        for row in xrange(1000):&lt;br /&gt;
            cursor.execute(&lt;br /&gt;
                &amp;quot;INSERT INTO test_things (x, y, z) &amp;quot;&lt;br /&gt;
                &amp;quot;VALUES (%(x)s, %(y)s, %(z)s)&amp;quot;,&lt;br /&gt;
                {&amp;quot;x&amp;quot;: row, &amp;quot;y&amp;quot;: &amp;quot;row number %d&amp;quot; % row,&lt;br /&gt;
                 &amp;quot;z&amp;quot;: row * 4.57292, }&lt;br /&gt;
            )&lt;br /&gt;
&lt;br /&gt;
        for x in xrange(500):&lt;br /&gt;
            cursor.execute(&lt;br /&gt;
                &amp;quot;select * from test_things&amp;quot;)&lt;br /&gt;
            for row in cursor.fetchall():&lt;br /&gt;
                row[0], row[1], row[2]&lt;br /&gt;
&lt;br /&gt;
    cursor.close()&lt;br /&gt;
    conn.close()&lt;br /&gt;
&lt;br /&gt;
go(pymysql, profiled)&lt;br /&gt;
go(mysqlconnector, profiled)&lt;br /&gt;
go(MySQLdb, profiled)&lt;br /&gt;
&lt;br /&gt;
go(pymysql, timeonly)&lt;br /&gt;
go(mysqlconnector, timeonly)&lt;br /&gt;
go(MySQLdb, timeonly)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DBAPI:  &amp;lt;module 'pymysql' from '/Users/classic/dev/PyMySQL/pymysql/__init__.pyc'&amp;gt;&lt;br /&gt;
         26851003 function calls in 15.924 seconds&lt;br /&gt;
&lt;br /&gt;
   Ordered by: cumulative time&lt;br /&gt;
&lt;br /&gt;
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;
     1500    0.010    0.000   15.923    0.011 /Users/classic/dev/PyMySQL/pymysql/cursors.py:105(execute)&lt;br /&gt;
     1500    0.003    0.000   15.883    0.011 /Users/classic/dev/PyMySQL/pymysql/cursors.py:269(_query)&lt;br /&gt;
     1500    0.004    0.000   15.859    0.011 /Users/classic/dev/PyMySQL/pymysql/connections.py:746(query)&lt;br /&gt;
     1500    0.003    0.000   15.819    0.011 /Users/classic/dev/PyMySQL/pymysql/connections.py:892(_read_query_result)&lt;br /&gt;
     1500    0.004    0.000   15.812    0.011 /Users/classic/dev/PyMySQL/pymysql/connections.py:1097(read)&lt;br /&gt;
      500    0.003    0.000   15.559    0.031 /Users/classic/dev/PyMySQL/pymysql/connections.py:1154(_read_result_packet)&lt;br /&gt;
      500    0.900    0.002   15.429    0.031 /Users/classic/dev/PyMySQL/pymysql/connections.py:1187(_read_rowdata_packet)&lt;br /&gt;
   500000    3.417    0.000    9.173    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1200(_read_row_from_packet)&lt;br /&gt;
  2012000    1.157    0.000    5.307    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:311(read_length_coded_string)&lt;br /&gt;
   504500    1.495    0.000    5.209    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:845(_read_packet)&lt;br /&gt;
  2014500    1.248    0.000    3.035    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:293(read_length_encoded_integer)&lt;br /&gt;
  3539500    2.439    0.000    2.750    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:245(read)&lt;br /&gt;
  1009000    0.779    0.000    2.307    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:870(_read_bytes)&lt;br /&gt;
  1009000    1.197    0.000    1.423    0.000 {method 'read' of '_io.BufferedReader' objects}&lt;br /&gt;
   508500    0.379    0.000    0.695    0.000 /Users/classic/dev/PyMySQL/pymysql/util.py:3(byte2int)&lt;br /&gt;
  5058000    0.465    0.000    0.465    0.000 {len}&lt;br /&gt;
   500500    0.222    0.000    0.455    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1146(_check_packet_is_eof)&lt;br /&gt;
   504500    0.205    0.000    0.375    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:342(check_error)&lt;br /&gt;
  2504000    0.275    0.000    0.275    0.000 {method 'append' of 'list' objects}&lt;br /&gt;
   501500    0.182    0.000    0.230    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:326(is_eof_packet)&lt;br /&gt;
     3500    0.009    0.000    0.226    0.000 /Users/classic/dev/PyMySQL/pymysql/_socketio.py:45(readinto)&lt;br /&gt;
     3500    0.207    0.000    0.207    0.000 {method 'recv_into' of '_socket.socket' objects}&lt;br /&gt;
  1022000    0.203    0.000    0.203    0.000 {_struct.unpack}&lt;br /&gt;
   526000    0.200    0.000    0.200    0.000 {isinstance}&lt;br /&gt;
  1501000    0.195    0.000    0.195    0.000 {method 'get' of 'dict' objects}&lt;br /&gt;
   504500    0.176    0.000    0.176    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:238(__init__)&lt;br /&gt;
   504500    0.170    0.000    0.170    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:339(is_error_packet)&lt;br /&gt;
  2014500    0.167    0.000    0.167    0.000 {ord}&lt;br /&gt;
      500    0.006    0.000    0.125    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1224(_get_descriptions)&lt;br /&gt;
     2000    0.003    0.000    0.087    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:361(__init__)&lt;br /&gt;
     2000    0.019    0.000    0.084    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:365(__parse_field_descriptor)&lt;br /&gt;
   500000    0.056    0.000    0.056    0.000 /Users/classic/dev/PyMySQL/pymysql/converters.py:301(through)&lt;br /&gt;
     1500    0.005    0.000    0.037    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:915(_execute_command)&lt;br /&gt;
     1500    0.003    0.000    0.027    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:886(_write_bytes)&lt;br /&gt;
     1500    0.002    0.000    0.024    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/socket.py:223(meth)&lt;br /&gt;
     1500    0.021    0.000    0.021    0.000 {method 'sendall' of '_socket.socket' objects}&lt;br /&gt;
     1500    0.020    0.000    0.020    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:276(_do_get_result)&lt;br /&gt;
     1000    0.003    0.000    0.019    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:95(_escape_args)&lt;br /&gt;
     4000    0.002    0.000    0.015    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:99(&amp;lt;genexpr&amp;gt;)&lt;br /&gt;
     1000    0.002    0.000    0.014    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1127(_read_ok_packet)&lt;br /&gt;
     3000    0.003    0.000    0.013    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:712(escape)&lt;br /&gt;
     1000    0.005    0.000    0.012    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:416(__init__)&lt;br /&gt;
     8000    0.011    0.000    0.011    0.000 {method 'decode' of 'str' objects}&lt;br /&gt;
     3500    0.003    0.000    0.007    0.000 {method '_checkReadable' of '_io._IOBase' objects}&lt;br /&gt;
     2000    0.003    0.000    0.006    0.000 /Users/classic/dev/PyMySQL/pymysql/converters.py:19(escape_item)&lt;br /&gt;
     4000    0.002    0.000    0.006    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:126(&amp;lt;genexpr&amp;gt;)&lt;br /&gt;
     7500    0.003    0.000    0.005    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:115(ensure_bytes)&lt;br /&gt;
     2000    0.003    0.000    0.005    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:386(description)&lt;br /&gt;
     5500    0.004    0.000    0.004    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:268(advance)&lt;br /&gt;
     1000    0.001    0.000    0.004    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:722(escape_string)&lt;br /&gt;
     3500    0.004    0.000    0.004    0.000 /Users/classic/dev/PyMySQL/pymysql/_socketio.py:87(readable)&lt;br /&gt;
      500    0.002    0.000    0.004    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:442(__init__)&lt;br /&gt;
     3500    0.003    0.000    0.003    0.000 {method '_checkClosed' of '_io._IOBase' objects}&lt;br /&gt;
     1000    0.001    0.000    0.003    0.000 /Users/classic/dev/PyMySQL/pymysql/converters.py:59(escape_string)&lt;br /&gt;
     1500    0.003    0.000    0.003    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1080(__init__)&lt;br /&gt;
     1500    0.001    0.000    0.003    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:92(nextset)&lt;br /&gt;
     4000    0.002    0.000    0.002    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:397(get_column_length)&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 /Users/classic/dev/PyMySQL/pymysql/converters.py:56(escape_float)&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 {method 'sub' of '_sre.SRE_Pattern' objects}&lt;br /&gt;
     3000    0.002    0.000    0.002    0.000 {_struct.pack}&lt;br /&gt;
     6000    0.002    0.000    0.002    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:62(_get_db)&lt;br /&gt;
     1500    0.001    0.000    0.002    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:80(_nextset)&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 /Users/classic/dev/PyMySQL/pymysql/util.py:9(int2byte)&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:244(fetchall)&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {min}&lt;br /&gt;
     2500    0.001    0.000    0.001    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:323(is_ok_packet)&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Users/classic/dev/PyMySQL/pymysql/converters.py:52(escape_int)&lt;br /&gt;
     2000    0.001    0.000    0.001    0.000 {method 'items' of 'dict' objects}&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {getattr}&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1093(__del__)&lt;br /&gt;
     1000    0.000    0.000    0.000    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:259(read_all)&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:67(_check_executed)&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:336(is_load_local_packet)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/contextlib.py:21(__exit__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 test.py:8(profiled)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DBAPI:  &amp;lt;module 'mysql.connector' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/__init__.pyc'&amp;gt;&lt;br /&gt;
         11674074 function calls in 19.049 seconds&lt;br /&gt;
&lt;br /&gt;
   Ordered by: cumulative time&lt;br /&gt;
&lt;br /&gt;
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;
      500    0.640    0.001   18.556    0.037 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:820(fetchall)&lt;br /&gt;
      500    0.003    0.000   11.196    0.022 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:655(get_rows)&lt;br /&gt;
      500    1.561    0.003   11.191    0.022 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:292(read_text_result)&lt;br /&gt;
   504500    3.303    0.000    7.306    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/network.py:219(recv_plain)&lt;br /&gt;
   500000    3.572    0.000    6.625    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:359(row_to_python)&lt;br /&gt;
  1009000    3.155    0.000    3.155    0.000 {method 'recv_into' of '_socket.socket' objects}&lt;br /&gt;
   500000    2.049    0.000    2.329    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:220(read_lc_string_list)&lt;br /&gt;
   500000    0.653    0.000    2.116    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:531(_STRING_to_python)&lt;br /&gt;
   502000    0.378    0.000    1.329    0.000 {method 'decode' of 'bytearray' objects}&lt;br /&gt;
   502000    0.221    0.000    0.951    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/utf_8.py:15(decode)&lt;br /&gt;
   502000    0.730    0.000    0.730    0.000 {_codecs.utf_8_decode}&lt;br /&gt;
   500000    0.514    0.000    0.514    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:413(_INT_to_python)&lt;br /&gt;
   509500    0.510    0.000    0.510    0.000 {_struct.unpack_from}&lt;br /&gt;
     1500    0.009    0.000    0.492    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:452(execute)&lt;br /&gt;
     1500    0.004    0.000    0.437    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:705(cmd_query)&lt;br /&gt;
   500000    0.367    0.000    0.367    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:405(_FLOAT_to_python)&lt;br /&gt;
   504500    0.343    0.000    0.343    0.000 {method 'extend' of 'bytearray' objects}&lt;br /&gt;
     1500    0.007    0.000    0.315    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:481(_send_cmd)&lt;br /&gt;
  2501500    0.275    0.000    0.275    0.000 {method 'append' of 'list' objects}&lt;br /&gt;
   500500    0.246    0.000    0.246    0.000 {method 'startswith' of 'bytearray' objects}&lt;br /&gt;
   524002    0.150    0.000    0.150    0.000 {isinstance}&lt;br /&gt;
     1500    0.011    0.000    0.118    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:613(_handle_result)&lt;br /&gt;
  1004500    0.112    0.000    0.112    0.000 {len}&lt;br /&gt;
   500000    0.092    0.000    0.092    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:204(description)&lt;br /&gt;
     1500    0.007    0.000    0.042    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/network.py:118(send_plain)&lt;br /&gt;
     2000    0.013    0.000    0.041    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:226(parse_column)&lt;br /&gt;
     1000    0.008    0.000    0.034    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:346(_process_params_dict)&lt;br /&gt;
     1500    0.002    0.000    0.029    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/socket.py:223(meth)&lt;br /&gt;
     1500    0.026    0.000    0.026    0.000 {method 'sendall' of '_socket.socket' objects}&lt;br /&gt;
     5000    0.007    0.000    0.014    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/catch23.py:79(struct_unpack)&lt;br /&gt;
     1000    0.001    0.000    0.013    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:562(_handle_ok)&lt;br /&gt;
    12000    0.011    0.000    0.011    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:167(read_lc_string)&lt;br /&gt;
     1000    0.004    0.000    0.010    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:199(parse_ok)&lt;br /&gt;
     3000    0.005    0.000    0.009    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:152(to_mysql)&lt;br /&gt;
     1500    0.003    0.000    0.007    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:118(make_command)&lt;br /&gt;
     3000    0.003    0.000    0.007    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:130(quote)&lt;br /&gt;
     1500    0.005    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/network.py:53(_prepare_packets)&lt;br /&gt;
     1500    0.003    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:406(_handle_result)&lt;br /&gt;
     3000    0.003    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:102(escape)&lt;br /&gt;
     1000    0.002    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:252(parse_eof)&lt;br /&gt;
      500    0.001    0.000    0.005    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:579(_handle_eof)&lt;br /&gt;
     2000    0.003    0.000    0.005    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:545(_handle_server_status)&lt;br /&gt;
     1500    0.003    0.000    0.004    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:53(int1store)&lt;br /&gt;
     3000    0.003    0.000    0.003    0.000 {method 'encode' of 'str' objects}&lt;br /&gt;
     9001    0.003    0.000    0.003    0.000 {method 'replace' of 'str' objects}&lt;br /&gt;
     1500    0.003    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:295(_reset_result)&lt;br /&gt;
     4500    0.003    0.000    0.003    0.000 {_struct.pack}&lt;br /&gt;
     2500    0.003    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:296(read_lc_int)&lt;br /&gt;
      500    0.002    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:757(_handle_eof)&lt;br /&gt;
     6027    0.003    0.000    0.003    0.000 {method 'format' of 'str' objects}&lt;br /&gt;
     3000    0.002    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:1221(_set_unread_result)&lt;br /&gt;
      500    0.001    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:219(parse_column_count)&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 {repr}&lt;br /&gt;
     4527    0.002    0.000    0.002    0.000 {getattr}&lt;br /&gt;
     4000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/constants.py:34(flag_is_set)&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:383(_handle_noresultset)&lt;br /&gt;
     4000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:1233(_get_unread_result)&lt;br /&gt;
     2001    0.001    0.000    0.001    0.000 {method 'items' of 'dict' objects}&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:307(_have_unread_result)&lt;br /&gt;
     3000    0.001    0.000    0.001    0.000 {method 'lower' of 'str' objects}&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 {range}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:173(_str_to_mysql)&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:169(_float_to_mysql)&lt;br /&gt;
     1000    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:161(_int_to_mysql)&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:1313(_get_getwarnings)&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:200(reset)&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:397(_handle_resultset)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/__init__.py:71(search_function)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/ascii.py:41(getregentry)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/contextlib.py:21(__exit__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/__init__.py:49(normalize_encoding)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 test.py:8(profiled)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/codecs.py:77(__new__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {__import__}&lt;br /&gt;
        3    0.000    0.000    0.000    0.000 {method 'get' of 'dict' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'split' of 'str' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {built-in method __new__ of type object at 0x1001534e8}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'translate' of 'str' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'join' of 'str' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {hasattr}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DBAPI:  &amp;lt;module 'MySQLdb' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/__init__.pyc'&amp;gt;&lt;br /&gt;
         47503 function calls in 0.918 seconds&lt;br /&gt;
&lt;br /&gt;
   Ordered by: cumulative time&lt;br /&gt;
&lt;br /&gt;
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;
     1500    0.009    0.000    0.917    0.001 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:164(execute)&lt;br /&gt;
     1500    0.002    0.000    0.890    0.001 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:353(_query)&lt;br /&gt;
     1500    0.004    0.000    0.498    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:315(_do_query)&lt;br /&gt;
     1500    0.016    0.000    0.391    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:358(_post_get_result)&lt;br /&gt;
     1500    0.001    0.000    0.375    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:324(_fetch_row)&lt;br /&gt;
      500    0.374    0.001    0.374    0.001 {built-in method fetch_row}&lt;br /&gt;
     1500    0.007    0.000    0.362    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:142(_do_get_result)&lt;br /&gt;
     1500    0.002    0.000    0.351    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:351(_get_result)&lt;br /&gt;
     1500    0.349    0.000    0.349    0.000 {method 'store_result' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.131    0.000    0.131    0.000 {method 'query' of '_mysql.connection' objects}&lt;br /&gt;
     4000    0.002    0.000    0.010    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:184(&amp;lt;genexpr&amp;gt;)&lt;br /&gt;
     3000    0.002    0.000    0.008    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/connections.py:267(literal)&lt;br /&gt;
     1500    0.007    0.000    0.007    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:107(_warning_check)&lt;br /&gt;
     3000    0.002    0.000    0.006    0.000 {method 'escape' of '_mysql.connection' objects}&lt;br /&gt;
     6000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:159(_get_db)&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/converters.py:81(Float2Str)&lt;br /&gt;
     2500    0.001    0.000    0.001    0.000 {isinstance}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/connections.py:202(string_literal)&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:380(fetchall)&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 {built-in method describe}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 {method 'string_literal' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {method 'insert_id' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {method 'affected_rows' of '_mysql.connection' objects}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/converters.py:69(Thing2Str)&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 {method 'warning_count' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 {method 'info' of '_mysql.connection' objects}&lt;br /&gt;
     1000    0.000    0.000    0.000    0.000 {method 'iteritems' of 'dict' objects}&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:103(_check_executed)&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 {len}&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 {built-in method field_flags}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/contextlib.py:21(__exit__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 test.py:8(profiled)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DBAPI:  &amp;lt;module 'pymysql' from '/Users/classic/dev/PyMySQL/pymysql/__init__.pyc'&amp;gt;, total seconds 10.046012&lt;br /&gt;
DBAPI:  &amp;lt;module 'mysql.connector' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/__init__.pyc'&amp;gt;, total seconds 15.403260&lt;br /&gt;
DBAPI:  &amp;lt;module 'MySQLdb' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/__init__.pyc'&amp;gt;, total seconds 1.028737&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mike Bayer</name></author>	</entry>

	<entry>
		<id>https://wiki.openstack.org/w/index.php?title=PyMySQL_evaluation&amp;diff=72557</id>
		<title>PyMySQL evaluation</title>
		<link rel="alternate" type="text/html" href="https://wiki.openstack.org/w/index.php?title=PyMySQL_evaluation&amp;diff=72557"/>
				<updated>2015-01-28T20:27:16Z</updated>
		
		<summary type="html">&lt;p&gt;Mike Bayer: /* MySQL DB Drivers Comparison */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= PyMySQL Evaluation =&lt;br /&gt;
&lt;br /&gt;
This page will capture issues related to Openstack moving to the [https://github.com/PyMySQL/PyMySQL/ PyMySQL driver] for MySQL/MariaDB dbapi access.&lt;br /&gt;
&lt;br /&gt;
== Rationale ==&lt;br /&gt;
&lt;br /&gt;
While the [https://github.com/farcepest/MySQLdb1 MySQL-Python] driver is a very mature and stable driver, it does not provide compatibility with either Python 3 or with [http://eventlet.net/doc/patching.html eventlet monkeypatching] (MySQL-Python  [https://github.com/eventlet/eventlet/blob/v0.16.1/eventlet/patcher.py#L225-L237 can be monkeypatched] with eventlet, but this feature disabled by default and missed in documentation). So OpenStack's usage of MySQL-Python, combined with the fact that concurrency is provided by eventlet, means that we currently have fully serialized database access within a single process, that is, only one database command occurs at a time within an Openstack Python process.&lt;br /&gt;
&lt;br /&gt;
== Drivers Under Consideration ==&lt;br /&gt;
&lt;br /&gt;
The two drivers that are known to provide eventlet-monkeypatch compatibility are MySQL-Connector and PyMySQL, as they are written in pure Python.   The two other well-known drivers for MySQL are MySQL-Python and OurSQL, both of which are written in C and offer no explicit support for eventlet or async.  They can reportedly be built to support gevent using a system called [https://github.com/CMGS/greenify Greenify], but the maturity and/or stabiltiy of this system is unknown.&lt;br /&gt;
&lt;br /&gt;
A comparison at http://www.diamondtin.com/2014/sqlalchemy-gevent-mysql-python-drivers-comparison/ illustrates performance metrics observed with all four of these drivers, with MySQL-python built both without and with the &amp;quot;greenify&amp;quot; system.&lt;br /&gt;
&lt;br /&gt;
A summary of the status of all four drivers is as follows.&lt;br /&gt;
&lt;br /&gt;
=== MySQL-Python ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/farcepest/MySQLdb1 MySQL-Python] is the most widely used Python driver.  However, it does not support Python 3, and it does not support async systems unless used within a thread pool.   While a system known as Greenify can potentially resolve the latter situation, the Python 3 limitation is still a deal-breaker - pull request with Python 3 support [https://github.com/farcepest/MySQLdb1/pull/62 was proposed] to MySQL-python in April 2014 and still not merged.&lt;br /&gt;
&lt;br /&gt;
=== mysqlclient ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/PyMySQL/mysqlclient-python mysqlclient] is a fork of MySQL-python. It add Python 3.3 support and merges some pull requests.&lt;br /&gt;
&lt;br /&gt;
=== OurSQL ===&lt;br /&gt;
&lt;br /&gt;
[https://pythonhosted.org/oursql/ OurSQL] is an alternative MySQL driver that is also written in C.  It features fast performance and uses a different execution model than MySQL-python, using prepared statements.    OurSQL does have a Python 3 port hosted on [https://launchpad.net/oursql/py3k/ launchpad] but is not integrated with OurSQL itself nor is it published on Pypi.     Because it is written in C, it does not offer compatibility with async systems including eventlet-style monkeypatching.&lt;br /&gt;
&lt;br /&gt;
Besides the shaky Python 3 support and lack of eventlet support, OurSQL is also not maintained, seeing its last master commit on [https://code.launchpad.net/~jerub/oursql/master 2012-06-05].&lt;br /&gt;
&lt;br /&gt;
=== MySQL-Connector-Python ===&lt;br /&gt;
&lt;br /&gt;
[https://pypi.python.org/pypi/mysql-connector-python MySQL-connector-Python] is a pure Python MySQL driver, and is now released under the auspices of the MySQL project itself, as owned by Oracle.  MySQL-connector-Python supports both Python 3 as well as eventlet monkeypatching, and is well maintained.  It is endorsed by Oracle as the official Oracle-supported driver for MySQL, so to that extent, it is in most ways Openstack's first choice in driver.  However, Oracle refuses to publish MySQL-connector-Python on Pypi, which is critical to the Openstack infrastructure.  Repeated attempts to communicate with Oracle in order to resolve this issue have not made any progress.    Therefore, for this one unfortunate reason, MySQL-connector-Python will not have a place in the Openstack ecosystem unless this issue is resolved.&lt;br /&gt;
&lt;br /&gt;
=== PyMySQL ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/PyMySQL/PyMySQL PyMySQL] is a pure Python MySQL&lt;br /&gt;
driver, first written as a rough port of the MySQL-Python driver.&lt;br /&gt;
PyMySQL meets all of Openstack's criterion for a driver: it is fully&lt;br /&gt;
open source, hosted on Github, released on Pypi, is actively&lt;br /&gt;
maintained, is written in pure Python so is eventlet-monkeypatch&lt;br /&gt;
compatible, and is fully Python 3 compatible.&lt;br /&gt;
&lt;br /&gt;
As this document is named &amp;quot;PyMySQL Evaluation&amp;quot;, it should be apparent that&lt;br /&gt;
this is the driver Openstack is currently leaning towards; because it is the only&lt;br /&gt;
one that meets all criteria fully, it is already most likely the &amp;quot;winner&amp;quot;.  However, it does&lt;br /&gt;
have some minor code quality issues which hopefully can be addressed in some&lt;br /&gt;
way; the section below titled &amp;quot;PyMySQL Code Review&amp;quot; will summarize the&lt;br /&gt;
current state of the code.&lt;br /&gt;
&lt;br /&gt;
===MySQL DB Drivers Comparison===&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Project !! PyPi hosted !! Eventlet friendly !! Python 3 compatibility !! Maturity and/or stability  !! Comment&lt;br /&gt;
|-&lt;br /&gt;
| MySQL-Python || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: orange;&amp;quot; | Partial || style=&amp;quot;background-color: red;&amp;quot; | No  || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || Can be monkeypatched by eventlet, but only to enable thread pooling&lt;br /&gt;
|-&lt;br /&gt;
| mysqlclient || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: orange;&amp;quot; | Partial || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes  || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || MySQL-python fork, so should be similar to it&lt;br /&gt;
|-&lt;br /&gt;
| OurSQL || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: red;&amp;quot; | No || style=&amp;quot;background-color: orange;&amp;quot; | Yes, but not Pypi hosted  || style=&amp;quot;background-color: orange;&amp;quot; | ??? || Has not seen commits/releases in two years&lt;br /&gt;
|-&lt;br /&gt;
| MySQL-Connector-Python || style=&amp;quot;background-color: red;&amp;quot; | No || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes  || style=&amp;quot;background-color: orange;&amp;quot; | The driver is still fairly new || The official Oracle-supported driver for MySQL&lt;br /&gt;
|-&lt;br /&gt;
| PyMySQL || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes  || style=&amp;quot;background-color: orange;&amp;quot; | Yes, however see notes below. || Actively maintained and popular.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== PyMySQL Code Review ==&lt;br /&gt;
&lt;br /&gt;
PyMySQL started roughly as a pure Python port of MySQL-Python.   The sections below will&lt;br /&gt;
detail various aspects of the driver and the status of the code.   Overall, the general&lt;br /&gt;
tone of PyMySQL is one of code that was written in the spirit of pragmatism and immediate&lt;br /&gt;
need; it is straightforward, relatively free of serious antipatterns and performs&lt;br /&gt;
its task in a matter-of-fact way.  However,&lt;br /&gt;
it lacks polish and completeness in many areas that would normally be in better&lt;br /&gt;
condition for a more mature project.  These areas are all highly fixable, and it is&lt;br /&gt;
hoped that these items can be shared with the current PyMySQL developers, through&lt;br /&gt;
a combination of this document itself as well as new issues and pull requests&lt;br /&gt;
on the PyMySQL tracker, so that this fairly promising library can be pushed&lt;br /&gt;
to the next level.   This would require that the developers are amenable to&lt;br /&gt;
these improvements.&lt;br /&gt;
&lt;br /&gt;
=== Coding Style / Pep8 ===&lt;br /&gt;
&lt;br /&gt;
PyMySQL's coding style is pretty good.  The majority of it passes all&lt;br /&gt;
flake8 tests, save for a few dozen whitespace issues and&lt;br /&gt;
some long lines here and there within the core&lt;br /&gt;
modules, and moreso within the tests which appear to be a bit more&lt;br /&gt;
crufty than the core modules.  A full run of flake8 produces only 128&lt;br /&gt;
errors, and with a default run of autopep8 against E1,E2,E3, we can&lt;br /&gt;
fix all of them in one pass except for 38 remaining long line warnings.&lt;br /&gt;
The code layout is mostly idiomatic and reasonable.&lt;br /&gt;
&lt;br /&gt;
The codebase is probably short on docstrings.  Those methods that are&lt;br /&gt;
part of the public DBAPI do have docstrings, but tend to be extremely&lt;br /&gt;
terse (example, docstring for cursor.execute(): &amp;quot;'Execute a query'&amp;quot;).&lt;br /&gt;
Other methods that aren't public tend to not have any docstrings, but&lt;br /&gt;
the codebase is not at all hard to read and the purpose of methods and&lt;br /&gt;
functions is pretty easy to discern just by their names and implementations,&lt;br /&gt;
however those who seek inline documentation as a means to understand a codebase&lt;br /&gt;
will be disappointed.   The terseness of public API docstrings is more of&lt;br /&gt;
an issue in terms of the overall lack of documentation for PyMySQL,&lt;br /&gt;
see the section &amp;quot;Documentation&amp;quot; below.&lt;br /&gt;
&lt;br /&gt;
=== Test Coverage ===&lt;br /&gt;
&lt;br /&gt;
The test coverage for PyMySQL is definitely lacking.   From a visual&lt;br /&gt;
inspection alone, it's apparent that of the dozen or so test modules,&lt;br /&gt;
most of them have less then ten tests each and the modules are very short.&lt;br /&gt;
For a full test run, there's only 126 tests; contrast this to&lt;br /&gt;
MySQL-Connector-Python which has 531 tests.&lt;br /&gt;
&lt;br /&gt;
Additionally, all of the tests are live round-trip tests against a&lt;br /&gt;
real database.  There are no non-live unit tests of any kind,&lt;br /&gt;
leaving codepaths that are not easily exercised&lt;br /&gt;
on a generic MySQL database out in the cold.  This means that features designed&lt;br /&gt;
for particular versions of MySQL, particular datatypes, particular error&lt;br /&gt;
conditions, and particular kinds of message packets that aren't created for&lt;br /&gt;
real are simply not tested.&lt;br /&gt;
&lt;br /&gt;
The lack of tests is apparent when run with coverage.  The test suite itself&lt;br /&gt;
does not appear to have any signs that it is normally run with coverage&lt;br /&gt;
turned on, with no directives in tox.ini or similar.   With coverage,&lt;br /&gt;
the overall coverage is 85%, but the modules most lacking in coverage&lt;br /&gt;
are also the most critical to PyMySQL's core functionality,&lt;br /&gt;
connections.py, converters.py and cursors.py:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Name                                                              Stmts   Miss  Cover&lt;br /&gt;
-------------------------------------------------------------------------------------&lt;br /&gt;
pymysql/__init__                                                     51      3    94%&lt;br /&gt;
pymysql/_compat                                                      14      4    71%&lt;br /&gt;
pymysql/_socketio                                                    72     30    58%&lt;br /&gt;
pymysql/charset                                                     228      3    99%&lt;br /&gt;
pymysql/connections                                                 827    188    77%&lt;br /&gt;
pymysql/constants/CLIENT                                             18      0   100%&lt;br /&gt;
pymysql/constants/COMMAND                                            32      0   100%&lt;br /&gt;
pymysql/constants/ER                                                471      0   100%&lt;br /&gt;
pymysql/constants/FIELD_TYPE                                         29      0   100%&lt;br /&gt;
pymysql/constants/FLAG                                               15      0   100%&lt;br /&gt;
pymysql/constants/SERVER_STATUS                                      10      0   100%&lt;br /&gt;
pymysql/constants/__init__                                            0      0   100%&lt;br /&gt;
pymysql/converters                                                  148     51    66%&lt;br /&gt;
pymysql/cursors                                                     273     37    86%&lt;br /&gt;
pymysql/err                                                          40      1    98%&lt;br /&gt;
pymysql/tests/__init__                                               14      5    64%&lt;br /&gt;
pymysql/tests/base                                                   25      2    92%&lt;br /&gt;
pymysql/tests/test_DictCursor                                        73      2    97%&lt;br /&gt;
pymysql/tests/test_SSCursor                                          56      6    89%&lt;br /&gt;
pymysql/tests/test_basic                                            186      4    98%&lt;br /&gt;
pymysql/tests/test_connection                                        57      8    86%&lt;br /&gt;
pymysql/tests/test_example                                           17      2    88%&lt;br /&gt;
pymysql/tests/test_issues                                           300     48    84%&lt;br /&gt;
pymysql/tests/test_load_local                                        39      2    95%&lt;br /&gt;
pymysql/tests/test_nextset                                           43      4    91%&lt;br /&gt;
pymysql/tests/thirdparty/__init__                                     7      5    29%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/__init__                        6      2    67%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/capabilities                  196     23    88%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/dbapi20                       423    121    71%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_capabilities      73      6    92%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_dbapi20          100      2    98%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_nonstandard       57      3    95%&lt;br /&gt;
pymysql/times                                                        12      0   100%&lt;br /&gt;
pymysql/util                                                         14      7    50%&lt;br /&gt;
-------------------------------------------------------------------------------------&lt;br /&gt;
TOTAL                                                              3926    569    86%&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The lack of coverage in converters.py refers to functions related to handling&lt;br /&gt;
incoming bound values as well as outgoing result values.    Key data escaping&lt;br /&gt;
features such as escaping of dictionaries, sets, some unicode objects and&lt;br /&gt;
byte objects as well as many pathways into date and time-processing functions are not&lt;br /&gt;
covered.&lt;br /&gt;
&lt;br /&gt;
In connections.py, uncovered features include various SSL and&lt;br /&gt;
very legacy (e.g. version 3.23) MySQL features, but also some protocol parsing&lt;br /&gt;
features.&lt;br /&gt;
&lt;br /&gt;
In cursors.py, there is coverage for an elaborate and mostly undocumented&lt;br /&gt;
performance-related feature that rewrites an INSERT statement to have an&lt;br /&gt;
extended VALUES clause (_do_execute_many), however it fails to cover the very&lt;br /&gt;
likely case where the function will need to break the input set into&lt;br /&gt;
multiple chunks:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;gt;         for arg in args:&lt;br /&gt;
&amp;gt;             v = values % escape(arg, conn)&lt;br /&gt;
&amp;gt;             if isinstance(v, text_type):&lt;br /&gt;
&amp;gt;                 v = v.encode(encoding)&lt;br /&gt;
&amp;gt;             if len(sql) + len(v) + 1 &amp;gt; max_stmt_length:&lt;br /&gt;
!                 rows += self.execute(sql)&lt;br /&gt;
!                 sql = bytearray(prefix)&lt;br /&gt;
&amp;gt;             else:&lt;br /&gt;
&amp;gt;                 sql += b','&lt;br /&gt;
&amp;gt;             sql += v&lt;br /&gt;
&amp;gt;         rows += self.execute(sql)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The most prominent missing coverage in cursors.py seems to be&lt;br /&gt;
for its &amp;quot;scrollable cursor&amp;quot; support.&lt;br /&gt;
&lt;br /&gt;
Beyond the lack of completeness in testing, many of the tests themselves&lt;br /&gt;
are written in an expedient &amp;quot;one giant test&amp;quot; style where dozens of&lt;br /&gt;
assertions and individual behaviors are lumped into one big test&lt;br /&gt;
case (see test_SSCursor.py, test_basic.py-&amp;gt;test_datatypes for examples).&lt;br /&gt;
Test cases like these are difficult to work with when debugging&lt;br /&gt;
regressions as well as when tests for new features need to be added, and ideally&lt;br /&gt;
these should be broken out into clean single-feature tests with consistent&lt;br /&gt;
fixtures.&lt;br /&gt;
&lt;br /&gt;
There is also a suite called &amp;quot;test_issues&amp;quot;, which intends to&lt;br /&gt;
accumulate tests against specific issues that have been reported. This&lt;br /&gt;
isn't a bad idea, though it would be nice to see an effort made into&lt;br /&gt;
categorizing these tests into the actual features they are testing,&lt;br /&gt;
rather than a meaningless list of issue numbers, which could then form&lt;br /&gt;
as the basis for new suites centered around those areas of&lt;br /&gt;
functionality.  The issues reported should be used as the inspiration&lt;br /&gt;
for improvement of the test suite, rather than just another line-item&lt;br /&gt;
to be filed away.&lt;br /&gt;
&lt;br /&gt;
=== Library Documentation ===&lt;br /&gt;
&lt;br /&gt;
As the Python DBAPI is already a well-documented API, and the users of&lt;br /&gt;
PyMySQL are typically coming from MySQL-Python which is documented&lt;br /&gt;
to some degree and for which the use contract is widely known, it seems&lt;br /&gt;
unlikely that users of PyMySQL are eager for comprehensive documentation&lt;br /&gt;
specific to this library.&lt;br /&gt;
&lt;br /&gt;
That being said, PyMySQL does not appear to have any real library&lt;br /&gt;
documentation at all.  There's a README which&lt;br /&gt;
essentially refers to pep-249, an example.py in the root which shows a&lt;br /&gt;
very basic connection / round trip, and that's it. In&lt;br /&gt;
order to know anything about the API, specific parameters, optimization&lt;br /&gt;
strategies, behaviors, etc., one either has to go off of the MySQL-Python documentation and assume&lt;br /&gt;
PyMySQL also features the same parameter, or read the source code.&lt;br /&gt;
&lt;br /&gt;
PyMySQL probably hasn't had much urgent need for real documentation thus&lt;br /&gt;
far.  However, relying upon being a port of MySQL-Python is fast becoming&lt;br /&gt;
something that can no longer be relied upon, as MySQL-Python's development&lt;br /&gt;
is quite stalled, and PyMySQL should aspire to move into the future&lt;br /&gt;
of MySQL and Python with its own featureset and behaviors.  To that end, it would be a &lt;br /&gt;
great idea if it at least made the start&lt;br /&gt;
of a rudimentary Sphinx build including autodoc for module documentation&lt;br /&gt;
which could then be published up to RTD.   This would be very easy to get&lt;br /&gt;
started, and once present, new documentation sections can be added&lt;br /&gt;
iteratively.&lt;br /&gt;
&lt;br /&gt;
=== Architecture and Performance ===&lt;br /&gt;
&lt;br /&gt;
As stated before, PyMySQL has a really matter-of-fact and straightforward&lt;br /&gt;
implementation.   The code is very readable and it's easy to discern even&lt;br /&gt;
unusual features such as the INSERT..VALUES rewriting.  As far as the design,&lt;br /&gt;
there are lots of areas where performance suffers a bit at the Python level;&lt;br /&gt;
these areas could be greatly improved very easily with a little bit of&lt;br /&gt;
attention.&lt;br /&gt;
&lt;br /&gt;
As we are dealing with a driver that is communicating with a low level&lt;br /&gt;
protocol over a socket, it's critical that sending and receiving messages&lt;br /&gt;
is done as efficiently as possible.  The mechanisms used in PyMySQL rely&lt;br /&gt;
heavily on standard Python objects, without the use of __slots__; this means&lt;br /&gt;
that for individual messages on a socket, we are creating a heavyweight object,&lt;br /&gt;
calling its __init__() method and a creating a new __dict__ each time;&lt;br /&gt;
the most common object, OKPacketWrapper(), already wraps a MysqlPacket()&lt;br /&gt;
object, so we're doing two __init__()s in this common case.  OKPacketWrapper&lt;br /&gt;
also implements a `__getattr__` scheme&lt;br /&gt;
to proxy attribute access from itself to the internal MysqlPacket; this is a&lt;br /&gt;
an inefficient mode of operation in Python as it means a lookup must first&lt;br /&gt;
fail on the OKPacketWrapper before it invokes the `__getattr__()` method&lt;br /&gt;
and does another attribute lookup.   A reorg of the packet classes to produce&lt;br /&gt;
far fewer objects for messages as well as to use __slots__ would be&lt;br /&gt;
recommended.&lt;br /&gt;
&lt;br /&gt;
There are also lots of areas where function call overhead is unnecessarily&lt;br /&gt;
high due to the organization of calls.  For example, when calling upon&lt;br /&gt;
cursor.execute(), for parameters passed as a dictionary under Python 2K,&lt;br /&gt;
the isinstance() builtin function will be called on the incoming arguments&lt;br /&gt;
four times at a minimum, with two of each call against the exact same&lt;br /&gt;
criteria.  Additional logic will then call isinstance() on every key of&lt;br /&gt;
the dictionary as well as at least twice on every value within the dictionary,&lt;br /&gt;
in order to determine if the value needs to be converted to bytes as well&lt;br /&gt;
as for some top-level escaping logic checks; the escaping logic then&lt;br /&gt;
goes further with more checks for type() to match the object to a final&lt;br /&gt;
converter function.   While the flow of this logic does appear to be&lt;br /&gt;
reasonable and straightforward, it isn't ideal for a low-level database&lt;br /&gt;
driver library where speed is a high priority; the various wrappers and&lt;br /&gt;
processors can easily be reorganized here to reduce the number of isinstance()&lt;br /&gt;
and type() calls significantly, which would save a lot on function call&lt;br /&gt;
overhead.  It is likely there are many other areas in the codebase where&lt;br /&gt;
similar reductions in call counts can be made without too much disruption.&lt;br /&gt;
&lt;br /&gt;
In Appendix A, a short performance suite is illustrated.  This&lt;br /&gt;
is run against PyMySQL, Mysql-Connector-Python, and MySQL-Python.&lt;br /&gt;
PyMySQL does in fact demonstrate the&lt;br /&gt;
highest call-count behavior, logging 26,851,003 function calls&lt;br /&gt;
compared to MySQL-connector's 11,674,074 for the same operations.  To PyMySQL's&lt;br /&gt;
credit, it somehow completes the test 25-50% faster than MySQL-connector&lt;br /&gt;
despite having more than twice as many function calls; this probably indicates&lt;br /&gt;
the use of something very slow within MySQL-connector, such as catching&lt;br /&gt;
an exception within a tight loop.  However, the real bar we're comparing to&lt;br /&gt;
is MySQL-Python, which is written&lt;br /&gt;
in C and does the whole test with only 47,503 Python function calls; while&lt;br /&gt;
PyMySQL and MySQL-connector-Python compete for time within the 10-15 second&lt;br /&gt;
range without profiling, MySQL-Python completes the whole test in **1.03 seconds**, a 1000%&lt;br /&gt;
improvement over PyMySQL.   This speed difference is not PyMySQL's fault,&lt;br /&gt;
as pure Python is known to be extremely slow compared to C code.&lt;br /&gt;
However, there's a lot of easy wins in PyMySQL's codebase where the function&lt;br /&gt;
call count could probably be reduced dramatically to be even better than that&lt;br /&gt;
of MySQL-connector-Python; ideally, the driver would be able to run this&lt;br /&gt;
test possibly 5-6x slower than MySQL-Python rather than 10x.&lt;br /&gt;
&lt;br /&gt;
=== History Documentation ===&lt;br /&gt;
&lt;br /&gt;
PyMySQL includes a CHANGELOG file in its root which describes the bug fixes&lt;br /&gt;
and features of each release.  The construction of this file is definitely&lt;br /&gt;
within the &amp;quot;expedient&amp;quot; style noted for PyMySQL overall, and for it to be&lt;br /&gt;
genuinely useful for enterprise-level software, it would need to be&lt;br /&gt;
much more complete in its detail.    Each bug fix mentioned is only referred to&lt;br /&gt;
tersely without any link to a pull request or bug report.  While some can&lt;br /&gt;
be linked to a specific change through detective work&lt;br /&gt;
(&amp;quot;Cursor.fetchall() and .fetchmany now return list, not tuple&amp;quot;), it would&lt;br /&gt;
be very difficult for many&lt;br /&gt;
(&amp;quot;Fixed BIT type handling&amp;quot;, &amp;quot;Fixed GC errors&amp;quot; - what was the issue?)&lt;br /&gt;
and in many cases impossible (&amp;quot;Improved Py3k support&amp;quot;).  The file also&lt;br /&gt;
includes no dates so one has to look at the git tags to figure this&lt;br /&gt;
part out (fortunately, releases to seem to be git tagged at least).&lt;br /&gt;
For a user trying to ascertain if particular issues are&lt;br /&gt;
fixed or if behaviors have changed, this file is at best highly frustrating.&lt;br /&gt;
&lt;br /&gt;
A listing of release dates illustrates an average of two releases per year,&lt;br /&gt;
excluding 2012 when PyMySQL was seeking a new maintainer:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
2014-12-02 12:05:10 -0200  (tag: pymysql-0.6.3)&lt;br /&gt;
2014-04-21 14:28:47 -0300  (tag: pymysql-0.6.2)&lt;br /&gt;
2013-10-11 13:36:56 -0300  (tag: pymysql-0.6.1)&lt;br /&gt;
2013-10-04 14:25:14 -0300  (tag: pymysql-0.6)&lt;br /&gt;
2011-11-08 10:27:41 -0800  (tag: pymysql-0.5)&lt;br /&gt;
2010-12-27 17:28:16 +0000  (tag: pymysql-0.4)&lt;br /&gt;
2010-09-03 00:55:29 +0000  (tag: pymysql-0.3)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Community Involvement ===&lt;br /&gt;
&lt;br /&gt;
This is an area where PyMySQL seems to shine, and might even be an advantage&lt;br /&gt;
as compared to MySQL-Connector-Python, which remains under the cloak&lt;br /&gt;
of MySQL's vastly overstuffed bugtracker under the ownership of&lt;br /&gt;
the famously-opaque Oracle.   For a small, low key library without&lt;br /&gt;
any real documentation and only 126 tests, it features 33 contributors on&lt;br /&gt;
Github and is up to pull request #285, which is quite high.  Because&lt;br /&gt;
PyMySQL is small and straightforward, it does lend itself to contribution&lt;br /&gt;
and seems to do very well in this area.   The developers so far have been&lt;br /&gt;
responsive to issues and pull requests made by the author.&lt;br /&gt;
&lt;br /&gt;
Based on github contributor graphs it appears that PyMySQL had a lull&lt;br /&gt;
in 2012 but was picked up again afterwards in 2013.  As of 2012, its most&lt;br /&gt;
prominent developer Pete Hunt stopped development, and on July 25, 2013&lt;br /&gt;
added a note to the README that the project was looking for a new maintainer.&lt;br /&gt;
As of 2013 it was picked up by the current most prominent developers&lt;br /&gt;
Marcel Rodrigues (github username lecram) and INADA Naoki&lt;br /&gt;
(github username methane).&lt;br /&gt;
&lt;br /&gt;
Most issues that have been noted thus far within the areas of testing,&lt;br /&gt;
documentation and architecture are all very fixable; if the Openstack community&lt;br /&gt;
can provide resources to address some of these areas, and if the PyMySQL&lt;br /&gt;
developers are reasonably open to accepting new changes and producing new&lt;br /&gt;
releases in a timely fashion, the PyMySQL driver&lt;br /&gt;
could rapidly become a fully formidable contender in the MySQL driver field.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Appendix A - Performance Tests ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
import cProfile&lt;br /&gt;
import StringIO&lt;br /&gt;
import pstats&lt;br /&gt;
import contextlib&lt;br /&gt;
import time&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextlib.contextmanager&lt;br /&gt;
def profiled(dbapi):&lt;br /&gt;
    pr = cProfile.Profile()&lt;br /&gt;
    pr.enable()&lt;br /&gt;
    yield&lt;br /&gt;
    pr.disable()&lt;br /&gt;
    s = StringIO.StringIO()&lt;br /&gt;
    ps = pstats.Stats(pr, stream=s).sort_stats('cumulative')&lt;br /&gt;
    ps.print_stats()&lt;br /&gt;
    print &amp;quot;DBAPI:  %s&amp;quot; % dbapi&lt;br /&gt;
    print s.getvalue()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextlib.contextmanager&lt;br /&gt;
def timeonly(dbapi):&lt;br /&gt;
    now = time.time()&lt;br /&gt;
    try:&lt;br /&gt;
        yield&lt;br /&gt;
    finally:&lt;br /&gt;
        total = time.time() - now&lt;br /&gt;
        print &amp;quot;DBAPI:  %s, total seconds %f&amp;quot; % (dbapi, total)&lt;br /&gt;
&lt;br /&gt;
import MySQLdb&lt;br /&gt;
import pymysql&lt;br /&gt;
from mysql import connector as mysqlconnector&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def go(dbapi, ctx):&lt;br /&gt;
    conn = dbapi.connect(&lt;br /&gt;
        user='scott', passwd='tiger', host='localhost', db='test')&lt;br /&gt;
    cursor = conn.cursor()&lt;br /&gt;
    cursor.execute(&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
CREATE TABLE IF NOT EXISTS test_things (&lt;br /&gt;
    x INTEGER,&lt;br /&gt;
    y VARCHAR(255),&lt;br /&gt;
    z FLOAT&lt;br /&gt;
) engine=InnoDB&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;)&lt;br /&gt;
    cursor.execute(&amp;quot;DELETE from test_things&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    with ctx(dbapi):&lt;br /&gt;
        for row in xrange(1000):&lt;br /&gt;
            cursor.execute(&lt;br /&gt;
                &amp;quot;INSERT INTO test_things (x, y, z) &amp;quot;&lt;br /&gt;
                &amp;quot;VALUES (%(x)s, %(y)s, %(z)s)&amp;quot;,&lt;br /&gt;
                {&amp;quot;x&amp;quot;: row, &amp;quot;y&amp;quot;: &amp;quot;row number %d&amp;quot; % row,&lt;br /&gt;
                 &amp;quot;z&amp;quot;: row * 4.57292, }&lt;br /&gt;
            )&lt;br /&gt;
&lt;br /&gt;
        for x in xrange(500):&lt;br /&gt;
            cursor.execute(&lt;br /&gt;
                &amp;quot;select * from test_things&amp;quot;)&lt;br /&gt;
            for row in cursor.fetchall():&lt;br /&gt;
                row[0], row[1], row[2]&lt;br /&gt;
&lt;br /&gt;
    cursor.close()&lt;br /&gt;
    conn.close()&lt;br /&gt;
&lt;br /&gt;
go(pymysql, profiled)&lt;br /&gt;
go(mysqlconnector, profiled)&lt;br /&gt;
go(MySQLdb, profiled)&lt;br /&gt;
&lt;br /&gt;
go(pymysql, timeonly)&lt;br /&gt;
go(mysqlconnector, timeonly)&lt;br /&gt;
go(MySQLdb, timeonly)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DBAPI:  &amp;lt;module 'pymysql' from '/Users/classic/dev/PyMySQL/pymysql/__init__.pyc'&amp;gt;&lt;br /&gt;
         26851003 function calls in 15.924 seconds&lt;br /&gt;
&lt;br /&gt;
   Ordered by: cumulative time&lt;br /&gt;
&lt;br /&gt;
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;
     1500    0.010    0.000   15.923    0.011 /Users/classic/dev/PyMySQL/pymysql/cursors.py:105(execute)&lt;br /&gt;
     1500    0.003    0.000   15.883    0.011 /Users/classic/dev/PyMySQL/pymysql/cursors.py:269(_query)&lt;br /&gt;
     1500    0.004    0.000   15.859    0.011 /Users/classic/dev/PyMySQL/pymysql/connections.py:746(query)&lt;br /&gt;
     1500    0.003    0.000   15.819    0.011 /Users/classic/dev/PyMySQL/pymysql/connections.py:892(_read_query_result)&lt;br /&gt;
     1500    0.004    0.000   15.812    0.011 /Users/classic/dev/PyMySQL/pymysql/connections.py:1097(read)&lt;br /&gt;
      500    0.003    0.000   15.559    0.031 /Users/classic/dev/PyMySQL/pymysql/connections.py:1154(_read_result_packet)&lt;br /&gt;
      500    0.900    0.002   15.429    0.031 /Users/classic/dev/PyMySQL/pymysql/connections.py:1187(_read_rowdata_packet)&lt;br /&gt;
   500000    3.417    0.000    9.173    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1200(_read_row_from_packet)&lt;br /&gt;
  2012000    1.157    0.000    5.307    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:311(read_length_coded_string)&lt;br /&gt;
   504500    1.495    0.000    5.209    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:845(_read_packet)&lt;br /&gt;
  2014500    1.248    0.000    3.035    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:293(read_length_encoded_integer)&lt;br /&gt;
  3539500    2.439    0.000    2.750    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:245(read)&lt;br /&gt;
  1009000    0.779    0.000    2.307    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:870(_read_bytes)&lt;br /&gt;
  1009000    1.197    0.000    1.423    0.000 {method 'read' of '_io.BufferedReader' objects}&lt;br /&gt;
   508500    0.379    0.000    0.695    0.000 /Users/classic/dev/PyMySQL/pymysql/util.py:3(byte2int)&lt;br /&gt;
  5058000    0.465    0.000    0.465    0.000 {len}&lt;br /&gt;
   500500    0.222    0.000    0.455    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1146(_check_packet_is_eof)&lt;br /&gt;
   504500    0.205    0.000    0.375    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:342(check_error)&lt;br /&gt;
  2504000    0.275    0.000    0.275    0.000 {method 'append' of 'list' objects}&lt;br /&gt;
   501500    0.182    0.000    0.230    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:326(is_eof_packet)&lt;br /&gt;
     3500    0.009    0.000    0.226    0.000 /Users/classic/dev/PyMySQL/pymysql/_socketio.py:45(readinto)&lt;br /&gt;
     3500    0.207    0.000    0.207    0.000 {method 'recv_into' of '_socket.socket' objects}&lt;br /&gt;
  1022000    0.203    0.000    0.203    0.000 {_struct.unpack}&lt;br /&gt;
   526000    0.200    0.000    0.200    0.000 {isinstance}&lt;br /&gt;
  1501000    0.195    0.000    0.195    0.000 {method 'get' of 'dict' objects}&lt;br /&gt;
   504500    0.176    0.000    0.176    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:238(__init__)&lt;br /&gt;
   504500    0.170    0.000    0.170    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:339(is_error_packet)&lt;br /&gt;
  2014500    0.167    0.000    0.167    0.000 {ord}&lt;br /&gt;
      500    0.006    0.000    0.125    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1224(_get_descriptions)&lt;br /&gt;
     2000    0.003    0.000    0.087    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:361(__init__)&lt;br /&gt;
     2000    0.019    0.000    0.084    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:365(__parse_field_descriptor)&lt;br /&gt;
   500000    0.056    0.000    0.056    0.000 /Users/classic/dev/PyMySQL/pymysql/converters.py:301(through)&lt;br /&gt;
     1500    0.005    0.000    0.037    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:915(_execute_command)&lt;br /&gt;
     1500    0.003    0.000    0.027    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:886(_write_bytes)&lt;br /&gt;
     1500    0.002    0.000    0.024    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/socket.py:223(meth)&lt;br /&gt;
     1500    0.021    0.000    0.021    0.000 {method 'sendall' of '_socket.socket' objects}&lt;br /&gt;
     1500    0.020    0.000    0.020    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:276(_do_get_result)&lt;br /&gt;
     1000    0.003    0.000    0.019    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:95(_escape_args)&lt;br /&gt;
     4000    0.002    0.000    0.015    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:99(&amp;lt;genexpr&amp;gt;)&lt;br /&gt;
     1000    0.002    0.000    0.014    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1127(_read_ok_packet)&lt;br /&gt;
     3000    0.003    0.000    0.013    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:712(escape)&lt;br /&gt;
     1000    0.005    0.000    0.012    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:416(__init__)&lt;br /&gt;
     8000    0.011    0.000    0.011    0.000 {method 'decode' of 'str' objects}&lt;br /&gt;
     3500    0.003    0.000    0.007    0.000 {method '_checkReadable' of '_io._IOBase' objects}&lt;br /&gt;
     2000    0.003    0.000    0.006    0.000 /Users/classic/dev/PyMySQL/pymysql/converters.py:19(escape_item)&lt;br /&gt;
     4000    0.002    0.000    0.006    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:126(&amp;lt;genexpr&amp;gt;)&lt;br /&gt;
     7500    0.003    0.000    0.005    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:115(ensure_bytes)&lt;br /&gt;
     2000    0.003    0.000    0.005    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:386(description)&lt;br /&gt;
     5500    0.004    0.000    0.004    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:268(advance)&lt;br /&gt;
     1000    0.001    0.000    0.004    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:722(escape_string)&lt;br /&gt;
     3500    0.004    0.000    0.004    0.000 /Users/classic/dev/PyMySQL/pymysql/_socketio.py:87(readable)&lt;br /&gt;
      500    0.002    0.000    0.004    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:442(__init__)&lt;br /&gt;
     3500    0.003    0.000    0.003    0.000 {method '_checkClosed' of '_io._IOBase' objects}&lt;br /&gt;
     1000    0.001    0.000    0.003    0.000 /Users/classic/dev/PyMySQL/pymysql/converters.py:59(escape_string)&lt;br /&gt;
     1500    0.003    0.000    0.003    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1080(__init__)&lt;br /&gt;
     1500    0.001    0.000    0.003    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:92(nextset)&lt;br /&gt;
     4000    0.002    0.000    0.002    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:397(get_column_length)&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 /Users/classic/dev/PyMySQL/pymysql/converters.py:56(escape_float)&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 {method 'sub' of '_sre.SRE_Pattern' objects}&lt;br /&gt;
     3000    0.002    0.000    0.002    0.000 {_struct.pack}&lt;br /&gt;
     6000    0.002    0.000    0.002    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:62(_get_db)&lt;br /&gt;
     1500    0.001    0.000    0.002    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:80(_nextset)&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 /Users/classic/dev/PyMySQL/pymysql/util.py:9(int2byte)&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:244(fetchall)&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {min}&lt;br /&gt;
     2500    0.001    0.000    0.001    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:323(is_ok_packet)&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Users/classic/dev/PyMySQL/pymysql/converters.py:52(escape_int)&lt;br /&gt;
     2000    0.001    0.000    0.001    0.000 {method 'items' of 'dict' objects}&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {getattr}&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1093(__del__)&lt;br /&gt;
     1000    0.000    0.000    0.000    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:259(read_all)&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:67(_check_executed)&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:336(is_load_local_packet)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/contextlib.py:21(__exit__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 test.py:8(profiled)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DBAPI:  &amp;lt;module 'mysql.connector' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/__init__.pyc'&amp;gt;&lt;br /&gt;
         11674074 function calls in 19.049 seconds&lt;br /&gt;
&lt;br /&gt;
   Ordered by: cumulative time&lt;br /&gt;
&lt;br /&gt;
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;
      500    0.640    0.001   18.556    0.037 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:820(fetchall)&lt;br /&gt;
      500    0.003    0.000   11.196    0.022 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:655(get_rows)&lt;br /&gt;
      500    1.561    0.003   11.191    0.022 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:292(read_text_result)&lt;br /&gt;
   504500    3.303    0.000    7.306    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/network.py:219(recv_plain)&lt;br /&gt;
   500000    3.572    0.000    6.625    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:359(row_to_python)&lt;br /&gt;
  1009000    3.155    0.000    3.155    0.000 {method 'recv_into' of '_socket.socket' objects}&lt;br /&gt;
   500000    2.049    0.000    2.329    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:220(read_lc_string_list)&lt;br /&gt;
   500000    0.653    0.000    2.116    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:531(_STRING_to_python)&lt;br /&gt;
   502000    0.378    0.000    1.329    0.000 {method 'decode' of 'bytearray' objects}&lt;br /&gt;
   502000    0.221    0.000    0.951    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/utf_8.py:15(decode)&lt;br /&gt;
   502000    0.730    0.000    0.730    0.000 {_codecs.utf_8_decode}&lt;br /&gt;
   500000    0.514    0.000    0.514    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:413(_INT_to_python)&lt;br /&gt;
   509500    0.510    0.000    0.510    0.000 {_struct.unpack_from}&lt;br /&gt;
     1500    0.009    0.000    0.492    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:452(execute)&lt;br /&gt;
     1500    0.004    0.000    0.437    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:705(cmd_query)&lt;br /&gt;
   500000    0.367    0.000    0.367    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:405(_FLOAT_to_python)&lt;br /&gt;
   504500    0.343    0.000    0.343    0.000 {method 'extend' of 'bytearray' objects}&lt;br /&gt;
     1500    0.007    0.000    0.315    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:481(_send_cmd)&lt;br /&gt;
  2501500    0.275    0.000    0.275    0.000 {method 'append' of 'list' objects}&lt;br /&gt;
   500500    0.246    0.000    0.246    0.000 {method 'startswith' of 'bytearray' objects}&lt;br /&gt;
   524002    0.150    0.000    0.150    0.000 {isinstance}&lt;br /&gt;
     1500    0.011    0.000    0.118    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:613(_handle_result)&lt;br /&gt;
  1004500    0.112    0.000    0.112    0.000 {len}&lt;br /&gt;
   500000    0.092    0.000    0.092    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:204(description)&lt;br /&gt;
     1500    0.007    0.000    0.042    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/network.py:118(send_plain)&lt;br /&gt;
     2000    0.013    0.000    0.041    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:226(parse_column)&lt;br /&gt;
     1000    0.008    0.000    0.034    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:346(_process_params_dict)&lt;br /&gt;
     1500    0.002    0.000    0.029    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/socket.py:223(meth)&lt;br /&gt;
     1500    0.026    0.000    0.026    0.000 {method 'sendall' of '_socket.socket' objects}&lt;br /&gt;
     5000    0.007    0.000    0.014    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/catch23.py:79(struct_unpack)&lt;br /&gt;
     1000    0.001    0.000    0.013    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:562(_handle_ok)&lt;br /&gt;
    12000    0.011    0.000    0.011    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:167(read_lc_string)&lt;br /&gt;
     1000    0.004    0.000    0.010    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:199(parse_ok)&lt;br /&gt;
     3000    0.005    0.000    0.009    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:152(to_mysql)&lt;br /&gt;
     1500    0.003    0.000    0.007    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:118(make_command)&lt;br /&gt;
     3000    0.003    0.000    0.007    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:130(quote)&lt;br /&gt;
     1500    0.005    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/network.py:53(_prepare_packets)&lt;br /&gt;
     1500    0.003    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:406(_handle_result)&lt;br /&gt;
     3000    0.003    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:102(escape)&lt;br /&gt;
     1000    0.002    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:252(parse_eof)&lt;br /&gt;
      500    0.001    0.000    0.005    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:579(_handle_eof)&lt;br /&gt;
     2000    0.003    0.000    0.005    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:545(_handle_server_status)&lt;br /&gt;
     1500    0.003    0.000    0.004    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:53(int1store)&lt;br /&gt;
     3000    0.003    0.000    0.003    0.000 {method 'encode' of 'str' objects}&lt;br /&gt;
     9001    0.003    0.000    0.003    0.000 {method 'replace' of 'str' objects}&lt;br /&gt;
     1500    0.003    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:295(_reset_result)&lt;br /&gt;
     4500    0.003    0.000    0.003    0.000 {_struct.pack}&lt;br /&gt;
     2500    0.003    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:296(read_lc_int)&lt;br /&gt;
      500    0.002    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:757(_handle_eof)&lt;br /&gt;
     6027    0.003    0.000    0.003    0.000 {method 'format' of 'str' objects}&lt;br /&gt;
     3000    0.002    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:1221(_set_unread_result)&lt;br /&gt;
      500    0.001    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:219(parse_column_count)&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 {repr}&lt;br /&gt;
     4527    0.002    0.000    0.002    0.000 {getattr}&lt;br /&gt;
     4000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/constants.py:34(flag_is_set)&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:383(_handle_noresultset)&lt;br /&gt;
     4000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:1233(_get_unread_result)&lt;br /&gt;
     2001    0.001    0.000    0.001    0.000 {method 'items' of 'dict' objects}&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:307(_have_unread_result)&lt;br /&gt;
     3000    0.001    0.000    0.001    0.000 {method 'lower' of 'str' objects}&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 {range}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:173(_str_to_mysql)&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:169(_float_to_mysql)&lt;br /&gt;
     1000    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:161(_int_to_mysql)&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:1313(_get_getwarnings)&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:200(reset)&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:397(_handle_resultset)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/__init__.py:71(search_function)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/ascii.py:41(getregentry)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/contextlib.py:21(__exit__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/__init__.py:49(normalize_encoding)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 test.py:8(profiled)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/codecs.py:77(__new__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {__import__}&lt;br /&gt;
        3    0.000    0.000    0.000    0.000 {method 'get' of 'dict' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'split' of 'str' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {built-in method __new__ of type object at 0x1001534e8}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'translate' of 'str' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'join' of 'str' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {hasattr}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DBAPI:  &amp;lt;module 'MySQLdb' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/__init__.pyc'&amp;gt;&lt;br /&gt;
         47503 function calls in 0.918 seconds&lt;br /&gt;
&lt;br /&gt;
   Ordered by: cumulative time&lt;br /&gt;
&lt;br /&gt;
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;
     1500    0.009    0.000    0.917    0.001 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:164(execute)&lt;br /&gt;
     1500    0.002    0.000    0.890    0.001 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:353(_query)&lt;br /&gt;
     1500    0.004    0.000    0.498    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:315(_do_query)&lt;br /&gt;
     1500    0.016    0.000    0.391    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:358(_post_get_result)&lt;br /&gt;
     1500    0.001    0.000    0.375    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:324(_fetch_row)&lt;br /&gt;
      500    0.374    0.001    0.374    0.001 {built-in method fetch_row}&lt;br /&gt;
     1500    0.007    0.000    0.362    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:142(_do_get_result)&lt;br /&gt;
     1500    0.002    0.000    0.351    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:351(_get_result)&lt;br /&gt;
     1500    0.349    0.000    0.349    0.000 {method 'store_result' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.131    0.000    0.131    0.000 {method 'query' of '_mysql.connection' objects}&lt;br /&gt;
     4000    0.002    0.000    0.010    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:184(&amp;lt;genexpr&amp;gt;)&lt;br /&gt;
     3000    0.002    0.000    0.008    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/connections.py:267(literal)&lt;br /&gt;
     1500    0.007    0.000    0.007    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:107(_warning_check)&lt;br /&gt;
     3000    0.002    0.000    0.006    0.000 {method 'escape' of '_mysql.connection' objects}&lt;br /&gt;
     6000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:159(_get_db)&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/converters.py:81(Float2Str)&lt;br /&gt;
     2500    0.001    0.000    0.001    0.000 {isinstance}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/connections.py:202(string_literal)&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:380(fetchall)&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 {built-in method describe}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 {method 'string_literal' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {method 'insert_id' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {method 'affected_rows' of '_mysql.connection' objects}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/converters.py:69(Thing2Str)&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 {method 'warning_count' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 {method 'info' of '_mysql.connection' objects}&lt;br /&gt;
     1000    0.000    0.000    0.000    0.000 {method 'iteritems' of 'dict' objects}&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:103(_check_executed)&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 {len}&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 {built-in method field_flags}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/contextlib.py:21(__exit__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 test.py:8(profiled)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DBAPI:  &amp;lt;module 'pymysql' from '/Users/classic/dev/PyMySQL/pymysql/__init__.pyc'&amp;gt;, total seconds 10.046012&lt;br /&gt;
DBAPI:  &amp;lt;module 'mysql.connector' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/__init__.pyc'&amp;gt;, total seconds 15.403260&lt;br /&gt;
DBAPI:  &amp;lt;module 'MySQLdb' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/__init__.pyc'&amp;gt;, total seconds 1.028737&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mike Bayer</name></author>	</entry>

	<entry>
		<id>https://wiki.openstack.org/w/index.php?title=PyMySQL_evaluation&amp;diff=72556</id>
		<title>PyMySQL evaluation</title>
		<link rel="alternate" type="text/html" href="https://wiki.openstack.org/w/index.php?title=PyMySQL_evaluation&amp;diff=72556"/>
				<updated>2015-01-28T20:26:01Z</updated>
		
		<summary type="html">&lt;p&gt;Mike Bayer: /* MySQL DB Drivers Comparison */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= PyMySQL Evaluation =&lt;br /&gt;
&lt;br /&gt;
This page will capture issues related to Openstack moving to the [https://github.com/PyMySQL/PyMySQL/ PyMySQL driver] for MySQL/MariaDB dbapi access.&lt;br /&gt;
&lt;br /&gt;
== Rationale ==&lt;br /&gt;
&lt;br /&gt;
While the [https://github.com/farcepest/MySQLdb1 MySQL-Python] driver is a very mature and stable driver, it does not provide compatibility with either Python 3 or with [http://eventlet.net/doc/patching.html eventlet monkeypatching] (MySQL-Python  [https://github.com/eventlet/eventlet/blob/v0.16.1/eventlet/patcher.py#L225-L237 can be monkeypatched] with eventlet, but this feature disabled by default and missed in documentation). So OpenStack's usage of MySQL-Python, combined with the fact that concurrency is provided by eventlet, means that we currently have fully serialized database access within a single process, that is, only one database command occurs at a time within an Openstack Python process.&lt;br /&gt;
&lt;br /&gt;
== Drivers Under Consideration ==&lt;br /&gt;
&lt;br /&gt;
The two drivers that are known to provide eventlet-monkeypatch compatibility are MySQL-Connector and PyMySQL, as they are written in pure Python.   The two other well-known drivers for MySQL are MySQL-Python and OurSQL, both of which are written in C and offer no explicit support for eventlet or async.  They can reportedly be built to support gevent using a system called [https://github.com/CMGS/greenify Greenify], but the maturity and/or stabiltiy of this system is unknown.&lt;br /&gt;
&lt;br /&gt;
A comparison at http://www.diamondtin.com/2014/sqlalchemy-gevent-mysql-python-drivers-comparison/ illustrates performance metrics observed with all four of these drivers, with MySQL-python built both without and with the &amp;quot;greenify&amp;quot; system.&lt;br /&gt;
&lt;br /&gt;
A summary of the status of all four drivers is as follows.&lt;br /&gt;
&lt;br /&gt;
=== MySQL-Python ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/farcepest/MySQLdb1 MySQL-Python] is the most widely used Python driver.  However, it does not support Python 3, and it does not support async systems unless used within a thread pool.   While a system known as Greenify can potentially resolve the latter situation, the Python 3 limitation is still a deal-breaker - pull request with Python 3 support [https://github.com/farcepest/MySQLdb1/pull/62 was proposed] to MySQL-python in April 2014 and still not merged.&lt;br /&gt;
&lt;br /&gt;
=== mysqlclient ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/PyMySQL/mysqlclient-python mysqlclient] is a fork of MySQL-python. It add Python 3.3 support and merges some pull requests.&lt;br /&gt;
&lt;br /&gt;
=== OurSQL ===&lt;br /&gt;
&lt;br /&gt;
[https://pythonhosted.org/oursql/ OurSQL] is an alternative MySQL driver that is also written in C.  It features fast performance and uses a different execution model than MySQL-python, using prepared statements.    OurSQL does have a Python 3 port hosted on [https://launchpad.net/oursql/py3k/ launchpad] but is not integrated with OurSQL itself nor is it published on Pypi.     Because it is written in C, it does not offer compatibility with async systems including eventlet-style monkeypatching.&lt;br /&gt;
&lt;br /&gt;
Besides the shaky Python 3 support and lack of eventlet support, OurSQL is also not maintained, seeing its last master commit on [https://code.launchpad.net/~jerub/oursql/master 2012-06-05].&lt;br /&gt;
&lt;br /&gt;
=== MySQL-Connector-Python ===&lt;br /&gt;
&lt;br /&gt;
[https://pypi.python.org/pypi/mysql-connector-python MySQL-connector-Python] is a pure Python MySQL driver, and is now released under the auspices of the MySQL project itself, as owned by Oracle.  MySQL-connector-Python supports both Python 3 as well as eventlet monkeypatching, and is well maintained.  It is endorsed by Oracle as the official Oracle-supported driver for MySQL, so to that extent, it is in most ways Openstack's first choice in driver.  However, Oracle refuses to publish MySQL-connector-Python on Pypi, which is critical to the Openstack infrastructure.  Repeated attempts to communicate with Oracle in order to resolve this issue have not made any progress.    Therefore, for this one unfortunate reason, MySQL-connector-Python will not have a place in the Openstack ecosystem unless this issue is resolved.&lt;br /&gt;
&lt;br /&gt;
=== PyMySQL ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/PyMySQL/PyMySQL PyMySQL] is a pure Python MySQL&lt;br /&gt;
driver, first written as a rough port of the MySQL-Python driver.&lt;br /&gt;
PyMySQL meets all of Openstack's criterion for a driver: it is fully&lt;br /&gt;
open source, hosted on Github, released on Pypi, is actively&lt;br /&gt;
maintained, is written in pure Python so is eventlet-monkeypatch&lt;br /&gt;
compatible, and is fully Python 3 compatible.&lt;br /&gt;
&lt;br /&gt;
As this document is named &amp;quot;PyMySQL Evaluation&amp;quot;, it should be apparent that&lt;br /&gt;
this is the driver Openstack is currently leaning towards; because it is the only&lt;br /&gt;
one that meets all criteria fully, it is already most likely the &amp;quot;winner&amp;quot;.  However, it does&lt;br /&gt;
have some minor code quality issues which hopefully can be addressed in some&lt;br /&gt;
way; the section below titled &amp;quot;PyMySQL Code Review&amp;quot; will summarize the&lt;br /&gt;
current state of the code.&lt;br /&gt;
&lt;br /&gt;
===MySQL DB Drivers Comparison===&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Project !! PyPi hosted !! Eventlet friendly !! Python 3 compatibility !! Maturity and/or stability  !! Comment&lt;br /&gt;
|-&lt;br /&gt;
| MySQL-Python || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: orange;&amp;quot; | Partial || style=&amp;quot;background-color: red;&amp;quot; | No  || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || Can be monkeypatched by eventlet, but only to enable thread pooling&lt;br /&gt;
|-&lt;br /&gt;
| mysqlclient || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: orange;&amp;quot; | Partial || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes  || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || MySQL-python fork, so should be similar to it&lt;br /&gt;
|-&lt;br /&gt;
| OurSQL || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: red;&amp;quot; | No || style=&amp;quot;background-color: orange;&amp;quot; | Yes, but not Pypi hosted  || style=&amp;quot;background-color: orange;&amp;quot; | ??? || Has not seen commits/releases in two years&lt;br /&gt;
|-&lt;br /&gt;
| MySQL-Connector-Python || style=&amp;quot;background-color: red;&amp;quot; | No || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes  || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || The official Oracle-supported driver for MySQL&lt;br /&gt;
|-&lt;br /&gt;
| PyMySQL || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes  || style=&amp;quot;background-color: orange;&amp;quot; | Yes, however see notes below. || Actively maintained and popular.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== PyMySQL Code Review ==&lt;br /&gt;
&lt;br /&gt;
PyMySQL started roughly as a pure Python port of MySQL-Python.   The sections below will&lt;br /&gt;
detail various aspects of the driver and the status of the code.   Overall, the general&lt;br /&gt;
tone of PyMySQL is one of code that was written in the spirit of pragmatism and immediate&lt;br /&gt;
need; it is straightforward, relatively free of serious antipatterns and performs&lt;br /&gt;
its task in a matter-of-fact way.  However,&lt;br /&gt;
it lacks polish and completeness in many areas that would normally be in better&lt;br /&gt;
condition for a more mature project.  These areas are all highly fixable, and it is&lt;br /&gt;
hoped that these items can be shared with the current PyMySQL developers, through&lt;br /&gt;
a combination of this document itself as well as new issues and pull requests&lt;br /&gt;
on the PyMySQL tracker, so that this fairly promising library can be pushed&lt;br /&gt;
to the next level.   This would require that the developers are amenable to&lt;br /&gt;
these improvements.&lt;br /&gt;
&lt;br /&gt;
=== Coding Style / Pep8 ===&lt;br /&gt;
&lt;br /&gt;
PyMySQL's coding style is pretty good.  The majority of it passes all&lt;br /&gt;
flake8 tests, save for a few dozen whitespace issues and&lt;br /&gt;
some long lines here and there within the core&lt;br /&gt;
modules, and moreso within the tests which appear to be a bit more&lt;br /&gt;
crufty than the core modules.  A full run of flake8 produces only 128&lt;br /&gt;
errors, and with a default run of autopep8 against E1,E2,E3, we can&lt;br /&gt;
fix all of them in one pass except for 38 remaining long line warnings.&lt;br /&gt;
The code layout is mostly idiomatic and reasonable.&lt;br /&gt;
&lt;br /&gt;
The codebase is probably short on docstrings.  Those methods that are&lt;br /&gt;
part of the public DBAPI do have docstrings, but tend to be extremely&lt;br /&gt;
terse (example, docstring for cursor.execute(): &amp;quot;'Execute a query'&amp;quot;).&lt;br /&gt;
Other methods that aren't public tend to not have any docstrings, but&lt;br /&gt;
the codebase is not at all hard to read and the purpose of methods and&lt;br /&gt;
functions is pretty easy to discern just by their names and implementations,&lt;br /&gt;
however those who seek inline documentation as a means to understand a codebase&lt;br /&gt;
will be disappointed.   The terseness of public API docstrings is more of&lt;br /&gt;
an issue in terms of the overall lack of documentation for PyMySQL,&lt;br /&gt;
see the section &amp;quot;Documentation&amp;quot; below.&lt;br /&gt;
&lt;br /&gt;
=== Test Coverage ===&lt;br /&gt;
&lt;br /&gt;
The test coverage for PyMySQL is definitely lacking.   From a visual&lt;br /&gt;
inspection alone, it's apparent that of the dozen or so test modules,&lt;br /&gt;
most of them have less then ten tests each and the modules are very short.&lt;br /&gt;
For a full test run, there's only 126 tests; contrast this to&lt;br /&gt;
MySQL-Connector-Python which has 531 tests.&lt;br /&gt;
&lt;br /&gt;
Additionally, all of the tests are live round-trip tests against a&lt;br /&gt;
real database.  There are no non-live unit tests of any kind,&lt;br /&gt;
leaving codepaths that are not easily exercised&lt;br /&gt;
on a generic MySQL database out in the cold.  This means that features designed&lt;br /&gt;
for particular versions of MySQL, particular datatypes, particular error&lt;br /&gt;
conditions, and particular kinds of message packets that aren't created for&lt;br /&gt;
real are simply not tested.&lt;br /&gt;
&lt;br /&gt;
The lack of tests is apparent when run with coverage.  The test suite itself&lt;br /&gt;
does not appear to have any signs that it is normally run with coverage&lt;br /&gt;
turned on, with no directives in tox.ini or similar.   With coverage,&lt;br /&gt;
the overall coverage is 85%, but the modules most lacking in coverage&lt;br /&gt;
are also the most critical to PyMySQL's core functionality,&lt;br /&gt;
connections.py, converters.py and cursors.py:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Name                                                              Stmts   Miss  Cover&lt;br /&gt;
-------------------------------------------------------------------------------------&lt;br /&gt;
pymysql/__init__                                                     51      3    94%&lt;br /&gt;
pymysql/_compat                                                      14      4    71%&lt;br /&gt;
pymysql/_socketio                                                    72     30    58%&lt;br /&gt;
pymysql/charset                                                     228      3    99%&lt;br /&gt;
pymysql/connections                                                 827    188    77%&lt;br /&gt;
pymysql/constants/CLIENT                                             18      0   100%&lt;br /&gt;
pymysql/constants/COMMAND                                            32      0   100%&lt;br /&gt;
pymysql/constants/ER                                                471      0   100%&lt;br /&gt;
pymysql/constants/FIELD_TYPE                                         29      0   100%&lt;br /&gt;
pymysql/constants/FLAG                                               15      0   100%&lt;br /&gt;
pymysql/constants/SERVER_STATUS                                      10      0   100%&lt;br /&gt;
pymysql/constants/__init__                                            0      0   100%&lt;br /&gt;
pymysql/converters                                                  148     51    66%&lt;br /&gt;
pymysql/cursors                                                     273     37    86%&lt;br /&gt;
pymysql/err                                                          40      1    98%&lt;br /&gt;
pymysql/tests/__init__                                               14      5    64%&lt;br /&gt;
pymysql/tests/base                                                   25      2    92%&lt;br /&gt;
pymysql/tests/test_DictCursor                                        73      2    97%&lt;br /&gt;
pymysql/tests/test_SSCursor                                          56      6    89%&lt;br /&gt;
pymysql/tests/test_basic                                            186      4    98%&lt;br /&gt;
pymysql/tests/test_connection                                        57      8    86%&lt;br /&gt;
pymysql/tests/test_example                                           17      2    88%&lt;br /&gt;
pymysql/tests/test_issues                                           300     48    84%&lt;br /&gt;
pymysql/tests/test_load_local                                        39      2    95%&lt;br /&gt;
pymysql/tests/test_nextset                                           43      4    91%&lt;br /&gt;
pymysql/tests/thirdparty/__init__                                     7      5    29%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/__init__                        6      2    67%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/capabilities                  196     23    88%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/dbapi20                       423    121    71%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_capabilities      73      6    92%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_dbapi20          100      2    98%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_nonstandard       57      3    95%&lt;br /&gt;
pymysql/times                                                        12      0   100%&lt;br /&gt;
pymysql/util                                                         14      7    50%&lt;br /&gt;
-------------------------------------------------------------------------------------&lt;br /&gt;
TOTAL                                                              3926    569    86%&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The lack of coverage in converters.py refers to functions related to handling&lt;br /&gt;
incoming bound values as well as outgoing result values.    Key data escaping&lt;br /&gt;
features such as escaping of dictionaries, sets, some unicode objects and&lt;br /&gt;
byte objects as well as many pathways into date and time-processing functions are not&lt;br /&gt;
covered.&lt;br /&gt;
&lt;br /&gt;
In connections.py, uncovered features include various SSL and&lt;br /&gt;
very legacy (e.g. version 3.23) MySQL features, but also some protocol parsing&lt;br /&gt;
features.&lt;br /&gt;
&lt;br /&gt;
In cursors.py, there is coverage for an elaborate and mostly undocumented&lt;br /&gt;
performance-related feature that rewrites an INSERT statement to have an&lt;br /&gt;
extended VALUES clause (_do_execute_many), however it fails to cover the very&lt;br /&gt;
likely case where the function will need to break the input set into&lt;br /&gt;
multiple chunks:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;gt;         for arg in args:&lt;br /&gt;
&amp;gt;             v = values % escape(arg, conn)&lt;br /&gt;
&amp;gt;             if isinstance(v, text_type):&lt;br /&gt;
&amp;gt;                 v = v.encode(encoding)&lt;br /&gt;
&amp;gt;             if len(sql) + len(v) + 1 &amp;gt; max_stmt_length:&lt;br /&gt;
!                 rows += self.execute(sql)&lt;br /&gt;
!                 sql = bytearray(prefix)&lt;br /&gt;
&amp;gt;             else:&lt;br /&gt;
&amp;gt;                 sql += b','&lt;br /&gt;
&amp;gt;             sql += v&lt;br /&gt;
&amp;gt;         rows += self.execute(sql)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The most prominent missing coverage in cursors.py seems to be&lt;br /&gt;
for its &amp;quot;scrollable cursor&amp;quot; support.&lt;br /&gt;
&lt;br /&gt;
Beyond the lack of completeness in testing, many of the tests themselves&lt;br /&gt;
are written in an expedient &amp;quot;one giant test&amp;quot; style where dozens of&lt;br /&gt;
assertions and individual behaviors are lumped into one big test&lt;br /&gt;
case (see test_SSCursor.py, test_basic.py-&amp;gt;test_datatypes for examples).&lt;br /&gt;
Test cases like these are difficult to work with when debugging&lt;br /&gt;
regressions as well as when tests for new features need to be added, and ideally&lt;br /&gt;
these should be broken out into clean single-feature tests with consistent&lt;br /&gt;
fixtures.&lt;br /&gt;
&lt;br /&gt;
There is also a suite called &amp;quot;test_issues&amp;quot;, which intends to&lt;br /&gt;
accumulate tests against specific issues that have been reported. This&lt;br /&gt;
isn't a bad idea, though it would be nice to see an effort made into&lt;br /&gt;
categorizing these tests into the actual features they are testing,&lt;br /&gt;
rather than a meaningless list of issue numbers, which could then form&lt;br /&gt;
as the basis for new suites centered around those areas of&lt;br /&gt;
functionality.  The issues reported should be used as the inspiration&lt;br /&gt;
for improvement of the test suite, rather than just another line-item&lt;br /&gt;
to be filed away.&lt;br /&gt;
&lt;br /&gt;
=== Library Documentation ===&lt;br /&gt;
&lt;br /&gt;
As the Python DBAPI is already a well-documented API, and the users of&lt;br /&gt;
PyMySQL are typically coming from MySQL-Python which is documented&lt;br /&gt;
to some degree and for which the use contract is widely known, it seems&lt;br /&gt;
unlikely that users of PyMySQL are eager for comprehensive documentation&lt;br /&gt;
specific to this library.&lt;br /&gt;
&lt;br /&gt;
That being said, PyMySQL does not appear to have any real library&lt;br /&gt;
documentation at all.  There's a README which&lt;br /&gt;
essentially refers to pep-249, an example.py in the root which shows a&lt;br /&gt;
very basic connection / round trip, and that's it. In&lt;br /&gt;
order to know anything about the API, specific parameters, optimization&lt;br /&gt;
strategies, behaviors, etc., one either has to go off of the MySQL-Python documentation and assume&lt;br /&gt;
PyMySQL also features the same parameter, or read the source code.&lt;br /&gt;
&lt;br /&gt;
PyMySQL probably hasn't had much urgent need for real documentation thus&lt;br /&gt;
far.  However, relying upon being a port of MySQL-Python is fast becoming&lt;br /&gt;
something that can no longer be relied upon, as MySQL-Python's development&lt;br /&gt;
is quite stalled, and PyMySQL should aspire to move into the future&lt;br /&gt;
of MySQL and Python with its own featureset and behaviors.  To that end, it would be a &lt;br /&gt;
great idea if it at least made the start&lt;br /&gt;
of a rudimentary Sphinx build including autodoc for module documentation&lt;br /&gt;
which could then be published up to RTD.   This would be very easy to get&lt;br /&gt;
started, and once present, new documentation sections can be added&lt;br /&gt;
iteratively.&lt;br /&gt;
&lt;br /&gt;
=== Architecture and Performance ===&lt;br /&gt;
&lt;br /&gt;
As stated before, PyMySQL has a really matter-of-fact and straightforward&lt;br /&gt;
implementation.   The code is very readable and it's easy to discern even&lt;br /&gt;
unusual features such as the INSERT..VALUES rewriting.  As far as the design,&lt;br /&gt;
there are lots of areas where performance suffers a bit at the Python level;&lt;br /&gt;
these areas could be greatly improved very easily with a little bit of&lt;br /&gt;
attention.&lt;br /&gt;
&lt;br /&gt;
As we are dealing with a driver that is communicating with a low level&lt;br /&gt;
protocol over a socket, it's critical that sending and receiving messages&lt;br /&gt;
is done as efficiently as possible.  The mechanisms used in PyMySQL rely&lt;br /&gt;
heavily on standard Python objects, without the use of __slots__; this means&lt;br /&gt;
that for individual messages on a socket, we are creating a heavyweight object,&lt;br /&gt;
calling its __init__() method and a creating a new __dict__ each time;&lt;br /&gt;
the most common object, OKPacketWrapper(), already wraps a MysqlPacket()&lt;br /&gt;
object, so we're doing two __init__()s in this common case.  OKPacketWrapper&lt;br /&gt;
also implements a `__getattr__` scheme&lt;br /&gt;
to proxy attribute access from itself to the internal MysqlPacket; this is a&lt;br /&gt;
an inefficient mode of operation in Python as it means a lookup must first&lt;br /&gt;
fail on the OKPacketWrapper before it invokes the `__getattr__()` method&lt;br /&gt;
and does another attribute lookup.   A reorg of the packet classes to produce&lt;br /&gt;
far fewer objects for messages as well as to use __slots__ would be&lt;br /&gt;
recommended.&lt;br /&gt;
&lt;br /&gt;
There are also lots of areas where function call overhead is unnecessarily&lt;br /&gt;
high due to the organization of calls.  For example, when calling upon&lt;br /&gt;
cursor.execute(), for parameters passed as a dictionary under Python 2K,&lt;br /&gt;
the isinstance() builtin function will be called on the incoming arguments&lt;br /&gt;
four times at a minimum, with two of each call against the exact same&lt;br /&gt;
criteria.  Additional logic will then call isinstance() on every key of&lt;br /&gt;
the dictionary as well as at least twice on every value within the dictionary,&lt;br /&gt;
in order to determine if the value needs to be converted to bytes as well&lt;br /&gt;
as for some top-level escaping logic checks; the escaping logic then&lt;br /&gt;
goes further with more checks for type() to match the object to a final&lt;br /&gt;
converter function.   While the flow of this logic does appear to be&lt;br /&gt;
reasonable and straightforward, it isn't ideal for a low-level database&lt;br /&gt;
driver library where speed is a high priority; the various wrappers and&lt;br /&gt;
processors can easily be reorganized here to reduce the number of isinstance()&lt;br /&gt;
and type() calls significantly, which would save a lot on function call&lt;br /&gt;
overhead.  It is likely there are many other areas in the codebase where&lt;br /&gt;
similar reductions in call counts can be made without too much disruption.&lt;br /&gt;
&lt;br /&gt;
In Appendix A, a short performance suite is illustrated.  This&lt;br /&gt;
is run against PyMySQL, Mysql-Connector-Python, and MySQL-Python.&lt;br /&gt;
PyMySQL does in fact demonstrate the&lt;br /&gt;
highest call-count behavior, logging 26,851,003 function calls&lt;br /&gt;
compared to MySQL-connector's 11,674,074 for the same operations.  To PyMySQL's&lt;br /&gt;
credit, it somehow completes the test 25-50% faster than MySQL-connector&lt;br /&gt;
despite having more than twice as many function calls; this probably indicates&lt;br /&gt;
the use of something very slow within MySQL-connector, such as catching&lt;br /&gt;
an exception within a tight loop.  However, the real bar we're comparing to&lt;br /&gt;
is MySQL-Python, which is written&lt;br /&gt;
in C and does the whole test with only 47,503 Python function calls; while&lt;br /&gt;
PyMySQL and MySQL-connector-Python compete for time within the 10-15 second&lt;br /&gt;
range without profiling, MySQL-Python completes the whole test in **1.03 seconds**, a 1000%&lt;br /&gt;
improvement over PyMySQL.   This speed difference is not PyMySQL's fault,&lt;br /&gt;
as pure Python is known to be extremely slow compared to C code.&lt;br /&gt;
However, there's a lot of easy wins in PyMySQL's codebase where the function&lt;br /&gt;
call count could probably be reduced dramatically to be even better than that&lt;br /&gt;
of MySQL-connector-Python; ideally, the driver would be able to run this&lt;br /&gt;
test possibly 5-6x slower than MySQL-Python rather than 10x.&lt;br /&gt;
&lt;br /&gt;
=== History Documentation ===&lt;br /&gt;
&lt;br /&gt;
PyMySQL includes a CHANGELOG file in its root which describes the bug fixes&lt;br /&gt;
and features of each release.  The construction of this file is definitely&lt;br /&gt;
within the &amp;quot;expedient&amp;quot; style noted for PyMySQL overall, and for it to be&lt;br /&gt;
genuinely useful for enterprise-level software, it would need to be&lt;br /&gt;
much more complete in its detail.    Each bug fix mentioned is only referred to&lt;br /&gt;
tersely without any link to a pull request or bug report.  While some can&lt;br /&gt;
be linked to a specific change through detective work&lt;br /&gt;
(&amp;quot;Cursor.fetchall() and .fetchmany now return list, not tuple&amp;quot;), it would&lt;br /&gt;
be very difficult for many&lt;br /&gt;
(&amp;quot;Fixed BIT type handling&amp;quot;, &amp;quot;Fixed GC errors&amp;quot; - what was the issue?)&lt;br /&gt;
and in many cases impossible (&amp;quot;Improved Py3k support&amp;quot;).  The file also&lt;br /&gt;
includes no dates so one has to look at the git tags to figure this&lt;br /&gt;
part out (fortunately, releases to seem to be git tagged at least).&lt;br /&gt;
For a user trying to ascertain if particular issues are&lt;br /&gt;
fixed or if behaviors have changed, this file is at best highly frustrating.&lt;br /&gt;
&lt;br /&gt;
A listing of release dates illustrates an average of two releases per year,&lt;br /&gt;
excluding 2012 when PyMySQL was seeking a new maintainer:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
2014-12-02 12:05:10 -0200  (tag: pymysql-0.6.3)&lt;br /&gt;
2014-04-21 14:28:47 -0300  (tag: pymysql-0.6.2)&lt;br /&gt;
2013-10-11 13:36:56 -0300  (tag: pymysql-0.6.1)&lt;br /&gt;
2013-10-04 14:25:14 -0300  (tag: pymysql-0.6)&lt;br /&gt;
2011-11-08 10:27:41 -0800  (tag: pymysql-0.5)&lt;br /&gt;
2010-12-27 17:28:16 +0000  (tag: pymysql-0.4)&lt;br /&gt;
2010-09-03 00:55:29 +0000  (tag: pymysql-0.3)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Community Involvement ===&lt;br /&gt;
&lt;br /&gt;
This is an area where PyMySQL seems to shine, and might even be an advantage&lt;br /&gt;
as compared to MySQL-Connector-Python, which remains under the cloak&lt;br /&gt;
of MySQL's vastly overstuffed bugtracker under the ownership of&lt;br /&gt;
the famously-opaque Oracle.   For a small, low key library without&lt;br /&gt;
any real documentation and only 126 tests, it features 33 contributors on&lt;br /&gt;
Github and is up to pull request #285, which is quite high.  Because&lt;br /&gt;
PyMySQL is small and straightforward, it does lend itself to contribution&lt;br /&gt;
and seems to do very well in this area.   The developers so far have been&lt;br /&gt;
responsive to issues and pull requests made by the author.&lt;br /&gt;
&lt;br /&gt;
Based on github contributor graphs it appears that PyMySQL had a lull&lt;br /&gt;
in 2012 but was picked up again afterwards in 2013.  As of 2012, its most&lt;br /&gt;
prominent developer Pete Hunt stopped development, and on July 25, 2013&lt;br /&gt;
added a note to the README that the project was looking for a new maintainer.&lt;br /&gt;
As of 2013 it was picked up by the current most prominent developers&lt;br /&gt;
Marcel Rodrigues (github username lecram) and INADA Naoki&lt;br /&gt;
(github username methane).&lt;br /&gt;
&lt;br /&gt;
Most issues that have been noted thus far within the areas of testing,&lt;br /&gt;
documentation and architecture are all very fixable; if the Openstack community&lt;br /&gt;
can provide resources to address some of these areas, and if the PyMySQL&lt;br /&gt;
developers are reasonably open to accepting new changes and producing new&lt;br /&gt;
releases in a timely fashion, the PyMySQL driver&lt;br /&gt;
could rapidly become a fully formidable contender in the MySQL driver field.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Appendix A - Performance Tests ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
import cProfile&lt;br /&gt;
import StringIO&lt;br /&gt;
import pstats&lt;br /&gt;
import contextlib&lt;br /&gt;
import time&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextlib.contextmanager&lt;br /&gt;
def profiled(dbapi):&lt;br /&gt;
    pr = cProfile.Profile()&lt;br /&gt;
    pr.enable()&lt;br /&gt;
    yield&lt;br /&gt;
    pr.disable()&lt;br /&gt;
    s = StringIO.StringIO()&lt;br /&gt;
    ps = pstats.Stats(pr, stream=s).sort_stats('cumulative')&lt;br /&gt;
    ps.print_stats()&lt;br /&gt;
    print &amp;quot;DBAPI:  %s&amp;quot; % dbapi&lt;br /&gt;
    print s.getvalue()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextlib.contextmanager&lt;br /&gt;
def timeonly(dbapi):&lt;br /&gt;
    now = time.time()&lt;br /&gt;
    try:&lt;br /&gt;
        yield&lt;br /&gt;
    finally:&lt;br /&gt;
        total = time.time() - now&lt;br /&gt;
        print &amp;quot;DBAPI:  %s, total seconds %f&amp;quot; % (dbapi, total)&lt;br /&gt;
&lt;br /&gt;
import MySQLdb&lt;br /&gt;
import pymysql&lt;br /&gt;
from mysql import connector as mysqlconnector&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def go(dbapi, ctx):&lt;br /&gt;
    conn = dbapi.connect(&lt;br /&gt;
        user='scott', passwd='tiger', host='localhost', db='test')&lt;br /&gt;
    cursor = conn.cursor()&lt;br /&gt;
    cursor.execute(&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
CREATE TABLE IF NOT EXISTS test_things (&lt;br /&gt;
    x INTEGER,&lt;br /&gt;
    y VARCHAR(255),&lt;br /&gt;
    z FLOAT&lt;br /&gt;
) engine=InnoDB&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;)&lt;br /&gt;
    cursor.execute(&amp;quot;DELETE from test_things&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    with ctx(dbapi):&lt;br /&gt;
        for row in xrange(1000):&lt;br /&gt;
            cursor.execute(&lt;br /&gt;
                &amp;quot;INSERT INTO test_things (x, y, z) &amp;quot;&lt;br /&gt;
                &amp;quot;VALUES (%(x)s, %(y)s, %(z)s)&amp;quot;,&lt;br /&gt;
                {&amp;quot;x&amp;quot;: row, &amp;quot;y&amp;quot;: &amp;quot;row number %d&amp;quot; % row,&lt;br /&gt;
                 &amp;quot;z&amp;quot;: row * 4.57292, }&lt;br /&gt;
            )&lt;br /&gt;
&lt;br /&gt;
        for x in xrange(500):&lt;br /&gt;
            cursor.execute(&lt;br /&gt;
                &amp;quot;select * from test_things&amp;quot;)&lt;br /&gt;
            for row in cursor.fetchall():&lt;br /&gt;
                row[0], row[1], row[2]&lt;br /&gt;
&lt;br /&gt;
    cursor.close()&lt;br /&gt;
    conn.close()&lt;br /&gt;
&lt;br /&gt;
go(pymysql, profiled)&lt;br /&gt;
go(mysqlconnector, profiled)&lt;br /&gt;
go(MySQLdb, profiled)&lt;br /&gt;
&lt;br /&gt;
go(pymysql, timeonly)&lt;br /&gt;
go(mysqlconnector, timeonly)&lt;br /&gt;
go(MySQLdb, timeonly)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DBAPI:  &amp;lt;module 'pymysql' from '/Users/classic/dev/PyMySQL/pymysql/__init__.pyc'&amp;gt;&lt;br /&gt;
         26851003 function calls in 15.924 seconds&lt;br /&gt;
&lt;br /&gt;
   Ordered by: cumulative time&lt;br /&gt;
&lt;br /&gt;
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;
     1500    0.010    0.000   15.923    0.011 /Users/classic/dev/PyMySQL/pymysql/cursors.py:105(execute)&lt;br /&gt;
     1500    0.003    0.000   15.883    0.011 /Users/classic/dev/PyMySQL/pymysql/cursors.py:269(_query)&lt;br /&gt;
     1500    0.004    0.000   15.859    0.011 /Users/classic/dev/PyMySQL/pymysql/connections.py:746(query)&lt;br /&gt;
     1500    0.003    0.000   15.819    0.011 /Users/classic/dev/PyMySQL/pymysql/connections.py:892(_read_query_result)&lt;br /&gt;
     1500    0.004    0.000   15.812    0.011 /Users/classic/dev/PyMySQL/pymysql/connections.py:1097(read)&lt;br /&gt;
      500    0.003    0.000   15.559    0.031 /Users/classic/dev/PyMySQL/pymysql/connections.py:1154(_read_result_packet)&lt;br /&gt;
      500    0.900    0.002   15.429    0.031 /Users/classic/dev/PyMySQL/pymysql/connections.py:1187(_read_rowdata_packet)&lt;br /&gt;
   500000    3.417    0.000    9.173    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1200(_read_row_from_packet)&lt;br /&gt;
  2012000    1.157    0.000    5.307    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:311(read_length_coded_string)&lt;br /&gt;
   504500    1.495    0.000    5.209    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:845(_read_packet)&lt;br /&gt;
  2014500    1.248    0.000    3.035    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:293(read_length_encoded_integer)&lt;br /&gt;
  3539500    2.439    0.000    2.750    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:245(read)&lt;br /&gt;
  1009000    0.779    0.000    2.307    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:870(_read_bytes)&lt;br /&gt;
  1009000    1.197    0.000    1.423    0.000 {method 'read' of '_io.BufferedReader' objects}&lt;br /&gt;
   508500    0.379    0.000    0.695    0.000 /Users/classic/dev/PyMySQL/pymysql/util.py:3(byte2int)&lt;br /&gt;
  5058000    0.465    0.000    0.465    0.000 {len}&lt;br /&gt;
   500500    0.222    0.000    0.455    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1146(_check_packet_is_eof)&lt;br /&gt;
   504500    0.205    0.000    0.375    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:342(check_error)&lt;br /&gt;
  2504000    0.275    0.000    0.275    0.000 {method 'append' of 'list' objects}&lt;br /&gt;
   501500    0.182    0.000    0.230    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:326(is_eof_packet)&lt;br /&gt;
     3500    0.009    0.000    0.226    0.000 /Users/classic/dev/PyMySQL/pymysql/_socketio.py:45(readinto)&lt;br /&gt;
     3500    0.207    0.000    0.207    0.000 {method 'recv_into' of '_socket.socket' objects}&lt;br /&gt;
  1022000    0.203    0.000    0.203    0.000 {_struct.unpack}&lt;br /&gt;
   526000    0.200    0.000    0.200    0.000 {isinstance}&lt;br /&gt;
  1501000    0.195    0.000    0.195    0.000 {method 'get' of 'dict' objects}&lt;br /&gt;
   504500    0.176    0.000    0.176    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:238(__init__)&lt;br /&gt;
   504500    0.170    0.000    0.170    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:339(is_error_packet)&lt;br /&gt;
  2014500    0.167    0.000    0.167    0.000 {ord}&lt;br /&gt;
      500    0.006    0.000    0.125    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1224(_get_descriptions)&lt;br /&gt;
     2000    0.003    0.000    0.087    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:361(__init__)&lt;br /&gt;
     2000    0.019    0.000    0.084    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:365(__parse_field_descriptor)&lt;br /&gt;
   500000    0.056    0.000    0.056    0.000 /Users/classic/dev/PyMySQL/pymysql/converters.py:301(through)&lt;br /&gt;
     1500    0.005    0.000    0.037    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:915(_execute_command)&lt;br /&gt;
     1500    0.003    0.000    0.027    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:886(_write_bytes)&lt;br /&gt;
     1500    0.002    0.000    0.024    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/socket.py:223(meth)&lt;br /&gt;
     1500    0.021    0.000    0.021    0.000 {method 'sendall' of '_socket.socket' objects}&lt;br /&gt;
     1500    0.020    0.000    0.020    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:276(_do_get_result)&lt;br /&gt;
     1000    0.003    0.000    0.019    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:95(_escape_args)&lt;br /&gt;
     4000    0.002    0.000    0.015    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:99(&amp;lt;genexpr&amp;gt;)&lt;br /&gt;
     1000    0.002    0.000    0.014    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1127(_read_ok_packet)&lt;br /&gt;
     3000    0.003    0.000    0.013    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:712(escape)&lt;br /&gt;
     1000    0.005    0.000    0.012    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:416(__init__)&lt;br /&gt;
     8000    0.011    0.000    0.011    0.000 {method 'decode' of 'str' objects}&lt;br /&gt;
     3500    0.003    0.000    0.007    0.000 {method '_checkReadable' of '_io._IOBase' objects}&lt;br /&gt;
     2000    0.003    0.000    0.006    0.000 /Users/classic/dev/PyMySQL/pymysql/converters.py:19(escape_item)&lt;br /&gt;
     4000    0.002    0.000    0.006    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:126(&amp;lt;genexpr&amp;gt;)&lt;br /&gt;
     7500    0.003    0.000    0.005    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:115(ensure_bytes)&lt;br /&gt;
     2000    0.003    0.000    0.005    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:386(description)&lt;br /&gt;
     5500    0.004    0.000    0.004    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:268(advance)&lt;br /&gt;
     1000    0.001    0.000    0.004    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:722(escape_string)&lt;br /&gt;
     3500    0.004    0.000    0.004    0.000 /Users/classic/dev/PyMySQL/pymysql/_socketio.py:87(readable)&lt;br /&gt;
      500    0.002    0.000    0.004    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:442(__init__)&lt;br /&gt;
     3500    0.003    0.000    0.003    0.000 {method '_checkClosed' of '_io._IOBase' objects}&lt;br /&gt;
     1000    0.001    0.000    0.003    0.000 /Users/classic/dev/PyMySQL/pymysql/converters.py:59(escape_string)&lt;br /&gt;
     1500    0.003    0.000    0.003    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1080(__init__)&lt;br /&gt;
     1500    0.001    0.000    0.003    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:92(nextset)&lt;br /&gt;
     4000    0.002    0.000    0.002    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:397(get_column_length)&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 /Users/classic/dev/PyMySQL/pymysql/converters.py:56(escape_float)&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 {method 'sub' of '_sre.SRE_Pattern' objects}&lt;br /&gt;
     3000    0.002    0.000    0.002    0.000 {_struct.pack}&lt;br /&gt;
     6000    0.002    0.000    0.002    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:62(_get_db)&lt;br /&gt;
     1500    0.001    0.000    0.002    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:80(_nextset)&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 /Users/classic/dev/PyMySQL/pymysql/util.py:9(int2byte)&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:244(fetchall)&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {min}&lt;br /&gt;
     2500    0.001    0.000    0.001    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:323(is_ok_packet)&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Users/classic/dev/PyMySQL/pymysql/converters.py:52(escape_int)&lt;br /&gt;
     2000    0.001    0.000    0.001    0.000 {method 'items' of 'dict' objects}&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {getattr}&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1093(__del__)&lt;br /&gt;
     1000    0.000    0.000    0.000    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:259(read_all)&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:67(_check_executed)&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:336(is_load_local_packet)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/contextlib.py:21(__exit__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 test.py:8(profiled)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DBAPI:  &amp;lt;module 'mysql.connector' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/__init__.pyc'&amp;gt;&lt;br /&gt;
         11674074 function calls in 19.049 seconds&lt;br /&gt;
&lt;br /&gt;
   Ordered by: cumulative time&lt;br /&gt;
&lt;br /&gt;
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;
      500    0.640    0.001   18.556    0.037 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:820(fetchall)&lt;br /&gt;
      500    0.003    0.000   11.196    0.022 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:655(get_rows)&lt;br /&gt;
      500    1.561    0.003   11.191    0.022 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:292(read_text_result)&lt;br /&gt;
   504500    3.303    0.000    7.306    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/network.py:219(recv_plain)&lt;br /&gt;
   500000    3.572    0.000    6.625    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:359(row_to_python)&lt;br /&gt;
  1009000    3.155    0.000    3.155    0.000 {method 'recv_into' of '_socket.socket' objects}&lt;br /&gt;
   500000    2.049    0.000    2.329    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:220(read_lc_string_list)&lt;br /&gt;
   500000    0.653    0.000    2.116    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:531(_STRING_to_python)&lt;br /&gt;
   502000    0.378    0.000    1.329    0.000 {method 'decode' of 'bytearray' objects}&lt;br /&gt;
   502000    0.221    0.000    0.951    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/utf_8.py:15(decode)&lt;br /&gt;
   502000    0.730    0.000    0.730    0.000 {_codecs.utf_8_decode}&lt;br /&gt;
   500000    0.514    0.000    0.514    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:413(_INT_to_python)&lt;br /&gt;
   509500    0.510    0.000    0.510    0.000 {_struct.unpack_from}&lt;br /&gt;
     1500    0.009    0.000    0.492    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:452(execute)&lt;br /&gt;
     1500    0.004    0.000    0.437    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:705(cmd_query)&lt;br /&gt;
   500000    0.367    0.000    0.367    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:405(_FLOAT_to_python)&lt;br /&gt;
   504500    0.343    0.000    0.343    0.000 {method 'extend' of 'bytearray' objects}&lt;br /&gt;
     1500    0.007    0.000    0.315    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:481(_send_cmd)&lt;br /&gt;
  2501500    0.275    0.000    0.275    0.000 {method 'append' of 'list' objects}&lt;br /&gt;
   500500    0.246    0.000    0.246    0.000 {method 'startswith' of 'bytearray' objects}&lt;br /&gt;
   524002    0.150    0.000    0.150    0.000 {isinstance}&lt;br /&gt;
     1500    0.011    0.000    0.118    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:613(_handle_result)&lt;br /&gt;
  1004500    0.112    0.000    0.112    0.000 {len}&lt;br /&gt;
   500000    0.092    0.000    0.092    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:204(description)&lt;br /&gt;
     1500    0.007    0.000    0.042    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/network.py:118(send_plain)&lt;br /&gt;
     2000    0.013    0.000    0.041    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:226(parse_column)&lt;br /&gt;
     1000    0.008    0.000    0.034    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:346(_process_params_dict)&lt;br /&gt;
     1500    0.002    0.000    0.029    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/socket.py:223(meth)&lt;br /&gt;
     1500    0.026    0.000    0.026    0.000 {method 'sendall' of '_socket.socket' objects}&lt;br /&gt;
     5000    0.007    0.000    0.014    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/catch23.py:79(struct_unpack)&lt;br /&gt;
     1000    0.001    0.000    0.013    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:562(_handle_ok)&lt;br /&gt;
    12000    0.011    0.000    0.011    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:167(read_lc_string)&lt;br /&gt;
     1000    0.004    0.000    0.010    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:199(parse_ok)&lt;br /&gt;
     3000    0.005    0.000    0.009    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:152(to_mysql)&lt;br /&gt;
     1500    0.003    0.000    0.007    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:118(make_command)&lt;br /&gt;
     3000    0.003    0.000    0.007    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:130(quote)&lt;br /&gt;
     1500    0.005    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/network.py:53(_prepare_packets)&lt;br /&gt;
     1500    0.003    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:406(_handle_result)&lt;br /&gt;
     3000    0.003    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:102(escape)&lt;br /&gt;
     1000    0.002    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:252(parse_eof)&lt;br /&gt;
      500    0.001    0.000    0.005    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:579(_handle_eof)&lt;br /&gt;
     2000    0.003    0.000    0.005    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:545(_handle_server_status)&lt;br /&gt;
     1500    0.003    0.000    0.004    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:53(int1store)&lt;br /&gt;
     3000    0.003    0.000    0.003    0.000 {method 'encode' of 'str' objects}&lt;br /&gt;
     9001    0.003    0.000    0.003    0.000 {method 'replace' of 'str' objects}&lt;br /&gt;
     1500    0.003    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:295(_reset_result)&lt;br /&gt;
     4500    0.003    0.000    0.003    0.000 {_struct.pack}&lt;br /&gt;
     2500    0.003    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:296(read_lc_int)&lt;br /&gt;
      500    0.002    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:757(_handle_eof)&lt;br /&gt;
     6027    0.003    0.000    0.003    0.000 {method 'format' of 'str' objects}&lt;br /&gt;
     3000    0.002    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:1221(_set_unread_result)&lt;br /&gt;
      500    0.001    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:219(parse_column_count)&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 {repr}&lt;br /&gt;
     4527    0.002    0.000    0.002    0.000 {getattr}&lt;br /&gt;
     4000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/constants.py:34(flag_is_set)&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:383(_handle_noresultset)&lt;br /&gt;
     4000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:1233(_get_unread_result)&lt;br /&gt;
     2001    0.001    0.000    0.001    0.000 {method 'items' of 'dict' objects}&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:307(_have_unread_result)&lt;br /&gt;
     3000    0.001    0.000    0.001    0.000 {method 'lower' of 'str' objects}&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 {range}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:173(_str_to_mysql)&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:169(_float_to_mysql)&lt;br /&gt;
     1000    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:161(_int_to_mysql)&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:1313(_get_getwarnings)&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:200(reset)&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:397(_handle_resultset)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/__init__.py:71(search_function)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/ascii.py:41(getregentry)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/contextlib.py:21(__exit__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/__init__.py:49(normalize_encoding)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 test.py:8(profiled)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/codecs.py:77(__new__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {__import__}&lt;br /&gt;
        3    0.000    0.000    0.000    0.000 {method 'get' of 'dict' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'split' of 'str' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {built-in method __new__ of type object at 0x1001534e8}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'translate' of 'str' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'join' of 'str' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {hasattr}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DBAPI:  &amp;lt;module 'MySQLdb' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/__init__.pyc'&amp;gt;&lt;br /&gt;
         47503 function calls in 0.918 seconds&lt;br /&gt;
&lt;br /&gt;
   Ordered by: cumulative time&lt;br /&gt;
&lt;br /&gt;
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;
     1500    0.009    0.000    0.917    0.001 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:164(execute)&lt;br /&gt;
     1500    0.002    0.000    0.890    0.001 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:353(_query)&lt;br /&gt;
     1500    0.004    0.000    0.498    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:315(_do_query)&lt;br /&gt;
     1500    0.016    0.000    0.391    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:358(_post_get_result)&lt;br /&gt;
     1500    0.001    0.000    0.375    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:324(_fetch_row)&lt;br /&gt;
      500    0.374    0.001    0.374    0.001 {built-in method fetch_row}&lt;br /&gt;
     1500    0.007    0.000    0.362    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:142(_do_get_result)&lt;br /&gt;
     1500    0.002    0.000    0.351    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:351(_get_result)&lt;br /&gt;
     1500    0.349    0.000    0.349    0.000 {method 'store_result' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.131    0.000    0.131    0.000 {method 'query' of '_mysql.connection' objects}&lt;br /&gt;
     4000    0.002    0.000    0.010    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:184(&amp;lt;genexpr&amp;gt;)&lt;br /&gt;
     3000    0.002    0.000    0.008    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/connections.py:267(literal)&lt;br /&gt;
     1500    0.007    0.000    0.007    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:107(_warning_check)&lt;br /&gt;
     3000    0.002    0.000    0.006    0.000 {method 'escape' of '_mysql.connection' objects}&lt;br /&gt;
     6000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:159(_get_db)&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/converters.py:81(Float2Str)&lt;br /&gt;
     2500    0.001    0.000    0.001    0.000 {isinstance}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/connections.py:202(string_literal)&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:380(fetchall)&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 {built-in method describe}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 {method 'string_literal' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {method 'insert_id' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {method 'affected_rows' of '_mysql.connection' objects}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/converters.py:69(Thing2Str)&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 {method 'warning_count' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 {method 'info' of '_mysql.connection' objects}&lt;br /&gt;
     1000    0.000    0.000    0.000    0.000 {method 'iteritems' of 'dict' objects}&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:103(_check_executed)&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 {len}&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 {built-in method field_flags}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/contextlib.py:21(__exit__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 test.py:8(profiled)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DBAPI:  &amp;lt;module 'pymysql' from '/Users/classic/dev/PyMySQL/pymysql/__init__.pyc'&amp;gt;, total seconds 10.046012&lt;br /&gt;
DBAPI:  &amp;lt;module 'mysql.connector' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/__init__.pyc'&amp;gt;, total seconds 15.403260&lt;br /&gt;
DBAPI:  &amp;lt;module 'MySQLdb' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/__init__.pyc'&amp;gt;, total seconds 1.028737&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mike Bayer</name></author>	</entry>

	<entry>
		<id>https://wiki.openstack.org/w/index.php?title=PyMySQL_evaluation&amp;diff=72555</id>
		<title>PyMySQL evaluation</title>
		<link rel="alternate" type="text/html" href="https://wiki.openstack.org/w/index.php?title=PyMySQL_evaluation&amp;diff=72555"/>
				<updated>2015-01-28T20:25:17Z</updated>
		
		<summary type="html">&lt;p&gt;Mike Bayer: /* MySQL DB Drivers Comparison */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= PyMySQL Evaluation =&lt;br /&gt;
&lt;br /&gt;
This page will capture issues related to Openstack moving to the [https://github.com/PyMySQL/PyMySQL/ PyMySQL driver] for MySQL/MariaDB dbapi access.&lt;br /&gt;
&lt;br /&gt;
== Rationale ==&lt;br /&gt;
&lt;br /&gt;
While the [https://github.com/farcepest/MySQLdb1 MySQL-Python] driver is a very mature and stable driver, it does not provide compatibility with either Python 3 or with [http://eventlet.net/doc/patching.html eventlet monkeypatching] (MySQL-Python  [https://github.com/eventlet/eventlet/blob/v0.16.1/eventlet/patcher.py#L225-L237 can be monkeypatched] with eventlet, but this feature disabled by default and missed in documentation). So OpenStack's usage of MySQL-Python, combined with the fact that concurrency is provided by eventlet, means that we currently have fully serialized database access within a single process, that is, only one database command occurs at a time within an Openstack Python process.&lt;br /&gt;
&lt;br /&gt;
== Drivers Under Consideration ==&lt;br /&gt;
&lt;br /&gt;
The two drivers that are known to provide eventlet-monkeypatch compatibility are MySQL-Connector and PyMySQL, as they are written in pure Python.   The two other well-known drivers for MySQL are MySQL-Python and OurSQL, both of which are written in C and offer no explicit support for eventlet or async.  They can reportedly be built to support gevent using a system called [https://github.com/CMGS/greenify Greenify], but the maturity and/or stabiltiy of this system is unknown.&lt;br /&gt;
&lt;br /&gt;
A comparison at http://www.diamondtin.com/2014/sqlalchemy-gevent-mysql-python-drivers-comparison/ illustrates performance metrics observed with all four of these drivers, with MySQL-python built both without and with the &amp;quot;greenify&amp;quot; system.&lt;br /&gt;
&lt;br /&gt;
A summary of the status of all four drivers is as follows.&lt;br /&gt;
&lt;br /&gt;
=== MySQL-Python ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/farcepest/MySQLdb1 MySQL-Python] is the most widely used Python driver.  However, it does not support Python 3, and it does not support async systems unless used within a thread pool.   While a system known as Greenify can potentially resolve the latter situation, the Python 3 limitation is still a deal-breaker - pull request with Python 3 support [https://github.com/farcepest/MySQLdb1/pull/62 was proposed] to MySQL-python in April 2014 and still not merged.&lt;br /&gt;
&lt;br /&gt;
=== mysqlclient ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/PyMySQL/mysqlclient-python mysqlclient] is a fork of MySQL-python. It add Python 3.3 support and merges some pull requests.&lt;br /&gt;
&lt;br /&gt;
=== OurSQL ===&lt;br /&gt;
&lt;br /&gt;
[https://pythonhosted.org/oursql/ OurSQL] is an alternative MySQL driver that is also written in C.  It features fast performance and uses a different execution model than MySQL-python, using prepared statements.    OurSQL does have a Python 3 port hosted on [https://launchpad.net/oursql/py3k/ launchpad] but is not integrated with OurSQL itself nor is it published on Pypi.     Because it is written in C, it does not offer compatibility with async systems including eventlet-style monkeypatching.&lt;br /&gt;
&lt;br /&gt;
Besides the shaky Python 3 support and lack of eventlet support, OurSQL is also not maintained, seeing its last master commit on [https://code.launchpad.net/~jerub/oursql/master 2012-06-05].&lt;br /&gt;
&lt;br /&gt;
=== MySQL-Connector-Python ===&lt;br /&gt;
&lt;br /&gt;
[https://pypi.python.org/pypi/mysql-connector-python MySQL-connector-Python] is a pure Python MySQL driver, and is now released under the auspices of the MySQL project itself, as owned by Oracle.  MySQL-connector-Python supports both Python 3 as well as eventlet monkeypatching, and is well maintained.  It is endorsed by Oracle as the official Oracle-supported driver for MySQL, so to that extent, it is in most ways Openstack's first choice in driver.  However, Oracle refuses to publish MySQL-connector-Python on Pypi, which is critical to the Openstack infrastructure.  Repeated attempts to communicate with Oracle in order to resolve this issue have not made any progress.    Therefore, for this one unfortunate reason, MySQL-connector-Python will not have a place in the Openstack ecosystem unless this issue is resolved.&lt;br /&gt;
&lt;br /&gt;
=== PyMySQL ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/PyMySQL/PyMySQL PyMySQL] is a pure Python MySQL&lt;br /&gt;
driver, first written as a rough port of the MySQL-Python driver.&lt;br /&gt;
PyMySQL meets all of Openstack's criterion for a driver: it is fully&lt;br /&gt;
open source, hosted on Github, released on Pypi, is actively&lt;br /&gt;
maintained, is written in pure Python so is eventlet-monkeypatch&lt;br /&gt;
compatible, and is fully Python 3 compatible.&lt;br /&gt;
&lt;br /&gt;
As this document is named &amp;quot;PyMySQL Evaluation&amp;quot;, it should be apparent that&lt;br /&gt;
this is the driver Openstack is currently leaning towards; because it is the only&lt;br /&gt;
one that meets all criteria fully, it is already most likely the &amp;quot;winner&amp;quot;.  However, it does&lt;br /&gt;
have some minor code quality issues which hopefully can be addressed in some&lt;br /&gt;
way; the section below titled &amp;quot;PyMySQL Code Review&amp;quot; will summarize the&lt;br /&gt;
current state of the code.&lt;br /&gt;
&lt;br /&gt;
===MySQL DB Drivers Comparison===&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Project !! PyPi hosted !! Eventlet friendly !! Python 3 compatibility !! Maturity and/or stability  !! Comment&lt;br /&gt;
|-&lt;br /&gt;
| MySQL-Python || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: orange;&amp;quot; | Partial || style=&amp;quot;background-color: red;&amp;quot; | No  || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || Can be monkeypatched by eventlet, but only to enable thread pooling&lt;br /&gt;
|-&lt;br /&gt;
| mysqlclient || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: orange;&amp;quot; | Partial || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes  || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || MySQL-python fork, so should be similar to it&lt;br /&gt;
|-&lt;br /&gt;
| OurSQL || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: red;&amp;quot; | No || style=&amp;quot;background-color: orange;&amp;quot; | Yes, but not Pypi hosted  || style=&amp;quot;background-color: orange;&amp;quot; | ??? || Has not seen commits/releases in two years&lt;br /&gt;
|-&lt;br /&gt;
| MySQL-Connector-Python || style=&amp;quot;background-color: red;&amp;quot; | No || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes  || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || The official Oracle-supported driver for MySQL&lt;br /&gt;
|-&lt;br /&gt;
| PyMySQL || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes  || style=&amp;quot;background-color: orange;&amp;quot; | Yes, however see notes below. ||&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== PyMySQL Code Review ==&lt;br /&gt;
&lt;br /&gt;
PyMySQL started roughly as a pure Python port of MySQL-Python.   The sections below will&lt;br /&gt;
detail various aspects of the driver and the status of the code.   Overall, the general&lt;br /&gt;
tone of PyMySQL is one of code that was written in the spirit of pragmatism and immediate&lt;br /&gt;
need; it is straightforward, relatively free of serious antipatterns and performs&lt;br /&gt;
its task in a matter-of-fact way.  However,&lt;br /&gt;
it lacks polish and completeness in many areas that would normally be in better&lt;br /&gt;
condition for a more mature project.  These areas are all highly fixable, and it is&lt;br /&gt;
hoped that these items can be shared with the current PyMySQL developers, through&lt;br /&gt;
a combination of this document itself as well as new issues and pull requests&lt;br /&gt;
on the PyMySQL tracker, so that this fairly promising library can be pushed&lt;br /&gt;
to the next level.   This would require that the developers are amenable to&lt;br /&gt;
these improvements.&lt;br /&gt;
&lt;br /&gt;
=== Coding Style / Pep8 ===&lt;br /&gt;
&lt;br /&gt;
PyMySQL's coding style is pretty good.  The majority of it passes all&lt;br /&gt;
flake8 tests, save for a few dozen whitespace issues and&lt;br /&gt;
some long lines here and there within the core&lt;br /&gt;
modules, and moreso within the tests which appear to be a bit more&lt;br /&gt;
crufty than the core modules.  A full run of flake8 produces only 128&lt;br /&gt;
errors, and with a default run of autopep8 against E1,E2,E3, we can&lt;br /&gt;
fix all of them in one pass except for 38 remaining long line warnings.&lt;br /&gt;
The code layout is mostly idiomatic and reasonable.&lt;br /&gt;
&lt;br /&gt;
The codebase is probably short on docstrings.  Those methods that are&lt;br /&gt;
part of the public DBAPI do have docstrings, but tend to be extremely&lt;br /&gt;
terse (example, docstring for cursor.execute(): &amp;quot;'Execute a query'&amp;quot;).&lt;br /&gt;
Other methods that aren't public tend to not have any docstrings, but&lt;br /&gt;
the codebase is not at all hard to read and the purpose of methods and&lt;br /&gt;
functions is pretty easy to discern just by their names and implementations,&lt;br /&gt;
however those who seek inline documentation as a means to understand a codebase&lt;br /&gt;
will be disappointed.   The terseness of public API docstrings is more of&lt;br /&gt;
an issue in terms of the overall lack of documentation for PyMySQL,&lt;br /&gt;
see the section &amp;quot;Documentation&amp;quot; below.&lt;br /&gt;
&lt;br /&gt;
=== Test Coverage ===&lt;br /&gt;
&lt;br /&gt;
The test coverage for PyMySQL is definitely lacking.   From a visual&lt;br /&gt;
inspection alone, it's apparent that of the dozen or so test modules,&lt;br /&gt;
most of them have less then ten tests each and the modules are very short.&lt;br /&gt;
For a full test run, there's only 126 tests; contrast this to&lt;br /&gt;
MySQL-Connector-Python which has 531 tests.&lt;br /&gt;
&lt;br /&gt;
Additionally, all of the tests are live round-trip tests against a&lt;br /&gt;
real database.  There are no non-live unit tests of any kind,&lt;br /&gt;
leaving codepaths that are not easily exercised&lt;br /&gt;
on a generic MySQL database out in the cold.  This means that features designed&lt;br /&gt;
for particular versions of MySQL, particular datatypes, particular error&lt;br /&gt;
conditions, and particular kinds of message packets that aren't created for&lt;br /&gt;
real are simply not tested.&lt;br /&gt;
&lt;br /&gt;
The lack of tests is apparent when run with coverage.  The test suite itself&lt;br /&gt;
does not appear to have any signs that it is normally run with coverage&lt;br /&gt;
turned on, with no directives in tox.ini or similar.   With coverage,&lt;br /&gt;
the overall coverage is 85%, but the modules most lacking in coverage&lt;br /&gt;
are also the most critical to PyMySQL's core functionality,&lt;br /&gt;
connections.py, converters.py and cursors.py:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Name                                                              Stmts   Miss  Cover&lt;br /&gt;
-------------------------------------------------------------------------------------&lt;br /&gt;
pymysql/__init__                                                     51      3    94%&lt;br /&gt;
pymysql/_compat                                                      14      4    71%&lt;br /&gt;
pymysql/_socketio                                                    72     30    58%&lt;br /&gt;
pymysql/charset                                                     228      3    99%&lt;br /&gt;
pymysql/connections                                                 827    188    77%&lt;br /&gt;
pymysql/constants/CLIENT                                             18      0   100%&lt;br /&gt;
pymysql/constants/COMMAND                                            32      0   100%&lt;br /&gt;
pymysql/constants/ER                                                471      0   100%&lt;br /&gt;
pymysql/constants/FIELD_TYPE                                         29      0   100%&lt;br /&gt;
pymysql/constants/FLAG                                               15      0   100%&lt;br /&gt;
pymysql/constants/SERVER_STATUS                                      10      0   100%&lt;br /&gt;
pymysql/constants/__init__                                            0      0   100%&lt;br /&gt;
pymysql/converters                                                  148     51    66%&lt;br /&gt;
pymysql/cursors                                                     273     37    86%&lt;br /&gt;
pymysql/err                                                          40      1    98%&lt;br /&gt;
pymysql/tests/__init__                                               14      5    64%&lt;br /&gt;
pymysql/tests/base                                                   25      2    92%&lt;br /&gt;
pymysql/tests/test_DictCursor                                        73      2    97%&lt;br /&gt;
pymysql/tests/test_SSCursor                                          56      6    89%&lt;br /&gt;
pymysql/tests/test_basic                                            186      4    98%&lt;br /&gt;
pymysql/tests/test_connection                                        57      8    86%&lt;br /&gt;
pymysql/tests/test_example                                           17      2    88%&lt;br /&gt;
pymysql/tests/test_issues                                           300     48    84%&lt;br /&gt;
pymysql/tests/test_load_local                                        39      2    95%&lt;br /&gt;
pymysql/tests/test_nextset                                           43      4    91%&lt;br /&gt;
pymysql/tests/thirdparty/__init__                                     7      5    29%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/__init__                        6      2    67%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/capabilities                  196     23    88%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/dbapi20                       423    121    71%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_capabilities      73      6    92%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_dbapi20          100      2    98%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_nonstandard       57      3    95%&lt;br /&gt;
pymysql/times                                                        12      0   100%&lt;br /&gt;
pymysql/util                                                         14      7    50%&lt;br /&gt;
-------------------------------------------------------------------------------------&lt;br /&gt;
TOTAL                                                              3926    569    86%&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The lack of coverage in converters.py refers to functions related to handling&lt;br /&gt;
incoming bound values as well as outgoing result values.    Key data escaping&lt;br /&gt;
features such as escaping of dictionaries, sets, some unicode objects and&lt;br /&gt;
byte objects as well as many pathways into date and time-processing functions are not&lt;br /&gt;
covered.&lt;br /&gt;
&lt;br /&gt;
In connections.py, uncovered features include various SSL and&lt;br /&gt;
very legacy (e.g. version 3.23) MySQL features, but also some protocol parsing&lt;br /&gt;
features.&lt;br /&gt;
&lt;br /&gt;
In cursors.py, there is coverage for an elaborate and mostly undocumented&lt;br /&gt;
performance-related feature that rewrites an INSERT statement to have an&lt;br /&gt;
extended VALUES clause (_do_execute_many), however it fails to cover the very&lt;br /&gt;
likely case where the function will need to break the input set into&lt;br /&gt;
multiple chunks:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;gt;         for arg in args:&lt;br /&gt;
&amp;gt;             v = values % escape(arg, conn)&lt;br /&gt;
&amp;gt;             if isinstance(v, text_type):&lt;br /&gt;
&amp;gt;                 v = v.encode(encoding)&lt;br /&gt;
&amp;gt;             if len(sql) + len(v) + 1 &amp;gt; max_stmt_length:&lt;br /&gt;
!                 rows += self.execute(sql)&lt;br /&gt;
!                 sql = bytearray(prefix)&lt;br /&gt;
&amp;gt;             else:&lt;br /&gt;
&amp;gt;                 sql += b','&lt;br /&gt;
&amp;gt;             sql += v&lt;br /&gt;
&amp;gt;         rows += self.execute(sql)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The most prominent missing coverage in cursors.py seems to be&lt;br /&gt;
for its &amp;quot;scrollable cursor&amp;quot; support.&lt;br /&gt;
&lt;br /&gt;
Beyond the lack of completeness in testing, many of the tests themselves&lt;br /&gt;
are written in an expedient &amp;quot;one giant test&amp;quot; style where dozens of&lt;br /&gt;
assertions and individual behaviors are lumped into one big test&lt;br /&gt;
case (see test_SSCursor.py, test_basic.py-&amp;gt;test_datatypes for examples).&lt;br /&gt;
Test cases like these are difficult to work with when debugging&lt;br /&gt;
regressions as well as when tests for new features need to be added, and ideally&lt;br /&gt;
these should be broken out into clean single-feature tests with consistent&lt;br /&gt;
fixtures.&lt;br /&gt;
&lt;br /&gt;
There is also a suite called &amp;quot;test_issues&amp;quot;, which intends to&lt;br /&gt;
accumulate tests against specific issues that have been reported. This&lt;br /&gt;
isn't a bad idea, though it would be nice to see an effort made into&lt;br /&gt;
categorizing these tests into the actual features they are testing,&lt;br /&gt;
rather than a meaningless list of issue numbers, which could then form&lt;br /&gt;
as the basis for new suites centered around those areas of&lt;br /&gt;
functionality.  The issues reported should be used as the inspiration&lt;br /&gt;
for improvement of the test suite, rather than just another line-item&lt;br /&gt;
to be filed away.&lt;br /&gt;
&lt;br /&gt;
=== Library Documentation ===&lt;br /&gt;
&lt;br /&gt;
As the Python DBAPI is already a well-documented API, and the users of&lt;br /&gt;
PyMySQL are typically coming from MySQL-Python which is documented&lt;br /&gt;
to some degree and for which the use contract is widely known, it seems&lt;br /&gt;
unlikely that users of PyMySQL are eager for comprehensive documentation&lt;br /&gt;
specific to this library.&lt;br /&gt;
&lt;br /&gt;
That being said, PyMySQL does not appear to have any real library&lt;br /&gt;
documentation at all.  There's a README which&lt;br /&gt;
essentially refers to pep-249, an example.py in the root which shows a&lt;br /&gt;
very basic connection / round trip, and that's it. In&lt;br /&gt;
order to know anything about the API, specific parameters, optimization&lt;br /&gt;
strategies, behaviors, etc., one either has to go off of the MySQL-Python documentation and assume&lt;br /&gt;
PyMySQL also features the same parameter, or read the source code.&lt;br /&gt;
&lt;br /&gt;
PyMySQL probably hasn't had much urgent need for real documentation thus&lt;br /&gt;
far.  However, relying upon being a port of MySQL-Python is fast becoming&lt;br /&gt;
something that can no longer be relied upon, as MySQL-Python's development&lt;br /&gt;
is quite stalled, and PyMySQL should aspire to move into the future&lt;br /&gt;
of MySQL and Python with its own featureset and behaviors.  To that end, it would be a &lt;br /&gt;
great idea if it at least made the start&lt;br /&gt;
of a rudimentary Sphinx build including autodoc for module documentation&lt;br /&gt;
which could then be published up to RTD.   This would be very easy to get&lt;br /&gt;
started, and once present, new documentation sections can be added&lt;br /&gt;
iteratively.&lt;br /&gt;
&lt;br /&gt;
=== Architecture and Performance ===&lt;br /&gt;
&lt;br /&gt;
As stated before, PyMySQL has a really matter-of-fact and straightforward&lt;br /&gt;
implementation.   The code is very readable and it's easy to discern even&lt;br /&gt;
unusual features such as the INSERT..VALUES rewriting.  As far as the design,&lt;br /&gt;
there are lots of areas where performance suffers a bit at the Python level;&lt;br /&gt;
these areas could be greatly improved very easily with a little bit of&lt;br /&gt;
attention.&lt;br /&gt;
&lt;br /&gt;
As we are dealing with a driver that is communicating with a low level&lt;br /&gt;
protocol over a socket, it's critical that sending and receiving messages&lt;br /&gt;
is done as efficiently as possible.  The mechanisms used in PyMySQL rely&lt;br /&gt;
heavily on standard Python objects, without the use of __slots__; this means&lt;br /&gt;
that for individual messages on a socket, we are creating a heavyweight object,&lt;br /&gt;
calling its __init__() method and a creating a new __dict__ each time;&lt;br /&gt;
the most common object, OKPacketWrapper(), already wraps a MysqlPacket()&lt;br /&gt;
object, so we're doing two __init__()s in this common case.  OKPacketWrapper&lt;br /&gt;
also implements a `__getattr__` scheme&lt;br /&gt;
to proxy attribute access from itself to the internal MysqlPacket; this is a&lt;br /&gt;
an inefficient mode of operation in Python as it means a lookup must first&lt;br /&gt;
fail on the OKPacketWrapper before it invokes the `__getattr__()` method&lt;br /&gt;
and does another attribute lookup.   A reorg of the packet classes to produce&lt;br /&gt;
far fewer objects for messages as well as to use __slots__ would be&lt;br /&gt;
recommended.&lt;br /&gt;
&lt;br /&gt;
There are also lots of areas where function call overhead is unnecessarily&lt;br /&gt;
high due to the organization of calls.  For example, when calling upon&lt;br /&gt;
cursor.execute(), for parameters passed as a dictionary under Python 2K,&lt;br /&gt;
the isinstance() builtin function will be called on the incoming arguments&lt;br /&gt;
four times at a minimum, with two of each call against the exact same&lt;br /&gt;
criteria.  Additional logic will then call isinstance() on every key of&lt;br /&gt;
the dictionary as well as at least twice on every value within the dictionary,&lt;br /&gt;
in order to determine if the value needs to be converted to bytes as well&lt;br /&gt;
as for some top-level escaping logic checks; the escaping logic then&lt;br /&gt;
goes further with more checks for type() to match the object to a final&lt;br /&gt;
converter function.   While the flow of this logic does appear to be&lt;br /&gt;
reasonable and straightforward, it isn't ideal for a low-level database&lt;br /&gt;
driver library where speed is a high priority; the various wrappers and&lt;br /&gt;
processors can easily be reorganized here to reduce the number of isinstance()&lt;br /&gt;
and type() calls significantly, which would save a lot on function call&lt;br /&gt;
overhead.  It is likely there are many other areas in the codebase where&lt;br /&gt;
similar reductions in call counts can be made without too much disruption.&lt;br /&gt;
&lt;br /&gt;
In Appendix A, a short performance suite is illustrated.  This&lt;br /&gt;
is run against PyMySQL, Mysql-Connector-Python, and MySQL-Python.&lt;br /&gt;
PyMySQL does in fact demonstrate the&lt;br /&gt;
highest call-count behavior, logging 26,851,003 function calls&lt;br /&gt;
compared to MySQL-connector's 11,674,074 for the same operations.  To PyMySQL's&lt;br /&gt;
credit, it somehow completes the test 25-50% faster than MySQL-connector&lt;br /&gt;
despite having more than twice as many function calls; this probably indicates&lt;br /&gt;
the use of something very slow within MySQL-connector, such as catching&lt;br /&gt;
an exception within a tight loop.  However, the real bar we're comparing to&lt;br /&gt;
is MySQL-Python, which is written&lt;br /&gt;
in C and does the whole test with only 47,503 Python function calls; while&lt;br /&gt;
PyMySQL and MySQL-connector-Python compete for time within the 10-15 second&lt;br /&gt;
range without profiling, MySQL-Python completes the whole test in **1.03 seconds**, a 1000%&lt;br /&gt;
improvement over PyMySQL.   This speed difference is not PyMySQL's fault,&lt;br /&gt;
as pure Python is known to be extremely slow compared to C code.&lt;br /&gt;
However, there's a lot of easy wins in PyMySQL's codebase where the function&lt;br /&gt;
call count could probably be reduced dramatically to be even better than that&lt;br /&gt;
of MySQL-connector-Python; ideally, the driver would be able to run this&lt;br /&gt;
test possibly 5-6x slower than MySQL-Python rather than 10x.&lt;br /&gt;
&lt;br /&gt;
=== History Documentation ===&lt;br /&gt;
&lt;br /&gt;
PyMySQL includes a CHANGELOG file in its root which describes the bug fixes&lt;br /&gt;
and features of each release.  The construction of this file is definitely&lt;br /&gt;
within the &amp;quot;expedient&amp;quot; style noted for PyMySQL overall, and for it to be&lt;br /&gt;
genuinely useful for enterprise-level software, it would need to be&lt;br /&gt;
much more complete in its detail.    Each bug fix mentioned is only referred to&lt;br /&gt;
tersely without any link to a pull request or bug report.  While some can&lt;br /&gt;
be linked to a specific change through detective work&lt;br /&gt;
(&amp;quot;Cursor.fetchall() and .fetchmany now return list, not tuple&amp;quot;), it would&lt;br /&gt;
be very difficult for many&lt;br /&gt;
(&amp;quot;Fixed BIT type handling&amp;quot;, &amp;quot;Fixed GC errors&amp;quot; - what was the issue?)&lt;br /&gt;
and in many cases impossible (&amp;quot;Improved Py3k support&amp;quot;).  The file also&lt;br /&gt;
includes no dates so one has to look at the git tags to figure this&lt;br /&gt;
part out (fortunately, releases to seem to be git tagged at least).&lt;br /&gt;
For a user trying to ascertain if particular issues are&lt;br /&gt;
fixed or if behaviors have changed, this file is at best highly frustrating.&lt;br /&gt;
&lt;br /&gt;
A listing of release dates illustrates an average of two releases per year,&lt;br /&gt;
excluding 2012 when PyMySQL was seeking a new maintainer:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
2014-12-02 12:05:10 -0200  (tag: pymysql-0.6.3)&lt;br /&gt;
2014-04-21 14:28:47 -0300  (tag: pymysql-0.6.2)&lt;br /&gt;
2013-10-11 13:36:56 -0300  (tag: pymysql-0.6.1)&lt;br /&gt;
2013-10-04 14:25:14 -0300  (tag: pymysql-0.6)&lt;br /&gt;
2011-11-08 10:27:41 -0800  (tag: pymysql-0.5)&lt;br /&gt;
2010-12-27 17:28:16 +0000  (tag: pymysql-0.4)&lt;br /&gt;
2010-09-03 00:55:29 +0000  (tag: pymysql-0.3)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Community Involvement ===&lt;br /&gt;
&lt;br /&gt;
This is an area where PyMySQL seems to shine, and might even be an advantage&lt;br /&gt;
as compared to MySQL-Connector-Python, which remains under the cloak&lt;br /&gt;
of MySQL's vastly overstuffed bugtracker under the ownership of&lt;br /&gt;
the famously-opaque Oracle.   For a small, low key library without&lt;br /&gt;
any real documentation and only 126 tests, it features 33 contributors on&lt;br /&gt;
Github and is up to pull request #285, which is quite high.  Because&lt;br /&gt;
PyMySQL is small and straightforward, it does lend itself to contribution&lt;br /&gt;
and seems to do very well in this area.   The developers so far have been&lt;br /&gt;
responsive to issues and pull requests made by the author.&lt;br /&gt;
&lt;br /&gt;
Based on github contributor graphs it appears that PyMySQL had a lull&lt;br /&gt;
in 2012 but was picked up again afterwards in 2013.  As of 2012, its most&lt;br /&gt;
prominent developer Pete Hunt stopped development, and on July 25, 2013&lt;br /&gt;
added a note to the README that the project was looking for a new maintainer.&lt;br /&gt;
As of 2013 it was picked up by the current most prominent developers&lt;br /&gt;
Marcel Rodrigues (github username lecram) and INADA Naoki&lt;br /&gt;
(github username methane).&lt;br /&gt;
&lt;br /&gt;
Most issues that have been noted thus far within the areas of testing,&lt;br /&gt;
documentation and architecture are all very fixable; if the Openstack community&lt;br /&gt;
can provide resources to address some of these areas, and if the PyMySQL&lt;br /&gt;
developers are reasonably open to accepting new changes and producing new&lt;br /&gt;
releases in a timely fashion, the PyMySQL driver&lt;br /&gt;
could rapidly become a fully formidable contender in the MySQL driver field.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Appendix A - Performance Tests ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
import cProfile&lt;br /&gt;
import StringIO&lt;br /&gt;
import pstats&lt;br /&gt;
import contextlib&lt;br /&gt;
import time&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextlib.contextmanager&lt;br /&gt;
def profiled(dbapi):&lt;br /&gt;
    pr = cProfile.Profile()&lt;br /&gt;
    pr.enable()&lt;br /&gt;
    yield&lt;br /&gt;
    pr.disable()&lt;br /&gt;
    s = StringIO.StringIO()&lt;br /&gt;
    ps = pstats.Stats(pr, stream=s).sort_stats('cumulative')&lt;br /&gt;
    ps.print_stats()&lt;br /&gt;
    print &amp;quot;DBAPI:  %s&amp;quot; % dbapi&lt;br /&gt;
    print s.getvalue()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextlib.contextmanager&lt;br /&gt;
def timeonly(dbapi):&lt;br /&gt;
    now = time.time()&lt;br /&gt;
    try:&lt;br /&gt;
        yield&lt;br /&gt;
    finally:&lt;br /&gt;
        total = time.time() - now&lt;br /&gt;
        print &amp;quot;DBAPI:  %s, total seconds %f&amp;quot; % (dbapi, total)&lt;br /&gt;
&lt;br /&gt;
import MySQLdb&lt;br /&gt;
import pymysql&lt;br /&gt;
from mysql import connector as mysqlconnector&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def go(dbapi, ctx):&lt;br /&gt;
    conn = dbapi.connect(&lt;br /&gt;
        user='scott', passwd='tiger', host='localhost', db='test')&lt;br /&gt;
    cursor = conn.cursor()&lt;br /&gt;
    cursor.execute(&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
CREATE TABLE IF NOT EXISTS test_things (&lt;br /&gt;
    x INTEGER,&lt;br /&gt;
    y VARCHAR(255),&lt;br /&gt;
    z FLOAT&lt;br /&gt;
) engine=InnoDB&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;)&lt;br /&gt;
    cursor.execute(&amp;quot;DELETE from test_things&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    with ctx(dbapi):&lt;br /&gt;
        for row in xrange(1000):&lt;br /&gt;
            cursor.execute(&lt;br /&gt;
                &amp;quot;INSERT INTO test_things (x, y, z) &amp;quot;&lt;br /&gt;
                &amp;quot;VALUES (%(x)s, %(y)s, %(z)s)&amp;quot;,&lt;br /&gt;
                {&amp;quot;x&amp;quot;: row, &amp;quot;y&amp;quot;: &amp;quot;row number %d&amp;quot; % row,&lt;br /&gt;
                 &amp;quot;z&amp;quot;: row * 4.57292, }&lt;br /&gt;
            )&lt;br /&gt;
&lt;br /&gt;
        for x in xrange(500):&lt;br /&gt;
            cursor.execute(&lt;br /&gt;
                &amp;quot;select * from test_things&amp;quot;)&lt;br /&gt;
            for row in cursor.fetchall():&lt;br /&gt;
                row[0], row[1], row[2]&lt;br /&gt;
&lt;br /&gt;
    cursor.close()&lt;br /&gt;
    conn.close()&lt;br /&gt;
&lt;br /&gt;
go(pymysql, profiled)&lt;br /&gt;
go(mysqlconnector, profiled)&lt;br /&gt;
go(MySQLdb, profiled)&lt;br /&gt;
&lt;br /&gt;
go(pymysql, timeonly)&lt;br /&gt;
go(mysqlconnector, timeonly)&lt;br /&gt;
go(MySQLdb, timeonly)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DBAPI:  &amp;lt;module 'pymysql' from '/Users/classic/dev/PyMySQL/pymysql/__init__.pyc'&amp;gt;&lt;br /&gt;
         26851003 function calls in 15.924 seconds&lt;br /&gt;
&lt;br /&gt;
   Ordered by: cumulative time&lt;br /&gt;
&lt;br /&gt;
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;
     1500    0.010    0.000   15.923    0.011 /Users/classic/dev/PyMySQL/pymysql/cursors.py:105(execute)&lt;br /&gt;
     1500    0.003    0.000   15.883    0.011 /Users/classic/dev/PyMySQL/pymysql/cursors.py:269(_query)&lt;br /&gt;
     1500    0.004    0.000   15.859    0.011 /Users/classic/dev/PyMySQL/pymysql/connections.py:746(query)&lt;br /&gt;
     1500    0.003    0.000   15.819    0.011 /Users/classic/dev/PyMySQL/pymysql/connections.py:892(_read_query_result)&lt;br /&gt;
     1500    0.004    0.000   15.812    0.011 /Users/classic/dev/PyMySQL/pymysql/connections.py:1097(read)&lt;br /&gt;
      500    0.003    0.000   15.559    0.031 /Users/classic/dev/PyMySQL/pymysql/connections.py:1154(_read_result_packet)&lt;br /&gt;
      500    0.900    0.002   15.429    0.031 /Users/classic/dev/PyMySQL/pymysql/connections.py:1187(_read_rowdata_packet)&lt;br /&gt;
   500000    3.417    0.000    9.173    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1200(_read_row_from_packet)&lt;br /&gt;
  2012000    1.157    0.000    5.307    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:311(read_length_coded_string)&lt;br /&gt;
   504500    1.495    0.000    5.209    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:845(_read_packet)&lt;br /&gt;
  2014500    1.248    0.000    3.035    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:293(read_length_encoded_integer)&lt;br /&gt;
  3539500    2.439    0.000    2.750    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:245(read)&lt;br /&gt;
  1009000    0.779    0.000    2.307    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:870(_read_bytes)&lt;br /&gt;
  1009000    1.197    0.000    1.423    0.000 {method 'read' of '_io.BufferedReader' objects}&lt;br /&gt;
   508500    0.379    0.000    0.695    0.000 /Users/classic/dev/PyMySQL/pymysql/util.py:3(byte2int)&lt;br /&gt;
  5058000    0.465    0.000    0.465    0.000 {len}&lt;br /&gt;
   500500    0.222    0.000    0.455    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1146(_check_packet_is_eof)&lt;br /&gt;
   504500    0.205    0.000    0.375    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:342(check_error)&lt;br /&gt;
  2504000    0.275    0.000    0.275    0.000 {method 'append' of 'list' objects}&lt;br /&gt;
   501500    0.182    0.000    0.230    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:326(is_eof_packet)&lt;br /&gt;
     3500    0.009    0.000    0.226    0.000 /Users/classic/dev/PyMySQL/pymysql/_socketio.py:45(readinto)&lt;br /&gt;
     3500    0.207    0.000    0.207    0.000 {method 'recv_into' of '_socket.socket' objects}&lt;br /&gt;
  1022000    0.203    0.000    0.203    0.000 {_struct.unpack}&lt;br /&gt;
   526000    0.200    0.000    0.200    0.000 {isinstance}&lt;br /&gt;
  1501000    0.195    0.000    0.195    0.000 {method 'get' of 'dict' objects}&lt;br /&gt;
   504500    0.176    0.000    0.176    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:238(__init__)&lt;br /&gt;
   504500    0.170    0.000    0.170    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:339(is_error_packet)&lt;br /&gt;
  2014500    0.167    0.000    0.167    0.000 {ord}&lt;br /&gt;
      500    0.006    0.000    0.125    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1224(_get_descriptions)&lt;br /&gt;
     2000    0.003    0.000    0.087    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:361(__init__)&lt;br /&gt;
     2000    0.019    0.000    0.084    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:365(__parse_field_descriptor)&lt;br /&gt;
   500000    0.056    0.000    0.056    0.000 /Users/classic/dev/PyMySQL/pymysql/converters.py:301(through)&lt;br /&gt;
     1500    0.005    0.000    0.037    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:915(_execute_command)&lt;br /&gt;
     1500    0.003    0.000    0.027    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:886(_write_bytes)&lt;br /&gt;
     1500    0.002    0.000    0.024    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/socket.py:223(meth)&lt;br /&gt;
     1500    0.021    0.000    0.021    0.000 {method 'sendall' of '_socket.socket' objects}&lt;br /&gt;
     1500    0.020    0.000    0.020    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:276(_do_get_result)&lt;br /&gt;
     1000    0.003    0.000    0.019    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:95(_escape_args)&lt;br /&gt;
     4000    0.002    0.000    0.015    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:99(&amp;lt;genexpr&amp;gt;)&lt;br /&gt;
     1000    0.002    0.000    0.014    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1127(_read_ok_packet)&lt;br /&gt;
     3000    0.003    0.000    0.013    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:712(escape)&lt;br /&gt;
     1000    0.005    0.000    0.012    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:416(__init__)&lt;br /&gt;
     8000    0.011    0.000    0.011    0.000 {method 'decode' of 'str' objects}&lt;br /&gt;
     3500    0.003    0.000    0.007    0.000 {method '_checkReadable' of '_io._IOBase' objects}&lt;br /&gt;
     2000    0.003    0.000    0.006    0.000 /Users/classic/dev/PyMySQL/pymysql/converters.py:19(escape_item)&lt;br /&gt;
     4000    0.002    0.000    0.006    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:126(&amp;lt;genexpr&amp;gt;)&lt;br /&gt;
     7500    0.003    0.000    0.005    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:115(ensure_bytes)&lt;br /&gt;
     2000    0.003    0.000    0.005    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:386(description)&lt;br /&gt;
     5500    0.004    0.000    0.004    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:268(advance)&lt;br /&gt;
     1000    0.001    0.000    0.004    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:722(escape_string)&lt;br /&gt;
     3500    0.004    0.000    0.004    0.000 /Users/classic/dev/PyMySQL/pymysql/_socketio.py:87(readable)&lt;br /&gt;
      500    0.002    0.000    0.004    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:442(__init__)&lt;br /&gt;
     3500    0.003    0.000    0.003    0.000 {method '_checkClosed' of '_io._IOBase' objects}&lt;br /&gt;
     1000    0.001    0.000    0.003    0.000 /Users/classic/dev/PyMySQL/pymysql/converters.py:59(escape_string)&lt;br /&gt;
     1500    0.003    0.000    0.003    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1080(__init__)&lt;br /&gt;
     1500    0.001    0.000    0.003    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:92(nextset)&lt;br /&gt;
     4000    0.002    0.000    0.002    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:397(get_column_length)&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 /Users/classic/dev/PyMySQL/pymysql/converters.py:56(escape_float)&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 {method 'sub' of '_sre.SRE_Pattern' objects}&lt;br /&gt;
     3000    0.002    0.000    0.002    0.000 {_struct.pack}&lt;br /&gt;
     6000    0.002    0.000    0.002    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:62(_get_db)&lt;br /&gt;
     1500    0.001    0.000    0.002    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:80(_nextset)&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 /Users/classic/dev/PyMySQL/pymysql/util.py:9(int2byte)&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:244(fetchall)&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {min}&lt;br /&gt;
     2500    0.001    0.000    0.001    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:323(is_ok_packet)&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Users/classic/dev/PyMySQL/pymysql/converters.py:52(escape_int)&lt;br /&gt;
     2000    0.001    0.000    0.001    0.000 {method 'items' of 'dict' objects}&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {getattr}&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1093(__del__)&lt;br /&gt;
     1000    0.000    0.000    0.000    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:259(read_all)&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:67(_check_executed)&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:336(is_load_local_packet)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/contextlib.py:21(__exit__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 test.py:8(profiled)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DBAPI:  &amp;lt;module 'mysql.connector' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/__init__.pyc'&amp;gt;&lt;br /&gt;
         11674074 function calls in 19.049 seconds&lt;br /&gt;
&lt;br /&gt;
   Ordered by: cumulative time&lt;br /&gt;
&lt;br /&gt;
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;
      500    0.640    0.001   18.556    0.037 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:820(fetchall)&lt;br /&gt;
      500    0.003    0.000   11.196    0.022 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:655(get_rows)&lt;br /&gt;
      500    1.561    0.003   11.191    0.022 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:292(read_text_result)&lt;br /&gt;
   504500    3.303    0.000    7.306    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/network.py:219(recv_plain)&lt;br /&gt;
   500000    3.572    0.000    6.625    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:359(row_to_python)&lt;br /&gt;
  1009000    3.155    0.000    3.155    0.000 {method 'recv_into' of '_socket.socket' objects}&lt;br /&gt;
   500000    2.049    0.000    2.329    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:220(read_lc_string_list)&lt;br /&gt;
   500000    0.653    0.000    2.116    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:531(_STRING_to_python)&lt;br /&gt;
   502000    0.378    0.000    1.329    0.000 {method 'decode' of 'bytearray' objects}&lt;br /&gt;
   502000    0.221    0.000    0.951    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/utf_8.py:15(decode)&lt;br /&gt;
   502000    0.730    0.000    0.730    0.000 {_codecs.utf_8_decode}&lt;br /&gt;
   500000    0.514    0.000    0.514    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:413(_INT_to_python)&lt;br /&gt;
   509500    0.510    0.000    0.510    0.000 {_struct.unpack_from}&lt;br /&gt;
     1500    0.009    0.000    0.492    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:452(execute)&lt;br /&gt;
     1500    0.004    0.000    0.437    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:705(cmd_query)&lt;br /&gt;
   500000    0.367    0.000    0.367    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:405(_FLOAT_to_python)&lt;br /&gt;
   504500    0.343    0.000    0.343    0.000 {method 'extend' of 'bytearray' objects}&lt;br /&gt;
     1500    0.007    0.000    0.315    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:481(_send_cmd)&lt;br /&gt;
  2501500    0.275    0.000    0.275    0.000 {method 'append' of 'list' objects}&lt;br /&gt;
   500500    0.246    0.000    0.246    0.000 {method 'startswith' of 'bytearray' objects}&lt;br /&gt;
   524002    0.150    0.000    0.150    0.000 {isinstance}&lt;br /&gt;
     1500    0.011    0.000    0.118    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:613(_handle_result)&lt;br /&gt;
  1004500    0.112    0.000    0.112    0.000 {len}&lt;br /&gt;
   500000    0.092    0.000    0.092    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:204(description)&lt;br /&gt;
     1500    0.007    0.000    0.042    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/network.py:118(send_plain)&lt;br /&gt;
     2000    0.013    0.000    0.041    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:226(parse_column)&lt;br /&gt;
     1000    0.008    0.000    0.034    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:346(_process_params_dict)&lt;br /&gt;
     1500    0.002    0.000    0.029    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/socket.py:223(meth)&lt;br /&gt;
     1500    0.026    0.000    0.026    0.000 {method 'sendall' of '_socket.socket' objects}&lt;br /&gt;
     5000    0.007    0.000    0.014    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/catch23.py:79(struct_unpack)&lt;br /&gt;
     1000    0.001    0.000    0.013    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:562(_handle_ok)&lt;br /&gt;
    12000    0.011    0.000    0.011    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:167(read_lc_string)&lt;br /&gt;
     1000    0.004    0.000    0.010    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:199(parse_ok)&lt;br /&gt;
     3000    0.005    0.000    0.009    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:152(to_mysql)&lt;br /&gt;
     1500    0.003    0.000    0.007    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:118(make_command)&lt;br /&gt;
     3000    0.003    0.000    0.007    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:130(quote)&lt;br /&gt;
     1500    0.005    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/network.py:53(_prepare_packets)&lt;br /&gt;
     1500    0.003    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:406(_handle_result)&lt;br /&gt;
     3000    0.003    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:102(escape)&lt;br /&gt;
     1000    0.002    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:252(parse_eof)&lt;br /&gt;
      500    0.001    0.000    0.005    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:579(_handle_eof)&lt;br /&gt;
     2000    0.003    0.000    0.005    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:545(_handle_server_status)&lt;br /&gt;
     1500    0.003    0.000    0.004    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:53(int1store)&lt;br /&gt;
     3000    0.003    0.000    0.003    0.000 {method 'encode' of 'str' objects}&lt;br /&gt;
     9001    0.003    0.000    0.003    0.000 {method 'replace' of 'str' objects}&lt;br /&gt;
     1500    0.003    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:295(_reset_result)&lt;br /&gt;
     4500    0.003    0.000    0.003    0.000 {_struct.pack}&lt;br /&gt;
     2500    0.003    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:296(read_lc_int)&lt;br /&gt;
      500    0.002    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:757(_handle_eof)&lt;br /&gt;
     6027    0.003    0.000    0.003    0.000 {method 'format' of 'str' objects}&lt;br /&gt;
     3000    0.002    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:1221(_set_unread_result)&lt;br /&gt;
      500    0.001    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:219(parse_column_count)&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 {repr}&lt;br /&gt;
     4527    0.002    0.000    0.002    0.000 {getattr}&lt;br /&gt;
     4000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/constants.py:34(flag_is_set)&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:383(_handle_noresultset)&lt;br /&gt;
     4000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:1233(_get_unread_result)&lt;br /&gt;
     2001    0.001    0.000    0.001    0.000 {method 'items' of 'dict' objects}&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:307(_have_unread_result)&lt;br /&gt;
     3000    0.001    0.000    0.001    0.000 {method 'lower' of 'str' objects}&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 {range}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:173(_str_to_mysql)&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:169(_float_to_mysql)&lt;br /&gt;
     1000    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:161(_int_to_mysql)&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:1313(_get_getwarnings)&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:200(reset)&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:397(_handle_resultset)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/__init__.py:71(search_function)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/ascii.py:41(getregentry)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/contextlib.py:21(__exit__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/__init__.py:49(normalize_encoding)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 test.py:8(profiled)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/codecs.py:77(__new__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {__import__}&lt;br /&gt;
        3    0.000    0.000    0.000    0.000 {method 'get' of 'dict' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'split' of 'str' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {built-in method __new__ of type object at 0x1001534e8}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'translate' of 'str' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'join' of 'str' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {hasattr}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DBAPI:  &amp;lt;module 'MySQLdb' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/__init__.pyc'&amp;gt;&lt;br /&gt;
         47503 function calls in 0.918 seconds&lt;br /&gt;
&lt;br /&gt;
   Ordered by: cumulative time&lt;br /&gt;
&lt;br /&gt;
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;
     1500    0.009    0.000    0.917    0.001 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:164(execute)&lt;br /&gt;
     1500    0.002    0.000    0.890    0.001 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:353(_query)&lt;br /&gt;
     1500    0.004    0.000    0.498    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:315(_do_query)&lt;br /&gt;
     1500    0.016    0.000    0.391    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:358(_post_get_result)&lt;br /&gt;
     1500    0.001    0.000    0.375    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:324(_fetch_row)&lt;br /&gt;
      500    0.374    0.001    0.374    0.001 {built-in method fetch_row}&lt;br /&gt;
     1500    0.007    0.000    0.362    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:142(_do_get_result)&lt;br /&gt;
     1500    0.002    0.000    0.351    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:351(_get_result)&lt;br /&gt;
     1500    0.349    0.000    0.349    0.000 {method 'store_result' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.131    0.000    0.131    0.000 {method 'query' of '_mysql.connection' objects}&lt;br /&gt;
     4000    0.002    0.000    0.010    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:184(&amp;lt;genexpr&amp;gt;)&lt;br /&gt;
     3000    0.002    0.000    0.008    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/connections.py:267(literal)&lt;br /&gt;
     1500    0.007    0.000    0.007    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:107(_warning_check)&lt;br /&gt;
     3000    0.002    0.000    0.006    0.000 {method 'escape' of '_mysql.connection' objects}&lt;br /&gt;
     6000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:159(_get_db)&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/converters.py:81(Float2Str)&lt;br /&gt;
     2500    0.001    0.000    0.001    0.000 {isinstance}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/connections.py:202(string_literal)&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:380(fetchall)&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 {built-in method describe}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 {method 'string_literal' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {method 'insert_id' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {method 'affected_rows' of '_mysql.connection' objects}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/converters.py:69(Thing2Str)&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 {method 'warning_count' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 {method 'info' of '_mysql.connection' objects}&lt;br /&gt;
     1000    0.000    0.000    0.000    0.000 {method 'iteritems' of 'dict' objects}&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:103(_check_executed)&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 {len}&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 {built-in method field_flags}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/contextlib.py:21(__exit__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 test.py:8(profiled)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DBAPI:  &amp;lt;module 'pymysql' from '/Users/classic/dev/PyMySQL/pymysql/__init__.pyc'&amp;gt;, total seconds 10.046012&lt;br /&gt;
DBAPI:  &amp;lt;module 'mysql.connector' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/__init__.pyc'&amp;gt;, total seconds 15.403260&lt;br /&gt;
DBAPI:  &amp;lt;module 'MySQLdb' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/__init__.pyc'&amp;gt;, total seconds 1.028737&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mike Bayer</name></author>	</entry>

	<entry>
		<id>https://wiki.openstack.org/w/index.php?title=PyMySQL_evaluation&amp;diff=72554</id>
		<title>PyMySQL evaluation</title>
		<link rel="alternate" type="text/html" href="https://wiki.openstack.org/w/index.php?title=PyMySQL_evaluation&amp;diff=72554"/>
				<updated>2015-01-28T20:24:36Z</updated>
		
		<summary type="html">&lt;p&gt;Mike Bayer: /* PyMySQL */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= PyMySQL Evaluation =&lt;br /&gt;
&lt;br /&gt;
This page will capture issues related to Openstack moving to the [https://github.com/PyMySQL/PyMySQL/ PyMySQL driver] for MySQL/MariaDB dbapi access.&lt;br /&gt;
&lt;br /&gt;
== Rationale ==&lt;br /&gt;
&lt;br /&gt;
While the [https://github.com/farcepest/MySQLdb1 MySQL-Python] driver is a very mature and stable driver, it does not provide compatibility with either Python 3 or with [http://eventlet.net/doc/patching.html eventlet monkeypatching] (MySQL-Python  [https://github.com/eventlet/eventlet/blob/v0.16.1/eventlet/patcher.py#L225-L237 can be monkeypatched] with eventlet, but this feature disabled by default and missed in documentation). So OpenStack's usage of MySQL-Python, combined with the fact that concurrency is provided by eventlet, means that we currently have fully serialized database access within a single process, that is, only one database command occurs at a time within an Openstack Python process.&lt;br /&gt;
&lt;br /&gt;
== Drivers Under Consideration ==&lt;br /&gt;
&lt;br /&gt;
The two drivers that are known to provide eventlet-monkeypatch compatibility are MySQL-Connector and PyMySQL, as they are written in pure Python.   The two other well-known drivers for MySQL are MySQL-Python and OurSQL, both of which are written in C and offer no explicit support for eventlet or async.  They can reportedly be built to support gevent using a system called [https://github.com/CMGS/greenify Greenify], but the maturity and/or stabiltiy of this system is unknown.&lt;br /&gt;
&lt;br /&gt;
A comparison at http://www.diamondtin.com/2014/sqlalchemy-gevent-mysql-python-drivers-comparison/ illustrates performance metrics observed with all four of these drivers, with MySQL-python built both without and with the &amp;quot;greenify&amp;quot; system.&lt;br /&gt;
&lt;br /&gt;
A summary of the status of all four drivers is as follows.&lt;br /&gt;
&lt;br /&gt;
=== MySQL-Python ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/farcepest/MySQLdb1 MySQL-Python] is the most widely used Python driver.  However, it does not support Python 3, and it does not support async systems unless used within a thread pool.   While a system known as Greenify can potentially resolve the latter situation, the Python 3 limitation is still a deal-breaker - pull request with Python 3 support [https://github.com/farcepest/MySQLdb1/pull/62 was proposed] to MySQL-python in April 2014 and still not merged.&lt;br /&gt;
&lt;br /&gt;
=== mysqlclient ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/PyMySQL/mysqlclient-python mysqlclient] is a fork of MySQL-python. It add Python 3.3 support and merges some pull requests.&lt;br /&gt;
&lt;br /&gt;
=== OurSQL ===&lt;br /&gt;
&lt;br /&gt;
[https://pythonhosted.org/oursql/ OurSQL] is an alternative MySQL driver that is also written in C.  It features fast performance and uses a different execution model than MySQL-python, using prepared statements.    OurSQL does have a Python 3 port hosted on [https://launchpad.net/oursql/py3k/ launchpad] but is not integrated with OurSQL itself nor is it published on Pypi.     Because it is written in C, it does not offer compatibility with async systems including eventlet-style monkeypatching.&lt;br /&gt;
&lt;br /&gt;
Besides the shaky Python 3 support and lack of eventlet support, OurSQL is also not maintained, seeing its last master commit on [https://code.launchpad.net/~jerub/oursql/master 2012-06-05].&lt;br /&gt;
&lt;br /&gt;
=== MySQL-Connector-Python ===&lt;br /&gt;
&lt;br /&gt;
[https://pypi.python.org/pypi/mysql-connector-python MySQL-connector-Python] is a pure Python MySQL driver, and is now released under the auspices of the MySQL project itself, as owned by Oracle.  MySQL-connector-Python supports both Python 3 as well as eventlet monkeypatching, and is well maintained.  It is endorsed by Oracle as the official Oracle-supported driver for MySQL, so to that extent, it is in most ways Openstack's first choice in driver.  However, Oracle refuses to publish MySQL-connector-Python on Pypi, which is critical to the Openstack infrastructure.  Repeated attempts to communicate with Oracle in order to resolve this issue have not made any progress.    Therefore, for this one unfortunate reason, MySQL-connector-Python will not have a place in the Openstack ecosystem unless this issue is resolved.&lt;br /&gt;
&lt;br /&gt;
=== PyMySQL ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/PyMySQL/PyMySQL PyMySQL] is a pure Python MySQL&lt;br /&gt;
driver, first written as a rough port of the MySQL-Python driver.&lt;br /&gt;
PyMySQL meets all of Openstack's criterion for a driver: it is fully&lt;br /&gt;
open source, hosted on Github, released on Pypi, is actively&lt;br /&gt;
maintained, is written in pure Python so is eventlet-monkeypatch&lt;br /&gt;
compatible, and is fully Python 3 compatible.&lt;br /&gt;
&lt;br /&gt;
As this document is named &amp;quot;PyMySQL Evaluation&amp;quot;, it should be apparent that&lt;br /&gt;
this is the driver Openstack is currently leaning towards; because it is the only&lt;br /&gt;
one that meets all criteria fully, it is already most likely the &amp;quot;winner&amp;quot;.  However, it does&lt;br /&gt;
have some minor code quality issues which hopefully can be addressed in some&lt;br /&gt;
way; the section below titled &amp;quot;PyMySQL Code Review&amp;quot; will summarize the&lt;br /&gt;
current state of the code.&lt;br /&gt;
&lt;br /&gt;
===MySQL DB Drivers Comparison===&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Project !! PyPi hosted !! Eventlet friendly !! Python 3 compatibility !! Maturity and/or stability  !! Comment&lt;br /&gt;
|-&lt;br /&gt;
| MySQL-Python || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: orange;&amp;quot; | Partial || style=&amp;quot;background-color: red;&amp;quot; | No  || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || Can be monkeypatched by eventlet, but only to enable thread pooling&lt;br /&gt;
|-&lt;br /&gt;
| mysqlclient || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: orange;&amp;quot; | Partial || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes  || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || MySQL-python fork, so should be similar to it&lt;br /&gt;
|-&lt;br /&gt;
| OurSQL || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: red;&amp;quot; | No || style=&amp;quot;background-color: orange;&amp;quot; | Yes, but not Pypi hosted  || style=&amp;quot;background-color: orange;&amp;quot; | ??? || Has not seen commits/releases in two years&lt;br /&gt;
|-&lt;br /&gt;
| MySQL-Connector-Python || style=&amp;quot;background-color: red;&amp;quot; | No || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes  || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || The official Oracle-supported driver for MySQL&lt;br /&gt;
|-&lt;br /&gt;
| PyMySQL || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes  || style=&amp;quot;background-color: orange;&amp;quot; | Yes, with a [http://lists.openstack.org/pipermail/openstack-dev/2014-December/052864.html proviso]. ||&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== PyMySQL Code Review ==&lt;br /&gt;
&lt;br /&gt;
PyMySQL started roughly as a pure Python port of MySQL-Python.   The sections below will&lt;br /&gt;
detail various aspects of the driver and the status of the code.   Overall, the general&lt;br /&gt;
tone of PyMySQL is one of code that was written in the spirit of pragmatism and immediate&lt;br /&gt;
need; it is straightforward, relatively free of serious antipatterns and performs&lt;br /&gt;
its task in a matter-of-fact way.  However,&lt;br /&gt;
it lacks polish and completeness in many areas that would normally be in better&lt;br /&gt;
condition for a more mature project.  These areas are all highly fixable, and it is&lt;br /&gt;
hoped that these items can be shared with the current PyMySQL developers, through&lt;br /&gt;
a combination of this document itself as well as new issues and pull requests&lt;br /&gt;
on the PyMySQL tracker, so that this fairly promising library can be pushed&lt;br /&gt;
to the next level.   This would require that the developers are amenable to&lt;br /&gt;
these improvements.&lt;br /&gt;
&lt;br /&gt;
=== Coding Style / Pep8 ===&lt;br /&gt;
&lt;br /&gt;
PyMySQL's coding style is pretty good.  The majority of it passes all&lt;br /&gt;
flake8 tests, save for a few dozen whitespace issues and&lt;br /&gt;
some long lines here and there within the core&lt;br /&gt;
modules, and moreso within the tests which appear to be a bit more&lt;br /&gt;
crufty than the core modules.  A full run of flake8 produces only 128&lt;br /&gt;
errors, and with a default run of autopep8 against E1,E2,E3, we can&lt;br /&gt;
fix all of them in one pass except for 38 remaining long line warnings.&lt;br /&gt;
The code layout is mostly idiomatic and reasonable.&lt;br /&gt;
&lt;br /&gt;
The codebase is probably short on docstrings.  Those methods that are&lt;br /&gt;
part of the public DBAPI do have docstrings, but tend to be extremely&lt;br /&gt;
terse (example, docstring for cursor.execute(): &amp;quot;'Execute a query'&amp;quot;).&lt;br /&gt;
Other methods that aren't public tend to not have any docstrings, but&lt;br /&gt;
the codebase is not at all hard to read and the purpose of methods and&lt;br /&gt;
functions is pretty easy to discern just by their names and implementations,&lt;br /&gt;
however those who seek inline documentation as a means to understand a codebase&lt;br /&gt;
will be disappointed.   The terseness of public API docstrings is more of&lt;br /&gt;
an issue in terms of the overall lack of documentation for PyMySQL,&lt;br /&gt;
see the section &amp;quot;Documentation&amp;quot; below.&lt;br /&gt;
&lt;br /&gt;
=== Test Coverage ===&lt;br /&gt;
&lt;br /&gt;
The test coverage for PyMySQL is definitely lacking.   From a visual&lt;br /&gt;
inspection alone, it's apparent that of the dozen or so test modules,&lt;br /&gt;
most of them have less then ten tests each and the modules are very short.&lt;br /&gt;
For a full test run, there's only 126 tests; contrast this to&lt;br /&gt;
MySQL-Connector-Python which has 531 tests.&lt;br /&gt;
&lt;br /&gt;
Additionally, all of the tests are live round-trip tests against a&lt;br /&gt;
real database.  There are no non-live unit tests of any kind,&lt;br /&gt;
leaving codepaths that are not easily exercised&lt;br /&gt;
on a generic MySQL database out in the cold.  This means that features designed&lt;br /&gt;
for particular versions of MySQL, particular datatypes, particular error&lt;br /&gt;
conditions, and particular kinds of message packets that aren't created for&lt;br /&gt;
real are simply not tested.&lt;br /&gt;
&lt;br /&gt;
The lack of tests is apparent when run with coverage.  The test suite itself&lt;br /&gt;
does not appear to have any signs that it is normally run with coverage&lt;br /&gt;
turned on, with no directives in tox.ini or similar.   With coverage,&lt;br /&gt;
the overall coverage is 85%, but the modules most lacking in coverage&lt;br /&gt;
are also the most critical to PyMySQL's core functionality,&lt;br /&gt;
connections.py, converters.py and cursors.py:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Name                                                              Stmts   Miss  Cover&lt;br /&gt;
-------------------------------------------------------------------------------------&lt;br /&gt;
pymysql/__init__                                                     51      3    94%&lt;br /&gt;
pymysql/_compat                                                      14      4    71%&lt;br /&gt;
pymysql/_socketio                                                    72     30    58%&lt;br /&gt;
pymysql/charset                                                     228      3    99%&lt;br /&gt;
pymysql/connections                                                 827    188    77%&lt;br /&gt;
pymysql/constants/CLIENT                                             18      0   100%&lt;br /&gt;
pymysql/constants/COMMAND                                            32      0   100%&lt;br /&gt;
pymysql/constants/ER                                                471      0   100%&lt;br /&gt;
pymysql/constants/FIELD_TYPE                                         29      0   100%&lt;br /&gt;
pymysql/constants/FLAG                                               15      0   100%&lt;br /&gt;
pymysql/constants/SERVER_STATUS                                      10      0   100%&lt;br /&gt;
pymysql/constants/__init__                                            0      0   100%&lt;br /&gt;
pymysql/converters                                                  148     51    66%&lt;br /&gt;
pymysql/cursors                                                     273     37    86%&lt;br /&gt;
pymysql/err                                                          40      1    98%&lt;br /&gt;
pymysql/tests/__init__                                               14      5    64%&lt;br /&gt;
pymysql/tests/base                                                   25      2    92%&lt;br /&gt;
pymysql/tests/test_DictCursor                                        73      2    97%&lt;br /&gt;
pymysql/tests/test_SSCursor                                          56      6    89%&lt;br /&gt;
pymysql/tests/test_basic                                            186      4    98%&lt;br /&gt;
pymysql/tests/test_connection                                        57      8    86%&lt;br /&gt;
pymysql/tests/test_example                                           17      2    88%&lt;br /&gt;
pymysql/tests/test_issues                                           300     48    84%&lt;br /&gt;
pymysql/tests/test_load_local                                        39      2    95%&lt;br /&gt;
pymysql/tests/test_nextset                                           43      4    91%&lt;br /&gt;
pymysql/tests/thirdparty/__init__                                     7      5    29%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/__init__                        6      2    67%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/capabilities                  196     23    88%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/dbapi20                       423    121    71%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_capabilities      73      6    92%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_dbapi20          100      2    98%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_nonstandard       57      3    95%&lt;br /&gt;
pymysql/times                                                        12      0   100%&lt;br /&gt;
pymysql/util                                                         14      7    50%&lt;br /&gt;
-------------------------------------------------------------------------------------&lt;br /&gt;
TOTAL                                                              3926    569    86%&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The lack of coverage in converters.py refers to functions related to handling&lt;br /&gt;
incoming bound values as well as outgoing result values.    Key data escaping&lt;br /&gt;
features such as escaping of dictionaries, sets, some unicode objects and&lt;br /&gt;
byte objects as well as many pathways into date and time-processing functions are not&lt;br /&gt;
covered.&lt;br /&gt;
&lt;br /&gt;
In connections.py, uncovered features include various SSL and&lt;br /&gt;
very legacy (e.g. version 3.23) MySQL features, but also some protocol parsing&lt;br /&gt;
features.&lt;br /&gt;
&lt;br /&gt;
In cursors.py, there is coverage for an elaborate and mostly undocumented&lt;br /&gt;
performance-related feature that rewrites an INSERT statement to have an&lt;br /&gt;
extended VALUES clause (_do_execute_many), however it fails to cover the very&lt;br /&gt;
likely case where the function will need to break the input set into&lt;br /&gt;
multiple chunks:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;gt;         for arg in args:&lt;br /&gt;
&amp;gt;             v = values % escape(arg, conn)&lt;br /&gt;
&amp;gt;             if isinstance(v, text_type):&lt;br /&gt;
&amp;gt;                 v = v.encode(encoding)&lt;br /&gt;
&amp;gt;             if len(sql) + len(v) + 1 &amp;gt; max_stmt_length:&lt;br /&gt;
!                 rows += self.execute(sql)&lt;br /&gt;
!                 sql = bytearray(prefix)&lt;br /&gt;
&amp;gt;             else:&lt;br /&gt;
&amp;gt;                 sql += b','&lt;br /&gt;
&amp;gt;             sql += v&lt;br /&gt;
&amp;gt;         rows += self.execute(sql)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The most prominent missing coverage in cursors.py seems to be&lt;br /&gt;
for its &amp;quot;scrollable cursor&amp;quot; support.&lt;br /&gt;
&lt;br /&gt;
Beyond the lack of completeness in testing, many of the tests themselves&lt;br /&gt;
are written in an expedient &amp;quot;one giant test&amp;quot; style where dozens of&lt;br /&gt;
assertions and individual behaviors are lumped into one big test&lt;br /&gt;
case (see test_SSCursor.py, test_basic.py-&amp;gt;test_datatypes for examples).&lt;br /&gt;
Test cases like these are difficult to work with when debugging&lt;br /&gt;
regressions as well as when tests for new features need to be added, and ideally&lt;br /&gt;
these should be broken out into clean single-feature tests with consistent&lt;br /&gt;
fixtures.&lt;br /&gt;
&lt;br /&gt;
There is also a suite called &amp;quot;test_issues&amp;quot;, which intends to&lt;br /&gt;
accumulate tests against specific issues that have been reported. This&lt;br /&gt;
isn't a bad idea, though it would be nice to see an effort made into&lt;br /&gt;
categorizing these tests into the actual features they are testing,&lt;br /&gt;
rather than a meaningless list of issue numbers, which could then form&lt;br /&gt;
as the basis for new suites centered around those areas of&lt;br /&gt;
functionality.  The issues reported should be used as the inspiration&lt;br /&gt;
for improvement of the test suite, rather than just another line-item&lt;br /&gt;
to be filed away.&lt;br /&gt;
&lt;br /&gt;
=== Library Documentation ===&lt;br /&gt;
&lt;br /&gt;
As the Python DBAPI is already a well-documented API, and the users of&lt;br /&gt;
PyMySQL are typically coming from MySQL-Python which is documented&lt;br /&gt;
to some degree and for which the use contract is widely known, it seems&lt;br /&gt;
unlikely that users of PyMySQL are eager for comprehensive documentation&lt;br /&gt;
specific to this library.&lt;br /&gt;
&lt;br /&gt;
That being said, PyMySQL does not appear to have any real library&lt;br /&gt;
documentation at all.  There's a README which&lt;br /&gt;
essentially refers to pep-249, an example.py in the root which shows a&lt;br /&gt;
very basic connection / round trip, and that's it. In&lt;br /&gt;
order to know anything about the API, specific parameters, optimization&lt;br /&gt;
strategies, behaviors, etc., one either has to go off of the MySQL-Python documentation and assume&lt;br /&gt;
PyMySQL also features the same parameter, or read the source code.&lt;br /&gt;
&lt;br /&gt;
PyMySQL probably hasn't had much urgent need for real documentation thus&lt;br /&gt;
far.  However, relying upon being a port of MySQL-Python is fast becoming&lt;br /&gt;
something that can no longer be relied upon, as MySQL-Python's development&lt;br /&gt;
is quite stalled, and PyMySQL should aspire to move into the future&lt;br /&gt;
of MySQL and Python with its own featureset and behaviors.  To that end, it would be a &lt;br /&gt;
great idea if it at least made the start&lt;br /&gt;
of a rudimentary Sphinx build including autodoc for module documentation&lt;br /&gt;
which could then be published up to RTD.   This would be very easy to get&lt;br /&gt;
started, and once present, new documentation sections can be added&lt;br /&gt;
iteratively.&lt;br /&gt;
&lt;br /&gt;
=== Architecture and Performance ===&lt;br /&gt;
&lt;br /&gt;
As stated before, PyMySQL has a really matter-of-fact and straightforward&lt;br /&gt;
implementation.   The code is very readable and it's easy to discern even&lt;br /&gt;
unusual features such as the INSERT..VALUES rewriting.  As far as the design,&lt;br /&gt;
there are lots of areas where performance suffers a bit at the Python level;&lt;br /&gt;
these areas could be greatly improved very easily with a little bit of&lt;br /&gt;
attention.&lt;br /&gt;
&lt;br /&gt;
As we are dealing with a driver that is communicating with a low level&lt;br /&gt;
protocol over a socket, it's critical that sending and receiving messages&lt;br /&gt;
is done as efficiently as possible.  The mechanisms used in PyMySQL rely&lt;br /&gt;
heavily on standard Python objects, without the use of __slots__; this means&lt;br /&gt;
that for individual messages on a socket, we are creating a heavyweight object,&lt;br /&gt;
calling its __init__() method and a creating a new __dict__ each time;&lt;br /&gt;
the most common object, OKPacketWrapper(), already wraps a MysqlPacket()&lt;br /&gt;
object, so we're doing two __init__()s in this common case.  OKPacketWrapper&lt;br /&gt;
also implements a `__getattr__` scheme&lt;br /&gt;
to proxy attribute access from itself to the internal MysqlPacket; this is a&lt;br /&gt;
an inefficient mode of operation in Python as it means a lookup must first&lt;br /&gt;
fail on the OKPacketWrapper before it invokes the `__getattr__()` method&lt;br /&gt;
and does another attribute lookup.   A reorg of the packet classes to produce&lt;br /&gt;
far fewer objects for messages as well as to use __slots__ would be&lt;br /&gt;
recommended.&lt;br /&gt;
&lt;br /&gt;
There are also lots of areas where function call overhead is unnecessarily&lt;br /&gt;
high due to the organization of calls.  For example, when calling upon&lt;br /&gt;
cursor.execute(), for parameters passed as a dictionary under Python 2K,&lt;br /&gt;
the isinstance() builtin function will be called on the incoming arguments&lt;br /&gt;
four times at a minimum, with two of each call against the exact same&lt;br /&gt;
criteria.  Additional logic will then call isinstance() on every key of&lt;br /&gt;
the dictionary as well as at least twice on every value within the dictionary,&lt;br /&gt;
in order to determine if the value needs to be converted to bytes as well&lt;br /&gt;
as for some top-level escaping logic checks; the escaping logic then&lt;br /&gt;
goes further with more checks for type() to match the object to a final&lt;br /&gt;
converter function.   While the flow of this logic does appear to be&lt;br /&gt;
reasonable and straightforward, it isn't ideal for a low-level database&lt;br /&gt;
driver library where speed is a high priority; the various wrappers and&lt;br /&gt;
processors can easily be reorganized here to reduce the number of isinstance()&lt;br /&gt;
and type() calls significantly, which would save a lot on function call&lt;br /&gt;
overhead.  It is likely there are many other areas in the codebase where&lt;br /&gt;
similar reductions in call counts can be made without too much disruption.&lt;br /&gt;
&lt;br /&gt;
In Appendix A, a short performance suite is illustrated.  This&lt;br /&gt;
is run against PyMySQL, Mysql-Connector-Python, and MySQL-Python.&lt;br /&gt;
PyMySQL does in fact demonstrate the&lt;br /&gt;
highest call-count behavior, logging 26,851,003 function calls&lt;br /&gt;
compared to MySQL-connector's 11,674,074 for the same operations.  To PyMySQL's&lt;br /&gt;
credit, it somehow completes the test 25-50% faster than MySQL-connector&lt;br /&gt;
despite having more than twice as many function calls; this probably indicates&lt;br /&gt;
the use of something very slow within MySQL-connector, such as catching&lt;br /&gt;
an exception within a tight loop.  However, the real bar we're comparing to&lt;br /&gt;
is MySQL-Python, which is written&lt;br /&gt;
in C and does the whole test with only 47,503 Python function calls; while&lt;br /&gt;
PyMySQL and MySQL-connector-Python compete for time within the 10-15 second&lt;br /&gt;
range without profiling, MySQL-Python completes the whole test in **1.03 seconds**, a 1000%&lt;br /&gt;
improvement over PyMySQL.   This speed difference is not PyMySQL's fault,&lt;br /&gt;
as pure Python is known to be extremely slow compared to C code.&lt;br /&gt;
However, there's a lot of easy wins in PyMySQL's codebase where the function&lt;br /&gt;
call count could probably be reduced dramatically to be even better than that&lt;br /&gt;
of MySQL-connector-Python; ideally, the driver would be able to run this&lt;br /&gt;
test possibly 5-6x slower than MySQL-Python rather than 10x.&lt;br /&gt;
&lt;br /&gt;
=== History Documentation ===&lt;br /&gt;
&lt;br /&gt;
PyMySQL includes a CHANGELOG file in its root which describes the bug fixes&lt;br /&gt;
and features of each release.  The construction of this file is definitely&lt;br /&gt;
within the &amp;quot;expedient&amp;quot; style noted for PyMySQL overall, and for it to be&lt;br /&gt;
genuinely useful for enterprise-level software, it would need to be&lt;br /&gt;
much more complete in its detail.    Each bug fix mentioned is only referred to&lt;br /&gt;
tersely without any link to a pull request or bug report.  While some can&lt;br /&gt;
be linked to a specific change through detective work&lt;br /&gt;
(&amp;quot;Cursor.fetchall() and .fetchmany now return list, not tuple&amp;quot;), it would&lt;br /&gt;
be very difficult for many&lt;br /&gt;
(&amp;quot;Fixed BIT type handling&amp;quot;, &amp;quot;Fixed GC errors&amp;quot; - what was the issue?)&lt;br /&gt;
and in many cases impossible (&amp;quot;Improved Py3k support&amp;quot;).  The file also&lt;br /&gt;
includes no dates so one has to look at the git tags to figure this&lt;br /&gt;
part out (fortunately, releases to seem to be git tagged at least).&lt;br /&gt;
For a user trying to ascertain if particular issues are&lt;br /&gt;
fixed or if behaviors have changed, this file is at best highly frustrating.&lt;br /&gt;
&lt;br /&gt;
A listing of release dates illustrates an average of two releases per year,&lt;br /&gt;
excluding 2012 when PyMySQL was seeking a new maintainer:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
2014-12-02 12:05:10 -0200  (tag: pymysql-0.6.3)&lt;br /&gt;
2014-04-21 14:28:47 -0300  (tag: pymysql-0.6.2)&lt;br /&gt;
2013-10-11 13:36:56 -0300  (tag: pymysql-0.6.1)&lt;br /&gt;
2013-10-04 14:25:14 -0300  (tag: pymysql-0.6)&lt;br /&gt;
2011-11-08 10:27:41 -0800  (tag: pymysql-0.5)&lt;br /&gt;
2010-12-27 17:28:16 +0000  (tag: pymysql-0.4)&lt;br /&gt;
2010-09-03 00:55:29 +0000  (tag: pymysql-0.3)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Community Involvement ===&lt;br /&gt;
&lt;br /&gt;
This is an area where PyMySQL seems to shine, and might even be an advantage&lt;br /&gt;
as compared to MySQL-Connector-Python, which remains under the cloak&lt;br /&gt;
of MySQL's vastly overstuffed bugtracker under the ownership of&lt;br /&gt;
the famously-opaque Oracle.   For a small, low key library without&lt;br /&gt;
any real documentation and only 126 tests, it features 33 contributors on&lt;br /&gt;
Github and is up to pull request #285, which is quite high.  Because&lt;br /&gt;
PyMySQL is small and straightforward, it does lend itself to contribution&lt;br /&gt;
and seems to do very well in this area.   The developers so far have been&lt;br /&gt;
responsive to issues and pull requests made by the author.&lt;br /&gt;
&lt;br /&gt;
Based on github contributor graphs it appears that PyMySQL had a lull&lt;br /&gt;
in 2012 but was picked up again afterwards in 2013.  As of 2012, its most&lt;br /&gt;
prominent developer Pete Hunt stopped development, and on July 25, 2013&lt;br /&gt;
added a note to the README that the project was looking for a new maintainer.&lt;br /&gt;
As of 2013 it was picked up by the current most prominent developers&lt;br /&gt;
Marcel Rodrigues (github username lecram) and INADA Naoki&lt;br /&gt;
(github username methane).&lt;br /&gt;
&lt;br /&gt;
Most issues that have been noted thus far within the areas of testing,&lt;br /&gt;
documentation and architecture are all very fixable; if the Openstack community&lt;br /&gt;
can provide resources to address some of these areas, and if the PyMySQL&lt;br /&gt;
developers are reasonably open to accepting new changes and producing new&lt;br /&gt;
releases in a timely fashion, the PyMySQL driver&lt;br /&gt;
could rapidly become a fully formidable contender in the MySQL driver field.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Appendix A - Performance Tests ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
import cProfile&lt;br /&gt;
import StringIO&lt;br /&gt;
import pstats&lt;br /&gt;
import contextlib&lt;br /&gt;
import time&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextlib.contextmanager&lt;br /&gt;
def profiled(dbapi):&lt;br /&gt;
    pr = cProfile.Profile()&lt;br /&gt;
    pr.enable()&lt;br /&gt;
    yield&lt;br /&gt;
    pr.disable()&lt;br /&gt;
    s = StringIO.StringIO()&lt;br /&gt;
    ps = pstats.Stats(pr, stream=s).sort_stats('cumulative')&lt;br /&gt;
    ps.print_stats()&lt;br /&gt;
    print &amp;quot;DBAPI:  %s&amp;quot; % dbapi&lt;br /&gt;
    print s.getvalue()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextlib.contextmanager&lt;br /&gt;
def timeonly(dbapi):&lt;br /&gt;
    now = time.time()&lt;br /&gt;
    try:&lt;br /&gt;
        yield&lt;br /&gt;
    finally:&lt;br /&gt;
        total = time.time() - now&lt;br /&gt;
        print &amp;quot;DBAPI:  %s, total seconds %f&amp;quot; % (dbapi, total)&lt;br /&gt;
&lt;br /&gt;
import MySQLdb&lt;br /&gt;
import pymysql&lt;br /&gt;
from mysql import connector as mysqlconnector&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def go(dbapi, ctx):&lt;br /&gt;
    conn = dbapi.connect(&lt;br /&gt;
        user='scott', passwd='tiger', host='localhost', db='test')&lt;br /&gt;
    cursor = conn.cursor()&lt;br /&gt;
    cursor.execute(&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
CREATE TABLE IF NOT EXISTS test_things (&lt;br /&gt;
    x INTEGER,&lt;br /&gt;
    y VARCHAR(255),&lt;br /&gt;
    z FLOAT&lt;br /&gt;
) engine=InnoDB&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;)&lt;br /&gt;
    cursor.execute(&amp;quot;DELETE from test_things&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    with ctx(dbapi):&lt;br /&gt;
        for row in xrange(1000):&lt;br /&gt;
            cursor.execute(&lt;br /&gt;
                &amp;quot;INSERT INTO test_things (x, y, z) &amp;quot;&lt;br /&gt;
                &amp;quot;VALUES (%(x)s, %(y)s, %(z)s)&amp;quot;,&lt;br /&gt;
                {&amp;quot;x&amp;quot;: row, &amp;quot;y&amp;quot;: &amp;quot;row number %d&amp;quot; % row,&lt;br /&gt;
                 &amp;quot;z&amp;quot;: row * 4.57292, }&lt;br /&gt;
            )&lt;br /&gt;
&lt;br /&gt;
        for x in xrange(500):&lt;br /&gt;
            cursor.execute(&lt;br /&gt;
                &amp;quot;select * from test_things&amp;quot;)&lt;br /&gt;
            for row in cursor.fetchall():&lt;br /&gt;
                row[0], row[1], row[2]&lt;br /&gt;
&lt;br /&gt;
    cursor.close()&lt;br /&gt;
    conn.close()&lt;br /&gt;
&lt;br /&gt;
go(pymysql, profiled)&lt;br /&gt;
go(mysqlconnector, profiled)&lt;br /&gt;
go(MySQLdb, profiled)&lt;br /&gt;
&lt;br /&gt;
go(pymysql, timeonly)&lt;br /&gt;
go(mysqlconnector, timeonly)&lt;br /&gt;
go(MySQLdb, timeonly)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DBAPI:  &amp;lt;module 'pymysql' from '/Users/classic/dev/PyMySQL/pymysql/__init__.pyc'&amp;gt;&lt;br /&gt;
         26851003 function calls in 15.924 seconds&lt;br /&gt;
&lt;br /&gt;
   Ordered by: cumulative time&lt;br /&gt;
&lt;br /&gt;
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;
     1500    0.010    0.000   15.923    0.011 /Users/classic/dev/PyMySQL/pymysql/cursors.py:105(execute)&lt;br /&gt;
     1500    0.003    0.000   15.883    0.011 /Users/classic/dev/PyMySQL/pymysql/cursors.py:269(_query)&lt;br /&gt;
     1500    0.004    0.000   15.859    0.011 /Users/classic/dev/PyMySQL/pymysql/connections.py:746(query)&lt;br /&gt;
     1500    0.003    0.000   15.819    0.011 /Users/classic/dev/PyMySQL/pymysql/connections.py:892(_read_query_result)&lt;br /&gt;
     1500    0.004    0.000   15.812    0.011 /Users/classic/dev/PyMySQL/pymysql/connections.py:1097(read)&lt;br /&gt;
      500    0.003    0.000   15.559    0.031 /Users/classic/dev/PyMySQL/pymysql/connections.py:1154(_read_result_packet)&lt;br /&gt;
      500    0.900    0.002   15.429    0.031 /Users/classic/dev/PyMySQL/pymysql/connections.py:1187(_read_rowdata_packet)&lt;br /&gt;
   500000    3.417    0.000    9.173    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1200(_read_row_from_packet)&lt;br /&gt;
  2012000    1.157    0.000    5.307    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:311(read_length_coded_string)&lt;br /&gt;
   504500    1.495    0.000    5.209    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:845(_read_packet)&lt;br /&gt;
  2014500    1.248    0.000    3.035    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:293(read_length_encoded_integer)&lt;br /&gt;
  3539500    2.439    0.000    2.750    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:245(read)&lt;br /&gt;
  1009000    0.779    0.000    2.307    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:870(_read_bytes)&lt;br /&gt;
  1009000    1.197    0.000    1.423    0.000 {method 'read' of '_io.BufferedReader' objects}&lt;br /&gt;
   508500    0.379    0.000    0.695    0.000 /Users/classic/dev/PyMySQL/pymysql/util.py:3(byte2int)&lt;br /&gt;
  5058000    0.465    0.000    0.465    0.000 {len}&lt;br /&gt;
   500500    0.222    0.000    0.455    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1146(_check_packet_is_eof)&lt;br /&gt;
   504500    0.205    0.000    0.375    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:342(check_error)&lt;br /&gt;
  2504000    0.275    0.000    0.275    0.000 {method 'append' of 'list' objects}&lt;br /&gt;
   501500    0.182    0.000    0.230    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:326(is_eof_packet)&lt;br /&gt;
     3500    0.009    0.000    0.226    0.000 /Users/classic/dev/PyMySQL/pymysql/_socketio.py:45(readinto)&lt;br /&gt;
     3500    0.207    0.000    0.207    0.000 {method 'recv_into' of '_socket.socket' objects}&lt;br /&gt;
  1022000    0.203    0.000    0.203    0.000 {_struct.unpack}&lt;br /&gt;
   526000    0.200    0.000    0.200    0.000 {isinstance}&lt;br /&gt;
  1501000    0.195    0.000    0.195    0.000 {method 'get' of 'dict' objects}&lt;br /&gt;
   504500    0.176    0.000    0.176    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:238(__init__)&lt;br /&gt;
   504500    0.170    0.000    0.170    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:339(is_error_packet)&lt;br /&gt;
  2014500    0.167    0.000    0.167    0.000 {ord}&lt;br /&gt;
      500    0.006    0.000    0.125    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1224(_get_descriptions)&lt;br /&gt;
     2000    0.003    0.000    0.087    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:361(__init__)&lt;br /&gt;
     2000    0.019    0.000    0.084    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:365(__parse_field_descriptor)&lt;br /&gt;
   500000    0.056    0.000    0.056    0.000 /Users/classic/dev/PyMySQL/pymysql/converters.py:301(through)&lt;br /&gt;
     1500    0.005    0.000    0.037    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:915(_execute_command)&lt;br /&gt;
     1500    0.003    0.000    0.027    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:886(_write_bytes)&lt;br /&gt;
     1500    0.002    0.000    0.024    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/socket.py:223(meth)&lt;br /&gt;
     1500    0.021    0.000    0.021    0.000 {method 'sendall' of '_socket.socket' objects}&lt;br /&gt;
     1500    0.020    0.000    0.020    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:276(_do_get_result)&lt;br /&gt;
     1000    0.003    0.000    0.019    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:95(_escape_args)&lt;br /&gt;
     4000    0.002    0.000    0.015    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:99(&amp;lt;genexpr&amp;gt;)&lt;br /&gt;
     1000    0.002    0.000    0.014    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1127(_read_ok_packet)&lt;br /&gt;
     3000    0.003    0.000    0.013    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:712(escape)&lt;br /&gt;
     1000    0.005    0.000    0.012    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:416(__init__)&lt;br /&gt;
     8000    0.011    0.000    0.011    0.000 {method 'decode' of 'str' objects}&lt;br /&gt;
     3500    0.003    0.000    0.007    0.000 {method '_checkReadable' of '_io._IOBase' objects}&lt;br /&gt;
     2000    0.003    0.000    0.006    0.000 /Users/classic/dev/PyMySQL/pymysql/converters.py:19(escape_item)&lt;br /&gt;
     4000    0.002    0.000    0.006    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:126(&amp;lt;genexpr&amp;gt;)&lt;br /&gt;
     7500    0.003    0.000    0.005    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:115(ensure_bytes)&lt;br /&gt;
     2000    0.003    0.000    0.005    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:386(description)&lt;br /&gt;
     5500    0.004    0.000    0.004    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:268(advance)&lt;br /&gt;
     1000    0.001    0.000    0.004    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:722(escape_string)&lt;br /&gt;
     3500    0.004    0.000    0.004    0.000 /Users/classic/dev/PyMySQL/pymysql/_socketio.py:87(readable)&lt;br /&gt;
      500    0.002    0.000    0.004    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:442(__init__)&lt;br /&gt;
     3500    0.003    0.000    0.003    0.000 {method '_checkClosed' of '_io._IOBase' objects}&lt;br /&gt;
     1000    0.001    0.000    0.003    0.000 /Users/classic/dev/PyMySQL/pymysql/converters.py:59(escape_string)&lt;br /&gt;
     1500    0.003    0.000    0.003    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1080(__init__)&lt;br /&gt;
     1500    0.001    0.000    0.003    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:92(nextset)&lt;br /&gt;
     4000    0.002    0.000    0.002    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:397(get_column_length)&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 /Users/classic/dev/PyMySQL/pymysql/converters.py:56(escape_float)&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 {method 'sub' of '_sre.SRE_Pattern' objects}&lt;br /&gt;
     3000    0.002    0.000    0.002    0.000 {_struct.pack}&lt;br /&gt;
     6000    0.002    0.000    0.002    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:62(_get_db)&lt;br /&gt;
     1500    0.001    0.000    0.002    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:80(_nextset)&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 /Users/classic/dev/PyMySQL/pymysql/util.py:9(int2byte)&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:244(fetchall)&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {min}&lt;br /&gt;
     2500    0.001    0.000    0.001    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:323(is_ok_packet)&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Users/classic/dev/PyMySQL/pymysql/converters.py:52(escape_int)&lt;br /&gt;
     2000    0.001    0.000    0.001    0.000 {method 'items' of 'dict' objects}&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {getattr}&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1093(__del__)&lt;br /&gt;
     1000    0.000    0.000    0.000    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:259(read_all)&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:67(_check_executed)&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:336(is_load_local_packet)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/contextlib.py:21(__exit__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 test.py:8(profiled)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DBAPI:  &amp;lt;module 'mysql.connector' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/__init__.pyc'&amp;gt;&lt;br /&gt;
         11674074 function calls in 19.049 seconds&lt;br /&gt;
&lt;br /&gt;
   Ordered by: cumulative time&lt;br /&gt;
&lt;br /&gt;
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;
      500    0.640    0.001   18.556    0.037 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:820(fetchall)&lt;br /&gt;
      500    0.003    0.000   11.196    0.022 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:655(get_rows)&lt;br /&gt;
      500    1.561    0.003   11.191    0.022 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:292(read_text_result)&lt;br /&gt;
   504500    3.303    0.000    7.306    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/network.py:219(recv_plain)&lt;br /&gt;
   500000    3.572    0.000    6.625    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:359(row_to_python)&lt;br /&gt;
  1009000    3.155    0.000    3.155    0.000 {method 'recv_into' of '_socket.socket' objects}&lt;br /&gt;
   500000    2.049    0.000    2.329    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:220(read_lc_string_list)&lt;br /&gt;
   500000    0.653    0.000    2.116    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:531(_STRING_to_python)&lt;br /&gt;
   502000    0.378    0.000    1.329    0.000 {method 'decode' of 'bytearray' objects}&lt;br /&gt;
   502000    0.221    0.000    0.951    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/utf_8.py:15(decode)&lt;br /&gt;
   502000    0.730    0.000    0.730    0.000 {_codecs.utf_8_decode}&lt;br /&gt;
   500000    0.514    0.000    0.514    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:413(_INT_to_python)&lt;br /&gt;
   509500    0.510    0.000    0.510    0.000 {_struct.unpack_from}&lt;br /&gt;
     1500    0.009    0.000    0.492    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:452(execute)&lt;br /&gt;
     1500    0.004    0.000    0.437    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:705(cmd_query)&lt;br /&gt;
   500000    0.367    0.000    0.367    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:405(_FLOAT_to_python)&lt;br /&gt;
   504500    0.343    0.000    0.343    0.000 {method 'extend' of 'bytearray' objects}&lt;br /&gt;
     1500    0.007    0.000    0.315    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:481(_send_cmd)&lt;br /&gt;
  2501500    0.275    0.000    0.275    0.000 {method 'append' of 'list' objects}&lt;br /&gt;
   500500    0.246    0.000    0.246    0.000 {method 'startswith' of 'bytearray' objects}&lt;br /&gt;
   524002    0.150    0.000    0.150    0.000 {isinstance}&lt;br /&gt;
     1500    0.011    0.000    0.118    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:613(_handle_result)&lt;br /&gt;
  1004500    0.112    0.000    0.112    0.000 {len}&lt;br /&gt;
   500000    0.092    0.000    0.092    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:204(description)&lt;br /&gt;
     1500    0.007    0.000    0.042    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/network.py:118(send_plain)&lt;br /&gt;
     2000    0.013    0.000    0.041    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:226(parse_column)&lt;br /&gt;
     1000    0.008    0.000    0.034    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:346(_process_params_dict)&lt;br /&gt;
     1500    0.002    0.000    0.029    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/socket.py:223(meth)&lt;br /&gt;
     1500    0.026    0.000    0.026    0.000 {method 'sendall' of '_socket.socket' objects}&lt;br /&gt;
     5000    0.007    0.000    0.014    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/catch23.py:79(struct_unpack)&lt;br /&gt;
     1000    0.001    0.000    0.013    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:562(_handle_ok)&lt;br /&gt;
    12000    0.011    0.000    0.011    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:167(read_lc_string)&lt;br /&gt;
     1000    0.004    0.000    0.010    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:199(parse_ok)&lt;br /&gt;
     3000    0.005    0.000    0.009    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:152(to_mysql)&lt;br /&gt;
     1500    0.003    0.000    0.007    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:118(make_command)&lt;br /&gt;
     3000    0.003    0.000    0.007    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:130(quote)&lt;br /&gt;
     1500    0.005    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/network.py:53(_prepare_packets)&lt;br /&gt;
     1500    0.003    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:406(_handle_result)&lt;br /&gt;
     3000    0.003    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:102(escape)&lt;br /&gt;
     1000    0.002    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:252(parse_eof)&lt;br /&gt;
      500    0.001    0.000    0.005    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:579(_handle_eof)&lt;br /&gt;
     2000    0.003    0.000    0.005    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:545(_handle_server_status)&lt;br /&gt;
     1500    0.003    0.000    0.004    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:53(int1store)&lt;br /&gt;
     3000    0.003    0.000    0.003    0.000 {method 'encode' of 'str' objects}&lt;br /&gt;
     9001    0.003    0.000    0.003    0.000 {method 'replace' of 'str' objects}&lt;br /&gt;
     1500    0.003    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:295(_reset_result)&lt;br /&gt;
     4500    0.003    0.000    0.003    0.000 {_struct.pack}&lt;br /&gt;
     2500    0.003    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:296(read_lc_int)&lt;br /&gt;
      500    0.002    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:757(_handle_eof)&lt;br /&gt;
     6027    0.003    0.000    0.003    0.000 {method 'format' of 'str' objects}&lt;br /&gt;
     3000    0.002    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:1221(_set_unread_result)&lt;br /&gt;
      500    0.001    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:219(parse_column_count)&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 {repr}&lt;br /&gt;
     4527    0.002    0.000    0.002    0.000 {getattr}&lt;br /&gt;
     4000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/constants.py:34(flag_is_set)&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:383(_handle_noresultset)&lt;br /&gt;
     4000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:1233(_get_unread_result)&lt;br /&gt;
     2001    0.001    0.000    0.001    0.000 {method 'items' of 'dict' objects}&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:307(_have_unread_result)&lt;br /&gt;
     3000    0.001    0.000    0.001    0.000 {method 'lower' of 'str' objects}&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 {range}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:173(_str_to_mysql)&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:169(_float_to_mysql)&lt;br /&gt;
     1000    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:161(_int_to_mysql)&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:1313(_get_getwarnings)&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:200(reset)&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:397(_handle_resultset)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/__init__.py:71(search_function)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/ascii.py:41(getregentry)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/contextlib.py:21(__exit__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/__init__.py:49(normalize_encoding)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 test.py:8(profiled)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/codecs.py:77(__new__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {__import__}&lt;br /&gt;
        3    0.000    0.000    0.000    0.000 {method 'get' of 'dict' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'split' of 'str' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {built-in method __new__ of type object at 0x1001534e8}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'translate' of 'str' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'join' of 'str' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {hasattr}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DBAPI:  &amp;lt;module 'MySQLdb' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/__init__.pyc'&amp;gt;&lt;br /&gt;
         47503 function calls in 0.918 seconds&lt;br /&gt;
&lt;br /&gt;
   Ordered by: cumulative time&lt;br /&gt;
&lt;br /&gt;
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;
     1500    0.009    0.000    0.917    0.001 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:164(execute)&lt;br /&gt;
     1500    0.002    0.000    0.890    0.001 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:353(_query)&lt;br /&gt;
     1500    0.004    0.000    0.498    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:315(_do_query)&lt;br /&gt;
     1500    0.016    0.000    0.391    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:358(_post_get_result)&lt;br /&gt;
     1500    0.001    0.000    0.375    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:324(_fetch_row)&lt;br /&gt;
      500    0.374    0.001    0.374    0.001 {built-in method fetch_row}&lt;br /&gt;
     1500    0.007    0.000    0.362    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:142(_do_get_result)&lt;br /&gt;
     1500    0.002    0.000    0.351    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:351(_get_result)&lt;br /&gt;
     1500    0.349    0.000    0.349    0.000 {method 'store_result' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.131    0.000    0.131    0.000 {method 'query' of '_mysql.connection' objects}&lt;br /&gt;
     4000    0.002    0.000    0.010    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:184(&amp;lt;genexpr&amp;gt;)&lt;br /&gt;
     3000    0.002    0.000    0.008    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/connections.py:267(literal)&lt;br /&gt;
     1500    0.007    0.000    0.007    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:107(_warning_check)&lt;br /&gt;
     3000    0.002    0.000    0.006    0.000 {method 'escape' of '_mysql.connection' objects}&lt;br /&gt;
     6000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:159(_get_db)&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/converters.py:81(Float2Str)&lt;br /&gt;
     2500    0.001    0.000    0.001    0.000 {isinstance}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/connections.py:202(string_literal)&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:380(fetchall)&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 {built-in method describe}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 {method 'string_literal' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {method 'insert_id' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {method 'affected_rows' of '_mysql.connection' objects}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/converters.py:69(Thing2Str)&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 {method 'warning_count' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 {method 'info' of '_mysql.connection' objects}&lt;br /&gt;
     1000    0.000    0.000    0.000    0.000 {method 'iteritems' of 'dict' objects}&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:103(_check_executed)&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 {len}&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 {built-in method field_flags}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/contextlib.py:21(__exit__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 test.py:8(profiled)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DBAPI:  &amp;lt;module 'pymysql' from '/Users/classic/dev/PyMySQL/pymysql/__init__.pyc'&amp;gt;, total seconds 10.046012&lt;br /&gt;
DBAPI:  &amp;lt;module 'mysql.connector' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/__init__.pyc'&amp;gt;, total seconds 15.403260&lt;br /&gt;
DBAPI:  &amp;lt;module 'MySQLdb' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/__init__.pyc'&amp;gt;, total seconds 1.028737&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mike Bayer</name></author>	</entry>

	<entry>
		<id>https://wiki.openstack.org/w/index.php?title=PyMySQL_evaluation&amp;diff=72552</id>
		<title>PyMySQL evaluation</title>
		<link rel="alternate" type="text/html" href="https://wiki.openstack.org/w/index.php?title=PyMySQL_evaluation&amp;diff=72552"/>
				<updated>2015-01-28T20:19:05Z</updated>
		
		<summary type="html">&lt;p&gt;Mike Bayer: /* Architecture and Performance */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= PyMySQL Evaluation =&lt;br /&gt;
&lt;br /&gt;
This page will capture issues related to Openstack moving to the [https://github.com/PyMySQL/PyMySQL/ PyMySQL driver] for MySQL/MariaDB dbapi access.&lt;br /&gt;
&lt;br /&gt;
== Rationale ==&lt;br /&gt;
&lt;br /&gt;
While the [https://github.com/farcepest/MySQLdb1 MySQL-Python] driver is a very mature and stable driver, it does not provide compatibility with either Python 3 or with [http://eventlet.net/doc/patching.html eventlet monkeypatching] (MySQL-Python  [https://github.com/eventlet/eventlet/blob/v0.16.1/eventlet/patcher.py#L225-L237 can be monkeypatched] with eventlet, but this feature disabled by default and missed in documentation). So OpenStack's usage of MySQL-Python, combined with the fact that concurrency is provided by eventlet, means that we currently have fully serialized database access within a single process, that is, only one database command occurs at a time within an Openstack Python process.&lt;br /&gt;
&lt;br /&gt;
== Drivers Under Consideration ==&lt;br /&gt;
&lt;br /&gt;
The two drivers that are known to provide eventlet-monkeypatch compatibility are MySQL-Connector and PyMySQL, as they are written in pure Python.   The two other well-known drivers for MySQL are MySQL-Python and OurSQL, both of which are written in C and offer no explicit support for eventlet or async.  They can reportedly be built to support gevent using a system called [https://github.com/CMGS/greenify Greenify], but the maturity and/or stabiltiy of this system is unknown.&lt;br /&gt;
&lt;br /&gt;
A comparison at http://www.diamondtin.com/2014/sqlalchemy-gevent-mysql-python-drivers-comparison/ illustrates performance metrics observed with all four of these drivers, with MySQL-python built both without and with the &amp;quot;greenify&amp;quot; system.&lt;br /&gt;
&lt;br /&gt;
A summary of the status of all four drivers is as follows.&lt;br /&gt;
&lt;br /&gt;
=== MySQL-Python ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/farcepest/MySQLdb1 MySQL-Python] is the most widely used Python driver.  However, it does not support Python 3, and it does not support async systems unless used within a thread pool.   While a system known as Greenify can potentially resolve the latter situation, the Python 3 limitation is still a deal-breaker - pull request with Python 3 support [https://github.com/farcepest/MySQLdb1/pull/62 was proposed] to MySQL-python in April 2014 and still not merged.&lt;br /&gt;
&lt;br /&gt;
=== mysqlclient ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/PyMySQL/mysqlclient-python mysqlclient] is a fork of MySQL-python. It add Python 3.3 support and merges some pull requests.&lt;br /&gt;
&lt;br /&gt;
=== OurSQL ===&lt;br /&gt;
&lt;br /&gt;
[https://pythonhosted.org/oursql/ OurSQL] is an alternative MySQL driver that is also written in C.  It features fast performance and uses a different execution model than MySQL-python, using prepared statements.    OurSQL does have a Python 3 port hosted on [https://launchpad.net/oursql/py3k/ launchpad] but is not integrated with OurSQL itself nor is it published on Pypi.     Because it is written in C, it does not offer compatibility with async systems including eventlet-style monkeypatching.&lt;br /&gt;
&lt;br /&gt;
Besides the shaky Python 3 support and lack of eventlet support, OurSQL is also not maintained, seeing its last master commit on [https://code.launchpad.net/~jerub/oursql/master 2012-06-05].&lt;br /&gt;
&lt;br /&gt;
=== MySQL-Connector-Python ===&lt;br /&gt;
&lt;br /&gt;
[https://pypi.python.org/pypi/mysql-connector-python MySQL-connector-Python] is a pure Python MySQL driver, and is now released under the auspices of the MySQL project itself, as owned by Oracle.  MySQL-connector-Python supports both Python 3 as well as eventlet monkeypatching, and is well maintained.  It is endorsed by Oracle as the official Oracle-supported driver for MySQL, so to that extent, it is in most ways Openstack's first choice in driver.  However, Oracle refuses to publish MySQL-connector-Python on Pypi, which is critical to the Openstack infrastructure.  Repeated attempts to communicate with Oracle in order to resolve this issue have not made any progress.    Therefore, for this one unfortunate reason, MySQL-connector-Python will not have a place in the Openstack ecosystem unless this issue is resolved.&lt;br /&gt;
&lt;br /&gt;
=== PyMySQL ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/PyMySQL/PyMySQL PyMySQL] is a pure Python MySQL&lt;br /&gt;
driver, first written as a rough port of the MySQL-Python driver.&lt;br /&gt;
PyMySQL meets all of Openstack's criterion for a driver: it is fully&lt;br /&gt;
open source, hosted on Github, released on Pypi, is actively&lt;br /&gt;
maintained, is written in pure Python so is eventlet-monkeypatch&lt;br /&gt;
compatible, and is fully Python 3 compatible.&lt;br /&gt;
&lt;br /&gt;
As this document is named &amp;quot;PyMySQL Evaluation&amp;quot;, it should be apparent that&lt;br /&gt;
this is the driver Openstack is currently leaning towards.  However, it does&lt;br /&gt;
have some minor code quality issues which hopefully can be addressed in some&lt;br /&gt;
way; the section below titled &amp;quot;PyMySQL Code Review&amp;quot; will summarize the&lt;br /&gt;
current state of the code.  We are in fact leaning towards this driver because&lt;br /&gt;
its the only one that actually meets all criteria.  PyMySQL appears to be&lt;br /&gt;
fairly widely used, especially considering its low profile, so it's possible&lt;br /&gt;
that this is the case for many other users as well.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===MySQL DB Drivers Comparison===&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Project !! PyPi hosted !! Eventlet friendly !! Python 3 compatibility !! Maturity and/or stability  !! Comment&lt;br /&gt;
|-&lt;br /&gt;
| MySQL-Python || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: orange;&amp;quot; | Partial || style=&amp;quot;background-color: red;&amp;quot; | No  || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || Can be monkeypatched by eventlet, but only to enable thread pooling&lt;br /&gt;
|-&lt;br /&gt;
| mysqlclient || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: orange;&amp;quot; | Partial || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes  || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || MySQL-python fork, so should be similar to it&lt;br /&gt;
|-&lt;br /&gt;
| OurSQL || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: red;&amp;quot; | No || style=&amp;quot;background-color: orange;&amp;quot; | Yes, but not Pypi hosted  || style=&amp;quot;background-color: orange;&amp;quot; | ??? || Has not seen commits/releases in two years&lt;br /&gt;
|-&lt;br /&gt;
| MySQL-Connector-Python || style=&amp;quot;background-color: red;&amp;quot; | No || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes  || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || The official Oracle-supported driver for MySQL&lt;br /&gt;
|-&lt;br /&gt;
| PyMySQL || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes  || style=&amp;quot;background-color: orange;&amp;quot; | Yes, with a [http://lists.openstack.org/pipermail/openstack-dev/2014-December/052864.html proviso]. ||&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== PyMySQL Code Review ==&lt;br /&gt;
&lt;br /&gt;
PyMySQL started roughly as a pure Python port of MySQL-Python.   The sections below will&lt;br /&gt;
detail various aspects of the driver and the status of the code.   Overall, the general&lt;br /&gt;
tone of PyMySQL is one of code that was written in the spirit of pragmatism and immediate&lt;br /&gt;
need; it is straightforward, relatively free of serious antipatterns and performs&lt;br /&gt;
its task in a matter-of-fact way.  However,&lt;br /&gt;
it lacks polish and completeness in many areas that would normally be in better&lt;br /&gt;
condition for a more mature project.  These areas are all highly fixable, and it is&lt;br /&gt;
hoped that these items can be shared with the current PyMySQL developers, through&lt;br /&gt;
a combination of this document itself as well as new issues and pull requests&lt;br /&gt;
on the PyMySQL tracker, so that this fairly promising library can be pushed&lt;br /&gt;
to the next level.   This would require that the developers are amenable to&lt;br /&gt;
these improvements.&lt;br /&gt;
&lt;br /&gt;
=== Coding Style / Pep8 ===&lt;br /&gt;
&lt;br /&gt;
PyMySQL's coding style is pretty good.  The majority of it passes all&lt;br /&gt;
flake8 tests, save for a few dozen whitespace issues and&lt;br /&gt;
some long lines here and there within the core&lt;br /&gt;
modules, and moreso within the tests which appear to be a bit more&lt;br /&gt;
crufty than the core modules.  A full run of flake8 produces only 128&lt;br /&gt;
errors, and with a default run of autopep8 against E1,E2,E3, we can&lt;br /&gt;
fix all of them in one pass except for 38 remaining long line warnings.&lt;br /&gt;
The code layout is mostly idiomatic and reasonable.&lt;br /&gt;
&lt;br /&gt;
The codebase is probably short on docstrings.  Those methods that are&lt;br /&gt;
part of the public DBAPI do have docstrings, but tend to be extremely&lt;br /&gt;
terse (example, docstring for cursor.execute(): &amp;quot;'Execute a query'&amp;quot;).&lt;br /&gt;
Other methods that aren't public tend to not have any docstrings, but&lt;br /&gt;
the codebase is not at all hard to read and the purpose of methods and&lt;br /&gt;
functions is pretty easy to discern just by their names and implementations,&lt;br /&gt;
however those who seek inline documentation as a means to understand a codebase&lt;br /&gt;
will be disappointed.   The terseness of public API docstrings is more of&lt;br /&gt;
an issue in terms of the overall lack of documentation for PyMySQL,&lt;br /&gt;
see the section &amp;quot;Documentation&amp;quot; below.&lt;br /&gt;
&lt;br /&gt;
=== Test Coverage ===&lt;br /&gt;
&lt;br /&gt;
The test coverage for PyMySQL is definitely lacking.   From a visual&lt;br /&gt;
inspection alone, it's apparent that of the dozen or so test modules,&lt;br /&gt;
most of them have less then ten tests each and the modules are very short.&lt;br /&gt;
For a full test run, there's only 126 tests; contrast this to&lt;br /&gt;
MySQL-Connector-Python which has 531 tests.&lt;br /&gt;
&lt;br /&gt;
Additionally, all of the tests are live round-trip tests against a&lt;br /&gt;
real database.  There are no non-live unit tests of any kind,&lt;br /&gt;
leaving codepaths that are not easily exercised&lt;br /&gt;
on a generic MySQL database out in the cold.  This means that features designed&lt;br /&gt;
for particular versions of MySQL, particular datatypes, particular error&lt;br /&gt;
conditions, and particular kinds of message packets that aren't created for&lt;br /&gt;
real are simply not tested.&lt;br /&gt;
&lt;br /&gt;
The lack of tests is apparent when run with coverage.  The test suite itself&lt;br /&gt;
does not appear to have any signs that it is normally run with coverage&lt;br /&gt;
turned on, with no directives in tox.ini or similar.   With coverage,&lt;br /&gt;
the overall coverage is 85%, but the modules most lacking in coverage&lt;br /&gt;
are also the most critical to PyMySQL's core functionality,&lt;br /&gt;
connections.py, converters.py and cursors.py:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Name                                                              Stmts   Miss  Cover&lt;br /&gt;
-------------------------------------------------------------------------------------&lt;br /&gt;
pymysql/__init__                                                     51      3    94%&lt;br /&gt;
pymysql/_compat                                                      14      4    71%&lt;br /&gt;
pymysql/_socketio                                                    72     30    58%&lt;br /&gt;
pymysql/charset                                                     228      3    99%&lt;br /&gt;
pymysql/connections                                                 827    188    77%&lt;br /&gt;
pymysql/constants/CLIENT                                             18      0   100%&lt;br /&gt;
pymysql/constants/COMMAND                                            32      0   100%&lt;br /&gt;
pymysql/constants/ER                                                471      0   100%&lt;br /&gt;
pymysql/constants/FIELD_TYPE                                         29      0   100%&lt;br /&gt;
pymysql/constants/FLAG                                               15      0   100%&lt;br /&gt;
pymysql/constants/SERVER_STATUS                                      10      0   100%&lt;br /&gt;
pymysql/constants/__init__                                            0      0   100%&lt;br /&gt;
pymysql/converters                                                  148     51    66%&lt;br /&gt;
pymysql/cursors                                                     273     37    86%&lt;br /&gt;
pymysql/err                                                          40      1    98%&lt;br /&gt;
pymysql/tests/__init__                                               14      5    64%&lt;br /&gt;
pymysql/tests/base                                                   25      2    92%&lt;br /&gt;
pymysql/tests/test_DictCursor                                        73      2    97%&lt;br /&gt;
pymysql/tests/test_SSCursor                                          56      6    89%&lt;br /&gt;
pymysql/tests/test_basic                                            186      4    98%&lt;br /&gt;
pymysql/tests/test_connection                                        57      8    86%&lt;br /&gt;
pymysql/tests/test_example                                           17      2    88%&lt;br /&gt;
pymysql/tests/test_issues                                           300     48    84%&lt;br /&gt;
pymysql/tests/test_load_local                                        39      2    95%&lt;br /&gt;
pymysql/tests/test_nextset                                           43      4    91%&lt;br /&gt;
pymysql/tests/thirdparty/__init__                                     7      5    29%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/__init__                        6      2    67%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/capabilities                  196     23    88%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/dbapi20                       423    121    71%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_capabilities      73      6    92%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_dbapi20          100      2    98%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_nonstandard       57      3    95%&lt;br /&gt;
pymysql/times                                                        12      0   100%&lt;br /&gt;
pymysql/util                                                         14      7    50%&lt;br /&gt;
-------------------------------------------------------------------------------------&lt;br /&gt;
TOTAL                                                              3926    569    86%&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The lack of coverage in converters.py refers to functions related to handling&lt;br /&gt;
incoming bound values as well as outgoing result values.    Key data escaping&lt;br /&gt;
features such as escaping of dictionaries, sets, some unicode objects and&lt;br /&gt;
byte objects as well as many pathways into date and time-processing functions are not&lt;br /&gt;
covered.&lt;br /&gt;
&lt;br /&gt;
In connections.py, uncovered features include various SSL and&lt;br /&gt;
very legacy (e.g. version 3.23) MySQL features, but also some protocol parsing&lt;br /&gt;
features.&lt;br /&gt;
&lt;br /&gt;
In cursors.py, there is coverage for an elaborate and mostly undocumented&lt;br /&gt;
performance-related feature that rewrites an INSERT statement to have an&lt;br /&gt;
extended VALUES clause (_do_execute_many), however it fails to cover the very&lt;br /&gt;
likely case where the function will need to break the input set into&lt;br /&gt;
multiple chunks:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;gt;         for arg in args:&lt;br /&gt;
&amp;gt;             v = values % escape(arg, conn)&lt;br /&gt;
&amp;gt;             if isinstance(v, text_type):&lt;br /&gt;
&amp;gt;                 v = v.encode(encoding)&lt;br /&gt;
&amp;gt;             if len(sql) + len(v) + 1 &amp;gt; max_stmt_length:&lt;br /&gt;
!                 rows += self.execute(sql)&lt;br /&gt;
!                 sql = bytearray(prefix)&lt;br /&gt;
&amp;gt;             else:&lt;br /&gt;
&amp;gt;                 sql += b','&lt;br /&gt;
&amp;gt;             sql += v&lt;br /&gt;
&amp;gt;         rows += self.execute(sql)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The most prominent missing coverage in cursors.py seems to be&lt;br /&gt;
for its &amp;quot;scrollable cursor&amp;quot; support.&lt;br /&gt;
&lt;br /&gt;
Beyond the lack of completeness in testing, many of the tests themselves&lt;br /&gt;
are written in an expedient &amp;quot;one giant test&amp;quot; style where dozens of&lt;br /&gt;
assertions and individual behaviors are lumped into one big test&lt;br /&gt;
case (see test_SSCursor.py, test_basic.py-&amp;gt;test_datatypes for examples).&lt;br /&gt;
Test cases like these are difficult to work with when debugging&lt;br /&gt;
regressions as well as when tests for new features need to be added, and ideally&lt;br /&gt;
these should be broken out into clean single-feature tests with consistent&lt;br /&gt;
fixtures.&lt;br /&gt;
&lt;br /&gt;
There is also a suite called &amp;quot;test_issues&amp;quot;, which intends to&lt;br /&gt;
accumulate tests against specific issues that have been reported. This&lt;br /&gt;
isn't a bad idea, though it would be nice to see an effort made into&lt;br /&gt;
categorizing these tests into the actual features they are testing,&lt;br /&gt;
rather than a meaningless list of issue numbers, which could then form&lt;br /&gt;
as the basis for new suites centered around those areas of&lt;br /&gt;
functionality.  The issues reported should be used as the inspiration&lt;br /&gt;
for improvement of the test suite, rather than just another line-item&lt;br /&gt;
to be filed away.&lt;br /&gt;
&lt;br /&gt;
=== Library Documentation ===&lt;br /&gt;
&lt;br /&gt;
As the Python DBAPI is already a well-documented API, and the users of&lt;br /&gt;
PyMySQL are typically coming from MySQL-Python which is documented&lt;br /&gt;
to some degree and for which the use contract is widely known, it seems&lt;br /&gt;
unlikely that users of PyMySQL are eager for comprehensive documentation&lt;br /&gt;
specific to this library.&lt;br /&gt;
&lt;br /&gt;
That being said, PyMySQL does not appear to have any real library&lt;br /&gt;
documentation at all.  There's a README which&lt;br /&gt;
essentially refers to pep-249, an example.py in the root which shows a&lt;br /&gt;
very basic connection / round trip, and that's it. In&lt;br /&gt;
order to know anything about the API, specific parameters, optimization&lt;br /&gt;
strategies, behaviors, etc., one either has to go off of the MySQL-Python documentation and assume&lt;br /&gt;
PyMySQL also features the same parameter, or read the source code.&lt;br /&gt;
&lt;br /&gt;
PyMySQL probably hasn't had much urgent need for real documentation thus&lt;br /&gt;
far.  However, relying upon being a port of MySQL-Python is fast becoming&lt;br /&gt;
something that can no longer be relied upon, as MySQL-Python's development&lt;br /&gt;
is quite stalled, and PyMySQL should aspire to move into the future&lt;br /&gt;
of MySQL and Python with its own featureset and behaviors.  To that end, it would be a &lt;br /&gt;
great idea if it at least made the start&lt;br /&gt;
of a rudimentary Sphinx build including autodoc for module documentation&lt;br /&gt;
which could then be published up to RTD.   This would be very easy to get&lt;br /&gt;
started, and once present, new documentation sections can be added&lt;br /&gt;
iteratively.&lt;br /&gt;
&lt;br /&gt;
=== Architecture and Performance ===&lt;br /&gt;
&lt;br /&gt;
As stated before, PyMySQL has a really matter-of-fact and straightforward&lt;br /&gt;
implementation.   The code is very readable and it's easy to discern even&lt;br /&gt;
unusual features such as the INSERT..VALUES rewriting.  As far as the design,&lt;br /&gt;
there are lots of areas where performance suffers a bit at the Python level;&lt;br /&gt;
these areas could be greatly improved very easily with a little bit of&lt;br /&gt;
attention.&lt;br /&gt;
&lt;br /&gt;
As we are dealing with a driver that is communicating with a low level&lt;br /&gt;
protocol over a socket, it's critical that sending and receiving messages&lt;br /&gt;
is done as efficiently as possible.  The mechanisms used in PyMySQL rely&lt;br /&gt;
heavily on standard Python objects, without the use of __slots__; this means&lt;br /&gt;
that for individual messages on a socket, we are creating a heavyweight object,&lt;br /&gt;
calling its __init__() method and a creating a new __dict__ each time;&lt;br /&gt;
the most common object, OKPacketWrapper(), already wraps a MysqlPacket()&lt;br /&gt;
object, so we're doing two __init__()s in this common case.  OKPacketWrapper&lt;br /&gt;
also implements a `__getattr__` scheme&lt;br /&gt;
to proxy attribute access from itself to the internal MysqlPacket; this is a&lt;br /&gt;
an inefficient mode of operation in Python as it means a lookup must first&lt;br /&gt;
fail on the OKPacketWrapper before it invokes the `__getattr__()` method&lt;br /&gt;
and does another attribute lookup.   A reorg of the packet classes to produce&lt;br /&gt;
far fewer objects for messages as well as to use __slots__ would be&lt;br /&gt;
recommended.&lt;br /&gt;
&lt;br /&gt;
There are also lots of areas where function call overhead is unnecessarily&lt;br /&gt;
high due to the organization of calls.  For example, when calling upon&lt;br /&gt;
cursor.execute(), for parameters passed as a dictionary under Python 2K,&lt;br /&gt;
the isinstance() builtin function will be called on the incoming arguments&lt;br /&gt;
four times at a minimum, with two of each call against the exact same&lt;br /&gt;
criteria.  Additional logic will then call isinstance() on every key of&lt;br /&gt;
the dictionary as well as at least twice on every value within the dictionary,&lt;br /&gt;
in order to determine if the value needs to be converted to bytes as well&lt;br /&gt;
as for some top-level escaping logic checks; the escaping logic then&lt;br /&gt;
goes further with more checks for type() to match the object to a final&lt;br /&gt;
converter function.   While the flow of this logic does appear to be&lt;br /&gt;
reasonable and straightforward, it isn't ideal for a low-level database&lt;br /&gt;
driver library where speed is a high priority; the various wrappers and&lt;br /&gt;
processors can easily be reorganized here to reduce the number of isinstance()&lt;br /&gt;
and type() calls significantly, which would save a lot on function call&lt;br /&gt;
overhead.  It is likely there are many other areas in the codebase where&lt;br /&gt;
similar reductions in call counts can be made without too much disruption.&lt;br /&gt;
&lt;br /&gt;
In Appendix A, a short performance suite is illustrated.  This&lt;br /&gt;
is run against PyMySQL, Mysql-Connector-Python, and MySQL-Python.&lt;br /&gt;
PyMySQL does in fact demonstrate the&lt;br /&gt;
highest call-count behavior, logging 26,851,003 function calls&lt;br /&gt;
compared to MySQL-connector's 11,674,074 for the same operations.  To PyMySQL's&lt;br /&gt;
credit, it somehow completes the test 25-50% faster than MySQL-connector&lt;br /&gt;
despite having more than twice as many function calls; this probably indicates&lt;br /&gt;
the use of something very slow within MySQL-connector, such as catching&lt;br /&gt;
an exception within a tight loop.  However, the real bar we're comparing to&lt;br /&gt;
is MySQL-Python, which is written&lt;br /&gt;
in C and does the whole test with only 47,503 Python function calls; while&lt;br /&gt;
PyMySQL and MySQL-connector-Python compete for time within the 10-15 second&lt;br /&gt;
range without profiling, MySQL-Python completes the whole test in **1.03 seconds**, a 1000%&lt;br /&gt;
improvement over PyMySQL.   This speed difference is not PyMySQL's fault,&lt;br /&gt;
as pure Python is known to be extremely slow compared to C code.&lt;br /&gt;
However, there's a lot of easy wins in PyMySQL's codebase where the function&lt;br /&gt;
call count could probably be reduced dramatically to be even better than that&lt;br /&gt;
of MySQL-connector-Python; ideally, the driver would be able to run this&lt;br /&gt;
test possibly 5-6x slower than MySQL-Python rather than 10x.&lt;br /&gt;
&lt;br /&gt;
=== History Documentation ===&lt;br /&gt;
&lt;br /&gt;
PyMySQL includes a CHANGELOG file in its root which describes the bug fixes&lt;br /&gt;
and features of each release.  The construction of this file is definitely&lt;br /&gt;
within the &amp;quot;expedient&amp;quot; style noted for PyMySQL overall, and for it to be&lt;br /&gt;
genuinely useful for enterprise-level software, it would need to be&lt;br /&gt;
much more complete in its detail.    Each bug fix mentioned is only referred to&lt;br /&gt;
tersely without any link to a pull request or bug report.  While some can&lt;br /&gt;
be linked to a specific change through detective work&lt;br /&gt;
(&amp;quot;Cursor.fetchall() and .fetchmany now return list, not tuple&amp;quot;), it would&lt;br /&gt;
be very difficult for many&lt;br /&gt;
(&amp;quot;Fixed BIT type handling&amp;quot;, &amp;quot;Fixed GC errors&amp;quot; - what was the issue?)&lt;br /&gt;
and in many cases impossible (&amp;quot;Improved Py3k support&amp;quot;).  The file also&lt;br /&gt;
includes no dates so one has to look at the git tags to figure this&lt;br /&gt;
part out (fortunately, releases to seem to be git tagged at least).&lt;br /&gt;
For a user trying to ascertain if particular issues are&lt;br /&gt;
fixed or if behaviors have changed, this file is at best highly frustrating.&lt;br /&gt;
&lt;br /&gt;
A listing of release dates illustrates an average of two releases per year,&lt;br /&gt;
excluding 2012 when PyMySQL was seeking a new maintainer:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
2014-12-02 12:05:10 -0200  (tag: pymysql-0.6.3)&lt;br /&gt;
2014-04-21 14:28:47 -0300  (tag: pymysql-0.6.2)&lt;br /&gt;
2013-10-11 13:36:56 -0300  (tag: pymysql-0.6.1)&lt;br /&gt;
2013-10-04 14:25:14 -0300  (tag: pymysql-0.6)&lt;br /&gt;
2011-11-08 10:27:41 -0800  (tag: pymysql-0.5)&lt;br /&gt;
2010-12-27 17:28:16 +0000  (tag: pymysql-0.4)&lt;br /&gt;
2010-09-03 00:55:29 +0000  (tag: pymysql-0.3)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Community Involvement ===&lt;br /&gt;
&lt;br /&gt;
This is an area where PyMySQL seems to shine, and might even be an advantage&lt;br /&gt;
as compared to MySQL-Connector-Python, which remains under the cloak&lt;br /&gt;
of MySQL's vastly overstuffed bugtracker under the ownership of&lt;br /&gt;
the famously-opaque Oracle.   For a small, low key library without&lt;br /&gt;
any real documentation and only 126 tests, it features 33 contributors on&lt;br /&gt;
Github and is up to pull request #285, which is quite high.  Because&lt;br /&gt;
PyMySQL is small and straightforward, it does lend itself to contribution&lt;br /&gt;
and seems to do very well in this area.   The developers so far have been&lt;br /&gt;
responsive to issues and pull requests made by the author.&lt;br /&gt;
&lt;br /&gt;
Based on github contributor graphs it appears that PyMySQL had a lull&lt;br /&gt;
in 2012 but was picked up again afterwards in 2013.  As of 2012, its most&lt;br /&gt;
prominent developer Pete Hunt stopped development, and on July 25, 2013&lt;br /&gt;
added a note to the README that the project was looking for a new maintainer.&lt;br /&gt;
As of 2013 it was picked up by the current most prominent developers&lt;br /&gt;
Marcel Rodrigues (github username lecram) and INADA Naoki&lt;br /&gt;
(github username methane).&lt;br /&gt;
&lt;br /&gt;
Most issues that have been noted thus far within the areas of testing,&lt;br /&gt;
documentation and architecture are all very fixable; if the Openstack community&lt;br /&gt;
can provide resources to address some of these areas, and if the PyMySQL&lt;br /&gt;
developers are reasonably open to accepting new changes and producing new&lt;br /&gt;
releases in a timely fashion, the PyMySQL driver&lt;br /&gt;
could rapidly become a fully formidable contender in the MySQL driver field.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Appendix A - Performance Tests ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
import cProfile&lt;br /&gt;
import StringIO&lt;br /&gt;
import pstats&lt;br /&gt;
import contextlib&lt;br /&gt;
import time&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextlib.contextmanager&lt;br /&gt;
def profiled(dbapi):&lt;br /&gt;
    pr = cProfile.Profile()&lt;br /&gt;
    pr.enable()&lt;br /&gt;
    yield&lt;br /&gt;
    pr.disable()&lt;br /&gt;
    s = StringIO.StringIO()&lt;br /&gt;
    ps = pstats.Stats(pr, stream=s).sort_stats('cumulative')&lt;br /&gt;
    ps.print_stats()&lt;br /&gt;
    print &amp;quot;DBAPI:  %s&amp;quot; % dbapi&lt;br /&gt;
    print s.getvalue()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextlib.contextmanager&lt;br /&gt;
def timeonly(dbapi):&lt;br /&gt;
    now = time.time()&lt;br /&gt;
    try:&lt;br /&gt;
        yield&lt;br /&gt;
    finally:&lt;br /&gt;
        total = time.time() - now&lt;br /&gt;
        print &amp;quot;DBAPI:  %s, total seconds %f&amp;quot; % (dbapi, total)&lt;br /&gt;
&lt;br /&gt;
import MySQLdb&lt;br /&gt;
import pymysql&lt;br /&gt;
from mysql import connector as mysqlconnector&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def go(dbapi, ctx):&lt;br /&gt;
    conn = dbapi.connect(&lt;br /&gt;
        user='scott', passwd='tiger', host='localhost', db='test')&lt;br /&gt;
    cursor = conn.cursor()&lt;br /&gt;
    cursor.execute(&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
CREATE TABLE IF NOT EXISTS test_things (&lt;br /&gt;
    x INTEGER,&lt;br /&gt;
    y VARCHAR(255),&lt;br /&gt;
    z FLOAT&lt;br /&gt;
) engine=InnoDB&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;)&lt;br /&gt;
    cursor.execute(&amp;quot;DELETE from test_things&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    with ctx(dbapi):&lt;br /&gt;
        for row in xrange(1000):&lt;br /&gt;
            cursor.execute(&lt;br /&gt;
                &amp;quot;INSERT INTO test_things (x, y, z) &amp;quot;&lt;br /&gt;
                &amp;quot;VALUES (%(x)s, %(y)s, %(z)s)&amp;quot;,&lt;br /&gt;
                {&amp;quot;x&amp;quot;: row, &amp;quot;y&amp;quot;: &amp;quot;row number %d&amp;quot; % row,&lt;br /&gt;
                 &amp;quot;z&amp;quot;: row * 4.57292, }&lt;br /&gt;
            )&lt;br /&gt;
&lt;br /&gt;
        for x in xrange(500):&lt;br /&gt;
            cursor.execute(&lt;br /&gt;
                &amp;quot;select * from test_things&amp;quot;)&lt;br /&gt;
            for row in cursor.fetchall():&lt;br /&gt;
                row[0], row[1], row[2]&lt;br /&gt;
&lt;br /&gt;
    cursor.close()&lt;br /&gt;
    conn.close()&lt;br /&gt;
&lt;br /&gt;
go(pymysql, profiled)&lt;br /&gt;
go(mysqlconnector, profiled)&lt;br /&gt;
go(MySQLdb, profiled)&lt;br /&gt;
&lt;br /&gt;
go(pymysql, timeonly)&lt;br /&gt;
go(mysqlconnector, timeonly)&lt;br /&gt;
go(MySQLdb, timeonly)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DBAPI:  &amp;lt;module 'pymysql' from '/Users/classic/dev/PyMySQL/pymysql/__init__.pyc'&amp;gt;&lt;br /&gt;
         26851003 function calls in 15.924 seconds&lt;br /&gt;
&lt;br /&gt;
   Ordered by: cumulative time&lt;br /&gt;
&lt;br /&gt;
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;
     1500    0.010    0.000   15.923    0.011 /Users/classic/dev/PyMySQL/pymysql/cursors.py:105(execute)&lt;br /&gt;
     1500    0.003    0.000   15.883    0.011 /Users/classic/dev/PyMySQL/pymysql/cursors.py:269(_query)&lt;br /&gt;
     1500    0.004    0.000   15.859    0.011 /Users/classic/dev/PyMySQL/pymysql/connections.py:746(query)&lt;br /&gt;
     1500    0.003    0.000   15.819    0.011 /Users/classic/dev/PyMySQL/pymysql/connections.py:892(_read_query_result)&lt;br /&gt;
     1500    0.004    0.000   15.812    0.011 /Users/classic/dev/PyMySQL/pymysql/connections.py:1097(read)&lt;br /&gt;
      500    0.003    0.000   15.559    0.031 /Users/classic/dev/PyMySQL/pymysql/connections.py:1154(_read_result_packet)&lt;br /&gt;
      500    0.900    0.002   15.429    0.031 /Users/classic/dev/PyMySQL/pymysql/connections.py:1187(_read_rowdata_packet)&lt;br /&gt;
   500000    3.417    0.000    9.173    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1200(_read_row_from_packet)&lt;br /&gt;
  2012000    1.157    0.000    5.307    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:311(read_length_coded_string)&lt;br /&gt;
   504500    1.495    0.000    5.209    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:845(_read_packet)&lt;br /&gt;
  2014500    1.248    0.000    3.035    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:293(read_length_encoded_integer)&lt;br /&gt;
  3539500    2.439    0.000    2.750    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:245(read)&lt;br /&gt;
  1009000    0.779    0.000    2.307    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:870(_read_bytes)&lt;br /&gt;
  1009000    1.197    0.000    1.423    0.000 {method 'read' of '_io.BufferedReader' objects}&lt;br /&gt;
   508500    0.379    0.000    0.695    0.000 /Users/classic/dev/PyMySQL/pymysql/util.py:3(byte2int)&lt;br /&gt;
  5058000    0.465    0.000    0.465    0.000 {len}&lt;br /&gt;
   500500    0.222    0.000    0.455    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1146(_check_packet_is_eof)&lt;br /&gt;
   504500    0.205    0.000    0.375    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:342(check_error)&lt;br /&gt;
  2504000    0.275    0.000    0.275    0.000 {method 'append' of 'list' objects}&lt;br /&gt;
   501500    0.182    0.000    0.230    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:326(is_eof_packet)&lt;br /&gt;
     3500    0.009    0.000    0.226    0.000 /Users/classic/dev/PyMySQL/pymysql/_socketio.py:45(readinto)&lt;br /&gt;
     3500    0.207    0.000    0.207    0.000 {method 'recv_into' of '_socket.socket' objects}&lt;br /&gt;
  1022000    0.203    0.000    0.203    0.000 {_struct.unpack}&lt;br /&gt;
   526000    0.200    0.000    0.200    0.000 {isinstance}&lt;br /&gt;
  1501000    0.195    0.000    0.195    0.000 {method 'get' of 'dict' objects}&lt;br /&gt;
   504500    0.176    0.000    0.176    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:238(__init__)&lt;br /&gt;
   504500    0.170    0.000    0.170    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:339(is_error_packet)&lt;br /&gt;
  2014500    0.167    0.000    0.167    0.000 {ord}&lt;br /&gt;
      500    0.006    0.000    0.125    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1224(_get_descriptions)&lt;br /&gt;
     2000    0.003    0.000    0.087    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:361(__init__)&lt;br /&gt;
     2000    0.019    0.000    0.084    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:365(__parse_field_descriptor)&lt;br /&gt;
   500000    0.056    0.000    0.056    0.000 /Users/classic/dev/PyMySQL/pymysql/converters.py:301(through)&lt;br /&gt;
     1500    0.005    0.000    0.037    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:915(_execute_command)&lt;br /&gt;
     1500    0.003    0.000    0.027    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:886(_write_bytes)&lt;br /&gt;
     1500    0.002    0.000    0.024    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/socket.py:223(meth)&lt;br /&gt;
     1500    0.021    0.000    0.021    0.000 {method 'sendall' of '_socket.socket' objects}&lt;br /&gt;
     1500    0.020    0.000    0.020    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:276(_do_get_result)&lt;br /&gt;
     1000    0.003    0.000    0.019    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:95(_escape_args)&lt;br /&gt;
     4000    0.002    0.000    0.015    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:99(&amp;lt;genexpr&amp;gt;)&lt;br /&gt;
     1000    0.002    0.000    0.014    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1127(_read_ok_packet)&lt;br /&gt;
     3000    0.003    0.000    0.013    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:712(escape)&lt;br /&gt;
     1000    0.005    0.000    0.012    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:416(__init__)&lt;br /&gt;
     8000    0.011    0.000    0.011    0.000 {method 'decode' of 'str' objects}&lt;br /&gt;
     3500    0.003    0.000    0.007    0.000 {method '_checkReadable' of '_io._IOBase' objects}&lt;br /&gt;
     2000    0.003    0.000    0.006    0.000 /Users/classic/dev/PyMySQL/pymysql/converters.py:19(escape_item)&lt;br /&gt;
     4000    0.002    0.000    0.006    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:126(&amp;lt;genexpr&amp;gt;)&lt;br /&gt;
     7500    0.003    0.000    0.005    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:115(ensure_bytes)&lt;br /&gt;
     2000    0.003    0.000    0.005    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:386(description)&lt;br /&gt;
     5500    0.004    0.000    0.004    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:268(advance)&lt;br /&gt;
     1000    0.001    0.000    0.004    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:722(escape_string)&lt;br /&gt;
     3500    0.004    0.000    0.004    0.000 /Users/classic/dev/PyMySQL/pymysql/_socketio.py:87(readable)&lt;br /&gt;
      500    0.002    0.000    0.004    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:442(__init__)&lt;br /&gt;
     3500    0.003    0.000    0.003    0.000 {method '_checkClosed' of '_io._IOBase' objects}&lt;br /&gt;
     1000    0.001    0.000    0.003    0.000 /Users/classic/dev/PyMySQL/pymysql/converters.py:59(escape_string)&lt;br /&gt;
     1500    0.003    0.000    0.003    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1080(__init__)&lt;br /&gt;
     1500    0.001    0.000    0.003    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:92(nextset)&lt;br /&gt;
     4000    0.002    0.000    0.002    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:397(get_column_length)&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 /Users/classic/dev/PyMySQL/pymysql/converters.py:56(escape_float)&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 {method 'sub' of '_sre.SRE_Pattern' objects}&lt;br /&gt;
     3000    0.002    0.000    0.002    0.000 {_struct.pack}&lt;br /&gt;
     6000    0.002    0.000    0.002    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:62(_get_db)&lt;br /&gt;
     1500    0.001    0.000    0.002    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:80(_nextset)&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 /Users/classic/dev/PyMySQL/pymysql/util.py:9(int2byte)&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:244(fetchall)&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {min}&lt;br /&gt;
     2500    0.001    0.000    0.001    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:323(is_ok_packet)&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Users/classic/dev/PyMySQL/pymysql/converters.py:52(escape_int)&lt;br /&gt;
     2000    0.001    0.000    0.001    0.000 {method 'items' of 'dict' objects}&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {getattr}&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1093(__del__)&lt;br /&gt;
     1000    0.000    0.000    0.000    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:259(read_all)&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:67(_check_executed)&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:336(is_load_local_packet)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/contextlib.py:21(__exit__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 test.py:8(profiled)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DBAPI:  &amp;lt;module 'mysql.connector' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/__init__.pyc'&amp;gt;&lt;br /&gt;
         11674074 function calls in 19.049 seconds&lt;br /&gt;
&lt;br /&gt;
   Ordered by: cumulative time&lt;br /&gt;
&lt;br /&gt;
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;
      500    0.640    0.001   18.556    0.037 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:820(fetchall)&lt;br /&gt;
      500    0.003    0.000   11.196    0.022 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:655(get_rows)&lt;br /&gt;
      500    1.561    0.003   11.191    0.022 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:292(read_text_result)&lt;br /&gt;
   504500    3.303    0.000    7.306    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/network.py:219(recv_plain)&lt;br /&gt;
   500000    3.572    0.000    6.625    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:359(row_to_python)&lt;br /&gt;
  1009000    3.155    0.000    3.155    0.000 {method 'recv_into' of '_socket.socket' objects}&lt;br /&gt;
   500000    2.049    0.000    2.329    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:220(read_lc_string_list)&lt;br /&gt;
   500000    0.653    0.000    2.116    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:531(_STRING_to_python)&lt;br /&gt;
   502000    0.378    0.000    1.329    0.000 {method 'decode' of 'bytearray' objects}&lt;br /&gt;
   502000    0.221    0.000    0.951    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/utf_8.py:15(decode)&lt;br /&gt;
   502000    0.730    0.000    0.730    0.000 {_codecs.utf_8_decode}&lt;br /&gt;
   500000    0.514    0.000    0.514    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:413(_INT_to_python)&lt;br /&gt;
   509500    0.510    0.000    0.510    0.000 {_struct.unpack_from}&lt;br /&gt;
     1500    0.009    0.000    0.492    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:452(execute)&lt;br /&gt;
     1500    0.004    0.000    0.437    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:705(cmd_query)&lt;br /&gt;
   500000    0.367    0.000    0.367    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:405(_FLOAT_to_python)&lt;br /&gt;
   504500    0.343    0.000    0.343    0.000 {method 'extend' of 'bytearray' objects}&lt;br /&gt;
     1500    0.007    0.000    0.315    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:481(_send_cmd)&lt;br /&gt;
  2501500    0.275    0.000    0.275    0.000 {method 'append' of 'list' objects}&lt;br /&gt;
   500500    0.246    0.000    0.246    0.000 {method 'startswith' of 'bytearray' objects}&lt;br /&gt;
   524002    0.150    0.000    0.150    0.000 {isinstance}&lt;br /&gt;
     1500    0.011    0.000    0.118    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:613(_handle_result)&lt;br /&gt;
  1004500    0.112    0.000    0.112    0.000 {len}&lt;br /&gt;
   500000    0.092    0.000    0.092    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:204(description)&lt;br /&gt;
     1500    0.007    0.000    0.042    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/network.py:118(send_plain)&lt;br /&gt;
     2000    0.013    0.000    0.041    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:226(parse_column)&lt;br /&gt;
     1000    0.008    0.000    0.034    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:346(_process_params_dict)&lt;br /&gt;
     1500    0.002    0.000    0.029    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/socket.py:223(meth)&lt;br /&gt;
     1500    0.026    0.000    0.026    0.000 {method 'sendall' of '_socket.socket' objects}&lt;br /&gt;
     5000    0.007    0.000    0.014    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/catch23.py:79(struct_unpack)&lt;br /&gt;
     1000    0.001    0.000    0.013    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:562(_handle_ok)&lt;br /&gt;
    12000    0.011    0.000    0.011    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:167(read_lc_string)&lt;br /&gt;
     1000    0.004    0.000    0.010    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:199(parse_ok)&lt;br /&gt;
     3000    0.005    0.000    0.009    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:152(to_mysql)&lt;br /&gt;
     1500    0.003    0.000    0.007    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:118(make_command)&lt;br /&gt;
     3000    0.003    0.000    0.007    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:130(quote)&lt;br /&gt;
     1500    0.005    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/network.py:53(_prepare_packets)&lt;br /&gt;
     1500    0.003    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:406(_handle_result)&lt;br /&gt;
     3000    0.003    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:102(escape)&lt;br /&gt;
     1000    0.002    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:252(parse_eof)&lt;br /&gt;
      500    0.001    0.000    0.005    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:579(_handle_eof)&lt;br /&gt;
     2000    0.003    0.000    0.005    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:545(_handle_server_status)&lt;br /&gt;
     1500    0.003    0.000    0.004    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:53(int1store)&lt;br /&gt;
     3000    0.003    0.000    0.003    0.000 {method 'encode' of 'str' objects}&lt;br /&gt;
     9001    0.003    0.000    0.003    0.000 {method 'replace' of 'str' objects}&lt;br /&gt;
     1500    0.003    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:295(_reset_result)&lt;br /&gt;
     4500    0.003    0.000    0.003    0.000 {_struct.pack}&lt;br /&gt;
     2500    0.003    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:296(read_lc_int)&lt;br /&gt;
      500    0.002    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:757(_handle_eof)&lt;br /&gt;
     6027    0.003    0.000    0.003    0.000 {method 'format' of 'str' objects}&lt;br /&gt;
     3000    0.002    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:1221(_set_unread_result)&lt;br /&gt;
      500    0.001    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:219(parse_column_count)&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 {repr}&lt;br /&gt;
     4527    0.002    0.000    0.002    0.000 {getattr}&lt;br /&gt;
     4000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/constants.py:34(flag_is_set)&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:383(_handle_noresultset)&lt;br /&gt;
     4000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:1233(_get_unread_result)&lt;br /&gt;
     2001    0.001    0.000    0.001    0.000 {method 'items' of 'dict' objects}&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:307(_have_unread_result)&lt;br /&gt;
     3000    0.001    0.000    0.001    0.000 {method 'lower' of 'str' objects}&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 {range}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:173(_str_to_mysql)&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:169(_float_to_mysql)&lt;br /&gt;
     1000    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:161(_int_to_mysql)&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:1313(_get_getwarnings)&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:200(reset)&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:397(_handle_resultset)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/__init__.py:71(search_function)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/ascii.py:41(getregentry)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/contextlib.py:21(__exit__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/__init__.py:49(normalize_encoding)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 test.py:8(profiled)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/codecs.py:77(__new__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {__import__}&lt;br /&gt;
        3    0.000    0.000    0.000    0.000 {method 'get' of 'dict' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'split' of 'str' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {built-in method __new__ of type object at 0x1001534e8}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'translate' of 'str' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'join' of 'str' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {hasattr}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DBAPI:  &amp;lt;module 'MySQLdb' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/__init__.pyc'&amp;gt;&lt;br /&gt;
         47503 function calls in 0.918 seconds&lt;br /&gt;
&lt;br /&gt;
   Ordered by: cumulative time&lt;br /&gt;
&lt;br /&gt;
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;
     1500    0.009    0.000    0.917    0.001 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:164(execute)&lt;br /&gt;
     1500    0.002    0.000    0.890    0.001 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:353(_query)&lt;br /&gt;
     1500    0.004    0.000    0.498    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:315(_do_query)&lt;br /&gt;
     1500    0.016    0.000    0.391    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:358(_post_get_result)&lt;br /&gt;
     1500    0.001    0.000    0.375    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:324(_fetch_row)&lt;br /&gt;
      500    0.374    0.001    0.374    0.001 {built-in method fetch_row}&lt;br /&gt;
     1500    0.007    0.000    0.362    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:142(_do_get_result)&lt;br /&gt;
     1500    0.002    0.000    0.351    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:351(_get_result)&lt;br /&gt;
     1500    0.349    0.000    0.349    0.000 {method 'store_result' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.131    0.000    0.131    0.000 {method 'query' of '_mysql.connection' objects}&lt;br /&gt;
     4000    0.002    0.000    0.010    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:184(&amp;lt;genexpr&amp;gt;)&lt;br /&gt;
     3000    0.002    0.000    0.008    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/connections.py:267(literal)&lt;br /&gt;
     1500    0.007    0.000    0.007    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:107(_warning_check)&lt;br /&gt;
     3000    0.002    0.000    0.006    0.000 {method 'escape' of '_mysql.connection' objects}&lt;br /&gt;
     6000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:159(_get_db)&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/converters.py:81(Float2Str)&lt;br /&gt;
     2500    0.001    0.000    0.001    0.000 {isinstance}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/connections.py:202(string_literal)&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:380(fetchall)&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 {built-in method describe}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 {method 'string_literal' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {method 'insert_id' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {method 'affected_rows' of '_mysql.connection' objects}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/converters.py:69(Thing2Str)&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 {method 'warning_count' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 {method 'info' of '_mysql.connection' objects}&lt;br /&gt;
     1000    0.000    0.000    0.000    0.000 {method 'iteritems' of 'dict' objects}&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:103(_check_executed)&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 {len}&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 {built-in method field_flags}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/contextlib.py:21(__exit__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 test.py:8(profiled)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DBAPI:  &amp;lt;module 'pymysql' from '/Users/classic/dev/PyMySQL/pymysql/__init__.pyc'&amp;gt;, total seconds 10.046012&lt;br /&gt;
DBAPI:  &amp;lt;module 'mysql.connector' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/__init__.pyc'&amp;gt;, total seconds 15.403260&lt;br /&gt;
DBAPI:  &amp;lt;module 'MySQLdb' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/__init__.pyc'&amp;gt;, total seconds 1.028737&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mike Bayer</name></author>	</entry>

	<entry>
		<id>https://wiki.openstack.org/w/index.php?title=PyMySQL_evaluation&amp;diff=72551</id>
		<title>PyMySQL evaluation</title>
		<link rel="alternate" type="text/html" href="https://wiki.openstack.org/w/index.php?title=PyMySQL_evaluation&amp;diff=72551"/>
				<updated>2015-01-28T20:14:33Z</updated>
		
		<summary type="html">&lt;p&gt;Mike Bayer: /* Appendix A - Performance Tests */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= PyMySQL Evaluation =&lt;br /&gt;
&lt;br /&gt;
This page will capture issues related to Openstack moving to the [https://github.com/PyMySQL/PyMySQL/ PyMySQL driver] for MySQL/MariaDB dbapi access.&lt;br /&gt;
&lt;br /&gt;
== Rationale ==&lt;br /&gt;
&lt;br /&gt;
While the [https://github.com/farcepest/MySQLdb1 MySQL-Python] driver is a very mature and stable driver, it does not provide compatibility with either Python 3 or with [http://eventlet.net/doc/patching.html eventlet monkeypatching] (MySQL-Python  [https://github.com/eventlet/eventlet/blob/v0.16.1/eventlet/patcher.py#L225-L237 can be monkeypatched] with eventlet, but this feature disabled by default and missed in documentation). So OpenStack's usage of MySQL-Python, combined with the fact that concurrency is provided by eventlet, means that we currently have fully serialized database access within a single process, that is, only one database command occurs at a time within an Openstack Python process.&lt;br /&gt;
&lt;br /&gt;
== Drivers Under Consideration ==&lt;br /&gt;
&lt;br /&gt;
The two drivers that are known to provide eventlet-monkeypatch compatibility are MySQL-Connector and PyMySQL, as they are written in pure Python.   The two other well-known drivers for MySQL are MySQL-Python and OurSQL, both of which are written in C and offer no explicit support for eventlet or async.  They can reportedly be built to support gevent using a system called [https://github.com/CMGS/greenify Greenify], but the maturity and/or stabiltiy of this system is unknown.&lt;br /&gt;
&lt;br /&gt;
A comparison at http://www.diamondtin.com/2014/sqlalchemy-gevent-mysql-python-drivers-comparison/ illustrates performance metrics observed with all four of these drivers, with MySQL-python built both without and with the &amp;quot;greenify&amp;quot; system.&lt;br /&gt;
&lt;br /&gt;
A summary of the status of all four drivers is as follows.&lt;br /&gt;
&lt;br /&gt;
=== MySQL-Python ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/farcepest/MySQLdb1 MySQL-Python] is the most widely used Python driver.  However, it does not support Python 3, and it does not support async systems unless used within a thread pool.   While a system known as Greenify can potentially resolve the latter situation, the Python 3 limitation is still a deal-breaker - pull request with Python 3 support [https://github.com/farcepest/MySQLdb1/pull/62 was proposed] to MySQL-python in April 2014 and still not merged.&lt;br /&gt;
&lt;br /&gt;
=== mysqlclient ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/PyMySQL/mysqlclient-python mysqlclient] is a fork of MySQL-python. It add Python 3.3 support and merges some pull requests.&lt;br /&gt;
&lt;br /&gt;
=== OurSQL ===&lt;br /&gt;
&lt;br /&gt;
[https://pythonhosted.org/oursql/ OurSQL] is an alternative MySQL driver that is also written in C.  It features fast performance and uses a different execution model than MySQL-python, using prepared statements.    OurSQL does have a Python 3 port hosted on [https://launchpad.net/oursql/py3k/ launchpad] but is not integrated with OurSQL itself nor is it published on Pypi.     Because it is written in C, it does not offer compatibility with async systems including eventlet-style monkeypatching.&lt;br /&gt;
&lt;br /&gt;
Besides the shaky Python 3 support and lack of eventlet support, OurSQL is also not maintained, seeing its last master commit on [https://code.launchpad.net/~jerub/oursql/master 2012-06-05].&lt;br /&gt;
&lt;br /&gt;
=== MySQL-Connector-Python ===&lt;br /&gt;
&lt;br /&gt;
[https://pypi.python.org/pypi/mysql-connector-python MySQL-connector-Python] is a pure Python MySQL driver, and is now released under the auspices of the MySQL project itself, as owned by Oracle.  MySQL-connector-Python supports both Python 3 as well as eventlet monkeypatching, and is well maintained.  It is endorsed by Oracle as the official Oracle-supported driver for MySQL, so to that extent, it is in most ways Openstack's first choice in driver.  However, Oracle refuses to publish MySQL-connector-Python on Pypi, which is critical to the Openstack infrastructure.  Repeated attempts to communicate with Oracle in order to resolve this issue have not made any progress.    Therefore, for this one unfortunate reason, MySQL-connector-Python will not have a place in the Openstack ecosystem unless this issue is resolved.&lt;br /&gt;
&lt;br /&gt;
=== PyMySQL ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/PyMySQL/PyMySQL PyMySQL] is a pure Python MySQL&lt;br /&gt;
driver, first written as a rough port of the MySQL-Python driver.&lt;br /&gt;
PyMySQL meets all of Openstack's criterion for a driver: it is fully&lt;br /&gt;
open source, hosted on Github, released on Pypi, is actively&lt;br /&gt;
maintained, is written in pure Python so is eventlet-monkeypatch&lt;br /&gt;
compatible, and is fully Python 3 compatible.&lt;br /&gt;
&lt;br /&gt;
As this document is named &amp;quot;PyMySQL Evaluation&amp;quot;, it should be apparent that&lt;br /&gt;
this is the driver Openstack is currently leaning towards.  However, it does&lt;br /&gt;
have some minor code quality issues which hopefully can be addressed in some&lt;br /&gt;
way; the section below titled &amp;quot;PyMySQL Code Review&amp;quot; will summarize the&lt;br /&gt;
current state of the code.  We are in fact leaning towards this driver because&lt;br /&gt;
its the only one that actually meets all criteria.  PyMySQL appears to be&lt;br /&gt;
fairly widely used, especially considering its low profile, so it's possible&lt;br /&gt;
that this is the case for many other users as well.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===MySQL DB Drivers Comparison===&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Project !! PyPi hosted !! Eventlet friendly !! Python 3 compatibility !! Maturity and/or stability  !! Comment&lt;br /&gt;
|-&lt;br /&gt;
| MySQL-Python || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: orange;&amp;quot; | Partial || style=&amp;quot;background-color: red;&amp;quot; | No  || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || Can be monkeypatched by eventlet, but only to enable thread pooling&lt;br /&gt;
|-&lt;br /&gt;
| mysqlclient || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: orange;&amp;quot; | Partial || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes  || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || MySQL-python fork, so should be similar to it&lt;br /&gt;
|-&lt;br /&gt;
| OurSQL || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: red;&amp;quot; | No || style=&amp;quot;background-color: orange;&amp;quot; | Yes, but not Pypi hosted  || style=&amp;quot;background-color: orange;&amp;quot; | ??? || Has not seen commits/releases in two years&lt;br /&gt;
|-&lt;br /&gt;
| MySQL-Connector-Python || style=&amp;quot;background-color: red;&amp;quot; | No || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes  || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || The official Oracle-supported driver for MySQL&lt;br /&gt;
|-&lt;br /&gt;
| PyMySQL || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes  || style=&amp;quot;background-color: orange;&amp;quot; | Yes, with a [http://lists.openstack.org/pipermail/openstack-dev/2014-December/052864.html proviso]. ||&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== PyMySQL Code Review ==&lt;br /&gt;
&lt;br /&gt;
PyMySQL started roughly as a pure Python port of MySQL-Python.   The sections below will&lt;br /&gt;
detail various aspects of the driver and the status of the code.   Overall, the general&lt;br /&gt;
tone of PyMySQL is one of code that was written in the spirit of pragmatism and immediate&lt;br /&gt;
need; it is straightforward, relatively free of serious antipatterns and performs&lt;br /&gt;
its task in a matter-of-fact way.  However,&lt;br /&gt;
it lacks polish and completeness in many areas that would normally be in better&lt;br /&gt;
condition for a more mature project.  These areas are all highly fixable, and it is&lt;br /&gt;
hoped that these items can be shared with the current PyMySQL developers, through&lt;br /&gt;
a combination of this document itself as well as new issues and pull requests&lt;br /&gt;
on the PyMySQL tracker, so that this fairly promising library can be pushed&lt;br /&gt;
to the next level.   This would require that the developers are amenable to&lt;br /&gt;
these improvements.&lt;br /&gt;
&lt;br /&gt;
=== Coding Style / Pep8 ===&lt;br /&gt;
&lt;br /&gt;
PyMySQL's coding style is pretty good.  The majority of it passes all&lt;br /&gt;
flake8 tests, save for a few dozen whitespace issues and&lt;br /&gt;
some long lines here and there within the core&lt;br /&gt;
modules, and moreso within the tests which appear to be a bit more&lt;br /&gt;
crufty than the core modules.  A full run of flake8 produces only 128&lt;br /&gt;
errors, and with a default run of autopep8 against E1,E2,E3, we can&lt;br /&gt;
fix all of them in one pass except for 38 remaining long line warnings.&lt;br /&gt;
The code layout is mostly idiomatic and reasonable.&lt;br /&gt;
&lt;br /&gt;
The codebase is probably short on docstrings.  Those methods that are&lt;br /&gt;
part of the public DBAPI do have docstrings, but tend to be extremely&lt;br /&gt;
terse (example, docstring for cursor.execute(): &amp;quot;'Execute a query'&amp;quot;).&lt;br /&gt;
Other methods that aren't public tend to not have any docstrings, but&lt;br /&gt;
the codebase is not at all hard to read and the purpose of methods and&lt;br /&gt;
functions is pretty easy to discern just by their names and implementations,&lt;br /&gt;
however those who seek inline documentation as a means to understand a codebase&lt;br /&gt;
will be disappointed.   The terseness of public API docstrings is more of&lt;br /&gt;
an issue in terms of the overall lack of documentation for PyMySQL,&lt;br /&gt;
see the section &amp;quot;Documentation&amp;quot; below.&lt;br /&gt;
&lt;br /&gt;
=== Test Coverage ===&lt;br /&gt;
&lt;br /&gt;
The test coverage for PyMySQL is definitely lacking.   From a visual&lt;br /&gt;
inspection alone, it's apparent that of the dozen or so test modules,&lt;br /&gt;
most of them have less then ten tests each and the modules are very short.&lt;br /&gt;
For a full test run, there's only 126 tests; contrast this to&lt;br /&gt;
MySQL-Connector-Python which has 531 tests.&lt;br /&gt;
&lt;br /&gt;
Additionally, all of the tests are live round-trip tests against a&lt;br /&gt;
real database.  There are no non-live unit tests of any kind,&lt;br /&gt;
leaving codepaths that are not easily exercised&lt;br /&gt;
on a generic MySQL database out in the cold.  This means that features designed&lt;br /&gt;
for particular versions of MySQL, particular datatypes, particular error&lt;br /&gt;
conditions, and particular kinds of message packets that aren't created for&lt;br /&gt;
real are simply not tested.&lt;br /&gt;
&lt;br /&gt;
The lack of tests is apparent when run with coverage.  The test suite itself&lt;br /&gt;
does not appear to have any signs that it is normally run with coverage&lt;br /&gt;
turned on, with no directives in tox.ini or similar.   With coverage,&lt;br /&gt;
the overall coverage is 85%, but the modules most lacking in coverage&lt;br /&gt;
are also the most critical to PyMySQL's core functionality,&lt;br /&gt;
connections.py, converters.py and cursors.py:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Name                                                              Stmts   Miss  Cover&lt;br /&gt;
-------------------------------------------------------------------------------------&lt;br /&gt;
pymysql/__init__                                                     51      3    94%&lt;br /&gt;
pymysql/_compat                                                      14      4    71%&lt;br /&gt;
pymysql/_socketio                                                    72     30    58%&lt;br /&gt;
pymysql/charset                                                     228      3    99%&lt;br /&gt;
pymysql/connections                                                 827    188    77%&lt;br /&gt;
pymysql/constants/CLIENT                                             18      0   100%&lt;br /&gt;
pymysql/constants/COMMAND                                            32      0   100%&lt;br /&gt;
pymysql/constants/ER                                                471      0   100%&lt;br /&gt;
pymysql/constants/FIELD_TYPE                                         29      0   100%&lt;br /&gt;
pymysql/constants/FLAG                                               15      0   100%&lt;br /&gt;
pymysql/constants/SERVER_STATUS                                      10      0   100%&lt;br /&gt;
pymysql/constants/__init__                                            0      0   100%&lt;br /&gt;
pymysql/converters                                                  148     51    66%&lt;br /&gt;
pymysql/cursors                                                     273     37    86%&lt;br /&gt;
pymysql/err                                                          40      1    98%&lt;br /&gt;
pymysql/tests/__init__                                               14      5    64%&lt;br /&gt;
pymysql/tests/base                                                   25      2    92%&lt;br /&gt;
pymysql/tests/test_DictCursor                                        73      2    97%&lt;br /&gt;
pymysql/tests/test_SSCursor                                          56      6    89%&lt;br /&gt;
pymysql/tests/test_basic                                            186      4    98%&lt;br /&gt;
pymysql/tests/test_connection                                        57      8    86%&lt;br /&gt;
pymysql/tests/test_example                                           17      2    88%&lt;br /&gt;
pymysql/tests/test_issues                                           300     48    84%&lt;br /&gt;
pymysql/tests/test_load_local                                        39      2    95%&lt;br /&gt;
pymysql/tests/test_nextset                                           43      4    91%&lt;br /&gt;
pymysql/tests/thirdparty/__init__                                     7      5    29%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/__init__                        6      2    67%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/capabilities                  196     23    88%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/dbapi20                       423    121    71%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_capabilities      73      6    92%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_dbapi20          100      2    98%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_nonstandard       57      3    95%&lt;br /&gt;
pymysql/times                                                        12      0   100%&lt;br /&gt;
pymysql/util                                                         14      7    50%&lt;br /&gt;
-------------------------------------------------------------------------------------&lt;br /&gt;
TOTAL                                                              3926    569    86%&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The lack of coverage in converters.py refers to functions related to handling&lt;br /&gt;
incoming bound values as well as outgoing result values.    Key data escaping&lt;br /&gt;
features such as escaping of dictionaries, sets, some unicode objects and&lt;br /&gt;
byte objects as well as many pathways into date and time-processing functions are not&lt;br /&gt;
covered.&lt;br /&gt;
&lt;br /&gt;
In connections.py, uncovered features include various SSL and&lt;br /&gt;
very legacy (e.g. version 3.23) MySQL features, but also some protocol parsing&lt;br /&gt;
features.&lt;br /&gt;
&lt;br /&gt;
In cursors.py, there is coverage for an elaborate and mostly undocumented&lt;br /&gt;
performance-related feature that rewrites an INSERT statement to have an&lt;br /&gt;
extended VALUES clause (_do_execute_many), however it fails to cover the very&lt;br /&gt;
likely case where the function will need to break the input set into&lt;br /&gt;
multiple chunks:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;gt;         for arg in args:&lt;br /&gt;
&amp;gt;             v = values % escape(arg, conn)&lt;br /&gt;
&amp;gt;             if isinstance(v, text_type):&lt;br /&gt;
&amp;gt;                 v = v.encode(encoding)&lt;br /&gt;
&amp;gt;             if len(sql) + len(v) + 1 &amp;gt; max_stmt_length:&lt;br /&gt;
!                 rows += self.execute(sql)&lt;br /&gt;
!                 sql = bytearray(prefix)&lt;br /&gt;
&amp;gt;             else:&lt;br /&gt;
&amp;gt;                 sql += b','&lt;br /&gt;
&amp;gt;             sql += v&lt;br /&gt;
&amp;gt;         rows += self.execute(sql)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The most prominent missing coverage in cursors.py seems to be&lt;br /&gt;
for its &amp;quot;scrollable cursor&amp;quot; support.&lt;br /&gt;
&lt;br /&gt;
Beyond the lack of completeness in testing, many of the tests themselves&lt;br /&gt;
are written in an expedient &amp;quot;one giant test&amp;quot; style where dozens of&lt;br /&gt;
assertions and individual behaviors are lumped into one big test&lt;br /&gt;
case (see test_SSCursor.py, test_basic.py-&amp;gt;test_datatypes for examples).&lt;br /&gt;
Test cases like these are difficult to work with when debugging&lt;br /&gt;
regressions as well as when tests for new features need to be added, and ideally&lt;br /&gt;
these should be broken out into clean single-feature tests with consistent&lt;br /&gt;
fixtures.&lt;br /&gt;
&lt;br /&gt;
There is also a suite called &amp;quot;test_issues&amp;quot;, which intends to&lt;br /&gt;
accumulate tests against specific issues that have been reported. This&lt;br /&gt;
isn't a bad idea, though it would be nice to see an effort made into&lt;br /&gt;
categorizing these tests into the actual features they are testing,&lt;br /&gt;
rather than a meaningless list of issue numbers, which could then form&lt;br /&gt;
as the basis for new suites centered around those areas of&lt;br /&gt;
functionality.  The issues reported should be used as the inspiration&lt;br /&gt;
for improvement of the test suite, rather than just another line-item&lt;br /&gt;
to be filed away.&lt;br /&gt;
&lt;br /&gt;
=== Library Documentation ===&lt;br /&gt;
&lt;br /&gt;
As the Python DBAPI is already a well-documented API, and the users of&lt;br /&gt;
PyMySQL are typically coming from MySQL-Python which is documented&lt;br /&gt;
to some degree and for which the use contract is widely known, it seems&lt;br /&gt;
unlikely that users of PyMySQL are eager for comprehensive documentation&lt;br /&gt;
specific to this library.&lt;br /&gt;
&lt;br /&gt;
That being said, PyMySQL does not appear to have any real library&lt;br /&gt;
documentation at all.  There's a README which&lt;br /&gt;
essentially refers to pep-249, an example.py in the root which shows a&lt;br /&gt;
very basic connection / round trip, and that's it. In&lt;br /&gt;
order to know anything about the API, specific parameters, optimization&lt;br /&gt;
strategies, behaviors, etc., one either has to go off of the MySQL-Python documentation and assume&lt;br /&gt;
PyMySQL also features the same parameter, or read the source code.&lt;br /&gt;
&lt;br /&gt;
PyMySQL probably hasn't had much urgent need for real documentation thus&lt;br /&gt;
far.  However, relying upon being a port of MySQL-Python is fast becoming&lt;br /&gt;
something that can no longer be relied upon, as MySQL-Python's development&lt;br /&gt;
is quite stalled, and PyMySQL should aspire to move into the future&lt;br /&gt;
of MySQL and Python with its own featureset and behaviors.  To that end, it would be a &lt;br /&gt;
great idea if it at least made the start&lt;br /&gt;
of a rudimentary Sphinx build including autodoc for module documentation&lt;br /&gt;
which could then be published up to RTD.   This would be very easy to get&lt;br /&gt;
started, and once present, new documentation sections can be added&lt;br /&gt;
iteratively.&lt;br /&gt;
&lt;br /&gt;
=== Architecture and Performance ===&lt;br /&gt;
&lt;br /&gt;
As stated before, PyMySQL has a really matter-of-fact and straightforward&lt;br /&gt;
implementation.   The code is very readable and it's easy to discern even&lt;br /&gt;
unusual features such as the INSERT..VALUES rewriting.  As far as the design,&lt;br /&gt;
there are lots of areas where performance suffers a bit at the Python level;&lt;br /&gt;
these areas could be greatly improved very easily with a little bit of&lt;br /&gt;
attention.&lt;br /&gt;
&lt;br /&gt;
As we are dealing with a driver that is communicating with a low level&lt;br /&gt;
protocol over a socket, it's critical that sending and receiving messages&lt;br /&gt;
is done as efficiently as possible.  The mechanisms used in PyMySQL rely&lt;br /&gt;
heavily on standard Python objects, without the use of __slots__; this means&lt;br /&gt;
that for individual messages on a socket, we are creating a heavyweight object,&lt;br /&gt;
calling its __init__() method and a creating a new __dict__ each time;&lt;br /&gt;
the most common object, OKPacketWrapper(), already wraps a MysqlPacket()&lt;br /&gt;
object, so we're doing two __init__()s in this common case.  OKPacketWrapper&lt;br /&gt;
also implements a `__getattr__` scheme&lt;br /&gt;
to proxy attribute access from itself to the internal MysqlPacket; this is a&lt;br /&gt;
an inefficient mode of operation in Python as it means a lookup must first&lt;br /&gt;
fail on the OKPacketWrapper before it invokes the `__getattr__()` method&lt;br /&gt;
and does another attribute lookup.   A reorg of the packet classes to produce&lt;br /&gt;
far fewer objects for messages as well as to use __slots__ would be&lt;br /&gt;
recommended.&lt;br /&gt;
&lt;br /&gt;
There are also lots of areas where function call overhead is unnecessarily&lt;br /&gt;
high due to the organization of calls.  For example, when calling upon&lt;br /&gt;
cursor.execute(), for parameters passed as a dictionary under Python 2K,&lt;br /&gt;
the isinstance() builtin function will be called on the incoming arguments&lt;br /&gt;
four times at a minimum, with two of each call against the exact same&lt;br /&gt;
criteria.  Additional logic will then call isinstance() on every key of&lt;br /&gt;
the dictionary as well as at least twice on every value within the dictionary,&lt;br /&gt;
in order to determine if the value needs to be converted to bytes as well&lt;br /&gt;
as for some top-level escaping logic checks; the escaping logic then&lt;br /&gt;
goes further with more checks for type() to match the object to a final&lt;br /&gt;
converter function.   While the flow of this logic does appear to be&lt;br /&gt;
reasonable and straightforward, it isn't ideal for a low-level database&lt;br /&gt;
driver library where speed is a high priority; the various wrappers and&lt;br /&gt;
processors can easily be reorganized here to reduce the number of isinstance()&lt;br /&gt;
and type() calls significantly, which would save a lot on function call&lt;br /&gt;
overhead.  It is likely there are many other areas in the codebase where&lt;br /&gt;
similar reductions in call counts can be made without too much disruption.&lt;br /&gt;
&lt;br /&gt;
In Appendix A, a short performance suite is illustrated.  This&lt;br /&gt;
is run against PyMySQL, Mysql-Connector-Python, and MySQL-Python.&lt;br /&gt;
PyMySQL does in fact demonstrate the&lt;br /&gt;
highest call-count behavior, logging 26,850,503 function calls&lt;br /&gt;
compared to MySQL-connector's 11,674,074 for the same operations.  To PyMySQL's&lt;br /&gt;
credit, it somehow completes the test a bit faster than MySQL-connector&lt;br /&gt;
despite having more than twice as many function calls; this probably indicates&lt;br /&gt;
the use of something very slow within MySQL-connector, such as catching&lt;br /&gt;
an exception within a tight loop.  However, the real bar we're comparing to&lt;br /&gt;
is MySQL-Python, which is written&lt;br /&gt;
in C and does the whole test with only 47,000 Python function calls; while&lt;br /&gt;
PyMySQL and MySQL-connector-Python compete for time within the 16-20 second&lt;br /&gt;
range, MySQL-Python completes the whole test in **0.9 seconds**, a 1600%&lt;br /&gt;
improvement over PyMySQL.   This speed difference is not PyMySQL's fault,&lt;br /&gt;
as pure Python is known to be extemely slow compared to C code.&lt;br /&gt;
However, there's a lot of easy wins in PyMySQL's codebase where the function&lt;br /&gt;
call count could probably be reduced dramatically to be even better than that&lt;br /&gt;
of MySQL-connector-Python; ideally, the driver would be able to run this&lt;br /&gt;
test possibly 5-6x slower than MySQL-Python rather than 16x.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== History Documentation ===&lt;br /&gt;
&lt;br /&gt;
PyMySQL includes a CHANGELOG file in its root which describes the bug fixes&lt;br /&gt;
and features of each release.  The construction of this file is definitely&lt;br /&gt;
within the &amp;quot;expedient&amp;quot; style noted for PyMySQL overall, and for it to be&lt;br /&gt;
genuinely useful for enterprise-level software, it would need to be&lt;br /&gt;
much more complete in its detail.    Each bug fix mentioned is only referred to&lt;br /&gt;
tersely without any link to a pull request or bug report.  While some can&lt;br /&gt;
be linked to a specific change through detective work&lt;br /&gt;
(&amp;quot;Cursor.fetchall() and .fetchmany now return list, not tuple&amp;quot;), it would&lt;br /&gt;
be very difficult for many&lt;br /&gt;
(&amp;quot;Fixed BIT type handling&amp;quot;, &amp;quot;Fixed GC errors&amp;quot; - what was the issue?)&lt;br /&gt;
and in many cases impossible (&amp;quot;Improved Py3k support&amp;quot;).  The file also&lt;br /&gt;
includes no dates so one has to look at the git tags to figure this&lt;br /&gt;
part out (fortunately, releases to seem to be git tagged at least).&lt;br /&gt;
For a user trying to ascertain if particular issues are&lt;br /&gt;
fixed or if behaviors have changed, this file is at best highly frustrating.&lt;br /&gt;
&lt;br /&gt;
A listing of release dates illustrates an average of two releases per year,&lt;br /&gt;
excluding 2012 when PyMySQL was seeking a new maintainer:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
2014-12-02 12:05:10 -0200  (tag: pymysql-0.6.3)&lt;br /&gt;
2014-04-21 14:28:47 -0300  (tag: pymysql-0.6.2)&lt;br /&gt;
2013-10-11 13:36:56 -0300  (tag: pymysql-0.6.1)&lt;br /&gt;
2013-10-04 14:25:14 -0300  (tag: pymysql-0.6)&lt;br /&gt;
2011-11-08 10:27:41 -0800  (tag: pymysql-0.5)&lt;br /&gt;
2010-12-27 17:28:16 +0000  (tag: pymysql-0.4)&lt;br /&gt;
2010-09-03 00:55:29 +0000  (tag: pymysql-0.3)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Community Involvement ===&lt;br /&gt;
&lt;br /&gt;
This is an area where PyMySQL seems to shine, and might even be an advantage&lt;br /&gt;
as compared to MySQL-Connector-Python, which remains under the cloak&lt;br /&gt;
of MySQL's vastly overstuffed bugtracker under the ownership of&lt;br /&gt;
the famously-opaque Oracle.   For a small, low key library without&lt;br /&gt;
any real documentation and only 126 tests, it features 33 contributors on&lt;br /&gt;
Github and is up to pull request #285, which is quite high.  Because&lt;br /&gt;
PyMySQL is small and straightforward, it does lend itself to contribution&lt;br /&gt;
and seems to do very well in this area.   The developers so far have been&lt;br /&gt;
responsive to issues and pull requests made by the author.&lt;br /&gt;
&lt;br /&gt;
Based on github contributor graphs it appears that PyMySQL had a lull&lt;br /&gt;
in 2012 but was picked up again afterwards in 2013.  As of 2012, its most&lt;br /&gt;
prominent developer Pete Hunt stopped development, and on July 25, 2013&lt;br /&gt;
added a note to the README that the project was looking for a new maintainer.&lt;br /&gt;
As of 2013 it was picked up by the current most prominent developers&lt;br /&gt;
Marcel Rodrigues (github username lecram) and INADA Naoki&lt;br /&gt;
(github username methane).&lt;br /&gt;
&lt;br /&gt;
Most issues that have been noted thus far within the areas of testing,&lt;br /&gt;
documentation and architecture are all very fixable; if the Openstack community&lt;br /&gt;
can provide resources to address some of these areas, and if the PyMySQL&lt;br /&gt;
developers are reasonably open to accepting new changes and producing new&lt;br /&gt;
releases in a timely fashion, the PyMySQL driver&lt;br /&gt;
could rapidly become a fully formidable contender in the MySQL driver field.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Appendix A - Performance Tests ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
import cProfile&lt;br /&gt;
import StringIO&lt;br /&gt;
import pstats&lt;br /&gt;
import contextlib&lt;br /&gt;
import time&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextlib.contextmanager&lt;br /&gt;
def profiled(dbapi):&lt;br /&gt;
    pr = cProfile.Profile()&lt;br /&gt;
    pr.enable()&lt;br /&gt;
    yield&lt;br /&gt;
    pr.disable()&lt;br /&gt;
    s = StringIO.StringIO()&lt;br /&gt;
    ps = pstats.Stats(pr, stream=s).sort_stats('cumulative')&lt;br /&gt;
    ps.print_stats()&lt;br /&gt;
    print &amp;quot;DBAPI:  %s&amp;quot; % dbapi&lt;br /&gt;
    print s.getvalue()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextlib.contextmanager&lt;br /&gt;
def timeonly(dbapi):&lt;br /&gt;
    now = time.time()&lt;br /&gt;
    try:&lt;br /&gt;
        yield&lt;br /&gt;
    finally:&lt;br /&gt;
        total = time.time() - now&lt;br /&gt;
        print &amp;quot;DBAPI:  %s, total seconds %f&amp;quot; % (dbapi, total)&lt;br /&gt;
&lt;br /&gt;
import MySQLdb&lt;br /&gt;
import pymysql&lt;br /&gt;
from mysql import connector as mysqlconnector&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def go(dbapi, ctx):&lt;br /&gt;
    conn = dbapi.connect(&lt;br /&gt;
        user='scott', passwd='tiger', host='localhost', db='test')&lt;br /&gt;
    cursor = conn.cursor()&lt;br /&gt;
    cursor.execute(&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
CREATE TABLE IF NOT EXISTS test_things (&lt;br /&gt;
    x INTEGER,&lt;br /&gt;
    y VARCHAR(255),&lt;br /&gt;
    z FLOAT&lt;br /&gt;
) engine=InnoDB&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;)&lt;br /&gt;
    cursor.execute(&amp;quot;DELETE from test_things&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    with ctx(dbapi):&lt;br /&gt;
        for row in xrange(1000):&lt;br /&gt;
            cursor.execute(&lt;br /&gt;
                &amp;quot;INSERT INTO test_things (x, y, z) &amp;quot;&lt;br /&gt;
                &amp;quot;VALUES (%(x)s, %(y)s, %(z)s)&amp;quot;,&lt;br /&gt;
                {&amp;quot;x&amp;quot;: row, &amp;quot;y&amp;quot;: &amp;quot;row number %d&amp;quot; % row,&lt;br /&gt;
                 &amp;quot;z&amp;quot;: row * 4.57292, }&lt;br /&gt;
            )&lt;br /&gt;
&lt;br /&gt;
        for x in xrange(500):&lt;br /&gt;
            cursor.execute(&lt;br /&gt;
                &amp;quot;select * from test_things&amp;quot;)&lt;br /&gt;
            for row in cursor.fetchall():&lt;br /&gt;
                row[0], row[1], row[2]&lt;br /&gt;
&lt;br /&gt;
    cursor.close()&lt;br /&gt;
    conn.close()&lt;br /&gt;
&lt;br /&gt;
go(pymysql, profiled)&lt;br /&gt;
go(mysqlconnector, profiled)&lt;br /&gt;
go(MySQLdb, profiled)&lt;br /&gt;
&lt;br /&gt;
go(pymysql, timeonly)&lt;br /&gt;
go(mysqlconnector, timeonly)&lt;br /&gt;
go(MySQLdb, timeonly)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DBAPI:  &amp;lt;module 'pymysql' from '/Users/classic/dev/PyMySQL/pymysql/__init__.pyc'&amp;gt;&lt;br /&gt;
         26851003 function calls in 15.924 seconds&lt;br /&gt;
&lt;br /&gt;
   Ordered by: cumulative time&lt;br /&gt;
&lt;br /&gt;
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;
     1500    0.010    0.000   15.923    0.011 /Users/classic/dev/PyMySQL/pymysql/cursors.py:105(execute)&lt;br /&gt;
     1500    0.003    0.000   15.883    0.011 /Users/classic/dev/PyMySQL/pymysql/cursors.py:269(_query)&lt;br /&gt;
     1500    0.004    0.000   15.859    0.011 /Users/classic/dev/PyMySQL/pymysql/connections.py:746(query)&lt;br /&gt;
     1500    0.003    0.000   15.819    0.011 /Users/classic/dev/PyMySQL/pymysql/connections.py:892(_read_query_result)&lt;br /&gt;
     1500    0.004    0.000   15.812    0.011 /Users/classic/dev/PyMySQL/pymysql/connections.py:1097(read)&lt;br /&gt;
      500    0.003    0.000   15.559    0.031 /Users/classic/dev/PyMySQL/pymysql/connections.py:1154(_read_result_packet)&lt;br /&gt;
      500    0.900    0.002   15.429    0.031 /Users/classic/dev/PyMySQL/pymysql/connections.py:1187(_read_rowdata_packet)&lt;br /&gt;
   500000    3.417    0.000    9.173    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1200(_read_row_from_packet)&lt;br /&gt;
  2012000    1.157    0.000    5.307    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:311(read_length_coded_string)&lt;br /&gt;
   504500    1.495    0.000    5.209    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:845(_read_packet)&lt;br /&gt;
  2014500    1.248    0.000    3.035    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:293(read_length_encoded_integer)&lt;br /&gt;
  3539500    2.439    0.000    2.750    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:245(read)&lt;br /&gt;
  1009000    0.779    0.000    2.307    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:870(_read_bytes)&lt;br /&gt;
  1009000    1.197    0.000    1.423    0.000 {method 'read' of '_io.BufferedReader' objects}&lt;br /&gt;
   508500    0.379    0.000    0.695    0.000 /Users/classic/dev/PyMySQL/pymysql/util.py:3(byte2int)&lt;br /&gt;
  5058000    0.465    0.000    0.465    0.000 {len}&lt;br /&gt;
   500500    0.222    0.000    0.455    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1146(_check_packet_is_eof)&lt;br /&gt;
   504500    0.205    0.000    0.375    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:342(check_error)&lt;br /&gt;
  2504000    0.275    0.000    0.275    0.000 {method 'append' of 'list' objects}&lt;br /&gt;
   501500    0.182    0.000    0.230    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:326(is_eof_packet)&lt;br /&gt;
     3500    0.009    0.000    0.226    0.000 /Users/classic/dev/PyMySQL/pymysql/_socketio.py:45(readinto)&lt;br /&gt;
     3500    0.207    0.000    0.207    0.000 {method 'recv_into' of '_socket.socket' objects}&lt;br /&gt;
  1022000    0.203    0.000    0.203    0.000 {_struct.unpack}&lt;br /&gt;
   526000    0.200    0.000    0.200    0.000 {isinstance}&lt;br /&gt;
  1501000    0.195    0.000    0.195    0.000 {method 'get' of 'dict' objects}&lt;br /&gt;
   504500    0.176    0.000    0.176    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:238(__init__)&lt;br /&gt;
   504500    0.170    0.000    0.170    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:339(is_error_packet)&lt;br /&gt;
  2014500    0.167    0.000    0.167    0.000 {ord}&lt;br /&gt;
      500    0.006    0.000    0.125    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1224(_get_descriptions)&lt;br /&gt;
     2000    0.003    0.000    0.087    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:361(__init__)&lt;br /&gt;
     2000    0.019    0.000    0.084    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:365(__parse_field_descriptor)&lt;br /&gt;
   500000    0.056    0.000    0.056    0.000 /Users/classic/dev/PyMySQL/pymysql/converters.py:301(through)&lt;br /&gt;
     1500    0.005    0.000    0.037    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:915(_execute_command)&lt;br /&gt;
     1500    0.003    0.000    0.027    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:886(_write_bytes)&lt;br /&gt;
     1500    0.002    0.000    0.024    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/socket.py:223(meth)&lt;br /&gt;
     1500    0.021    0.000    0.021    0.000 {method 'sendall' of '_socket.socket' objects}&lt;br /&gt;
     1500    0.020    0.000    0.020    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:276(_do_get_result)&lt;br /&gt;
     1000    0.003    0.000    0.019    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:95(_escape_args)&lt;br /&gt;
     4000    0.002    0.000    0.015    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:99(&amp;lt;genexpr&amp;gt;)&lt;br /&gt;
     1000    0.002    0.000    0.014    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1127(_read_ok_packet)&lt;br /&gt;
     3000    0.003    0.000    0.013    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:712(escape)&lt;br /&gt;
     1000    0.005    0.000    0.012    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:416(__init__)&lt;br /&gt;
     8000    0.011    0.000    0.011    0.000 {method 'decode' of 'str' objects}&lt;br /&gt;
     3500    0.003    0.000    0.007    0.000 {method '_checkReadable' of '_io._IOBase' objects}&lt;br /&gt;
     2000    0.003    0.000    0.006    0.000 /Users/classic/dev/PyMySQL/pymysql/converters.py:19(escape_item)&lt;br /&gt;
     4000    0.002    0.000    0.006    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:126(&amp;lt;genexpr&amp;gt;)&lt;br /&gt;
     7500    0.003    0.000    0.005    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:115(ensure_bytes)&lt;br /&gt;
     2000    0.003    0.000    0.005    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:386(description)&lt;br /&gt;
     5500    0.004    0.000    0.004    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:268(advance)&lt;br /&gt;
     1000    0.001    0.000    0.004    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:722(escape_string)&lt;br /&gt;
     3500    0.004    0.000    0.004    0.000 /Users/classic/dev/PyMySQL/pymysql/_socketio.py:87(readable)&lt;br /&gt;
      500    0.002    0.000    0.004    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:442(__init__)&lt;br /&gt;
     3500    0.003    0.000    0.003    0.000 {method '_checkClosed' of '_io._IOBase' objects}&lt;br /&gt;
     1000    0.001    0.000    0.003    0.000 /Users/classic/dev/PyMySQL/pymysql/converters.py:59(escape_string)&lt;br /&gt;
     1500    0.003    0.000    0.003    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1080(__init__)&lt;br /&gt;
     1500    0.001    0.000    0.003    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:92(nextset)&lt;br /&gt;
     4000    0.002    0.000    0.002    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:397(get_column_length)&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 /Users/classic/dev/PyMySQL/pymysql/converters.py:56(escape_float)&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 {method 'sub' of '_sre.SRE_Pattern' objects}&lt;br /&gt;
     3000    0.002    0.000    0.002    0.000 {_struct.pack}&lt;br /&gt;
     6000    0.002    0.000    0.002    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:62(_get_db)&lt;br /&gt;
     1500    0.001    0.000    0.002    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:80(_nextset)&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 /Users/classic/dev/PyMySQL/pymysql/util.py:9(int2byte)&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:244(fetchall)&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {min}&lt;br /&gt;
     2500    0.001    0.000    0.001    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:323(is_ok_packet)&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Users/classic/dev/PyMySQL/pymysql/converters.py:52(escape_int)&lt;br /&gt;
     2000    0.001    0.000    0.001    0.000 {method 'items' of 'dict' objects}&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {getattr}&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:1093(__del__)&lt;br /&gt;
     1000    0.000    0.000    0.000    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:259(read_all)&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 /Users/classic/dev/PyMySQL/pymysql/cursors.py:67(_check_executed)&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 /Users/classic/dev/PyMySQL/pymysql/connections.py:336(is_load_local_packet)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/contextlib.py:21(__exit__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 test.py:8(profiled)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DBAPI:  &amp;lt;module 'mysql.connector' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/__init__.pyc'&amp;gt;&lt;br /&gt;
         11674074 function calls in 19.049 seconds&lt;br /&gt;
&lt;br /&gt;
   Ordered by: cumulative time&lt;br /&gt;
&lt;br /&gt;
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;
      500    0.640    0.001   18.556    0.037 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:820(fetchall)&lt;br /&gt;
      500    0.003    0.000   11.196    0.022 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:655(get_rows)&lt;br /&gt;
      500    1.561    0.003   11.191    0.022 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:292(read_text_result)&lt;br /&gt;
   504500    3.303    0.000    7.306    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/network.py:219(recv_plain)&lt;br /&gt;
   500000    3.572    0.000    6.625    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:359(row_to_python)&lt;br /&gt;
  1009000    3.155    0.000    3.155    0.000 {method 'recv_into' of '_socket.socket' objects}&lt;br /&gt;
   500000    2.049    0.000    2.329    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:220(read_lc_string_list)&lt;br /&gt;
   500000    0.653    0.000    2.116    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:531(_STRING_to_python)&lt;br /&gt;
   502000    0.378    0.000    1.329    0.000 {method 'decode' of 'bytearray' objects}&lt;br /&gt;
   502000    0.221    0.000    0.951    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/utf_8.py:15(decode)&lt;br /&gt;
   502000    0.730    0.000    0.730    0.000 {_codecs.utf_8_decode}&lt;br /&gt;
   500000    0.514    0.000    0.514    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:413(_INT_to_python)&lt;br /&gt;
   509500    0.510    0.000    0.510    0.000 {_struct.unpack_from}&lt;br /&gt;
     1500    0.009    0.000    0.492    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:452(execute)&lt;br /&gt;
     1500    0.004    0.000    0.437    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:705(cmd_query)&lt;br /&gt;
   500000    0.367    0.000    0.367    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:405(_FLOAT_to_python)&lt;br /&gt;
   504500    0.343    0.000    0.343    0.000 {method 'extend' of 'bytearray' objects}&lt;br /&gt;
     1500    0.007    0.000    0.315    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:481(_send_cmd)&lt;br /&gt;
  2501500    0.275    0.000    0.275    0.000 {method 'append' of 'list' objects}&lt;br /&gt;
   500500    0.246    0.000    0.246    0.000 {method 'startswith' of 'bytearray' objects}&lt;br /&gt;
   524002    0.150    0.000    0.150    0.000 {isinstance}&lt;br /&gt;
     1500    0.011    0.000    0.118    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:613(_handle_result)&lt;br /&gt;
  1004500    0.112    0.000    0.112    0.000 {len}&lt;br /&gt;
   500000    0.092    0.000    0.092    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:204(description)&lt;br /&gt;
     1500    0.007    0.000    0.042    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/network.py:118(send_plain)&lt;br /&gt;
     2000    0.013    0.000    0.041    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:226(parse_column)&lt;br /&gt;
     1000    0.008    0.000    0.034    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:346(_process_params_dict)&lt;br /&gt;
     1500    0.002    0.000    0.029    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/socket.py:223(meth)&lt;br /&gt;
     1500    0.026    0.000    0.026    0.000 {method 'sendall' of '_socket.socket' objects}&lt;br /&gt;
     5000    0.007    0.000    0.014    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/catch23.py:79(struct_unpack)&lt;br /&gt;
     1000    0.001    0.000    0.013    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:562(_handle_ok)&lt;br /&gt;
    12000    0.011    0.000    0.011    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:167(read_lc_string)&lt;br /&gt;
     1000    0.004    0.000    0.010    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:199(parse_ok)&lt;br /&gt;
     3000    0.005    0.000    0.009    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:152(to_mysql)&lt;br /&gt;
     1500    0.003    0.000    0.007    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:118(make_command)&lt;br /&gt;
     3000    0.003    0.000    0.007    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:130(quote)&lt;br /&gt;
     1500    0.005    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/network.py:53(_prepare_packets)&lt;br /&gt;
     1500    0.003    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:406(_handle_result)&lt;br /&gt;
     3000    0.003    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:102(escape)&lt;br /&gt;
     1000    0.002    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:252(parse_eof)&lt;br /&gt;
      500    0.001    0.000    0.005    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:579(_handle_eof)&lt;br /&gt;
     2000    0.003    0.000    0.005    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:545(_handle_server_status)&lt;br /&gt;
     1500    0.003    0.000    0.004    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:53(int1store)&lt;br /&gt;
     3000    0.003    0.000    0.003    0.000 {method 'encode' of 'str' objects}&lt;br /&gt;
     9001    0.003    0.000    0.003    0.000 {method 'replace' of 'str' objects}&lt;br /&gt;
     1500    0.003    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:295(_reset_result)&lt;br /&gt;
     4500    0.003    0.000    0.003    0.000 {_struct.pack}&lt;br /&gt;
     2500    0.003    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:296(read_lc_int)&lt;br /&gt;
      500    0.002    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:757(_handle_eof)&lt;br /&gt;
     6027    0.003    0.000    0.003    0.000 {method 'format' of 'str' objects}&lt;br /&gt;
     3000    0.002    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:1221(_set_unread_result)&lt;br /&gt;
      500    0.001    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:219(parse_column_count)&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 {repr}&lt;br /&gt;
     4527    0.002    0.000    0.002    0.000 {getattr}&lt;br /&gt;
     4000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/constants.py:34(flag_is_set)&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:383(_handle_noresultset)&lt;br /&gt;
     4000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:1233(_get_unread_result)&lt;br /&gt;
     2001    0.001    0.000    0.001    0.000 {method 'items' of 'dict' objects}&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:307(_have_unread_result)&lt;br /&gt;
     3000    0.001    0.000    0.001    0.000 {method 'lower' of 'str' objects}&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 {range}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:173(_str_to_mysql)&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:169(_float_to_mysql)&lt;br /&gt;
     1000    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:161(_int_to_mysql)&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:1313(_get_getwarnings)&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:200(reset)&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:397(_handle_resultset)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/__init__.py:71(search_function)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/ascii.py:41(getregentry)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/contextlib.py:21(__exit__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/__init__.py:49(normalize_encoding)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 test.py:8(profiled)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/codecs.py:77(__new__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {__import__}&lt;br /&gt;
        3    0.000    0.000    0.000    0.000 {method 'get' of 'dict' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'split' of 'str' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {built-in method __new__ of type object at 0x1001534e8}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'translate' of 'str' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'join' of 'str' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {hasattr}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DBAPI:  &amp;lt;module 'MySQLdb' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/__init__.pyc'&amp;gt;&lt;br /&gt;
         47503 function calls in 0.918 seconds&lt;br /&gt;
&lt;br /&gt;
   Ordered by: cumulative time&lt;br /&gt;
&lt;br /&gt;
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;
     1500    0.009    0.000    0.917    0.001 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:164(execute)&lt;br /&gt;
     1500    0.002    0.000    0.890    0.001 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:353(_query)&lt;br /&gt;
     1500    0.004    0.000    0.498    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:315(_do_query)&lt;br /&gt;
     1500    0.016    0.000    0.391    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:358(_post_get_result)&lt;br /&gt;
     1500    0.001    0.000    0.375    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:324(_fetch_row)&lt;br /&gt;
      500    0.374    0.001    0.374    0.001 {built-in method fetch_row}&lt;br /&gt;
     1500    0.007    0.000    0.362    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:142(_do_get_result)&lt;br /&gt;
     1500    0.002    0.000    0.351    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:351(_get_result)&lt;br /&gt;
     1500    0.349    0.000    0.349    0.000 {method 'store_result' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.131    0.000    0.131    0.000 {method 'query' of '_mysql.connection' objects}&lt;br /&gt;
     4000    0.002    0.000    0.010    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:184(&amp;lt;genexpr&amp;gt;)&lt;br /&gt;
     3000    0.002    0.000    0.008    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/connections.py:267(literal)&lt;br /&gt;
     1500    0.007    0.000    0.007    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:107(_warning_check)&lt;br /&gt;
     3000    0.002    0.000    0.006    0.000 {method 'escape' of '_mysql.connection' objects}&lt;br /&gt;
     6000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:159(_get_db)&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/converters.py:81(Float2Str)&lt;br /&gt;
     2500    0.001    0.000    0.001    0.000 {isinstance}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/connections.py:202(string_literal)&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:380(fetchall)&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 {built-in method describe}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 {method 'string_literal' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {method 'insert_id' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {method 'affected_rows' of '_mysql.connection' objects}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/converters.py:69(Thing2Str)&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 {method 'warning_count' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 {method 'info' of '_mysql.connection' objects}&lt;br /&gt;
     1000    0.000    0.000    0.000    0.000 {method 'iteritems' of 'dict' objects}&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:103(_check_executed)&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 {len}&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 {built-in method field_flags}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/contextlib.py:21(__exit__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 test.py:8(profiled)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DBAPI:  &amp;lt;module 'pymysql' from '/Users/classic/dev/PyMySQL/pymysql/__init__.pyc'&amp;gt;, total seconds 10.046012&lt;br /&gt;
DBAPI:  &amp;lt;module 'mysql.connector' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/__init__.pyc'&amp;gt;, total seconds 15.403260&lt;br /&gt;
DBAPI:  &amp;lt;module 'MySQLdb' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/__init__.pyc'&amp;gt;, total seconds 1.028737&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mike Bayer</name></author>	</entry>

	<entry>
		<id>https://wiki.openstack.org/w/index.php?title=PyMySQL_evaluation&amp;diff=72550</id>
		<title>PyMySQL evaluation</title>
		<link rel="alternate" type="text/html" href="https://wiki.openstack.org/w/index.php?title=PyMySQL_evaluation&amp;diff=72550"/>
				<updated>2015-01-28T20:11:47Z</updated>
		
		<summary type="html">&lt;p&gt;Mike Bayer: /* Library Documentation */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= PyMySQL Evaluation =&lt;br /&gt;
&lt;br /&gt;
This page will capture issues related to Openstack moving to the [https://github.com/PyMySQL/PyMySQL/ PyMySQL driver] for MySQL/MariaDB dbapi access.&lt;br /&gt;
&lt;br /&gt;
== Rationale ==&lt;br /&gt;
&lt;br /&gt;
While the [https://github.com/farcepest/MySQLdb1 MySQL-Python] driver is a very mature and stable driver, it does not provide compatibility with either Python 3 or with [http://eventlet.net/doc/patching.html eventlet monkeypatching] (MySQL-Python  [https://github.com/eventlet/eventlet/blob/v0.16.1/eventlet/patcher.py#L225-L237 can be monkeypatched] with eventlet, but this feature disabled by default and missed in documentation). So OpenStack's usage of MySQL-Python, combined with the fact that concurrency is provided by eventlet, means that we currently have fully serialized database access within a single process, that is, only one database command occurs at a time within an Openstack Python process.&lt;br /&gt;
&lt;br /&gt;
== Drivers Under Consideration ==&lt;br /&gt;
&lt;br /&gt;
The two drivers that are known to provide eventlet-monkeypatch compatibility are MySQL-Connector and PyMySQL, as they are written in pure Python.   The two other well-known drivers for MySQL are MySQL-Python and OurSQL, both of which are written in C and offer no explicit support for eventlet or async.  They can reportedly be built to support gevent using a system called [https://github.com/CMGS/greenify Greenify], but the maturity and/or stabiltiy of this system is unknown.&lt;br /&gt;
&lt;br /&gt;
A comparison at http://www.diamondtin.com/2014/sqlalchemy-gevent-mysql-python-drivers-comparison/ illustrates performance metrics observed with all four of these drivers, with MySQL-python built both without and with the &amp;quot;greenify&amp;quot; system.&lt;br /&gt;
&lt;br /&gt;
A summary of the status of all four drivers is as follows.&lt;br /&gt;
&lt;br /&gt;
=== MySQL-Python ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/farcepest/MySQLdb1 MySQL-Python] is the most widely used Python driver.  However, it does not support Python 3, and it does not support async systems unless used within a thread pool.   While a system known as Greenify can potentially resolve the latter situation, the Python 3 limitation is still a deal-breaker - pull request with Python 3 support [https://github.com/farcepest/MySQLdb1/pull/62 was proposed] to MySQL-python in April 2014 and still not merged.&lt;br /&gt;
&lt;br /&gt;
=== mysqlclient ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/PyMySQL/mysqlclient-python mysqlclient] is a fork of MySQL-python. It add Python 3.3 support and merges some pull requests.&lt;br /&gt;
&lt;br /&gt;
=== OurSQL ===&lt;br /&gt;
&lt;br /&gt;
[https://pythonhosted.org/oursql/ OurSQL] is an alternative MySQL driver that is also written in C.  It features fast performance and uses a different execution model than MySQL-python, using prepared statements.    OurSQL does have a Python 3 port hosted on [https://launchpad.net/oursql/py3k/ launchpad] but is not integrated with OurSQL itself nor is it published on Pypi.     Because it is written in C, it does not offer compatibility with async systems including eventlet-style monkeypatching.&lt;br /&gt;
&lt;br /&gt;
Besides the shaky Python 3 support and lack of eventlet support, OurSQL is also not maintained, seeing its last master commit on [https://code.launchpad.net/~jerub/oursql/master 2012-06-05].&lt;br /&gt;
&lt;br /&gt;
=== MySQL-Connector-Python ===&lt;br /&gt;
&lt;br /&gt;
[https://pypi.python.org/pypi/mysql-connector-python MySQL-connector-Python] is a pure Python MySQL driver, and is now released under the auspices of the MySQL project itself, as owned by Oracle.  MySQL-connector-Python supports both Python 3 as well as eventlet monkeypatching, and is well maintained.  It is endorsed by Oracle as the official Oracle-supported driver for MySQL, so to that extent, it is in most ways Openstack's first choice in driver.  However, Oracle refuses to publish MySQL-connector-Python on Pypi, which is critical to the Openstack infrastructure.  Repeated attempts to communicate with Oracle in order to resolve this issue have not made any progress.    Therefore, for this one unfortunate reason, MySQL-connector-Python will not have a place in the Openstack ecosystem unless this issue is resolved.&lt;br /&gt;
&lt;br /&gt;
=== PyMySQL ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/PyMySQL/PyMySQL PyMySQL] is a pure Python MySQL&lt;br /&gt;
driver, first written as a rough port of the MySQL-Python driver.&lt;br /&gt;
PyMySQL meets all of Openstack's criterion for a driver: it is fully&lt;br /&gt;
open source, hosted on Github, released on Pypi, is actively&lt;br /&gt;
maintained, is written in pure Python so is eventlet-monkeypatch&lt;br /&gt;
compatible, and is fully Python 3 compatible.&lt;br /&gt;
&lt;br /&gt;
As this document is named &amp;quot;PyMySQL Evaluation&amp;quot;, it should be apparent that&lt;br /&gt;
this is the driver Openstack is currently leaning towards.  However, it does&lt;br /&gt;
have some minor code quality issues which hopefully can be addressed in some&lt;br /&gt;
way; the section below titled &amp;quot;PyMySQL Code Review&amp;quot; will summarize the&lt;br /&gt;
current state of the code.  We are in fact leaning towards this driver because&lt;br /&gt;
its the only one that actually meets all criteria.  PyMySQL appears to be&lt;br /&gt;
fairly widely used, especially considering its low profile, so it's possible&lt;br /&gt;
that this is the case for many other users as well.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===MySQL DB Drivers Comparison===&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Project !! PyPi hosted !! Eventlet friendly !! Python 3 compatibility !! Maturity and/or stability  !! Comment&lt;br /&gt;
|-&lt;br /&gt;
| MySQL-Python || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: orange;&amp;quot; | Partial || style=&amp;quot;background-color: red;&amp;quot; | No  || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || Can be monkeypatched by eventlet, but only to enable thread pooling&lt;br /&gt;
|-&lt;br /&gt;
| mysqlclient || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: orange;&amp;quot; | Partial || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes  || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || MySQL-python fork, so should be similar to it&lt;br /&gt;
|-&lt;br /&gt;
| OurSQL || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: red;&amp;quot; | No || style=&amp;quot;background-color: orange;&amp;quot; | Yes, but not Pypi hosted  || style=&amp;quot;background-color: orange;&amp;quot; | ??? || Has not seen commits/releases in two years&lt;br /&gt;
|-&lt;br /&gt;
| MySQL-Connector-Python || style=&amp;quot;background-color: red;&amp;quot; | No || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes  || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || The official Oracle-supported driver for MySQL&lt;br /&gt;
|-&lt;br /&gt;
| PyMySQL || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes  || style=&amp;quot;background-color: orange;&amp;quot; | Yes, with a [http://lists.openstack.org/pipermail/openstack-dev/2014-December/052864.html proviso]. ||&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== PyMySQL Code Review ==&lt;br /&gt;
&lt;br /&gt;
PyMySQL started roughly as a pure Python port of MySQL-Python.   The sections below will&lt;br /&gt;
detail various aspects of the driver and the status of the code.   Overall, the general&lt;br /&gt;
tone of PyMySQL is one of code that was written in the spirit of pragmatism and immediate&lt;br /&gt;
need; it is straightforward, relatively free of serious antipatterns and performs&lt;br /&gt;
its task in a matter-of-fact way.  However,&lt;br /&gt;
it lacks polish and completeness in many areas that would normally be in better&lt;br /&gt;
condition for a more mature project.  These areas are all highly fixable, and it is&lt;br /&gt;
hoped that these items can be shared with the current PyMySQL developers, through&lt;br /&gt;
a combination of this document itself as well as new issues and pull requests&lt;br /&gt;
on the PyMySQL tracker, so that this fairly promising library can be pushed&lt;br /&gt;
to the next level.   This would require that the developers are amenable to&lt;br /&gt;
these improvements.&lt;br /&gt;
&lt;br /&gt;
=== Coding Style / Pep8 ===&lt;br /&gt;
&lt;br /&gt;
PyMySQL's coding style is pretty good.  The majority of it passes all&lt;br /&gt;
flake8 tests, save for a few dozen whitespace issues and&lt;br /&gt;
some long lines here and there within the core&lt;br /&gt;
modules, and moreso within the tests which appear to be a bit more&lt;br /&gt;
crufty than the core modules.  A full run of flake8 produces only 128&lt;br /&gt;
errors, and with a default run of autopep8 against E1,E2,E3, we can&lt;br /&gt;
fix all of them in one pass except for 38 remaining long line warnings.&lt;br /&gt;
The code layout is mostly idiomatic and reasonable.&lt;br /&gt;
&lt;br /&gt;
The codebase is probably short on docstrings.  Those methods that are&lt;br /&gt;
part of the public DBAPI do have docstrings, but tend to be extremely&lt;br /&gt;
terse (example, docstring for cursor.execute(): &amp;quot;'Execute a query'&amp;quot;).&lt;br /&gt;
Other methods that aren't public tend to not have any docstrings, but&lt;br /&gt;
the codebase is not at all hard to read and the purpose of methods and&lt;br /&gt;
functions is pretty easy to discern just by their names and implementations,&lt;br /&gt;
however those who seek inline documentation as a means to understand a codebase&lt;br /&gt;
will be disappointed.   The terseness of public API docstrings is more of&lt;br /&gt;
an issue in terms of the overall lack of documentation for PyMySQL,&lt;br /&gt;
see the section &amp;quot;Documentation&amp;quot; below.&lt;br /&gt;
&lt;br /&gt;
=== Test Coverage ===&lt;br /&gt;
&lt;br /&gt;
The test coverage for PyMySQL is definitely lacking.   From a visual&lt;br /&gt;
inspection alone, it's apparent that of the dozen or so test modules,&lt;br /&gt;
most of them have less then ten tests each and the modules are very short.&lt;br /&gt;
For a full test run, there's only 126 tests; contrast this to&lt;br /&gt;
MySQL-Connector-Python which has 531 tests.&lt;br /&gt;
&lt;br /&gt;
Additionally, all of the tests are live round-trip tests against a&lt;br /&gt;
real database.  There are no non-live unit tests of any kind,&lt;br /&gt;
leaving codepaths that are not easily exercised&lt;br /&gt;
on a generic MySQL database out in the cold.  This means that features designed&lt;br /&gt;
for particular versions of MySQL, particular datatypes, particular error&lt;br /&gt;
conditions, and particular kinds of message packets that aren't created for&lt;br /&gt;
real are simply not tested.&lt;br /&gt;
&lt;br /&gt;
The lack of tests is apparent when run with coverage.  The test suite itself&lt;br /&gt;
does not appear to have any signs that it is normally run with coverage&lt;br /&gt;
turned on, with no directives in tox.ini or similar.   With coverage,&lt;br /&gt;
the overall coverage is 85%, but the modules most lacking in coverage&lt;br /&gt;
are also the most critical to PyMySQL's core functionality,&lt;br /&gt;
connections.py, converters.py and cursors.py:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Name                                                              Stmts   Miss  Cover&lt;br /&gt;
-------------------------------------------------------------------------------------&lt;br /&gt;
pymysql/__init__                                                     51      3    94%&lt;br /&gt;
pymysql/_compat                                                      14      4    71%&lt;br /&gt;
pymysql/_socketio                                                    72     30    58%&lt;br /&gt;
pymysql/charset                                                     228      3    99%&lt;br /&gt;
pymysql/connections                                                 827    188    77%&lt;br /&gt;
pymysql/constants/CLIENT                                             18      0   100%&lt;br /&gt;
pymysql/constants/COMMAND                                            32      0   100%&lt;br /&gt;
pymysql/constants/ER                                                471      0   100%&lt;br /&gt;
pymysql/constants/FIELD_TYPE                                         29      0   100%&lt;br /&gt;
pymysql/constants/FLAG                                               15      0   100%&lt;br /&gt;
pymysql/constants/SERVER_STATUS                                      10      0   100%&lt;br /&gt;
pymysql/constants/__init__                                            0      0   100%&lt;br /&gt;
pymysql/converters                                                  148     51    66%&lt;br /&gt;
pymysql/cursors                                                     273     37    86%&lt;br /&gt;
pymysql/err                                                          40      1    98%&lt;br /&gt;
pymysql/tests/__init__                                               14      5    64%&lt;br /&gt;
pymysql/tests/base                                                   25      2    92%&lt;br /&gt;
pymysql/tests/test_DictCursor                                        73      2    97%&lt;br /&gt;
pymysql/tests/test_SSCursor                                          56      6    89%&lt;br /&gt;
pymysql/tests/test_basic                                            186      4    98%&lt;br /&gt;
pymysql/tests/test_connection                                        57      8    86%&lt;br /&gt;
pymysql/tests/test_example                                           17      2    88%&lt;br /&gt;
pymysql/tests/test_issues                                           300     48    84%&lt;br /&gt;
pymysql/tests/test_load_local                                        39      2    95%&lt;br /&gt;
pymysql/tests/test_nextset                                           43      4    91%&lt;br /&gt;
pymysql/tests/thirdparty/__init__                                     7      5    29%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/__init__                        6      2    67%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/capabilities                  196     23    88%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/dbapi20                       423    121    71%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_capabilities      73      6    92%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_dbapi20          100      2    98%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_nonstandard       57      3    95%&lt;br /&gt;
pymysql/times                                                        12      0   100%&lt;br /&gt;
pymysql/util                                                         14      7    50%&lt;br /&gt;
-------------------------------------------------------------------------------------&lt;br /&gt;
TOTAL                                                              3926    569    86%&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The lack of coverage in converters.py refers to functions related to handling&lt;br /&gt;
incoming bound values as well as outgoing result values.    Key data escaping&lt;br /&gt;
features such as escaping of dictionaries, sets, some unicode objects and&lt;br /&gt;
byte objects as well as many pathways into date and time-processing functions are not&lt;br /&gt;
covered.&lt;br /&gt;
&lt;br /&gt;
In connections.py, uncovered features include various SSL and&lt;br /&gt;
very legacy (e.g. version 3.23) MySQL features, but also some protocol parsing&lt;br /&gt;
features.&lt;br /&gt;
&lt;br /&gt;
In cursors.py, there is coverage for an elaborate and mostly undocumented&lt;br /&gt;
performance-related feature that rewrites an INSERT statement to have an&lt;br /&gt;
extended VALUES clause (_do_execute_many), however it fails to cover the very&lt;br /&gt;
likely case where the function will need to break the input set into&lt;br /&gt;
multiple chunks:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;gt;         for arg in args:&lt;br /&gt;
&amp;gt;             v = values % escape(arg, conn)&lt;br /&gt;
&amp;gt;             if isinstance(v, text_type):&lt;br /&gt;
&amp;gt;                 v = v.encode(encoding)&lt;br /&gt;
&amp;gt;             if len(sql) + len(v) + 1 &amp;gt; max_stmt_length:&lt;br /&gt;
!                 rows += self.execute(sql)&lt;br /&gt;
!                 sql = bytearray(prefix)&lt;br /&gt;
&amp;gt;             else:&lt;br /&gt;
&amp;gt;                 sql += b','&lt;br /&gt;
&amp;gt;             sql += v&lt;br /&gt;
&amp;gt;         rows += self.execute(sql)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The most prominent missing coverage in cursors.py seems to be&lt;br /&gt;
for its &amp;quot;scrollable cursor&amp;quot; support.&lt;br /&gt;
&lt;br /&gt;
Beyond the lack of completeness in testing, many of the tests themselves&lt;br /&gt;
are written in an expedient &amp;quot;one giant test&amp;quot; style where dozens of&lt;br /&gt;
assertions and individual behaviors are lumped into one big test&lt;br /&gt;
case (see test_SSCursor.py, test_basic.py-&amp;gt;test_datatypes for examples).&lt;br /&gt;
Test cases like these are difficult to work with when debugging&lt;br /&gt;
regressions as well as when tests for new features need to be added, and ideally&lt;br /&gt;
these should be broken out into clean single-feature tests with consistent&lt;br /&gt;
fixtures.&lt;br /&gt;
&lt;br /&gt;
There is also a suite called &amp;quot;test_issues&amp;quot;, which intends to&lt;br /&gt;
accumulate tests against specific issues that have been reported. This&lt;br /&gt;
isn't a bad idea, though it would be nice to see an effort made into&lt;br /&gt;
categorizing these tests into the actual features they are testing,&lt;br /&gt;
rather than a meaningless list of issue numbers, which could then form&lt;br /&gt;
as the basis for new suites centered around those areas of&lt;br /&gt;
functionality.  The issues reported should be used as the inspiration&lt;br /&gt;
for improvement of the test suite, rather than just another line-item&lt;br /&gt;
to be filed away.&lt;br /&gt;
&lt;br /&gt;
=== Library Documentation ===&lt;br /&gt;
&lt;br /&gt;
As the Python DBAPI is already a well-documented API, and the users of&lt;br /&gt;
PyMySQL are typically coming from MySQL-Python which is documented&lt;br /&gt;
to some degree and for which the use contract is widely known, it seems&lt;br /&gt;
unlikely that users of PyMySQL are eager for comprehensive documentation&lt;br /&gt;
specific to this library.&lt;br /&gt;
&lt;br /&gt;
That being said, PyMySQL does not appear to have any real library&lt;br /&gt;
documentation at all.  There's a README which&lt;br /&gt;
essentially refers to pep-249, an example.py in the root which shows a&lt;br /&gt;
very basic connection / round trip, and that's it. In&lt;br /&gt;
order to know anything about the API, specific parameters, optimization&lt;br /&gt;
strategies, behaviors, etc., one either has to go off of the MySQL-Python documentation and assume&lt;br /&gt;
PyMySQL also features the same parameter, or read the source code.&lt;br /&gt;
&lt;br /&gt;
PyMySQL probably hasn't had much urgent need for real documentation thus&lt;br /&gt;
far.  However, relying upon being a port of MySQL-Python is fast becoming&lt;br /&gt;
something that can no longer be relied upon, as MySQL-Python's development&lt;br /&gt;
is quite stalled, and PyMySQL should aspire to move into the future&lt;br /&gt;
of MySQL and Python with its own featureset and behaviors.  To that end, it would be a &lt;br /&gt;
great idea if it at least made the start&lt;br /&gt;
of a rudimentary Sphinx build including autodoc for module documentation&lt;br /&gt;
which could then be published up to RTD.   This would be very easy to get&lt;br /&gt;
started, and once present, new documentation sections can be added&lt;br /&gt;
iteratively.&lt;br /&gt;
&lt;br /&gt;
=== Architecture and Performance ===&lt;br /&gt;
&lt;br /&gt;
As stated before, PyMySQL has a really matter-of-fact and straightforward&lt;br /&gt;
implementation.   The code is very readable and it's easy to discern even&lt;br /&gt;
unusual features such as the INSERT..VALUES rewriting.  As far as the design,&lt;br /&gt;
there are lots of areas where performance suffers a bit at the Python level;&lt;br /&gt;
these areas could be greatly improved very easily with a little bit of&lt;br /&gt;
attention.&lt;br /&gt;
&lt;br /&gt;
As we are dealing with a driver that is communicating with a low level&lt;br /&gt;
protocol over a socket, it's critical that sending and receiving messages&lt;br /&gt;
is done as efficiently as possible.  The mechanisms used in PyMySQL rely&lt;br /&gt;
heavily on standard Python objects, without the use of __slots__; this means&lt;br /&gt;
that for individual messages on a socket, we are creating a heavyweight object,&lt;br /&gt;
calling its __init__() method and a creating a new __dict__ each time;&lt;br /&gt;
the most common object, OKPacketWrapper(), already wraps a MysqlPacket()&lt;br /&gt;
object, so we're doing two __init__()s in this common case.  OKPacketWrapper&lt;br /&gt;
also implements a `__getattr__` scheme&lt;br /&gt;
to proxy attribute access from itself to the internal MysqlPacket; this is a&lt;br /&gt;
an inefficient mode of operation in Python as it means a lookup must first&lt;br /&gt;
fail on the OKPacketWrapper before it invokes the `__getattr__()` method&lt;br /&gt;
and does another attribute lookup.   A reorg of the packet classes to produce&lt;br /&gt;
far fewer objects for messages as well as to use __slots__ would be&lt;br /&gt;
recommended.&lt;br /&gt;
&lt;br /&gt;
There are also lots of areas where function call overhead is unnecessarily&lt;br /&gt;
high due to the organization of calls.  For example, when calling upon&lt;br /&gt;
cursor.execute(), for parameters passed as a dictionary under Python 2K,&lt;br /&gt;
the isinstance() builtin function will be called on the incoming arguments&lt;br /&gt;
four times at a minimum, with two of each call against the exact same&lt;br /&gt;
criteria.  Additional logic will then call isinstance() on every key of&lt;br /&gt;
the dictionary as well as at least twice on every value within the dictionary,&lt;br /&gt;
in order to determine if the value needs to be converted to bytes as well&lt;br /&gt;
as for some top-level escaping logic checks; the escaping logic then&lt;br /&gt;
goes further with more checks for type() to match the object to a final&lt;br /&gt;
converter function.   While the flow of this logic does appear to be&lt;br /&gt;
reasonable and straightforward, it isn't ideal for a low-level database&lt;br /&gt;
driver library where speed is a high priority; the various wrappers and&lt;br /&gt;
processors can easily be reorganized here to reduce the number of isinstance()&lt;br /&gt;
and type() calls significantly, which would save a lot on function call&lt;br /&gt;
overhead.  It is likely there are many other areas in the codebase where&lt;br /&gt;
similar reductions in call counts can be made without too much disruption.&lt;br /&gt;
&lt;br /&gt;
In Appendix A, a short performance suite is illustrated.  This&lt;br /&gt;
is run against PyMySQL, Mysql-Connector-Python, and MySQL-Python.&lt;br /&gt;
PyMySQL does in fact demonstrate the&lt;br /&gt;
highest call-count behavior, logging 26,850,503 function calls&lt;br /&gt;
compared to MySQL-connector's 11,674,074 for the same operations.  To PyMySQL's&lt;br /&gt;
credit, it somehow completes the test a bit faster than MySQL-connector&lt;br /&gt;
despite having more than twice as many function calls; this probably indicates&lt;br /&gt;
the use of something very slow within MySQL-connector, such as catching&lt;br /&gt;
an exception within a tight loop.  However, the real bar we're comparing to&lt;br /&gt;
is MySQL-Python, which is written&lt;br /&gt;
in C and does the whole test with only 47,000 Python function calls; while&lt;br /&gt;
PyMySQL and MySQL-connector-Python compete for time within the 16-20 second&lt;br /&gt;
range, MySQL-Python completes the whole test in **0.9 seconds**, a 1600%&lt;br /&gt;
improvement over PyMySQL.   This speed difference is not PyMySQL's fault,&lt;br /&gt;
as pure Python is known to be extemely slow compared to C code.&lt;br /&gt;
However, there's a lot of easy wins in PyMySQL's codebase where the function&lt;br /&gt;
call count could probably be reduced dramatically to be even better than that&lt;br /&gt;
of MySQL-connector-Python; ideally, the driver would be able to run this&lt;br /&gt;
test possibly 5-6x slower than MySQL-Python rather than 16x.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== History Documentation ===&lt;br /&gt;
&lt;br /&gt;
PyMySQL includes a CHANGELOG file in its root which describes the bug fixes&lt;br /&gt;
and features of each release.  The construction of this file is definitely&lt;br /&gt;
within the &amp;quot;expedient&amp;quot; style noted for PyMySQL overall, and for it to be&lt;br /&gt;
genuinely useful for enterprise-level software, it would need to be&lt;br /&gt;
much more complete in its detail.    Each bug fix mentioned is only referred to&lt;br /&gt;
tersely without any link to a pull request or bug report.  While some can&lt;br /&gt;
be linked to a specific change through detective work&lt;br /&gt;
(&amp;quot;Cursor.fetchall() and .fetchmany now return list, not tuple&amp;quot;), it would&lt;br /&gt;
be very difficult for many&lt;br /&gt;
(&amp;quot;Fixed BIT type handling&amp;quot;, &amp;quot;Fixed GC errors&amp;quot; - what was the issue?)&lt;br /&gt;
and in many cases impossible (&amp;quot;Improved Py3k support&amp;quot;).  The file also&lt;br /&gt;
includes no dates so one has to look at the git tags to figure this&lt;br /&gt;
part out (fortunately, releases to seem to be git tagged at least).&lt;br /&gt;
For a user trying to ascertain if particular issues are&lt;br /&gt;
fixed or if behaviors have changed, this file is at best highly frustrating.&lt;br /&gt;
&lt;br /&gt;
A listing of release dates illustrates an average of two releases per year,&lt;br /&gt;
excluding 2012 when PyMySQL was seeking a new maintainer:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
2014-12-02 12:05:10 -0200  (tag: pymysql-0.6.3)&lt;br /&gt;
2014-04-21 14:28:47 -0300  (tag: pymysql-0.6.2)&lt;br /&gt;
2013-10-11 13:36:56 -0300  (tag: pymysql-0.6.1)&lt;br /&gt;
2013-10-04 14:25:14 -0300  (tag: pymysql-0.6)&lt;br /&gt;
2011-11-08 10:27:41 -0800  (tag: pymysql-0.5)&lt;br /&gt;
2010-12-27 17:28:16 +0000  (tag: pymysql-0.4)&lt;br /&gt;
2010-09-03 00:55:29 +0000  (tag: pymysql-0.3)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Community Involvement ===&lt;br /&gt;
&lt;br /&gt;
This is an area where PyMySQL seems to shine, and might even be an advantage&lt;br /&gt;
as compared to MySQL-Connector-Python, which remains under the cloak&lt;br /&gt;
of MySQL's vastly overstuffed bugtracker under the ownership of&lt;br /&gt;
the famously-opaque Oracle.   For a small, low key library without&lt;br /&gt;
any real documentation and only 126 tests, it features 33 contributors on&lt;br /&gt;
Github and is up to pull request #285, which is quite high.  Because&lt;br /&gt;
PyMySQL is small and straightforward, it does lend itself to contribution&lt;br /&gt;
and seems to do very well in this area.   The developers so far have been&lt;br /&gt;
responsive to issues and pull requests made by the author.&lt;br /&gt;
&lt;br /&gt;
Based on github contributor graphs it appears that PyMySQL had a lull&lt;br /&gt;
in 2012 but was picked up again afterwards in 2013.  As of 2012, its most&lt;br /&gt;
prominent developer Pete Hunt stopped development, and on July 25, 2013&lt;br /&gt;
added a note to the README that the project was looking for a new maintainer.&lt;br /&gt;
As of 2013 it was picked up by the current most prominent developers&lt;br /&gt;
Marcel Rodrigues (github username lecram) and INADA Naoki&lt;br /&gt;
(github username methane).&lt;br /&gt;
&lt;br /&gt;
Most issues that have been noted thus far within the areas of testing,&lt;br /&gt;
documentation and architecture are all very fixable; if the Openstack community&lt;br /&gt;
can provide resources to address some of these areas, and if the PyMySQL&lt;br /&gt;
developers are reasonably open to accepting new changes and producing new&lt;br /&gt;
releases in a timely fashion, the PyMySQL driver&lt;br /&gt;
could rapidly become a fully formidable contender in the MySQL driver field.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Appendix A - Performance Tests ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
import cProfile&lt;br /&gt;
import StringIO&lt;br /&gt;
import pstats&lt;br /&gt;
import contextlib&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextlib.contextmanager&lt;br /&gt;
def profiled(dbapi):&lt;br /&gt;
    pr = cProfile.Profile()&lt;br /&gt;
    pr.enable()&lt;br /&gt;
    yield&lt;br /&gt;
    pr.disable()&lt;br /&gt;
    s = StringIO.StringIO()&lt;br /&gt;
    ps = pstats.Stats(pr, stream=s).sort_stats('cumulative')&lt;br /&gt;
    print &amp;quot;DBAPI:  %s&amp;quot; % dbapi&lt;br /&gt;
    ps.print_stats()&lt;br /&gt;
    # uncomment this to see who's calling what&lt;br /&gt;
    # ps.print_callers()&lt;br /&gt;
    print &amp;quot;total calls: %s&amp;quot; % s.getvalue()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
import MySQLdb&lt;br /&gt;
import pymysql&lt;br /&gt;
from mysql import connector as mysqlconnector&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def go(dbapi):&lt;br /&gt;
    conn = dbapi.connect(&lt;br /&gt;
        user='scott', passwd='tiger', host='localhost', db='test')&lt;br /&gt;
    cursor = conn.cursor()&lt;br /&gt;
    cursor.execute(&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
CREATE TABLE IF NOT EXISTS test_things (&lt;br /&gt;
    x INTEGER,&lt;br /&gt;
    y VARCHAR(255),&lt;br /&gt;
    z FLOAT&lt;br /&gt;
) engine=InnoDB&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;)&lt;br /&gt;
    cursor.execute(&amp;quot;DELETE from test_things&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    with profiled(dbapi):&lt;br /&gt;
        for row in xrange(1000):&lt;br /&gt;
            cursor.execute(&lt;br /&gt;
                &amp;quot;INSERT INTO test_things (x, y, z) &amp;quot;&lt;br /&gt;
                &amp;quot;VALUES (%(x)s, %(y)s, %(z)s)&amp;quot;,&lt;br /&gt;
                {&amp;quot;x&amp;quot;: row, &amp;quot;y&amp;quot;: &amp;quot;row number %d&amp;quot; % row,&lt;br /&gt;
                 &amp;quot;z&amp;quot;: row * 4.57292, }&lt;br /&gt;
            )&lt;br /&gt;
&lt;br /&gt;
        for x in xrange(500):&lt;br /&gt;
            cursor.execute(&lt;br /&gt;
                &amp;quot;select * from test_things&amp;quot;)&lt;br /&gt;
            for row in cursor.fetchall():&lt;br /&gt;
                row[0], row[1], row[2]&lt;br /&gt;
&lt;br /&gt;
    cursor.close()&lt;br /&gt;
    conn.close()&lt;br /&gt;
&lt;br /&gt;
go(pymysql)&lt;br /&gt;
go(mysqlconnector)&lt;br /&gt;
go(MySQLdb)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DBAPI:  &amp;lt;module 'pymysql' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/__init__.pyc'&amp;gt;&lt;br /&gt;
total calls:          26850503 function calls in 16.017 seconds&lt;br /&gt;
&lt;br /&gt;
   Ordered by: cumulative time&lt;br /&gt;
&lt;br /&gt;
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;
     1500    0.010    0.000   16.016    0.011 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:107(execute)&lt;br /&gt;
     1500    0.003    0.000   15.976    0.011 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:271(_query)&lt;br /&gt;
     1500    0.003    0.000   15.952    0.011 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:708(query)&lt;br /&gt;
     1500    0.003    0.000   15.911    0.011 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:854(_read_query_result)&lt;br /&gt;
     1500    0.004    0.000   15.905    0.011 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:1060(read)&lt;br /&gt;
      500    0.003    0.000   15.658    0.031 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:1106(_read_result_packet)&lt;br /&gt;
      500    0.921    0.002   15.527    0.031 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:1139(_read_rowdata_packet)&lt;br /&gt;
   500000    3.404    0.000    9.210    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:1152(_read_row_from_packet)&lt;br /&gt;
  2012000    1.179    0.000    5.374    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:314(read_length_coded_string)&lt;br /&gt;
   504500    1.533    0.000    5.246    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:808(_read_packet)&lt;br /&gt;
  2014500    1.265    0.000    3.056    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:296(read_length_encoded_integer)&lt;br /&gt;
  3539500    2.469    0.000    2.782    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:248(read)&lt;br /&gt;
  1009000    0.795    0.000    2.300    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:833(_read_bytes)&lt;br /&gt;
  1009000    1.178    0.000    1.400    0.000 {method 'read' of '_io.BufferedReader' objects}&lt;br /&gt;
   508500    0.381    0.000    0.699    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/util.py:3(byte2int)&lt;br /&gt;
  5058000    0.468    0.000    0.468    0.000 {len}&lt;br /&gt;
   500500    0.219    0.000    0.453    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:1098(_check_packet_is_eof)&lt;br /&gt;
   504500    0.211    0.000    0.380    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:342(check_error)&lt;br /&gt;
  2504000    0.259    0.000    0.259    0.000 {method 'append' of 'list' objects}&lt;br /&gt;
   501500    0.182    0.000    0.231    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:329(is_eof_packet)&lt;br /&gt;
     3500    0.009    0.000    0.222    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/_socketio.py:45(readinto)&lt;br /&gt;
  1022000    0.205    0.000    0.205    0.000 {_struct.unpack}&lt;br /&gt;
     3500    0.203    0.000    0.203    0.000 {method 'recv_into' of '_socket.socket' objects}&lt;br /&gt;
   526000    0.198    0.000    0.198    0.000 {isinstance}&lt;br /&gt;
  1501000    0.197    0.000    0.197    0.000 {method 'get' of 'dict' objects}&lt;br /&gt;
   504500    0.173    0.000    0.173    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:241(__init__)&lt;br /&gt;
   504500    0.169    0.000    0.169    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:339(is_error_packet)&lt;br /&gt;
  2014500    0.162    0.000    0.162    0.000 {ord}&lt;br /&gt;
      500    0.006    0.000    0.127    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:1176(_get_descriptions)&lt;br /&gt;
     2000    0.003    0.000    0.088    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:361(__init__)&lt;br /&gt;
     2000    0.020    0.000    0.085    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:365(__parse_field_descriptor)&lt;br /&gt;
   500000    0.055    0.000    0.055    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/converters.py:301(through)&lt;br /&gt;
     1500    0.006    0.000    0.037    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:877(_execute_command)&lt;br /&gt;
     1500    0.003    0.000    0.027    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:848(_write_bytes)&lt;br /&gt;
     1500    0.002    0.000    0.024    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/socket.py:223(meth)&lt;br /&gt;
     1500    0.021    0.000    0.021    0.000 {method 'sendall' of '_socket.socket' objects}&lt;br /&gt;
     1500    0.020    0.000    0.021    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:278(_do_get_result)&lt;br /&gt;
     1000    0.003    0.000    0.019    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:97(_escape_args)&lt;br /&gt;
     4000    0.002    0.000    0.015    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:101(&amp;lt;genexpr&amp;gt;)&lt;br /&gt;
     1000    0.001    0.000    0.014    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:1089(_read_ok_packet)&lt;br /&gt;
     3000    0.003    0.000    0.013    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:674(escape)&lt;br /&gt;
     1000    0.005    0.000    0.012    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:416(__init__)&lt;br /&gt;
     8000    0.011    0.000    0.011    0.000 {method 'decode' of 'str' objects}&lt;br /&gt;
     3500    0.002    0.000    0.006    0.000 {method '_checkReadable' of '_io._IOBase' objects}&lt;br /&gt;
     4000    0.002    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:128(&amp;lt;genexpr&amp;gt;)&lt;br /&gt;
     2000    0.003    0.000    0.005    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/converters.py:19(escape_item)&lt;br /&gt;
     7500    0.003    0.000    0.005    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:117(ensure_bytes)&lt;br /&gt;
     2000    0.003    0.000    0.005    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:386(description)&lt;br /&gt;
     5500    0.004    0.000    0.004    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:271(advance)&lt;br /&gt;
      500    0.002    0.000    0.004    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:442(__init__)&lt;br /&gt;
     1000    0.001    0.000    0.004    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:684(escape_string)&lt;br /&gt;
     3500    0.004    0.000    0.004    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/_socketio.py:87(readable)&lt;br /&gt;
     3500    0.003    0.000    0.003    0.000 {method '_checkClosed' of '_io._IOBase' objects}&lt;br /&gt;
     1000    0.001    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/converters.py:59(escape_string)&lt;br /&gt;
     1500    0.003    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:1043(__init__)&lt;br /&gt;
     1500    0.001    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:94(nextset)&lt;br /&gt;
     4000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:397(get_column_length)&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/converters.py:56(escape_float)&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 {method 'sub' of '_sre.SRE_Pattern' objects}&lt;br /&gt;
     3000    0.002    0.000    0.002    0.000 {_struct.pack}&lt;br /&gt;
     6000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:64(_get_db)&lt;br /&gt;
     1500    0.001    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:82(_nextset)&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/util.py:9(int2byte)&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:246(fetchall)&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {min}&lt;br /&gt;
     2500    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:326(is_ok_packet)&lt;br /&gt;
     2000    0.001    0.000    0.001    0.000 {method 'items' of 'dict' objects}&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {getattr}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/converters.py:52(escape_int)&lt;br /&gt;
     1000    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:262(read_all)&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:1056(__del__)&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:69(_check_executed)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/contextlib.py:21(__exit__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 test.py:7(profiled)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
DBAPI:  &amp;lt;module 'mysql.connector' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/__init__.pyc'&amp;gt;&lt;br /&gt;
total calls:          11674074 function calls in 18.316 seconds&lt;br /&gt;
&lt;br /&gt;
   Ordered by: cumulative time&lt;br /&gt;
&lt;br /&gt;
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;
      500    0.611    0.001   17.825    0.036 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:820(fetchall)&lt;br /&gt;
      500    0.002    0.000   10.829    0.022 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:655(get_rows)&lt;br /&gt;
      500    1.505    0.003   10.825    0.022 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:292(read_text_result)&lt;br /&gt;
   504500    3.208    0.000    7.065    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/network.py:219(recv_plain)&lt;br /&gt;
   500000    3.398    0.000    6.297    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:359(row_to_python)&lt;br /&gt;
  1009000    3.039    0.000    3.039    0.000 {method 'recv_into' of '_socket.socket' objects}&lt;br /&gt;
   500000    2.003    0.000    2.283    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:220(read_lc_string_list)&lt;br /&gt;
   500000    0.625    0.000    2.010    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:531(_STRING_to_python)&lt;br /&gt;
   502000    0.355    0.000    1.254    0.000 {method 'decode' of 'bytearray' objects}&lt;br /&gt;
   502000    0.212    0.000    0.899    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/utf_8.py:15(decode)&lt;br /&gt;
   502000    0.688    0.000    0.688    0.000 {_codecs.utf_8_decode}&lt;br /&gt;
   509500    0.498    0.000    0.498    0.000 {_struct.unpack_from}&lt;br /&gt;
     1500    0.009    0.000    0.490    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:452(execute)&lt;br /&gt;
   500000    0.482    0.000    0.482    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:413(_INT_to_python)&lt;br /&gt;
     1500    0.004    0.000    0.436    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:705(cmd_query)&lt;br /&gt;
   500000    0.352    0.000    0.352    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:405(_FLOAT_to_python)&lt;br /&gt;
   504500    0.326    0.000    0.326    0.000 {method 'extend' of 'bytearray' objects}&lt;br /&gt;
     1500    0.006    0.000    0.318    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:481(_send_cmd)&lt;br /&gt;
  2501500    0.273    0.000    0.273    0.000 {method 'append' of 'list' objects}&lt;br /&gt;
   500500    0.240    0.000    0.240    0.000 {method 'startswith' of 'bytearray' objects}&lt;br /&gt;
   524002    0.143    0.000    0.143    0.000 {isinstance}&lt;br /&gt;
     1500    0.010    0.000    0.114    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:613(_handle_result)&lt;br /&gt;
  1004500    0.109    0.000    0.109    0.000 {len}&lt;br /&gt;
   500000    0.086    0.000    0.086    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:204(description)&lt;br /&gt;
     2000    0.013    0.000    0.038    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:226(parse_column)&lt;br /&gt;
     1000    0.008    0.000    0.034    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:346(_process_params_dict)&lt;br /&gt;
     1500    0.006    0.000    0.034    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/network.py:118(send_plain)&lt;br /&gt;
     1500    0.002    0.000    0.023    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/socket.py:223(meth)&lt;br /&gt;
     1500    0.020    0.000    0.020    0.000 {method 'sendall' of '_socket.socket' objects}&lt;br /&gt;
     5000    0.007    0.000    0.014    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/catch23.py:79(struct_unpack)&lt;br /&gt;
     1000    0.001    0.000    0.014    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:562(_handle_ok)&lt;br /&gt;
    12000    0.011    0.000    0.011    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:167(read_lc_string)&lt;br /&gt;
     1000    0.004    0.000    0.011    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:199(parse_ok)&lt;br /&gt;
     3000    0.005    0.000    0.009    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:152(to_mysql)&lt;br /&gt;
     3000    0.003    0.000    0.007    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:130(quote)&lt;br /&gt;
     1500    0.002    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:118(make_command)&lt;br /&gt;
     1500    0.004    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:406(_handle_result)&lt;br /&gt;
     3000    0.003    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:102(escape)&lt;br /&gt;
     1500    0.004    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/network.py:53(_prepare_packets)&lt;br /&gt;
     1000    0.002    0.000    0.005    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:252(parse_eof)&lt;br /&gt;
      500    0.001    0.000    0.005    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:579(_handle_eof)&lt;br /&gt;
     2000    0.003    0.000    0.004    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:545(_handle_server_status)&lt;br /&gt;
     1500    0.002    0.000    0.004    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:53(int1store)&lt;br /&gt;
     3000    0.003    0.000    0.003    0.000 {method 'encode' of 'str' objects}&lt;br /&gt;
     9001    0.003    0.000    0.003    0.000 {method 'replace' of 'str' objects}&lt;br /&gt;
     1500    0.003    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:295(_reset_result)&lt;br /&gt;
     6027    0.003    0.000    0.003    0.000 {method 'format' of 'str' objects}&lt;br /&gt;
     2500    0.003    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:296(read_lc_int)&lt;br /&gt;
     4500    0.002    0.000    0.002    0.000 {_struct.pack}&lt;br /&gt;
     3000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:1221(_set_unread_result)&lt;br /&gt;
      500    0.001    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:219(parse_column_count)&lt;br /&gt;
      500    0.001    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:757(_handle_eof)&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 {repr}&lt;br /&gt;
     4527    0.001    0.000    0.001    0.000 {getattr}&lt;br /&gt;
     4000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/constants.py:34(flag_is_set)&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:383(_handle_noresultset)&lt;br /&gt;
     4000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:1233(_get_unread_result)&lt;br /&gt;
     2001    0.001    0.000    0.001    0.000 {method 'items' of 'dict' objects}&lt;br /&gt;
     3000    0.001    0.000    0.001    0.000 {method 'lower' of 'str' objects}&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:307(_have_unread_result)&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:173(_str_to_mysql)&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 {range}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:169(_float_to_mysql)&lt;br /&gt;
     1000    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:161(_int_to_mysql)&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:1313(_get_getwarnings)&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:200(reset)&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:397(_handle_resultset)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/__init__.py:71(search_function)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/ascii.py:41(getregentry)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/contextlib.py:21(__exit__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/__init__.py:49(normalize_encoding)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/codecs.py:77(__new__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {__import__}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 test.py:7(profiled)&lt;br /&gt;
        3    0.000    0.000    0.000    0.000 {method 'get' of 'dict' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'split' of 'str' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'translate' of 'str' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'join' of 'str' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {built-in method __new__ of type object at 0x1001534e8}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {hasattr}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DBAPI:  &amp;lt;module 'MySQLdb' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/__init__.pyc'&amp;gt;&lt;br /&gt;
total calls:          47503 function calls in 0.916 seconds&lt;br /&gt;
&lt;br /&gt;
   Ordered by: cumulative time&lt;br /&gt;
&lt;br /&gt;
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;
     1500    0.008    0.000    0.915    0.001 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:164(execute)&lt;br /&gt;
     1500    0.002    0.000    0.888    0.001 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:353(_query)&lt;br /&gt;
     1500    0.004    0.000    0.498    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:315(_do_query)&lt;br /&gt;
     1500    0.016    0.000    0.388    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:358(_post_get_result)&lt;br /&gt;
     1500    0.001    0.000    0.372    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:324(_fetch_row)&lt;br /&gt;
      500    0.371    0.001    0.371    0.001 {built-in method fetch_row}&lt;br /&gt;
     1500    0.008    0.000    0.359    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:142(_do_get_result)&lt;br /&gt;
     1500    0.002    0.000    0.348    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:351(_get_result)&lt;br /&gt;
     1500    0.346    0.000    0.346    0.000 {method 'store_result' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.134    0.000    0.134    0.000 {method 'query' of '_mysql.connection' objects}&lt;br /&gt;
     4000    0.002    0.000    0.010    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:184(&amp;lt;genexpr&amp;gt;)&lt;br /&gt;
     3000    0.002    0.000    0.008    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/connections.py:267(literal)&lt;br /&gt;
     1500    0.007    0.000    0.007    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:107(_warning_check)&lt;br /&gt;
     3000    0.002    0.000    0.006    0.000 {method 'escape' of '_mysql.connection' objects}&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/converters.py:81(Float2Str)&lt;br /&gt;
     6000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:159(_get_db)&lt;br /&gt;
     2500    0.002    0.000    0.002    0.000 {isinstance}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/connections.py:202(string_literal)&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:380(fetchall)&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 {built-in method describe}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 {method 'string_literal' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {method 'affected_rows' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {method 'insert_id' of '_mysql.connection' objects}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/converters.py:69(Thing2Str)&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 {method 'info' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 {method 'warning_count' of '_mysql.connection' objects}&lt;br /&gt;
     1000    0.000    0.000    0.000    0.000 {method 'iteritems' of 'dict' objects}&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:103(_check_executed)&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 {len}&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 {built-in method field_flags}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/contextlib.py:21(__exit__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 test.py:7(profiled)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mike Bayer</name></author>	</entry>

	<entry>
		<id>https://wiki.openstack.org/w/index.php?title=PyMySQL_evaluation&amp;diff=72549</id>
		<title>PyMySQL evaluation</title>
		<link rel="alternate" type="text/html" href="https://wiki.openstack.org/w/index.php?title=PyMySQL_evaluation&amp;diff=72549"/>
				<updated>2015-01-28T20:08:39Z</updated>
		
		<summary type="html">&lt;p&gt;Mike Bayer: /* Test Coverage */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= PyMySQL Evaluation =&lt;br /&gt;
&lt;br /&gt;
This page will capture issues related to Openstack moving to the [https://github.com/PyMySQL/PyMySQL/ PyMySQL driver] for MySQL/MariaDB dbapi access.&lt;br /&gt;
&lt;br /&gt;
== Rationale ==&lt;br /&gt;
&lt;br /&gt;
While the [https://github.com/farcepest/MySQLdb1 MySQL-Python] driver is a very mature and stable driver, it does not provide compatibility with either Python 3 or with [http://eventlet.net/doc/patching.html eventlet monkeypatching] (MySQL-Python  [https://github.com/eventlet/eventlet/blob/v0.16.1/eventlet/patcher.py#L225-L237 can be monkeypatched] with eventlet, but this feature disabled by default and missed in documentation). So OpenStack's usage of MySQL-Python, combined with the fact that concurrency is provided by eventlet, means that we currently have fully serialized database access within a single process, that is, only one database command occurs at a time within an Openstack Python process.&lt;br /&gt;
&lt;br /&gt;
== Drivers Under Consideration ==&lt;br /&gt;
&lt;br /&gt;
The two drivers that are known to provide eventlet-monkeypatch compatibility are MySQL-Connector and PyMySQL, as they are written in pure Python.   The two other well-known drivers for MySQL are MySQL-Python and OurSQL, both of which are written in C and offer no explicit support for eventlet or async.  They can reportedly be built to support gevent using a system called [https://github.com/CMGS/greenify Greenify], but the maturity and/or stabiltiy of this system is unknown.&lt;br /&gt;
&lt;br /&gt;
A comparison at http://www.diamondtin.com/2014/sqlalchemy-gevent-mysql-python-drivers-comparison/ illustrates performance metrics observed with all four of these drivers, with MySQL-python built both without and with the &amp;quot;greenify&amp;quot; system.&lt;br /&gt;
&lt;br /&gt;
A summary of the status of all four drivers is as follows.&lt;br /&gt;
&lt;br /&gt;
=== MySQL-Python ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/farcepest/MySQLdb1 MySQL-Python] is the most widely used Python driver.  However, it does not support Python 3, and it does not support async systems unless used within a thread pool.   While a system known as Greenify can potentially resolve the latter situation, the Python 3 limitation is still a deal-breaker - pull request with Python 3 support [https://github.com/farcepest/MySQLdb1/pull/62 was proposed] to MySQL-python in April 2014 and still not merged.&lt;br /&gt;
&lt;br /&gt;
=== mysqlclient ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/PyMySQL/mysqlclient-python mysqlclient] is a fork of MySQL-python. It add Python 3.3 support and merges some pull requests.&lt;br /&gt;
&lt;br /&gt;
=== OurSQL ===&lt;br /&gt;
&lt;br /&gt;
[https://pythonhosted.org/oursql/ OurSQL] is an alternative MySQL driver that is also written in C.  It features fast performance and uses a different execution model than MySQL-python, using prepared statements.    OurSQL does have a Python 3 port hosted on [https://launchpad.net/oursql/py3k/ launchpad] but is not integrated with OurSQL itself nor is it published on Pypi.     Because it is written in C, it does not offer compatibility with async systems including eventlet-style monkeypatching.&lt;br /&gt;
&lt;br /&gt;
Besides the shaky Python 3 support and lack of eventlet support, OurSQL is also not maintained, seeing its last master commit on [https://code.launchpad.net/~jerub/oursql/master 2012-06-05].&lt;br /&gt;
&lt;br /&gt;
=== MySQL-Connector-Python ===&lt;br /&gt;
&lt;br /&gt;
[https://pypi.python.org/pypi/mysql-connector-python MySQL-connector-Python] is a pure Python MySQL driver, and is now released under the auspices of the MySQL project itself, as owned by Oracle.  MySQL-connector-Python supports both Python 3 as well as eventlet monkeypatching, and is well maintained.  It is endorsed by Oracle as the official Oracle-supported driver for MySQL, so to that extent, it is in most ways Openstack's first choice in driver.  However, Oracle refuses to publish MySQL-connector-Python on Pypi, which is critical to the Openstack infrastructure.  Repeated attempts to communicate with Oracle in order to resolve this issue have not made any progress.    Therefore, for this one unfortunate reason, MySQL-connector-Python will not have a place in the Openstack ecosystem unless this issue is resolved.&lt;br /&gt;
&lt;br /&gt;
=== PyMySQL ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/PyMySQL/PyMySQL PyMySQL] is a pure Python MySQL&lt;br /&gt;
driver, first written as a rough port of the MySQL-Python driver.&lt;br /&gt;
PyMySQL meets all of Openstack's criterion for a driver: it is fully&lt;br /&gt;
open source, hosted on Github, released on Pypi, is actively&lt;br /&gt;
maintained, is written in pure Python so is eventlet-monkeypatch&lt;br /&gt;
compatible, and is fully Python 3 compatible.&lt;br /&gt;
&lt;br /&gt;
As this document is named &amp;quot;PyMySQL Evaluation&amp;quot;, it should be apparent that&lt;br /&gt;
this is the driver Openstack is currently leaning towards.  However, it does&lt;br /&gt;
have some minor code quality issues which hopefully can be addressed in some&lt;br /&gt;
way; the section below titled &amp;quot;PyMySQL Code Review&amp;quot; will summarize the&lt;br /&gt;
current state of the code.  We are in fact leaning towards this driver because&lt;br /&gt;
its the only one that actually meets all criteria.  PyMySQL appears to be&lt;br /&gt;
fairly widely used, especially considering its low profile, so it's possible&lt;br /&gt;
that this is the case for many other users as well.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===MySQL DB Drivers Comparison===&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Project !! PyPi hosted !! Eventlet friendly !! Python 3 compatibility !! Maturity and/or stability  !! Comment&lt;br /&gt;
|-&lt;br /&gt;
| MySQL-Python || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: orange;&amp;quot; | Partial || style=&amp;quot;background-color: red;&amp;quot; | No  || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || Can be monkeypatched by eventlet, but only to enable thread pooling&lt;br /&gt;
|-&lt;br /&gt;
| mysqlclient || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: orange;&amp;quot; | Partial || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes  || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || MySQL-python fork, so should be similar to it&lt;br /&gt;
|-&lt;br /&gt;
| OurSQL || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: red;&amp;quot; | No || style=&amp;quot;background-color: orange;&amp;quot; | Yes, but not Pypi hosted  || style=&amp;quot;background-color: orange;&amp;quot; | ??? || Has not seen commits/releases in two years&lt;br /&gt;
|-&lt;br /&gt;
| MySQL-Connector-Python || style=&amp;quot;background-color: red;&amp;quot; | No || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes  || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || The official Oracle-supported driver for MySQL&lt;br /&gt;
|-&lt;br /&gt;
| PyMySQL || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes  || style=&amp;quot;background-color: orange;&amp;quot; | Yes, with a [http://lists.openstack.org/pipermail/openstack-dev/2014-December/052864.html proviso]. ||&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== PyMySQL Code Review ==&lt;br /&gt;
&lt;br /&gt;
PyMySQL started roughly as a pure Python port of MySQL-Python.   The sections below will&lt;br /&gt;
detail various aspects of the driver and the status of the code.   Overall, the general&lt;br /&gt;
tone of PyMySQL is one of code that was written in the spirit of pragmatism and immediate&lt;br /&gt;
need; it is straightforward, relatively free of serious antipatterns and performs&lt;br /&gt;
its task in a matter-of-fact way.  However,&lt;br /&gt;
it lacks polish and completeness in many areas that would normally be in better&lt;br /&gt;
condition for a more mature project.  These areas are all highly fixable, and it is&lt;br /&gt;
hoped that these items can be shared with the current PyMySQL developers, through&lt;br /&gt;
a combination of this document itself as well as new issues and pull requests&lt;br /&gt;
on the PyMySQL tracker, so that this fairly promising library can be pushed&lt;br /&gt;
to the next level.   This would require that the developers are amenable to&lt;br /&gt;
these improvements.&lt;br /&gt;
&lt;br /&gt;
=== Coding Style / Pep8 ===&lt;br /&gt;
&lt;br /&gt;
PyMySQL's coding style is pretty good.  The majority of it passes all&lt;br /&gt;
flake8 tests, save for a few dozen whitespace issues and&lt;br /&gt;
some long lines here and there within the core&lt;br /&gt;
modules, and moreso within the tests which appear to be a bit more&lt;br /&gt;
crufty than the core modules.  A full run of flake8 produces only 128&lt;br /&gt;
errors, and with a default run of autopep8 against E1,E2,E3, we can&lt;br /&gt;
fix all of them in one pass except for 38 remaining long line warnings.&lt;br /&gt;
The code layout is mostly idiomatic and reasonable.&lt;br /&gt;
&lt;br /&gt;
The codebase is probably short on docstrings.  Those methods that are&lt;br /&gt;
part of the public DBAPI do have docstrings, but tend to be extremely&lt;br /&gt;
terse (example, docstring for cursor.execute(): &amp;quot;'Execute a query'&amp;quot;).&lt;br /&gt;
Other methods that aren't public tend to not have any docstrings, but&lt;br /&gt;
the codebase is not at all hard to read and the purpose of methods and&lt;br /&gt;
functions is pretty easy to discern just by their names and implementations,&lt;br /&gt;
however those who seek inline documentation as a means to understand a codebase&lt;br /&gt;
will be disappointed.   The terseness of public API docstrings is more of&lt;br /&gt;
an issue in terms of the overall lack of documentation for PyMySQL,&lt;br /&gt;
see the section &amp;quot;Documentation&amp;quot; below.&lt;br /&gt;
&lt;br /&gt;
=== Test Coverage ===&lt;br /&gt;
&lt;br /&gt;
The test coverage for PyMySQL is definitely lacking.   From a visual&lt;br /&gt;
inspection alone, it's apparent that of the dozen or so test modules,&lt;br /&gt;
most of them have less then ten tests each and the modules are very short.&lt;br /&gt;
For a full test run, there's only 126 tests; contrast this to&lt;br /&gt;
MySQL-Connector-Python which has 531 tests.&lt;br /&gt;
&lt;br /&gt;
Additionally, all of the tests are live round-trip tests against a&lt;br /&gt;
real database.  There are no non-live unit tests of any kind,&lt;br /&gt;
leaving codepaths that are not easily exercised&lt;br /&gt;
on a generic MySQL database out in the cold.  This means that features designed&lt;br /&gt;
for particular versions of MySQL, particular datatypes, particular error&lt;br /&gt;
conditions, and particular kinds of message packets that aren't created for&lt;br /&gt;
real are simply not tested.&lt;br /&gt;
&lt;br /&gt;
The lack of tests is apparent when run with coverage.  The test suite itself&lt;br /&gt;
does not appear to have any signs that it is normally run with coverage&lt;br /&gt;
turned on, with no directives in tox.ini or similar.   With coverage,&lt;br /&gt;
the overall coverage is 85%, but the modules most lacking in coverage&lt;br /&gt;
are also the most critical to PyMySQL's core functionality,&lt;br /&gt;
connections.py, converters.py and cursors.py:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Name                                                              Stmts   Miss  Cover&lt;br /&gt;
-------------------------------------------------------------------------------------&lt;br /&gt;
pymysql/__init__                                                     51      3    94%&lt;br /&gt;
pymysql/_compat                                                      14      4    71%&lt;br /&gt;
pymysql/_socketio                                                    72     30    58%&lt;br /&gt;
pymysql/charset                                                     228      3    99%&lt;br /&gt;
pymysql/connections                                                 827    188    77%&lt;br /&gt;
pymysql/constants/CLIENT                                             18      0   100%&lt;br /&gt;
pymysql/constants/COMMAND                                            32      0   100%&lt;br /&gt;
pymysql/constants/ER                                                471      0   100%&lt;br /&gt;
pymysql/constants/FIELD_TYPE                                         29      0   100%&lt;br /&gt;
pymysql/constants/FLAG                                               15      0   100%&lt;br /&gt;
pymysql/constants/SERVER_STATUS                                      10      0   100%&lt;br /&gt;
pymysql/constants/__init__                                            0      0   100%&lt;br /&gt;
pymysql/converters                                                  148     51    66%&lt;br /&gt;
pymysql/cursors                                                     273     37    86%&lt;br /&gt;
pymysql/err                                                          40      1    98%&lt;br /&gt;
pymysql/tests/__init__                                               14      5    64%&lt;br /&gt;
pymysql/tests/base                                                   25      2    92%&lt;br /&gt;
pymysql/tests/test_DictCursor                                        73      2    97%&lt;br /&gt;
pymysql/tests/test_SSCursor                                          56      6    89%&lt;br /&gt;
pymysql/tests/test_basic                                            186      4    98%&lt;br /&gt;
pymysql/tests/test_connection                                        57      8    86%&lt;br /&gt;
pymysql/tests/test_example                                           17      2    88%&lt;br /&gt;
pymysql/tests/test_issues                                           300     48    84%&lt;br /&gt;
pymysql/tests/test_load_local                                        39      2    95%&lt;br /&gt;
pymysql/tests/test_nextset                                           43      4    91%&lt;br /&gt;
pymysql/tests/thirdparty/__init__                                     7      5    29%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/__init__                        6      2    67%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/capabilities                  196     23    88%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/dbapi20                       423    121    71%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_capabilities      73      6    92%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_dbapi20          100      2    98%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_nonstandard       57      3    95%&lt;br /&gt;
pymysql/times                                                        12      0   100%&lt;br /&gt;
pymysql/util                                                         14      7    50%&lt;br /&gt;
-------------------------------------------------------------------------------------&lt;br /&gt;
TOTAL                                                              3926    569    86%&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The lack of coverage in converters.py refers to functions related to handling&lt;br /&gt;
incoming bound values as well as outgoing result values.    Key data escaping&lt;br /&gt;
features such as escaping of dictionaries, sets, some unicode objects and&lt;br /&gt;
byte objects as well as many pathways into date and time-processing functions are not&lt;br /&gt;
covered.&lt;br /&gt;
&lt;br /&gt;
In connections.py, uncovered features include various SSL and&lt;br /&gt;
very legacy (e.g. version 3.23) MySQL features, but also some protocol parsing&lt;br /&gt;
features.&lt;br /&gt;
&lt;br /&gt;
In cursors.py, there is coverage for an elaborate and mostly undocumented&lt;br /&gt;
performance-related feature that rewrites an INSERT statement to have an&lt;br /&gt;
extended VALUES clause (_do_execute_many), however it fails to cover the very&lt;br /&gt;
likely case where the function will need to break the input set into&lt;br /&gt;
multiple chunks:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;gt;         for arg in args:&lt;br /&gt;
&amp;gt;             v = values % escape(arg, conn)&lt;br /&gt;
&amp;gt;             if isinstance(v, text_type):&lt;br /&gt;
&amp;gt;                 v = v.encode(encoding)&lt;br /&gt;
&amp;gt;             if len(sql) + len(v) + 1 &amp;gt; max_stmt_length:&lt;br /&gt;
!                 rows += self.execute(sql)&lt;br /&gt;
!                 sql = bytearray(prefix)&lt;br /&gt;
&amp;gt;             else:&lt;br /&gt;
&amp;gt;                 sql += b','&lt;br /&gt;
&amp;gt;             sql += v&lt;br /&gt;
&amp;gt;         rows += self.execute(sql)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The most prominent missing coverage in cursors.py seems to be&lt;br /&gt;
for its &amp;quot;scrollable cursor&amp;quot; support.&lt;br /&gt;
&lt;br /&gt;
Beyond the lack of completeness in testing, many of the tests themselves&lt;br /&gt;
are written in an expedient &amp;quot;one giant test&amp;quot; style where dozens of&lt;br /&gt;
assertions and individual behaviors are lumped into one big test&lt;br /&gt;
case (see test_SSCursor.py, test_basic.py-&amp;gt;test_datatypes for examples).&lt;br /&gt;
Test cases like these are difficult to work with when debugging&lt;br /&gt;
regressions as well as when tests for new features need to be added, and ideally&lt;br /&gt;
these should be broken out into clean single-feature tests with consistent&lt;br /&gt;
fixtures.&lt;br /&gt;
&lt;br /&gt;
There is also a suite called &amp;quot;test_issues&amp;quot;, which intends to&lt;br /&gt;
accumulate tests against specific issues that have been reported. This&lt;br /&gt;
isn't a bad idea, though it would be nice to see an effort made into&lt;br /&gt;
categorizing these tests into the actual features they are testing,&lt;br /&gt;
rather than a meaningless list of issue numbers, which could then form&lt;br /&gt;
as the basis for new suites centered around those areas of&lt;br /&gt;
functionality.  The issues reported should be used as the inspiration&lt;br /&gt;
for improvement of the test suite, rather than just another line-item&lt;br /&gt;
to be filed away.&lt;br /&gt;
&lt;br /&gt;
=== Library Documentation ===&lt;br /&gt;
&lt;br /&gt;
As the Python DBAPI is already a well-documented API, and the users of&lt;br /&gt;
PyMySQL are typically coming from MySQL-Python which is documented&lt;br /&gt;
to some degree and for which the use contract is widely known, it seems&lt;br /&gt;
unlikely that users of PyMySQL are eager for comprehensive documentation&lt;br /&gt;
specific to this library.&lt;br /&gt;
&lt;br /&gt;
That being said, as far as can be discerned, PyMySQL has absolutely no&lt;br /&gt;
library documentation of any kind.  There's a README which&lt;br /&gt;
essentially refers to pep-249, an example.py in the root which shows a&lt;br /&gt;
very basic connection / round trip, and that is absolutely it. In&lt;br /&gt;
order to know anything about the API, specific parameters, etc., one&lt;br /&gt;
either has to go off of the MySQL-Python documentation and assume&lt;br /&gt;
PyMySQL also features the same parameter, or read the source code.&lt;br /&gt;
&lt;br /&gt;
PyMySQL probably hasn't had much urgent need for real documentation thus&lt;br /&gt;
far, however it would be a great idea if it at least made the start&lt;br /&gt;
of a rudimentary Sphinx build including autodoc for module documentation&lt;br /&gt;
which could then be published up to RTD.   This would be very easy to get&lt;br /&gt;
started, and once present, new documentation sections can be added&lt;br /&gt;
iteratively.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Architecture and Performance ===&lt;br /&gt;
&lt;br /&gt;
As stated before, PyMySQL has a really matter-of-fact and straightforward&lt;br /&gt;
implementation.   The code is very readable and it's easy to discern even&lt;br /&gt;
unusual features such as the INSERT..VALUES rewriting.  As far as the design,&lt;br /&gt;
there are lots of areas where performance suffers a bit at the Python level;&lt;br /&gt;
these areas could be greatly improved very easily with a little bit of&lt;br /&gt;
attention.&lt;br /&gt;
&lt;br /&gt;
As we are dealing with a driver that is communicating with a low level&lt;br /&gt;
protocol over a socket, it's critical that sending and receiving messages&lt;br /&gt;
is done as efficiently as possible.  The mechanisms used in PyMySQL rely&lt;br /&gt;
heavily on standard Python objects, without the use of __slots__; this means&lt;br /&gt;
that for individual messages on a socket, we are creating a heavyweight object,&lt;br /&gt;
calling its __init__() method and a creating a new __dict__ each time;&lt;br /&gt;
the most common object, OKPacketWrapper(), already wraps a MysqlPacket()&lt;br /&gt;
object, so we're doing two __init__()s in this common case.  OKPacketWrapper&lt;br /&gt;
also implements a `__getattr__` scheme&lt;br /&gt;
to proxy attribute access from itself to the internal MysqlPacket; this is a&lt;br /&gt;
an inefficient mode of operation in Python as it means a lookup must first&lt;br /&gt;
fail on the OKPacketWrapper before it invokes the `__getattr__()` method&lt;br /&gt;
and does another attribute lookup.   A reorg of the packet classes to produce&lt;br /&gt;
far fewer objects for messages as well as to use __slots__ would be&lt;br /&gt;
recommended.&lt;br /&gt;
&lt;br /&gt;
There are also lots of areas where function call overhead is unnecessarily&lt;br /&gt;
high due to the organization of calls.  For example, when calling upon&lt;br /&gt;
cursor.execute(), for parameters passed as a dictionary under Python 2K,&lt;br /&gt;
the isinstance() builtin function will be called on the incoming arguments&lt;br /&gt;
four times at a minimum, with two of each call against the exact same&lt;br /&gt;
criteria.  Additional logic will then call isinstance() on every key of&lt;br /&gt;
the dictionary as well as at least twice on every value within the dictionary,&lt;br /&gt;
in order to determine if the value needs to be converted to bytes as well&lt;br /&gt;
as for some top-level escaping logic checks; the escaping logic then&lt;br /&gt;
goes further with more checks for type() to match the object to a final&lt;br /&gt;
converter function.   While the flow of this logic does appear to be&lt;br /&gt;
reasonable and straightforward, it isn't ideal for a low-level database&lt;br /&gt;
driver library where speed is a high priority; the various wrappers and&lt;br /&gt;
processors can easily be reorganized here to reduce the number of isinstance()&lt;br /&gt;
and type() calls significantly, which would save a lot on function call&lt;br /&gt;
overhead.  It is likely there are many other areas in the codebase where&lt;br /&gt;
similar reductions in call counts can be made without too much disruption.&lt;br /&gt;
&lt;br /&gt;
In Appendix A, a short performance suite is illustrated.  This&lt;br /&gt;
is run against PyMySQL, Mysql-Connector-Python, and MySQL-Python.&lt;br /&gt;
PyMySQL does in fact demonstrate the&lt;br /&gt;
highest call-count behavior, logging 26,850,503 function calls&lt;br /&gt;
compared to MySQL-connector's 11,674,074 for the same operations.  To PyMySQL's&lt;br /&gt;
credit, it somehow completes the test a bit faster than MySQL-connector&lt;br /&gt;
despite having more than twice as many function calls; this probably indicates&lt;br /&gt;
the use of something very slow within MySQL-connector, such as catching&lt;br /&gt;
an exception within a tight loop.  However, the real bar we're comparing to&lt;br /&gt;
is MySQL-Python, which is written&lt;br /&gt;
in C and does the whole test with only 47,000 Python function calls; while&lt;br /&gt;
PyMySQL and MySQL-connector-Python compete for time within the 16-20 second&lt;br /&gt;
range, MySQL-Python completes the whole test in **0.9 seconds**, a 1600%&lt;br /&gt;
improvement over PyMySQL.   This speed difference is not PyMySQL's fault,&lt;br /&gt;
as pure Python is known to be extemely slow compared to C code.&lt;br /&gt;
However, there's a lot of easy wins in PyMySQL's codebase where the function&lt;br /&gt;
call count could probably be reduced dramatically to be even better than that&lt;br /&gt;
of MySQL-connector-Python; ideally, the driver would be able to run this&lt;br /&gt;
test possibly 5-6x slower than MySQL-Python rather than 16x.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== History Documentation ===&lt;br /&gt;
&lt;br /&gt;
PyMySQL includes a CHANGELOG file in its root which describes the bug fixes&lt;br /&gt;
and features of each release.  The construction of this file is definitely&lt;br /&gt;
within the &amp;quot;expedient&amp;quot; style noted for PyMySQL overall, and for it to be&lt;br /&gt;
genuinely useful for enterprise-level software, it would need to be&lt;br /&gt;
much more complete in its detail.    Each bug fix mentioned is only referred to&lt;br /&gt;
tersely without any link to a pull request or bug report.  While some can&lt;br /&gt;
be linked to a specific change through detective work&lt;br /&gt;
(&amp;quot;Cursor.fetchall() and .fetchmany now return list, not tuple&amp;quot;), it would&lt;br /&gt;
be very difficult for many&lt;br /&gt;
(&amp;quot;Fixed BIT type handling&amp;quot;, &amp;quot;Fixed GC errors&amp;quot; - what was the issue?)&lt;br /&gt;
and in many cases impossible (&amp;quot;Improved Py3k support&amp;quot;).  The file also&lt;br /&gt;
includes no dates so one has to look at the git tags to figure this&lt;br /&gt;
part out (fortunately, releases to seem to be git tagged at least).&lt;br /&gt;
For a user trying to ascertain if particular issues are&lt;br /&gt;
fixed or if behaviors have changed, this file is at best highly frustrating.&lt;br /&gt;
&lt;br /&gt;
A listing of release dates illustrates an average of two releases per year,&lt;br /&gt;
excluding 2012 when PyMySQL was seeking a new maintainer:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
2014-12-02 12:05:10 -0200  (tag: pymysql-0.6.3)&lt;br /&gt;
2014-04-21 14:28:47 -0300  (tag: pymysql-0.6.2)&lt;br /&gt;
2013-10-11 13:36:56 -0300  (tag: pymysql-0.6.1)&lt;br /&gt;
2013-10-04 14:25:14 -0300  (tag: pymysql-0.6)&lt;br /&gt;
2011-11-08 10:27:41 -0800  (tag: pymysql-0.5)&lt;br /&gt;
2010-12-27 17:28:16 +0000  (tag: pymysql-0.4)&lt;br /&gt;
2010-09-03 00:55:29 +0000  (tag: pymysql-0.3)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Community Involvement ===&lt;br /&gt;
&lt;br /&gt;
This is an area where PyMySQL seems to shine, and might even be an advantage&lt;br /&gt;
as compared to MySQL-Connector-Python, which remains under the cloak&lt;br /&gt;
of MySQL's vastly overstuffed bugtracker under the ownership of&lt;br /&gt;
the famously-opaque Oracle.   For a small, low key library without&lt;br /&gt;
any real documentation and only 126 tests, it features 33 contributors on&lt;br /&gt;
Github and is up to pull request #285, which is quite high.  Because&lt;br /&gt;
PyMySQL is small and straightforward, it does lend itself to contribution&lt;br /&gt;
and seems to do very well in this area.   The developers so far have been&lt;br /&gt;
responsive to issues and pull requests made by the author.&lt;br /&gt;
&lt;br /&gt;
Based on github contributor graphs it appears that PyMySQL had a lull&lt;br /&gt;
in 2012 but was picked up again afterwards in 2013.  As of 2012, its most&lt;br /&gt;
prominent developer Pete Hunt stopped development, and on July 25, 2013&lt;br /&gt;
added a note to the README that the project was looking for a new maintainer.&lt;br /&gt;
As of 2013 it was picked up by the current most prominent developers&lt;br /&gt;
Marcel Rodrigues (github username lecram) and INADA Naoki&lt;br /&gt;
(github username methane).&lt;br /&gt;
&lt;br /&gt;
Most issues that have been noted thus far within the areas of testing,&lt;br /&gt;
documentation and architecture are all very fixable; if the Openstack community&lt;br /&gt;
can provide resources to address some of these areas, and if the PyMySQL&lt;br /&gt;
developers are reasonably open to accepting new changes and producing new&lt;br /&gt;
releases in a timely fashion, the PyMySQL driver&lt;br /&gt;
could rapidly become a fully formidable contender in the MySQL driver field.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Appendix A - Performance Tests ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
import cProfile&lt;br /&gt;
import StringIO&lt;br /&gt;
import pstats&lt;br /&gt;
import contextlib&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextlib.contextmanager&lt;br /&gt;
def profiled(dbapi):&lt;br /&gt;
    pr = cProfile.Profile()&lt;br /&gt;
    pr.enable()&lt;br /&gt;
    yield&lt;br /&gt;
    pr.disable()&lt;br /&gt;
    s = StringIO.StringIO()&lt;br /&gt;
    ps = pstats.Stats(pr, stream=s).sort_stats('cumulative')&lt;br /&gt;
    print &amp;quot;DBAPI:  %s&amp;quot; % dbapi&lt;br /&gt;
    ps.print_stats()&lt;br /&gt;
    # uncomment this to see who's calling what&lt;br /&gt;
    # ps.print_callers()&lt;br /&gt;
    print &amp;quot;total calls: %s&amp;quot; % s.getvalue()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
import MySQLdb&lt;br /&gt;
import pymysql&lt;br /&gt;
from mysql import connector as mysqlconnector&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def go(dbapi):&lt;br /&gt;
    conn = dbapi.connect(&lt;br /&gt;
        user='scott', passwd='tiger', host='localhost', db='test')&lt;br /&gt;
    cursor = conn.cursor()&lt;br /&gt;
    cursor.execute(&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
CREATE TABLE IF NOT EXISTS test_things (&lt;br /&gt;
    x INTEGER,&lt;br /&gt;
    y VARCHAR(255),&lt;br /&gt;
    z FLOAT&lt;br /&gt;
) engine=InnoDB&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;)&lt;br /&gt;
    cursor.execute(&amp;quot;DELETE from test_things&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    with profiled(dbapi):&lt;br /&gt;
        for row in xrange(1000):&lt;br /&gt;
            cursor.execute(&lt;br /&gt;
                &amp;quot;INSERT INTO test_things (x, y, z) &amp;quot;&lt;br /&gt;
                &amp;quot;VALUES (%(x)s, %(y)s, %(z)s)&amp;quot;,&lt;br /&gt;
                {&amp;quot;x&amp;quot;: row, &amp;quot;y&amp;quot;: &amp;quot;row number %d&amp;quot; % row,&lt;br /&gt;
                 &amp;quot;z&amp;quot;: row * 4.57292, }&lt;br /&gt;
            )&lt;br /&gt;
&lt;br /&gt;
        for x in xrange(500):&lt;br /&gt;
            cursor.execute(&lt;br /&gt;
                &amp;quot;select * from test_things&amp;quot;)&lt;br /&gt;
            for row in cursor.fetchall():&lt;br /&gt;
                row[0], row[1], row[2]&lt;br /&gt;
&lt;br /&gt;
    cursor.close()&lt;br /&gt;
    conn.close()&lt;br /&gt;
&lt;br /&gt;
go(pymysql)&lt;br /&gt;
go(mysqlconnector)&lt;br /&gt;
go(MySQLdb)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DBAPI:  &amp;lt;module 'pymysql' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/__init__.pyc'&amp;gt;&lt;br /&gt;
total calls:          26850503 function calls in 16.017 seconds&lt;br /&gt;
&lt;br /&gt;
   Ordered by: cumulative time&lt;br /&gt;
&lt;br /&gt;
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;
     1500    0.010    0.000   16.016    0.011 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:107(execute)&lt;br /&gt;
     1500    0.003    0.000   15.976    0.011 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:271(_query)&lt;br /&gt;
     1500    0.003    0.000   15.952    0.011 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:708(query)&lt;br /&gt;
     1500    0.003    0.000   15.911    0.011 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:854(_read_query_result)&lt;br /&gt;
     1500    0.004    0.000   15.905    0.011 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:1060(read)&lt;br /&gt;
      500    0.003    0.000   15.658    0.031 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:1106(_read_result_packet)&lt;br /&gt;
      500    0.921    0.002   15.527    0.031 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:1139(_read_rowdata_packet)&lt;br /&gt;
   500000    3.404    0.000    9.210    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:1152(_read_row_from_packet)&lt;br /&gt;
  2012000    1.179    0.000    5.374    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:314(read_length_coded_string)&lt;br /&gt;
   504500    1.533    0.000    5.246    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:808(_read_packet)&lt;br /&gt;
  2014500    1.265    0.000    3.056    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:296(read_length_encoded_integer)&lt;br /&gt;
  3539500    2.469    0.000    2.782    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:248(read)&lt;br /&gt;
  1009000    0.795    0.000    2.300    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:833(_read_bytes)&lt;br /&gt;
  1009000    1.178    0.000    1.400    0.000 {method 'read' of '_io.BufferedReader' objects}&lt;br /&gt;
   508500    0.381    0.000    0.699    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/util.py:3(byte2int)&lt;br /&gt;
  5058000    0.468    0.000    0.468    0.000 {len}&lt;br /&gt;
   500500    0.219    0.000    0.453    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:1098(_check_packet_is_eof)&lt;br /&gt;
   504500    0.211    0.000    0.380    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:342(check_error)&lt;br /&gt;
  2504000    0.259    0.000    0.259    0.000 {method 'append' of 'list' objects}&lt;br /&gt;
   501500    0.182    0.000    0.231    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:329(is_eof_packet)&lt;br /&gt;
     3500    0.009    0.000    0.222    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/_socketio.py:45(readinto)&lt;br /&gt;
  1022000    0.205    0.000    0.205    0.000 {_struct.unpack}&lt;br /&gt;
     3500    0.203    0.000    0.203    0.000 {method 'recv_into' of '_socket.socket' objects}&lt;br /&gt;
   526000    0.198    0.000    0.198    0.000 {isinstance}&lt;br /&gt;
  1501000    0.197    0.000    0.197    0.000 {method 'get' of 'dict' objects}&lt;br /&gt;
   504500    0.173    0.000    0.173    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:241(__init__)&lt;br /&gt;
   504500    0.169    0.000    0.169    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:339(is_error_packet)&lt;br /&gt;
  2014500    0.162    0.000    0.162    0.000 {ord}&lt;br /&gt;
      500    0.006    0.000    0.127    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:1176(_get_descriptions)&lt;br /&gt;
     2000    0.003    0.000    0.088    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:361(__init__)&lt;br /&gt;
     2000    0.020    0.000    0.085    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:365(__parse_field_descriptor)&lt;br /&gt;
   500000    0.055    0.000    0.055    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/converters.py:301(through)&lt;br /&gt;
     1500    0.006    0.000    0.037    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:877(_execute_command)&lt;br /&gt;
     1500    0.003    0.000    0.027    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:848(_write_bytes)&lt;br /&gt;
     1500    0.002    0.000    0.024    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/socket.py:223(meth)&lt;br /&gt;
     1500    0.021    0.000    0.021    0.000 {method 'sendall' of '_socket.socket' objects}&lt;br /&gt;
     1500    0.020    0.000    0.021    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:278(_do_get_result)&lt;br /&gt;
     1000    0.003    0.000    0.019    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:97(_escape_args)&lt;br /&gt;
     4000    0.002    0.000    0.015    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:101(&amp;lt;genexpr&amp;gt;)&lt;br /&gt;
     1000    0.001    0.000    0.014    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:1089(_read_ok_packet)&lt;br /&gt;
     3000    0.003    0.000    0.013    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:674(escape)&lt;br /&gt;
     1000    0.005    0.000    0.012    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:416(__init__)&lt;br /&gt;
     8000    0.011    0.000    0.011    0.000 {method 'decode' of 'str' objects}&lt;br /&gt;
     3500    0.002    0.000    0.006    0.000 {method '_checkReadable' of '_io._IOBase' objects}&lt;br /&gt;
     4000    0.002    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:128(&amp;lt;genexpr&amp;gt;)&lt;br /&gt;
     2000    0.003    0.000    0.005    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/converters.py:19(escape_item)&lt;br /&gt;
     7500    0.003    0.000    0.005    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:117(ensure_bytes)&lt;br /&gt;
     2000    0.003    0.000    0.005    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:386(description)&lt;br /&gt;
     5500    0.004    0.000    0.004    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:271(advance)&lt;br /&gt;
      500    0.002    0.000    0.004    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:442(__init__)&lt;br /&gt;
     1000    0.001    0.000    0.004    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:684(escape_string)&lt;br /&gt;
     3500    0.004    0.000    0.004    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/_socketio.py:87(readable)&lt;br /&gt;
     3500    0.003    0.000    0.003    0.000 {method '_checkClosed' of '_io._IOBase' objects}&lt;br /&gt;
     1000    0.001    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/converters.py:59(escape_string)&lt;br /&gt;
     1500    0.003    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:1043(__init__)&lt;br /&gt;
     1500    0.001    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:94(nextset)&lt;br /&gt;
     4000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:397(get_column_length)&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/converters.py:56(escape_float)&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 {method 'sub' of '_sre.SRE_Pattern' objects}&lt;br /&gt;
     3000    0.002    0.000    0.002    0.000 {_struct.pack}&lt;br /&gt;
     6000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:64(_get_db)&lt;br /&gt;
     1500    0.001    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:82(_nextset)&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/util.py:9(int2byte)&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:246(fetchall)&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {min}&lt;br /&gt;
     2500    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:326(is_ok_packet)&lt;br /&gt;
     2000    0.001    0.000    0.001    0.000 {method 'items' of 'dict' objects}&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {getattr}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/converters.py:52(escape_int)&lt;br /&gt;
     1000    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:262(read_all)&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:1056(__del__)&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:69(_check_executed)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/contextlib.py:21(__exit__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 test.py:7(profiled)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
DBAPI:  &amp;lt;module 'mysql.connector' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/__init__.pyc'&amp;gt;&lt;br /&gt;
total calls:          11674074 function calls in 18.316 seconds&lt;br /&gt;
&lt;br /&gt;
   Ordered by: cumulative time&lt;br /&gt;
&lt;br /&gt;
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;
      500    0.611    0.001   17.825    0.036 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:820(fetchall)&lt;br /&gt;
      500    0.002    0.000   10.829    0.022 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:655(get_rows)&lt;br /&gt;
      500    1.505    0.003   10.825    0.022 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:292(read_text_result)&lt;br /&gt;
   504500    3.208    0.000    7.065    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/network.py:219(recv_plain)&lt;br /&gt;
   500000    3.398    0.000    6.297    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:359(row_to_python)&lt;br /&gt;
  1009000    3.039    0.000    3.039    0.000 {method 'recv_into' of '_socket.socket' objects}&lt;br /&gt;
   500000    2.003    0.000    2.283    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:220(read_lc_string_list)&lt;br /&gt;
   500000    0.625    0.000    2.010    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:531(_STRING_to_python)&lt;br /&gt;
   502000    0.355    0.000    1.254    0.000 {method 'decode' of 'bytearray' objects}&lt;br /&gt;
   502000    0.212    0.000    0.899    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/utf_8.py:15(decode)&lt;br /&gt;
   502000    0.688    0.000    0.688    0.000 {_codecs.utf_8_decode}&lt;br /&gt;
   509500    0.498    0.000    0.498    0.000 {_struct.unpack_from}&lt;br /&gt;
     1500    0.009    0.000    0.490    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:452(execute)&lt;br /&gt;
   500000    0.482    0.000    0.482    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:413(_INT_to_python)&lt;br /&gt;
     1500    0.004    0.000    0.436    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:705(cmd_query)&lt;br /&gt;
   500000    0.352    0.000    0.352    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:405(_FLOAT_to_python)&lt;br /&gt;
   504500    0.326    0.000    0.326    0.000 {method 'extend' of 'bytearray' objects}&lt;br /&gt;
     1500    0.006    0.000    0.318    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:481(_send_cmd)&lt;br /&gt;
  2501500    0.273    0.000    0.273    0.000 {method 'append' of 'list' objects}&lt;br /&gt;
   500500    0.240    0.000    0.240    0.000 {method 'startswith' of 'bytearray' objects}&lt;br /&gt;
   524002    0.143    0.000    0.143    0.000 {isinstance}&lt;br /&gt;
     1500    0.010    0.000    0.114    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:613(_handle_result)&lt;br /&gt;
  1004500    0.109    0.000    0.109    0.000 {len}&lt;br /&gt;
   500000    0.086    0.000    0.086    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:204(description)&lt;br /&gt;
     2000    0.013    0.000    0.038    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:226(parse_column)&lt;br /&gt;
     1000    0.008    0.000    0.034    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:346(_process_params_dict)&lt;br /&gt;
     1500    0.006    0.000    0.034    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/network.py:118(send_plain)&lt;br /&gt;
     1500    0.002    0.000    0.023    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/socket.py:223(meth)&lt;br /&gt;
     1500    0.020    0.000    0.020    0.000 {method 'sendall' of '_socket.socket' objects}&lt;br /&gt;
     5000    0.007    0.000    0.014    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/catch23.py:79(struct_unpack)&lt;br /&gt;
     1000    0.001    0.000    0.014    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:562(_handle_ok)&lt;br /&gt;
    12000    0.011    0.000    0.011    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:167(read_lc_string)&lt;br /&gt;
     1000    0.004    0.000    0.011    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:199(parse_ok)&lt;br /&gt;
     3000    0.005    0.000    0.009    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:152(to_mysql)&lt;br /&gt;
     3000    0.003    0.000    0.007    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:130(quote)&lt;br /&gt;
     1500    0.002    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:118(make_command)&lt;br /&gt;
     1500    0.004    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:406(_handle_result)&lt;br /&gt;
     3000    0.003    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:102(escape)&lt;br /&gt;
     1500    0.004    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/network.py:53(_prepare_packets)&lt;br /&gt;
     1000    0.002    0.000    0.005    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:252(parse_eof)&lt;br /&gt;
      500    0.001    0.000    0.005    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:579(_handle_eof)&lt;br /&gt;
     2000    0.003    0.000    0.004    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:545(_handle_server_status)&lt;br /&gt;
     1500    0.002    0.000    0.004    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:53(int1store)&lt;br /&gt;
     3000    0.003    0.000    0.003    0.000 {method 'encode' of 'str' objects}&lt;br /&gt;
     9001    0.003    0.000    0.003    0.000 {method 'replace' of 'str' objects}&lt;br /&gt;
     1500    0.003    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:295(_reset_result)&lt;br /&gt;
     6027    0.003    0.000    0.003    0.000 {method 'format' of 'str' objects}&lt;br /&gt;
     2500    0.003    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:296(read_lc_int)&lt;br /&gt;
     4500    0.002    0.000    0.002    0.000 {_struct.pack}&lt;br /&gt;
     3000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:1221(_set_unread_result)&lt;br /&gt;
      500    0.001    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:219(parse_column_count)&lt;br /&gt;
      500    0.001    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:757(_handle_eof)&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 {repr}&lt;br /&gt;
     4527    0.001    0.000    0.001    0.000 {getattr}&lt;br /&gt;
     4000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/constants.py:34(flag_is_set)&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:383(_handle_noresultset)&lt;br /&gt;
     4000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:1233(_get_unread_result)&lt;br /&gt;
     2001    0.001    0.000    0.001    0.000 {method 'items' of 'dict' objects}&lt;br /&gt;
     3000    0.001    0.000    0.001    0.000 {method 'lower' of 'str' objects}&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:307(_have_unread_result)&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:173(_str_to_mysql)&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 {range}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:169(_float_to_mysql)&lt;br /&gt;
     1000    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:161(_int_to_mysql)&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:1313(_get_getwarnings)&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:200(reset)&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:397(_handle_resultset)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/__init__.py:71(search_function)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/ascii.py:41(getregentry)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/contextlib.py:21(__exit__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/__init__.py:49(normalize_encoding)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/codecs.py:77(__new__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {__import__}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 test.py:7(profiled)&lt;br /&gt;
        3    0.000    0.000    0.000    0.000 {method 'get' of 'dict' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'split' of 'str' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'translate' of 'str' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'join' of 'str' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {built-in method __new__ of type object at 0x1001534e8}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {hasattr}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DBAPI:  &amp;lt;module 'MySQLdb' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/__init__.pyc'&amp;gt;&lt;br /&gt;
total calls:          47503 function calls in 0.916 seconds&lt;br /&gt;
&lt;br /&gt;
   Ordered by: cumulative time&lt;br /&gt;
&lt;br /&gt;
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;
     1500    0.008    0.000    0.915    0.001 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:164(execute)&lt;br /&gt;
     1500    0.002    0.000    0.888    0.001 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:353(_query)&lt;br /&gt;
     1500    0.004    0.000    0.498    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:315(_do_query)&lt;br /&gt;
     1500    0.016    0.000    0.388    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:358(_post_get_result)&lt;br /&gt;
     1500    0.001    0.000    0.372    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:324(_fetch_row)&lt;br /&gt;
      500    0.371    0.001    0.371    0.001 {built-in method fetch_row}&lt;br /&gt;
     1500    0.008    0.000    0.359    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:142(_do_get_result)&lt;br /&gt;
     1500    0.002    0.000    0.348    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:351(_get_result)&lt;br /&gt;
     1500    0.346    0.000    0.346    0.000 {method 'store_result' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.134    0.000    0.134    0.000 {method 'query' of '_mysql.connection' objects}&lt;br /&gt;
     4000    0.002    0.000    0.010    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:184(&amp;lt;genexpr&amp;gt;)&lt;br /&gt;
     3000    0.002    0.000    0.008    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/connections.py:267(literal)&lt;br /&gt;
     1500    0.007    0.000    0.007    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:107(_warning_check)&lt;br /&gt;
     3000    0.002    0.000    0.006    0.000 {method 'escape' of '_mysql.connection' objects}&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/converters.py:81(Float2Str)&lt;br /&gt;
     6000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:159(_get_db)&lt;br /&gt;
     2500    0.002    0.000    0.002    0.000 {isinstance}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/connections.py:202(string_literal)&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:380(fetchall)&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 {built-in method describe}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 {method 'string_literal' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {method 'affected_rows' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {method 'insert_id' of '_mysql.connection' objects}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/converters.py:69(Thing2Str)&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 {method 'info' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 {method 'warning_count' of '_mysql.connection' objects}&lt;br /&gt;
     1000    0.000    0.000    0.000    0.000 {method 'iteritems' of 'dict' objects}&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:103(_check_executed)&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 {len}&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 {built-in method field_flags}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/contextlib.py:21(__exit__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 test.py:7(profiled)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mike Bayer</name></author>	</entry>

	<entry>
		<id>https://wiki.openstack.org/w/index.php?title=PyMySQL_evaluation&amp;diff=72547</id>
		<title>PyMySQL evaluation</title>
		<link rel="alternate" type="text/html" href="https://wiki.openstack.org/w/index.php?title=PyMySQL_evaluation&amp;diff=72547"/>
				<updated>2015-01-28T20:07:17Z</updated>
		
		<summary type="html">&lt;p&gt;Mike Bayer: /* Coding Style / Pep8 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= PyMySQL Evaluation =&lt;br /&gt;
&lt;br /&gt;
This page will capture issues related to Openstack moving to the [https://github.com/PyMySQL/PyMySQL/ PyMySQL driver] for MySQL/MariaDB dbapi access.&lt;br /&gt;
&lt;br /&gt;
== Rationale ==&lt;br /&gt;
&lt;br /&gt;
While the [https://github.com/farcepest/MySQLdb1 MySQL-Python] driver is a very mature and stable driver, it does not provide compatibility with either Python 3 or with [http://eventlet.net/doc/patching.html eventlet monkeypatching] (MySQL-Python  [https://github.com/eventlet/eventlet/blob/v0.16.1/eventlet/patcher.py#L225-L237 can be monkeypatched] with eventlet, but this feature disabled by default and missed in documentation). So OpenStack's usage of MySQL-Python, combined with the fact that concurrency is provided by eventlet, means that we currently have fully serialized database access within a single process, that is, only one database command occurs at a time within an Openstack Python process.&lt;br /&gt;
&lt;br /&gt;
== Drivers Under Consideration ==&lt;br /&gt;
&lt;br /&gt;
The two drivers that are known to provide eventlet-monkeypatch compatibility are MySQL-Connector and PyMySQL, as they are written in pure Python.   The two other well-known drivers for MySQL are MySQL-Python and OurSQL, both of which are written in C and offer no explicit support for eventlet or async.  They can reportedly be built to support gevent using a system called [https://github.com/CMGS/greenify Greenify], but the maturity and/or stabiltiy of this system is unknown.&lt;br /&gt;
&lt;br /&gt;
A comparison at http://www.diamondtin.com/2014/sqlalchemy-gevent-mysql-python-drivers-comparison/ illustrates performance metrics observed with all four of these drivers, with MySQL-python built both without and with the &amp;quot;greenify&amp;quot; system.&lt;br /&gt;
&lt;br /&gt;
A summary of the status of all four drivers is as follows.&lt;br /&gt;
&lt;br /&gt;
=== MySQL-Python ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/farcepest/MySQLdb1 MySQL-Python] is the most widely used Python driver.  However, it does not support Python 3, and it does not support async systems unless used within a thread pool.   While a system known as Greenify can potentially resolve the latter situation, the Python 3 limitation is still a deal-breaker - pull request with Python 3 support [https://github.com/farcepest/MySQLdb1/pull/62 was proposed] to MySQL-python in April 2014 and still not merged.&lt;br /&gt;
&lt;br /&gt;
=== mysqlclient ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/PyMySQL/mysqlclient-python mysqlclient] is a fork of MySQL-python. It add Python 3.3 support and merges some pull requests.&lt;br /&gt;
&lt;br /&gt;
=== OurSQL ===&lt;br /&gt;
&lt;br /&gt;
[https://pythonhosted.org/oursql/ OurSQL] is an alternative MySQL driver that is also written in C.  It features fast performance and uses a different execution model than MySQL-python, using prepared statements.    OurSQL does have a Python 3 port hosted on [https://launchpad.net/oursql/py3k/ launchpad] but is not integrated with OurSQL itself nor is it published on Pypi.     Because it is written in C, it does not offer compatibility with async systems including eventlet-style monkeypatching.&lt;br /&gt;
&lt;br /&gt;
Besides the shaky Python 3 support and lack of eventlet support, OurSQL is also not maintained, seeing its last master commit on [https://code.launchpad.net/~jerub/oursql/master 2012-06-05].&lt;br /&gt;
&lt;br /&gt;
=== MySQL-Connector-Python ===&lt;br /&gt;
&lt;br /&gt;
[https://pypi.python.org/pypi/mysql-connector-python MySQL-connector-Python] is a pure Python MySQL driver, and is now released under the auspices of the MySQL project itself, as owned by Oracle.  MySQL-connector-Python supports both Python 3 as well as eventlet monkeypatching, and is well maintained.  It is endorsed by Oracle as the official Oracle-supported driver for MySQL, so to that extent, it is in most ways Openstack's first choice in driver.  However, Oracle refuses to publish MySQL-connector-Python on Pypi, which is critical to the Openstack infrastructure.  Repeated attempts to communicate with Oracle in order to resolve this issue have not made any progress.    Therefore, for this one unfortunate reason, MySQL-connector-Python will not have a place in the Openstack ecosystem unless this issue is resolved.&lt;br /&gt;
&lt;br /&gt;
=== PyMySQL ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/PyMySQL/PyMySQL PyMySQL] is a pure Python MySQL&lt;br /&gt;
driver, first written as a rough port of the MySQL-Python driver.&lt;br /&gt;
PyMySQL meets all of Openstack's criterion for a driver: it is fully&lt;br /&gt;
open source, hosted on Github, released on Pypi, is actively&lt;br /&gt;
maintained, is written in pure Python so is eventlet-monkeypatch&lt;br /&gt;
compatible, and is fully Python 3 compatible.&lt;br /&gt;
&lt;br /&gt;
As this document is named &amp;quot;PyMySQL Evaluation&amp;quot;, it should be apparent that&lt;br /&gt;
this is the driver Openstack is currently leaning towards.  However, it does&lt;br /&gt;
have some minor code quality issues which hopefully can be addressed in some&lt;br /&gt;
way; the section below titled &amp;quot;PyMySQL Code Review&amp;quot; will summarize the&lt;br /&gt;
current state of the code.  We are in fact leaning towards this driver because&lt;br /&gt;
its the only one that actually meets all criteria.  PyMySQL appears to be&lt;br /&gt;
fairly widely used, especially considering its low profile, so it's possible&lt;br /&gt;
that this is the case for many other users as well.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===MySQL DB Drivers Comparison===&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Project !! PyPi hosted !! Eventlet friendly !! Python 3 compatibility !! Maturity and/or stability  !! Comment&lt;br /&gt;
|-&lt;br /&gt;
| MySQL-Python || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: orange;&amp;quot; | Partial || style=&amp;quot;background-color: red;&amp;quot; | No  || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || Can be monkeypatched by eventlet, but only to enable thread pooling&lt;br /&gt;
|-&lt;br /&gt;
| mysqlclient || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: orange;&amp;quot; | Partial || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes  || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || MySQL-python fork, so should be similar to it&lt;br /&gt;
|-&lt;br /&gt;
| OurSQL || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: red;&amp;quot; | No || style=&amp;quot;background-color: orange;&amp;quot; | Yes, but not Pypi hosted  || style=&amp;quot;background-color: orange;&amp;quot; | ??? || Has not seen commits/releases in two years&lt;br /&gt;
|-&lt;br /&gt;
| MySQL-Connector-Python || style=&amp;quot;background-color: red;&amp;quot; | No || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes  || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || The official Oracle-supported driver for MySQL&lt;br /&gt;
|-&lt;br /&gt;
| PyMySQL || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes  || style=&amp;quot;background-color: orange;&amp;quot; | Yes, with a [http://lists.openstack.org/pipermail/openstack-dev/2014-December/052864.html proviso]. ||&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== PyMySQL Code Review ==&lt;br /&gt;
&lt;br /&gt;
PyMySQL started roughly as a pure Python port of MySQL-Python.   The sections below will&lt;br /&gt;
detail various aspects of the driver and the status of the code.   Overall, the general&lt;br /&gt;
tone of PyMySQL is one of code that was written in the spirit of pragmatism and immediate&lt;br /&gt;
need; it is straightforward, relatively free of serious antipatterns and performs&lt;br /&gt;
its task in a matter-of-fact way.  However,&lt;br /&gt;
it lacks polish and completeness in many areas that would normally be in better&lt;br /&gt;
condition for a more mature project.  These areas are all highly fixable, and it is&lt;br /&gt;
hoped that these items can be shared with the current PyMySQL developers, through&lt;br /&gt;
a combination of this document itself as well as new issues and pull requests&lt;br /&gt;
on the PyMySQL tracker, so that this fairly promising library can be pushed&lt;br /&gt;
to the next level.   This would require that the developers are amenable to&lt;br /&gt;
these improvements.&lt;br /&gt;
&lt;br /&gt;
=== Coding Style / Pep8 ===&lt;br /&gt;
&lt;br /&gt;
PyMySQL's coding style is pretty good.  The majority of it passes all&lt;br /&gt;
flake8 tests, save for a few dozen whitespace issues and&lt;br /&gt;
some long lines here and there within the core&lt;br /&gt;
modules, and moreso within the tests which appear to be a bit more&lt;br /&gt;
crufty than the core modules.  A full run of flake8 produces only 128&lt;br /&gt;
errors, and with a default run of autopep8 against E1,E2,E3, we can&lt;br /&gt;
fix all of them in one pass except for 38 remaining long line warnings.&lt;br /&gt;
The code layout is mostly idiomatic and reasonable.&lt;br /&gt;
&lt;br /&gt;
The codebase is probably short on docstrings.  Those methods that are&lt;br /&gt;
part of the public DBAPI do have docstrings, but tend to be extremely&lt;br /&gt;
terse (example, docstring for cursor.execute(): &amp;quot;'Execute a query'&amp;quot;).&lt;br /&gt;
Other methods that aren't public tend to not have any docstrings, but&lt;br /&gt;
the codebase is not at all hard to read and the purpose of methods and&lt;br /&gt;
functions is pretty easy to discern just by their names and implementations,&lt;br /&gt;
however those who seek inline documentation as a means to understand a codebase&lt;br /&gt;
will be disappointed.   The terseness of public API docstrings is more of&lt;br /&gt;
an issue in terms of the overall lack of documentation for PyMySQL,&lt;br /&gt;
see the section &amp;quot;Documentation&amp;quot; below.&lt;br /&gt;
&lt;br /&gt;
=== Test Coverage ===&lt;br /&gt;
&lt;br /&gt;
The test coverage for PyMySQL is definitely lacking.   From a visual&lt;br /&gt;
inspection alone, it's apparent that of the dozen or so test modules,&lt;br /&gt;
most of them have less then ten tests each and the modules are very short.&lt;br /&gt;
For a full test run, there's only 126 tests; contrast this to&lt;br /&gt;
MySQL-Connector-Python which has 531 tests.&lt;br /&gt;
&lt;br /&gt;
Additionally, all of the tests are live round-trip tests against a&lt;br /&gt;
real database.  There are no non-live unit tests of any kind,&lt;br /&gt;
leaving codepaths that are not easily exercised&lt;br /&gt;
on a generic MySQL database out in the cold.  This means that features designed&lt;br /&gt;
for particular versions of MySQL, particular datatypes, particular error&lt;br /&gt;
condtions, and particular kinds of message packets that aren't created for&lt;br /&gt;
real are simply not tested.&lt;br /&gt;
&lt;br /&gt;
The lack of tests is apparent when run with coverage.  The test suite itself&lt;br /&gt;
does not appear to have any signs that it is normally run with coverage&lt;br /&gt;
turned on, with no directives in tox.ini or similar.   With coverage,&lt;br /&gt;
the overall coverage is 85%, but the modules most lacking in coverage&lt;br /&gt;
are also the most critical to PyMySQL's core functionality,&lt;br /&gt;
connections.py, converters.py and cursors.py:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Name                                                              Stmts   Miss  Cover&lt;br /&gt;
-------------------------------------------------------------------------------------&lt;br /&gt;
pymysql/__init__                                                     51      3    94%&lt;br /&gt;
pymysql/_compat                                                      14      4    71%&lt;br /&gt;
pymysql/_socketio                                                    72     30    58%&lt;br /&gt;
pymysql/charset                                                     228      3    99%&lt;br /&gt;
pymysql/connections                                                 827    188    77%&lt;br /&gt;
pymysql/constants/CLIENT                                             18      0   100%&lt;br /&gt;
pymysql/constants/COMMAND                                            32      0   100%&lt;br /&gt;
pymysql/constants/ER                                                471      0   100%&lt;br /&gt;
pymysql/constants/FIELD_TYPE                                         29      0   100%&lt;br /&gt;
pymysql/constants/FLAG                                               15      0   100%&lt;br /&gt;
pymysql/constants/SERVER_STATUS                                      10      0   100%&lt;br /&gt;
pymysql/constants/__init__                                            0      0   100%&lt;br /&gt;
pymysql/converters                                                  148     51    66%&lt;br /&gt;
pymysql/cursors                                                     273     37    86%&lt;br /&gt;
pymysql/err                                                          40      1    98%&lt;br /&gt;
pymysql/tests/__init__                                               14      5    64%&lt;br /&gt;
pymysql/tests/base                                                   25      2    92%&lt;br /&gt;
pymysql/tests/test_DictCursor                                        73      2    97%&lt;br /&gt;
pymysql/tests/test_SSCursor                                          56      6    89%&lt;br /&gt;
pymysql/tests/test_basic                                            186      4    98%&lt;br /&gt;
pymysql/tests/test_connection                                        57      8    86%&lt;br /&gt;
pymysql/tests/test_example                                           17      2    88%&lt;br /&gt;
pymysql/tests/test_issues                                           300     48    84%&lt;br /&gt;
pymysql/tests/test_load_local                                        39      2    95%&lt;br /&gt;
pymysql/tests/test_nextset                                           43      4    91%&lt;br /&gt;
pymysql/tests/thirdparty/__init__                                     7      5    29%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/__init__                        6      2    67%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/capabilities                  196     23    88%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/dbapi20                       423    121    71%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_capabilities      73      6    92%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_dbapi20          100      2    98%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_nonstandard       57      3    95%&lt;br /&gt;
pymysql/times                                                        12      0   100%&lt;br /&gt;
pymysql/util                                                         14      7    50%&lt;br /&gt;
-------------------------------------------------------------------------------------&lt;br /&gt;
TOTAL                                                              3926    569    86%&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The lack of coverage in converters.py refers to functions related to handling&lt;br /&gt;
incoming bound values as well as outgoing result values.    Key data escaping&lt;br /&gt;
features such as escaping of dictionaries, sets, some unicode objects and&lt;br /&gt;
byte objects as well as many pathways into date and time-processing functions are not&lt;br /&gt;
covered.&lt;br /&gt;
&lt;br /&gt;
In connections.py, uncovered features include various SSL and&lt;br /&gt;
very legacy (e.g. version 3.23) MySQL features, but also some protocol parsing&lt;br /&gt;
features.&lt;br /&gt;
&lt;br /&gt;
In cursors.py, there is coverage for an elaborate and mostly undocumented&lt;br /&gt;
performance-related feature that rewrites an INSERT statement to have an&lt;br /&gt;
extended VALUES clause (_do_execute_many), however it fails to cover the very&lt;br /&gt;
likely case where the function will need to break the input set into&lt;br /&gt;
multiple chunks:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;gt;         for arg in args:&lt;br /&gt;
&amp;gt;             v = values % escape(arg, conn)&lt;br /&gt;
&amp;gt;             if isinstance(v, text_type):&lt;br /&gt;
&amp;gt;                 v = v.encode(encoding)&lt;br /&gt;
&amp;gt;             if len(sql) + len(v) + 1 &amp;gt; max_stmt_length:&lt;br /&gt;
!                 rows += self.execute(sql)&lt;br /&gt;
!                 sql = bytearray(prefix)&lt;br /&gt;
&amp;gt;             else:&lt;br /&gt;
&amp;gt;                 sql += b','&lt;br /&gt;
&amp;gt;             sql += v&lt;br /&gt;
&amp;gt;         rows += self.execute(sql)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The most prominent missing coverage in cursors.py seems to be&lt;br /&gt;
for its &amp;quot;scrollable cursor&amp;quot; support.&lt;br /&gt;
&lt;br /&gt;
Beyond the lack of completeness in testing, many of the tests themselves&lt;br /&gt;
are written in an expedient &amp;quot;one giant test&amp;quot; style where dozens of&lt;br /&gt;
assertions and individual behaviors are lumped into one big test&lt;br /&gt;
case (see test_SSCursor.py, test_basic.py-&amp;gt;test_datatypes for examples).&lt;br /&gt;
Test cases like these are difficult to work with when debugging&lt;br /&gt;
regresions as well as when tests for new features need to be added, and ideally&lt;br /&gt;
these should be broken out into clean single-feature tests with consistent&lt;br /&gt;
fixtures.&lt;br /&gt;
&lt;br /&gt;
There is also a suite called &amp;quot;test_issues&amp;quot;, which intends to&lt;br /&gt;
accumulate tests against specific issues that have been reported. This&lt;br /&gt;
isn't a bad idea, though it would be nice to see an effort made into&lt;br /&gt;
categorizing these tests into the actual features they are testing,&lt;br /&gt;
rather than a meaningless list of issue numbers, which could then form&lt;br /&gt;
as the basis for new suites centered around those areas of&lt;br /&gt;
functionality.  The issues reported should be used as the inspiration&lt;br /&gt;
for improvement of the test suite, rather than just another line-item&lt;br /&gt;
to be filed away.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Library Documentation ===&lt;br /&gt;
&lt;br /&gt;
As the Python DBAPI is already a well-documented API, and the users of&lt;br /&gt;
PyMySQL are typically coming from MySQL-Python which is documented&lt;br /&gt;
to some degree and for which the use contract is widely known, it seems&lt;br /&gt;
unlikely that users of PyMySQL are eager for comprehensive documentation&lt;br /&gt;
specific to this library.&lt;br /&gt;
&lt;br /&gt;
That being said, as far as can be discerned, PyMySQL has absolutely no&lt;br /&gt;
library documentation of any kind.  There's a README which&lt;br /&gt;
essentially refers to pep-249, an example.py in the root which shows a&lt;br /&gt;
very basic connection / round trip, and that is absolutely it. In&lt;br /&gt;
order to know anything about the API, specific parameters, etc., one&lt;br /&gt;
either has to go off of the MySQL-Python documentation and assume&lt;br /&gt;
PyMySQL also features the same parameter, or read the source code.&lt;br /&gt;
&lt;br /&gt;
PyMySQL probably hasn't had much urgent need for real documentation thus&lt;br /&gt;
far, however it would be a great idea if it at least made the start&lt;br /&gt;
of a rudimentary Sphinx build including autodoc for module documentation&lt;br /&gt;
which could then be published up to RTD.   This would be very easy to get&lt;br /&gt;
started, and once present, new documentation sections can be added&lt;br /&gt;
iteratively.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Architecture and Performance ===&lt;br /&gt;
&lt;br /&gt;
As stated before, PyMySQL has a really matter-of-fact and straightforward&lt;br /&gt;
implementation.   The code is very readable and it's easy to discern even&lt;br /&gt;
unusual features such as the INSERT..VALUES rewriting.  As far as the design,&lt;br /&gt;
there are lots of areas where performance suffers a bit at the Python level;&lt;br /&gt;
these areas could be greatly improved very easily with a little bit of&lt;br /&gt;
attention.&lt;br /&gt;
&lt;br /&gt;
As we are dealing with a driver that is communicating with a low level&lt;br /&gt;
protocol over a socket, it's critical that sending and receiving messages&lt;br /&gt;
is done as efficiently as possible.  The mechanisms used in PyMySQL rely&lt;br /&gt;
heavily on standard Python objects, without the use of __slots__; this means&lt;br /&gt;
that for individual messages on a socket, we are creating a heavyweight object,&lt;br /&gt;
calling its __init__() method and a creating a new __dict__ each time;&lt;br /&gt;
the most common object, OKPacketWrapper(), already wraps a MysqlPacket()&lt;br /&gt;
object, so we're doing two __init__()s in this common case.  OKPacketWrapper&lt;br /&gt;
also implements a `__getattr__` scheme&lt;br /&gt;
to proxy attribute access from itself to the internal MysqlPacket; this is a&lt;br /&gt;
an inefficient mode of operation in Python as it means a lookup must first&lt;br /&gt;
fail on the OKPacketWrapper before it invokes the `__getattr__()` method&lt;br /&gt;
and does another attribute lookup.   A reorg of the packet classes to produce&lt;br /&gt;
far fewer objects for messages as well as to use __slots__ would be&lt;br /&gt;
recommended.&lt;br /&gt;
&lt;br /&gt;
There are also lots of areas where function call overhead is unnecessarily&lt;br /&gt;
high due to the organization of calls.  For example, when calling upon&lt;br /&gt;
cursor.execute(), for parameters passed as a dictionary under Python 2K,&lt;br /&gt;
the isinstance() builtin function will be called on the incoming arguments&lt;br /&gt;
four times at a minimum, with two of each call against the exact same&lt;br /&gt;
criteria.  Additional logic will then call isinstance() on every key of&lt;br /&gt;
the dictionary as well as at least twice on every value within the dictionary,&lt;br /&gt;
in order to determine if the value needs to be converted to bytes as well&lt;br /&gt;
as for some top-level escaping logic checks; the escaping logic then&lt;br /&gt;
goes further with more checks for type() to match the object to a final&lt;br /&gt;
converter function.   While the flow of this logic does appear to be&lt;br /&gt;
reasonable and straightforward, it isn't ideal for a low-level database&lt;br /&gt;
driver library where speed is a high priority; the various wrappers and&lt;br /&gt;
processors can easily be reorganized here to reduce the number of isinstance()&lt;br /&gt;
and type() calls significantly, which would save a lot on function call&lt;br /&gt;
overhead.  It is likely there are many other areas in the codebase where&lt;br /&gt;
similar reductions in call counts can be made without too much disruption.&lt;br /&gt;
&lt;br /&gt;
In Appendix A, a short performance suite is illustrated.  This&lt;br /&gt;
is run against PyMySQL, Mysql-Connector-Python, and MySQL-Python.&lt;br /&gt;
PyMySQL does in fact demonstrate the&lt;br /&gt;
highest call-count behavior, logging 26,850,503 function calls&lt;br /&gt;
compared to MySQL-connector's 11,674,074 for the same operations.  To PyMySQL's&lt;br /&gt;
credit, it somehow completes the test a bit faster than MySQL-connector&lt;br /&gt;
despite having more than twice as many function calls; this probably indicates&lt;br /&gt;
the use of something very slow within MySQL-connector, such as catching&lt;br /&gt;
an exception within a tight loop.  However, the real bar we're comparing to&lt;br /&gt;
is MySQL-Python, which is written&lt;br /&gt;
in C and does the whole test with only 47,000 Python function calls; while&lt;br /&gt;
PyMySQL and MySQL-connector-Python compete for time within the 16-20 second&lt;br /&gt;
range, MySQL-Python completes the whole test in **0.9 seconds**, a 1600%&lt;br /&gt;
improvement over PyMySQL.   This speed difference is not PyMySQL's fault,&lt;br /&gt;
as pure Python is known to be extemely slow compared to C code.&lt;br /&gt;
However, there's a lot of easy wins in PyMySQL's codebase where the function&lt;br /&gt;
call count could probably be reduced dramatically to be even better than that&lt;br /&gt;
of MySQL-connector-Python; ideally, the driver would be able to run this&lt;br /&gt;
test possibly 5-6x slower than MySQL-Python rather than 16x.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== History Documentation ===&lt;br /&gt;
&lt;br /&gt;
PyMySQL includes a CHANGELOG file in its root which describes the bug fixes&lt;br /&gt;
and features of each release.  The construction of this file is definitely&lt;br /&gt;
within the &amp;quot;expedient&amp;quot; style noted for PyMySQL overall, and for it to be&lt;br /&gt;
genuinely useful for enterprise-level software, it would need to be&lt;br /&gt;
much more complete in its detail.    Each bug fix mentioned is only referred to&lt;br /&gt;
tersely without any link to a pull request or bug report.  While some can&lt;br /&gt;
be linked to a specific change through detective work&lt;br /&gt;
(&amp;quot;Cursor.fetchall() and .fetchmany now return list, not tuple&amp;quot;), it would&lt;br /&gt;
be very difficult for many&lt;br /&gt;
(&amp;quot;Fixed BIT type handling&amp;quot;, &amp;quot;Fixed GC errors&amp;quot; - what was the issue?)&lt;br /&gt;
and in many cases impossible (&amp;quot;Improved Py3k support&amp;quot;).  The file also&lt;br /&gt;
includes no dates so one has to look at the git tags to figure this&lt;br /&gt;
part out (fortunately, releases to seem to be git tagged at least).&lt;br /&gt;
For a user trying to ascertain if particular issues are&lt;br /&gt;
fixed or if behaviors have changed, this file is at best highly frustrating.&lt;br /&gt;
&lt;br /&gt;
A listing of release dates illustrates an average of two releases per year,&lt;br /&gt;
excluding 2012 when PyMySQL was seeking a new maintainer:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
2014-12-02 12:05:10 -0200  (tag: pymysql-0.6.3)&lt;br /&gt;
2014-04-21 14:28:47 -0300  (tag: pymysql-0.6.2)&lt;br /&gt;
2013-10-11 13:36:56 -0300  (tag: pymysql-0.6.1)&lt;br /&gt;
2013-10-04 14:25:14 -0300  (tag: pymysql-0.6)&lt;br /&gt;
2011-11-08 10:27:41 -0800  (tag: pymysql-0.5)&lt;br /&gt;
2010-12-27 17:28:16 +0000  (tag: pymysql-0.4)&lt;br /&gt;
2010-09-03 00:55:29 +0000  (tag: pymysql-0.3)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Community Involvement ===&lt;br /&gt;
&lt;br /&gt;
This is an area where PyMySQL seems to shine, and might even be an advantage&lt;br /&gt;
as compared to MySQL-Connector-Python, which remains under the cloak&lt;br /&gt;
of MySQL's vastly overstuffed bugtracker under the ownership of&lt;br /&gt;
the famously-opaque Oracle.   For a small, low key library without&lt;br /&gt;
any real documentation and only 126 tests, it features 33 contributors on&lt;br /&gt;
Github and is up to pull request #285, which is quite high.  Because&lt;br /&gt;
PyMySQL is small and straightforward, it does lend itself to contribution&lt;br /&gt;
and seems to do very well in this area.   The developers so far have been&lt;br /&gt;
responsive to issues and pull requests made by the author.&lt;br /&gt;
&lt;br /&gt;
Based on github contributor graphs it appears that PyMySQL had a lull&lt;br /&gt;
in 2012 but was picked up again afterwards in 2013.  As of 2012, its most&lt;br /&gt;
prominent developer Pete Hunt stopped development, and on July 25, 2013&lt;br /&gt;
added a note to the README that the project was looking for a new maintainer.&lt;br /&gt;
As of 2013 it was picked up by the current most prominent developers&lt;br /&gt;
Marcel Rodrigues (github username lecram) and INADA Naoki&lt;br /&gt;
(github username methane).&lt;br /&gt;
&lt;br /&gt;
Most issues that have been noted thus far within the areas of testing,&lt;br /&gt;
documentation and architecture are all very fixable; if the Openstack community&lt;br /&gt;
can provide resources to address some of these areas, and if the PyMySQL&lt;br /&gt;
developers are reasonably open to accepting new changes and producing new&lt;br /&gt;
releases in a timely fashion, the PyMySQL driver&lt;br /&gt;
could rapidly become a fully formidable contender in the MySQL driver field.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Appendix A - Performance Tests ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
import cProfile&lt;br /&gt;
import StringIO&lt;br /&gt;
import pstats&lt;br /&gt;
import contextlib&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextlib.contextmanager&lt;br /&gt;
def profiled(dbapi):&lt;br /&gt;
    pr = cProfile.Profile()&lt;br /&gt;
    pr.enable()&lt;br /&gt;
    yield&lt;br /&gt;
    pr.disable()&lt;br /&gt;
    s = StringIO.StringIO()&lt;br /&gt;
    ps = pstats.Stats(pr, stream=s).sort_stats('cumulative')&lt;br /&gt;
    print &amp;quot;DBAPI:  %s&amp;quot; % dbapi&lt;br /&gt;
    ps.print_stats()&lt;br /&gt;
    # uncomment this to see who's calling what&lt;br /&gt;
    # ps.print_callers()&lt;br /&gt;
    print &amp;quot;total calls: %s&amp;quot; % s.getvalue()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
import MySQLdb&lt;br /&gt;
import pymysql&lt;br /&gt;
from mysql import connector as mysqlconnector&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def go(dbapi):&lt;br /&gt;
    conn = dbapi.connect(&lt;br /&gt;
        user='scott', passwd='tiger', host='localhost', db='test')&lt;br /&gt;
    cursor = conn.cursor()&lt;br /&gt;
    cursor.execute(&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
CREATE TABLE IF NOT EXISTS test_things (&lt;br /&gt;
    x INTEGER,&lt;br /&gt;
    y VARCHAR(255),&lt;br /&gt;
    z FLOAT&lt;br /&gt;
) engine=InnoDB&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;)&lt;br /&gt;
    cursor.execute(&amp;quot;DELETE from test_things&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    with profiled(dbapi):&lt;br /&gt;
        for row in xrange(1000):&lt;br /&gt;
            cursor.execute(&lt;br /&gt;
                &amp;quot;INSERT INTO test_things (x, y, z) &amp;quot;&lt;br /&gt;
                &amp;quot;VALUES (%(x)s, %(y)s, %(z)s)&amp;quot;,&lt;br /&gt;
                {&amp;quot;x&amp;quot;: row, &amp;quot;y&amp;quot;: &amp;quot;row number %d&amp;quot; % row,&lt;br /&gt;
                 &amp;quot;z&amp;quot;: row * 4.57292, }&lt;br /&gt;
            )&lt;br /&gt;
&lt;br /&gt;
        for x in xrange(500):&lt;br /&gt;
            cursor.execute(&lt;br /&gt;
                &amp;quot;select * from test_things&amp;quot;)&lt;br /&gt;
            for row in cursor.fetchall():&lt;br /&gt;
                row[0], row[1], row[2]&lt;br /&gt;
&lt;br /&gt;
    cursor.close()&lt;br /&gt;
    conn.close()&lt;br /&gt;
&lt;br /&gt;
go(pymysql)&lt;br /&gt;
go(mysqlconnector)&lt;br /&gt;
go(MySQLdb)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DBAPI:  &amp;lt;module 'pymysql' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/__init__.pyc'&amp;gt;&lt;br /&gt;
total calls:          26850503 function calls in 16.017 seconds&lt;br /&gt;
&lt;br /&gt;
   Ordered by: cumulative time&lt;br /&gt;
&lt;br /&gt;
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;
     1500    0.010    0.000   16.016    0.011 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:107(execute)&lt;br /&gt;
     1500    0.003    0.000   15.976    0.011 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:271(_query)&lt;br /&gt;
     1500    0.003    0.000   15.952    0.011 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:708(query)&lt;br /&gt;
     1500    0.003    0.000   15.911    0.011 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:854(_read_query_result)&lt;br /&gt;
     1500    0.004    0.000   15.905    0.011 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:1060(read)&lt;br /&gt;
      500    0.003    0.000   15.658    0.031 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:1106(_read_result_packet)&lt;br /&gt;
      500    0.921    0.002   15.527    0.031 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:1139(_read_rowdata_packet)&lt;br /&gt;
   500000    3.404    0.000    9.210    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:1152(_read_row_from_packet)&lt;br /&gt;
  2012000    1.179    0.000    5.374    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:314(read_length_coded_string)&lt;br /&gt;
   504500    1.533    0.000    5.246    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:808(_read_packet)&lt;br /&gt;
  2014500    1.265    0.000    3.056    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:296(read_length_encoded_integer)&lt;br /&gt;
  3539500    2.469    0.000    2.782    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:248(read)&lt;br /&gt;
  1009000    0.795    0.000    2.300    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:833(_read_bytes)&lt;br /&gt;
  1009000    1.178    0.000    1.400    0.000 {method 'read' of '_io.BufferedReader' objects}&lt;br /&gt;
   508500    0.381    0.000    0.699    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/util.py:3(byte2int)&lt;br /&gt;
  5058000    0.468    0.000    0.468    0.000 {len}&lt;br /&gt;
   500500    0.219    0.000    0.453    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:1098(_check_packet_is_eof)&lt;br /&gt;
   504500    0.211    0.000    0.380    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:342(check_error)&lt;br /&gt;
  2504000    0.259    0.000    0.259    0.000 {method 'append' of 'list' objects}&lt;br /&gt;
   501500    0.182    0.000    0.231    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:329(is_eof_packet)&lt;br /&gt;
     3500    0.009    0.000    0.222    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/_socketio.py:45(readinto)&lt;br /&gt;
  1022000    0.205    0.000    0.205    0.000 {_struct.unpack}&lt;br /&gt;
     3500    0.203    0.000    0.203    0.000 {method 'recv_into' of '_socket.socket' objects}&lt;br /&gt;
   526000    0.198    0.000    0.198    0.000 {isinstance}&lt;br /&gt;
  1501000    0.197    0.000    0.197    0.000 {method 'get' of 'dict' objects}&lt;br /&gt;
   504500    0.173    0.000    0.173    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:241(__init__)&lt;br /&gt;
   504500    0.169    0.000    0.169    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:339(is_error_packet)&lt;br /&gt;
  2014500    0.162    0.000    0.162    0.000 {ord}&lt;br /&gt;
      500    0.006    0.000    0.127    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:1176(_get_descriptions)&lt;br /&gt;
     2000    0.003    0.000    0.088    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:361(__init__)&lt;br /&gt;
     2000    0.020    0.000    0.085    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:365(__parse_field_descriptor)&lt;br /&gt;
   500000    0.055    0.000    0.055    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/converters.py:301(through)&lt;br /&gt;
     1500    0.006    0.000    0.037    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:877(_execute_command)&lt;br /&gt;
     1500    0.003    0.000    0.027    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:848(_write_bytes)&lt;br /&gt;
     1500    0.002    0.000    0.024    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/socket.py:223(meth)&lt;br /&gt;
     1500    0.021    0.000    0.021    0.000 {method 'sendall' of '_socket.socket' objects}&lt;br /&gt;
     1500    0.020    0.000    0.021    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:278(_do_get_result)&lt;br /&gt;
     1000    0.003    0.000    0.019    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:97(_escape_args)&lt;br /&gt;
     4000    0.002    0.000    0.015    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:101(&amp;lt;genexpr&amp;gt;)&lt;br /&gt;
     1000    0.001    0.000    0.014    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:1089(_read_ok_packet)&lt;br /&gt;
     3000    0.003    0.000    0.013    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:674(escape)&lt;br /&gt;
     1000    0.005    0.000    0.012    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:416(__init__)&lt;br /&gt;
     8000    0.011    0.000    0.011    0.000 {method 'decode' of 'str' objects}&lt;br /&gt;
     3500    0.002    0.000    0.006    0.000 {method '_checkReadable' of '_io._IOBase' objects}&lt;br /&gt;
     4000    0.002    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:128(&amp;lt;genexpr&amp;gt;)&lt;br /&gt;
     2000    0.003    0.000    0.005    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/converters.py:19(escape_item)&lt;br /&gt;
     7500    0.003    0.000    0.005    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:117(ensure_bytes)&lt;br /&gt;
     2000    0.003    0.000    0.005    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:386(description)&lt;br /&gt;
     5500    0.004    0.000    0.004    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:271(advance)&lt;br /&gt;
      500    0.002    0.000    0.004    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:442(__init__)&lt;br /&gt;
     1000    0.001    0.000    0.004    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:684(escape_string)&lt;br /&gt;
     3500    0.004    0.000    0.004    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/_socketio.py:87(readable)&lt;br /&gt;
     3500    0.003    0.000    0.003    0.000 {method '_checkClosed' of '_io._IOBase' objects}&lt;br /&gt;
     1000    0.001    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/converters.py:59(escape_string)&lt;br /&gt;
     1500    0.003    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:1043(__init__)&lt;br /&gt;
     1500    0.001    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:94(nextset)&lt;br /&gt;
     4000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:397(get_column_length)&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/converters.py:56(escape_float)&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 {method 'sub' of '_sre.SRE_Pattern' objects}&lt;br /&gt;
     3000    0.002    0.000    0.002    0.000 {_struct.pack}&lt;br /&gt;
     6000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:64(_get_db)&lt;br /&gt;
     1500    0.001    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:82(_nextset)&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/util.py:9(int2byte)&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:246(fetchall)&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {min}&lt;br /&gt;
     2500    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:326(is_ok_packet)&lt;br /&gt;
     2000    0.001    0.000    0.001    0.000 {method 'items' of 'dict' objects}&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {getattr}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/converters.py:52(escape_int)&lt;br /&gt;
     1000    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:262(read_all)&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:1056(__del__)&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:69(_check_executed)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/contextlib.py:21(__exit__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 test.py:7(profiled)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
DBAPI:  &amp;lt;module 'mysql.connector' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/__init__.pyc'&amp;gt;&lt;br /&gt;
total calls:          11674074 function calls in 18.316 seconds&lt;br /&gt;
&lt;br /&gt;
   Ordered by: cumulative time&lt;br /&gt;
&lt;br /&gt;
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;
      500    0.611    0.001   17.825    0.036 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:820(fetchall)&lt;br /&gt;
      500    0.002    0.000   10.829    0.022 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:655(get_rows)&lt;br /&gt;
      500    1.505    0.003   10.825    0.022 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:292(read_text_result)&lt;br /&gt;
   504500    3.208    0.000    7.065    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/network.py:219(recv_plain)&lt;br /&gt;
   500000    3.398    0.000    6.297    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:359(row_to_python)&lt;br /&gt;
  1009000    3.039    0.000    3.039    0.000 {method 'recv_into' of '_socket.socket' objects}&lt;br /&gt;
   500000    2.003    0.000    2.283    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:220(read_lc_string_list)&lt;br /&gt;
   500000    0.625    0.000    2.010    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:531(_STRING_to_python)&lt;br /&gt;
   502000    0.355    0.000    1.254    0.000 {method 'decode' of 'bytearray' objects}&lt;br /&gt;
   502000    0.212    0.000    0.899    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/utf_8.py:15(decode)&lt;br /&gt;
   502000    0.688    0.000    0.688    0.000 {_codecs.utf_8_decode}&lt;br /&gt;
   509500    0.498    0.000    0.498    0.000 {_struct.unpack_from}&lt;br /&gt;
     1500    0.009    0.000    0.490    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:452(execute)&lt;br /&gt;
   500000    0.482    0.000    0.482    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:413(_INT_to_python)&lt;br /&gt;
     1500    0.004    0.000    0.436    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:705(cmd_query)&lt;br /&gt;
   500000    0.352    0.000    0.352    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:405(_FLOAT_to_python)&lt;br /&gt;
   504500    0.326    0.000    0.326    0.000 {method 'extend' of 'bytearray' objects}&lt;br /&gt;
     1500    0.006    0.000    0.318    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:481(_send_cmd)&lt;br /&gt;
  2501500    0.273    0.000    0.273    0.000 {method 'append' of 'list' objects}&lt;br /&gt;
   500500    0.240    0.000    0.240    0.000 {method 'startswith' of 'bytearray' objects}&lt;br /&gt;
   524002    0.143    0.000    0.143    0.000 {isinstance}&lt;br /&gt;
     1500    0.010    0.000    0.114    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:613(_handle_result)&lt;br /&gt;
  1004500    0.109    0.000    0.109    0.000 {len}&lt;br /&gt;
   500000    0.086    0.000    0.086    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:204(description)&lt;br /&gt;
     2000    0.013    0.000    0.038    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:226(parse_column)&lt;br /&gt;
     1000    0.008    0.000    0.034    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:346(_process_params_dict)&lt;br /&gt;
     1500    0.006    0.000    0.034    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/network.py:118(send_plain)&lt;br /&gt;
     1500    0.002    0.000    0.023    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/socket.py:223(meth)&lt;br /&gt;
     1500    0.020    0.000    0.020    0.000 {method 'sendall' of '_socket.socket' objects}&lt;br /&gt;
     5000    0.007    0.000    0.014    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/catch23.py:79(struct_unpack)&lt;br /&gt;
     1000    0.001    0.000    0.014    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:562(_handle_ok)&lt;br /&gt;
    12000    0.011    0.000    0.011    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:167(read_lc_string)&lt;br /&gt;
     1000    0.004    0.000    0.011    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:199(parse_ok)&lt;br /&gt;
     3000    0.005    0.000    0.009    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:152(to_mysql)&lt;br /&gt;
     3000    0.003    0.000    0.007    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:130(quote)&lt;br /&gt;
     1500    0.002    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:118(make_command)&lt;br /&gt;
     1500    0.004    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:406(_handle_result)&lt;br /&gt;
     3000    0.003    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:102(escape)&lt;br /&gt;
     1500    0.004    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/network.py:53(_prepare_packets)&lt;br /&gt;
     1000    0.002    0.000    0.005    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:252(parse_eof)&lt;br /&gt;
      500    0.001    0.000    0.005    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:579(_handle_eof)&lt;br /&gt;
     2000    0.003    0.000    0.004    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:545(_handle_server_status)&lt;br /&gt;
     1500    0.002    0.000    0.004    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:53(int1store)&lt;br /&gt;
     3000    0.003    0.000    0.003    0.000 {method 'encode' of 'str' objects}&lt;br /&gt;
     9001    0.003    0.000    0.003    0.000 {method 'replace' of 'str' objects}&lt;br /&gt;
     1500    0.003    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:295(_reset_result)&lt;br /&gt;
     6027    0.003    0.000    0.003    0.000 {method 'format' of 'str' objects}&lt;br /&gt;
     2500    0.003    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:296(read_lc_int)&lt;br /&gt;
     4500    0.002    0.000    0.002    0.000 {_struct.pack}&lt;br /&gt;
     3000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:1221(_set_unread_result)&lt;br /&gt;
      500    0.001    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:219(parse_column_count)&lt;br /&gt;
      500    0.001    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:757(_handle_eof)&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 {repr}&lt;br /&gt;
     4527    0.001    0.000    0.001    0.000 {getattr}&lt;br /&gt;
     4000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/constants.py:34(flag_is_set)&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:383(_handle_noresultset)&lt;br /&gt;
     4000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:1233(_get_unread_result)&lt;br /&gt;
     2001    0.001    0.000    0.001    0.000 {method 'items' of 'dict' objects}&lt;br /&gt;
     3000    0.001    0.000    0.001    0.000 {method 'lower' of 'str' objects}&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:307(_have_unread_result)&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:173(_str_to_mysql)&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 {range}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:169(_float_to_mysql)&lt;br /&gt;
     1000    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:161(_int_to_mysql)&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:1313(_get_getwarnings)&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:200(reset)&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:397(_handle_resultset)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/__init__.py:71(search_function)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/ascii.py:41(getregentry)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/contextlib.py:21(__exit__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/__init__.py:49(normalize_encoding)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/codecs.py:77(__new__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {__import__}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 test.py:7(profiled)&lt;br /&gt;
        3    0.000    0.000    0.000    0.000 {method 'get' of 'dict' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'split' of 'str' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'translate' of 'str' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'join' of 'str' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {built-in method __new__ of type object at 0x1001534e8}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {hasattr}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DBAPI:  &amp;lt;module 'MySQLdb' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/__init__.pyc'&amp;gt;&lt;br /&gt;
total calls:          47503 function calls in 0.916 seconds&lt;br /&gt;
&lt;br /&gt;
   Ordered by: cumulative time&lt;br /&gt;
&lt;br /&gt;
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;
     1500    0.008    0.000    0.915    0.001 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:164(execute)&lt;br /&gt;
     1500    0.002    0.000    0.888    0.001 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:353(_query)&lt;br /&gt;
     1500    0.004    0.000    0.498    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:315(_do_query)&lt;br /&gt;
     1500    0.016    0.000    0.388    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:358(_post_get_result)&lt;br /&gt;
     1500    0.001    0.000    0.372    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:324(_fetch_row)&lt;br /&gt;
      500    0.371    0.001    0.371    0.001 {built-in method fetch_row}&lt;br /&gt;
     1500    0.008    0.000    0.359    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:142(_do_get_result)&lt;br /&gt;
     1500    0.002    0.000    0.348    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:351(_get_result)&lt;br /&gt;
     1500    0.346    0.000    0.346    0.000 {method 'store_result' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.134    0.000    0.134    0.000 {method 'query' of '_mysql.connection' objects}&lt;br /&gt;
     4000    0.002    0.000    0.010    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:184(&amp;lt;genexpr&amp;gt;)&lt;br /&gt;
     3000    0.002    0.000    0.008    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/connections.py:267(literal)&lt;br /&gt;
     1500    0.007    0.000    0.007    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:107(_warning_check)&lt;br /&gt;
     3000    0.002    0.000    0.006    0.000 {method 'escape' of '_mysql.connection' objects}&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/converters.py:81(Float2Str)&lt;br /&gt;
     6000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:159(_get_db)&lt;br /&gt;
     2500    0.002    0.000    0.002    0.000 {isinstance}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/connections.py:202(string_literal)&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:380(fetchall)&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 {built-in method describe}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 {method 'string_literal' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {method 'affected_rows' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {method 'insert_id' of '_mysql.connection' objects}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/converters.py:69(Thing2Str)&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 {method 'info' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 {method 'warning_count' of '_mysql.connection' objects}&lt;br /&gt;
     1000    0.000    0.000    0.000    0.000 {method 'iteritems' of 'dict' objects}&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:103(_check_executed)&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 {len}&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 {built-in method field_flags}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/contextlib.py:21(__exit__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 test.py:7(profiled)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mike Bayer</name></author>	</entry>

	<entry>
		<id>https://wiki.openstack.org/w/index.php?title=PyMySQL_evaluation&amp;diff=72540</id>
		<title>PyMySQL evaluation</title>
		<link rel="alternate" type="text/html" href="https://wiki.openstack.org/w/index.php?title=PyMySQL_evaluation&amp;diff=72540"/>
				<updated>2015-01-28T18:39:20Z</updated>
		
		<summary type="html">&lt;p&gt;Mike Bayer: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= PyMySQL Evaluation =&lt;br /&gt;
&lt;br /&gt;
This page will capture issues related to Openstack moving to the [https://github.com/PyMySQL/PyMySQL/ PyMySQL driver] for MySQL/MariaDB dbapi access.&lt;br /&gt;
&lt;br /&gt;
== Rationale ==&lt;br /&gt;
&lt;br /&gt;
While the [https://github.com/farcepest/MySQLdb1 MySQL-Python] driver is a very mature and stable driver, it does not provide compatibility with either Python 3 or with [http://eventlet.net/doc/patching.html eventlet monkeypatching] (MySQL-Python  [https://github.com/eventlet/eventlet/blob/v0.16.1/eventlet/patcher.py#L225-L237 can be monkeypatched] with eventlet, but this feature disabled by default and missed in documentation). So OpenStack's usage of MySQL-Python, combined with the fact that concurrency is provided by eventlet, means that we currently have fully serialized database access within a single process, that is, only one database command occurs at a time within an Openstack Python process.&lt;br /&gt;
&lt;br /&gt;
== Drivers Under Consideration ==&lt;br /&gt;
&lt;br /&gt;
The two drivers that are known to provide eventlet-monkeypatch compatibility are MySQL-Connector and PyMySQL, as they are written in pure Python.   The two other well-known drivers for MySQL are MySQL-Python and OurSQL, both of which are written in C and offer no explicit support for eventlet or async.  They can reportedly be built to support gevent using a system called [https://github.com/CMGS/greenify Greenify], but the maturity and/or stabiltiy of this system is unknown.&lt;br /&gt;
&lt;br /&gt;
A comparison at http://www.diamondtin.com/2014/sqlalchemy-gevent-mysql-python-drivers-comparison/ illustrates performance metrics observed with all four of these drivers, with MySQL-python built both without and with the &amp;quot;greenify&amp;quot; system.&lt;br /&gt;
&lt;br /&gt;
A summary of the status of all four drivers is as follows.&lt;br /&gt;
&lt;br /&gt;
=== MySQL-Python ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/farcepest/MySQLdb1 MySQL-Python] is the most widely used Python driver.  However, it does not support Python 3, and it does not support async systems unless used within a thread pool.   While a system known as Greenify can potentially resolve the latter situation, the Python 3 limitation is still a deal-breaker - pull request with Python 3 support [https://github.com/farcepest/MySQLdb1/pull/62 was proposed] to MySQL-python in April 2014 and still not merged.&lt;br /&gt;
&lt;br /&gt;
=== mysqlclient ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/PyMySQL/mysqlclient-python mysqlclient] is a fork of MySQL-python. It add Python 3.3 support and merges some pull requests.&lt;br /&gt;
&lt;br /&gt;
=== OurSQL ===&lt;br /&gt;
&lt;br /&gt;
[https://pythonhosted.org/oursql/ OurSQL] is an alternative MySQL driver that is also written in C.  It features fast performance and uses a different execution model than MySQL-python, using prepared statements.    OurSQL does have a Python 3 port hosted on [https://launchpad.net/oursql/py3k/ launchpad] but is not integrated with OurSQL itself nor is it published on Pypi.     Because it is written in C, it does not offer compatibility with async systems including eventlet-style monkeypatching.&lt;br /&gt;
&lt;br /&gt;
Besides the shaky Python 3 support and lack of eventlet support, OurSQL is also not maintained, seeing its last master commit on [https://code.launchpad.net/~jerub/oursql/master 2012-06-05].&lt;br /&gt;
&lt;br /&gt;
=== MySQL-Connector-Python ===&lt;br /&gt;
&lt;br /&gt;
[https://pypi.python.org/pypi/mysql-connector-python MySQL-connector-Python] is a pure Python MySQL driver, and is now released under the auspices of the MySQL project itself, as owned by Oracle.  MySQL-connector-Python supports both Python 3 as well as eventlet monkeypatching, and is well maintained.  It is endorsed by Oracle as the official Oracle-supported driver for MySQL, so to that extent, it is in most ways Openstack's first choice in driver.  However, Oracle refuses to publish MySQL-connector-Python on Pypi, which is critical to the Openstack infrastructure.  Repeated attempts to communicate with Oracle in order to resolve this issue have not made any progress.    Therefore, for this one unfortunate reason, MySQL-connector-Python will not have a place in the Openstack ecosystem unless this issue is resolved.&lt;br /&gt;
&lt;br /&gt;
=== PyMySQL ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/PyMySQL/PyMySQL PyMySQL] is a pure Python MySQL&lt;br /&gt;
driver, first written as a rough port of the MySQL-Python driver.&lt;br /&gt;
PyMySQL meets all of Openstack's criterion for a driver: it is fully&lt;br /&gt;
open source, hosted on Github, released on Pypi, is actively&lt;br /&gt;
maintained, is written in pure Python so is eventlet-monkeypatch&lt;br /&gt;
compatible, and is fully Python 3 compatible.&lt;br /&gt;
&lt;br /&gt;
As this document is named &amp;quot;PyMySQL Evaluation&amp;quot;, it should be apparent that&lt;br /&gt;
this is the driver Openstack is currently leaning towards.  However, it does&lt;br /&gt;
have some minor code quality issues which hopefully can be addressed in some&lt;br /&gt;
way; the section below titled &amp;quot;PyMySQL Code Review&amp;quot; will summarize the&lt;br /&gt;
current state of the code.  We are in fact leaning towards this driver because&lt;br /&gt;
its the only one that actually meets all criteria.  PyMySQL appears to be&lt;br /&gt;
fairly widely used, especially considering its low profile, so it's possible&lt;br /&gt;
that this is the case for many other users as well.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===MySQL DB Drivers Comparison===&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Project !! PyPi hosted !! Eventlet friendly !! Python 3 compatibility !! Maturity and/or stability  !! Comment&lt;br /&gt;
|-&lt;br /&gt;
| MySQL-Python || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: orange;&amp;quot; | Partial || style=&amp;quot;background-color: red;&amp;quot; | No  || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || Can be monkeypatched by eventlet, but only to enable thread pooling&lt;br /&gt;
|-&lt;br /&gt;
| mysqlclient || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: orange;&amp;quot; | Partial || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes  || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || MySQL-python fork, so should be similar to it&lt;br /&gt;
|-&lt;br /&gt;
| OurSQL || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: red;&amp;quot; | No || style=&amp;quot;background-color: orange;&amp;quot; | Yes, but not Pypi hosted  || style=&amp;quot;background-color: orange;&amp;quot; | ??? || Has not seen commits/releases in two years&lt;br /&gt;
|-&lt;br /&gt;
| MySQL-Connector-Python || style=&amp;quot;background-color: red;&amp;quot; | No || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes  || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || The official Oracle-supported driver for MySQL&lt;br /&gt;
|-&lt;br /&gt;
| PyMySQL || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes  || style=&amp;quot;background-color: orange;&amp;quot; | Yes, with a [http://lists.openstack.org/pipermail/openstack-dev/2014-December/052864.html proviso]. ||&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== PyMySQL Code Review ==&lt;br /&gt;
&lt;br /&gt;
PyMySQL started roughly as a pure Python port of MySQL-Python.   The sections below will&lt;br /&gt;
detail various aspects of the driver and the status of the code.   Overall, the general&lt;br /&gt;
tone of PyMySQL is one of code that was written in the spirit of pragmatism and immediate&lt;br /&gt;
need; it is straightforward, relatively free of serious antipatterns and performs&lt;br /&gt;
its task in a matter-of-fact way.  However,&lt;br /&gt;
it lacks polish and completeness in many areas that would normally be in better&lt;br /&gt;
condition for a more mature project.  These areas are all highly fixable, and it is&lt;br /&gt;
hoped that these items can be shared with the current PyMySQL developers, through&lt;br /&gt;
a combination of this document itself as well as new issues and pull requests&lt;br /&gt;
on the PyMySQL tracker, so that this fairly promising library can be pushed&lt;br /&gt;
to the next level.   This would require that the developers are amenable to&lt;br /&gt;
these improvements.&lt;br /&gt;
&lt;br /&gt;
=== Coding Style / Pep8 ===&lt;br /&gt;
&lt;br /&gt;
PyMySQL's coding style is pretty good.  The majority of it passes all&lt;br /&gt;
flake8 tests, save for a few dozen whitespace issues and&lt;br /&gt;
some long lines here and there within the core&lt;br /&gt;
modules, and moreso within the tests which appear to be a bit more&lt;br /&gt;
crufty than the core modules.  A full run of flake8 produces only 128&lt;br /&gt;
errors, and with a default run of autopep8 against E1,E2,E3, we can&lt;br /&gt;
fix all of them in one pass except for 38 remaining long line warnings.&lt;br /&gt;
The code layout is mostly idiomatic and reasonable.&lt;br /&gt;
&lt;br /&gt;
The codebase is probably short on docstrings.  Those methods that are&lt;br /&gt;
part of the public DBAPI do have docstrings, but tend to be extremely&lt;br /&gt;
terse (example, docstring for cursor.execute(): &amp;quot;'''Execute a query'''&amp;quot;).&lt;br /&gt;
Other methods that aren't public tend to not have any docstrings, but&lt;br /&gt;
the codebase is not at all hard to read and the purpose of methods and&lt;br /&gt;
functions is pretty easy to discern just by their names and implementations,&lt;br /&gt;
however those who seek inline documentation as a means to understand a codebase&lt;br /&gt;
will be disappointed.   The terseness of public API docstrings is more of&lt;br /&gt;
an issue in terms of the overall lack of documentation for PyMySQL,&lt;br /&gt;
see the section &amp;quot;Documentation&amp;quot; below.&lt;br /&gt;
&lt;br /&gt;
=== Test Coverage ===&lt;br /&gt;
&lt;br /&gt;
The test coverage for PyMySQL is definitely lacking.   From a visual&lt;br /&gt;
inspection alone, it's apparent that of the dozen or so test modules,&lt;br /&gt;
most of them have less then ten tests each and the modules are very short.&lt;br /&gt;
For a full test run, there's only 126 tests; contrast this to&lt;br /&gt;
MySQL-Connector-Python which has 531 tests.&lt;br /&gt;
&lt;br /&gt;
Additionally, all of the tests are live round-trip tests against a&lt;br /&gt;
real database.  There are no non-live unit tests of any kind,&lt;br /&gt;
leaving codepaths that are not easily exercised&lt;br /&gt;
on a generic MySQL database out in the cold.  This means that features designed&lt;br /&gt;
for particular versions of MySQL, particular datatypes, particular error&lt;br /&gt;
condtions, and particular kinds of message packets that aren't created for&lt;br /&gt;
real are simply not tested.&lt;br /&gt;
&lt;br /&gt;
The lack of tests is apparent when run with coverage.  The test suite itself&lt;br /&gt;
does not appear to have any signs that it is normally run with coverage&lt;br /&gt;
turned on, with no directives in tox.ini or similar.   With coverage,&lt;br /&gt;
the overall coverage is 85%, but the modules most lacking in coverage&lt;br /&gt;
are also the most critical to PyMySQL's core functionality,&lt;br /&gt;
connections.py, converters.py and cursors.py:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Name                                                              Stmts   Miss  Cover&lt;br /&gt;
-------------------------------------------------------------------------------------&lt;br /&gt;
pymysql/__init__                                                     51      3    94%&lt;br /&gt;
pymysql/_compat                                                      14      4    71%&lt;br /&gt;
pymysql/_socketio                                                    72     30    58%&lt;br /&gt;
pymysql/charset                                                     228      3    99%&lt;br /&gt;
pymysql/connections                                                 827    188    77%&lt;br /&gt;
pymysql/constants/CLIENT                                             18      0   100%&lt;br /&gt;
pymysql/constants/COMMAND                                            32      0   100%&lt;br /&gt;
pymysql/constants/ER                                                471      0   100%&lt;br /&gt;
pymysql/constants/FIELD_TYPE                                         29      0   100%&lt;br /&gt;
pymysql/constants/FLAG                                               15      0   100%&lt;br /&gt;
pymysql/constants/SERVER_STATUS                                      10      0   100%&lt;br /&gt;
pymysql/constants/__init__                                            0      0   100%&lt;br /&gt;
pymysql/converters                                                  148     51    66%&lt;br /&gt;
pymysql/cursors                                                     273     37    86%&lt;br /&gt;
pymysql/err                                                          40      1    98%&lt;br /&gt;
pymysql/tests/__init__                                               14      5    64%&lt;br /&gt;
pymysql/tests/base                                                   25      2    92%&lt;br /&gt;
pymysql/tests/test_DictCursor                                        73      2    97%&lt;br /&gt;
pymysql/tests/test_SSCursor                                          56      6    89%&lt;br /&gt;
pymysql/tests/test_basic                                            186      4    98%&lt;br /&gt;
pymysql/tests/test_connection                                        57      8    86%&lt;br /&gt;
pymysql/tests/test_example                                           17      2    88%&lt;br /&gt;
pymysql/tests/test_issues                                           300     48    84%&lt;br /&gt;
pymysql/tests/test_load_local                                        39      2    95%&lt;br /&gt;
pymysql/tests/test_nextset                                           43      4    91%&lt;br /&gt;
pymysql/tests/thirdparty/__init__                                     7      5    29%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/__init__                        6      2    67%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/capabilities                  196     23    88%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/dbapi20                       423    121    71%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_capabilities      73      6    92%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_dbapi20          100      2    98%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_nonstandard       57      3    95%&lt;br /&gt;
pymysql/times                                                        12      0   100%&lt;br /&gt;
pymysql/util                                                         14      7    50%&lt;br /&gt;
-------------------------------------------------------------------------------------&lt;br /&gt;
TOTAL                                                              3926    569    86%&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The lack of coverage in converters.py refers to functions related to handling&lt;br /&gt;
incoming bound values as well as outgoing result values.    Key data escaping&lt;br /&gt;
features such as escaping of dictionaries, sets, some unicode objects and&lt;br /&gt;
byte objects as well as many pathways into date and time-processing functions are not&lt;br /&gt;
covered.&lt;br /&gt;
&lt;br /&gt;
In connections.py, uncovered features include various SSL and&lt;br /&gt;
very legacy (e.g. version 3.23) MySQL features, but also some protocol parsing&lt;br /&gt;
features.&lt;br /&gt;
&lt;br /&gt;
In cursors.py, there is coverage for an elaborate and mostly undocumented&lt;br /&gt;
performance-related feature that rewrites an INSERT statement to have an&lt;br /&gt;
extended VALUES clause (_do_execute_many), however it fails to cover the very&lt;br /&gt;
likely case where the function will need to break the input set into&lt;br /&gt;
multiple chunks:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;gt;         for arg in args:&lt;br /&gt;
&amp;gt;             v = values % escape(arg, conn)&lt;br /&gt;
&amp;gt;             if isinstance(v, text_type):&lt;br /&gt;
&amp;gt;                 v = v.encode(encoding)&lt;br /&gt;
&amp;gt;             if len(sql) + len(v) + 1 &amp;gt; max_stmt_length:&lt;br /&gt;
!                 rows += self.execute(sql)&lt;br /&gt;
!                 sql = bytearray(prefix)&lt;br /&gt;
&amp;gt;             else:&lt;br /&gt;
&amp;gt;                 sql += b','&lt;br /&gt;
&amp;gt;             sql += v&lt;br /&gt;
&amp;gt;         rows += self.execute(sql)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The most prominent missing coverage in cursors.py seems to be&lt;br /&gt;
for its &amp;quot;scrollable cursor&amp;quot; support.&lt;br /&gt;
&lt;br /&gt;
Beyond the lack of completeness in testing, many of the tests themselves&lt;br /&gt;
are written in an expedient &amp;quot;one giant test&amp;quot; style where dozens of&lt;br /&gt;
assertions and individual behaviors are lumped into one big test&lt;br /&gt;
case (see test_SSCursor.py, test_basic.py-&amp;gt;test_datatypes for examples).&lt;br /&gt;
Test cases like these are difficult to work with when debugging&lt;br /&gt;
regresions as well as when tests for new features need to be added, and ideally&lt;br /&gt;
these should be broken out into clean single-feature tests with consistent&lt;br /&gt;
fixtures.&lt;br /&gt;
&lt;br /&gt;
There is also a suite called &amp;quot;test_issues&amp;quot;, which intends to&lt;br /&gt;
accumulate tests against specific issues that have been reported. This&lt;br /&gt;
isn't a bad idea, though it would be nice to see an effort made into&lt;br /&gt;
categorizing these tests into the actual features they are testing,&lt;br /&gt;
rather than a meaningless list of issue numbers, which could then form&lt;br /&gt;
as the basis for new suites centered around those areas of&lt;br /&gt;
functionality.  The issues reported should be used as the inspiration&lt;br /&gt;
for improvement of the test suite, rather than just another line-item&lt;br /&gt;
to be filed away.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Library Documentation ===&lt;br /&gt;
&lt;br /&gt;
As the Python DBAPI is already a well-documented API, and the users of&lt;br /&gt;
PyMySQL are typically coming from MySQL-Python which is documented&lt;br /&gt;
to some degree and for which the use contract is widely known, it seems&lt;br /&gt;
unlikely that users of PyMySQL are eager for comprehensive documentation&lt;br /&gt;
specific to this library.&lt;br /&gt;
&lt;br /&gt;
That being said, as far as can be discerned, PyMySQL has absolutely no&lt;br /&gt;
library documentation of any kind.  There's a README which&lt;br /&gt;
essentially refers to pep-249, an example.py in the root which shows a&lt;br /&gt;
very basic connection / round trip, and that is absolutely it. In&lt;br /&gt;
order to know anything about the API, specific parameters, etc., one&lt;br /&gt;
either has to go off of the MySQL-Python documentation and assume&lt;br /&gt;
PyMySQL also features the same parameter, or read the source code.&lt;br /&gt;
&lt;br /&gt;
PyMySQL probably hasn't had much urgent need for real documentation thus&lt;br /&gt;
far, however it would be a great idea if it at least made the start&lt;br /&gt;
of a rudimentary Sphinx build including autodoc for module documentation&lt;br /&gt;
which could then be published up to RTD.   This would be very easy to get&lt;br /&gt;
started, and once present, new documentation sections can be added&lt;br /&gt;
iteratively.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Architecture and Performance ===&lt;br /&gt;
&lt;br /&gt;
As stated before, PyMySQL has a really matter-of-fact and straightforward&lt;br /&gt;
implementation.   The code is very readable and it's easy to discern even&lt;br /&gt;
unusual features such as the INSERT..VALUES rewriting.  As far as the design,&lt;br /&gt;
there are lots of areas where performance suffers a bit at the Python level;&lt;br /&gt;
these areas could be greatly improved very easily with a little bit of&lt;br /&gt;
attention.&lt;br /&gt;
&lt;br /&gt;
As we are dealing with a driver that is communicating with a low level&lt;br /&gt;
protocol over a socket, it's critical that sending and receiving messages&lt;br /&gt;
is done as efficiently as possible.  The mechanisms used in PyMySQL rely&lt;br /&gt;
heavily on standard Python objects, without the use of __slots__; this means&lt;br /&gt;
that for individual messages on a socket, we are creating a heavyweight object,&lt;br /&gt;
calling its __init__() method and a creating a new __dict__ each time;&lt;br /&gt;
the most common object, OKPacketWrapper(), already wraps a MysqlPacket()&lt;br /&gt;
object, so we're doing two __init__()s in this common case.  OKPacketWrapper&lt;br /&gt;
also implements a `__getattr__` scheme&lt;br /&gt;
to proxy attribute access from itself to the internal MysqlPacket; this is a&lt;br /&gt;
an inefficient mode of operation in Python as it means a lookup must first&lt;br /&gt;
fail on the OKPacketWrapper before it invokes the `__getattr__()` method&lt;br /&gt;
and does another attribute lookup.   A reorg of the packet classes to produce&lt;br /&gt;
far fewer objects for messages as well as to use __slots__ would be&lt;br /&gt;
recommended.&lt;br /&gt;
&lt;br /&gt;
There are also lots of areas where function call overhead is unnecessarily&lt;br /&gt;
high due to the organization of calls.  For example, when calling upon&lt;br /&gt;
cursor.execute(), for parameters passed as a dictionary under Python 2K,&lt;br /&gt;
the isinstance() builtin function will be called on the incoming arguments&lt;br /&gt;
four times at a minimum, with two of each call against the exact same&lt;br /&gt;
criteria.  Additional logic will then call isinstance() on every key of&lt;br /&gt;
the dictionary as well as at least twice on every value within the dictionary,&lt;br /&gt;
in order to determine if the value needs to be converted to bytes as well&lt;br /&gt;
as for some top-level escaping logic checks; the escaping logic then&lt;br /&gt;
goes further with more checks for type() to match the object to a final&lt;br /&gt;
converter function.   While the flow of this logic does appear to be&lt;br /&gt;
reasonable and straightforward, it isn't ideal for a low-level database&lt;br /&gt;
driver library where speed is a high priority; the various wrappers and&lt;br /&gt;
processors can easily be reorganized here to reduce the number of isinstance()&lt;br /&gt;
and type() calls significantly, which would save a lot on function call&lt;br /&gt;
overhead.  It is likely there are many other areas in the codebase where&lt;br /&gt;
similar reductions in call counts can be made without too much disruption.&lt;br /&gt;
&lt;br /&gt;
In Appendix A, a short performance suite is illustrated.  This&lt;br /&gt;
is run against PyMySQL, Mysql-Connector-Python, and MySQL-Python.&lt;br /&gt;
PyMySQL does in fact demonstrate the&lt;br /&gt;
highest call-count behavior, logging 26,850,503 function calls&lt;br /&gt;
compared to MySQL-connector's 11,674,074 for the same operations.  To PyMySQL's&lt;br /&gt;
credit, it somehow completes the test a bit faster than MySQL-connector&lt;br /&gt;
despite having more than twice as many function calls; this probably indicates&lt;br /&gt;
the use of something very slow within MySQL-connector, such as catching&lt;br /&gt;
an exception within a tight loop.  However, the real bar we're comparing to&lt;br /&gt;
is MySQL-Python, which is written&lt;br /&gt;
in C and does the whole test with only 47,000 Python function calls; while&lt;br /&gt;
PyMySQL and MySQL-connector-Python compete for time within the 16-20 second&lt;br /&gt;
range, MySQL-Python completes the whole test in **0.9 seconds**, a 1600%&lt;br /&gt;
improvement over PyMySQL.   This speed difference is not PyMySQL's fault,&lt;br /&gt;
as pure Python is known to be extemely slow compared to C code.&lt;br /&gt;
However, there's a lot of easy wins in PyMySQL's codebase where the function&lt;br /&gt;
call count could probably be reduced dramatically to be even better than that&lt;br /&gt;
of MySQL-connector-Python; ideally, the driver would be able to run this&lt;br /&gt;
test possibly 5-6x slower than MySQL-Python rather than 16x.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== History Documentation ===&lt;br /&gt;
&lt;br /&gt;
PyMySQL includes a CHANGELOG file in its root which describes the bug fixes&lt;br /&gt;
and features of each release.  The construction of this file is definitely&lt;br /&gt;
within the &amp;quot;expedient&amp;quot; style noted for PyMySQL overall, and for it to be&lt;br /&gt;
genuinely useful for enterprise-level software, it would need to be&lt;br /&gt;
much more complete in its detail.    Each bug fix mentioned is only referred to&lt;br /&gt;
tersely without any link to a pull request or bug report.  While some can&lt;br /&gt;
be linked to a specific change through detective work&lt;br /&gt;
(&amp;quot;Cursor.fetchall() and .fetchmany now return list, not tuple&amp;quot;), it would&lt;br /&gt;
be very difficult for many&lt;br /&gt;
(&amp;quot;Fixed BIT type handling&amp;quot;, &amp;quot;Fixed GC errors&amp;quot; - what was the issue?)&lt;br /&gt;
and in many cases impossible (&amp;quot;Improved Py3k support&amp;quot;).  The file also&lt;br /&gt;
includes no dates so one has to look at the git tags to figure this&lt;br /&gt;
part out (fortunately, releases to seem to be git tagged at least).&lt;br /&gt;
For a user trying to ascertain if particular issues are&lt;br /&gt;
fixed or if behaviors have changed, this file is at best highly frustrating.&lt;br /&gt;
&lt;br /&gt;
A listing of release dates illustrates an average of two releases per year,&lt;br /&gt;
excluding 2012 when PyMySQL was seeking a new maintainer:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
2014-12-02 12:05:10 -0200  (tag: pymysql-0.6.3)&lt;br /&gt;
2014-04-21 14:28:47 -0300  (tag: pymysql-0.6.2)&lt;br /&gt;
2013-10-11 13:36:56 -0300  (tag: pymysql-0.6.1)&lt;br /&gt;
2013-10-04 14:25:14 -0300  (tag: pymysql-0.6)&lt;br /&gt;
2011-11-08 10:27:41 -0800  (tag: pymysql-0.5)&lt;br /&gt;
2010-12-27 17:28:16 +0000  (tag: pymysql-0.4)&lt;br /&gt;
2010-09-03 00:55:29 +0000  (tag: pymysql-0.3)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Community Involvement ===&lt;br /&gt;
&lt;br /&gt;
This is an area where PyMySQL seems to shine, and might even be an advantage&lt;br /&gt;
as compared to MySQL-Connector-Python, which remains under the cloak&lt;br /&gt;
of MySQL's vastly overstuffed bugtracker under the ownership of&lt;br /&gt;
the famously-opaque Oracle.   For a small, low key library without&lt;br /&gt;
any real documentation and only 126 tests, it features 33 contributors on&lt;br /&gt;
Github and is up to pull request #285, which is quite high.  Because&lt;br /&gt;
PyMySQL is small and straightforward, it does lend itself to contribution&lt;br /&gt;
and seems to do very well in this area.   The developers so far have been&lt;br /&gt;
responsive to issues and pull requests made by the author.&lt;br /&gt;
&lt;br /&gt;
Based on github contributor graphs it appears that PyMySQL had a lull&lt;br /&gt;
in 2012 but was picked up again afterwards in 2013.  As of 2012, its most&lt;br /&gt;
prominent developer Pete Hunt stopped development, and on July 25, 2013&lt;br /&gt;
added a note to the README that the project was looking for a new maintainer.&lt;br /&gt;
As of 2013 it was picked up by the current most prominent developers&lt;br /&gt;
Marcel Rodrigues (github username lecram) and INADA Naoki&lt;br /&gt;
(github username methane).&lt;br /&gt;
&lt;br /&gt;
Most issues that have been noted thus far within the areas of testing,&lt;br /&gt;
documentation and architecture are all very fixable; if the Openstack community&lt;br /&gt;
can provide resources to address some of these areas, and if the PyMySQL&lt;br /&gt;
developers are reasonably open to accepting new changes and producing new&lt;br /&gt;
releases in a timely fashion, the PyMySQL driver&lt;br /&gt;
could rapidly become a fully formidable contender in the MySQL driver field.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Appendix A - Performance Tests ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
import cProfile&lt;br /&gt;
import StringIO&lt;br /&gt;
import pstats&lt;br /&gt;
import contextlib&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextlib.contextmanager&lt;br /&gt;
def profiled(dbapi):&lt;br /&gt;
    pr = cProfile.Profile()&lt;br /&gt;
    pr.enable()&lt;br /&gt;
    yield&lt;br /&gt;
    pr.disable()&lt;br /&gt;
    s = StringIO.StringIO()&lt;br /&gt;
    ps = pstats.Stats(pr, stream=s).sort_stats('cumulative')&lt;br /&gt;
    print &amp;quot;DBAPI:  %s&amp;quot; % dbapi&lt;br /&gt;
    ps.print_stats()&lt;br /&gt;
    # uncomment this to see who's calling what&lt;br /&gt;
    # ps.print_callers()&lt;br /&gt;
    print &amp;quot;total calls: %s&amp;quot; % s.getvalue()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
import MySQLdb&lt;br /&gt;
import pymysql&lt;br /&gt;
from mysql import connector as mysqlconnector&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def go(dbapi):&lt;br /&gt;
    conn = dbapi.connect(&lt;br /&gt;
        user='scott', passwd='tiger', host='localhost', db='test')&lt;br /&gt;
    cursor = conn.cursor()&lt;br /&gt;
    cursor.execute(&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
CREATE TABLE IF NOT EXISTS test_things (&lt;br /&gt;
    x INTEGER,&lt;br /&gt;
    y VARCHAR(255),&lt;br /&gt;
    z FLOAT&lt;br /&gt;
) engine=InnoDB&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;)&lt;br /&gt;
    cursor.execute(&amp;quot;DELETE from test_things&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    with profiled(dbapi):&lt;br /&gt;
        for row in xrange(1000):&lt;br /&gt;
            cursor.execute(&lt;br /&gt;
                &amp;quot;INSERT INTO test_things (x, y, z) &amp;quot;&lt;br /&gt;
                &amp;quot;VALUES (%(x)s, %(y)s, %(z)s)&amp;quot;,&lt;br /&gt;
                {&amp;quot;x&amp;quot;: row, &amp;quot;y&amp;quot;: &amp;quot;row number %d&amp;quot; % row,&lt;br /&gt;
                 &amp;quot;z&amp;quot;: row * 4.57292, }&lt;br /&gt;
            )&lt;br /&gt;
&lt;br /&gt;
        for x in xrange(500):&lt;br /&gt;
            cursor.execute(&lt;br /&gt;
                &amp;quot;select * from test_things&amp;quot;)&lt;br /&gt;
            for row in cursor.fetchall():&lt;br /&gt;
                row[0], row[1], row[2]&lt;br /&gt;
&lt;br /&gt;
    cursor.close()&lt;br /&gt;
    conn.close()&lt;br /&gt;
&lt;br /&gt;
go(pymysql)&lt;br /&gt;
go(mysqlconnector)&lt;br /&gt;
go(MySQLdb)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DBAPI:  &amp;lt;module 'pymysql' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/__init__.pyc'&amp;gt;&lt;br /&gt;
total calls:          26850503 function calls in 16.017 seconds&lt;br /&gt;
&lt;br /&gt;
   Ordered by: cumulative time&lt;br /&gt;
&lt;br /&gt;
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;
     1500    0.010    0.000   16.016    0.011 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:107(execute)&lt;br /&gt;
     1500    0.003    0.000   15.976    0.011 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:271(_query)&lt;br /&gt;
     1500    0.003    0.000   15.952    0.011 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:708(query)&lt;br /&gt;
     1500    0.003    0.000   15.911    0.011 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:854(_read_query_result)&lt;br /&gt;
     1500    0.004    0.000   15.905    0.011 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:1060(read)&lt;br /&gt;
      500    0.003    0.000   15.658    0.031 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:1106(_read_result_packet)&lt;br /&gt;
      500    0.921    0.002   15.527    0.031 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:1139(_read_rowdata_packet)&lt;br /&gt;
   500000    3.404    0.000    9.210    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:1152(_read_row_from_packet)&lt;br /&gt;
  2012000    1.179    0.000    5.374    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:314(read_length_coded_string)&lt;br /&gt;
   504500    1.533    0.000    5.246    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:808(_read_packet)&lt;br /&gt;
  2014500    1.265    0.000    3.056    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:296(read_length_encoded_integer)&lt;br /&gt;
  3539500    2.469    0.000    2.782    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:248(read)&lt;br /&gt;
  1009000    0.795    0.000    2.300    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:833(_read_bytes)&lt;br /&gt;
  1009000    1.178    0.000    1.400    0.000 {method 'read' of '_io.BufferedReader' objects}&lt;br /&gt;
   508500    0.381    0.000    0.699    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/util.py:3(byte2int)&lt;br /&gt;
  5058000    0.468    0.000    0.468    0.000 {len}&lt;br /&gt;
   500500    0.219    0.000    0.453    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:1098(_check_packet_is_eof)&lt;br /&gt;
   504500    0.211    0.000    0.380    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:342(check_error)&lt;br /&gt;
  2504000    0.259    0.000    0.259    0.000 {method 'append' of 'list' objects}&lt;br /&gt;
   501500    0.182    0.000    0.231    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:329(is_eof_packet)&lt;br /&gt;
     3500    0.009    0.000    0.222    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/_socketio.py:45(readinto)&lt;br /&gt;
  1022000    0.205    0.000    0.205    0.000 {_struct.unpack}&lt;br /&gt;
     3500    0.203    0.000    0.203    0.000 {method 'recv_into' of '_socket.socket' objects}&lt;br /&gt;
   526000    0.198    0.000    0.198    0.000 {isinstance}&lt;br /&gt;
  1501000    0.197    0.000    0.197    0.000 {method 'get' of 'dict' objects}&lt;br /&gt;
   504500    0.173    0.000    0.173    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:241(__init__)&lt;br /&gt;
   504500    0.169    0.000    0.169    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:339(is_error_packet)&lt;br /&gt;
  2014500    0.162    0.000    0.162    0.000 {ord}&lt;br /&gt;
      500    0.006    0.000    0.127    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:1176(_get_descriptions)&lt;br /&gt;
     2000    0.003    0.000    0.088    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:361(__init__)&lt;br /&gt;
     2000    0.020    0.000    0.085    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:365(__parse_field_descriptor)&lt;br /&gt;
   500000    0.055    0.000    0.055    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/converters.py:301(through)&lt;br /&gt;
     1500    0.006    0.000    0.037    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:877(_execute_command)&lt;br /&gt;
     1500    0.003    0.000    0.027    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:848(_write_bytes)&lt;br /&gt;
     1500    0.002    0.000    0.024    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/socket.py:223(meth)&lt;br /&gt;
     1500    0.021    0.000    0.021    0.000 {method 'sendall' of '_socket.socket' objects}&lt;br /&gt;
     1500    0.020    0.000    0.021    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:278(_do_get_result)&lt;br /&gt;
     1000    0.003    0.000    0.019    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:97(_escape_args)&lt;br /&gt;
     4000    0.002    0.000    0.015    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:101(&amp;lt;genexpr&amp;gt;)&lt;br /&gt;
     1000    0.001    0.000    0.014    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:1089(_read_ok_packet)&lt;br /&gt;
     3000    0.003    0.000    0.013    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:674(escape)&lt;br /&gt;
     1000    0.005    0.000    0.012    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:416(__init__)&lt;br /&gt;
     8000    0.011    0.000    0.011    0.000 {method 'decode' of 'str' objects}&lt;br /&gt;
     3500    0.002    0.000    0.006    0.000 {method '_checkReadable' of '_io._IOBase' objects}&lt;br /&gt;
     4000    0.002    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:128(&amp;lt;genexpr&amp;gt;)&lt;br /&gt;
     2000    0.003    0.000    0.005    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/converters.py:19(escape_item)&lt;br /&gt;
     7500    0.003    0.000    0.005    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:117(ensure_bytes)&lt;br /&gt;
     2000    0.003    0.000    0.005    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:386(description)&lt;br /&gt;
     5500    0.004    0.000    0.004    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:271(advance)&lt;br /&gt;
      500    0.002    0.000    0.004    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:442(__init__)&lt;br /&gt;
     1000    0.001    0.000    0.004    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:684(escape_string)&lt;br /&gt;
     3500    0.004    0.000    0.004    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/_socketio.py:87(readable)&lt;br /&gt;
     3500    0.003    0.000    0.003    0.000 {method '_checkClosed' of '_io._IOBase' objects}&lt;br /&gt;
     1000    0.001    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/converters.py:59(escape_string)&lt;br /&gt;
     1500    0.003    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:1043(__init__)&lt;br /&gt;
     1500    0.001    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:94(nextset)&lt;br /&gt;
     4000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:397(get_column_length)&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/converters.py:56(escape_float)&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 {method 'sub' of '_sre.SRE_Pattern' objects}&lt;br /&gt;
     3000    0.002    0.000    0.002    0.000 {_struct.pack}&lt;br /&gt;
     6000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:64(_get_db)&lt;br /&gt;
     1500    0.001    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:82(_nextset)&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/util.py:9(int2byte)&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:246(fetchall)&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {min}&lt;br /&gt;
     2500    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:326(is_ok_packet)&lt;br /&gt;
     2000    0.001    0.000    0.001    0.000 {method 'items' of 'dict' objects}&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {getattr}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/converters.py:52(escape_int)&lt;br /&gt;
     1000    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:262(read_all)&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:1056(__del__)&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:69(_check_executed)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/contextlib.py:21(__exit__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 test.py:7(profiled)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
DBAPI:  &amp;lt;module 'mysql.connector' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/__init__.pyc'&amp;gt;&lt;br /&gt;
total calls:          11674074 function calls in 18.316 seconds&lt;br /&gt;
&lt;br /&gt;
   Ordered by: cumulative time&lt;br /&gt;
&lt;br /&gt;
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;
      500    0.611    0.001   17.825    0.036 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:820(fetchall)&lt;br /&gt;
      500    0.002    0.000   10.829    0.022 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:655(get_rows)&lt;br /&gt;
      500    1.505    0.003   10.825    0.022 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:292(read_text_result)&lt;br /&gt;
   504500    3.208    0.000    7.065    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/network.py:219(recv_plain)&lt;br /&gt;
   500000    3.398    0.000    6.297    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:359(row_to_python)&lt;br /&gt;
  1009000    3.039    0.000    3.039    0.000 {method 'recv_into' of '_socket.socket' objects}&lt;br /&gt;
   500000    2.003    0.000    2.283    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:220(read_lc_string_list)&lt;br /&gt;
   500000    0.625    0.000    2.010    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:531(_STRING_to_python)&lt;br /&gt;
   502000    0.355    0.000    1.254    0.000 {method 'decode' of 'bytearray' objects}&lt;br /&gt;
   502000    0.212    0.000    0.899    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/utf_8.py:15(decode)&lt;br /&gt;
   502000    0.688    0.000    0.688    0.000 {_codecs.utf_8_decode}&lt;br /&gt;
   509500    0.498    0.000    0.498    0.000 {_struct.unpack_from}&lt;br /&gt;
     1500    0.009    0.000    0.490    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:452(execute)&lt;br /&gt;
   500000    0.482    0.000    0.482    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:413(_INT_to_python)&lt;br /&gt;
     1500    0.004    0.000    0.436    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:705(cmd_query)&lt;br /&gt;
   500000    0.352    0.000    0.352    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:405(_FLOAT_to_python)&lt;br /&gt;
   504500    0.326    0.000    0.326    0.000 {method 'extend' of 'bytearray' objects}&lt;br /&gt;
     1500    0.006    0.000    0.318    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:481(_send_cmd)&lt;br /&gt;
  2501500    0.273    0.000    0.273    0.000 {method 'append' of 'list' objects}&lt;br /&gt;
   500500    0.240    0.000    0.240    0.000 {method 'startswith' of 'bytearray' objects}&lt;br /&gt;
   524002    0.143    0.000    0.143    0.000 {isinstance}&lt;br /&gt;
     1500    0.010    0.000    0.114    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:613(_handle_result)&lt;br /&gt;
  1004500    0.109    0.000    0.109    0.000 {len}&lt;br /&gt;
   500000    0.086    0.000    0.086    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:204(description)&lt;br /&gt;
     2000    0.013    0.000    0.038    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:226(parse_column)&lt;br /&gt;
     1000    0.008    0.000    0.034    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:346(_process_params_dict)&lt;br /&gt;
     1500    0.006    0.000    0.034    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/network.py:118(send_plain)&lt;br /&gt;
     1500    0.002    0.000    0.023    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/socket.py:223(meth)&lt;br /&gt;
     1500    0.020    0.000    0.020    0.000 {method 'sendall' of '_socket.socket' objects}&lt;br /&gt;
     5000    0.007    0.000    0.014    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/catch23.py:79(struct_unpack)&lt;br /&gt;
     1000    0.001    0.000    0.014    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:562(_handle_ok)&lt;br /&gt;
    12000    0.011    0.000    0.011    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:167(read_lc_string)&lt;br /&gt;
     1000    0.004    0.000    0.011    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:199(parse_ok)&lt;br /&gt;
     3000    0.005    0.000    0.009    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:152(to_mysql)&lt;br /&gt;
     3000    0.003    0.000    0.007    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:130(quote)&lt;br /&gt;
     1500    0.002    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:118(make_command)&lt;br /&gt;
     1500    0.004    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:406(_handle_result)&lt;br /&gt;
     3000    0.003    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:102(escape)&lt;br /&gt;
     1500    0.004    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/network.py:53(_prepare_packets)&lt;br /&gt;
     1000    0.002    0.000    0.005    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:252(parse_eof)&lt;br /&gt;
      500    0.001    0.000    0.005    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:579(_handle_eof)&lt;br /&gt;
     2000    0.003    0.000    0.004    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:545(_handle_server_status)&lt;br /&gt;
     1500    0.002    0.000    0.004    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:53(int1store)&lt;br /&gt;
     3000    0.003    0.000    0.003    0.000 {method 'encode' of 'str' objects}&lt;br /&gt;
     9001    0.003    0.000    0.003    0.000 {method 'replace' of 'str' objects}&lt;br /&gt;
     1500    0.003    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:295(_reset_result)&lt;br /&gt;
     6027    0.003    0.000    0.003    0.000 {method 'format' of 'str' objects}&lt;br /&gt;
     2500    0.003    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:296(read_lc_int)&lt;br /&gt;
     4500    0.002    0.000    0.002    0.000 {_struct.pack}&lt;br /&gt;
     3000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:1221(_set_unread_result)&lt;br /&gt;
      500    0.001    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:219(parse_column_count)&lt;br /&gt;
      500    0.001    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:757(_handle_eof)&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 {repr}&lt;br /&gt;
     4527    0.001    0.000    0.001    0.000 {getattr}&lt;br /&gt;
     4000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/constants.py:34(flag_is_set)&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:383(_handle_noresultset)&lt;br /&gt;
     4000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:1233(_get_unread_result)&lt;br /&gt;
     2001    0.001    0.000    0.001    0.000 {method 'items' of 'dict' objects}&lt;br /&gt;
     3000    0.001    0.000    0.001    0.000 {method 'lower' of 'str' objects}&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:307(_have_unread_result)&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:173(_str_to_mysql)&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 {range}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:169(_float_to_mysql)&lt;br /&gt;
     1000    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:161(_int_to_mysql)&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:1313(_get_getwarnings)&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:200(reset)&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:397(_handle_resultset)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/__init__.py:71(search_function)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/ascii.py:41(getregentry)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/contextlib.py:21(__exit__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/__init__.py:49(normalize_encoding)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/codecs.py:77(__new__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {__import__}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 test.py:7(profiled)&lt;br /&gt;
        3    0.000    0.000    0.000    0.000 {method 'get' of 'dict' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'split' of 'str' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'translate' of 'str' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'join' of 'str' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {built-in method __new__ of type object at 0x1001534e8}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {hasattr}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DBAPI:  &amp;lt;module 'MySQLdb' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/__init__.pyc'&amp;gt;&lt;br /&gt;
total calls:          47503 function calls in 0.916 seconds&lt;br /&gt;
&lt;br /&gt;
   Ordered by: cumulative time&lt;br /&gt;
&lt;br /&gt;
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;
     1500    0.008    0.000    0.915    0.001 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:164(execute)&lt;br /&gt;
     1500    0.002    0.000    0.888    0.001 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:353(_query)&lt;br /&gt;
     1500    0.004    0.000    0.498    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:315(_do_query)&lt;br /&gt;
     1500    0.016    0.000    0.388    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:358(_post_get_result)&lt;br /&gt;
     1500    0.001    0.000    0.372    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:324(_fetch_row)&lt;br /&gt;
      500    0.371    0.001    0.371    0.001 {built-in method fetch_row}&lt;br /&gt;
     1500    0.008    0.000    0.359    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:142(_do_get_result)&lt;br /&gt;
     1500    0.002    0.000    0.348    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:351(_get_result)&lt;br /&gt;
     1500    0.346    0.000    0.346    0.000 {method 'store_result' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.134    0.000    0.134    0.000 {method 'query' of '_mysql.connection' objects}&lt;br /&gt;
     4000    0.002    0.000    0.010    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:184(&amp;lt;genexpr&amp;gt;)&lt;br /&gt;
     3000    0.002    0.000    0.008    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/connections.py:267(literal)&lt;br /&gt;
     1500    0.007    0.000    0.007    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:107(_warning_check)&lt;br /&gt;
     3000    0.002    0.000    0.006    0.000 {method 'escape' of '_mysql.connection' objects}&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/converters.py:81(Float2Str)&lt;br /&gt;
     6000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:159(_get_db)&lt;br /&gt;
     2500    0.002    0.000    0.002    0.000 {isinstance}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/connections.py:202(string_literal)&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:380(fetchall)&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 {built-in method describe}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 {method 'string_literal' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {method 'affected_rows' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {method 'insert_id' of '_mysql.connection' objects}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/converters.py:69(Thing2Str)&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 {method 'info' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 {method 'warning_count' of '_mysql.connection' objects}&lt;br /&gt;
     1000    0.000    0.000    0.000    0.000 {method 'iteritems' of 'dict' objects}&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:103(_check_executed)&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 {len}&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 {built-in method field_flags}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/contextlib.py:21(__exit__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 test.py:7(profiled)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mike Bayer</name></author>	</entry>

	<entry>
		<id>https://wiki.openstack.org/w/index.php?title=PyMySQL_evaluation&amp;diff=72539</id>
		<title>PyMySQL evaluation</title>
		<link rel="alternate" type="text/html" href="https://wiki.openstack.org/w/index.php?title=PyMySQL_evaluation&amp;diff=72539"/>
				<updated>2015-01-28T18:36:58Z</updated>
		
		<summary type="html">&lt;p&gt;Mike Bayer: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= PyMySQL Evaluation =&lt;br /&gt;
&lt;br /&gt;
This page will capture issues related to Openstack moving to the [https://github.com/PyMySQL/PyMySQL/ PyMySQL driver] for MySQL/MariaDB dbapi access.&lt;br /&gt;
&lt;br /&gt;
== Rationale ==&lt;br /&gt;
&lt;br /&gt;
While the [https://github.com/farcepest/MySQLdb1 MySQL-Python] driver is a very mature and stable driver, it does not provide compatibility with either Python 3 or with [http://eventlet.net/doc/patching.html eventlet monkeypatching] (MySQL-Python  [https://github.com/eventlet/eventlet/blob/v0.16.1/eventlet/patcher.py#L225-L237 can be monkeypatched] with eventlet, but this feature disabled by default and missed in documentation). So OpenStack's usage of MySQL-Python, combined with the fact that concurrency is provided by eventlet, means that we currently have fully serialized database access within a single process, that is, only one database command occurs at a time within an Openstack Python process.&lt;br /&gt;
&lt;br /&gt;
== Drivers Under Consideration ==&lt;br /&gt;
&lt;br /&gt;
The two drivers that are known to provide eventlet-monkeypatch compatibility are MySQL-Connector and PyMySQL, as they are written in pure Python.   The two other well-known drivers for MySQL are MySQL-Python and OurSQL, both of which are written in C and offer no explicit support for eventlet or async.  They can reportedly be built to support gevent using a system called [https://github.com/CMGS/greenify Greenify], but the maturity and/or stabiltiy of this system is unknown.&lt;br /&gt;
&lt;br /&gt;
A comparison at http://www.diamondtin.com/2014/sqlalchemy-gevent-mysql-python-drivers-comparison/ illustrates performance metrics observed with all four of these drivers, with MySQL-python built both without and with the &amp;quot;greenify&amp;quot; system.&lt;br /&gt;
&lt;br /&gt;
A summary of the status of all four drivers is as follows.&lt;br /&gt;
&lt;br /&gt;
=== MySQL-Python ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/farcepest/MySQLdb1 MySQL-Python] is the most widely used Python driver.  However, it does not support Python 3, and it does not support async systems unless used within a thread pool.   While a system known as Greenify can potentially resolve the latter situation, the Python 3 limitation is still a deal-breaker - pull request with Python 3 support [https://github.com/farcepest/MySQLdb1/pull/62 was proposed] to MySQL-python in April 2014 and still not merged.&lt;br /&gt;
&lt;br /&gt;
=== mysqlclient ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/PyMySQL/mysqlclient-python mysqlclient] is a fork of MySQL-python. It add Python 3.3 support and merges some pull requests.&lt;br /&gt;
&lt;br /&gt;
=== OurSQL ===&lt;br /&gt;
&lt;br /&gt;
[https://pythonhosted.org/oursql/ OurSQL] is an alternative MySQL driver that is also written in C.  It features fast performance and uses a different execution model than MySQL-python, using prepared statements.    OurSQL does have a Python 3 port hosted on [https://launchpad.net/oursql/py3k/ launchpad] but is not integrated with OurSQL itself nor is it published on Pypi.     Because it is written in C, it does not offer compatibility with async systems including eventlet-style monkeypatching.&lt;br /&gt;
&lt;br /&gt;
Besides the shaky Python 3 support and lack of eventlet support, OurSQL is also not maintained, seeing its last master commit on [https://code.launchpad.net/~jerub/oursql/master 2012-06-05].&lt;br /&gt;
&lt;br /&gt;
=== MySQL-Connector-Python ===&lt;br /&gt;
&lt;br /&gt;
[https://pypi.python.org/pypi/mysql-connector-python MySQL-connector-Python] is a pure Python MySQL driver, and is now released under the auspices of the MySQL project itself, as owned by Oracle.  MySQL-connector-Python supports both Python 3 as well as eventlet monkeypatching, and is well maintained.  It is endorsed by Oracle as the official Oracle-supported driver for MySQL, so to that extent, it is in most ways Openstack's first choice in driver.  However, Oracle refuses to publish MySQL-connector-Python on Pypi, which is critical to the Openstack infrastructure.  Repeated attempts to communicate with Oracle in order to resolve this issue have not made any progress.    Therefore, for this one unfortunate reason, MySQL-connector-Python will not have a place in the Openstack ecosystem unless this issue is resolved.&lt;br /&gt;
&lt;br /&gt;
=== PyMySQL ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/PyMySQL/PyMySQL PyMySQL] is a pure Python MySQL&lt;br /&gt;
driver, first written as a rough port of the MySQL-Python driver.&lt;br /&gt;
PyMySQL meets all of Openstack's criterion for a driver: it is fully&lt;br /&gt;
open source, hosted on Github, released on Pypi, is actively&lt;br /&gt;
maintained, is written in pure Python so is eventlet-monkeypatch&lt;br /&gt;
compatible, and is fully Python 3 compatible.&lt;br /&gt;
&lt;br /&gt;
As this document is named &amp;quot;PyMySQL Evaluation&amp;quot;, it should be apparent that&lt;br /&gt;
this is the driver Openstack is currently leaning towards.  However, it does&lt;br /&gt;
have some minor code quality issues which hopefully can be addressed in some&lt;br /&gt;
way; the section below titled &amp;quot;PyMySQL Code Review&amp;quot; will summarize the&lt;br /&gt;
current state of the code.  We are in fact leaning towards this driver because&lt;br /&gt;
its the only one that actually meets all criteria.  PyMySQL appears to be&lt;br /&gt;
fairly widely used, especially considering its low profile, so it's possible&lt;br /&gt;
that this is the case for many other users as well.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===MySQL DB Drivers Comparison===&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Project !! PyPi hosted !! Eventlet friendly !! Python 3 compatibility !! Maturity and/or stability  !! Comment&lt;br /&gt;
|-&lt;br /&gt;
| MySQL-Python || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: orange;&amp;quot; | Partial || style=&amp;quot;background-color: red;&amp;quot; | No  || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || Can be monkeypatched by eventlet, but only to enable thread pooling&lt;br /&gt;
|-&lt;br /&gt;
| mysqlclient || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: orange;&amp;quot; | Partial || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes  || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || MySQL-python fork, so should be similar to it&lt;br /&gt;
|-&lt;br /&gt;
| OurSQL || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: red;&amp;quot; | No || style=&amp;quot;background-color: orange;&amp;quot; | Yes, but not Pypi hosted  || style=&amp;quot;background-color: orange;&amp;quot; | ??? || Has not seen commits/releases in two years&lt;br /&gt;
|-&lt;br /&gt;
| MySQL-Connector-Python || style=&amp;quot;background-color: red;&amp;quot; | No || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes  || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || The official Oracle-supported driver for MySQL&lt;br /&gt;
|-&lt;br /&gt;
| PyMySQL || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes  || style=&amp;quot;background-color: orange;&amp;quot; | Yes, with a [http://lists.openstack.org/pipermail/openstack-dev/2014-December/052864.html proviso]. ||&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== PyMySQL Code Review ==&lt;br /&gt;
&lt;br /&gt;
PyMySQL started roughly as a pure Python port of MySQL-Python.   The sections below will&lt;br /&gt;
detail various aspects of the driver and the status of the code.   Overall, the general&lt;br /&gt;
tone of PyMySQL is one of code that was written in the spirit of pragmatism and immediate&lt;br /&gt;
need; it is straightforward, relatively free of serious antipatterns and performs&lt;br /&gt;
its task in a matter-of-fact way.  However,&lt;br /&gt;
it lacks polish and completeness in many areas that would normally be in better&lt;br /&gt;
condition for a more mature project.  These areas are all highly fixable, and it is&lt;br /&gt;
hoped that these items can be shared with the current PyMySQL developers, through&lt;br /&gt;
a combination of this document itself as well as new issues and pull requests&lt;br /&gt;
on the PyMySQL tracker, so that this fairly promising library can be pushed&lt;br /&gt;
to the next level.   This would require that the developers are amenable to&lt;br /&gt;
these improvements.&lt;br /&gt;
&lt;br /&gt;
=== Coding Style / Pep8 ===&lt;br /&gt;
&lt;br /&gt;
PyMySQL's coding style is pretty good.  The majority of it passes all&lt;br /&gt;
flake8 tests, save for a few dozen whitespace issues and&lt;br /&gt;
some long lines here and there within the core&lt;br /&gt;
modules, and moreso within the tests which appear to be a bit more&lt;br /&gt;
crufty than the core modules.  A full run of flake8 produces only 128&lt;br /&gt;
errors, and with a default run of autopep8 against E1,E2,E3, we can&lt;br /&gt;
fix all of them in one pass except for 38 remaining long line warnings.&lt;br /&gt;
The code layout is mostly idiomatic and reasonable.&lt;br /&gt;
&lt;br /&gt;
The codebase is probably short on docstrings.  Those methods that are&lt;br /&gt;
part of the public DBAPI do have docstrings, but tend to be extremely&lt;br /&gt;
terse (example, docstring for cursor.execute(): &amp;quot;'''Execute a query'''&amp;quot;).&lt;br /&gt;
Other methods that aren't public tend to not have any docstrings, but&lt;br /&gt;
the codebase is not at all hard to read and the purpose of methods and&lt;br /&gt;
functions is pretty easy to discern just by their names and implementations,&lt;br /&gt;
however those who seek inline documentation as a means to understand a codebase&lt;br /&gt;
will be disappointed.   The terseness of public API docstrings is more of&lt;br /&gt;
an issue in terms of the overall lack of documentation for PyMySQL,&lt;br /&gt;
see the section &amp;quot;Documentation&amp;quot; below.&lt;br /&gt;
&lt;br /&gt;
=== Test Coverage ===&lt;br /&gt;
&lt;br /&gt;
The test coverage for PyMySQL is definitely lacking.   From a visual&lt;br /&gt;
inspection alone, it's apparent that of the dozen or so test modules,&lt;br /&gt;
most of them have less then ten tests each and the modules are very short.&lt;br /&gt;
For a full test run, there's only 126 tests; contrast this to&lt;br /&gt;
MySQL-Connector-Python which has 531 tests.&lt;br /&gt;
&lt;br /&gt;
Additionally, all of the tests are live round-trip tests against a&lt;br /&gt;
real database.  There are no non-live unit tests of any kind,&lt;br /&gt;
leaving codepaths that are not easily exercised&lt;br /&gt;
on a generic MySQL database out in the cold.  This means that features designed&lt;br /&gt;
for particular versions of MySQL, particular datatypes, particular error&lt;br /&gt;
condtions, and particular kinds of message packets that aren't created for&lt;br /&gt;
real are simply not tested.&lt;br /&gt;
&lt;br /&gt;
The lack of tests is apparent when run with coverage.  The test suite itself&lt;br /&gt;
does not appear to have any signs that it is normally run with coverage&lt;br /&gt;
turned on, with no directives in tox.ini or similar.   With coverage,&lt;br /&gt;
the overall coverage is 85%, but the modules most lacking in coverage&lt;br /&gt;
are also the most critical to PyMySQL's core functionality,&lt;br /&gt;
connections.py, converters.py and cursors.py:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Name                                                              Stmts   Miss  Cover&lt;br /&gt;
-------------------------------------------------------------------------------------&lt;br /&gt;
pymysql/__init__                                                     51      3    94%&lt;br /&gt;
pymysql/_compat                                                      14      4    71%&lt;br /&gt;
pymysql/_socketio                                                    72     30    58%&lt;br /&gt;
pymysql/charset                                                     228      3    99%&lt;br /&gt;
pymysql/connections                                                 827    188    77%&lt;br /&gt;
pymysql/constants/CLIENT                                             18      0   100%&lt;br /&gt;
pymysql/constants/COMMAND                                            32      0   100%&lt;br /&gt;
pymysql/constants/ER                                                471      0   100%&lt;br /&gt;
pymysql/constants/FIELD_TYPE                                         29      0   100%&lt;br /&gt;
pymysql/constants/FLAG                                               15      0   100%&lt;br /&gt;
pymysql/constants/SERVER_STATUS                                      10      0   100%&lt;br /&gt;
pymysql/constants/__init__                                            0      0   100%&lt;br /&gt;
pymysql/converters                                                  148     51    66%&lt;br /&gt;
pymysql/cursors                                                     273     37    86%&lt;br /&gt;
pymysql/err                                                          40      1    98%&lt;br /&gt;
pymysql/tests/__init__                                               14      5    64%&lt;br /&gt;
pymysql/tests/base                                                   25      2    92%&lt;br /&gt;
pymysql/tests/test_DictCursor                                        73      2    97%&lt;br /&gt;
pymysql/tests/test_SSCursor                                          56      6    89%&lt;br /&gt;
pymysql/tests/test_basic                                            186      4    98%&lt;br /&gt;
pymysql/tests/test_connection                                        57      8    86%&lt;br /&gt;
pymysql/tests/test_example                                           17      2    88%&lt;br /&gt;
pymysql/tests/test_issues                                           300     48    84%&lt;br /&gt;
pymysql/tests/test_load_local                                        39      2    95%&lt;br /&gt;
pymysql/tests/test_nextset                                           43      4    91%&lt;br /&gt;
pymysql/tests/thirdparty/__init__                                     7      5    29%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/__init__                        6      2    67%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/capabilities                  196     23    88%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/dbapi20                       423    121    71%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_capabilities      73      6    92%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_dbapi20          100      2    98%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_nonstandard       57      3    95%&lt;br /&gt;
pymysql/times                                                        12      0   100%&lt;br /&gt;
pymysql/util                                                         14      7    50%&lt;br /&gt;
-------------------------------------------------------------------------------------&lt;br /&gt;
TOTAL                                                              3926    569    86%&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The lack of coverage in converters.py refers to functions related to handling&lt;br /&gt;
incoming bound values as well as outgoing result values.    Key data escaping&lt;br /&gt;
features such as escaping of dictionaries, sets, some unicode objects and&lt;br /&gt;
byte objects as well as many pathways into date and time-processing functions are not&lt;br /&gt;
covered.&lt;br /&gt;
&lt;br /&gt;
In connections.py, uncovered features include various SSL and&lt;br /&gt;
very legacy (e.g. version 3.23) MySQL features, but also some protocol parsing&lt;br /&gt;
features.&lt;br /&gt;
&lt;br /&gt;
In cursors.py, there is coverage for an elaborate and mostly undocumented&lt;br /&gt;
performance-related feature that rewrites an INSERT statement to have an&lt;br /&gt;
extended VALUES clause (_do_execute_many), however it fails to cover the very&lt;br /&gt;
likely case where the function will need to break the input set into&lt;br /&gt;
multiple chunks:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;gt;         for arg in args:&lt;br /&gt;
&amp;gt;             v = values % escape(arg, conn)&lt;br /&gt;
&amp;gt;             if isinstance(v, text_type):&lt;br /&gt;
&amp;gt;                 v = v.encode(encoding)&lt;br /&gt;
&amp;gt;             if len(sql) + len(v) + 1 &amp;gt; max_stmt_length:&lt;br /&gt;
!                 rows += self.execute(sql)&lt;br /&gt;
!                 sql = bytearray(prefix)&lt;br /&gt;
&amp;gt;             else:&lt;br /&gt;
&amp;gt;                 sql += b','&lt;br /&gt;
&amp;gt;             sql += v&lt;br /&gt;
&amp;gt;         rows += self.execute(sql)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The most prominent missing coverage in cursors.py seems to be&lt;br /&gt;
for its &amp;quot;scrollable cursor&amp;quot; support.&lt;br /&gt;
&lt;br /&gt;
Beyond the lack of completeness in testing, many of the tests themselves&lt;br /&gt;
are written in an expedient &amp;quot;one giant test&amp;quot; style where dozens of&lt;br /&gt;
assertions and individual behaviors are lumped into one big test&lt;br /&gt;
case (see test_SSCursor.py, test_basic.py-&amp;gt;test_datatypes for examples).&lt;br /&gt;
Test cases like these are difficult to work with when debugging&lt;br /&gt;
regresions as well as when tests for new features need to be added, and ideally&lt;br /&gt;
these should be broken out into clean single-feature tests with consistent&lt;br /&gt;
fixtures.&lt;br /&gt;
&lt;br /&gt;
There is also a suite called &amp;quot;test_issues&amp;quot;, which intends to&lt;br /&gt;
accumulate tests against specific issues that have been reported. This&lt;br /&gt;
isn't a bad idea, though it would be nice to see an effort made into&lt;br /&gt;
categorizing these tests into the actual features they are testing,&lt;br /&gt;
rather than a meaningless list of issue numbers, which could then form&lt;br /&gt;
as the basis for new suites centered around those areas of&lt;br /&gt;
functionality.  The issues reported should be used as the inspiration&lt;br /&gt;
for improvement of the test suite, rather than just another line-item&lt;br /&gt;
to be filed away.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Library Documentation ===&lt;br /&gt;
&lt;br /&gt;
As the Python DBAPI is already a well-documented API, and the users of&lt;br /&gt;
PyMySQL are typically coming from MySQL-Python which is documented&lt;br /&gt;
to some degree and for which the use contract is widely known, it seems&lt;br /&gt;
unlikely that users of PyMySQL are eager for comprehensive documentation&lt;br /&gt;
specific to this library.&lt;br /&gt;
&lt;br /&gt;
That being said, as far as can be discerned, PyMySQL has absolutely no&lt;br /&gt;
library documentation of any kind.  There's a README which&lt;br /&gt;
essentially refers to pep-249, an example.py in the root which shows a&lt;br /&gt;
very basic connection / round trip, and that is absolutely it. In&lt;br /&gt;
order to know anything about the API, specific parameters, etc., one&lt;br /&gt;
either has to go off of the MySQL-Python documentation and assume&lt;br /&gt;
PyMySQL also features the same parameter, or read the source code.&lt;br /&gt;
&lt;br /&gt;
PyMySQL probably hasn't had much urgent need for real documentation thus&lt;br /&gt;
far, however it would be a great idea if it at least made the start&lt;br /&gt;
of a rudimentary Sphinx build including autodoc for module documentation&lt;br /&gt;
which could then be published up to RTD.   This would be very easy to get&lt;br /&gt;
started, and once present, new documentation sections can be added&lt;br /&gt;
iteratively.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Architecture and Performance ===&lt;br /&gt;
&lt;br /&gt;
As stated before, PyMySQL has a really matter-of-fact and straightforward&lt;br /&gt;
implementation.   The code is very readable and it's easy to discern even&lt;br /&gt;
unusual features such as the INSERT..VALUES rewriting.  As far as the design,&lt;br /&gt;
there are lots of areas where performance suffers a bit at the Python level;&lt;br /&gt;
these areas could be greatly improved very easily with a little bit of&lt;br /&gt;
attention.&lt;br /&gt;
&lt;br /&gt;
As we are dealing with a driver that is communicating with a low level&lt;br /&gt;
protocol over a socket, it's critical that sending and receiving messages&lt;br /&gt;
is done as efficiently as possible.  The mechanisms used in PyMySQL rely&lt;br /&gt;
heavily on standard Python objects, without the use of __slots__; this means&lt;br /&gt;
that for individual messages on a socket, we are creating a heavyweight object,&lt;br /&gt;
calling its __init__() method and a creating a new __dict__ each time;&lt;br /&gt;
the most common object, OKPacketWrapper(), already wraps a MysqlPacket()&lt;br /&gt;
object, so we're doing two __init__()s in this common case.  OKPacketWrapper&lt;br /&gt;
also implements a `__getattr__` scheme&lt;br /&gt;
to proxy attribute access from itself to the internal MysqlPacket; this is a&lt;br /&gt;
an inefficient mode of operation in Python as it means a lookup must first&lt;br /&gt;
fail on the OKPacketWrapper before it invokes the `__getattr__()` method&lt;br /&gt;
and does another attribute lookup.   A reorg of the packet classes to produce&lt;br /&gt;
far fewer objects for messages as well as to use __slots__ would be&lt;br /&gt;
recommended.&lt;br /&gt;
&lt;br /&gt;
There are also lots of areas where function call overhead is unnecessarily&lt;br /&gt;
high due to the organization of calls.  For example, when calling upon&lt;br /&gt;
cursor.execute(), for parameters passed as a dictionary under Python 2K,&lt;br /&gt;
the isinstance() builtin function will be called on the incoming arguments&lt;br /&gt;
four times at a minimum, with two of each call against the exact same&lt;br /&gt;
criteria.  Additional logic will then call isinstance() on every key of&lt;br /&gt;
the dictionary as well as at least twice on every value within the dictionary,&lt;br /&gt;
in order to determine if the value needs to be converted to bytes as well&lt;br /&gt;
as for some top-level escaping logic checks; the escaping logic then&lt;br /&gt;
goes further with more checks for type() to match the object to a final&lt;br /&gt;
converter function.   While the flow of this logic does appear to be&lt;br /&gt;
reasonable and straightforward, it isn't ideal for a low-level database&lt;br /&gt;
driver library where speed is a high priority; the various wrappers and&lt;br /&gt;
processors can easily be reorganized here to reduce the number of isinstance()&lt;br /&gt;
and type() calls significantly, which would save a lot on function call&lt;br /&gt;
overhead.  It is likely there are many other areas in the codebase where&lt;br /&gt;
similar reductions in call counts can be made without too much disruption.&lt;br /&gt;
&lt;br /&gt;
In Appendix A, a short performance suite is illustrated.  This&lt;br /&gt;
is run against PyMySQL, Mysql-Connector-Python, and MySQL-Python.&lt;br /&gt;
PyMySQL does in fact demonstrate the&lt;br /&gt;
highest call-count behavior, logging 26,850,503 function calls&lt;br /&gt;
compared to MySQL-connector's 11,674,074 for the same operations.  To PyMySQL's&lt;br /&gt;
credit, it somehow completes the test a bit faster than MySQL-connector&lt;br /&gt;
despite having more than twice as many function calls; this probably indicates&lt;br /&gt;
the use of something very slow within MySQL-connector, such as catching&lt;br /&gt;
an exception within a tight loop.  However, the real bar we're comparing to&lt;br /&gt;
is MySQL-Python, which is written&lt;br /&gt;
in C and does the whole test with only 47,000 Python function calls; while&lt;br /&gt;
PyMySQL and MySQL-connector-Python compete for time within the 16-20 second&lt;br /&gt;
range, MySQL-Python completes the whole test in **0.9 seconds**, a 1600%&lt;br /&gt;
improvement over PyMySQL.   This speed difference is not PyMySQL's fault,&lt;br /&gt;
as pure Python is known to be extemely slow compared to C code.&lt;br /&gt;
However, there's a lot of easy wins in PyMySQL's codebase where the function&lt;br /&gt;
call count could probably be reduced dramatically to be even better than that&lt;br /&gt;
of MySQL-connector-Python; ideally, the driver would be able to run this&lt;br /&gt;
test possibly 5-6x slower than MySQL-Python rather than 16x.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== History Documentation ===&lt;br /&gt;
&lt;br /&gt;
PyMySQL includes a CHANGELOG file in its root which describes the bug fixes&lt;br /&gt;
and features of each release.  The construction of this file is definitely&lt;br /&gt;
within the &amp;quot;expedient&amp;quot; style noted for PyMySQL overall, and for it to be&lt;br /&gt;
genuinely useful for enterprise-level software, it would need to be&lt;br /&gt;
much more complete in its detail.    Each bug fix mentioned is only referred to&lt;br /&gt;
tersely without any link to a pull request or bug report.  While some can&lt;br /&gt;
be linked to a specific change through detective work&lt;br /&gt;
(&amp;quot;Cursor.fetchall() and .fetchmany now return list, not tuple&amp;quot;), it would&lt;br /&gt;
be very difficult for many&lt;br /&gt;
(&amp;quot;Fixed BIT type handling&amp;quot;, &amp;quot;Fixed GC errors&amp;quot; - what was the issue?)&lt;br /&gt;
and in many cases impossible (&amp;quot;Improved Py3k support&amp;quot;).  The file also&lt;br /&gt;
includes no dates so one has to look at the git tags to figure this&lt;br /&gt;
part out (fortunately, releases to seem to be git tagged at least).&lt;br /&gt;
For a user trying to ascertain if particular issues are&lt;br /&gt;
fixed or if behaviors have changed, this file is at best highly frustrating.&lt;br /&gt;
&lt;br /&gt;
A listing of release dates illustrates an average of two releases per year:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
2014-12-02 12:05:10 -0200  (tag: pymysql-0.6.3)&lt;br /&gt;
2014-04-21 14:28:47 -0300  (tag: pymysql-0.6.2)&lt;br /&gt;
2013-10-11 13:36:56 -0300  (tag: pymysql-0.6.1)&lt;br /&gt;
2013-10-04 14:25:14 -0300  (tag: pymysql-0.6)&lt;br /&gt;
2011-11-08 10:27:41 -0800  (tag: pymysql-0.5)&lt;br /&gt;
2010-12-27 17:28:16 +0000  (tag: pymysql-0.4)&lt;br /&gt;
2010-09-03 00:55:29 +0000  (tag: pymysql-0.3)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Community Involvement ===&lt;br /&gt;
&lt;br /&gt;
This is an area where PyMySQL seems to shine, and might even be an advantage&lt;br /&gt;
as compared to MySQL-Connector-Python, which remains under the cloak&lt;br /&gt;
of MySQL's vastly overstuffed bugtracker under the ownership of&lt;br /&gt;
the famously-opaque Oracle.   For a small, low key library without&lt;br /&gt;
any real documentation and only 126 tests, it features 33 contributors on&lt;br /&gt;
Github and is up to pull request #285, which is quite high.  Because&lt;br /&gt;
PyMySQL is small and straightforward, it does lend itself to contribution&lt;br /&gt;
and seems to do very well in this area.   The developers so far have been&lt;br /&gt;
responsive to issues and pull requests made by the author.&lt;br /&gt;
&lt;br /&gt;
Based on github contributor graphs it appears that PyMySQL had a lull&lt;br /&gt;
in 2012 but was picked up again afterwards in 2013.  As of 2012, its most&lt;br /&gt;
prominent developer Pete Hunt stopped development, and on July 25, 2013&lt;br /&gt;
added a note to the README that the project was looking for a new maintainer.&lt;br /&gt;
As of 2013 it was picked up by the current most prominent developers&lt;br /&gt;
Marcel Rodrigues (github username lecram) and INADA Naoki&lt;br /&gt;
(github username methane).&lt;br /&gt;
&lt;br /&gt;
Most issues that have been noted thus far within the areas of testing,&lt;br /&gt;
documentation and architecture are all very fixable; if the Openstack community&lt;br /&gt;
can provide resources to address some of these areas, and if the PyMySQL&lt;br /&gt;
developers are reasonably open to accepting new changes and producing new&lt;br /&gt;
releases in a timely fashion, the PyMySQL driver&lt;br /&gt;
could rapidly become a fully formidable contender in the MySQL driver field.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Appendix A - Performance Tests ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
import cProfile&lt;br /&gt;
import StringIO&lt;br /&gt;
import pstats&lt;br /&gt;
import contextlib&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextlib.contextmanager&lt;br /&gt;
def profiled(dbapi):&lt;br /&gt;
    pr = cProfile.Profile()&lt;br /&gt;
    pr.enable()&lt;br /&gt;
    yield&lt;br /&gt;
    pr.disable()&lt;br /&gt;
    s = StringIO.StringIO()&lt;br /&gt;
    ps = pstats.Stats(pr, stream=s).sort_stats('cumulative')&lt;br /&gt;
    print &amp;quot;DBAPI:  %s&amp;quot; % dbapi&lt;br /&gt;
    ps.print_stats()&lt;br /&gt;
    # uncomment this to see who's calling what&lt;br /&gt;
    # ps.print_callers()&lt;br /&gt;
    print &amp;quot;total calls: %s&amp;quot; % s.getvalue()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
import MySQLdb&lt;br /&gt;
import pymysql&lt;br /&gt;
from mysql import connector as mysqlconnector&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def go(dbapi):&lt;br /&gt;
    conn = dbapi.connect(&lt;br /&gt;
        user='scott', passwd='tiger', host='localhost', db='test')&lt;br /&gt;
    cursor = conn.cursor()&lt;br /&gt;
    cursor.execute(&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
CREATE TABLE IF NOT EXISTS test_things (&lt;br /&gt;
    x INTEGER,&lt;br /&gt;
    y VARCHAR(255),&lt;br /&gt;
    z FLOAT&lt;br /&gt;
) engine=InnoDB&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;)&lt;br /&gt;
    cursor.execute(&amp;quot;DELETE from test_things&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    with profiled(dbapi):&lt;br /&gt;
        for row in xrange(1000):&lt;br /&gt;
            cursor.execute(&lt;br /&gt;
                &amp;quot;INSERT INTO test_things (x, y, z) &amp;quot;&lt;br /&gt;
                &amp;quot;VALUES (%(x)s, %(y)s, %(z)s)&amp;quot;,&lt;br /&gt;
                {&amp;quot;x&amp;quot;: row, &amp;quot;y&amp;quot;: &amp;quot;row number %d&amp;quot; % row,&lt;br /&gt;
                 &amp;quot;z&amp;quot;: row * 4.57292, }&lt;br /&gt;
            )&lt;br /&gt;
&lt;br /&gt;
        for x in xrange(500):&lt;br /&gt;
            cursor.execute(&lt;br /&gt;
                &amp;quot;select * from test_things&amp;quot;)&lt;br /&gt;
            for row in cursor.fetchall():&lt;br /&gt;
                row[0], row[1], row[2]&lt;br /&gt;
&lt;br /&gt;
    cursor.close()&lt;br /&gt;
    conn.close()&lt;br /&gt;
&lt;br /&gt;
go(pymysql)&lt;br /&gt;
go(mysqlconnector)&lt;br /&gt;
go(MySQLdb)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DBAPI:  &amp;lt;module 'pymysql' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/__init__.pyc'&amp;gt;&lt;br /&gt;
total calls:          26850503 function calls in 16.017 seconds&lt;br /&gt;
&lt;br /&gt;
   Ordered by: cumulative time&lt;br /&gt;
&lt;br /&gt;
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;
     1500    0.010    0.000   16.016    0.011 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:107(execute)&lt;br /&gt;
     1500    0.003    0.000   15.976    0.011 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:271(_query)&lt;br /&gt;
     1500    0.003    0.000   15.952    0.011 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:708(query)&lt;br /&gt;
     1500    0.003    0.000   15.911    0.011 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:854(_read_query_result)&lt;br /&gt;
     1500    0.004    0.000   15.905    0.011 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:1060(read)&lt;br /&gt;
      500    0.003    0.000   15.658    0.031 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:1106(_read_result_packet)&lt;br /&gt;
      500    0.921    0.002   15.527    0.031 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:1139(_read_rowdata_packet)&lt;br /&gt;
   500000    3.404    0.000    9.210    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:1152(_read_row_from_packet)&lt;br /&gt;
  2012000    1.179    0.000    5.374    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:314(read_length_coded_string)&lt;br /&gt;
   504500    1.533    0.000    5.246    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:808(_read_packet)&lt;br /&gt;
  2014500    1.265    0.000    3.056    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:296(read_length_encoded_integer)&lt;br /&gt;
  3539500    2.469    0.000    2.782    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:248(read)&lt;br /&gt;
  1009000    0.795    0.000    2.300    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:833(_read_bytes)&lt;br /&gt;
  1009000    1.178    0.000    1.400    0.000 {method 'read' of '_io.BufferedReader' objects}&lt;br /&gt;
   508500    0.381    0.000    0.699    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/util.py:3(byte2int)&lt;br /&gt;
  5058000    0.468    0.000    0.468    0.000 {len}&lt;br /&gt;
   500500    0.219    0.000    0.453    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:1098(_check_packet_is_eof)&lt;br /&gt;
   504500    0.211    0.000    0.380    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:342(check_error)&lt;br /&gt;
  2504000    0.259    0.000    0.259    0.000 {method 'append' of 'list' objects}&lt;br /&gt;
   501500    0.182    0.000    0.231    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:329(is_eof_packet)&lt;br /&gt;
     3500    0.009    0.000    0.222    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/_socketio.py:45(readinto)&lt;br /&gt;
  1022000    0.205    0.000    0.205    0.000 {_struct.unpack}&lt;br /&gt;
     3500    0.203    0.000    0.203    0.000 {method 'recv_into' of '_socket.socket' objects}&lt;br /&gt;
   526000    0.198    0.000    0.198    0.000 {isinstance}&lt;br /&gt;
  1501000    0.197    0.000    0.197    0.000 {method 'get' of 'dict' objects}&lt;br /&gt;
   504500    0.173    0.000    0.173    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:241(__init__)&lt;br /&gt;
   504500    0.169    0.000    0.169    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:339(is_error_packet)&lt;br /&gt;
  2014500    0.162    0.000    0.162    0.000 {ord}&lt;br /&gt;
      500    0.006    0.000    0.127    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:1176(_get_descriptions)&lt;br /&gt;
     2000    0.003    0.000    0.088    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:361(__init__)&lt;br /&gt;
     2000    0.020    0.000    0.085    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:365(__parse_field_descriptor)&lt;br /&gt;
   500000    0.055    0.000    0.055    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/converters.py:301(through)&lt;br /&gt;
     1500    0.006    0.000    0.037    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:877(_execute_command)&lt;br /&gt;
     1500    0.003    0.000    0.027    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:848(_write_bytes)&lt;br /&gt;
     1500    0.002    0.000    0.024    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/socket.py:223(meth)&lt;br /&gt;
     1500    0.021    0.000    0.021    0.000 {method 'sendall' of '_socket.socket' objects}&lt;br /&gt;
     1500    0.020    0.000    0.021    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:278(_do_get_result)&lt;br /&gt;
     1000    0.003    0.000    0.019    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:97(_escape_args)&lt;br /&gt;
     4000    0.002    0.000    0.015    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:101(&amp;lt;genexpr&amp;gt;)&lt;br /&gt;
     1000    0.001    0.000    0.014    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:1089(_read_ok_packet)&lt;br /&gt;
     3000    0.003    0.000    0.013    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:674(escape)&lt;br /&gt;
     1000    0.005    0.000    0.012    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:416(__init__)&lt;br /&gt;
     8000    0.011    0.000    0.011    0.000 {method 'decode' of 'str' objects}&lt;br /&gt;
     3500    0.002    0.000    0.006    0.000 {method '_checkReadable' of '_io._IOBase' objects}&lt;br /&gt;
     4000    0.002    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:128(&amp;lt;genexpr&amp;gt;)&lt;br /&gt;
     2000    0.003    0.000    0.005    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/converters.py:19(escape_item)&lt;br /&gt;
     7500    0.003    0.000    0.005    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:117(ensure_bytes)&lt;br /&gt;
     2000    0.003    0.000    0.005    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:386(description)&lt;br /&gt;
     5500    0.004    0.000    0.004    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:271(advance)&lt;br /&gt;
      500    0.002    0.000    0.004    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:442(__init__)&lt;br /&gt;
     1000    0.001    0.000    0.004    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:684(escape_string)&lt;br /&gt;
     3500    0.004    0.000    0.004    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/_socketio.py:87(readable)&lt;br /&gt;
     3500    0.003    0.000    0.003    0.000 {method '_checkClosed' of '_io._IOBase' objects}&lt;br /&gt;
     1000    0.001    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/converters.py:59(escape_string)&lt;br /&gt;
     1500    0.003    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:1043(__init__)&lt;br /&gt;
     1500    0.001    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:94(nextset)&lt;br /&gt;
     4000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:397(get_column_length)&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/converters.py:56(escape_float)&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 {method 'sub' of '_sre.SRE_Pattern' objects}&lt;br /&gt;
     3000    0.002    0.000    0.002    0.000 {_struct.pack}&lt;br /&gt;
     6000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:64(_get_db)&lt;br /&gt;
     1500    0.001    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:82(_nextset)&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/util.py:9(int2byte)&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:246(fetchall)&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {min}&lt;br /&gt;
     2500    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:326(is_ok_packet)&lt;br /&gt;
     2000    0.001    0.000    0.001    0.000 {method 'items' of 'dict' objects}&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {getattr}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/converters.py:52(escape_int)&lt;br /&gt;
     1000    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:262(read_all)&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:1056(__del__)&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:69(_check_executed)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/contextlib.py:21(__exit__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 test.py:7(profiled)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
DBAPI:  &amp;lt;module 'mysql.connector' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/__init__.pyc'&amp;gt;&lt;br /&gt;
total calls:          11674074 function calls in 18.316 seconds&lt;br /&gt;
&lt;br /&gt;
   Ordered by: cumulative time&lt;br /&gt;
&lt;br /&gt;
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;
      500    0.611    0.001   17.825    0.036 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:820(fetchall)&lt;br /&gt;
      500    0.002    0.000   10.829    0.022 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:655(get_rows)&lt;br /&gt;
      500    1.505    0.003   10.825    0.022 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:292(read_text_result)&lt;br /&gt;
   504500    3.208    0.000    7.065    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/network.py:219(recv_plain)&lt;br /&gt;
   500000    3.398    0.000    6.297    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:359(row_to_python)&lt;br /&gt;
  1009000    3.039    0.000    3.039    0.000 {method 'recv_into' of '_socket.socket' objects}&lt;br /&gt;
   500000    2.003    0.000    2.283    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:220(read_lc_string_list)&lt;br /&gt;
   500000    0.625    0.000    2.010    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:531(_STRING_to_python)&lt;br /&gt;
   502000    0.355    0.000    1.254    0.000 {method 'decode' of 'bytearray' objects}&lt;br /&gt;
   502000    0.212    0.000    0.899    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/utf_8.py:15(decode)&lt;br /&gt;
   502000    0.688    0.000    0.688    0.000 {_codecs.utf_8_decode}&lt;br /&gt;
   509500    0.498    0.000    0.498    0.000 {_struct.unpack_from}&lt;br /&gt;
     1500    0.009    0.000    0.490    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:452(execute)&lt;br /&gt;
   500000    0.482    0.000    0.482    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:413(_INT_to_python)&lt;br /&gt;
     1500    0.004    0.000    0.436    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:705(cmd_query)&lt;br /&gt;
   500000    0.352    0.000    0.352    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:405(_FLOAT_to_python)&lt;br /&gt;
   504500    0.326    0.000    0.326    0.000 {method 'extend' of 'bytearray' objects}&lt;br /&gt;
     1500    0.006    0.000    0.318    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:481(_send_cmd)&lt;br /&gt;
  2501500    0.273    0.000    0.273    0.000 {method 'append' of 'list' objects}&lt;br /&gt;
   500500    0.240    0.000    0.240    0.000 {method 'startswith' of 'bytearray' objects}&lt;br /&gt;
   524002    0.143    0.000    0.143    0.000 {isinstance}&lt;br /&gt;
     1500    0.010    0.000    0.114    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:613(_handle_result)&lt;br /&gt;
  1004500    0.109    0.000    0.109    0.000 {len}&lt;br /&gt;
   500000    0.086    0.000    0.086    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:204(description)&lt;br /&gt;
     2000    0.013    0.000    0.038    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:226(parse_column)&lt;br /&gt;
     1000    0.008    0.000    0.034    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:346(_process_params_dict)&lt;br /&gt;
     1500    0.006    0.000    0.034    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/network.py:118(send_plain)&lt;br /&gt;
     1500    0.002    0.000    0.023    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/socket.py:223(meth)&lt;br /&gt;
     1500    0.020    0.000    0.020    0.000 {method 'sendall' of '_socket.socket' objects}&lt;br /&gt;
     5000    0.007    0.000    0.014    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/catch23.py:79(struct_unpack)&lt;br /&gt;
     1000    0.001    0.000    0.014    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:562(_handle_ok)&lt;br /&gt;
    12000    0.011    0.000    0.011    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:167(read_lc_string)&lt;br /&gt;
     1000    0.004    0.000    0.011    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:199(parse_ok)&lt;br /&gt;
     3000    0.005    0.000    0.009    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:152(to_mysql)&lt;br /&gt;
     3000    0.003    0.000    0.007    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:130(quote)&lt;br /&gt;
     1500    0.002    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:118(make_command)&lt;br /&gt;
     1500    0.004    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:406(_handle_result)&lt;br /&gt;
     3000    0.003    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:102(escape)&lt;br /&gt;
     1500    0.004    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/network.py:53(_prepare_packets)&lt;br /&gt;
     1000    0.002    0.000    0.005    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:252(parse_eof)&lt;br /&gt;
      500    0.001    0.000    0.005    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:579(_handle_eof)&lt;br /&gt;
     2000    0.003    0.000    0.004    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:545(_handle_server_status)&lt;br /&gt;
     1500    0.002    0.000    0.004    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:53(int1store)&lt;br /&gt;
     3000    0.003    0.000    0.003    0.000 {method 'encode' of 'str' objects}&lt;br /&gt;
     9001    0.003    0.000    0.003    0.000 {method 'replace' of 'str' objects}&lt;br /&gt;
     1500    0.003    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:295(_reset_result)&lt;br /&gt;
     6027    0.003    0.000    0.003    0.000 {method 'format' of 'str' objects}&lt;br /&gt;
     2500    0.003    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:296(read_lc_int)&lt;br /&gt;
     4500    0.002    0.000    0.002    0.000 {_struct.pack}&lt;br /&gt;
     3000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:1221(_set_unread_result)&lt;br /&gt;
      500    0.001    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:219(parse_column_count)&lt;br /&gt;
      500    0.001    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:757(_handle_eof)&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 {repr}&lt;br /&gt;
     4527    0.001    0.000    0.001    0.000 {getattr}&lt;br /&gt;
     4000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/constants.py:34(flag_is_set)&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:383(_handle_noresultset)&lt;br /&gt;
     4000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:1233(_get_unread_result)&lt;br /&gt;
     2001    0.001    0.000    0.001    0.000 {method 'items' of 'dict' objects}&lt;br /&gt;
     3000    0.001    0.000    0.001    0.000 {method 'lower' of 'str' objects}&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:307(_have_unread_result)&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:173(_str_to_mysql)&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 {range}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:169(_float_to_mysql)&lt;br /&gt;
     1000    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:161(_int_to_mysql)&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:1313(_get_getwarnings)&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:200(reset)&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:397(_handle_resultset)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/__init__.py:71(search_function)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/ascii.py:41(getregentry)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/contextlib.py:21(__exit__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/__init__.py:49(normalize_encoding)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/codecs.py:77(__new__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {__import__}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 test.py:7(profiled)&lt;br /&gt;
        3    0.000    0.000    0.000    0.000 {method 'get' of 'dict' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'split' of 'str' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'translate' of 'str' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'join' of 'str' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {built-in method __new__ of type object at 0x1001534e8}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {hasattr}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DBAPI:  &amp;lt;module 'MySQLdb' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/__init__.pyc'&amp;gt;&lt;br /&gt;
total calls:          47503 function calls in 0.916 seconds&lt;br /&gt;
&lt;br /&gt;
   Ordered by: cumulative time&lt;br /&gt;
&lt;br /&gt;
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;
     1500    0.008    0.000    0.915    0.001 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:164(execute)&lt;br /&gt;
     1500    0.002    0.000    0.888    0.001 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:353(_query)&lt;br /&gt;
     1500    0.004    0.000    0.498    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:315(_do_query)&lt;br /&gt;
     1500    0.016    0.000    0.388    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:358(_post_get_result)&lt;br /&gt;
     1500    0.001    0.000    0.372    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:324(_fetch_row)&lt;br /&gt;
      500    0.371    0.001    0.371    0.001 {built-in method fetch_row}&lt;br /&gt;
     1500    0.008    0.000    0.359    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:142(_do_get_result)&lt;br /&gt;
     1500    0.002    0.000    0.348    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:351(_get_result)&lt;br /&gt;
     1500    0.346    0.000    0.346    0.000 {method 'store_result' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.134    0.000    0.134    0.000 {method 'query' of '_mysql.connection' objects}&lt;br /&gt;
     4000    0.002    0.000    0.010    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:184(&amp;lt;genexpr&amp;gt;)&lt;br /&gt;
     3000    0.002    0.000    0.008    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/connections.py:267(literal)&lt;br /&gt;
     1500    0.007    0.000    0.007    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:107(_warning_check)&lt;br /&gt;
     3000    0.002    0.000    0.006    0.000 {method 'escape' of '_mysql.connection' objects}&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/converters.py:81(Float2Str)&lt;br /&gt;
     6000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:159(_get_db)&lt;br /&gt;
     2500    0.002    0.000    0.002    0.000 {isinstance}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/connections.py:202(string_literal)&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:380(fetchall)&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 {built-in method describe}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 {method 'string_literal' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {method 'affected_rows' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {method 'insert_id' of '_mysql.connection' objects}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/converters.py:69(Thing2Str)&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 {method 'info' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 {method 'warning_count' of '_mysql.connection' objects}&lt;br /&gt;
     1000    0.000    0.000    0.000    0.000 {method 'iteritems' of 'dict' objects}&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:103(_check_executed)&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 {len}&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 {built-in method field_flags}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/contextlib.py:21(__exit__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 test.py:7(profiled)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mike Bayer</name></author>	</entry>

	<entry>
		<id>https://wiki.openstack.org/w/index.php?title=PyMySQL_evaluation&amp;diff=72501</id>
		<title>PyMySQL evaluation</title>
		<link rel="alternate" type="text/html" href="https://wiki.openstack.org/w/index.php?title=PyMySQL_evaluation&amp;diff=72501"/>
				<updated>2015-01-28T01:33:10Z</updated>
		
		<summary type="html">&lt;p&gt;Mike Bayer: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= PyMySQL Evaluation =&lt;br /&gt;
&lt;br /&gt;
This page will capture issues related to Openstack moving to the [https://github.com/PyMySQL/PyMySQL/ PyMySQL driver] for MySQL/MariaDB dbapi access.&lt;br /&gt;
&lt;br /&gt;
== Rationale ==&lt;br /&gt;
&lt;br /&gt;
While the [https://github.com/farcepest/MySQLdb1 MySQL-Python] driver is a very mature and stable driver, it does not provide compatibility with either Python 3 or with [http://eventlet.net/doc/patching.html eventlet monkeypatching] (MySQL-Python  [https://github.com/eventlet/eventlet/blob/v0.16.1/eventlet/patcher.py#L225-L237 can be monkeypatched] with eventlet, but this feature disabled by default and missed in documentation). So OpenStack's usage of MySQL-Python, combined with the fact that concurrency is provided by eventlet, means that we currently have fully serialized database access within a single process, that is, only one database command occurs at a time within an Openstack Python process.&lt;br /&gt;
&lt;br /&gt;
== Drivers Under Consideration ==&lt;br /&gt;
&lt;br /&gt;
The two drivers that are known to provide eventlet-monkeypatch compatibility are MySQL-Connector and PyMySQL, as they are written in pure Python.   The two other well-known drivers for MySQL are MySQL-Python and OurSQL, both of which are written in C and offer no explicit support for eventlet or async.  They can reportedly be built to support gevent using a system called [https://github.com/CMGS/greenify Greenify], but the maturity and/or stabiltiy of this system is unknown.&lt;br /&gt;
&lt;br /&gt;
A comparison at http://www.diamondtin.com/2014/sqlalchemy-gevent-mysql-python-drivers-comparison/ illustrates performance metrics observed with all four of these drivers, with MySQL-python built both without and with the &amp;quot;greenify&amp;quot; system.&lt;br /&gt;
&lt;br /&gt;
A summary of the status of all four drivers is as follows.&lt;br /&gt;
&lt;br /&gt;
=== MySQL-Python ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/farcepest/MySQLdb1 MySQL-Python] is the most widely used Python driver.  However, it does not support Python 3, and it does not support async systems unless used within a thread pool.   While a system known as Greenify can potentially resolve the latter situation, the Python 3 limitation is still a deal-breaker - pull request with Python 3 support [https://github.com/farcepest/MySQLdb1/pull/62 was proposed] to MySQL-python in April 2014 and still not merged.&lt;br /&gt;
&lt;br /&gt;
=== mysqlclient ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/PyMySQL/mysqlclient-python mysqlclient] is a fork of MySQL-python. It add Python 3.3 support and merges some pull requests.&lt;br /&gt;
&lt;br /&gt;
=== OurSQL ===&lt;br /&gt;
&lt;br /&gt;
[https://pythonhosted.org/oursql/ OurSQL] is an alternative MySQL driver that is also written in C.  It features fast performance and uses a different execution model than MySQL-python, using prepared statements.    OurSQL does have a Python 3 port hosted on [https://launchpad.net/oursql/py3k/ launchpad] but is not integrated with OurSQL itself nor is it published on Pypi.     Because it is written in C, it does not offer compatibility with async systems including eventlet-style monkeypatching.&lt;br /&gt;
&lt;br /&gt;
Besides the shaky Python 3 support and lack of eventlet support, OurSQL is also not maintained, seeing its last master commit on [https://code.launchpad.net/~jerub/oursql/master 2012-06-05].&lt;br /&gt;
&lt;br /&gt;
=== MySQL-Connector-Python ===&lt;br /&gt;
&lt;br /&gt;
[https://pypi.python.org/pypi/mysql-connector-python MySQL-connector-Python] is a pure Python MySQL driver, and is now released under the auspices of the MySQL project itself, as owned by Oracle.  MySQL-connector-Python supports both Python 3 as well as eventlet monkeypatching, and is well maintained.  It is endorsed by Oracle as the official Oracle-supported driver for MySQL, so to that extent, it is in most ways Openstack's first choice in driver.  However, Oracle refuses to publish MySQL-connector-Python on Pypi, which is critical to the Openstack infrastructure.  Repeated attempts to communicate with Oracle in order to resolve this issue have not made any progress.    Therefore, for this one unfortunate reason, MySQL-connector-Python will not have a place in the Openstack ecosystem unless this issue is resolved.&lt;br /&gt;
&lt;br /&gt;
=== PyMySQL ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/PyMySQL/PyMySQL PyMySQL] is a pure Python MySQL&lt;br /&gt;
driver, first written as a rough port of the MySQL-Python driver.&lt;br /&gt;
PyMySQL meets all of Openstack's criterion for a driver: it is fully&lt;br /&gt;
open source, hosted on Github, released on Pypi, is actively&lt;br /&gt;
maintained, is written in pure Python so is eventlet-monkeypatch&lt;br /&gt;
compatible, and is fully Python 3 compatible.&lt;br /&gt;
&lt;br /&gt;
As this document is named &amp;quot;PyMySQL Evaluation&amp;quot;, it should be apparent that&lt;br /&gt;
this is the driver Openstack is currently leaning towards.  However, it does&lt;br /&gt;
have some minor code quality issues which hopefully can be addressed in some&lt;br /&gt;
way; the section below titled &amp;quot;PyMySQL Code Review&amp;quot; will summarize the&lt;br /&gt;
current state of the code.  We are in fact leaning towards this driver because&lt;br /&gt;
its the only one that actually meets all criteria.  PyMySQL appears to be&lt;br /&gt;
fairly widely used, especially considering its low profile, so it's possible&lt;br /&gt;
that this is the case for many other users as well.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===MySQL DB Drivers Comparison===&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Project !! PyPi hosted !! Eventlet friendly !! Python 3 compatibility !! Maturity and/or stability  !! Comment&lt;br /&gt;
|-&lt;br /&gt;
| MySQL-Python || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: orange;&amp;quot; | Partial || style=&amp;quot;background-color: red;&amp;quot; | No  || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || Can be monkeypatched by eventlet, but only to enable thread pooling&lt;br /&gt;
|-&lt;br /&gt;
| mysqlclient || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: orange;&amp;quot; | Partial || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes  || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || MySQL-python fork, so should be similar to it&lt;br /&gt;
|-&lt;br /&gt;
| OurSQL || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: red;&amp;quot; | No || style=&amp;quot;background-color: orange;&amp;quot; | Yes, but not Pypi hosted  || style=&amp;quot;background-color: orange;&amp;quot; | ??? || Has not seen commits/releases in two years&lt;br /&gt;
|-&lt;br /&gt;
| MySQL-Connector-Python || style=&amp;quot;background-color: red;&amp;quot; | No || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes  || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || The official Oracle-supported driver for MySQL&lt;br /&gt;
|-&lt;br /&gt;
| PyMySQL || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes  || style=&amp;quot;background-color: orange;&amp;quot; | Yes, with a [http://lists.openstack.org/pipermail/openstack-dev/2014-December/052864.html proviso]. ||&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== PyMySQL Code Review ==&lt;br /&gt;
&lt;br /&gt;
PyMySQL started roughly as a pure Python port of MySQL-Python.   The sections below will&lt;br /&gt;
detail various aspects of the driver and the status of the code.   Overall, the general&lt;br /&gt;
tone of PyMySQL is one of code that was written in the spirit of pragmatism and immediate&lt;br /&gt;
need; it is straightforward, relatively free of serious antipatterns and performs&lt;br /&gt;
its task in a matter-of-fact way.  However,&lt;br /&gt;
it lacks polish and completeness in many areas that would normally be in better&lt;br /&gt;
condition for a more mature project.  These areas are all highly fixable, and it is&lt;br /&gt;
hoped that these items can be shared with the current PyMySQL developers, through&lt;br /&gt;
a combination of this document itself as well as new issues and pull requests&lt;br /&gt;
on the PyMySQL tracker, so that this fairly promising library can be pushed&lt;br /&gt;
to the next level.   This would require that the developers are amenable to&lt;br /&gt;
these improvements.&lt;br /&gt;
&lt;br /&gt;
=== Coding Style / Pep8 ===&lt;br /&gt;
&lt;br /&gt;
PyMySQL's coding style is pretty good.  The majority of it passes all&lt;br /&gt;
flake8 tests, save for a few dozen whitespace issues and&lt;br /&gt;
some long lines here and there within the core&lt;br /&gt;
modules, and moreso within the tests which appear to be a bit more&lt;br /&gt;
crufty than the core modules.  A full run of flake8 produces only 128&lt;br /&gt;
errors, and with a default run of autopep8 against E1,E2,E3, we can&lt;br /&gt;
fix all of them in one pass except for 38 remaining long line warnings.&lt;br /&gt;
The code layout is mostly idiomatic and reasonable.&lt;br /&gt;
&lt;br /&gt;
The codebase is probably short on docstrings.  Most of the Python DBAPI&lt;br /&gt;
that's public is very sparse, so the majority of functions and methods&lt;br /&gt;
we see within the core modules here are not typically invoked from the&lt;br /&gt;
outside.  The methods that are part of the DBAPI spec do seem to have&lt;br /&gt;
docstrings for the most part, although these docstrings are mostly&lt;br /&gt;
extremely terse (example, docstring for cursor.execute():&lt;br /&gt;
&amp;quot;'''Execute a query'''&amp;quot;).  The purpose of methods and functions is&lt;br /&gt;
pretty easy to discern just by their names and implementations, however&lt;br /&gt;
those who seek inline documentation as a means to understand a codebase&lt;br /&gt;
will be very disappointed.&lt;br /&gt;
&lt;br /&gt;
=== Test Coverage ===&lt;br /&gt;
&lt;br /&gt;
The test coverage for PyMySQL is definitely lacking.   A visual inspection&lt;br /&gt;
of the test modules that are present will be surprising to most seasoned&lt;br /&gt;
DBAPI users, as while there are about a dozen distinct test modules, each&lt;br /&gt;
one is surprisingly short, most containing fewer than ten tests; for a&lt;br /&gt;
full test run, there's only 126 tests; contrast this to MySQL-Connector-Python&lt;br /&gt;
which has 531 tests.&lt;br /&gt;
&lt;br /&gt;
An additional critical issue with the tests is that virtually all tests are&lt;br /&gt;
live round-trip tests against a real database; there are no non-live&lt;br /&gt;
unit tests of any kind, leaving codepaths that are not easily exercised&lt;br /&gt;
on a generic MySQL database out in the cold.  This means that features designed&lt;br /&gt;
for particular versions of MySQL, particular datatypes, particular error&lt;br /&gt;
condtions, and particular kinds of message packets that aren't created for&lt;br /&gt;
real are simply not tested.&lt;br /&gt;
&lt;br /&gt;
This lack of tests definitely shows when run with coverage.  The test suite&lt;br /&gt;
is designed to be run with unittest, and features a simple &amp;quot;runtests.py&amp;quot;&lt;br /&gt;
front-end script, with no sign of any use of coverage tools anywhere,&lt;br /&gt;
including in its tox.ini script.  Running the suite with py.test plus py-test-cov&lt;br /&gt;
produces an 85% coverage level overall, which isn't *too* terrible, but&lt;br /&gt;
concerningly, the biggest gaps are in the three most critical modules,&lt;br /&gt;
connections.py, converters.py and cursors.py:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Name                                                              Stmts   Miss  Cover&lt;br /&gt;
-------------------------------------------------------------------------------------&lt;br /&gt;
pymysql/__init__                                                     51      3    94%&lt;br /&gt;
pymysql/_compat                                                      14      4    71%&lt;br /&gt;
pymysql/_socketio                                                    72     30    58%&lt;br /&gt;
pymysql/charset                                                     228      3    99%&lt;br /&gt;
pymysql/connections                                                 827    188    77%&lt;br /&gt;
pymysql/constants/CLIENT                                             18      0   100%&lt;br /&gt;
pymysql/constants/COMMAND                                            32      0   100%&lt;br /&gt;
pymysql/constants/ER                                                471      0   100%&lt;br /&gt;
pymysql/constants/FIELD_TYPE                                         29      0   100%&lt;br /&gt;
pymysql/constants/FLAG                                               15      0   100%&lt;br /&gt;
pymysql/constants/SERVER_STATUS                                      10      0   100%&lt;br /&gt;
pymysql/constants/__init__                                            0      0   100%&lt;br /&gt;
pymysql/converters                                                  148     51    66%&lt;br /&gt;
pymysql/cursors                                                     273     37    86%&lt;br /&gt;
pymysql/err                                                          40      1    98%&lt;br /&gt;
pymysql/tests/__init__                                               14      5    64%&lt;br /&gt;
pymysql/tests/base                                                   25      2    92%&lt;br /&gt;
pymysql/tests/test_DictCursor                                        73      2    97%&lt;br /&gt;
pymysql/tests/test_SSCursor                                          56      6    89%&lt;br /&gt;
pymysql/tests/test_basic                                            186      4    98%&lt;br /&gt;
pymysql/tests/test_connection                                        57      8    86%&lt;br /&gt;
pymysql/tests/test_example                                           17      2    88%&lt;br /&gt;
pymysql/tests/test_issues                                           300     48    84%&lt;br /&gt;
pymysql/tests/test_load_local                                        39      2    95%&lt;br /&gt;
pymysql/tests/test_nextset                                           43      4    91%&lt;br /&gt;
pymysql/tests/thirdparty/__init__                                     7      5    29%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/__init__                        6      2    67%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/capabilities                  196     23    88%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/dbapi20                       423    121    71%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_capabilities      73      6    92%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_dbapi20          100      2    98%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_nonstandard       57      3    95%&lt;br /&gt;
pymysql/times                                                        12      0   100%&lt;br /&gt;
pymysql/util                                                         14      7    50%&lt;br /&gt;
-------------------------------------------------------------------------------------&lt;br /&gt;
TOTAL                                                              3926    569    86%&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The lack of coverage in converters.py refers to functions related to handling&lt;br /&gt;
incoming bound values as well as outgoing result values.    Key data escaping&lt;br /&gt;
features such as escaping of dictionaries, sets, some unicode objects and&lt;br /&gt;
byte objects as well as many pathways into date and time-processing functions are not&lt;br /&gt;
covered.&lt;br /&gt;
&lt;br /&gt;
In connections.py, uncovered features include various SSL and&lt;br /&gt;
very legacy (e.g. version 3.23) MySQL features, but also some protocol parsing&lt;br /&gt;
features.&lt;br /&gt;
&lt;br /&gt;
In cursors.py, there is coverage for an elaborate and mostly undocumented&lt;br /&gt;
performance-related feature that rewrites an INSERT statement to have an&lt;br /&gt;
extended VALUES clause (_do_execute_many), however it fails to cover the very&lt;br /&gt;
likely case where the function will need to break the input set into&lt;br /&gt;
multiple chunks.   The code which does this obviously works and looks perfectly&lt;br /&gt;
fine, but coverage marks it with a &amp;quot;!&amp;quot;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;gt;         for arg in args:&lt;br /&gt;
&amp;gt;             v = values % escape(arg, conn)&lt;br /&gt;
&amp;gt;             if isinstance(v, text_type):&lt;br /&gt;
&amp;gt;                 v = v.encode(encoding)&lt;br /&gt;
&amp;gt;             if len(sql) + len(v) + 1 &amp;gt; max_stmt_length:&lt;br /&gt;
!                 rows += self.execute(sql)&lt;br /&gt;
!                 sql = bytearray(prefix)&lt;br /&gt;
&amp;gt;             else:&lt;br /&gt;
&amp;gt;                 sql += b','&lt;br /&gt;
&amp;gt;             sql += v&lt;br /&gt;
&amp;gt;         rows += self.execute(sql)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The most prominent missing coverage in cursors.py seems to be&lt;br /&gt;
for its &amp;quot;scrollable cursor&amp;quot; support.&lt;br /&gt;
&lt;br /&gt;
The lack of unit-style non-live tests as well as the overall terseness&lt;br /&gt;
of the tests is a major concern for PyMySQL.  An additional concern&lt;br /&gt;
is that in many areas,&lt;br /&gt;
many dozens of individual &amp;quot;feature tests&amp;quot; are combined together into&lt;br /&gt;
a single method, making it extremely difficult to identify and debug&lt;br /&gt;
regressions as well as to expand upon the test suite.&lt;br /&gt;
In test_SSCursor.py we have essentially one giant&lt;br /&gt;
test with about fourteen individual assertions.  In test_basic.py, test_datatypes&lt;br /&gt;
attempts to run round-trip tests against about fourteen datatypes at once,&lt;br /&gt;
though to its credit it appears to have more individualized tests for many&lt;br /&gt;
of these datatypes as well.&lt;br /&gt;
&lt;br /&gt;
This style of testing is common for newer projects where the tests are&lt;br /&gt;
written in as much of an expedient way as possible; the author of this&lt;br /&gt;
document will freely admit that his own SQLAlchemy still&lt;br /&gt;
has a lot of tests like this as well (though at well over 5000 tests, that&lt;br /&gt;
proportion has become fairly small :) ).  The proportion of tests which&lt;br /&gt;
are like this in PyMySQL, relative to its very low number of tests to&lt;br /&gt;
start with, can be easily improved.&lt;br /&gt;
&lt;br /&gt;
There is also a suite called &amp;quot;test_issues&amp;quot;, which intends to&lt;br /&gt;
accumulate tests against specific issues that have been reported. This&lt;br /&gt;
isn't a bad idea, though it would be nice to see an effort made into&lt;br /&gt;
categorizing these tests into the actual features they are testing,&lt;br /&gt;
rather than a meaningless list of issue numbers, which could then form&lt;br /&gt;
as the basis for new suites centered around those areas of&lt;br /&gt;
functionality.  The issues reported should be used as the inspiration&lt;br /&gt;
for improvement of the test suite, rather than just another line-item&lt;br /&gt;
to be filed away.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Library Documentation ===&lt;br /&gt;
&lt;br /&gt;
As the Python DBAPI is already a well-documented API, and the users of&lt;br /&gt;
PyMySQL are typically coming from MySQL-Python which is documented&lt;br /&gt;
to some degree and for which the use contract is widely known, it seems&lt;br /&gt;
unlikely that users of PyMySQL are eager for comprehensive documentation&lt;br /&gt;
specific to this library.&lt;br /&gt;
&lt;br /&gt;
That being said, as far as can be discerned, PyMySQL has absolutely no&lt;br /&gt;
library documentation of any kind.  There's a README which&lt;br /&gt;
essentially refers to pep-249, an example.py in the root which shows a&lt;br /&gt;
very basic connection / round trip, and that is absolutely it. In&lt;br /&gt;
order to know anything about the API, specific parameters, etc., one&lt;br /&gt;
either has to go off of the MySQL-Python documentation and assume&lt;br /&gt;
PyMySQL also features the same parameter, or read the source code.&lt;br /&gt;
&lt;br /&gt;
This has probably been perfectly fine for PyMySQL and its userbase, however&lt;br /&gt;
for PyMySQL to enter the big time, it should really build up a rudimentary&lt;br /&gt;
Sphinx build and publish it up to RTD.   This would be very easy to get&lt;br /&gt;
started, and once present, new documentation sections can be added&lt;br /&gt;
iteratively.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Architecture and Performance ===&lt;br /&gt;
&lt;br /&gt;
As stated before, PyMySQL has a really matter-of-fact and straightforward&lt;br /&gt;
implementation.   The code is very readable and it's easy to discern even&lt;br /&gt;
unusual features such as the INSERT..VALUES rewriting.  As far as the design,&lt;br /&gt;
there are lots of areas where performance suffers a bit at the Python level;&lt;br /&gt;
these areas could be greatly improved very easily with a little bit of&lt;br /&gt;
attention, however it's seems likely that there hasn't been too much&lt;br /&gt;
work done with Python performance profiling here (that is, using cProfile).&lt;br /&gt;
&lt;br /&gt;
As we are dealing with a driver that is communicating with a low level&lt;br /&gt;
protocol over a socket, it's critical that sending and receiving messages&lt;br /&gt;
is done as efficiently as possible.  The mechanisms used in PyMySQL rely&lt;br /&gt;
heavily on standard Python objects, without the use of __slots__; this means&lt;br /&gt;
that for individual messages on a socket, we are creating a heavyweight object,&lt;br /&gt;
calling its __init__() method and a creating a new __dict__ each time;&lt;br /&gt;
the most common object, OKPacketWrapper(), already wraps a MysqlPacket()&lt;br /&gt;
object, so this is fairly wasteful; it also implements a `__getattr__` scheme&lt;br /&gt;
to proxy attribute access from itself to the internal MysqlPacket; this is a&lt;br /&gt;
very inefficient mode of operation in Python as it means a lookup must first&lt;br /&gt;
fail on the OKPacketWrapper before it invokes the `__getattr__()` method&lt;br /&gt;
and does another attribute lookup.&lt;br /&gt;
&lt;br /&gt;
There are also lots of areas where the same fairly expensive operation&lt;br /&gt;
is done more times than needed.  In particular, when calling upon&lt;br /&gt;
cursor.execute(), for parameters passed as a dictionary under Python 2K,&lt;br /&gt;
the isinstance() builtin function will be called on the incoming arguments&lt;br /&gt;
four times at a minimum, with two of each call against the exact same&lt;br /&gt;
criteria.  Additional logic will then call isinstance() on every key of&lt;br /&gt;
the dictionary as well as at least twice on every value within the dictionary,&lt;br /&gt;
in order to determine if the value needs to be converted to bytes as well&lt;br /&gt;
as for some top-level escaping logic checks; the escaping logic then&lt;br /&gt;
goes further with more checks for type() to match the object to a final&lt;br /&gt;
converter function.  These areas of functionality would appear to most&lt;br /&gt;
Python programmers to be reasonable and straightforward, however under&lt;br /&gt;
Python profiling the repeated calls to functions shows up very prominently.&lt;br /&gt;
The nesting of multiple process/converter calls should at least be inlined&lt;br /&gt;
so that as few isinstance()/type() checks as possible are performed &lt;br /&gt;
on a particular object.  &lt;br /&gt;
&lt;br /&gt;
In Appendix A, a short performance suite is run against PyMySQL, &lt;br /&gt;
Mysql-Connector-Python, and MySQLdb.   The heavy function call count&lt;br /&gt;
is definitely apparent in PyMySQL, which logs 26,850,503 function calls&lt;br /&gt;
compared to MySQL-connector's 11,674,074 for the same operations.  For some&lt;br /&gt;
very odd reason, PyMySQL still outperforms MySQL-connector timewise; this&lt;br /&gt;
is very unusual for a program with twice as many function calls, however&lt;br /&gt;
one explaination could be that MySQL-connector is doing something &lt;br /&gt;
in particular very slow, such as relying upon catching an exception within &lt;br /&gt;
a tight loop (exception throws in Python are extremely slow).   Both pure-Python&lt;br /&gt;
libraries use similar numbers of isinstance() calls, about 500K.   &lt;br /&gt;
&lt;br /&gt;
The dismal truth of how inefficient both of these libraries are is &lt;br /&gt;
apparent when we look at MySQLdb - as it is written in C and not pure &lt;br /&gt;
Python, it runs the same operations in 0.9 seconds,&lt;br /&gt;
a full 1600% faster than PyMySQL's 16 seconds and even more than MySQL-connector's&lt;br /&gt;
18 seconds, and logs only 47,000 Python function calls.&lt;br /&gt;
&lt;br /&gt;
In any case, if we're to use PyMySQL, while it will never be anywhere near&lt;br /&gt;
as fast as MySQLdb, the call counts and time spent for core operations can &lt;br /&gt;
be greatly improved with some simple refactorings, which we may consider &lt;br /&gt;
pull-requesting to the project.   &lt;br /&gt;
&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
=== History Documentation ===&lt;br /&gt;
&lt;br /&gt;
=== Community Involvement ===&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Appendix A - Performance Tests ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
import cProfile&lt;br /&gt;
import StringIO&lt;br /&gt;
import pstats&lt;br /&gt;
import contextlib&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextlib.contextmanager&lt;br /&gt;
def profiled(dbapi):&lt;br /&gt;
    pr = cProfile.Profile()&lt;br /&gt;
    pr.enable()&lt;br /&gt;
    yield&lt;br /&gt;
    pr.disable()&lt;br /&gt;
    s = StringIO.StringIO()&lt;br /&gt;
    ps = pstats.Stats(pr, stream=s).sort_stats('cumulative')&lt;br /&gt;
    print &amp;quot;DBAPI:  %s&amp;quot; % dbapi&lt;br /&gt;
    ps.print_stats()&lt;br /&gt;
    # uncomment this to see who's calling what&lt;br /&gt;
    # ps.print_callers()&lt;br /&gt;
    print &amp;quot;total calls: %s&amp;quot; % s.getvalue()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
import MySQLdb&lt;br /&gt;
import pymysql&lt;br /&gt;
from mysql import connector as mysqlconnector&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def go(dbapi):&lt;br /&gt;
    conn = dbapi.connect(&lt;br /&gt;
        user='scott', passwd='tiger', host='localhost', db='test')&lt;br /&gt;
    cursor = conn.cursor()&lt;br /&gt;
    cursor.execute(&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
CREATE TABLE IF NOT EXISTS test_things (&lt;br /&gt;
    x INTEGER,&lt;br /&gt;
    y VARCHAR(255),&lt;br /&gt;
    z FLOAT&lt;br /&gt;
) engine=InnoDB&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;)&lt;br /&gt;
    cursor.execute(&amp;quot;DELETE from test_things&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    with profiled(dbapi):&lt;br /&gt;
        for row in xrange(1000):&lt;br /&gt;
            cursor.execute(&lt;br /&gt;
                &amp;quot;INSERT INTO test_things (x, y, z) &amp;quot;&lt;br /&gt;
                &amp;quot;VALUES (%(x)s, %(y)s, %(z)s)&amp;quot;,&lt;br /&gt;
                {&amp;quot;x&amp;quot;: row, &amp;quot;y&amp;quot;: &amp;quot;row number %d&amp;quot; % row,&lt;br /&gt;
                 &amp;quot;z&amp;quot;: row * 4.57292, }&lt;br /&gt;
            )&lt;br /&gt;
&lt;br /&gt;
        for x in xrange(500):&lt;br /&gt;
            cursor.execute(&lt;br /&gt;
                &amp;quot;select * from test_things&amp;quot;)&lt;br /&gt;
            for row in cursor.fetchall():&lt;br /&gt;
                row[0], row[1], row[2]&lt;br /&gt;
&lt;br /&gt;
    cursor.close()&lt;br /&gt;
    conn.close()&lt;br /&gt;
&lt;br /&gt;
go(pymysql)&lt;br /&gt;
go(mysqlconnector)&lt;br /&gt;
go(MySQLdb)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DBAPI:  &amp;lt;module 'pymysql' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/__init__.pyc'&amp;gt;&lt;br /&gt;
total calls:          26850503 function calls in 16.017 seconds&lt;br /&gt;
&lt;br /&gt;
   Ordered by: cumulative time&lt;br /&gt;
&lt;br /&gt;
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;
     1500    0.010    0.000   16.016    0.011 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:107(execute)&lt;br /&gt;
     1500    0.003    0.000   15.976    0.011 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:271(_query)&lt;br /&gt;
     1500    0.003    0.000   15.952    0.011 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:708(query)&lt;br /&gt;
     1500    0.003    0.000   15.911    0.011 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:854(_read_query_result)&lt;br /&gt;
     1500    0.004    0.000   15.905    0.011 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:1060(read)&lt;br /&gt;
      500    0.003    0.000   15.658    0.031 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:1106(_read_result_packet)&lt;br /&gt;
      500    0.921    0.002   15.527    0.031 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:1139(_read_rowdata_packet)&lt;br /&gt;
   500000    3.404    0.000    9.210    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:1152(_read_row_from_packet)&lt;br /&gt;
  2012000    1.179    0.000    5.374    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:314(read_length_coded_string)&lt;br /&gt;
   504500    1.533    0.000    5.246    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:808(_read_packet)&lt;br /&gt;
  2014500    1.265    0.000    3.056    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:296(read_length_encoded_integer)&lt;br /&gt;
  3539500    2.469    0.000    2.782    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:248(read)&lt;br /&gt;
  1009000    0.795    0.000    2.300    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:833(_read_bytes)&lt;br /&gt;
  1009000    1.178    0.000    1.400    0.000 {method 'read' of '_io.BufferedReader' objects}&lt;br /&gt;
   508500    0.381    0.000    0.699    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/util.py:3(byte2int)&lt;br /&gt;
  5058000    0.468    0.000    0.468    0.000 {len}&lt;br /&gt;
   500500    0.219    0.000    0.453    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:1098(_check_packet_is_eof)&lt;br /&gt;
   504500    0.211    0.000    0.380    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:342(check_error)&lt;br /&gt;
  2504000    0.259    0.000    0.259    0.000 {method 'append' of 'list' objects}&lt;br /&gt;
   501500    0.182    0.000    0.231    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:329(is_eof_packet)&lt;br /&gt;
     3500    0.009    0.000    0.222    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/_socketio.py:45(readinto)&lt;br /&gt;
  1022000    0.205    0.000    0.205    0.000 {_struct.unpack}&lt;br /&gt;
     3500    0.203    0.000    0.203    0.000 {method 'recv_into' of '_socket.socket' objects}&lt;br /&gt;
   526000    0.198    0.000    0.198    0.000 {isinstance}&lt;br /&gt;
  1501000    0.197    0.000    0.197    0.000 {method 'get' of 'dict' objects}&lt;br /&gt;
   504500    0.173    0.000    0.173    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:241(__init__)&lt;br /&gt;
   504500    0.169    0.000    0.169    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:339(is_error_packet)&lt;br /&gt;
  2014500    0.162    0.000    0.162    0.000 {ord}&lt;br /&gt;
      500    0.006    0.000    0.127    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:1176(_get_descriptions)&lt;br /&gt;
     2000    0.003    0.000    0.088    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:361(__init__)&lt;br /&gt;
     2000    0.020    0.000    0.085    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:365(__parse_field_descriptor)&lt;br /&gt;
   500000    0.055    0.000    0.055    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/converters.py:301(through)&lt;br /&gt;
     1500    0.006    0.000    0.037    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:877(_execute_command)&lt;br /&gt;
     1500    0.003    0.000    0.027    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:848(_write_bytes)&lt;br /&gt;
     1500    0.002    0.000    0.024    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/socket.py:223(meth)&lt;br /&gt;
     1500    0.021    0.000    0.021    0.000 {method 'sendall' of '_socket.socket' objects}&lt;br /&gt;
     1500    0.020    0.000    0.021    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:278(_do_get_result)&lt;br /&gt;
     1000    0.003    0.000    0.019    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:97(_escape_args)&lt;br /&gt;
     4000    0.002    0.000    0.015    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:101(&amp;lt;genexpr&amp;gt;)&lt;br /&gt;
     1000    0.001    0.000    0.014    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:1089(_read_ok_packet)&lt;br /&gt;
     3000    0.003    0.000    0.013    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:674(escape)&lt;br /&gt;
     1000    0.005    0.000    0.012    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:416(__init__)&lt;br /&gt;
     8000    0.011    0.000    0.011    0.000 {method 'decode' of 'str' objects}&lt;br /&gt;
     3500    0.002    0.000    0.006    0.000 {method '_checkReadable' of '_io._IOBase' objects}&lt;br /&gt;
     4000    0.002    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:128(&amp;lt;genexpr&amp;gt;)&lt;br /&gt;
     2000    0.003    0.000    0.005    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/converters.py:19(escape_item)&lt;br /&gt;
     7500    0.003    0.000    0.005    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:117(ensure_bytes)&lt;br /&gt;
     2000    0.003    0.000    0.005    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:386(description)&lt;br /&gt;
     5500    0.004    0.000    0.004    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:271(advance)&lt;br /&gt;
      500    0.002    0.000    0.004    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:442(__init__)&lt;br /&gt;
     1000    0.001    0.000    0.004    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:684(escape_string)&lt;br /&gt;
     3500    0.004    0.000    0.004    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/_socketio.py:87(readable)&lt;br /&gt;
     3500    0.003    0.000    0.003    0.000 {method '_checkClosed' of '_io._IOBase' objects}&lt;br /&gt;
     1000    0.001    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/converters.py:59(escape_string)&lt;br /&gt;
     1500    0.003    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:1043(__init__)&lt;br /&gt;
     1500    0.001    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:94(nextset)&lt;br /&gt;
     4000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:397(get_column_length)&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/converters.py:56(escape_float)&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 {method 'sub' of '_sre.SRE_Pattern' objects}&lt;br /&gt;
     3000    0.002    0.000    0.002    0.000 {_struct.pack}&lt;br /&gt;
     6000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:64(_get_db)&lt;br /&gt;
     1500    0.001    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:82(_nextset)&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/util.py:9(int2byte)&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:246(fetchall)&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {min}&lt;br /&gt;
     2500    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:326(is_ok_packet)&lt;br /&gt;
     2000    0.001    0.000    0.001    0.000 {method 'items' of 'dict' objects}&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {getattr}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/converters.py:52(escape_int)&lt;br /&gt;
     1000    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:262(read_all)&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:1056(__del__)&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:69(_check_executed)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/contextlib.py:21(__exit__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 test.py:7(profiled)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
DBAPI:  &amp;lt;module 'mysql.connector' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/__init__.pyc'&amp;gt;&lt;br /&gt;
total calls:          11674074 function calls in 18.316 seconds&lt;br /&gt;
&lt;br /&gt;
   Ordered by: cumulative time&lt;br /&gt;
&lt;br /&gt;
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;
      500    0.611    0.001   17.825    0.036 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:820(fetchall)&lt;br /&gt;
      500    0.002    0.000   10.829    0.022 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:655(get_rows)&lt;br /&gt;
      500    1.505    0.003   10.825    0.022 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:292(read_text_result)&lt;br /&gt;
   504500    3.208    0.000    7.065    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/network.py:219(recv_plain)&lt;br /&gt;
   500000    3.398    0.000    6.297    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:359(row_to_python)&lt;br /&gt;
  1009000    3.039    0.000    3.039    0.000 {method 'recv_into' of '_socket.socket' objects}&lt;br /&gt;
   500000    2.003    0.000    2.283    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:220(read_lc_string_list)&lt;br /&gt;
   500000    0.625    0.000    2.010    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:531(_STRING_to_python)&lt;br /&gt;
   502000    0.355    0.000    1.254    0.000 {method 'decode' of 'bytearray' objects}&lt;br /&gt;
   502000    0.212    0.000    0.899    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/utf_8.py:15(decode)&lt;br /&gt;
   502000    0.688    0.000    0.688    0.000 {_codecs.utf_8_decode}&lt;br /&gt;
   509500    0.498    0.000    0.498    0.000 {_struct.unpack_from}&lt;br /&gt;
     1500    0.009    0.000    0.490    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:452(execute)&lt;br /&gt;
   500000    0.482    0.000    0.482    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:413(_INT_to_python)&lt;br /&gt;
     1500    0.004    0.000    0.436    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:705(cmd_query)&lt;br /&gt;
   500000    0.352    0.000    0.352    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:405(_FLOAT_to_python)&lt;br /&gt;
   504500    0.326    0.000    0.326    0.000 {method 'extend' of 'bytearray' objects}&lt;br /&gt;
     1500    0.006    0.000    0.318    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:481(_send_cmd)&lt;br /&gt;
  2501500    0.273    0.000    0.273    0.000 {method 'append' of 'list' objects}&lt;br /&gt;
   500500    0.240    0.000    0.240    0.000 {method 'startswith' of 'bytearray' objects}&lt;br /&gt;
   524002    0.143    0.000    0.143    0.000 {isinstance}&lt;br /&gt;
     1500    0.010    0.000    0.114    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:613(_handle_result)&lt;br /&gt;
  1004500    0.109    0.000    0.109    0.000 {len}&lt;br /&gt;
   500000    0.086    0.000    0.086    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:204(description)&lt;br /&gt;
     2000    0.013    0.000    0.038    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:226(parse_column)&lt;br /&gt;
     1000    0.008    0.000    0.034    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:346(_process_params_dict)&lt;br /&gt;
     1500    0.006    0.000    0.034    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/network.py:118(send_plain)&lt;br /&gt;
     1500    0.002    0.000    0.023    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/socket.py:223(meth)&lt;br /&gt;
     1500    0.020    0.000    0.020    0.000 {method 'sendall' of '_socket.socket' objects}&lt;br /&gt;
     5000    0.007    0.000    0.014    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/catch23.py:79(struct_unpack)&lt;br /&gt;
     1000    0.001    0.000    0.014    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:562(_handle_ok)&lt;br /&gt;
    12000    0.011    0.000    0.011    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:167(read_lc_string)&lt;br /&gt;
     1000    0.004    0.000    0.011    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:199(parse_ok)&lt;br /&gt;
     3000    0.005    0.000    0.009    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:152(to_mysql)&lt;br /&gt;
     3000    0.003    0.000    0.007    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:130(quote)&lt;br /&gt;
     1500    0.002    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:118(make_command)&lt;br /&gt;
     1500    0.004    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:406(_handle_result)&lt;br /&gt;
     3000    0.003    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:102(escape)&lt;br /&gt;
     1500    0.004    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/network.py:53(_prepare_packets)&lt;br /&gt;
     1000    0.002    0.000    0.005    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:252(parse_eof)&lt;br /&gt;
      500    0.001    0.000    0.005    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:579(_handle_eof)&lt;br /&gt;
     2000    0.003    0.000    0.004    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:545(_handle_server_status)&lt;br /&gt;
     1500    0.002    0.000    0.004    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:53(int1store)&lt;br /&gt;
     3000    0.003    0.000    0.003    0.000 {method 'encode' of 'str' objects}&lt;br /&gt;
     9001    0.003    0.000    0.003    0.000 {method 'replace' of 'str' objects}&lt;br /&gt;
     1500    0.003    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:295(_reset_result)&lt;br /&gt;
     6027    0.003    0.000    0.003    0.000 {method 'format' of 'str' objects}&lt;br /&gt;
     2500    0.003    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:296(read_lc_int)&lt;br /&gt;
     4500    0.002    0.000    0.002    0.000 {_struct.pack}&lt;br /&gt;
     3000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:1221(_set_unread_result)&lt;br /&gt;
      500    0.001    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:219(parse_column_count)&lt;br /&gt;
      500    0.001    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:757(_handle_eof)&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 {repr}&lt;br /&gt;
     4527    0.001    0.000    0.001    0.000 {getattr}&lt;br /&gt;
     4000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/constants.py:34(flag_is_set)&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:383(_handle_noresultset)&lt;br /&gt;
     4000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:1233(_get_unread_result)&lt;br /&gt;
     2001    0.001    0.000    0.001    0.000 {method 'items' of 'dict' objects}&lt;br /&gt;
     3000    0.001    0.000    0.001    0.000 {method 'lower' of 'str' objects}&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:307(_have_unread_result)&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:173(_str_to_mysql)&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 {range}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:169(_float_to_mysql)&lt;br /&gt;
     1000    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:161(_int_to_mysql)&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:1313(_get_getwarnings)&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:200(reset)&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:397(_handle_resultset)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/__init__.py:71(search_function)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/ascii.py:41(getregentry)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/contextlib.py:21(__exit__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/__init__.py:49(normalize_encoding)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/codecs.py:77(__new__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {__import__}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 test.py:7(profiled)&lt;br /&gt;
        3    0.000    0.000    0.000    0.000 {method 'get' of 'dict' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'split' of 'str' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'translate' of 'str' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'join' of 'str' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {built-in method __new__ of type object at 0x1001534e8}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {hasattr}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DBAPI:  &amp;lt;module 'MySQLdb' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/__init__.pyc'&amp;gt;&lt;br /&gt;
total calls:          47503 function calls in 0.916 seconds&lt;br /&gt;
&lt;br /&gt;
   Ordered by: cumulative time&lt;br /&gt;
&lt;br /&gt;
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;
     1500    0.008    0.000    0.915    0.001 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:164(execute)&lt;br /&gt;
     1500    0.002    0.000    0.888    0.001 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:353(_query)&lt;br /&gt;
     1500    0.004    0.000    0.498    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:315(_do_query)&lt;br /&gt;
     1500    0.016    0.000    0.388    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:358(_post_get_result)&lt;br /&gt;
     1500    0.001    0.000    0.372    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:324(_fetch_row)&lt;br /&gt;
      500    0.371    0.001    0.371    0.001 {built-in method fetch_row}&lt;br /&gt;
     1500    0.008    0.000    0.359    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:142(_do_get_result)&lt;br /&gt;
     1500    0.002    0.000    0.348    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:351(_get_result)&lt;br /&gt;
     1500    0.346    0.000    0.346    0.000 {method 'store_result' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.134    0.000    0.134    0.000 {method 'query' of '_mysql.connection' objects}&lt;br /&gt;
     4000    0.002    0.000    0.010    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:184(&amp;lt;genexpr&amp;gt;)&lt;br /&gt;
     3000    0.002    0.000    0.008    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/connections.py:267(literal)&lt;br /&gt;
     1500    0.007    0.000    0.007    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:107(_warning_check)&lt;br /&gt;
     3000    0.002    0.000    0.006    0.000 {method 'escape' of '_mysql.connection' objects}&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/converters.py:81(Float2Str)&lt;br /&gt;
     6000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:159(_get_db)&lt;br /&gt;
     2500    0.002    0.000    0.002    0.000 {isinstance}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/connections.py:202(string_literal)&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:380(fetchall)&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 {built-in method describe}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 {method 'string_literal' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {method 'affected_rows' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {method 'insert_id' of '_mysql.connection' objects}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/converters.py:69(Thing2Str)&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 {method 'info' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 {method 'warning_count' of '_mysql.connection' objects}&lt;br /&gt;
     1000    0.000    0.000    0.000    0.000 {method 'iteritems' of 'dict' objects}&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:103(_check_executed)&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 {len}&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 {built-in method field_flags}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/contextlib.py:21(__exit__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 test.py:7(profiled)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mike Bayer</name></author>	</entry>

	<entry>
		<id>https://wiki.openstack.org/w/index.php?title=PyMySQL_evaluation&amp;diff=72498</id>
		<title>PyMySQL evaluation</title>
		<link rel="alternate" type="text/html" href="https://wiki.openstack.org/w/index.php?title=PyMySQL_evaluation&amp;diff=72498"/>
				<updated>2015-01-28T00:55:29Z</updated>
		
		<summary type="html">&lt;p&gt;Mike Bayer: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= PyMySQL Evaluation =&lt;br /&gt;
&lt;br /&gt;
This page will capture issues related to Openstack moving to the [https://github.com/PyMySQL/PyMySQL/ PyMySQL driver] for MySQL/MariaDB dbapi access.&lt;br /&gt;
&lt;br /&gt;
== Rationale ==&lt;br /&gt;
&lt;br /&gt;
While the [https://github.com/farcepest/MySQLdb1 MySQL-Python] driver is a very mature and stable driver, it does not provide compatibility with either Python 3 or with [http://eventlet.net/doc/patching.html eventlet monkeypatching] (MySQL-Python  [https://github.com/eventlet/eventlet/blob/v0.16.1/eventlet/patcher.py#L225-L237 can be monkeypatched] with eventlet, but this feature disabled by default and missed in documentation). So OpenStack's usage of MySQL-Python, combined with the fact that concurrency is provided by eventlet, means that we currently have fully serialized database access within a single process, that is, only one database command occurs at a time within an Openstack Python process.&lt;br /&gt;
&lt;br /&gt;
== Drivers Under Consideration ==&lt;br /&gt;
&lt;br /&gt;
The two drivers that are known to provide eventlet-monkeypatch compatibility are MySQL-Connector and PyMySQL, as they are written in pure Python.   The two other well-known drivers for MySQL are MySQL-Python and OurSQL, both of which are written in C and offer no explicit support for eventlet or async.  They can reportedly be built to support gevent using a system called [https://github.com/CMGS/greenify Greenify], but the maturity and/or stabiltiy of this system is unknown.&lt;br /&gt;
&lt;br /&gt;
A comparison at http://www.diamondtin.com/2014/sqlalchemy-gevent-mysql-python-drivers-comparison/ illustrates performance metrics observed with all four of these drivers, with MySQL-python built both without and with the &amp;quot;greenify&amp;quot; system.&lt;br /&gt;
&lt;br /&gt;
A summary of the status of all four drivers is as follows.&lt;br /&gt;
&lt;br /&gt;
=== MySQL-Python ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/farcepest/MySQLdb1 MySQL-Python] is the most widely used Python driver.  However, it does not support Python 3, and it does not support async systems unless used within a thread pool.   While a system known as Greenify can potentially resolve the latter situation, the Python 3 limitation is still a deal-breaker - pull request with Python 3 support [https://github.com/farcepest/MySQLdb1/pull/62 was proposed] to MySQL-python in April 2014 and still not merged.&lt;br /&gt;
&lt;br /&gt;
=== mysqlclient ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/PyMySQL/mysqlclient-python mysqlclient] is a fork of MySQL-python. It add Python 3.3 support and merges some pull requests.&lt;br /&gt;
&lt;br /&gt;
=== OurSQL ===&lt;br /&gt;
&lt;br /&gt;
[https://pythonhosted.org/oursql/ OurSQL] is an alternative MySQL driver that is also written in C.  It features fast performance and uses a different execution model than MySQL-python, using prepared statements.    OurSQL does have a Python 3 port hosted on [https://launchpad.net/oursql/py3k/ launchpad] but is not integrated with OurSQL itself nor is it published on Pypi.     Because it is written in C, it does not offer compatibility with async systems including eventlet-style monkeypatching.&lt;br /&gt;
&lt;br /&gt;
Besides the shaky Python 3 support and lack of eventlet support, OurSQL is also not maintained, seeing its last master commit on [https://code.launchpad.net/~jerub/oursql/master 2012-06-05].&lt;br /&gt;
&lt;br /&gt;
=== MySQL-Connector-Python ===&lt;br /&gt;
&lt;br /&gt;
[https://pypi.python.org/pypi/mysql-connector-python MySQL-connector-Python] is a pure Python MySQL driver, and is now released under the auspices of the MySQL project itself, as owned by Oracle.  MySQL-connector-Python supports both Python 3 as well as eventlet monkeypatching, and is well maintained.  It is endorsed by Oracle as the official Oracle-supported driver for MySQL, so to that extent, it is in most ways Openstack's first choice in driver.  However, Oracle refuses to publish MySQL-connector-Python on Pypi, which is critical to the Openstack infrastructure.  Repeated attempts to communicate with Oracle in order to resolve this issue have not made any progress.    Therefore, for this one unfortunate reason, MySQL-connector-Python will not have a place in the Openstack ecosystem unless this issue is resolved.&lt;br /&gt;
&lt;br /&gt;
=== PyMySQL ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/PyMySQL/PyMySQL PyMySQL] is a pure Python MySQL&lt;br /&gt;
driver, first written as a rough port of the MySQL-Python driver.&lt;br /&gt;
PyMySQL meets all of Openstack's criterion for a driver: it is fully&lt;br /&gt;
open source, hosted on Github, released on Pypi, is actively&lt;br /&gt;
maintained, is written in pure Python so is eventlet-monkeypatch&lt;br /&gt;
compatible, and is fully Python 3 compatible.&lt;br /&gt;
&lt;br /&gt;
As this document is named &amp;quot;PyMySQL Evaluation&amp;quot;, it should be apparent that&lt;br /&gt;
this is the driver Openstack is currently leaning towards.  However, it does&lt;br /&gt;
have some minor code quality issues which hopefully can be addressed in some&lt;br /&gt;
way; the section below titled &amp;quot;PyMySQL Code Review&amp;quot; will summarize the&lt;br /&gt;
current state of the code.  We are in fact leaning towards this driver because&lt;br /&gt;
its the only one that actually meets all criteria.  PyMySQL appears to be&lt;br /&gt;
fairly widely used, especially considering its low profile, so it's possible&lt;br /&gt;
that this is the case for many other users as well.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===MySQL DB Drivers Comparison===&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Project !! PyPi hosted !! Eventlet friendly !! Python 3 compatibility !! Maturity and/or stability  !! Comment&lt;br /&gt;
|-&lt;br /&gt;
| MySQL-Python || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: orange;&amp;quot; | Partial || style=&amp;quot;background-color: red;&amp;quot; | No  || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || Can be monkeypatched by eventlet, but only to enable thread pooling&lt;br /&gt;
|-&lt;br /&gt;
| mysqlclient || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: orange;&amp;quot; | Partial || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes  || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || MySQL-python fork, so should be similar to it&lt;br /&gt;
|-&lt;br /&gt;
| OurSQL || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: red;&amp;quot; | No || style=&amp;quot;background-color: orange;&amp;quot; | Yes, but not Pypi hosted  || style=&amp;quot;background-color: orange;&amp;quot; | ??? || Has not seen commits/releases in two years&lt;br /&gt;
|-&lt;br /&gt;
| MySQL-Connector-Python || style=&amp;quot;background-color: red;&amp;quot; | No || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes  || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || The official Oracle-supported driver for MySQL&lt;br /&gt;
|-&lt;br /&gt;
| PyMySQL || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes  || style=&amp;quot;background-color: orange;&amp;quot; | Yes, with a [http://lists.openstack.org/pipermail/openstack-dev/2014-December/052864.html proviso]. ||&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== PyMySQL Code Review ==&lt;br /&gt;
&lt;br /&gt;
PyMySQL started roughly as a pure Python port of MySQL-Python.   The sections below will&lt;br /&gt;
detail various aspects of the driver and the status of the code.   Overall, the general&lt;br /&gt;
tone of PyMySQL is one of code that was written in the spirit of pragmatism and immediate&lt;br /&gt;
need; it is straightforward, relatively free of serious antipatterns and performs&lt;br /&gt;
its task in a matter-of-fact way.  However,&lt;br /&gt;
it lacks polish and completeness in many areas that would normally be in better&lt;br /&gt;
condition for a more mature project.  These areas are all highly fixable, and it is&lt;br /&gt;
hoped that these items can be shared with the current PyMySQL developers, through&lt;br /&gt;
a combination of this document itself as well as new issues and pull requests&lt;br /&gt;
on the PyMySQL tracker, so that this fairly promising library can be pushed&lt;br /&gt;
to the next level.   This would require that the developers are amenable to&lt;br /&gt;
these improvements.&lt;br /&gt;
&lt;br /&gt;
=== Coding Style / Pep8 ===&lt;br /&gt;
&lt;br /&gt;
PyMySQL's coding style is pretty good.  The majority of it passes all&lt;br /&gt;
flake8 tests, save for a few dozen whitespace issues and&lt;br /&gt;
some long lines here and there within the core&lt;br /&gt;
modules, and moreso within the tests which appear to be a bit more&lt;br /&gt;
crufty than the core modules.  A full run of flake8 produces only 128&lt;br /&gt;
errors, and with a default run of autopep8 against E1,E2,E3, we can&lt;br /&gt;
fix all of them in one pass except for 38 remaining long line warnings.&lt;br /&gt;
The code layout is mostly idiomatic and reasonable.&lt;br /&gt;
&lt;br /&gt;
The codebase is probably short on docstrings.  Most of the Python DBAPI&lt;br /&gt;
that's public is very sparse, so the majority of functions and methods&lt;br /&gt;
we see within the core modules here are not typically invoked from the&lt;br /&gt;
outside.  The methods that are part of the DBAPI spec do seem to have&lt;br /&gt;
docstrings for the most part, although these docstrings are mostly&lt;br /&gt;
extremely terse (example, docstring for cursor.execute():&lt;br /&gt;
&amp;quot;'''Execute a query'''&amp;quot;).  The purpose of methods and functions is&lt;br /&gt;
pretty easy to discern just by their names and implementations, however&lt;br /&gt;
those who seek inline documentation as a means to understand a codebase&lt;br /&gt;
will be very disappointed.&lt;br /&gt;
&lt;br /&gt;
=== Test Coverage ===&lt;br /&gt;
&lt;br /&gt;
The test coverage for PyMySQL is definitely lacking.   A visual inspection&lt;br /&gt;
of the test modules that are present will be surprising to most seasoned&lt;br /&gt;
DBAPI users, as while there are about a dozen distinct test modules, each&lt;br /&gt;
one is surprisingly short, most containing fewer than ten tests; for a&lt;br /&gt;
full test run, there's only 126 tests; contrast this to MySQL-Connector-Python&lt;br /&gt;
which has 531 tests.&lt;br /&gt;
&lt;br /&gt;
An additional critical issue with the tests is that virtually all tests are&lt;br /&gt;
live round-trip tests against a real database; there are no non-live&lt;br /&gt;
unit tests of any kind, leaving codepaths that are not easily exercised&lt;br /&gt;
on a generic MySQL database out in the cold.  This means that features designed&lt;br /&gt;
for particular versions of MySQL, particular datatypes, particular error&lt;br /&gt;
condtions, and particular kinds of message packets that aren't created for&lt;br /&gt;
real are simply not tested.&lt;br /&gt;
&lt;br /&gt;
This lack of tests definitely shows when run with coverage.  The test suite&lt;br /&gt;
is designed to be run with unittest, and features a simple &amp;quot;runtests.py&amp;quot;&lt;br /&gt;
front-end script, with no sign of any use of coverage tools anywhere,&lt;br /&gt;
including in its tox.ini script.  Running the suite with py.test plus py-test-cov&lt;br /&gt;
produces an 85% coverage level overall, which isn't *too* terrible, but&lt;br /&gt;
concerningly, the biggest gaps are in the three most critical modules,&lt;br /&gt;
connections.py, converters.py and cursors.py:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Name                                                              Stmts   Miss  Cover&lt;br /&gt;
-------------------------------------------------------------------------------------&lt;br /&gt;
pymysql/__init__                                                     51      3    94%&lt;br /&gt;
pymysql/_compat                                                      14      4    71%&lt;br /&gt;
pymysql/_socketio                                                    72     30    58%&lt;br /&gt;
pymysql/charset                                                     228      3    99%&lt;br /&gt;
pymysql/connections                                                 827    188    77%&lt;br /&gt;
pymysql/constants/CLIENT                                             18      0   100%&lt;br /&gt;
pymysql/constants/COMMAND                                            32      0   100%&lt;br /&gt;
pymysql/constants/ER                                                471      0   100%&lt;br /&gt;
pymysql/constants/FIELD_TYPE                                         29      0   100%&lt;br /&gt;
pymysql/constants/FLAG                                               15      0   100%&lt;br /&gt;
pymysql/constants/SERVER_STATUS                                      10      0   100%&lt;br /&gt;
pymysql/constants/__init__                                            0      0   100%&lt;br /&gt;
pymysql/converters                                                  148     51    66%&lt;br /&gt;
pymysql/cursors                                                     273     37    86%&lt;br /&gt;
pymysql/err                                                          40      1    98%&lt;br /&gt;
pymysql/tests/__init__                                               14      5    64%&lt;br /&gt;
pymysql/tests/base                                                   25      2    92%&lt;br /&gt;
pymysql/tests/test_DictCursor                                        73      2    97%&lt;br /&gt;
pymysql/tests/test_SSCursor                                          56      6    89%&lt;br /&gt;
pymysql/tests/test_basic                                            186      4    98%&lt;br /&gt;
pymysql/tests/test_connection                                        57      8    86%&lt;br /&gt;
pymysql/tests/test_example                                           17      2    88%&lt;br /&gt;
pymysql/tests/test_issues                                           300     48    84%&lt;br /&gt;
pymysql/tests/test_load_local                                        39      2    95%&lt;br /&gt;
pymysql/tests/test_nextset                                           43      4    91%&lt;br /&gt;
pymysql/tests/thirdparty/__init__                                     7      5    29%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/__init__                        6      2    67%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/capabilities                  196     23    88%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/dbapi20                       423    121    71%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_capabilities      73      6    92%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_dbapi20          100      2    98%&lt;br /&gt;
pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_nonstandard       57      3    95%&lt;br /&gt;
pymysql/times                                                        12      0   100%&lt;br /&gt;
pymysql/util                                                         14      7    50%&lt;br /&gt;
-------------------------------------------------------------------------------------&lt;br /&gt;
TOTAL                                                              3926    569    86%&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The lack of coverage in converters.py refers to functions related to handling&lt;br /&gt;
incoming bound values as well as outgoing result values.    Key data escaping&lt;br /&gt;
features such as escaping of dictionaries, sets, some unicode objects and&lt;br /&gt;
byte objects as well as many pathways into date and time-processing functions are not&lt;br /&gt;
covered.&lt;br /&gt;
&lt;br /&gt;
In connections.py, uncovered features include various SSL and&lt;br /&gt;
very legacy (e.g. version 3.23) MySQL features, but also some protocol parsing&lt;br /&gt;
features.&lt;br /&gt;
&lt;br /&gt;
In cursors.py, there is coverage for an elaborate and mostly undocumented&lt;br /&gt;
performance-related feature that rewrites an INSERT statement to have an&lt;br /&gt;
extended VALUES clause (_do_execute_many), however it fails to cover the very&lt;br /&gt;
likely case where the function will need to break the input set into&lt;br /&gt;
multiple chunks.   The code which does this obviously works and looks perfectly&lt;br /&gt;
fine, but coverage marks it with a &amp;quot;!&amp;quot;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;gt;         for arg in args:&lt;br /&gt;
&amp;gt;             v = values % escape(arg, conn)&lt;br /&gt;
&amp;gt;             if isinstance(v, text_type):&lt;br /&gt;
&amp;gt;                 v = v.encode(encoding)&lt;br /&gt;
&amp;gt;             if len(sql) + len(v) + 1 &amp;gt; max_stmt_length:&lt;br /&gt;
!                 rows += self.execute(sql)&lt;br /&gt;
!                 sql = bytearray(prefix)&lt;br /&gt;
&amp;gt;             else:&lt;br /&gt;
&amp;gt;                 sql += b','&lt;br /&gt;
&amp;gt;             sql += v&lt;br /&gt;
&amp;gt;         rows += self.execute(sql)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The most prominent missing coverage in cursors.py seems to be&lt;br /&gt;
for its &amp;quot;scrollable cursor&amp;quot; support.&lt;br /&gt;
&lt;br /&gt;
The lack of unit-style non-live tests as well as the overall terseness&lt;br /&gt;
of the tests is a major concern for PyMySQL.  An additional concern&lt;br /&gt;
is that in many areas,&lt;br /&gt;
many dozens of individual &amp;quot;feature tests&amp;quot; are combined together into&lt;br /&gt;
a single method, making it extremely difficult to identify and debug&lt;br /&gt;
regressions as well as to expand upon the test suite.&lt;br /&gt;
In test_SSCursor.py we have essentially one giant&lt;br /&gt;
test with about fourteen individual assertions.  In test_basic.py, test_datatypes&lt;br /&gt;
attempts to run round-trip tests against about fourteen datatypes at once,&lt;br /&gt;
though to its credit it appears to have more individualized tests for many&lt;br /&gt;
of these datatypes as well.&lt;br /&gt;
&lt;br /&gt;
This style of testing is common for newer projects where the tests are&lt;br /&gt;
written in as much of an expedient way as possible; the author of this&lt;br /&gt;
document will freely admit that his own SQLAlchemy still&lt;br /&gt;
has a lot of tests like this as well (though at well over 5000 tests, that&lt;br /&gt;
proportion has become fairly small :) ).  The proportion of tests which&lt;br /&gt;
are like this in PyMySQL, relative to its very low number of tests to&lt;br /&gt;
start with, can be easily improved.&lt;br /&gt;
&lt;br /&gt;
There is also a suite called &amp;quot;test_issues&amp;quot;, which intends to&lt;br /&gt;
accumulate tests against specific issues that have been reported. This&lt;br /&gt;
isn't a bad idea, though it would be nice to see an effort made into&lt;br /&gt;
categorizing these tests into the actual features they are testing,&lt;br /&gt;
rather than a meaningless list of issue numbers, which could then form&lt;br /&gt;
as the basis for new suites centered around those areas of&lt;br /&gt;
functionality.  The issues reported should be used as the inspiration&lt;br /&gt;
for improvement of the test suite, rather than just another line-item&lt;br /&gt;
to be filed away.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Library Documentation ===&lt;br /&gt;
&lt;br /&gt;
As the Python DBAPI is already a well-documented API, and the users of&lt;br /&gt;
PyMySQL are typically coming from MySQL-Python which is documented&lt;br /&gt;
to some degree and for which the use contract is widely known, it seems&lt;br /&gt;
unlikely that users of PyMySQL are eager for comprehensive documentation&lt;br /&gt;
specific to this library.&lt;br /&gt;
&lt;br /&gt;
That being said, as far as can be discerned, PyMySQL has absolutely no&lt;br /&gt;
library documentation of any kind.  There's a README which&lt;br /&gt;
essentially refers to pep-249, an example.py in the root which shows a&lt;br /&gt;
very basic connection / round trip, and that is absolutely it. In&lt;br /&gt;
order to know anything about the API, specific parameters, etc., one&lt;br /&gt;
either has to go off of the MySQL-Python documentation and assume&lt;br /&gt;
PyMySQL also features the same parameter, or read the source code.&lt;br /&gt;
&lt;br /&gt;
This has probably been perfectly fine for PyMySQL and its userbase, however&lt;br /&gt;
for PyMySQL to enter the big time, it should really build up a rudimentary&lt;br /&gt;
Sphinx build and publish it up to RTD.   This would be very easy to get&lt;br /&gt;
started, and once present, new documentation sections can be added&lt;br /&gt;
iteratively.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Architecture and Performance ===&lt;br /&gt;
&lt;br /&gt;
As stated before, PyMySQL has a really matter-of-fact and straightforward&lt;br /&gt;
implementation.   The code is very readable and its easy to discern even&lt;br /&gt;
unusual features such as the INSERT..VALUES rewriting.  As far as the design,&lt;br /&gt;
there are lots of areas where performance suffers a bit at the Python level;&lt;br /&gt;
these areas could be greatly improved very easily with a little bit of&lt;br /&gt;
attention, however it's seems likely that there hasn't been too much&lt;br /&gt;
work done with Python performance profiling here (that is, using cProfile).&lt;br /&gt;
&lt;br /&gt;
As we are dealing with a driver that is communicating with a low level&lt;br /&gt;
protocol over a socket, it's critical that sending and receiving messages&lt;br /&gt;
is done as efficiently as possible.  The mechanisms used in PyMySQL rely&lt;br /&gt;
heavily on standard Python objects, without the use of __slots__; this means&lt;br /&gt;
that for individual messages on a socket, we are creating a heavyweight object,&lt;br /&gt;
calling its __init__() method and a creating a new __dict__ each time;&lt;br /&gt;
the most common object, OKPacketWrapper(), already wraps a MysqlPacket()&lt;br /&gt;
object, so this is fairly wasteful; it also implements a `__getattr__` scheme&lt;br /&gt;
to proxy attribute access from itself to the internal MysqlPacket; this is a&lt;br /&gt;
very inefficient mode of operation in Python as it means a lookup must first&lt;br /&gt;
fail on the OKPacketWrapper before it invokes the `__getattr__()` method&lt;br /&gt;
and does another attribute lookup.&lt;br /&gt;
&lt;br /&gt;
There are also lots of areas where the same fairly expensive operation&lt;br /&gt;
is done more times than needed.  In particular, when calling upon&lt;br /&gt;
cursor.execute(), for parameters passed as a dictionary under Python 2K,&lt;br /&gt;
the isinstance() builtin function will be called on the incoming arguments&lt;br /&gt;
four times at a minimum, with two of each call against the exact same&lt;br /&gt;
criteria.  Additional logic will then call isinstance() on every key of&lt;br /&gt;
the dictionary as well as at least twice on every value within the dictionary,&lt;br /&gt;
in order to determine if the value needs to be converted to bytes as well&lt;br /&gt;
as for some top-level escaping logic checks; the escaping logic then&lt;br /&gt;
goes further with more checks for type() to match the object to a final&lt;br /&gt;
converter function.  These areas of functionality would appear to most&lt;br /&gt;
Python programmers to be reasonable and straightforward, however under&lt;br /&gt;
Python profiling the repeated calls to functions shows up very prominently;&lt;br /&gt;
see Appendix A for the test suite and full performance results, where we&lt;br /&gt;
run the same calls under PyMySQL and MySQL-Connector-Python; the PyMySQL&lt;br /&gt;
version uses 26850503 calls and MySQL-Connector-Python uses 11674074.&lt;br /&gt;
&lt;br /&gt;
In PyMySQL's defense, despite more than twice as many function calls on average,&lt;br /&gt;
for some reason it is still outperforming MySQL-Connector-Python, leading&lt;br /&gt;
me to suspect perhaps MySQL-connector is relying upon a try/except somewhere&lt;br /&gt;
or otherwise doing something less efficiently; also, while PyMySQL used 526000&lt;br /&gt;
isinstance() calls, MySQL-Connector-Python is using almost that much as well.&lt;br /&gt;
&lt;br /&gt;
Regardless, PyMySQL is still far slower than a native library like MySQL-Python&lt;br /&gt;
and can greatly improve its callcount.  MySQLdb is added at the end and&lt;br /&gt;
runs the whole test in only 47K function calls and an incredible .9 seconds,&lt;br /&gt;
compared to PyMySQL's 16 seconds and MySQL-connectors 18 seconds.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== History Documentation ===&lt;br /&gt;
&lt;br /&gt;
=== Community Involvement ===&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Appendix A - Performance Tests ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
import cProfile&lt;br /&gt;
import StringIO&lt;br /&gt;
import pstats&lt;br /&gt;
import contextlib&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextlib.contextmanager&lt;br /&gt;
def profiled(dbapi):&lt;br /&gt;
    pr = cProfile.Profile()&lt;br /&gt;
    pr.enable()&lt;br /&gt;
    yield&lt;br /&gt;
    pr.disable()&lt;br /&gt;
    s = StringIO.StringIO()&lt;br /&gt;
    ps = pstats.Stats(pr, stream=s).sort_stats('cumulative')&lt;br /&gt;
    print &amp;quot;DBAPI:  %s&amp;quot; % dbapi&lt;br /&gt;
    ps.print_stats()&lt;br /&gt;
    # uncomment this to see who's calling what&lt;br /&gt;
    # ps.print_callers()&lt;br /&gt;
    print &amp;quot;total calls: %s&amp;quot; % s.getvalue()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
import MySQLdb&lt;br /&gt;
import pymysql&lt;br /&gt;
from mysql import connector as mysqlconnector&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def go(dbapi):&lt;br /&gt;
    conn = dbapi.connect(&lt;br /&gt;
        user='scott', passwd='tiger', host='localhost', db='test')&lt;br /&gt;
    cursor = conn.cursor()&lt;br /&gt;
    cursor.execute(&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
CREATE TABLE IF NOT EXISTS test_things (&lt;br /&gt;
    x INTEGER,&lt;br /&gt;
    y VARCHAR(255),&lt;br /&gt;
    z FLOAT&lt;br /&gt;
) engine=InnoDB&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;)&lt;br /&gt;
    cursor.execute(&amp;quot;DELETE from test_things&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    with profiled(dbapi):&lt;br /&gt;
        for row in xrange(1000):&lt;br /&gt;
            cursor.execute(&lt;br /&gt;
                &amp;quot;INSERT INTO test_things (x, y, z) &amp;quot;&lt;br /&gt;
                &amp;quot;VALUES (%(x)s, %(y)s, %(z)s)&amp;quot;,&lt;br /&gt;
                {&amp;quot;x&amp;quot;: row, &amp;quot;y&amp;quot;: &amp;quot;row number %d&amp;quot; % row,&lt;br /&gt;
                 &amp;quot;z&amp;quot;: row * 4.57292, }&lt;br /&gt;
            )&lt;br /&gt;
&lt;br /&gt;
        for x in xrange(500):&lt;br /&gt;
            cursor.execute(&lt;br /&gt;
                &amp;quot;select * from test_things&amp;quot;)&lt;br /&gt;
            for row in cursor.fetchall():&lt;br /&gt;
                row[0], row[1], row[2]&lt;br /&gt;
&lt;br /&gt;
    cursor.close()&lt;br /&gt;
    conn.close()&lt;br /&gt;
&lt;br /&gt;
go(pymysql)&lt;br /&gt;
go(mysqlconnector)&lt;br /&gt;
go(MySQLdb)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DBAPI:  &amp;lt;module 'pymysql' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/__init__.pyc'&amp;gt;&lt;br /&gt;
total calls:          26850503 function calls in 16.017 seconds&lt;br /&gt;
&lt;br /&gt;
   Ordered by: cumulative time&lt;br /&gt;
&lt;br /&gt;
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;
     1500    0.010    0.000   16.016    0.011 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:107(execute)&lt;br /&gt;
     1500    0.003    0.000   15.976    0.011 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:271(_query)&lt;br /&gt;
     1500    0.003    0.000   15.952    0.011 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:708(query)&lt;br /&gt;
     1500    0.003    0.000   15.911    0.011 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:854(_read_query_result)&lt;br /&gt;
     1500    0.004    0.000   15.905    0.011 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:1060(read)&lt;br /&gt;
      500    0.003    0.000   15.658    0.031 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:1106(_read_result_packet)&lt;br /&gt;
      500    0.921    0.002   15.527    0.031 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:1139(_read_rowdata_packet)&lt;br /&gt;
   500000    3.404    0.000    9.210    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:1152(_read_row_from_packet)&lt;br /&gt;
  2012000    1.179    0.000    5.374    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:314(read_length_coded_string)&lt;br /&gt;
   504500    1.533    0.000    5.246    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:808(_read_packet)&lt;br /&gt;
  2014500    1.265    0.000    3.056    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:296(read_length_encoded_integer)&lt;br /&gt;
  3539500    2.469    0.000    2.782    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:248(read)&lt;br /&gt;
  1009000    0.795    0.000    2.300    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:833(_read_bytes)&lt;br /&gt;
  1009000    1.178    0.000    1.400    0.000 {method 'read' of '_io.BufferedReader' objects}&lt;br /&gt;
   508500    0.381    0.000    0.699    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/util.py:3(byte2int)&lt;br /&gt;
  5058000    0.468    0.000    0.468    0.000 {len}&lt;br /&gt;
   500500    0.219    0.000    0.453    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:1098(_check_packet_is_eof)&lt;br /&gt;
   504500    0.211    0.000    0.380    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:342(check_error)&lt;br /&gt;
  2504000    0.259    0.000    0.259    0.000 {method 'append' of 'list' objects}&lt;br /&gt;
   501500    0.182    0.000    0.231    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:329(is_eof_packet)&lt;br /&gt;
     3500    0.009    0.000    0.222    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/_socketio.py:45(readinto)&lt;br /&gt;
  1022000    0.205    0.000    0.205    0.000 {_struct.unpack}&lt;br /&gt;
     3500    0.203    0.000    0.203    0.000 {method 'recv_into' of '_socket.socket' objects}&lt;br /&gt;
   526000    0.198    0.000    0.198    0.000 {isinstance}&lt;br /&gt;
  1501000    0.197    0.000    0.197    0.000 {method 'get' of 'dict' objects}&lt;br /&gt;
   504500    0.173    0.000    0.173    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:241(__init__)&lt;br /&gt;
   504500    0.169    0.000    0.169    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:339(is_error_packet)&lt;br /&gt;
  2014500    0.162    0.000    0.162    0.000 {ord}&lt;br /&gt;
      500    0.006    0.000    0.127    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:1176(_get_descriptions)&lt;br /&gt;
     2000    0.003    0.000    0.088    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:361(__init__)&lt;br /&gt;
     2000    0.020    0.000    0.085    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:365(__parse_field_descriptor)&lt;br /&gt;
   500000    0.055    0.000    0.055    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/converters.py:301(through)&lt;br /&gt;
     1500    0.006    0.000    0.037    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:877(_execute_command)&lt;br /&gt;
     1500    0.003    0.000    0.027    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:848(_write_bytes)&lt;br /&gt;
     1500    0.002    0.000    0.024    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/socket.py:223(meth)&lt;br /&gt;
     1500    0.021    0.000    0.021    0.000 {method 'sendall' of '_socket.socket' objects}&lt;br /&gt;
     1500    0.020    0.000    0.021    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:278(_do_get_result)&lt;br /&gt;
     1000    0.003    0.000    0.019    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:97(_escape_args)&lt;br /&gt;
     4000    0.002    0.000    0.015    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:101(&amp;lt;genexpr&amp;gt;)&lt;br /&gt;
     1000    0.001    0.000    0.014    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:1089(_read_ok_packet)&lt;br /&gt;
     3000    0.003    0.000    0.013    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:674(escape)&lt;br /&gt;
     1000    0.005    0.000    0.012    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:416(__init__)&lt;br /&gt;
     8000    0.011    0.000    0.011    0.000 {method 'decode' of 'str' objects}&lt;br /&gt;
     3500    0.002    0.000    0.006    0.000 {method '_checkReadable' of '_io._IOBase' objects}&lt;br /&gt;
     4000    0.002    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:128(&amp;lt;genexpr&amp;gt;)&lt;br /&gt;
     2000    0.003    0.000    0.005    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/converters.py:19(escape_item)&lt;br /&gt;
     7500    0.003    0.000    0.005    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:117(ensure_bytes)&lt;br /&gt;
     2000    0.003    0.000    0.005    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:386(description)&lt;br /&gt;
     5500    0.004    0.000    0.004    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:271(advance)&lt;br /&gt;
      500    0.002    0.000    0.004    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:442(__init__)&lt;br /&gt;
     1000    0.001    0.000    0.004    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:684(escape_string)&lt;br /&gt;
     3500    0.004    0.000    0.004    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/_socketio.py:87(readable)&lt;br /&gt;
     3500    0.003    0.000    0.003    0.000 {method '_checkClosed' of '_io._IOBase' objects}&lt;br /&gt;
     1000    0.001    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/converters.py:59(escape_string)&lt;br /&gt;
     1500    0.003    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:1043(__init__)&lt;br /&gt;
     1500    0.001    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:94(nextset)&lt;br /&gt;
     4000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:397(get_column_length)&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/converters.py:56(escape_float)&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 {method 'sub' of '_sre.SRE_Pattern' objects}&lt;br /&gt;
     3000    0.002    0.000    0.002    0.000 {_struct.pack}&lt;br /&gt;
     6000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:64(_get_db)&lt;br /&gt;
     1500    0.001    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:82(_nextset)&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/util.py:9(int2byte)&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:246(fetchall)&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {min}&lt;br /&gt;
     2500    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:326(is_ok_packet)&lt;br /&gt;
     2000    0.001    0.000    0.001    0.000 {method 'items' of 'dict' objects}&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {getattr}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/converters.py:52(escape_int)&lt;br /&gt;
     1000    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:262(read_all)&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/connections.py:1056(__del__)&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pymysql/cursors.py:69(_check_executed)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/contextlib.py:21(__exit__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 test.py:7(profiled)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
DBAPI:  &amp;lt;module 'mysql.connector' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/__init__.pyc'&amp;gt;&lt;br /&gt;
total calls:          11674074 function calls in 18.316 seconds&lt;br /&gt;
&lt;br /&gt;
   Ordered by: cumulative time&lt;br /&gt;
&lt;br /&gt;
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;
      500    0.611    0.001   17.825    0.036 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:820(fetchall)&lt;br /&gt;
      500    0.002    0.000   10.829    0.022 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:655(get_rows)&lt;br /&gt;
      500    1.505    0.003   10.825    0.022 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:292(read_text_result)&lt;br /&gt;
   504500    3.208    0.000    7.065    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/network.py:219(recv_plain)&lt;br /&gt;
   500000    3.398    0.000    6.297    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:359(row_to_python)&lt;br /&gt;
  1009000    3.039    0.000    3.039    0.000 {method 'recv_into' of '_socket.socket' objects}&lt;br /&gt;
   500000    2.003    0.000    2.283    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:220(read_lc_string_list)&lt;br /&gt;
   500000    0.625    0.000    2.010    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:531(_STRING_to_python)&lt;br /&gt;
   502000    0.355    0.000    1.254    0.000 {method 'decode' of 'bytearray' objects}&lt;br /&gt;
   502000    0.212    0.000    0.899    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/utf_8.py:15(decode)&lt;br /&gt;
   502000    0.688    0.000    0.688    0.000 {_codecs.utf_8_decode}&lt;br /&gt;
   509500    0.498    0.000    0.498    0.000 {_struct.unpack_from}&lt;br /&gt;
     1500    0.009    0.000    0.490    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:452(execute)&lt;br /&gt;
   500000    0.482    0.000    0.482    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:413(_INT_to_python)&lt;br /&gt;
     1500    0.004    0.000    0.436    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:705(cmd_query)&lt;br /&gt;
   500000    0.352    0.000    0.352    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:405(_FLOAT_to_python)&lt;br /&gt;
   504500    0.326    0.000    0.326    0.000 {method 'extend' of 'bytearray' objects}&lt;br /&gt;
     1500    0.006    0.000    0.318    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:481(_send_cmd)&lt;br /&gt;
  2501500    0.273    0.000    0.273    0.000 {method 'append' of 'list' objects}&lt;br /&gt;
   500500    0.240    0.000    0.240    0.000 {method 'startswith' of 'bytearray' objects}&lt;br /&gt;
   524002    0.143    0.000    0.143    0.000 {isinstance}&lt;br /&gt;
     1500    0.010    0.000    0.114    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:613(_handle_result)&lt;br /&gt;
  1004500    0.109    0.000    0.109    0.000 {len}&lt;br /&gt;
   500000    0.086    0.000    0.086    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:204(description)&lt;br /&gt;
     2000    0.013    0.000    0.038    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:226(parse_column)&lt;br /&gt;
     1000    0.008    0.000    0.034    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:346(_process_params_dict)&lt;br /&gt;
     1500    0.006    0.000    0.034    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/network.py:118(send_plain)&lt;br /&gt;
     1500    0.002    0.000    0.023    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/socket.py:223(meth)&lt;br /&gt;
     1500    0.020    0.000    0.020    0.000 {method 'sendall' of '_socket.socket' objects}&lt;br /&gt;
     5000    0.007    0.000    0.014    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/catch23.py:79(struct_unpack)&lt;br /&gt;
     1000    0.001    0.000    0.014    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:562(_handle_ok)&lt;br /&gt;
    12000    0.011    0.000    0.011    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:167(read_lc_string)&lt;br /&gt;
     1000    0.004    0.000    0.011    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:199(parse_ok)&lt;br /&gt;
     3000    0.005    0.000    0.009    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:152(to_mysql)&lt;br /&gt;
     3000    0.003    0.000    0.007    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:130(quote)&lt;br /&gt;
     1500    0.002    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:118(make_command)&lt;br /&gt;
     1500    0.004    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:406(_handle_result)&lt;br /&gt;
     3000    0.003    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:102(escape)&lt;br /&gt;
     1500    0.004    0.000    0.006    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/network.py:53(_prepare_packets)&lt;br /&gt;
     1000    0.002    0.000    0.005    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:252(parse_eof)&lt;br /&gt;
      500    0.001    0.000    0.005    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:579(_handle_eof)&lt;br /&gt;
     2000    0.003    0.000    0.004    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:545(_handle_server_status)&lt;br /&gt;
     1500    0.002    0.000    0.004    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:53(int1store)&lt;br /&gt;
     3000    0.003    0.000    0.003    0.000 {method 'encode' of 'str' objects}&lt;br /&gt;
     9001    0.003    0.000    0.003    0.000 {method 'replace' of 'str' objects}&lt;br /&gt;
     1500    0.003    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:295(_reset_result)&lt;br /&gt;
     6027    0.003    0.000    0.003    0.000 {method 'format' of 'str' objects}&lt;br /&gt;
     2500    0.003    0.000    0.003    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/utils.py:296(read_lc_int)&lt;br /&gt;
     4500    0.002    0.000    0.002    0.000 {_struct.pack}&lt;br /&gt;
     3000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:1221(_set_unread_result)&lt;br /&gt;
      500    0.001    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/protocol.py:219(parse_column_count)&lt;br /&gt;
      500    0.001    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:757(_handle_eof)&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 {repr}&lt;br /&gt;
     4527    0.001    0.000    0.001    0.000 {getattr}&lt;br /&gt;
     4000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/constants.py:34(flag_is_set)&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:383(_handle_noresultset)&lt;br /&gt;
     4000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:1233(_get_unread_result)&lt;br /&gt;
     2001    0.001    0.000    0.001    0.000 {method 'items' of 'dict' objects}&lt;br /&gt;
     3000    0.001    0.000    0.001    0.000 {method 'lower' of 'str' objects}&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:307(_have_unread_result)&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:173(_str_to_mysql)&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 {range}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:169(_float_to_mysql)&lt;br /&gt;
     1000    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/conversion.py:161(_int_to_mysql)&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/connection.py:1313(_get_getwarnings)&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:200(reset)&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mysql/connector/cursor.py:397(_handle_resultset)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/__init__.py:71(search_function)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/ascii.py:41(getregentry)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/contextlib.py:21(__exit__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/__init__.py:49(normalize_encoding)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/codecs.py:77(__new__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {__import__}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 test.py:7(profiled)&lt;br /&gt;
        3    0.000    0.000    0.000    0.000 {method 'get' of 'dict' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'split' of 'str' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'translate' of 'str' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'join' of 'str' objects}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {built-in method __new__ of type object at 0x1001534e8}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {hasattr}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DBAPI:  &amp;lt;module 'MySQLdb' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/__init__.pyc'&amp;gt;&lt;br /&gt;
total calls:          47503 function calls in 0.916 seconds&lt;br /&gt;
&lt;br /&gt;
   Ordered by: cumulative time&lt;br /&gt;
&lt;br /&gt;
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;
     1500    0.008    0.000    0.915    0.001 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:164(execute)&lt;br /&gt;
     1500    0.002    0.000    0.888    0.001 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:353(_query)&lt;br /&gt;
     1500    0.004    0.000    0.498    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:315(_do_query)&lt;br /&gt;
     1500    0.016    0.000    0.388    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:358(_post_get_result)&lt;br /&gt;
     1500    0.001    0.000    0.372    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:324(_fetch_row)&lt;br /&gt;
      500    0.371    0.001    0.371    0.001 {built-in method fetch_row}&lt;br /&gt;
     1500    0.008    0.000    0.359    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:142(_do_get_result)&lt;br /&gt;
     1500    0.002    0.000    0.348    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:351(_get_result)&lt;br /&gt;
     1500    0.346    0.000    0.346    0.000 {method 'store_result' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.134    0.000    0.134    0.000 {method 'query' of '_mysql.connection' objects}&lt;br /&gt;
     4000    0.002    0.000    0.010    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:184(&amp;lt;genexpr&amp;gt;)&lt;br /&gt;
     3000    0.002    0.000    0.008    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/connections.py:267(literal)&lt;br /&gt;
     1500    0.007    0.000    0.007    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:107(_warning_check)&lt;br /&gt;
     3000    0.002    0.000    0.006    0.000 {method 'escape' of '_mysql.connection' objects}&lt;br /&gt;
     1000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/converters.py:81(Float2Str)&lt;br /&gt;
     6000    0.002    0.000    0.002    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:159(_get_db)&lt;br /&gt;
     2500    0.002    0.000    0.002    0.000 {isinstance}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/connections.py:202(string_literal)&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:380(fetchall)&lt;br /&gt;
      500    0.001    0.000    0.001    0.000 {built-in method describe}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 {method 'string_literal' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {method 'affected_rows' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.001    0.000    0.001    0.000 {method 'insert_id' of '_mysql.connection' objects}&lt;br /&gt;
     1000    0.001    0.000    0.001    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/converters.py:69(Thing2Str)&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 {method 'info' of '_mysql.connection' objects}&lt;br /&gt;
     1500    0.000    0.000    0.000    0.000 {method 'warning_count' of '_mysql.connection' objects}&lt;br /&gt;
     1000    0.000    0.000    0.000    0.000 {method 'iteritems' of 'dict' objects}&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/MySQLdb/cursors.py:103(_check_executed)&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 {len}&lt;br /&gt;
      500    0.000    0.000    0.000    0.000 {built-in method field_flags}&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/contextlib.py:21(__exit__)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 test.py:7(profiled)&lt;br /&gt;
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mike Bayer</name></author>	</entry>

	<entry>
		<id>https://wiki.openstack.org/w/index.php?title=PyMySQL_evaluation&amp;diff=71851</id>
		<title>PyMySQL evaluation</title>
		<link rel="alternate" type="text/html" href="https://wiki.openstack.org/w/index.php?title=PyMySQL_evaluation&amp;diff=71851"/>
				<updated>2015-01-14T17:13:50Z</updated>
		
		<summary type="html">&lt;p&gt;Mike Bayer: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= PyMySQL Evaluation =&lt;br /&gt;
&lt;br /&gt;
This page will capture issues related to Openstack moving to the [https://github.com/PyMySQL/PyMySQL/ PyMySQL driver] for MySQL/MariaDB dbapi access.&lt;br /&gt;
&lt;br /&gt;
== Rationale ==&lt;br /&gt;
&lt;br /&gt;
While the [https://github.com/farcepest/MySQLdb1 MySQL-Python] driver is a very mature and stable driver, it does not provide compatibility with either Python 3 or with [http://eventlet.net/doc/patching.html eventlet monkeypatching] (MySQL-Python  [https://github.com/eventlet/eventlet/blob/v0.16.1/eventlet/patcher.py#L225-L237 can be monkeypatched] with eventlet, but this feature disabled by default and missed in documentation). So OpenStack's usage of MySQL-Python, combined with the fact that concurrency is provided by eventlet, means that we currently have fully serialized database access within a single process, that is, only one database command occurs at a time within an Openstack Python process.&lt;br /&gt;
&lt;br /&gt;
== Drivers Under Consideration ==&lt;br /&gt;
&lt;br /&gt;
The two drivers that are known to provide eventlet-monkeypatch compatibility are MySQL-Connector and PyMySQL, as they are written in pure Python.   The two other well-known drivers for MySQL are MySQL-Python and OurSQL, both of which are written in C and offer no explicit support for eventlet or async.  They can reportedly be built to support gevent using a system called [https://github.com/CMGS/greenify Greenify], but the maturity and/or stabiltiy of this system is unknown.   &lt;br /&gt;
&lt;br /&gt;
A comparison at http://www.diamondtin.com/2014/sqlalchemy-gevent-mysql-python-drivers-comparison/ illustrates performance metrics observed with all four of these drivers, with MySQL-python built both without and with the &amp;quot;greenify&amp;quot; system.&lt;br /&gt;
&lt;br /&gt;
A summary of the status of all four drivers is as follows.&lt;br /&gt;
&lt;br /&gt;
=== MySQL-Python ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/farcepest/MySQLdb1 MySQL-Python] is the most widely used Python driver.  However, it does not support Python 3, and it does not support async systems unless used within a thread pool.   While a system known as Greenify can potentially resolve the latter situation, the Python 3 limitation is still a deal-breaker - pull request with Python 3 support [https://github.com/farcepest/MySQLdb1/pull/62 was proposed] to MySQL-python in April 2014 and still not merged. &lt;br /&gt;
&lt;br /&gt;
=== mysqlclient ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/PyMySQL/mysqlclient-python mysqlclient] is a fork of MySQL-python. It add Python 3.3 support and merges some pull requests.&lt;br /&gt;
&lt;br /&gt;
=== OurSQL ===&lt;br /&gt;
&lt;br /&gt;
[https://pythonhosted.org/oursql/ OurSQL] is an alternative MySQL driver that is also written in C.  It features fast performance and uses a different execution model than MySQL-python, using prepared statements.    OurSQL does have a Python 3 port hosted on [https://launchpad.net/oursql/py3k/ launchpad] but is not integrated with OurSQL itself nor is it published on Pypi.     Because it is written in C, it does not offer compatibility with async systems including eventlet-style monkeypatching.&lt;br /&gt;
&lt;br /&gt;
Besides the shaky Python 3 support and lack of eventlet support, OurSQL is also not maintained, seeing its last master commit on [https://code.launchpad.net/~jerub/oursql/master 2012-06-05].&lt;br /&gt;
&lt;br /&gt;
=== MySQL-Connector-Python ===&lt;br /&gt;
&lt;br /&gt;
[https://pypi.python.org/pypi/mysql-connector-python MySQL-connector-Python] is a pure Python MySQL driver, and is now released under the auspices of the MySQL project itself, as owned by Oracle.  MySQL-connector-Python supports both Python 3 as well as eventlet monkeypatching, and is well maintained.  It is endorsed by Oracle as the official Oracle-supported driver for MySQL, so to that extent, it is in most ways Openstack's first choice in driver.  However, Oracle refuses to publish MySQL-connector-Python on Pypi, which is critical to the Openstack infrastructure.  Repeated attempts to communicate with Oracle in order to resolve this issue have not made any progress.    Therefore, for this one unfortunate reason, MySQL-connector-Python will not have a place in the Openstack ecosystem unless this issue is resolved.&lt;br /&gt;
&lt;br /&gt;
=== PyMySQL ===&lt;br /&gt;
&lt;br /&gt;
As this document is named &amp;quot;PyMySQL Evaluation&amp;quot;, one would expect that it has achieved our highest ratings for quality and appropriateness.   But as should now be apparent, at the moment PyMySQL is winning pretty much because all the alternatives are worse :).   &lt;br /&gt;
&lt;br /&gt;
PyMySQL meets all of Openstack's criterion for a driver: it is fully open source, hosted on Github, released on Pypi, is actively maintained, is written in pure Python so is eventlet-monkeypatch compatible, and is fully Python 3 compatible.&lt;br /&gt;
&lt;br /&gt;
===MySQL DB Drivers Comparison===&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Project !! PyPi hosted !! Eventlet friendly !! Python 3 compatibility !! Maturity and/or stability  !! Comment&lt;br /&gt;
|-&lt;br /&gt;
| MySQL-Python || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: orange;&amp;quot; | Partial || style=&amp;quot;background-color: red;&amp;quot; | No  || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || Can be monkeypatched by eventlet, but only to enable thread pooling&lt;br /&gt;
|-&lt;br /&gt;
| mysqlclient || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: orange;&amp;quot; | Partial || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes  || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || MySQL-python fork, so should be similar to it&lt;br /&gt;
|-&lt;br /&gt;
| OurSQL || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: red;&amp;quot; | No || style=&amp;quot;background-color: orange;&amp;quot; | Yes, but not Pypi hosted  || style=&amp;quot;background-color: orange;&amp;quot; | ??? || Has not seen commits/releases in two years&lt;br /&gt;
|-&lt;br /&gt;
| MySQL-Connector-Python || style=&amp;quot;background-color: red;&amp;quot; | No || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes  || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || The official Oracle-supported driver for MySQL&lt;br /&gt;
|-&lt;br /&gt;
| PyMySQL || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes  || style=&amp;quot;background-color: lightgreen;&amp;quot; | Yes || &lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== PyMySQL Overview ==&lt;br /&gt;
&lt;br /&gt;
TODO&lt;/div&gt;</summary>
		<author><name>Mike Bayer</name></author>	</entry>

	<entry>
		<id>https://wiki.openstack.org/w/index.php?title=PyMySQL_evaluation&amp;diff=71658</id>
		<title>PyMySQL evaluation</title>
		<link rel="alternate" type="text/html" href="https://wiki.openstack.org/w/index.php?title=PyMySQL_evaluation&amp;diff=71658"/>
				<updated>2015-01-12T23:52:39Z</updated>
		
		<summary type="html">&lt;p&gt;Mike Bayer: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= PyMySQL Evaluation =&lt;br /&gt;
&lt;br /&gt;
This page will capture issues related to Openstack moving to the [https://github.com/PyMySQL/PyMySQL/ PyMySQL driver] for MySQL/MariaDB dbapi access.&lt;br /&gt;
&lt;br /&gt;
== Rationale ==&lt;br /&gt;
&lt;br /&gt;
While the [https://github.com/farcepest/MySQLdb1 MySQL-Python] driver is a very mature and stable driver, it does not provide compatibility with either Python 3 or with [http://eventlet.net/doc/patching.html eventlet monkeypatching].  Openstack's usage of MySQL-Python, combined with the fact that concurrency is provided by eventlet, means that we currently have fully serialized database access within a single process, that is, only one database command occurs at a time within an Openstack Python process.&lt;br /&gt;
&lt;br /&gt;
== Drivers Under Consideration ==&lt;br /&gt;
&lt;br /&gt;
The two drivers that are known to provide eventlet-monkeypatch compatibility are MySQL-Connector and PyMySQL, as they are written in pure Python.   The two other well-known drivers for MySQL are MySQL-Python and OurSQL, both of which are written in C and offer no explicit support for eventlet or async.  They can reportedly be built to support gevent using a system called [https://github.com/CMGS/greenify Greenify], but the maturity and/or stabiltiy of this system is unknown.   &lt;br /&gt;
&lt;br /&gt;
A comparison at http://www.diamondtin.com/2014/sqlalchemy-gevent-mysql-python-drivers-comparison/ illustrates performance metrics observed with all four of these drivers, with MySQL-python built both without and with the &amp;quot;greenify&amp;quot; system.&lt;br /&gt;
&lt;br /&gt;
A summary of the status of all four drivers is as follows.&lt;br /&gt;
&lt;br /&gt;
=== MySQL-Python ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/farcepest/MySQLdb1 MySQL-Python] is the most widely used Python driver.  However, it does not support Python 3, and it does not support async systems unless used within a thread pool.   While a system known as Greenify can potentially resolve the latter situation, the Python 3 limitation is still a deal-breaker.&lt;br /&gt;
&lt;br /&gt;
=== OurSQL ===&lt;br /&gt;
&lt;br /&gt;
[https://pythonhosted.org/oursql/ OurSQL] is an alternative MySQL driver that is also written in C.  It features fast performance and uses a different execution model than MySQL-python, using prepared statements.    OurSQL does have a Python 3 port hosted on [https://launchpad.net/oursql/py3k/ launchpad] but is not integrated with OurSQL itself nor is it published on Pypi.     Because it is written in C, it does not offer compatibility with async systems including eventlet-style monkeypatching.&lt;br /&gt;
&lt;br /&gt;
Besides the shaky Python 3 support and lack of eventlet support, OurSQL is also not maintained, seeing its last master commit on [https://code.launchpad.net/~jerub/oursql/master 2012-06-05].&lt;br /&gt;
&lt;br /&gt;
=== MySQL-Connector-Python ===&lt;br /&gt;
&lt;br /&gt;
[https://pypi.python.org/pypi/mysql-connector-python MySQL-connector-Python] is a pure Python MySQL driver, and is now released under the auspices of the MySQL project itself, as owned by Oracle.  MySQL-connector-Python supports both Python 3 as well as eventlet monkeypatching, and is well maintained.  It is endorsed by Oracle as the official Oracle-supported driver for MySQL, so to that extent, it is in most ways Openstack's first choice in driver.  However, Oracle refuses to publish MySQL-connector-Python on Pypi, which is critical to the Openstack infrastructure.  Repeated attempts to communicate with Oracle in order to resolve this issue have not made any progress.    Therefore, for this one unfortunate reason, MySQL-connector-Python will not have a place in the Openstack ecosystem unless this issue is resolved.&lt;br /&gt;
&lt;br /&gt;
=== PyMySQL ===&lt;br /&gt;
&lt;br /&gt;
As this document is named &amp;quot;PyMySQL Evaluation&amp;quot;, one would expect that it has achieved our highest ratings for quality and appropriateness.   But as should now be apparent, at the moment PyMySQL is winning pretty much because all the alternatives are worse :).   &lt;br /&gt;
&lt;br /&gt;
PyMySQL meets all of Openstack's criterion for a driver: it is fully open source, hosted on Github, released on Pypi, is actively maintained, is written in pure Python so is eventlet-monkeypatch compatible, and is fully Python 3 compatible.&lt;br /&gt;
&lt;br /&gt;
== PyMySQL Overview ==&lt;br /&gt;
&lt;br /&gt;
 TODO&lt;/div&gt;</summary>
		<author><name>Mike Bayer</name></author>	</entry>

	<entry>
		<id>https://wiki.openstack.org/w/index.php?title=PyMySQL_evaluation&amp;diff=71656</id>
		<title>PyMySQL evaluation</title>
		<link rel="alternate" type="text/html" href="https://wiki.openstack.org/w/index.php?title=PyMySQL_evaluation&amp;diff=71656"/>
				<updated>2015-01-12T23:39:06Z</updated>
		
		<summary type="html">&lt;p&gt;Mike Bayer: Created page with &amp;quot;= PyMySQL Evaluation =  This page will capture issues related to Openstack moving to the [https://github.com/PyMySQL/PyMySQL/ PyMySQL driver] for MySQL/MariaDB dbapi access....&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= PyMySQL Evaluation =&lt;br /&gt;
&lt;br /&gt;
This page will capture issues related to Openstack moving to the [https://github.com/PyMySQL/PyMySQL/ PyMySQL driver] for MySQL/MariaDB dbapi access.&lt;br /&gt;
&lt;br /&gt;
== Rationale ==&lt;br /&gt;
&lt;br /&gt;
While the [https://github.com/farcepest/MySQLdb1 MySQL-Python] driver is a very mature and stable driver, it does not provide compatibility with either Python 3 or with [http://eventlet.net/doc/patching.html eventlet monkeypatching].  Openstack's usage of MySQL-Python, combined with the fact that concurrency is provided by eventlet, means that we currently have fully serialized database access within a single process, that is, only one database command occurs at a time within an Openstack Python process.&lt;br /&gt;
&lt;br /&gt;
== Drivers Under Consideration ==&lt;br /&gt;
&lt;br /&gt;
The two drivers that are known to provide eventlet-monkeypatch compatibility are MySQL-Connector and PyMySQL, as they are written in pure Python.   The two other well-known drivers for MySQL are MySQL-Python and OurSQL, both of which are written in C and offer no explicit support for eventlet or async.  They can reportedly be built to support gevent using a system called [https://github.com/CMGS/greenify Greenify], but the maturity and/or stabiltiy of this system is unknown.   &lt;br /&gt;
&lt;br /&gt;
A comparison at http://www.diamondtin.com/2014/sqlalchemy-gevent-mysql-python-drivers-comparison/ illustrates performance metrics observed with all four of these drivers, with the C-based libraries built both without and with the &amp;quot;greenify&amp;quot; system.&lt;/div&gt;</summary>
		<author><name>Mike Bayer</name></author>	</entry>

	<entry>
		<id>https://wiki.openstack.org/w/index.php?title=OpenStack_and_SQLAlchemy&amp;diff=64802</id>
		<title>OpenStack and SQLAlchemy</title>
		<link rel="alternate" type="text/html" href="https://wiki.openstack.org/w/index.php?title=OpenStack_and_SQLAlchemy&amp;diff=64802"/>
				<updated>2014-10-10T18:28:02Z</updated>
		
		<summary type="html">&lt;p&gt;Mike Bayer: /* Transaction Demarcation */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= OpenStack and SQLAlchemy =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
This page describes the ongoing efforts of Mike Bayer, creator/maintainer of&lt;br /&gt;
SQLAlchemy, Alembic Migrations, and Dogpile caching, to spearhead what is&lt;br /&gt;
essentially an &amp;quot;OpenStack SQLAlchemy 2.0&amp;quot; effort - as I am now full-time&lt;br /&gt;
employed by an OpenStack vendor (Red Hat), I've been hired to produce a strong&lt;br /&gt;
and sustained pass through OpenStack's relational database integration,&lt;br /&gt;
improving areas that have been problematic and establishing a very strong&lt;br /&gt;
set of practices that are specific to OpenStack's needs.&lt;br /&gt;
&lt;br /&gt;
Within this effort, I'd like to stress that from my POV, everything is&lt;br /&gt;
on the table, including new improvements / fixes / features to&lt;br /&gt;
SQLAlchemy and Alembic directly, migration of projects for which&lt;br /&gt;
relational databases aren't suitable to non-relational systems,&lt;br /&gt;
improvement of relational schemas on the OpenStack side, and of course&lt;br /&gt;
improvement of exising relational patterns within OpenStack&lt;br /&gt;
applications, for which oslo.db will serve as the foundation.&lt;br /&gt;
&lt;br /&gt;
As of June 30, 2014, I've been on the job for about a month, so below&lt;br /&gt;
is an outline of most of what I've come across and my thoughts going forward.&lt;br /&gt;
&lt;br /&gt;
My goal here is to share with the community what I've seen and done so far,&lt;br /&gt;
so that interested parties can chime in with further advice and information,&lt;br /&gt;
and most importantly to help steer me towards the initial blueprints that&lt;br /&gt;
I should be starting on, that is, which features are most likely to be&lt;br /&gt;
accepted by the community as well as which are the top priority.  Not to mention, if you're looking&lt;br /&gt;
for something to do, you can help me implement some of these things!&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== OpenStack presence within SQLAlchemy's CI environment (a.k.a. &amp;quot;The Version that shall not be Named&amp;quot;) ==&lt;br /&gt;
&lt;br /&gt;
On Monday, June 23, I got up in the AM with two goals: get a release of SQLAlchemy&lt;br /&gt;
out the door, as it had been several months since the last point release, and then&lt;br /&gt;
do some work learning how Nova's unit tests run and function.&lt;br /&gt;
&lt;br /&gt;
Release 0.9.5 of SQLAlchemy was not particularly different than any of the other&lt;br /&gt;
hundred or so point releases I've put out over nine years, it was a little more overdue&lt;br /&gt;
than some, and out it went.  Later that day, as I was learning to run Nova's test suite, I&lt;br /&gt;
was hitting some failures within the FixedIp/FloatingIp tests for which I couldn't&lt;br /&gt;
find the cause, assuming that I just wasn't setting up my environment correctly,&lt;br /&gt;
as Nova's tests are passing for all the Jenkins servers the vast majority of the time.&lt;br /&gt;
&lt;br /&gt;
As we learned within about 30 minutes of my hitting these problems, it&lt;br /&gt;
turns out 0.9.5 had a bad fix in it; one that didn't fail any of&lt;br /&gt;
SQLAlchemy's 5000 tests but hit a use case within Nova that was not&lt;br /&gt;
very unusual; not the kind of use I usually do myself but nonetheless&lt;br /&gt;
something that was pretty common.   IRC began  jumping that builds&lt;br /&gt;
were failing everywhere and it was due to the 0.9.5 release!   The&lt;br /&gt;
bizarre coiincidence of this was that none of the thirty-odd point&lt;br /&gt;
releases of SQLAlchemy released under OpenStack's lifetime have ever&lt;br /&gt;
really had this effect, only the major jumps saw any issues.&lt;br /&gt;
&lt;br /&gt;
Luckily, as I had just learned 30 minutes earlier how to run Nova's tests I was&lt;br /&gt;
able to drill down to exactly what was going on and SQLAlchemy 0.9.6 was out&lt;br /&gt;
within an hour, but we shall now forever see in requirements.txt, the Version&lt;br /&gt;
that shall Not Be Named (except in requirements.txt):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
SQLAlchemy&amp;gt;=0.7.8,!=0.9.5,&amp;lt;=0.9.99&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In response to this I have now&lt;br /&gt;
[http://jenkins.sqlalchemy.org/job/openstack_sqla_master/ added openstack builds to our Jenkins CI system],&lt;br /&gt;
so far running tests for oslo.db and the SQLAlchemy tests within Nova; I spent&lt;br /&gt;
a couple of days figuring out how to get everything to build and run against&lt;br /&gt;
specific versions of SQLAlchemy and Alembic, even versions above the 0.9.99 in&lt;br /&gt;
all the requirements files as we are set for 1.0 hopefully within the next year.&lt;br /&gt;
I will be adding in as many other OpenStack projects as I can here so that we will no&lt;br /&gt;
longer have a SQLAlchemy release that hasn't been run against a battery of&lt;br /&gt;
basic OpenStack tests.&lt;br /&gt;
&lt;br /&gt;
== ORM Woes ==&lt;br /&gt;
&lt;br /&gt;
After chatting with a decent amount of folks for a month, I can break up&lt;br /&gt;
the take on OpenStack / SQLAlchemy ORM into three camps:&lt;br /&gt;
&lt;br /&gt;
1. We love SQLAlchemy ORM, we know that we can improve how we use it and we're looking forward to getting there (oslo.db, Keystone)&lt;br /&gt;
&lt;br /&gt;
2. Our app uses a relational database but the SQLAlchemy ORM slows us down, we already have our own &amp;quot;orm-like&amp;quot; system (e.g. APIs that shuttle rows into &amp;quot;values&amp;quot; dictionaries, nova.objects, etc.) so we want to ultimately get rid of the ORM part, and probably continue using SQLAlchemy Core to some degree (some Nova developers I've spoken to).&lt;br /&gt;
&lt;br /&gt;
3. Our application has a SQLAlchemy backend that almost nobody uses in production, most users use MongoDB, and the SQLAlchemy backend is only there because MongoDB is AGPL (Ceilometer, Marconi).  We'd like to get a better NoSQL backend without Mongo's licensing issue and do away with relational storage entirely ([http://lists.openstack.org/pipermail/openstack-dev/2014-June/038778.html Marconi]).&lt;br /&gt;
&lt;br /&gt;
Here's my observations / thoughts about these three camps:&lt;br /&gt;
&lt;br /&gt;
1. Great!  Read on, I have some things we can start with.&lt;br /&gt;
&lt;br /&gt;
2. I agree that a lot of the patterns that I've seen in Nova in particular are not well-suited to the approach used by SQLAlchemy's ORM, however I have observed that the applications still rely a lot on very sophisticated and mature features provided by SQLAlchemy (namely &amp;lt;code&amp;gt;relationship()&amp;lt;/code&amp;gt; and it's integration with eager loading and the unit of work features), and throwing it away entirely will incur a lot of reinvention, not just of the features that were lost but also of a totally new crop of stability and performance issues to solve.&lt;br /&gt;
&lt;br /&gt;
To that degree, I'd like to propose a twist to the &amp;quot;we want to write our own ORM&amp;quot; idea (edit: OK nova.objects is really an ORM-like thing on top of their API on top of relational DB, but that's an issue for another day) and say, &amp;quot;let's write our own ORM that's based on the SQLAlchemy ORM primitives&amp;quot;, e.g. a hybrid approach where existing ORM logic that is fine can stay that way, ORM logic that has performance or behavioral issues can be reorganized using systems, some of them new, some of them just underutilized, that bypass unneeded SQLAlchemy features on a case-by-case basis.&lt;br /&gt;
&lt;br /&gt;
As a secondary issue, some devs I've spoken to have referred to the nature&lt;br /&gt;
of OpenStack API-based applications being a part of the problem.&lt;br /&gt;
This refers to the fact that OpenStack apps like Nova&lt;br /&gt;
and Neutron expose fine-grained APIs which end up&lt;br /&gt;
breaking out composed use cases into dozens or hundreds of small API&lt;br /&gt;
operations, each of which invokes within it's own database transaction and&lt;br /&gt;
ORM session state.  This makes it impossible, without introducing some elaborate&lt;br /&gt;
offline state management system, for a larger operation to be&lt;br /&gt;
performed so that larger groups of objects can be loaded and manipulated&lt;br /&gt;
at once, greatly decreasing the number of database round trips present.&lt;br /&gt;
No matter what ORM you use or don't use, the pattern of many small round trips&lt;br /&gt;
is difficult to scale on relational databases.&lt;br /&gt;
&lt;br /&gt;
There are under way projects which aim to apply detailed profiling&lt;br /&gt;
to OpenStack applications so that a breakdown of performance issues due&lt;br /&gt;
to API use and/or database&lt;br /&gt;
access might be plainly reasoned about; see [https://github.com/stackforge/rally Rally]&lt;br /&gt;
and [https://github.com/stackforge/osprofiler OS Profiler] for examples.&lt;br /&gt;
&lt;br /&gt;
On my end, I have no intention of focusing on this second issue for the time&lt;br /&gt;
being, I'm going to stick with leaving OS applications as much as they are right now as possible,&lt;br /&gt;
applying iterative changes to database access code which I hope can optimize&lt;br /&gt;
to a significant degree within the current usage patterns.&lt;br /&gt;
&lt;br /&gt;
3. Projects with alternate MongoDB / MySQL backends (since after all, MySQL is the database here being rejected, SQLAlchemy is just a Python API) should probably do what they're doing and go to an all key/value model.  It is not at all fun to maintain the same datamodel and API against both a KV store as well as relational tables.  I had a detailed look at Ceilometer with its team members early on, and while I'm pretty confident that if it were desireable, I could get it's relational backend to compete with the Mongo backend performance-wise, there's no reason to get into this if the Mongo licensing issue can be solved alone.&lt;br /&gt;
&lt;br /&gt;
== ORM Quick Wins Proof of Concept ==&lt;br /&gt;
&lt;br /&gt;
I've built up a proof of concept at [https://github.com/zzzeek/nova_poc nova_poc] which seeks to demonstrate&lt;br /&gt;
the range of database and ORM performance that can be achieved within&lt;br /&gt;
one particular method of the Nova API.   Naturally, I picked an example that&lt;br /&gt;
is very juicy in this regard; it has a very easily fixable issue with the&lt;br /&gt;
kind of query it emits that has an enormous 10x impact on its performance&lt;br /&gt;
(which I feel is worth illustrating just for how dramatic it is),&lt;br /&gt;
and it then illustrates some new SQLAlchemy extensions that more or less&lt;br /&gt;
may be going into SQLAlchemy directly, which allow the remaining operation&lt;br /&gt;
to complete in less than half the time.  These extensions can be ported to&lt;br /&gt;
oslo.db to work against all supported SQLAlchemy versions prior to the official&lt;br /&gt;
release of these features.&lt;br /&gt;
&lt;br /&gt;
To summarize, we're looking at the API method &amp;lt;code&amp;gt;floating_ip_update()&amp;lt;/code&amp;gt;, and&lt;br /&gt;
I run a series of profiles against a database table 10K rows (e.g. &amp;lt;code&amp;gt;FloatingIp&amp;lt;/code&amp;gt;&lt;br /&gt;
objects), where each &amp;lt;code&amp;gt;FloatingIp&amp;lt;/code&amp;gt; refers to a single &amp;lt;code&amp;gt;FixedIp&amp;lt;/code&amp;gt; that then&lt;br /&gt;
refers to an &amp;lt;code&amp;gt;Instance&amp;lt;/code&amp;gt;.   The profiling focuses on the nature of this&lt;br /&gt;
method as an API feature all it's own, and simulates the case where the API&lt;br /&gt;
method is called thousands of times, not unlike other API methods that seek&lt;br /&gt;
to add some small amount of data each time they are called.&lt;br /&gt;
&lt;br /&gt;
=== Eager load and Column load tuning ===&lt;br /&gt;
&lt;br /&gt;
Turning off an eagerload in the &amp;lt;code&amp;gt;_floating_ip_get_by_address()&amp;lt;/code&amp;gt; function&lt;br /&gt;
is the most dramatic win; while removing this method seems like a reasonable&lt;br /&gt;
fix to remove some unneeded overhead, in profiling it shows itself to&lt;br /&gt;
be enormous:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Running scenario default&lt;br /&gt;
Scenario default, total calls for 1000 operations: 24543047&lt;br /&gt;
Scenario default, total time for 10000 operations: 222&lt;br /&gt;
&lt;br /&gt;
Running scenario default_optimized&lt;br /&gt;
Scenario default_optimized, total calls for 1000 operations: 1881626&lt;br /&gt;
Scenario default_optimized, total time for 10000 operations: 25&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
While not illustrated in the POC code itself, talk with various OpenStack&lt;br /&gt;
devs has revealed that we could probably do a lot more individual column&lt;br /&gt;
fetching as well, that is instead of:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
session.query(FixedIp).all()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We say:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
session.query(FixedIp.address, FixedIp.host, FixedIp.reserved).all()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
By loading indivdual columns, we get read-only objects that are not identity&lt;br /&gt;
managed, bypassing a huge amount of work that is done on instances both&lt;br /&gt;
in instantiating them, populating relationships and doing bookkeeping.&lt;br /&gt;
Any API feature that only returns a simple read-only object or a &amp;quot;values&amp;quot;&lt;br /&gt;
dictionary that does not rely upon relationship loading should be using&lt;br /&gt;
this pattern, or similar, by using a&lt;br /&gt;
[http://docs.sqlalchemy.org/en/rel_0_9/orm/mapper_config.html#column-bundles Column Bundle].&lt;br /&gt;
I will be exploring adding the following feature to oslo.db:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
session.query(FixedIp.bundle).all()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The above will return a fast-loading, read-only Bundle object that otherwise&lt;br /&gt;
expresses the full interface of &amp;lt;code&amp;gt;FixedIp&amp;lt;/code&amp;gt;.   Relationships might be tricky&lt;br /&gt;
here but I can at least get them on board as regular lazy loads (just like&lt;br /&gt;
any other ORM would do anyway).&lt;br /&gt;
&lt;br /&gt;
=== Fast Object Save ===&lt;br /&gt;
&lt;br /&gt;
We explore replacing the unit of work &amp;lt;code&amp;gt;flush()&amp;lt;/code&amp;gt; call used by &amp;lt;code&amp;gt;object.save()&amp;lt;/code&amp;gt; within oslo.db-style applications with a more direct &amp;quot;flush single object&amp;quot; call; this grants us only a 12% savings so far, though it is very simple to use:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Running scenario default_optimized&lt;br /&gt;
Scenario default_optimized, total calls for 1000 operations: 1881626&lt;br /&gt;
Scenario default_optimized, total time for 10000 operations: 25&lt;br /&gt;
&lt;br /&gt;
Running scenario fast_save&lt;br /&gt;
Scenario fast_save, total calls for 1000 operations: 1685221&lt;br /&gt;
Scenario fast_save, total time for 10000 operations: 22&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Update: see https://bitbucket.org/zzzeek/sqlalchemy/issue/3100/sessionflush_object for an updated version of this feature that includes a &amp;quot;bulk save&amp;quot; operation.   Inserting 10000 records runs in half the time/function calls with this simple proof of concept.&lt;br /&gt;
&lt;br /&gt;
=== Baked Queries ===&lt;br /&gt;
&lt;br /&gt;
Something that has been in the works for a long time and has&lt;br /&gt;
recently    seen lots of work in the past months is the &amp;quot;baked query&amp;quot;&lt;br /&gt;
feature; this    pattern is ideal for OpenStack's &amp;quot;many short queries&amp;quot;&lt;br /&gt;
pattern, and allows    caching of the generation of SQL.  Recent&lt;br /&gt;
versions of this pattern have    gotten very slick, and can cache&lt;br /&gt;
virtually everything that happens Python-wise    from the construction&lt;br /&gt;
of the &amp;lt;code&amp;gt;Query&amp;lt;/code&amp;gt; object, to calling all the methods    on the&lt;br /&gt;
query, to the query-objects construction of a Core SQL statement,&lt;br /&gt;
to the compilation of that statement as a string - all of these steps&lt;br /&gt;
are removed from the call-graph after the first such call.  In&lt;br /&gt;
SQLAlchemy 1.0    I've also thrown in the construction of column&lt;br /&gt;
metadata from the result set    too.   The pattern involves a bit more&lt;br /&gt;
verbosity to that of constructing a    query, where here I've built&lt;br /&gt;
off of some of the ideas of the    Pony ORM to use Python function&lt;br /&gt;
information as the source of a cache key.    A query such as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
result = model_query(&lt;br /&gt;
            context, models.FloatingIp, session=session).\&lt;br /&gt;
            filter_by(address=address)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
would be expressed in &amp;quot;baked&amp;quot; form as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# note model_query is using the &amp;quot;baked&amp;quot; process internally as well&lt;br /&gt;
result = model_query(context, models.FloatingIp, session=session)&lt;br /&gt;
&lt;br /&gt;
result.bake(lambda query:&lt;br /&gt;
    query.filter_by(&lt;br /&gt;
        address=bindparam('address'))).params(address=address)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the above form, everything within each lambda is invoked only once,&lt;br /&gt;
the result of which becomes part of a cached value.&lt;br /&gt;
&lt;br /&gt;
For this slight increase in verbosity, we get an improvement like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Running scenario default_optimized&lt;br /&gt;
Scenario default_optimized, total calls for 1000 operations: 1881626&lt;br /&gt;
Scenario default_optimized, total time for 10000 operations: 25&lt;br /&gt;
&lt;br /&gt;
Running scenario baked&lt;br /&gt;
Scenario baked, total calls for 1000 operations: 1052935&lt;br /&gt;
Scenario baked, total time for 10000 operations: 16&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
That is, around a 40% improvement.&lt;br /&gt;
&lt;br /&gt;
Putting together both &amp;quot;fast save&amp;quot; plus &amp;quot;baked&amp;quot; we get down to a full 50%&lt;br /&gt;
improvement vs. the plain optimized version:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Running scenario fast_save_plus_baked&lt;br /&gt;
Scenario fast_save_plus_baked, total calls for 1000 operations: 856035&lt;br /&gt;
Scenario fast_save_plus_baked, total time for 10000 operations: 13&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
13 seconds for 10K operations vs. 222 seconds in the current code, I hope&lt;br /&gt;
I've established that there is hope!&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Migrations ==&lt;br /&gt;
&lt;br /&gt;
Migrations are a huge deal.  Here's where it looks like this is going.&lt;br /&gt;
&lt;br /&gt;
Alembic - &amp;quot;Alembic is the defacto standard going forward&amp;quot; - ultimately&lt;br /&gt;
we want to get everything SQLAlchemy-Migrate onto Alembic.  And in talking&lt;br /&gt;
with Nova devs, they really like that they can test their migrations against&lt;br /&gt;
SQLite.  So from my POV, SQLite migrations on Alembic, we're doing that!&lt;br /&gt;
Unless folks think otherwise.&lt;br /&gt;
&lt;br /&gt;
=== SQLite Support ===&lt;br /&gt;
&lt;br /&gt;
SQLite has extremely limited support for ALTER TABLE, essentially only being able&lt;br /&gt;
to add new columns, and that's about it.  In order to provide the full suite&lt;br /&gt;
of ALTER operations that all other databases provide, tools such as&lt;br /&gt;
SQLAlchemy-Migrate will create a copy of the target table with changes applied,&lt;br /&gt;
copy data from old table to new, then drop the old table and rename the new one.&lt;br /&gt;
I've always been peeved at SQLite's insistence on never providing some better&lt;br /&gt;
pathway to ALTER built in.  But it has been on the Alembic roadmap to add SQLite migrations&lt;br /&gt;
in a style similar to that of Migrate for a long time, emulating this same&lt;br /&gt;
approach in some way.  Alembic will have this!&lt;br /&gt;
&lt;br /&gt;
However in talking with some folks, it appears like some people might actually&lt;br /&gt;
like this &amp;quot;make a new table and switch it&amp;quot; approach for other databases too,&lt;br /&gt;
as a way to work around locking of tables.  I don't know that I agree with this&lt;br /&gt;
long term, the &amp;quot;ALTER locks the database!&amp;quot; issue in my experience has always&lt;br /&gt;
been kind of a MySQL problem alone, but it can be present for other backends&lt;br /&gt;
as well.&lt;br /&gt;
&lt;br /&gt;
So the API I'm looking towards here is going to try to solve a few problems;&lt;br /&gt;
one is that, copying the whole table to a new one each time for *every single&lt;br /&gt;
column* is crazy, and also that the technique we use on SQLite might be nice&lt;br /&gt;
for other databases too, so here's the API:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
with op.batch_alter(&amp;quot;mytable&amp;quot;, recreate=True) as batch_op:&lt;br /&gt;
    batch_op.drop_column(&amp;quot;some_column&amp;quot;)&lt;br /&gt;
    batch_op.alter_column(&amp;quot;q&amp;quot;, type=Integer)&lt;br /&gt;
    batch_op.add_column(Column('y', String(50), nullable=False))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
That is, we assign a whole series of column operations under the umbrella of&lt;br /&gt;
one table, and they are created at once.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;batch_alter()&amp;lt;/code&amp;gt; call can even be conditional, dropping into traditional&lt;br /&gt;
migration behavior for all backends except SQLite and whatever else:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
with op.batch_alter(&amp;quot;mytable&amp;quot;, recreate_on=('sqlite',)) as batch_op:&lt;br /&gt;
    batch_op.drop_column(&amp;quot;some_column&amp;quot;)&lt;br /&gt;
    batch_op.alter_column(&amp;quot;q&amp;quot;, type=Integer)&lt;br /&gt;
    batch_op.add_column(Column('y', String(50), nullable=False))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
SQlite-compatible alterations is [https://bitbucket.org/zzzeek/alembic/issue/21/column-renames-not-supported-on-sqlite issue #21].&lt;br /&gt;
&lt;br /&gt;
=== Finishing up Autogenerate ===&lt;br /&gt;
&lt;br /&gt;
I started writing Alembic long before SQLAlchemy-Migrate had autogenerate.  But by the time&lt;br /&gt;
I got Alembic released and later autogenerate working, Migrate already had it.&lt;br /&gt;
Here's what we need to finish up in autogenerate:&lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;code&amp;gt;ForeignKeyConstraint&amp;lt;/code&amp;gt; change detection - this is an entirely straightforward feature add and has long been on the todo list.&lt;br /&gt;
&lt;br /&gt;
2. MySQL implicit indexes - this is actually done; recent versions of Alembic can navigate around MySQLs goofy production of indexes automatically on foreign key columns, and not accidentally spit them out in autogenerates. It was seriously tough to get index autogen mostly working on all backends, so new issues will continue to be fixed as they are reported.&lt;br /&gt;
&lt;br /&gt;
3. Type comparison - Alembic balks on type comparison by default, because ultimately SQLAlchemy should add comparision features to its type objects natively. However, on Alembic backends like that of MySQL, we can add explicit rules for the very common and annoying ones, namely the detection of MySQL's TINYINT as a SQLAlchemy Boolean.  Alembic already allows user-specified rules in this regard, so they can be part of oslo.db in any case, however Alembic can also add this rule and others in directly.&lt;br /&gt;
&lt;br /&gt;
4. Table order - Alembic's autogenerate should be spitting out table creates and drops in the order of foreign key dependency, though we don't have good test coverage for this yet and it might not be working.  We'll fix that, no biggie!  Alternatively, Alembic could include a mode of operation that includes all the ForeignKeyConstraint objects after all the tables. Input on how we'd like to see this work would be welcome.&lt;br /&gt;
&lt;br /&gt;
=== New Migration Features ===&lt;br /&gt;
&lt;br /&gt;
There's a lot of features I'd like to add to Alembic, and if OpenStack has&lt;br /&gt;
a need for them, that would justify the effort:&lt;br /&gt;
&lt;br /&gt;
1. Full branch support - [https://bitbucket.org/zzzeek/alembic/issue/167/multiple-heads-branch-resolution-support issue 167 multiple heads / branch resolution support] has a detailed architecture laid out for this feature.  Lots of people are looking for it. This would turn Alembic's current &amp;quot;linked list&amp;quot; version model into a full directed acyclic graph (DAG).   Any particular migration can be dependent on any other group of migrations, or none at all; individual branches can be maintained and upgraded along their path, or automatically merged.&lt;br /&gt;
&lt;br /&gt;
2. [https://bitbucket.org/zzzeek/alembic/issue/124/multiple-versions-directories Multiple version directories] - This would allow migration files to be present in more than one place for a single migrations environment.  Currently, you can get this approach by using multiple base directories, but that requires separate &amp;lt;code&amp;gt;env.py&amp;lt;/code&amp;gt; files for each. With this issue, cross-dependent migration files can live in multiple places, working nicely with the multiple heads support of #167.&lt;br /&gt;
&lt;br /&gt;
== Testing ==&lt;br /&gt;
&lt;br /&gt;
I spent quite a bit looking into testing.  The main thing we can see&lt;br /&gt;
in testing is that there may be a desire for &amp;quot;transactional testing&amp;quot;;&lt;br /&gt;
run tests within a fixed schema, that is the schema    is created&lt;br /&gt;
once, many tests are run, each one rolling back their work, the&lt;br /&gt;
schema is only dropped once all tests for that schema are complete.&lt;br /&gt;
&lt;br /&gt;
Transactional testing is described in the SQLAlchemy docs and in some of&lt;br /&gt;
my talks; for OpenStack, we need the version that supports graceful handling&lt;br /&gt;
of tests that actually do a rollback.  This is doable and the&lt;br /&gt;
recipe for that is currently [https://gist.github.com/zzzeek/8443477 a github gist].&lt;br /&gt;
&lt;br /&gt;
In order to accommodate parallelized runs, the system will create N&lt;br /&gt;
temporary schemas for N concurrent runs.&lt;br /&gt;
&lt;br /&gt;
I really want to get into this feature but as I talk to people, I'm&lt;br /&gt;
getting mixed signals on how important this feature is.   So far I've&lt;br /&gt;
only worked with unit tests, which seem to be entirely against SQLite&lt;br /&gt;
in-memory databases, and I haven't gotten my head around to what&lt;br /&gt;
extent other databases besides SQLite might be desireable in unit&lt;br /&gt;
tests, nor have I gotten to fully understand how integration tests&lt;br /&gt;
run, so input is welcome here.&lt;br /&gt;
&lt;br /&gt;
== Improvement of ORM Patterns ==&lt;br /&gt;
&lt;br /&gt;
Looking around oslo.db and other apps, I want to improve upon&lt;br /&gt;
some kinds of verbosity I see, which make the code more repetetive and inconsistent, welds together application-level and business level concepts (e.g. transaction control and exception reporting melded with data manipulation) and makes it harder to reason about how the application interacts with sessions, as there are a variety of ad-hoc patterns present.&lt;br /&gt;
&lt;br /&gt;
=== Connectivity Unification ===&lt;br /&gt;
&lt;br /&gt;
If there's one thing that bugs me, it's when a large system has five different ways that it gets around to connecting to a database.  Oslo.db has a single point of &amp;lt;code&amp;gt;create_engine()&amp;lt;/code&amp;gt; call, and that's great, but it isn't used everywhere.   Particularly, we need to get projects that use Alembic like Neutron to use a consistent connectivity approach in their env.py files, and additionally test suites need to be able to run on any DB or DBs you want to point them at.   There's a lot of hardcoding / multiple code path stuff going on and I want to get in there and try to propose a system that does the whole thing just one way, and in a very flexible and environmentally-open way.&lt;br /&gt;
&lt;br /&gt;
* https://bugs.launchpad.net/oslo/+bug/1339206 - oslo.db test suite is too rigidly hardcoded to a small number of potential database backends&lt;br /&gt;
&lt;br /&gt;
=== Transaction Demarcation ===&lt;br /&gt;
&lt;br /&gt;
For the oft-repeated &amp;quot;get_session(); with session.begin(subtransactions=True)&amp;quot; phrase and other variants, I'd like to move that into something more abstracted:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def my_api_thing(x):&lt;br /&gt;
    with transactional() as session:&lt;br /&gt;
    	# do stuff&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I want context managers and/or decorators that are oslo.db specific to replace any kind of containing code that deals with transactions or sessions. That way the containinment logic can be maintained in once place and configured separately from the business logic contained within.  Projects like Keystone are already using a form more like this; it would be nice to nail down a consistent pattern and get it into oslo.db for everyone to use.&lt;br /&gt;
&lt;br /&gt;
* https://review.openstack.org/#/c/125181/ - Make EngineFacade a Facade!&lt;br /&gt;
&lt;br /&gt;
=== MySQLdb + eventlet = sad ===&lt;br /&gt;
&lt;br /&gt;
This is the basic thing that happens with these two actors.   We're in an eventlet &amp;quot;thread&amp;quot;.   The code uses MySQLdb to do something ordinary that locks a row in a table - this can happen with a SELECT FOR UPDATE but it really can happen just on any old UPDATE too depending on isolation level (see https://review.openstack.org/#/c/104111/).   If the eventlet &amp;quot;thread&amp;quot; then does *anything* that causes it to defer out, another eventlet thread might run, and if that thread tries to work with MySQLdb and lock the same row, you're done, it's a deadlock.   The simple reason is, MySQLdb doesn't integrate with eventlet, so a simple thing like blocking on a transactional lock, it doesn't defer.   MySQL times out on these so we're getting exceptions instead of permanent freezes, but IMHO this is a really silly problem.  Eventlet and MySQLdb do not work together, we're done.   MySQLdb 1000% either has to go, or be fixed, even if that means forking it.    A good blog post that illustrates both scenarios is [http://www.diamondtin.com/2014/sqlalchemy-gevent-mysql-python-drivers-comparison/ here] - in this post, the author has used &amp;quot;greenify&amp;quot; to patch MySQLdb's blocking, but without this change (which I'm told isn't compatible with eventlet?), MySQLdb is a nonstarter.    Because it is in pure C, it is faster than MySQL-connector when it's been patched, but if it hasn't, it's dog slow.  MySQL-connector IMHO is probably the driver we should get behind [http://dev.mysql.com/doc/connector-python/en/index.html because it's the official MySQL driver]. The various workarounds and hacks (which I guess is why this is under &amp;quot;ORM patterns&amp;quot;) I've seen proposed are all technically viable but IMHO totally unnecessary.   OpenStack should be serious about this, it's a totally solvable problem, let's just solve it.&lt;br /&gt;
&lt;br /&gt;
=== Exception Rewriting ===&lt;br /&gt;
&lt;br /&gt;
Exception rewriting - I notice that the oslo.db pattern includes that DBAPI level exceptions are re-thrown as OpenStack specific;  I'd like to move this into handling using SQLAlchemy events.  The [http://docs.sqlalchemy.org/en/rel_0_9/core/events.html#sqlalchemy.events.ConnectionEvents.dbapi_error dbapi_error] event goes all the way back to 0.7, and [https://bitbucket.org/zzzeek/sqlalchemy/issue/3076/support-reraise-of-custom-exceptions ticket 3076] will allow it to be used to reraise with a new exception.  For older SQLAlchemy versions, I can create a wrapper that will allow it to be used in this way safely for older versions, despite the warnings stated on that document.&lt;br /&gt;
&lt;br /&gt;
* blueprint: https://review.openstack.org/#/c/105306/&lt;br /&gt;
&lt;br /&gt;
=== Deleted Object Selecting ===&lt;br /&gt;
&lt;br /&gt;
There's a need to be able to run queries that automatically de-select for certain rows, particularly rows that have a &amp;quot;deleted&amp;quot; flag set, or that otherwise include various identifiers that are given for a context.  Right now oslo.db has a function `model_query()` for this purpose.   I'd like to streamline `model_query()` and allow for a more flexible pattern, where instead of plainly adding WHERE clauses to a SELECT against a primary table, we can use rules to produce a SQL transformation that will apply the desired criteria to all &amp;quot;FROM&amp;quot; clauses at the point at which they are named, even within a more complex, composed query.   This will allow `model_query()` to be more flexible and ultimately to allow its behavior to be more transparently integrated into the app.&lt;br /&gt;
&lt;br /&gt;
=== Pessimistic Locking - SELECT FOR UPDATE ===&lt;br /&gt;
&lt;br /&gt;
This issue has been coming up for some projects, that they make sometimes great use of SQLAlchemy's &amp;lt;code&amp;gt;with_lockmode()&amp;lt;/code&amp;gt; method, which ultimately emits SELECT..FOR UPDATE, so that selected rows are aggressively locked within a transaction.   The issue is that backends like Galera don't support FOR UPDATE, meaning the locking that is desired is not actually provided.&lt;br /&gt;
&lt;br /&gt;
While I'm not sure that there's a generalized solution to this, in some cases I've been helping developers move to more of an &amp;quot;optimistic&amp;quot; approach, where a particular UPDATE statement targets some kind of timestamp or value counter in the row, then counts the number of rows matched.  If the number of rows matched is not what's expected, it is assumed that the UPDATE failed to match the row, and the operation can either try again or can bail out.   This approach might not be suitable in all cases.  In cases where a large stream of data is affecting a relatively small set of rows, it can perhaps be feasible to serialize those updates by sending them all to a queue, so that they are invoked in isolation without the need for up-front locking.   For very high throughput situations, queues can be constructed to use multiple, concurrent UPDATE streams separated via hashing, or can batch together updates for particular rows over a period of time and emit fewer UPDATE statements than if each source of data did it individually.&lt;br /&gt;
&lt;br /&gt;
=== Encouraging Bundle / Baked Query Use ===&lt;br /&gt;
&lt;br /&gt;
Encouraging Bundle use - as stated earlier, I want to look into bringing the very efficient [http://docs.sqlalchemy.org/en/rel_0_9/orm/mapper_config.html#column-bundles Column Bundle] pattern into the fold for mainstream use, not to mention the baked query pattern illustrated previously.&lt;br /&gt;
&lt;br /&gt;
=== Transparent Retries of Transactions ===&lt;br /&gt;
&lt;br /&gt;
I have ready to go an [https://bitbucket.org/zzzeek/sqlalchemy/issue/3104/transaction-replay-extension already in production extension for transaction replaying], developed for a client with the agreement that the extension is open source.  This extension installs as an event handler using a new series of hooks that were added in 0.9, or alternatively using a series of compatibility hooks that were backported to 0.7 and 0.8.   This extension allows logging of SQL statements as they proceed within a transaction, and allows them to be transparently replayed when a database disconnect error occurs. This means that SQLAlchemy's usual behavior of raising an exception and forcing you to re-try explicitly can be hidden so that it occurs implicitly, without any interruption or special error handling required in application code.  The extension includes a lot of configuration options, such as what statements should be replayed and which shouldn't, as well as what exceptions should be handled this way.   Transaction replaying is an elaborate feature which might introduce lots of new edge cases, namely that the state of the database may have changed since the last transaction failed, so to that extent I'm a little wary of this feature myself.  However I have seen lots of messages alluding to this kind of thing.    The mechanics to pull this into SQLAlchemy as a transparent feature are ready to go, if this is a feature people are looking for (I'm not sure if people are looking for this).&lt;br /&gt;
&lt;br /&gt;
== Contributors ==&lt;br /&gt;
&lt;br /&gt;
I would be remiss if I didn't mention that SQLAlchemy, Alembic, Dogpile and everything else I do are collaborative projects, and while SQLAlchemy in particular might present a daunting codebase, we would love to get new collaborators on board!   Any of the features above are fair game and I can walk someone through the steps to be taken, and additionally everything we add features for always needs lots of tests and review, if you're looking for a way to get into the project I can bring you right in, there's lots of (often very tedious and boring) work to be done.   I'm on all the IRC channels now as zzzeek so ping away.&lt;br /&gt;
&lt;br /&gt;
== Coffee and Cake ==&lt;br /&gt;
&lt;br /&gt;
So! I hope you've enjoyed reading this document, and whether you're a fan or a hater, please know that I'm on board now and I'm here to help, with whatever database kind of thing you want to do - it's all part of my job now.    If we wanted to use/build a whole new ORM, I'd be doing that too right alongside.  OpenStack's lessons in database integration are now my lessons too.  So let me know where I should dig in first!  Have a save drive home.&lt;/div&gt;</summary>
		<author><name>Mike Bayer</name></author>	</entry>

	<entry>
		<id>https://wiki.openstack.org/w/index.php?title=Meetings/Oslo&amp;diff=61728</id>
		<title>Meetings/Oslo</title>
		<link rel="alternate" type="text/html" href="https://wiki.openstack.org/w/index.php?title=Meetings/Oslo&amp;diff=61728"/>
				<updated>2014-08-29T16:14:14Z</updated>
		
		<summary type="html">&lt;p&gt;Mike Bayer: /* Agenda for Next Meeting */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Oslo will hold IRC meetings weekly at the time scheduled below.&lt;br /&gt;
&lt;br /&gt;
If there's an Oslo topic you think warrants a project meeting, please add it to the agenda section below and notify the [http://lists.openstack.org/cgi-bin/mailman/listinfo/openstack-dev openstack-dev@lists.openstack.org] mailing list. Please give everyone at least 24 hours notice.&lt;br /&gt;
&lt;br /&gt;
== Agenda for Next Meeting ==&lt;br /&gt;
&lt;br /&gt;
''Please include your IRC handle along with any agenda items you add, so we know who to call on in the meeting.''&lt;br /&gt;
&lt;br /&gt;
Next meeting: 29 Aug 2014&lt;br /&gt;
&lt;br /&gt;
* Review action items from previous meeting&lt;br /&gt;
** dhellmann approach the other integrated projects who have not started work on oslo.i18n&lt;br /&gt;
*** planning to postpone this until the RC period when we can start talking about k1&lt;br /&gt;
** dimsum_ propose patch adding mask_password to oslo.utils.strutils&lt;br /&gt;
** make sure oslo.concurrency is in the graduating libs query for the dashboard&lt;br /&gt;
*** done (the wildcard pattern means all &amp;quot;oslo.&amp;quot; libraries are included)&lt;br /&gt;
** cores to look at zzzeek's patches https://review.openstack.org/#/q/status:open+project:openstack/oslo.db+branch:master+topic:use-dispatch-for-engine-init,n,z&lt;br /&gt;
** bnemec and dims to try get oslo.serialization out next week&lt;br /&gt;
** dims to chase oslo.utils adoption in stables branches&lt;br /&gt;
** raise this topic again next week - Better way to test oslo.libs releases don't break consuming projects&lt;br /&gt;
**viktors to look at merging run_cross_tests.sh in oslo.db&lt;br /&gt;
* Mike's patches&lt;br /&gt;
** per-test-run provisioning: https://review.openstack.org/#/c/110486/  - a late request to rework the whole approach to use testresources has come in, and it is probably a good idea, blueprint at https://review.openstack.org/#/c/117335/&lt;br /&gt;
** patches: engine setup, listener overhaul: https://review.openstack.org/#/q/status:open+project:openstack/oslo.db+branch:master+topic:use-dispatch-for-engine-init,n,z&lt;br /&gt;
* Red flags for/from liaisons&lt;br /&gt;
* Please review proposed changes to our incubator deprecation policy and respond on the ML&lt;br /&gt;
** http://lists.openstack.org/pipermail/openstack-dev/2014-August/044360.html&lt;br /&gt;
* Adoption status&lt;br /&gt;
** https://etherpad.openstack.org/p/juno-oslo-adoption-status&lt;br /&gt;
* Spec status&lt;br /&gt;
* Graduation status&lt;br /&gt;
** oslo.serialization - https://blueprints.launchpad.net/oslo/+spec/graduate-oslo-serialization&lt;br /&gt;
* Release review&lt;br /&gt;
** oslo.utils&lt;br /&gt;
** oslo.db&lt;br /&gt;
** oslo.concurrency&lt;br /&gt;
* review priorities for this week&lt;br /&gt;
** blueprints and bugs targeted for juno&lt;br /&gt;
** taskflow, oslo.db, and pbr all need extra love!&lt;br /&gt;
** updating the graduation status of our libs: https://review.openstack.org/#/q/status:open+project:openstack/oslo-incubator+branch:master+topic:update-graduation-status,n,z&lt;br /&gt;
**&lt;br /&gt;
&lt;br /&gt;
== General Information ==&lt;br /&gt;
=== Regular Meeting Schedule ===&lt;br /&gt;
* What day: Friday&lt;br /&gt;
* What time: [http://www.timeanddate.com/worldclock/converted.html?iso=20140425T16&amp;amp;p1=0&amp;amp;p2=2133&amp;amp;p3=195&amp;amp;p4=224&amp;amp;p5=43 1600 UTC]&lt;br /&gt;
* Where: #openstack-meeting-alt on freenode&lt;br /&gt;
* Who: All are welcome to participate&lt;br /&gt;
&lt;br /&gt;
=== Notes from Previous Meetings ===&lt;br /&gt;
&lt;br /&gt;
http://eavesdrop.openstack.org/meetings/oslo&lt;br /&gt;
&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-07-11-16.01.html Jul 11, 2014] - topics: oslo.db exception handling; sprint report&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-06-27-16.00.html Jun 27, 2014]&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-06-20-16.01.html Jun 20, 2014] - topics: oslo.db initial release; oslo.messaging good progress in neutron; alpha releases of 5 libraries next week; oslo.db test bugs reported by devananda&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-06-13-16.00.html Jun 13, 2014] - topics: oslo.db alpha release; db migration bug; &lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-06-06-16.00.html Jun 06, 2014] - topics: juno specs, spec approval process&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-05-30-16.00.html May 30, 2014]&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-05-23-16.01.html May 23, 2014] - topics: osprofile (postponed), run_test.sh, juno specs, oslo.test issue in tempest&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-05-09-16.02.html May 09, 2014] - topics: oslo-specs, oslo.messaging, summit prep, oslo.db, oslo.i18n&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-04-25-16.00.html April 24, 2014] - topics: oslotest, oslo.db, oslo.i18n, creating a specs repo&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-02-28-14.00.html Feb 28, 2014] - topics: icehouse feature freeze; syncing cinder &amp;amp; nova; uuidutils&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-02-14-14.01.html Feb 14, 2014] - topics: oslo.db, icehouse-3&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-01-31-14.01.html Jan 31, 2014] - topics: translation, deprecation policy, adopting taskflow, stevedore, and cliff&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2013/oslo.2013-11-15-14.01.html Nov 15, 2013] - topics: translation, pecan/wsme common code, icehouse scheduling&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2013/oslo.2013-10-25-14.00.html Oct 25, 2013] - topics: deprecated decorator and delayed translation implementation plan&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2013/oslo.2013-10-11-14.00.html Oct 11, 2013] - topics: delayed translations&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2013/oslo.2013-08-16-14.00.html Aug 16, 2013] - topic was new messaging API, message security and reject/reque/ack&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2013/oslo.2013-07-19-14.00.html July 19, 2013] - topic was new messaging API, message security, qpid/proton messaging driver and removing logging dependency on eventlet&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2013/oslo.2013-06-07-14.00.html June 7, 2013] - topic was new messaging API and message security&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2013/oslo.2013-05-03-14.01.html May 3, 2013] - topic was new messaging API and message security&lt;br /&gt;
&lt;br /&gt;
(In case the list of notes is not up to date, please consult http://eavesdrop.openstack.org/meetings/oslo/)&lt;/div&gt;</summary>
		<author><name>Mike Bayer</name></author>	</entry>

	<entry>
		<id>https://wiki.openstack.org/w/index.php?title=Meetings/Oslo&amp;diff=61121</id>
		<title>Meetings/Oslo</title>
		<link rel="alternate" type="text/html" href="https://wiki.openstack.org/w/index.php?title=Meetings/Oslo&amp;diff=61121"/>
				<updated>2014-08-22T15:55:40Z</updated>
		
		<summary type="html">&lt;p&gt;Mike Bayer: /* Agenda for Next Meeting */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Oslo will hold IRC meetings weekly at the time scheduled below.&lt;br /&gt;
&lt;br /&gt;
If there's an Oslo topic you think warrants a project meeting, please add it to the agenda section below and notify the [http://lists.openstack.org/cgi-bin/mailman/listinfo/openstack-dev openstack-dev@lists.openstack.org] mailing list. Please give everyone at least 24 hours notice.&lt;br /&gt;
&lt;br /&gt;
== Agenda for Next Meeting ==&lt;br /&gt;
&lt;br /&gt;
''Please include your IRC handle along with any agenda items you add, so we know who to call on in the meeting.''&lt;br /&gt;
&lt;br /&gt;
Next meeting: 22 Aug 2014&lt;br /&gt;
&lt;br /&gt;
* Review action items from previous meeting&lt;br /&gt;
** dhellmann approach the other integrated projects who have not started work on oslo.i18n&lt;br /&gt;
** dimsum_ propose patch adding mask_password to oslo.utils.strutils&lt;br /&gt;
** make sure oslo.concurrency is in the graduating libs query for the dashboard&lt;br /&gt;
* Mike's patches&lt;br /&gt;
** per-test-run provisioning: https://review.openstack.org/#/c/110486/  - a late request to rework the whole approach to use testresources has come in, and it is probably a good idea&lt;br /&gt;
** patches: engine setup, listener overhaul: https://review.openstack.org/#/q/status:open+project:openstack/oslo.db+branch:master+topic:use-dispatch-for-engine-init,n,z&lt;br /&gt;
* Red flags for/from liaisons&lt;br /&gt;
* Adoption status&lt;br /&gt;
** https://etherpad.openstack.org/p/juno-oslo-adoption-status&lt;br /&gt;
* Spec status&lt;br /&gt;
* Graduation status&lt;br /&gt;
** oslo.serialization - https://blueprints.launchpad.net/oslo/+spec/graduate-oslo-serialization&lt;br /&gt;
* Release review&lt;br /&gt;
** oslo.utils&lt;br /&gt;
** oslo.db&lt;br /&gt;
** oslo.concurrency&lt;br /&gt;
* review priorities for this week&lt;br /&gt;
&lt;br /&gt;
== General Information ==&lt;br /&gt;
=== Regular Meeting Schedule ===&lt;br /&gt;
* What day: Friday&lt;br /&gt;
* What time: [http://www.timeanddate.com/worldclock/converted.html?iso=20140425T16&amp;amp;p1=0&amp;amp;p2=2133&amp;amp;p3=195&amp;amp;p4=224&amp;amp;p5=43 1600 UTC]&lt;br /&gt;
* Where: #openstack-meeting-alt on freenode&lt;br /&gt;
* Who: All are welcome to participate&lt;br /&gt;
&lt;br /&gt;
=== Notes from Previous Meetings ===&lt;br /&gt;
&lt;br /&gt;
http://eavesdrop.openstack.org/meetings/oslo&lt;br /&gt;
&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-07-11-16.01.html Jul 11, 2014] - topics: oslo.db exception handling; sprint report&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-06-27-16.00.html Jun 27, 2014]&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-06-20-16.01.html Jun 20, 2014] - topics: oslo.db initial release; oslo.messaging good progress in neutron; alpha releases of 5 libraries next week; oslo.db test bugs reported by devananda&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-06-13-16.00.html Jun 13, 2014] - topics: oslo.db alpha release; db migration bug; &lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-06-06-16.00.html Jun 06, 2014] - topics: juno specs, spec approval process&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-05-30-16.00.html May 30, 2014]&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-05-23-16.01.html May 23, 2014] - topics: osprofile (postponed), run_test.sh, juno specs, oslo.test issue in tempest&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-05-09-16.02.html May 09, 2014] - topics: oslo-specs, oslo.messaging, summit prep, oslo.db, oslo.i18n&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-04-25-16.00.html April 24, 2014] - topics: oslotest, oslo.db, oslo.i18n, creating a specs repo&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-02-28-14.00.html Feb 28, 2014] - topics: icehouse feature freeze; syncing cinder &amp;amp; nova; uuidutils&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-02-14-14.01.html Feb 14, 2014] - topics: oslo.db, icehouse-3&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-01-31-14.01.html Jan 31, 2014] - topics: translation, deprecation policy, adopting taskflow, stevedore, and cliff&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2013/oslo.2013-11-15-14.01.html Nov 15, 2013] - topics: translation, pecan/wsme common code, icehouse scheduling&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2013/oslo.2013-10-25-14.00.html Oct 25, 2013] - topics: deprecated decorator and delayed translation implementation plan&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2013/oslo.2013-10-11-14.00.html Oct 11, 2013] - topics: delayed translations&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2013/oslo.2013-08-16-14.00.html Aug 16, 2013] - topic was new messaging API, message security and reject/reque/ack&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2013/oslo.2013-07-19-14.00.html July 19, 2013] - topic was new messaging API, message security, qpid/proton messaging driver and removing logging dependency on eventlet&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2013/oslo.2013-06-07-14.00.html June 7, 2013] - topic was new messaging API and message security&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2013/oslo.2013-05-03-14.01.html May 3, 2013] - topic was new messaging API and message security&lt;br /&gt;
&lt;br /&gt;
(In case the list of notes is not up to date, please consult http://eavesdrop.openstack.org/meetings/oslo/)&lt;/div&gt;</summary>
		<author><name>Mike Bayer</name></author>	</entry>

	<entry>
		<id>https://wiki.openstack.org/w/index.php?title=Meetings/Oslo&amp;diff=61120</id>
		<title>Meetings/Oslo</title>
		<link rel="alternate" type="text/html" href="https://wiki.openstack.org/w/index.php?title=Meetings/Oslo&amp;diff=61120"/>
				<updated>2014-08-22T15:55:12Z</updated>
		
		<summary type="html">&lt;p&gt;Mike Bayer: /* Agenda for Next Meeting */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Oslo will hold IRC meetings weekly at the time scheduled below.&lt;br /&gt;
&lt;br /&gt;
If there's an Oslo topic you think warrants a project meeting, please add it to the agenda section below and notify the [http://lists.openstack.org/cgi-bin/mailman/listinfo/openstack-dev openstack-dev@lists.openstack.org] mailing list. Please give everyone at least 24 hours notice.&lt;br /&gt;
&lt;br /&gt;
== Agenda for Next Meeting ==&lt;br /&gt;
&lt;br /&gt;
''Please include your IRC handle along with any agenda items you add, so we know who to call on in the meeting.''&lt;br /&gt;
&lt;br /&gt;
Next meeting: 22 Aug 2014&lt;br /&gt;
&lt;br /&gt;
* Review action items from previous meeting&lt;br /&gt;
** dhellmann approach the other integrated projects who have not started work on oslo.i18n&lt;br /&gt;
** dimsum_ propose patch adding mask_password to oslo.utils.strutils&lt;br /&gt;
** make sure oslo.concurrency is in the graduating libs query for the dashboard&lt;br /&gt;
* Mike's patches&lt;br /&gt;
** per-test-run provisioning: https://review.openstack.org/#/c/110486/  - a late request to rework the whole approach to use testresources has come in, and it is probably a good idea&lt;br /&gt;
** seemingly non-controverisal patches: engine setup, listener overhaul: https://review.openstack.org/#/q/status:open+project:openstack/oslo.db+branch:master+topic:use-dispatch-for-engine-init,n,z&lt;br /&gt;
* Red flags for/from liaisons&lt;br /&gt;
* Adoption status&lt;br /&gt;
** https://etherpad.openstack.org/p/juno-oslo-adoption-status&lt;br /&gt;
* Spec status&lt;br /&gt;
* Graduation status&lt;br /&gt;
** oslo.serialization - https://blueprints.launchpad.net/oslo/+spec/graduate-oslo-serialization&lt;br /&gt;
* Release review&lt;br /&gt;
** oslo.utils&lt;br /&gt;
** oslo.db&lt;br /&gt;
** oslo.concurrency&lt;br /&gt;
* review priorities for this week&lt;br /&gt;
&lt;br /&gt;
== General Information ==&lt;br /&gt;
=== Regular Meeting Schedule ===&lt;br /&gt;
* What day: Friday&lt;br /&gt;
* What time: [http://www.timeanddate.com/worldclock/converted.html?iso=20140425T16&amp;amp;p1=0&amp;amp;p2=2133&amp;amp;p3=195&amp;amp;p4=224&amp;amp;p5=43 1600 UTC]&lt;br /&gt;
* Where: #openstack-meeting-alt on freenode&lt;br /&gt;
* Who: All are welcome to participate&lt;br /&gt;
&lt;br /&gt;
=== Notes from Previous Meetings ===&lt;br /&gt;
&lt;br /&gt;
http://eavesdrop.openstack.org/meetings/oslo&lt;br /&gt;
&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-07-11-16.01.html Jul 11, 2014] - topics: oslo.db exception handling; sprint report&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-06-27-16.00.html Jun 27, 2014]&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-06-20-16.01.html Jun 20, 2014] - topics: oslo.db initial release; oslo.messaging good progress in neutron; alpha releases of 5 libraries next week; oslo.db test bugs reported by devananda&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-06-13-16.00.html Jun 13, 2014] - topics: oslo.db alpha release; db migration bug; &lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-06-06-16.00.html Jun 06, 2014] - topics: juno specs, spec approval process&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-05-30-16.00.html May 30, 2014]&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-05-23-16.01.html May 23, 2014] - topics: osprofile (postponed), run_test.sh, juno specs, oslo.test issue in tempest&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-05-09-16.02.html May 09, 2014] - topics: oslo-specs, oslo.messaging, summit prep, oslo.db, oslo.i18n&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-04-25-16.00.html April 24, 2014] - topics: oslotest, oslo.db, oslo.i18n, creating a specs repo&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-02-28-14.00.html Feb 28, 2014] - topics: icehouse feature freeze; syncing cinder &amp;amp; nova; uuidutils&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-02-14-14.01.html Feb 14, 2014] - topics: oslo.db, icehouse-3&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-01-31-14.01.html Jan 31, 2014] - topics: translation, deprecation policy, adopting taskflow, stevedore, and cliff&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2013/oslo.2013-11-15-14.01.html Nov 15, 2013] - topics: translation, pecan/wsme common code, icehouse scheduling&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2013/oslo.2013-10-25-14.00.html Oct 25, 2013] - topics: deprecated decorator and delayed translation implementation plan&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2013/oslo.2013-10-11-14.00.html Oct 11, 2013] - topics: delayed translations&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2013/oslo.2013-08-16-14.00.html Aug 16, 2013] - topic was new messaging API, message security and reject/reque/ack&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2013/oslo.2013-07-19-14.00.html July 19, 2013] - topic was new messaging API, message security, qpid/proton messaging driver and removing logging dependency on eventlet&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2013/oslo.2013-06-07-14.00.html June 7, 2013] - topic was new messaging API and message security&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2013/oslo.2013-05-03-14.01.html May 3, 2013] - topic was new messaging API and message security&lt;br /&gt;
&lt;br /&gt;
(In case the list of notes is not up to date, please consult http://eavesdrop.openstack.org/meetings/oslo/)&lt;/div&gt;</summary>
		<author><name>Mike Bayer</name></author>	</entry>

	<entry>
		<id>https://wiki.openstack.org/w/index.php?title=Meetings/Oslo&amp;diff=61110</id>
		<title>Meetings/Oslo</title>
		<link rel="alternate" type="text/html" href="https://wiki.openstack.org/w/index.php?title=Meetings/Oslo&amp;diff=61110"/>
				<updated>2014-08-22T14:01:30Z</updated>
		
		<summary type="html">&lt;p&gt;Mike Bayer: /* Agenda for Next Meeting */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Oslo will hold IRC meetings weekly at the time scheduled below.&lt;br /&gt;
&lt;br /&gt;
If there's an Oslo topic you think warrants a project meeting, please add it to the agenda section below and notify the [http://lists.openstack.org/cgi-bin/mailman/listinfo/openstack-dev openstack-dev@lists.openstack.org] mailing list. Please give everyone at least 24 hours notice.&lt;br /&gt;
&lt;br /&gt;
== Agenda for Next Meeting ==&lt;br /&gt;
&lt;br /&gt;
''Please include your IRC handle along with any agenda items you add, so we know who to call on in the meeting.''&lt;br /&gt;
&lt;br /&gt;
Next meeting: 22 Aug 2014&lt;br /&gt;
&lt;br /&gt;
* Review action items from previous meeting&lt;br /&gt;
** dhellmann approach the other integrated projects who have not started work on oslo.i18n&lt;br /&gt;
** dimsum_ propose patch adding mask_password to oslo.utils.strutils&lt;br /&gt;
** make sure oslo.concurrency is in the graduating libs query for the dashboard&lt;br /&gt;
* Mike's patches&lt;br /&gt;
** per-test-run provisioning: https://review.openstack.org/#/c/110486/  - can we merge as is, or is it necessary to attempt integration with testresources.  What is the universe of test runners we seek to be compatible with?  we may be losing compatibility with py.test with the deep unittest magic these packages (testresources, testscenarios) do.&lt;br /&gt;
** seemingly non-controverisal patches: engine setup, listener overhaul: https://review.openstack.org/#/q/status:open+project:openstack/oslo.db+branch:master+topic:use-dispatch-for-engine-init,n,z&lt;br /&gt;
* Red flags for/from liaisons&lt;br /&gt;
* Adoption status&lt;br /&gt;
** https://etherpad.openstack.org/p/juno-oslo-adoption-status&lt;br /&gt;
* Spec status&lt;br /&gt;
* Graduation status&lt;br /&gt;
** oslo.serialization - https://blueprints.launchpad.net/oslo/+spec/graduate-oslo-serialization&lt;br /&gt;
* Release review&lt;br /&gt;
** oslo.utils&lt;br /&gt;
** oslo.db&lt;br /&gt;
** oslo.concurrency&lt;br /&gt;
* review priorities for this week&lt;br /&gt;
&lt;br /&gt;
== General Information ==&lt;br /&gt;
=== Regular Meeting Schedule ===&lt;br /&gt;
* What day: Friday&lt;br /&gt;
* What time: [http://www.timeanddate.com/worldclock/converted.html?iso=20140425T16&amp;amp;p1=0&amp;amp;p2=2133&amp;amp;p3=195&amp;amp;p4=224&amp;amp;p5=43 1600 UTC]&lt;br /&gt;
* Where: #openstack-meeting-alt on freenode&lt;br /&gt;
* Who: All are welcome to participate&lt;br /&gt;
&lt;br /&gt;
=== Notes from Previous Meetings ===&lt;br /&gt;
&lt;br /&gt;
http://eavesdrop.openstack.org/meetings/oslo&lt;br /&gt;
&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-07-11-16.01.html Jul 11, 2014] - topics: oslo.db exception handling; sprint report&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-06-27-16.00.html Jun 27, 2014]&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-06-20-16.01.html Jun 20, 2014] - topics: oslo.db initial release; oslo.messaging good progress in neutron; alpha releases of 5 libraries next week; oslo.db test bugs reported by devananda&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-06-13-16.00.html Jun 13, 2014] - topics: oslo.db alpha release; db migration bug; &lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-06-06-16.00.html Jun 06, 2014] - topics: juno specs, spec approval process&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-05-30-16.00.html May 30, 2014]&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-05-23-16.01.html May 23, 2014] - topics: osprofile (postponed), run_test.sh, juno specs, oslo.test issue in tempest&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-05-09-16.02.html May 09, 2014] - topics: oslo-specs, oslo.messaging, summit prep, oslo.db, oslo.i18n&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-04-25-16.00.html April 24, 2014] - topics: oslotest, oslo.db, oslo.i18n, creating a specs repo&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-02-28-14.00.html Feb 28, 2014] - topics: icehouse feature freeze; syncing cinder &amp;amp; nova; uuidutils&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-02-14-14.01.html Feb 14, 2014] - topics: oslo.db, icehouse-3&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-01-31-14.01.html Jan 31, 2014] - topics: translation, deprecation policy, adopting taskflow, stevedore, and cliff&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2013/oslo.2013-11-15-14.01.html Nov 15, 2013] - topics: translation, pecan/wsme common code, icehouse scheduling&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2013/oslo.2013-10-25-14.00.html Oct 25, 2013] - topics: deprecated decorator and delayed translation implementation plan&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2013/oslo.2013-10-11-14.00.html Oct 11, 2013] - topics: delayed translations&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2013/oslo.2013-08-16-14.00.html Aug 16, 2013] - topic was new messaging API, message security and reject/reque/ack&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2013/oslo.2013-07-19-14.00.html July 19, 2013] - topic was new messaging API, message security, qpid/proton messaging driver and removing logging dependency on eventlet&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2013/oslo.2013-06-07-14.00.html June 7, 2013] - topic was new messaging API and message security&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2013/oslo.2013-05-03-14.01.html May 3, 2013] - topic was new messaging API and message security&lt;br /&gt;
&lt;br /&gt;
(In case the list of notes is not up to date, please consult http://eavesdrop.openstack.org/meetings/oslo/)&lt;/div&gt;</summary>
		<author><name>Mike Bayer</name></author>	</entry>

	<entry>
		<id>https://wiki.openstack.org/w/index.php?title=Meetings/Oslo&amp;diff=61103</id>
		<title>Meetings/Oslo</title>
		<link rel="alternate" type="text/html" href="https://wiki.openstack.org/w/index.php?title=Meetings/Oslo&amp;diff=61103"/>
				<updated>2014-08-22T05:30:34Z</updated>
		
		<summary type="html">&lt;p&gt;Mike Bayer: /* Agenda for Next Meeting */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Oslo will hold IRC meetings weekly at the time scheduled below.&lt;br /&gt;
&lt;br /&gt;
If there's an Oslo topic you think warrants a project meeting, please add it to the agenda section below and notify the [http://lists.openstack.org/cgi-bin/mailman/listinfo/openstack-dev openstack-dev@lists.openstack.org] mailing list. Please give everyone at least 24 hours notice.&lt;br /&gt;
&lt;br /&gt;
== Agenda for Next Meeting ==&lt;br /&gt;
&lt;br /&gt;
''Please include your IRC handle along with any agenda items you add, so we know who to call on in the meeting.''&lt;br /&gt;
&lt;br /&gt;
Next meeting: 22 Aug 2014&lt;br /&gt;
&lt;br /&gt;
* Review action items from previous meeting&lt;br /&gt;
** dhellmann approach the other integrated projects who have not started work on oslo.i18n&lt;br /&gt;
** dimsum_ propose patch adding mask_password to oslo.utils.strutils&lt;br /&gt;
** make sure oslo.concurrency is in the graduating libs query for the dashboard&lt;br /&gt;
* Mike's patches&lt;br /&gt;
** per-test-run provisioning: https://review.openstack.org/#/c/110486/ is making people upset, need to evaluate practices here (e.g. people are *very* upset by testr hooks, and want to be &amp;quot;agnostic&amp;quot;, but then recommend tools that probably don't work with py.test, so I need to understand what the required universe of runners are)&lt;br /&gt;
** seemingly non-controverisal patches: engine setup, listener overhaul: https://review.openstack.org/#/q/status:open+project:openstack/oslo.db+branch:master+topic:use-dispatch-for-engine-init,n,z&lt;br /&gt;
* Red flags for/from liaisons&lt;br /&gt;
* Adoption status&lt;br /&gt;
** https://etherpad.openstack.org/p/juno-oslo-adoption-status&lt;br /&gt;
* Spec status&lt;br /&gt;
* Graduation status&lt;br /&gt;
** oslo.serialization - https://blueprints.launchpad.net/oslo/+spec/graduate-oslo-serialization&lt;br /&gt;
* Release review&lt;br /&gt;
** oslo.utils&lt;br /&gt;
** oslo.db&lt;br /&gt;
** oslo.concurrency&lt;br /&gt;
* review priorities for this week&lt;br /&gt;
&lt;br /&gt;
== General Information ==&lt;br /&gt;
=== Regular Meeting Schedule ===&lt;br /&gt;
* What day: Friday&lt;br /&gt;
* What time: [http://www.timeanddate.com/worldclock/converted.html?iso=20140425T16&amp;amp;p1=0&amp;amp;p2=2133&amp;amp;p3=195&amp;amp;p4=224&amp;amp;p5=43 1600 UTC]&lt;br /&gt;
* Where: #openstack-meeting-alt on freenode&lt;br /&gt;
* Who: All are welcome to participate&lt;br /&gt;
&lt;br /&gt;
=== Notes from Previous Meetings ===&lt;br /&gt;
&lt;br /&gt;
http://eavesdrop.openstack.org/meetings/oslo&lt;br /&gt;
&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-07-11-16.01.html Jul 11, 2014] - topics: oslo.db exception handling; sprint report&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-06-27-16.00.html Jun 27, 2014]&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-06-20-16.01.html Jun 20, 2014] - topics: oslo.db initial release; oslo.messaging good progress in neutron; alpha releases of 5 libraries next week; oslo.db test bugs reported by devananda&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-06-13-16.00.html Jun 13, 2014] - topics: oslo.db alpha release; db migration bug; &lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-06-06-16.00.html Jun 06, 2014] - topics: juno specs, spec approval process&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-05-30-16.00.html May 30, 2014]&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-05-23-16.01.html May 23, 2014] - topics: osprofile (postponed), run_test.sh, juno specs, oslo.test issue in tempest&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-05-09-16.02.html May 09, 2014] - topics: oslo-specs, oslo.messaging, summit prep, oslo.db, oslo.i18n&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-04-25-16.00.html April 24, 2014] - topics: oslotest, oslo.db, oslo.i18n, creating a specs repo&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-02-28-14.00.html Feb 28, 2014] - topics: icehouse feature freeze; syncing cinder &amp;amp; nova; uuidutils&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-02-14-14.01.html Feb 14, 2014] - topics: oslo.db, icehouse-3&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-01-31-14.01.html Jan 31, 2014] - topics: translation, deprecation policy, adopting taskflow, stevedore, and cliff&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2013/oslo.2013-11-15-14.01.html Nov 15, 2013] - topics: translation, pecan/wsme common code, icehouse scheduling&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2013/oslo.2013-10-25-14.00.html Oct 25, 2013] - topics: deprecated decorator and delayed translation implementation plan&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2013/oslo.2013-10-11-14.00.html Oct 11, 2013] - topics: delayed translations&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2013/oslo.2013-08-16-14.00.html Aug 16, 2013] - topic was new messaging API, message security and reject/reque/ack&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2013/oslo.2013-07-19-14.00.html July 19, 2013] - topic was new messaging API, message security, qpid/proton messaging driver and removing logging dependency on eventlet&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2013/oslo.2013-06-07-14.00.html June 7, 2013] - topic was new messaging API and message security&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2013/oslo.2013-05-03-14.01.html May 3, 2013] - topic was new messaging API and message security&lt;br /&gt;
&lt;br /&gt;
(In case the list of notes is not up to date, please consult http://eavesdrop.openstack.org/meetings/oslo/)&lt;/div&gt;</summary>
		<author><name>Mike Bayer</name></author>	</entry>

	<entry>
		<id>https://wiki.openstack.org/w/index.php?title=OpenStack_and_SQLAlchemy&amp;diff=60565</id>
		<title>OpenStack and SQLAlchemy</title>
		<link rel="alternate" type="text/html" href="https://wiki.openstack.org/w/index.php?title=OpenStack_and_SQLAlchemy&amp;diff=60565"/>
				<updated>2014-08-14T19:44:09Z</updated>
		
		<summary type="html">&lt;p&gt;Mike Bayer: /* Fast Object Save */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= OpenStack and SQLAlchemy =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
This page describes the ongoing efforts of Mike Bayer, creator/maintainer of&lt;br /&gt;
SQLAlchemy, Alembic Migrations, and Dogpile caching, to spearhead what is&lt;br /&gt;
essentially an &amp;quot;OpenStack SQLAlchemy 2.0&amp;quot; effort - as I am now full-time&lt;br /&gt;
employed by an OpenStack vendor (Red Hat), I've been hired to produce a strong&lt;br /&gt;
and sustained pass through OpenStack's relational database integration,&lt;br /&gt;
improving areas that have been problematic and establishing a very strong&lt;br /&gt;
set of practices that are specific to OpenStack's needs.&lt;br /&gt;
&lt;br /&gt;
Within this effort, I'd like to stress that from my POV, everything is&lt;br /&gt;
on the table, including new improvements / fixes / features to&lt;br /&gt;
SQLAlchemy and Alembic directly, migration of projects for which&lt;br /&gt;
relational databases aren't suitable to non-relational systems,&lt;br /&gt;
improvement of relational schemas on the OpenStack side, and of course&lt;br /&gt;
improvement of exising relational patterns within OpenStack&lt;br /&gt;
applications, for which oslo.db will serve as the foundation.&lt;br /&gt;
&lt;br /&gt;
As of June 30, 2014, I've been on the job for about a month, so below&lt;br /&gt;
is an outline of most of what I've come across and my thoughts going forward.&lt;br /&gt;
&lt;br /&gt;
My goal here is to share with the community what I've seen and done so far,&lt;br /&gt;
so that interested parties can chime in with further advice and information,&lt;br /&gt;
and most importantly to help steer me towards the initial blueprints that&lt;br /&gt;
I should be starting on, that is, which features are most likely to be&lt;br /&gt;
accepted by the community as well as which are the top priority.  Not to mention, if you're looking&lt;br /&gt;
for something to do, you can help me implement some of these things!&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== OpenStack presence within SQLAlchemy's CI environment (a.k.a. &amp;quot;The Version that shall not be Named&amp;quot;) ==&lt;br /&gt;
&lt;br /&gt;
On Monday, June 23, I got up in the AM with two goals: get a release of SQLAlchemy&lt;br /&gt;
out the door, as it had been several months since the last point release, and then&lt;br /&gt;
do some work learning how Nova's unit tests run and function.&lt;br /&gt;
&lt;br /&gt;
Release 0.9.5 of SQLAlchemy was not particularly different than any of the other&lt;br /&gt;
hundred or so point releases I've put out over nine years, it was a little more overdue&lt;br /&gt;
than some, and out it went.  Later that day, as I was learning to run Nova's test suite, I&lt;br /&gt;
was hitting some failures within the FixedIp/FloatingIp tests for which I couldn't&lt;br /&gt;
find the cause, assuming that I just wasn't setting up my environment correctly,&lt;br /&gt;
as Nova's tests are passing for all the Jenkins servers the vast majority of the time.&lt;br /&gt;
&lt;br /&gt;
As we learned within about 30 minutes of my hitting these problems, it&lt;br /&gt;
turns out 0.9.5 had a bad fix in it; one that didn't fail any of&lt;br /&gt;
SQLAlchemy's 5000 tests but hit a use case within Nova that was not&lt;br /&gt;
very unusual; not the kind of use I usually do myself but nonetheless&lt;br /&gt;
something that was pretty common.   IRC began  jumping that builds&lt;br /&gt;
were failing everywhere and it was due to the 0.9.5 release!   The&lt;br /&gt;
bizarre coiincidence of this was that none of the thirty-odd point&lt;br /&gt;
releases of SQLAlchemy released under OpenStack's lifetime have ever&lt;br /&gt;
really had this effect, only the major jumps saw any issues.&lt;br /&gt;
&lt;br /&gt;
Luckily, as I had just learned 30 minutes earlier how to run Nova's tests I was&lt;br /&gt;
able to drill down to exactly what was going on and SQLAlchemy 0.9.6 was out&lt;br /&gt;
within an hour, but we shall now forever see in requirements.txt, the Version&lt;br /&gt;
that shall Not Be Named (except in requirements.txt):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
SQLAlchemy&amp;gt;=0.7.8,!=0.9.5,&amp;lt;=0.9.99&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In response to this I have now&lt;br /&gt;
[http://jenkins.sqlalchemy.org/job/openstack_sqla_master/ added openstack builds to our Jenkins CI system],&lt;br /&gt;
so far running tests for oslo.db and the SQLAlchemy tests within Nova; I spent&lt;br /&gt;
a couple of days figuring out how to get everything to build and run against&lt;br /&gt;
specific versions of SQLAlchemy and Alembic, even versions above the 0.9.99 in&lt;br /&gt;
all the requirements files as we are set for 1.0 hopefully within the next year.&lt;br /&gt;
I will be adding in as many other OpenStack projects as I can here so that we will no&lt;br /&gt;
longer have a SQLAlchemy release that hasn't been run against a battery of&lt;br /&gt;
basic OpenStack tests.&lt;br /&gt;
&lt;br /&gt;
== ORM Woes ==&lt;br /&gt;
&lt;br /&gt;
After chatting with a decent amount of folks for a month, I can break up&lt;br /&gt;
the take on OpenStack / SQLAlchemy ORM into three camps:&lt;br /&gt;
&lt;br /&gt;
1. We love SQLAlchemy ORM, we know that we can improve how we use it and we're looking forward to getting there (oslo.db, Keystone)&lt;br /&gt;
&lt;br /&gt;
2. Our app uses a relational database but the SQLAlchemy ORM slows us down, we already have our own &amp;quot;orm-like&amp;quot; system (e.g. APIs that shuttle rows into &amp;quot;values&amp;quot; dictionaries, nova.objects, etc.) so we want to ultimately get rid of the ORM part, and probably continue using SQLAlchemy Core to some degree (some Nova developers I've spoken to).&lt;br /&gt;
&lt;br /&gt;
3. Our application has a SQLAlchemy backend that almost nobody uses in production, most users use MongoDB, and the SQLAlchemy backend is only there because MongoDB is AGPL (Ceilometer, Marconi).  We'd like to get a better NoSQL backend without Mongo's licensing issue and do away with relational storage entirely ([http://lists.openstack.org/pipermail/openstack-dev/2014-June/038778.html Marconi]).&lt;br /&gt;
&lt;br /&gt;
Here's my observations / thoughts about these three camps:&lt;br /&gt;
&lt;br /&gt;
1. Great!  Read on, I have some things we can start with.&lt;br /&gt;
&lt;br /&gt;
2. I agree that a lot of the patterns that I've seen in Nova in particular are not well-suited to the approach used by SQLAlchemy's ORM, however I have observed that the applications still rely a lot on very sophisticated and mature features provided by SQLAlchemy (namely &amp;lt;code&amp;gt;relationship()&amp;lt;/code&amp;gt; and it's integration with eager loading and the unit of work features), and throwing it away entirely will incur a lot of reinvention, not just of the features that were lost but also of a totally new crop of stability and performance issues to solve.&lt;br /&gt;
&lt;br /&gt;
To that degree, I'd like to propose a twist to the &amp;quot;we want to write our own ORM&amp;quot; idea (edit: OK nova.objects is really an ORM-like thing on top of their API on top of relational DB, but that's an issue for another day) and say, &amp;quot;let's write our own ORM that's based on the SQLAlchemy ORM primitives&amp;quot;, e.g. a hybrid approach where existing ORM logic that is fine can stay that way, ORM logic that has performance or behavioral issues can be reorganized using systems, some of them new, some of them just underutilized, that bypass unneeded SQLAlchemy features on a case-by-case basis.&lt;br /&gt;
&lt;br /&gt;
As a secondary issue, some devs I've spoken to have referred to the nature&lt;br /&gt;
of OpenStack API-based applications being a part of the problem.&lt;br /&gt;
This refers to the fact that OpenStack apps like Nova&lt;br /&gt;
and Neutron expose fine-grained APIs which end up&lt;br /&gt;
breaking out composed use cases into dozens or hundreds of small API&lt;br /&gt;
operations, each of which invokes within it's own database transaction and&lt;br /&gt;
ORM session state.  This makes it impossible, without introducing some elaborate&lt;br /&gt;
offline state management system, for a larger operation to be&lt;br /&gt;
performed so that larger groups of objects can be loaded and manipulated&lt;br /&gt;
at once, greatly decreasing the number of database round trips present.&lt;br /&gt;
No matter what ORM you use or don't use, the pattern of many small round trips&lt;br /&gt;
is difficult to scale on relational databases.&lt;br /&gt;
&lt;br /&gt;
There are under way projects which aim to apply detailed profiling&lt;br /&gt;
to OpenStack applications so that a breakdown of performance issues due&lt;br /&gt;
to API use and/or database&lt;br /&gt;
access might be plainly reasoned about; see [https://github.com/stackforge/rally Rally]&lt;br /&gt;
and [https://github.com/stackforge/osprofiler OS Profiler] for examples.&lt;br /&gt;
&lt;br /&gt;
On my end, I have no intention of focusing on this second issue for the time&lt;br /&gt;
being, I'm going to stick with leaving OS applications as much as they are right now as possible,&lt;br /&gt;
applying iterative changes to database access code which I hope can optimize&lt;br /&gt;
to a significant degree within the current usage patterns.&lt;br /&gt;
&lt;br /&gt;
3. Projects with alternate MongoDB / MySQL backends (since after all, MySQL is the database here being rejected, SQLAlchemy is just a Python API) should probably do what they're doing and go to an all key/value model.  It is not at all fun to maintain the same datamodel and API against both a KV store as well as relational tables.  I had a detailed look at Ceilometer with its team members early on, and while I'm pretty confident that if it were desireable, I could get it's relational backend to compete with the Mongo backend performance-wise, there's no reason to get into this if the Mongo licensing issue can be solved alone.&lt;br /&gt;
&lt;br /&gt;
== ORM Quick Wins Proof of Concept ==&lt;br /&gt;
&lt;br /&gt;
I've built up a proof of concept at [https://github.com/zzzeek/nova_poc nova_poc] which seeks to demonstrate&lt;br /&gt;
the range of database and ORM performance that can be achieved within&lt;br /&gt;
one particular method of the Nova API.   Naturally, I picked an example that&lt;br /&gt;
is very juicy in this regard; it has a very easily fixable issue with the&lt;br /&gt;
kind of query it emits that has an enormous 10x impact on its performance&lt;br /&gt;
(which I feel is worth illustrating just for how dramatic it is),&lt;br /&gt;
and it then illustrates some new SQLAlchemy extensions that more or less&lt;br /&gt;
may be going into SQLAlchemy directly, which allow the remaining operation&lt;br /&gt;
to complete in less than half the time.  These extensions can be ported to&lt;br /&gt;
oslo.db to work against all supported SQLAlchemy versions prior to the official&lt;br /&gt;
release of these features.&lt;br /&gt;
&lt;br /&gt;
To summarize, we're looking at the API method &amp;lt;code&amp;gt;floating_ip_update()&amp;lt;/code&amp;gt;, and&lt;br /&gt;
I run a series of profiles against a database table 10K rows (e.g. &amp;lt;code&amp;gt;FloatingIp&amp;lt;/code&amp;gt;&lt;br /&gt;
objects), where each &amp;lt;code&amp;gt;FloatingIp&amp;lt;/code&amp;gt; refers to a single &amp;lt;code&amp;gt;FixedIp&amp;lt;/code&amp;gt; that then&lt;br /&gt;
refers to an &amp;lt;code&amp;gt;Instance&amp;lt;/code&amp;gt;.   The profiling focuses on the nature of this&lt;br /&gt;
method as an API feature all it's own, and simulates the case where the API&lt;br /&gt;
method is called thousands of times, not unlike other API methods that seek&lt;br /&gt;
to add some small amount of data each time they are called.&lt;br /&gt;
&lt;br /&gt;
=== Eager load and Column load tuning ===&lt;br /&gt;
&lt;br /&gt;
Turning off an eagerload in the &amp;lt;code&amp;gt;_floating_ip_get_by_address()&amp;lt;/code&amp;gt; function&lt;br /&gt;
is the most dramatic win; while removing this method seems like a reasonable&lt;br /&gt;
fix to remove some unneeded overhead, in profiling it shows itself to&lt;br /&gt;
be enormous:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Running scenario default&lt;br /&gt;
Scenario default, total calls for 1000 operations: 24543047&lt;br /&gt;
Scenario default, total time for 10000 operations: 222&lt;br /&gt;
&lt;br /&gt;
Running scenario default_optimized&lt;br /&gt;
Scenario default_optimized, total calls for 1000 operations: 1881626&lt;br /&gt;
Scenario default_optimized, total time for 10000 operations: 25&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
While not illustrated in the POC code itself, talk with various OpenStack&lt;br /&gt;
devs has revealed that we could probably do a lot more individual column&lt;br /&gt;
fetching as well, that is instead of:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
session.query(FixedIp).all()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We say:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
session.query(FixedIp.address, FixedIp.host, FixedIp.reserved).all()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
By loading indivdual columns, we get read-only objects that are not identity&lt;br /&gt;
managed, bypassing a huge amount of work that is done on instances both&lt;br /&gt;
in instantiating them, populating relationships and doing bookkeeping.&lt;br /&gt;
Any API feature that only returns a simple read-only object or a &amp;quot;values&amp;quot;&lt;br /&gt;
dictionary that does not rely upon relationship loading should be using&lt;br /&gt;
this pattern, or similar, by using a&lt;br /&gt;
[http://docs.sqlalchemy.org/en/rel_0_9/orm/mapper_config.html#column-bundles Column Bundle].&lt;br /&gt;
I will be exploring adding the following feature to oslo.db:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
session.query(FixedIp.bundle).all()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The above will return a fast-loading, read-only Bundle object that otherwise&lt;br /&gt;
expresses the full interface of &amp;lt;code&amp;gt;FixedIp&amp;lt;/code&amp;gt;.   Relationships might be tricky&lt;br /&gt;
here but I can at least get them on board as regular lazy loads (just like&lt;br /&gt;
any other ORM would do anyway).&lt;br /&gt;
&lt;br /&gt;
=== Fast Object Save ===&lt;br /&gt;
&lt;br /&gt;
We explore replacing the unit of work &amp;lt;code&amp;gt;flush()&amp;lt;/code&amp;gt; call used by &amp;lt;code&amp;gt;object.save()&amp;lt;/code&amp;gt; within oslo.db-style applications with a more direct &amp;quot;flush single object&amp;quot; call; this grants us only a 12% savings so far, though it is very simple to use:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Running scenario default_optimized&lt;br /&gt;
Scenario default_optimized, total calls for 1000 operations: 1881626&lt;br /&gt;
Scenario default_optimized, total time for 10000 operations: 25&lt;br /&gt;
&lt;br /&gt;
Running scenario fast_save&lt;br /&gt;
Scenario fast_save, total calls for 1000 operations: 1685221&lt;br /&gt;
Scenario fast_save, total time for 10000 operations: 22&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Update: see https://bitbucket.org/zzzeek/sqlalchemy/issue/3100/sessionflush_object for an updated version of this feature that includes a &amp;quot;bulk save&amp;quot; operation.   Inserting 10000 records runs in half the time/function calls with this simple proof of concept.&lt;br /&gt;
&lt;br /&gt;
=== Baked Queries ===&lt;br /&gt;
&lt;br /&gt;
Something that has been in the works for a long time and has&lt;br /&gt;
recently    seen lots of work in the past months is the &amp;quot;baked query&amp;quot;&lt;br /&gt;
feature; this    pattern is ideal for OpenStack's &amp;quot;many short queries&amp;quot;&lt;br /&gt;
pattern, and allows    caching of the generation of SQL.  Recent&lt;br /&gt;
versions of this pattern have    gotten very slick, and can cache&lt;br /&gt;
virtually everything that happens Python-wise    from the construction&lt;br /&gt;
of the &amp;lt;code&amp;gt;Query&amp;lt;/code&amp;gt; object, to calling all the methods    on the&lt;br /&gt;
query, to the query-objects construction of a Core SQL statement,&lt;br /&gt;
to the compilation of that statement as a string - all of these steps&lt;br /&gt;
are removed from the call-graph after the first such call.  In&lt;br /&gt;
SQLAlchemy 1.0    I've also thrown in the construction of column&lt;br /&gt;
metadata from the result set    too.   The pattern involves a bit more&lt;br /&gt;
verbosity to that of constructing a    query, where here I've built&lt;br /&gt;
off of some of the ideas of the    Pony ORM to use Python function&lt;br /&gt;
information as the source of a cache key.    A query such as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
result = model_query(&lt;br /&gt;
            context, models.FloatingIp, session=session).\&lt;br /&gt;
            filter_by(address=address)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
would be expressed in &amp;quot;baked&amp;quot; form as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# note model_query is using the &amp;quot;baked&amp;quot; process internally as well&lt;br /&gt;
result = model_query(context, models.FloatingIp, session=session)&lt;br /&gt;
&lt;br /&gt;
result.bake(lambda query:&lt;br /&gt;
    query.filter_by(&lt;br /&gt;
        address=bindparam('address'))).params(address=address)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the above form, everything within each lambda is invoked only once,&lt;br /&gt;
the result of which becomes part of a cached value.&lt;br /&gt;
&lt;br /&gt;
For this slight increase in verbosity, we get an improvement like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Running scenario default_optimized&lt;br /&gt;
Scenario default_optimized, total calls for 1000 operations: 1881626&lt;br /&gt;
Scenario default_optimized, total time for 10000 operations: 25&lt;br /&gt;
&lt;br /&gt;
Running scenario baked&lt;br /&gt;
Scenario baked, total calls for 1000 operations: 1052935&lt;br /&gt;
Scenario baked, total time for 10000 operations: 16&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
That is, around a 40% improvement.&lt;br /&gt;
&lt;br /&gt;
Putting together both &amp;quot;fast save&amp;quot; plus &amp;quot;baked&amp;quot; we get down to a full 50%&lt;br /&gt;
improvement vs. the plain optimized version:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Running scenario fast_save_plus_baked&lt;br /&gt;
Scenario fast_save_plus_baked, total calls for 1000 operations: 856035&lt;br /&gt;
Scenario fast_save_plus_baked, total time for 10000 operations: 13&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
13 seconds for 10K operations vs. 222 seconds in the current code, I hope&lt;br /&gt;
I've established that there is hope!&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Migrations ==&lt;br /&gt;
&lt;br /&gt;
Migrations are a huge deal.  Here's where it looks like this is going.&lt;br /&gt;
&lt;br /&gt;
Alembic - &amp;quot;Alembic is the defacto standard going forward&amp;quot; - ultimately&lt;br /&gt;
we want to get everything SQLAlchemy-Migrate onto Alembic.  And in talking&lt;br /&gt;
with Nova devs, they really like that they can test their migrations against&lt;br /&gt;
SQLite.  So from my POV, SQLite migrations on Alembic, we're doing that!&lt;br /&gt;
Unless folks think otherwise.&lt;br /&gt;
&lt;br /&gt;
=== SQLite Support ===&lt;br /&gt;
&lt;br /&gt;
SQLite has extremely limited support for ALTER TABLE, essentially only being able&lt;br /&gt;
to add new columns, and that's about it.  In order to provide the full suite&lt;br /&gt;
of ALTER operations that all other databases provide, tools such as&lt;br /&gt;
SQLAlchemy-Migrate will create a copy of the target table with changes applied,&lt;br /&gt;
copy data from old table to new, then drop the old table and rename the new one.&lt;br /&gt;
I've always been peeved at SQLite's insistence on never providing some better&lt;br /&gt;
pathway to ALTER built in.  But it has been on the Alembic roadmap to add SQLite migrations&lt;br /&gt;
in a style similar to that of Migrate for a long time, emulating this same&lt;br /&gt;
approach in some way.  Alembic will have this!&lt;br /&gt;
&lt;br /&gt;
However in talking with some folks, it appears like some people might actually&lt;br /&gt;
like this &amp;quot;make a new table and switch it&amp;quot; approach for other databases too,&lt;br /&gt;
as a way to work around locking of tables.  I don't know that I agree with this&lt;br /&gt;
long term, the &amp;quot;ALTER locks the database!&amp;quot; issue in my experience has always&lt;br /&gt;
been kind of a MySQL problem alone, but it can be present for other backends&lt;br /&gt;
as well.&lt;br /&gt;
&lt;br /&gt;
So the API I'm looking towards here is going to try to solve a few problems;&lt;br /&gt;
one is that, copying the whole table to a new one each time for *every single&lt;br /&gt;
column* is crazy, and also that the technique we use on SQLite might be nice&lt;br /&gt;
for other databases too, so here's the API:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
with op.batch_alter(&amp;quot;mytable&amp;quot;, recreate=True) as batch_op:&lt;br /&gt;
    batch_op.drop_column(&amp;quot;some_column&amp;quot;)&lt;br /&gt;
    batch_op.alter_column(&amp;quot;q&amp;quot;, type=Integer)&lt;br /&gt;
    batch_op.add_column(Column('y', String(50), nullable=False))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
That is, we assign a whole series of column operations under the umbrella of&lt;br /&gt;
one table, and they are created at once.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;batch_alter()&amp;lt;/code&amp;gt; call can even be conditional, dropping into traditional&lt;br /&gt;
migration behavior for all backends except SQLite and whatever else:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
with op.batch_alter(&amp;quot;mytable&amp;quot;, recreate_on=('sqlite',)) as batch_op:&lt;br /&gt;
    batch_op.drop_column(&amp;quot;some_column&amp;quot;)&lt;br /&gt;
    batch_op.alter_column(&amp;quot;q&amp;quot;, type=Integer)&lt;br /&gt;
    batch_op.add_column(Column('y', String(50), nullable=False))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
SQlite-compatible alterations is [https://bitbucket.org/zzzeek/alembic/issue/21/column-renames-not-supported-on-sqlite issue #21].&lt;br /&gt;
&lt;br /&gt;
=== Finishing up Autogenerate ===&lt;br /&gt;
&lt;br /&gt;
I started writing Alembic long before SQLAlchemy-Migrate had autogenerate.  But by the time&lt;br /&gt;
I got Alembic released and later autogenerate working, Migrate already had it.&lt;br /&gt;
Here's what we need to finish up in autogenerate:&lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;code&amp;gt;ForeignKeyConstraint&amp;lt;/code&amp;gt; change detection - this is an entirely straightforward feature add and has long been on the todo list.&lt;br /&gt;
&lt;br /&gt;
2. MySQL implicit indexes - this is actually done; recent versions of Alembic can navigate around MySQLs goofy production of indexes automatically on foreign key columns, and not accidentally spit them out in autogenerates. It was seriously tough to get index autogen mostly working on all backends, so new issues will continue to be fixed as they are reported.&lt;br /&gt;
&lt;br /&gt;
3. Type comparison - Alembic balks on type comparison by default, because ultimately SQLAlchemy should add comparision features to its type objects natively. However, on Alembic backends like that of MySQL, we can add explicit rules for the very common and annoying ones, namely the detection of MySQL's TINYINT as a SQLAlchemy Boolean.  Alembic already allows user-specified rules in this regard, so they can be part of oslo.db in any case, however Alembic can also add this rule and others in directly.&lt;br /&gt;
&lt;br /&gt;
4. Table order - Alembic's autogenerate should be spitting out table creates and drops in the order of foreign key dependency, though we don't have good test coverage for this yet and it might not be working.  We'll fix that, no biggie!  Alternatively, Alembic could include a mode of operation that includes all the ForeignKeyConstraint objects after all the tables. Input on how we'd like to see this work would be welcome.&lt;br /&gt;
&lt;br /&gt;
=== New Migration Features ===&lt;br /&gt;
&lt;br /&gt;
There's a lot of features I'd like to add to Alembic, and if OpenStack has&lt;br /&gt;
a need for them, that would justify the effort:&lt;br /&gt;
&lt;br /&gt;
1. Full branch support - [https://bitbucket.org/zzzeek/alembic/issue/167/multiple-heads-branch-resolution-support issue 167 multiple heads / branch resolution support] has a detailed architecture laid out for this feature.  Lots of people are looking for it. This would turn Alembic's current &amp;quot;linked list&amp;quot; version model into a full directed acyclic graph (DAG).   Any particular migration can be dependent on any other group of migrations, or none at all; individual branches can be maintained and upgraded along their path, or automatically merged.&lt;br /&gt;
&lt;br /&gt;
2. [https://bitbucket.org/zzzeek/alembic/issue/124/multiple-versions-directories Multiple version directories] - This would allow migration files to be present in more than one place for a single migrations environment.  Currently, you can get this approach by using multiple base directories, but that requires separate &amp;lt;code&amp;gt;env.py&amp;lt;/code&amp;gt; files for each. With this issue, cross-dependent migration files can live in multiple places, working nicely with the multiple heads support of #167.&lt;br /&gt;
&lt;br /&gt;
== Testing ==&lt;br /&gt;
&lt;br /&gt;
I spent quite a bit looking into testing.  The main thing we can see&lt;br /&gt;
in testing is that there may be a desire for &amp;quot;transactional testing&amp;quot;;&lt;br /&gt;
run tests within a fixed schema, that is the schema    is created&lt;br /&gt;
once, many tests are run, each one rolling back their work, the&lt;br /&gt;
schema is only dropped once all tests for that schema are complete.&lt;br /&gt;
&lt;br /&gt;
Transactional testing is described in the SQLAlchemy docs and in some of&lt;br /&gt;
my talks; for OpenStack, we need the version that supports graceful handling&lt;br /&gt;
of tests that actually do a rollback.  This is doable and the&lt;br /&gt;
recipe for that is currently [https://gist.github.com/zzzeek/8443477 a github gist].&lt;br /&gt;
&lt;br /&gt;
In order to accommodate parallelized runs, the system will create N&lt;br /&gt;
temporary schemas for N concurrent runs.&lt;br /&gt;
&lt;br /&gt;
I really want to get into this feature but as I talk to people, I'm&lt;br /&gt;
getting mixed signals on how important this feature is.   So far I've&lt;br /&gt;
only worked with unit tests, which seem to be entirely against SQLite&lt;br /&gt;
in-memory databases, and I haven't gotten my head around to what&lt;br /&gt;
extent other databases besides SQLite might be desireable in unit&lt;br /&gt;
tests, nor have I gotten to fully understand how integration tests&lt;br /&gt;
run, so input is welcome here.&lt;br /&gt;
&lt;br /&gt;
== Improvement of ORM Patterns ==&lt;br /&gt;
&lt;br /&gt;
Looking around oslo.db and other apps, I want to improve upon&lt;br /&gt;
some kinds of verbosity I see, which make the code more repetetive and inconsistent, welds together application-level and business level concepts (e.g. transaction control and exception reporting melded with data manipulation) and makes it harder to reason about how the application interacts with sessions, as there are a variety of ad-hoc patterns present.&lt;br /&gt;
&lt;br /&gt;
=== Connectivity Unification ===&lt;br /&gt;
&lt;br /&gt;
If there's one thing that bugs me, it's when a large system has five different ways that it gets around to connecting to a database.  Oslo.db has a single point of &amp;lt;code&amp;gt;create_engine()&amp;lt;/code&amp;gt; call, and that's great, but it isn't used everywhere.   Particularly, we need to get projects that use Alembic like Neutron to use a consistent connectivity approach in their env.py files, and additionally test suites need to be able to run on any DB or DBs you want to point them at.   There's a lot of hardcoding / multiple code path stuff going on and I want to get in there and try to propose a system that does the whole thing just one way, and in a very flexible and environmentally-open way.&lt;br /&gt;
&lt;br /&gt;
* https://bugs.launchpad.net/oslo/+bug/1339206 - oslo.db test suite is too rigidly hardcoded to a small number of potential database backends&lt;br /&gt;
&lt;br /&gt;
=== Transaction Demarcation ===&lt;br /&gt;
&lt;br /&gt;
For the oft-repeated &amp;quot;get_session(); with session.begin(subtransactions=True)&amp;quot; phrase and other variants, I'd like to move that into something more abstracted:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def my_api_thing(x):&lt;br /&gt;
    with transactional() as session:&lt;br /&gt;
    	# do stuff&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I want context managers and/or decorators that are oslo.db specific to replace any kind of containing code that deals with transactions or sessions. That way the containinment logic can be maintained in once place and configured separately from the business logic contained within.  Projects like Keystone are already using a form more like this; it would be nice to nail down a consistent pattern and get it into oslo.db for everyone to use.&lt;br /&gt;
&lt;br /&gt;
=== MySQLdb + eventlet = sad ===&lt;br /&gt;
&lt;br /&gt;
This is the basic thing that happens with these two actors.   We're in an eventlet &amp;quot;thread&amp;quot;.   The code uses MySQLdb to do something ordinary that locks a row in a table - this can happen with a SELECT FOR UPDATE but it really can happen just on any old UPDATE too depending on isolation level (see https://review.openstack.org/#/c/104111/).   If the eventlet &amp;quot;thread&amp;quot; then does *anything* that causes it to defer out, another eventlet thread might run, and if that thread tries to work with MySQLdb and lock the same row, you're done, it's a deadlock.   The simple reason is, MySQLdb doesn't integrate with eventlet, so a simple thing like blocking on a transactional lock, it doesn't defer.   MySQL times out on these so we're getting exceptions instead of permanent freezes, but IMHO this is a really silly problem.  Eventlet and MySQLdb do not work together, we're done.   MySQLdb 1000% either has to go, or be fixed, even if that means forking it.    A good blog post that illustrates both scenarios is [http://www.diamondtin.com/2014/sqlalchemy-gevent-mysql-python-drivers-comparison/ here] - in this post, the author has used &amp;quot;greenify&amp;quot; to patch MySQLdb's blocking, but without this change (which I'm told isn't compatible with eventlet?), MySQLdb is a nonstarter.    Because it is in pure C, it is faster than MySQL-connector when it's been patched, but if it hasn't, it's dog slow.  MySQL-connector IMHO is probably the driver we should get behind [http://dev.mysql.com/doc/connector-python/en/index.html because it's the official MySQL driver]. The various workarounds and hacks (which I guess is why this is under &amp;quot;ORM patterns&amp;quot;) I've seen proposed are all technically viable but IMHO totally unnecessary.   OpenStack should be serious about this, it's a totally solvable problem, let's just solve it.&lt;br /&gt;
&lt;br /&gt;
=== Exception Rewriting ===&lt;br /&gt;
&lt;br /&gt;
Exception rewriting - I notice that the oslo.db pattern includes that DBAPI level exceptions are re-thrown as OpenStack specific;  I'd like to move this into handling using SQLAlchemy events.  The [http://docs.sqlalchemy.org/en/rel_0_9/core/events.html#sqlalchemy.events.ConnectionEvents.dbapi_error dbapi_error] event goes all the way back to 0.7, and [https://bitbucket.org/zzzeek/sqlalchemy/issue/3076/support-reraise-of-custom-exceptions ticket 3076] will allow it to be used to reraise with a new exception.  For older SQLAlchemy versions, I can create a wrapper that will allow it to be used in this way safely for older versions, despite the warnings stated on that document.&lt;br /&gt;
&lt;br /&gt;
* blueprint: https://review.openstack.org/#/c/105306/&lt;br /&gt;
&lt;br /&gt;
=== Deleted Object Selecting ===&lt;br /&gt;
&lt;br /&gt;
There's a need to be able to run queries that automatically de-select for certain rows, particularly rows that have a &amp;quot;deleted&amp;quot; flag set, or that otherwise include various identifiers that are given for a context.  Right now oslo.db has a function `model_query()` for this purpose.   I'd like to streamline `model_query()` and allow for a more flexible pattern, where instead of plainly adding WHERE clauses to a SELECT against a primary table, we can use rules to produce a SQL transformation that will apply the desired criteria to all &amp;quot;FROM&amp;quot; clauses at the point at which they are named, even within a more complex, composed query.   This will allow `model_query()` to be more flexible and ultimately to allow its behavior to be more transparently integrated into the app.&lt;br /&gt;
&lt;br /&gt;
=== Pessimistic Locking - SELECT FOR UPDATE ===&lt;br /&gt;
&lt;br /&gt;
This issue has been coming up for some projects, that they make sometimes great use of SQLAlchemy's &amp;lt;code&amp;gt;with_lockmode()&amp;lt;/code&amp;gt; method, which ultimately emits SELECT..FOR UPDATE, so that selected rows are aggressively locked within a transaction.   The issue is that backends like Galera don't support FOR UPDATE, meaning the locking that is desired is not actually provided.&lt;br /&gt;
&lt;br /&gt;
While I'm not sure that there's a generalized solution to this, in some cases I've been helping developers move to more of an &amp;quot;optimistic&amp;quot; approach, where a particular UPDATE statement targets some kind of timestamp or value counter in the row, then counts the number of rows matched.  If the number of rows matched is not what's expected, it is assumed that the UPDATE failed to match the row, and the operation can either try again or can bail out.   This approach might not be suitable in all cases.  In cases where a large stream of data is affecting a relatively small set of rows, it can perhaps be feasible to serialize those updates by sending them all to a queue, so that they are invoked in isolation without the need for up-front locking.   For very high throughput situations, queues can be constructed to use multiple, concurrent UPDATE streams separated via hashing, or can batch together updates for particular rows over a period of time and emit fewer UPDATE statements than if each source of data did it individually.&lt;br /&gt;
&lt;br /&gt;
=== Encouraging Bundle / Baked Query Use ===&lt;br /&gt;
&lt;br /&gt;
Encouraging Bundle use - as stated earlier, I want to look into bringing the very efficient [http://docs.sqlalchemy.org/en/rel_0_9/orm/mapper_config.html#column-bundles Column Bundle] pattern into the fold for mainstream use, not to mention the baked query pattern illustrated previously.&lt;br /&gt;
&lt;br /&gt;
=== Transparent Retries of Transactions ===&lt;br /&gt;
&lt;br /&gt;
I have ready to go an [https://bitbucket.org/zzzeek/sqlalchemy/issue/3104/transaction-replay-extension already in production extension for transaction replaying], developed for a client with the agreement that the extension is open source.  This extension installs as an event handler using a new series of hooks that were added in 0.9, or alternatively using a series of compatibility hooks that were backported to 0.7 and 0.8.   This extension allows logging of SQL statements as they proceed within a transaction, and allows them to be transparently replayed when a database disconnect error occurs. This means that SQLAlchemy's usual behavior of raising an exception and forcing you to re-try explicitly can be hidden so that it occurs implicitly, without any interruption or special error handling required in application code.  The extension includes a lot of configuration options, such as what statements should be replayed and which shouldn't, as well as what exceptions should be handled this way.   Transaction replaying is an elaborate feature which might introduce lots of new edge cases, namely that the state of the database may have changed since the last transaction failed, so to that extent I'm a little wary of this feature myself.  However I have seen lots of messages alluding to this kind of thing.    The mechanics to pull this into SQLAlchemy as a transparent feature are ready to go, if this is a feature people are looking for (I'm not sure if people are looking for this).&lt;br /&gt;
&lt;br /&gt;
== Contributors ==&lt;br /&gt;
&lt;br /&gt;
I would be remiss if I didn't mention that SQLAlchemy, Alembic, Dogpile and everything else I do are collaborative projects, and while SQLAlchemy in particular might present a daunting codebase, we would love to get new collaborators on board!   Any of the features above are fair game and I can walk someone through the steps to be taken, and additionally everything we add features for always needs lots of tests and review, if you're looking for a way to get into the project I can bring you right in, there's lots of (often very tedious and boring) work to be done.   I'm on all the IRC channels now as zzzeek so ping away.&lt;br /&gt;
&lt;br /&gt;
== Coffee and Cake ==&lt;br /&gt;
&lt;br /&gt;
So! I hope you've enjoyed reading this document, and whether you're a fan or a hater, please know that I'm on board now and I'm here to help, with whatever database kind of thing you want to do - it's all part of my job now.    If we wanted to use/build a whole new ORM, I'd be doing that too right alongside.  OpenStack's lessons in database integration are now my lessons too.  So let me know where I should dig in first!  Have a save drive home.&lt;/div&gt;</summary>
		<author><name>Mike Bayer</name></author>	</entry>

	<entry>
		<id>https://wiki.openstack.org/w/index.php?title=Meetings/Oslo&amp;diff=59013</id>
		<title>Meetings/Oslo</title>
		<link rel="alternate" type="text/html" href="https://wiki.openstack.org/w/index.php?title=Meetings/Oslo&amp;diff=59013"/>
				<updated>2014-07-25T18:38:25Z</updated>
		
		<summary type="html">&lt;p&gt;Mike Bayer: /* Agenda for Next Meeting */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Oslo will hold IRC meetings weekly at the time scheduled below.&lt;br /&gt;
&lt;br /&gt;
If there's an Oslo topic you think warrants a project meeting, please add it to the agenda section below and notify the [http://lists.openstack.org/cgi-bin/mailman/listinfo/openstack-dev openstack-dev@lists.openstack.org] mailing list. Please give everyone at least 24 hours notice.&lt;br /&gt;
&lt;br /&gt;
== Agenda for Next Meeting ==&lt;br /&gt;
&lt;br /&gt;
''Please include your IRC handle along with any agenda items you add, so we know who to call on in the meeting.''&lt;br /&gt;
&lt;br /&gt;
Meeting 1 Aug 2014 cancelled&lt;br /&gt;
&lt;br /&gt;
Next meeting: 8 Aug 2014&lt;br /&gt;
&lt;br /&gt;
* Review action items from previous meeting&lt;br /&gt;
** viktors, rpodolyaka, zzzeek to prepare report about db driver-specific exceptions caught in integrated and incubated projects and what changes will be needed to handle the new wrapped exceptions from oslo.db.      (zzzeek) my current findings and methodology are detailed here: https://etherpad.openstack.org/p/sqla_exceptions_caught&lt;br /&gt;
** dhellmann approach the other integrated projects who have not started work on oslo.i18n&lt;br /&gt;
** jd__ post about pylockfile adoption to the mailing list&lt;br /&gt;
** dolphm start [http://lists.openstack.org/pipermail/openstack-dev/2014-July/041269.html discussion on the mailing list] to get feedback from the oslo team about taking over pycadf library&lt;br /&gt;
* Red flags for/from liaisons&lt;br /&gt;
* Adoption status&lt;br /&gt;
** https://etherpad.openstack.org/p/juno-oslo-adoption-status&lt;br /&gt;
* Spec status&lt;br /&gt;
* Moving &amp;lt;code&amp;gt;openstack/pycadf&amp;lt;/code&amp;gt; to the (current) [https://review.openstack.org/#/c/108739/ Identity program] (&amp;lt;code&amp;gt;dolphm&amp;lt;/code&amp;gt;)&lt;br /&gt;
* Graduation status&lt;br /&gt;
** oslo.utils - https://blueprints.launchpad.net/oslo/+spec/graduate-oslo-utils&lt;br /&gt;
** oslo.serialization - https://blueprints.launchpad.net/oslo/+spec/graduate-oslo-serialization&lt;br /&gt;
** oslo.concurrency - https://blueprints.launchpad.net/oslo/+spec/graduate-oslo-concurrency&lt;br /&gt;
* Release review&lt;br /&gt;
* review priorities for this week&lt;br /&gt;
&lt;br /&gt;
== General Information ==&lt;br /&gt;
=== Regular Meeting Schedule ===&lt;br /&gt;
* What day: Friday&lt;br /&gt;
* What time: [http://www.timeanddate.com/worldclock/converted.html?iso=20140425T16&amp;amp;p1=0&amp;amp;p2=2133&amp;amp;p3=195&amp;amp;p4=224&amp;amp;p5=43 1600 UTC]&lt;br /&gt;
* Where: #openstack-meeting-alt on freenode&lt;br /&gt;
* Who: All are welcome to participate&lt;br /&gt;
&lt;br /&gt;
=== Notes from Previous Meetings ===&lt;br /&gt;
&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-07-25-16.00.html Jul 25, 2014]&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-07-18-16.00.html Jul 18, 2014]&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-07-11-16.01.html Jul 11, 2014] - topics: oslo.db exception handling; sprint report&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-06-27-16.00.html Jun 27, 2014]&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-06-20-16.01.html Jun 20, 2014] - topics: oslo.db initial release; oslo.messaging good progress in neutron; alpha releases of 5 libraries next week; oslo.db test bugs reported by devananda&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-06-13-16.00.html Jun 13, 2014] - topics: oslo.db alpha release; db migration bug; &lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-06-06-16.00.html Jun 06, 2014] - topics: juno specs, spec approval process&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-05-30-16.00.html May 30, 2014]&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-05-23-16.01.html May 23, 2014] - topics: osprofile (postponed), run_test.sh, juno specs, oslo.test issue in tempest&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-05-09-16.02.html May 09, 2014] - topics: oslo-specs, oslo.messaging, summit prep, oslo.db, oslo.i18n&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-04-25-16.00.html April 24, 2014] - topics: oslotest, oslo.db, oslo.i18n, creating a specs repo&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-02-28-14.00.html Feb 28, 2014] - topics: icehouse feature freeze; syncing cinder &amp;amp; nova; uuidutils&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-02-14-14.01.html Feb 14, 2014] - topics: oslo.db, icehouse-3&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-01-31-14.01.html Jan 31, 2014] - topics: translation, deprecation policy, adopting taskflow, stevedore, and cliff&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2013/oslo.2013-11-15-14.01.html Nov 15, 2013] - topics: translation, pecan/wsme common code, icehouse scheduling&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2013/oslo.2013-10-25-14.00.html Oct 25, 2013] - topics: deprecated decorator and delayed translation implementation plan&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2013/oslo.2013-10-11-14.00.html Oct 11, 2013] - topics: delayed translations&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2013/oslo.2013-08-16-14.00.html Aug 16, 2013] - topic was new messaging API, message security and reject/reque/ack&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2013/oslo.2013-07-19-14.00.html July 19, 2013] - topic was new messaging API, message security, qpid/proton messaging driver and removing logging dependency on eventlet&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2013/oslo.2013-06-07-14.00.html June 7, 2013] - topic was new messaging API and message security&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2013/oslo.2013-05-03-14.01.html May 3, 2013] - topic was new messaging API and message security&lt;br /&gt;
&lt;br /&gt;
(In case the list of notes is not up to date, please consult http://eavesdrop.openstack.org/meetings/oslo/)&lt;/div&gt;</summary>
		<author><name>Mike Bayer</name></author>	</entry>

	<entry>
		<id>https://wiki.openstack.org/w/index.php?title=OpenStack_and_SQLAlchemy&amp;diff=57907</id>
		<title>OpenStack and SQLAlchemy</title>
		<link rel="alternate" type="text/html" href="https://wiki.openstack.org/w/index.php?title=OpenStack_and_SQLAlchemy&amp;diff=57907"/>
				<updated>2014-07-10T17:59:23Z</updated>
		
		<summary type="html">&lt;p&gt;Mike Bayer: /* Improvement of ORM Patterns */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= OpenStack and SQLAlchemy =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
This page describes the ongoing efforts of Mike Bayer, creator/maintainer of&lt;br /&gt;
SQLAlchemy, Alembic Migrations, and Dogpile caching, to spearhead what is&lt;br /&gt;
essentially an &amp;quot;OpenStack SQLAlchemy 2.0&amp;quot; effort - as I am now full-time&lt;br /&gt;
employed by an OpenStack vendor (Red Hat), I've been hired to produce a strong&lt;br /&gt;
and sustained pass through OpenStack's relational database integration,&lt;br /&gt;
improving areas that have been problematic and establishing a very strong&lt;br /&gt;
set of practices that are specific to OpenStack's needs.&lt;br /&gt;
&lt;br /&gt;
Within this effort, I'd like to stress that from my POV, everything is&lt;br /&gt;
on the table, including new improvements / fixes / features to&lt;br /&gt;
SQLAlchemy and Alembic directly, migration of projects for which&lt;br /&gt;
relational databases aren't suitable to non-relational systems,&lt;br /&gt;
improvement of relational schemas on the OpenStack side, and of course&lt;br /&gt;
improvement of exising relational patterns within OpenStack&lt;br /&gt;
applications, for which oslo.db will serve as the foundation.&lt;br /&gt;
&lt;br /&gt;
As of June 30, 2014, I've been on the job for about a month, so below&lt;br /&gt;
is an outline of most of what I've come across and my thoughts going forward.&lt;br /&gt;
&lt;br /&gt;
My goal here is to share with the community what I've seen and done so far,&lt;br /&gt;
so that interested parties can chime in with further advice and information,&lt;br /&gt;
and most importantly to help steer me towards the initial blueprints that&lt;br /&gt;
I should be starting on, that is, which features are most likely to be&lt;br /&gt;
accepted by the community as well as which are the top priority.  Not to mention, if you're looking&lt;br /&gt;
for something to do, you can help me implement some of these things!&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== OpenStack presence within SQLAlchemy's CI environment (a.k.a. &amp;quot;The Version that shall not be Named&amp;quot;) ==&lt;br /&gt;
&lt;br /&gt;
On Monday, June 23, I got up in the AM with two goals: get a release of SQLAlchemy&lt;br /&gt;
out the door, as it had been several months since the last point release, and then&lt;br /&gt;
do some work learning how Nova's unit tests run and function.&lt;br /&gt;
&lt;br /&gt;
Release 0.9.5 of SQLAlchemy was not particularly different than any of the other&lt;br /&gt;
hundred or so point releases I've put out over nine years, it was a little more overdue&lt;br /&gt;
than some, and out it went.  Later that day, as I was learning to run Nova's test suite, I&lt;br /&gt;
was hitting some failures within the FixedIp/FloatingIp tests for which I couldn't&lt;br /&gt;
find the cause, assuming that I just wasn't setting up my environment correctly,&lt;br /&gt;
as Nova's tests are passing for all the Jenkins servers the vast majority of the time.&lt;br /&gt;
&lt;br /&gt;
As we learned within about 30 minutes of my hitting these problems, it&lt;br /&gt;
turns out 0.9.5 had a bad fix in it; one that didn't fail any of&lt;br /&gt;
SQLAlchemy's 5000 tests but hit a use case within Nova that was not&lt;br /&gt;
very unusual; not the kind of use I usually do myself but nonetheless&lt;br /&gt;
something that was pretty common.   IRC began  jumping that builds&lt;br /&gt;
were failing everywhere and it was due to the 0.9.5 release!   The&lt;br /&gt;
bizarre coiincidence of this was that none of the thirty-odd point&lt;br /&gt;
releases of SQLAlchemy released under OpenStack's lifetime have ever&lt;br /&gt;
really had this effect, only the major jumps saw any issues.&lt;br /&gt;
&lt;br /&gt;
Luckily, as I had just learned 30 minutes earlier how to run Nova's tests I was&lt;br /&gt;
able to drill down to exactly what was going on and SQLAlchemy 0.9.6 was out&lt;br /&gt;
within an hour, but we shall now forever see in requirements.txt, the Version&lt;br /&gt;
that shall Not Be Named (except in requirements.txt):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
SQLAlchemy&amp;gt;=0.7.8,!=0.9.5,&amp;lt;=0.9.99&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In response to this I have now&lt;br /&gt;
[http://jenkins.sqlalchemy.org/job/openstack_sqla_master/ added openstack builds to our Jenkins CI system],&lt;br /&gt;
so far running tests for oslo.db and the SQLAlchemy tests within Nova; I spent&lt;br /&gt;
a couple of days figuring out how to get everything to build and run against&lt;br /&gt;
specific versions of SQLAlchemy and Alembic, even versions above the 0.9.99 in&lt;br /&gt;
all the requirements files as we are set for 1.0 hopefully within the next year.&lt;br /&gt;
I will be adding in as many other OpenStack projects as I can here so that we will no&lt;br /&gt;
longer have a SQLAlchemy release that hasn't been run against a battery of&lt;br /&gt;
basic OpenStack tests.&lt;br /&gt;
&lt;br /&gt;
== ORM Woes ==&lt;br /&gt;
&lt;br /&gt;
After chatting with a decent amount of folks for a month, I can break up&lt;br /&gt;
the take on OpenStack / SQLAlchemy ORM into three camps:&lt;br /&gt;
&lt;br /&gt;
1. We love SQLAlchemy ORM, we know that we can improve how we use it and we're looking forward to getting there (oslo.db, Keystone)&lt;br /&gt;
&lt;br /&gt;
2. Our app uses a relational database but the SQLAlchemy ORM slows us down, we already have our own &amp;quot;orm-like&amp;quot; system (e.g. APIs that shuttle rows into &amp;quot;values&amp;quot; dictionaries, nova.objects, etc.) so we want to ultimately get rid of the ORM part, and probably continue using SQLAlchemy Core to some degree (some Nova developers I've spoken to).&lt;br /&gt;
&lt;br /&gt;
3. Our application has a SQLAlchemy backend that almost nobody uses in production, most users use MongoDB, and the SQLAlchemy backend is only there because MongoDB is AGPL (Ceilometer, Marconi).  We'd like to get a better NoSQL backend without Mongo's licensing issue and do away with relational storage entirely ([http://lists.openstack.org/pipermail/openstack-dev/2014-June/038778.html Marconi]).&lt;br /&gt;
&lt;br /&gt;
Here's my observations / thoughts about these three camps:&lt;br /&gt;
&lt;br /&gt;
1. Great!  Read on, I have some things we can start with.&lt;br /&gt;
&lt;br /&gt;
2. I agree that a lot of the patterns that I've seen in Nova in particular are not well-suited to the approach used by SQLAlchemy's ORM, however I have observed that the applications still rely a lot on very sophisticated and mature features provided by SQLAlchemy (namely &amp;lt;code&amp;gt;relationship()&amp;lt;/code&amp;gt; and it's integration with eager loading and the unit of work features), and throwing it away entirely will incur a lot of reinvention, not just of the features that were lost but also of a totally new crop of stability and performance issues to solve.&lt;br /&gt;
&lt;br /&gt;
To that degree, I'd like to propose a twist to the &amp;quot;we want to write our own ORM&amp;quot; idea (edit: OK nova.objects is really an ORM-like thing on top of their API on top of relational DB, but that's an issue for another day) and say, &amp;quot;let's write our own ORM that's based on the SQLAlchemy ORM primitives&amp;quot;, e.g. a hybrid approach where existing ORM logic that is fine can stay that way, ORM logic that has performance or behavioral issues can be reorganized using systems, some of them new, some of them just underutilized, that bypass unneeded SQLAlchemy features on a case-by-case basis.&lt;br /&gt;
&lt;br /&gt;
As a secondary issue, some devs I've spoken to have referred to the nature&lt;br /&gt;
of OpenStack API-based applications being a part of the problem.&lt;br /&gt;
This refers to the fact that OpenStack apps like Nova&lt;br /&gt;
and Neutron expose fine-grained APIs which end up&lt;br /&gt;
breaking out composed use cases into dozens or hundreds of small API&lt;br /&gt;
operations, each of which invokes within it's own database transaction and&lt;br /&gt;
ORM session state.  This makes it impossible, without introducing some elaborate&lt;br /&gt;
offline state management system, for a larger operation to be&lt;br /&gt;
performed so that larger groups of objects can be loaded and manipulated&lt;br /&gt;
at once, greatly decreasing the number of database round trips present.&lt;br /&gt;
No matter what ORM you use or don't use, the pattern of many small round trips&lt;br /&gt;
is difficult to scale on relational databases.&lt;br /&gt;
&lt;br /&gt;
There are under way projects which aim to apply detailed profiling&lt;br /&gt;
to OpenStack applications so that a breakdown of performance issues due&lt;br /&gt;
to API use and/or database&lt;br /&gt;
access might be plainly reasoned about; see [https://github.com/stackforge/rally Rally]&lt;br /&gt;
and [https://github.com/stackforge/osprofiler OS Profiler] for examples.&lt;br /&gt;
&lt;br /&gt;
On my end, I have no intention of focusing on this second issue for the time&lt;br /&gt;
being, I'm going to stick with leaving OS applications as much as they are right now as possible,&lt;br /&gt;
applying iterative changes to database access code which I hope can optimize&lt;br /&gt;
to a significant degree within the current usage patterns.&lt;br /&gt;
&lt;br /&gt;
3. Projects with alternate MongoDB / MySQL backends (since after all, MySQL is the database here being rejected, SQLAlchemy is just a Python API) should probably do what they're doing and go to an all key/value model.  It is not at all fun to maintain the same datamodel and API against both a KV store as well as relational tables.  I had a detailed look at Ceilometer with its team members early on, and while I'm pretty confident that if it were desireable, I could get it's relational backend to compete with the Mongo backend performance-wise, there's no reason to get into this if the Mongo licensing issue can be solved alone.&lt;br /&gt;
&lt;br /&gt;
== ORM Quick Wins Proof of Concept ==&lt;br /&gt;
&lt;br /&gt;
I've built up a proof of concept at [https://github.com/zzzeek/nova_poc nova_poc] which seeks to demonstrate&lt;br /&gt;
the range of database and ORM performance that can be achieved within&lt;br /&gt;
one particular method of the Nova API.   Naturally, I picked an example that&lt;br /&gt;
is very juicy in this regard; it has a very easily fixable issue with the&lt;br /&gt;
kind of query it emits that has an enormous 10x impact on its performance&lt;br /&gt;
(which I feel is worth illustrating just for how dramatic it is),&lt;br /&gt;
and it then illustrates some new SQLAlchemy extensions that more or less&lt;br /&gt;
may be going into SQLAlchemy directly, which allow the remaining operation&lt;br /&gt;
to complete in less than half the time.  These extensions can be ported to&lt;br /&gt;
oslo.db to work against all supported SQLAlchemy versions prior to the official&lt;br /&gt;
release of these features.&lt;br /&gt;
&lt;br /&gt;
To summarize, we're looking at the API method &amp;lt;code&amp;gt;floating_ip_update()&amp;lt;/code&amp;gt;, and&lt;br /&gt;
I run a series of profiles against a database table 10K rows (e.g. &amp;lt;code&amp;gt;FloatingIp&amp;lt;/code&amp;gt;&lt;br /&gt;
objects), where each &amp;lt;code&amp;gt;FloatingIp&amp;lt;/code&amp;gt; refers to a single &amp;lt;code&amp;gt;FixedIp&amp;lt;/code&amp;gt; that then&lt;br /&gt;
refers to an &amp;lt;code&amp;gt;Instance&amp;lt;/code&amp;gt;.   The profiling focuses on the nature of this&lt;br /&gt;
method as an API feature all it's own, and simulates the case where the API&lt;br /&gt;
method is called thousands of times, not unlike other API methods that seek&lt;br /&gt;
to add some small amount of data each time they are called.&lt;br /&gt;
&lt;br /&gt;
=== Eager load and Column load tuning ===&lt;br /&gt;
&lt;br /&gt;
Turning off an eagerload in the &amp;lt;code&amp;gt;_floating_ip_get_by_address()&amp;lt;/code&amp;gt; function&lt;br /&gt;
is the most dramatic win; while removing this method seems like a reasonable&lt;br /&gt;
fix to remove some unneeded overhead, in profiling it shows itself to&lt;br /&gt;
be enormous:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Running scenario default&lt;br /&gt;
Scenario default, total calls for 1000 operations: 24543047&lt;br /&gt;
Scenario default, total time for 10000 operations: 222&lt;br /&gt;
&lt;br /&gt;
Running scenario default_optimized&lt;br /&gt;
Scenario default_optimized, total calls for 1000 operations: 1881626&lt;br /&gt;
Scenario default_optimized, total time for 10000 operations: 25&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
While not illustrated in the POC code itself, talk with various OpenStack&lt;br /&gt;
devs has revealed that we could probably do a lot more individual column&lt;br /&gt;
fetching as well, that is instead of:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
session.query(FixedIp).all()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We say:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
session.query(FixedIp.address, FixedIp.host, FixedIp.reserved).all()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
By loading indivdual columns, we get read-only objects that are not identity&lt;br /&gt;
managed, bypassing a huge amount of work that is done on instances both&lt;br /&gt;
in instantiating them, populating relationships and doing bookkeeping.&lt;br /&gt;
Any API feature that only returns a simple read-only object or a &amp;quot;values&amp;quot;&lt;br /&gt;
dictionary that does not rely upon relationship loading should be using&lt;br /&gt;
this pattern, or similar, by using a&lt;br /&gt;
[http://docs.sqlalchemy.org/en/rel_0_9/orm/mapper_config.html#column-bundles Column Bundle].&lt;br /&gt;
I will be exploring adding the following feature to oslo.db:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
session.query(FixedIp.bundle).all()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The above will return a fast-loading, read-only Bundle object that otherwise&lt;br /&gt;
expresses the full interface of &amp;lt;code&amp;gt;FixedIp&amp;lt;/code&amp;gt;.   Relationships might be tricky&lt;br /&gt;
here but I can at least get them on board as regular lazy loads (just like&lt;br /&gt;
any other ORM would do anyway).&lt;br /&gt;
&lt;br /&gt;
=== Fast Object Save ===&lt;br /&gt;
&lt;br /&gt;
We explore replacing the unit of work &amp;lt;code&amp;gt;flush()&amp;lt;/code&amp;gt; call used by &amp;lt;code&amp;gt;object.save()&amp;lt;/code&amp;gt; within oslo.db-style applications with a more direct &amp;quot;flush single object&amp;quot; call; this grants us only a 12% savings so far, though it is very simple to use:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Running scenario default_optimized&lt;br /&gt;
Scenario default_optimized, total calls for 1000 operations: 1881626&lt;br /&gt;
Scenario default_optimized, total time for 10000 operations: 25&lt;br /&gt;
&lt;br /&gt;
Running scenario fast_save&lt;br /&gt;
Scenario fast_save, total calls for 1000 operations: 1685221&lt;br /&gt;
Scenario fast_save, total time for 10000 operations: 22&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Baked Queries ===&lt;br /&gt;
&lt;br /&gt;
Something that has been in the works for a long time and has&lt;br /&gt;
recently    seen lots of work in the past months is the &amp;quot;baked query&amp;quot;&lt;br /&gt;
feature; this    pattern is ideal for OpenStack's &amp;quot;many short queries&amp;quot;&lt;br /&gt;
pattern, and allows    caching of the generation of SQL.  Recent&lt;br /&gt;
versions of this pattern have    gotten very slick, and can cache&lt;br /&gt;
virtually everything that happens Python-wise    from the construction&lt;br /&gt;
of the &amp;lt;code&amp;gt;Query&amp;lt;/code&amp;gt; object, to calling all the methods    on the&lt;br /&gt;
query, to the query-objects construction of a Core SQL statement,&lt;br /&gt;
to the compilation of that statement as a string - all of these steps&lt;br /&gt;
are removed from the call-graph after the first such call.  In&lt;br /&gt;
SQLAlchemy 1.0    I've also thrown in the construction of column&lt;br /&gt;
metadata from the result set    too.   The pattern involves a bit more&lt;br /&gt;
verbosity to that of constructing a    query, where here I've built&lt;br /&gt;
off of some of the ideas of the    Pony ORM to use Python function&lt;br /&gt;
information as the source of a cache key.    A query such as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
result = model_query(&lt;br /&gt;
            context, models.FloatingIp, session=session).\&lt;br /&gt;
            filter_by(address=address)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
would be expressed in &amp;quot;baked&amp;quot; form as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# note model_query is using the &amp;quot;baked&amp;quot; process internally as well&lt;br /&gt;
result = model_query(context, models.FloatingIp, session=session)&lt;br /&gt;
&lt;br /&gt;
result.bake(lambda query:&lt;br /&gt;
    query.filter_by(&lt;br /&gt;
        address=bindparam('address'))).params(address=address)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the above form, everything within each lambda is invoked only once,&lt;br /&gt;
the result of which becomes part of a cached value.&lt;br /&gt;
&lt;br /&gt;
For this slight increase in verbosity, we get an improvement like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Running scenario default_optimized&lt;br /&gt;
Scenario default_optimized, total calls for 1000 operations: 1881626&lt;br /&gt;
Scenario default_optimized, total time for 10000 operations: 25&lt;br /&gt;
&lt;br /&gt;
Running scenario baked&lt;br /&gt;
Scenario baked, total calls for 1000 operations: 1052935&lt;br /&gt;
Scenario baked, total time for 10000 operations: 16&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
That is, around a 40% improvement.&lt;br /&gt;
&lt;br /&gt;
Putting together both &amp;quot;fast save&amp;quot; plus &amp;quot;baked&amp;quot; we get down to a full 50%&lt;br /&gt;
improvement vs. the plain optimized version:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Running scenario fast_save_plus_baked&lt;br /&gt;
Scenario fast_save_plus_baked, total calls for 1000 operations: 856035&lt;br /&gt;
Scenario fast_save_plus_baked, total time for 10000 operations: 13&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
13 seconds for 10K operations vs. 222 seconds in the current code, I hope&lt;br /&gt;
I've established that there is hope!&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Migrations ==&lt;br /&gt;
&lt;br /&gt;
Migrations are a huge deal.  Here's where it looks like this is going.&lt;br /&gt;
&lt;br /&gt;
Alembic - &amp;quot;Alembic is the defacto standard going forward&amp;quot; - ultimately&lt;br /&gt;
we want to get everything SQLAlchemy-Migrate onto Alembic.  And in talking&lt;br /&gt;
with Nova devs, they really like that they can test their migrations against&lt;br /&gt;
SQLite.  So from my POV, SQLite migrations on Alembic, we're doing that!&lt;br /&gt;
Unless folks think otherwise.&lt;br /&gt;
&lt;br /&gt;
=== SQLite Support ===&lt;br /&gt;
&lt;br /&gt;
SQLite has extremely limited support for ALTER TABLE, essentially only being able&lt;br /&gt;
to add new columns, and that's about it.  In order to provide the full suite&lt;br /&gt;
of ALTER operations that all other databases provide, tools such as&lt;br /&gt;
SQLAlchemy-Migrate will create a copy of the target table with changes applied,&lt;br /&gt;
copy data from old table to new, then drop the old table and rename the new one.&lt;br /&gt;
I've always been peeved at SQLite's insistence on never providing some better&lt;br /&gt;
pathway to ALTER built in.  But it has been on the Alembic roadmap to add SQLite migrations&lt;br /&gt;
in a style similar to that of Migrate for a long time, emulating this same&lt;br /&gt;
approach in some way.  Alembic will have this!&lt;br /&gt;
&lt;br /&gt;
However in talking with some folks, it appears like some people might actually&lt;br /&gt;
like this &amp;quot;make a new table and switch it&amp;quot; approach for other databases too,&lt;br /&gt;
as a way to work around locking of tables.  I don't know that I agree with this&lt;br /&gt;
long term, the &amp;quot;ALTER locks the database!&amp;quot; issue in my experience has always&lt;br /&gt;
been kind of a MySQL problem alone, but it can be present for other backends&lt;br /&gt;
as well.&lt;br /&gt;
&lt;br /&gt;
So the API I'm looking towards here is going to try to solve a few problems;&lt;br /&gt;
one is that, copying the whole table to a new one each time for *every single&lt;br /&gt;
column* is crazy, and also that the technique we use on SQLite might be nice&lt;br /&gt;
for other databases too, so here's the API:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
with op.batch_alter(&amp;quot;mytable&amp;quot;, recreate=True) as batch_op:&lt;br /&gt;
    batch_op.drop_column(&amp;quot;some_column&amp;quot;)&lt;br /&gt;
    batch_op.alter_column(&amp;quot;q&amp;quot;, type=Integer)&lt;br /&gt;
    batch_op.add_column(Column('y', String(50), nullable=False))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
That is, we assign a whole series of column operations under the umbrella of&lt;br /&gt;
one table, and they are created at once.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;batch_alter()&amp;lt;/code&amp;gt; call can even be conditional, dropping into traditional&lt;br /&gt;
migration behavior for all backends except SQLite and whatever else:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
with op.batch_alter(&amp;quot;mytable&amp;quot;, recreate_on=('sqlite',)) as batch_op:&lt;br /&gt;
    batch_op.drop_column(&amp;quot;some_column&amp;quot;)&lt;br /&gt;
    batch_op.alter_column(&amp;quot;q&amp;quot;, type=Integer)&lt;br /&gt;
    batch_op.add_column(Column('y', String(50), nullable=False))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
SQlite-compatible alterations is [https://bitbucket.org/zzzeek/alembic/issue/21/column-renames-not-supported-on-sqlite issue #21].&lt;br /&gt;
&lt;br /&gt;
=== Finishing up Autogenerate ===&lt;br /&gt;
&lt;br /&gt;
I started writing Alembic long before SQLAlchemy-Migrate had autogenerate.  But by the time&lt;br /&gt;
I got Alembic released and later autogenerate working, Migrate already had it.&lt;br /&gt;
Here's what we need to finish up in autogenerate:&lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;code&amp;gt;ForeignKeyConstraint&amp;lt;/code&amp;gt; change detection - this is an entirely straightforward feature add and has long been on the todo list.&lt;br /&gt;
&lt;br /&gt;
2. MySQL implicit indexes - this is actually done; recent versions of Alembic can navigate around MySQLs goofy production of indexes automatically on foreign key columns, and not accidentally spit them out in autogenerates. It was seriously tough to get index autogen mostly working on all backends, so new issues will continue to be fixed as they are reported.&lt;br /&gt;
&lt;br /&gt;
3. Type comparison - Alembic balks on type comparison by default, because ultimately SQLAlchemy should add comparision features to its type objects natively. However, on Alembic backends like that of MySQL, we can add explicit rules for the very common and annoying ones, namely the detection of MySQL's TINYINT as a SQLAlchemy Boolean.  Alembic already allows user-specified rules in this regard, so they can be part of oslo.db in any case, however Alembic can also add this rule and others in directly.&lt;br /&gt;
&lt;br /&gt;
4. Table order - Alembic's autogenerate should be spitting out table creates and drops in the order of foreign key dependency, though we don't have good test coverage for this yet and it might not be working.  We'll fix that, no biggie!  Alternatively, Alembic could include a mode of operation that includes all the ForeignKeyConstraint objects after all the tables. Input on how we'd like to see this work would be welcome.&lt;br /&gt;
&lt;br /&gt;
=== New Migration Features ===&lt;br /&gt;
&lt;br /&gt;
There's a lot of features I'd like to add to Alembic, and if OpenStack has&lt;br /&gt;
a need for them, that would justify the effort:&lt;br /&gt;
&lt;br /&gt;
1. Full branch support - [https://bitbucket.org/zzzeek/alembic/issue/167/multiple-heads-branch-resolution-support issue 167 multiple heads / branch resolution support] has a detailed architecture laid out for this feature.  Lots of people are looking for it. This would turn Alembic's current &amp;quot;linked list&amp;quot; version model into a full directed acyclic graph (DAG).   Any particular migration can be dependent on any other group of migrations, or none at all; individual branches can be maintained and upgraded along their path, or automatically merged.&lt;br /&gt;
&lt;br /&gt;
2. [https://bitbucket.org/zzzeek/alembic/issue/124/multiple-versions-directories Multiple version directories] - This would allow migration files to be present in more than one place for a single migrations environment.  Currently, you can get this approach by using multiple base directories, but that requires separate &amp;lt;code&amp;gt;env.py&amp;lt;/code&amp;gt; files for each. With this issue, cross-dependent migration files can live in multiple places, working nicely with the multiple heads support of #167.&lt;br /&gt;
&lt;br /&gt;
== Testing ==&lt;br /&gt;
&lt;br /&gt;
I spent quite a bit looking into testing.  The main thing we can see&lt;br /&gt;
in testing is that there may be a desire for &amp;quot;transactional testing&amp;quot;;&lt;br /&gt;
run tests within a fixed schema, that is the schema    is created&lt;br /&gt;
once, many tests are run, each one rolling back their work, the&lt;br /&gt;
schema is only dropped once all tests for that schema are complete.&lt;br /&gt;
&lt;br /&gt;
Transactional testing is described in the SQLAlchemy docs and in some of&lt;br /&gt;
my talks; for OpenStack, we need the version that supports graceful handling&lt;br /&gt;
of tests that actually do a rollback.  This is doable and the&lt;br /&gt;
recipe for that is currently [https://gist.github.com/zzzeek/8443477 a github gist].&lt;br /&gt;
&lt;br /&gt;
In order to accommodate parallelized runs, the system will create N&lt;br /&gt;
temporary schemas for N concurrent runs.&lt;br /&gt;
&lt;br /&gt;
I really want to get into this feature but as I talk to people, I'm&lt;br /&gt;
getting mixed signals on how important this feature is.   So far I've&lt;br /&gt;
only worked with unit tests, which seem to be entirely against SQLite&lt;br /&gt;
in-memory databases, and I haven't gotten my head around to what&lt;br /&gt;
extent other databases besides SQLite might be desireable in unit&lt;br /&gt;
tests, nor have I gotten to fully understand how integration tests&lt;br /&gt;
run, so input is welcome here.&lt;br /&gt;
&lt;br /&gt;
== Improvement of ORM Patterns ==&lt;br /&gt;
&lt;br /&gt;
Looking around oslo.db and other apps, I want to improve upon&lt;br /&gt;
some kinds of verbosity I see, which make the code more repetetive and inconsistent, welds together application-level and business level concepts (e.g. transaction control and exception reporting melded with data manipulation) and makes it harder to reason about how the application interacts with sessions, as there are a variety of ad-hoc patterns present.&lt;br /&gt;
&lt;br /&gt;
=== Connectivity Unification ===&lt;br /&gt;
&lt;br /&gt;
If there's one thing that bugs me, it's when a large system has five different ways that it gets around to connecting to a database.  Oslo.db has a single point of &amp;lt;code&amp;gt;create_engine()&amp;lt;/code&amp;gt; call, and that's great, but it isn't used everywhere.   Particularly, we need to get projects that use Alembic like Neutron to use a consistent connectivity approach in their env.py files, and additionally test suites need to be able to run on any DB or DBs you want to point them at.   There's a lot of hardcoding / multiple code path stuff going on and I want to get in there and try to propose a system that does the whole thing just one way, and in a very flexible and environmentally-open way.&lt;br /&gt;
&lt;br /&gt;
* https://bugs.launchpad.net/oslo/+bug/1339206 - oslo.db test suite is too rigidly hardcoded to a small number of potential database backends&lt;br /&gt;
&lt;br /&gt;
=== Transaction Demarcation ===&lt;br /&gt;
&lt;br /&gt;
For the oft-repeated &amp;quot;get_session(); with session.begin(subtransactions=True)&amp;quot; phrase and other variants, I'd like to move that into something more abstracted:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def my_api_thing(x):&lt;br /&gt;
    with transactional() as session:&lt;br /&gt;
    	# do stuff&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I want context managers and/or decorators that are oslo.db specific to replace any kind of containing code that deals with transactions or sessions. That way the containinment logic can be maintained in once place and configured separately from the business logic contained within.  Projects like Keystone are already using a form more like this; it would be nice to nail down a consistent pattern and get it into oslo.db for everyone to use.&lt;br /&gt;
&lt;br /&gt;
=== MySQLdb + eventlet = sad ===&lt;br /&gt;
&lt;br /&gt;
This is the basic thing that happens with these two actors.   We're in an eventlet &amp;quot;thread&amp;quot;.   The code uses MySQLdb to do something ordinary that locks a row in a table - this can happen with a SELECT FOR UPDATE but it really can happen just on any old UPDATE too depending on isolation level (see https://review.openstack.org/#/c/104111/).   If the eventlet &amp;quot;thread&amp;quot; then does *anything* that causes it to defer out, another eventlet thread might run, and if that thread tries to work with MySQLdb and lock the same row, you're done, it's a deadlock.   The simple reason is, MySQLdb doesn't integrate with eventlet, so a simple thing like blocking on a transactional lock, it doesn't defer.   MySQL times out on these so we're getting exceptions instead of permanent freezes, but IMHO this is a really silly problem.  Eventlet and MySQLdb do not work together, we're done.   MySQLdb 1000% either has to go, or be fixed, even if that means forking it.    A good blog post that illustrates both scenarios is [http://www.diamondtin.com/2014/sqlalchemy-gevent-mysql-python-drivers-comparison/ here] - in this post, the author has used &amp;quot;greenify&amp;quot; to patch MySQLdb's blocking, but without this change (which I'm told isn't compatible with eventlet?), MySQLdb is a nonstarter.    Because it is in pure C, it is faster than MySQL-connector when it's been patched, but if it hasn't, it's dog slow.  MySQL-connector IMHO is probably the driver we should get behind [http://dev.mysql.com/doc/connector-python/en/index.html because it's the official MySQL driver]. The various workarounds and hacks (which I guess is why this is under &amp;quot;ORM patterns&amp;quot;) I've seen proposed are all technically viable but IMHO totally unnecessary.   OpenStack should be serious about this, it's a totally solvable problem, let's just solve it.&lt;br /&gt;
&lt;br /&gt;
=== Exception Rewriting ===&lt;br /&gt;
&lt;br /&gt;
Exception rewriting - I notice that the oslo.db pattern includes that DBAPI level exceptions are re-thrown as OpenStack specific;  I'd like to move this into handling using SQLAlchemy events.  The [http://docs.sqlalchemy.org/en/rel_0_9/core/events.html#sqlalchemy.events.ConnectionEvents.dbapi_error dbapi_error] event goes all the way back to 0.7, and [https://bitbucket.org/zzzeek/sqlalchemy/issue/3076/support-reraise-of-custom-exceptions ticket 3076] will allow it to be used to reraise with a new exception.  For older SQLAlchemy versions, I can create a wrapper that will allow it to be used in this way safely for older versions, despite the warnings stated on that document.&lt;br /&gt;
&lt;br /&gt;
* blueprint: https://review.openstack.org/#/c/105306/&lt;br /&gt;
&lt;br /&gt;
=== Deleted Object Selecting ===&lt;br /&gt;
&lt;br /&gt;
There's a need to be able to run queries that automatically de-select for certain rows, particularly rows that have a &amp;quot;deleted&amp;quot; flag set, or that otherwise include various identifiers that are given for a context.  Right now oslo.db has a function `model_query()` for this purpose.   I'd like to streamline `model_query()` and allow for a more flexible pattern, where instead of plainly adding WHERE clauses to a SELECT against a primary table, we can use rules to produce a SQL transformation that will apply the desired criteria to all &amp;quot;FROM&amp;quot; clauses at the point at which they are named, even within a more complex, composed query.   This will allow `model_query()` to be more flexible and ultimately to allow its behavior to be more transparently integrated into the app.&lt;br /&gt;
&lt;br /&gt;
=== Pessimistic Locking - SELECT FOR UPDATE ===&lt;br /&gt;
&lt;br /&gt;
This issue has been coming up for some projects, that they make sometimes great use of SQLAlchemy's &amp;lt;code&amp;gt;with_lockmode()&amp;lt;/code&amp;gt; method, which ultimately emits SELECT..FOR UPDATE, so that selected rows are aggressively locked within a transaction.   The issue is that backends like Galera don't support FOR UPDATE, meaning the locking that is desired is not actually provided.&lt;br /&gt;
&lt;br /&gt;
While I'm not sure that there's a generalized solution to this, in some cases I've been helping developers move to more of an &amp;quot;optimistic&amp;quot; approach, where a particular UPDATE statement targets some kind of timestamp or value counter in the row, then counts the number of rows matched.  If the number of rows matched is not what's expected, it is assumed that the UPDATE failed to match the row, and the operation can either try again or can bail out.   This approach might not be suitable in all cases.  In cases where a large stream of data is affecting a relatively small set of rows, it can perhaps be feasible to serialize those updates by sending them all to a queue, so that they are invoked in isolation without the need for up-front locking.   For very high throughput situations, queues can be constructed to use multiple, concurrent UPDATE streams separated via hashing, or can batch together updates for particular rows over a period of time and emit fewer UPDATE statements than if each source of data did it individually.&lt;br /&gt;
&lt;br /&gt;
=== Encouraging Bundle / Baked Query Use ===&lt;br /&gt;
&lt;br /&gt;
Encouraging Bundle use - as stated earlier, I want to look into bringing the very efficient [http://docs.sqlalchemy.org/en/rel_0_9/orm/mapper_config.html#column-bundles Column Bundle] pattern into the fold for mainstream use, not to mention the baked query pattern illustrated previously.&lt;br /&gt;
&lt;br /&gt;
=== Transparent Retries of Transactions ===&lt;br /&gt;
&lt;br /&gt;
I have ready to go an [https://bitbucket.org/zzzeek/sqlalchemy/issue/3104/transaction-replay-extension already in production extension for transaction replaying], developed for a client with the agreement that the extension is open source.  This extension installs as an event handler using a new series of hooks that were added in 0.9, or alternatively using a series of compatibility hooks that were backported to 0.7 and 0.8.   This extension allows logging of SQL statements as they proceed within a transaction, and allows them to be transparently replayed when a database disconnect error occurs. This means that SQLAlchemy's usual behavior of raising an exception and forcing you to re-try explicitly can be hidden so that it occurs implicitly, without any interruption or special error handling required in application code.  The extension includes a lot of configuration options, such as what statements should be replayed and which shouldn't, as well as what exceptions should be handled this way.   Transaction replaying is an elaborate feature which might introduce lots of new edge cases, namely that the state of the database may have changed since the last transaction failed, so to that extent I'm a little wary of this feature myself.  However I have seen lots of messages alluding to this kind of thing.    The mechanics to pull this into SQLAlchemy as a transparent feature are ready to go, if this is a feature people are looking for (I'm not sure if people are looking for this).&lt;br /&gt;
&lt;br /&gt;
== Contributors ==&lt;br /&gt;
&lt;br /&gt;
I would be remiss if I didn't mention that SQLAlchemy, Alembic, Dogpile and everything else I do are collaborative projects, and while SQLAlchemy in particular might present a daunting codebase, we would love to get new collaborators on board!   Any of the features above are fair game and I can walk someone through the steps to be taken, and additionally everything we add features for always needs lots of tests and review, if you're looking for a way to get into the project I can bring you right in, there's lots of (often very tedious and boring) work to be done.   I'm on all the IRC channels now as zzzeek so ping away.&lt;br /&gt;
&lt;br /&gt;
== Coffee and Cake ==&lt;br /&gt;
&lt;br /&gt;
So! I hope you've enjoyed reading this document, and whether you're a fan or a hater, please know that I'm on board now and I'm here to help, with whatever database kind of thing you want to do - it's all part of my job now.    If we wanted to use/build a whole new ORM, I'd be doing that too right alongside.  OpenStack's lessons in database integration are now my lessons too.  So let me know where I should dig in first!  Have a save drive home.&lt;/div&gt;</summary>
		<author><name>Mike Bayer</name></author>	</entry>

	<entry>
		<id>https://wiki.openstack.org/w/index.php?title=OpenStack_and_SQLAlchemy&amp;diff=57889</id>
		<title>OpenStack and SQLAlchemy</title>
		<link rel="alternate" type="text/html" href="https://wiki.openstack.org/w/index.php?title=OpenStack_and_SQLAlchemy&amp;diff=57889"/>
				<updated>2014-07-10T16:57:29Z</updated>
		
		<summary type="html">&lt;p&gt;Mike Bayer: /* Exception Rewriting */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= OpenStack and SQLAlchemy =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
This page describes the ongoing efforts of Mike Bayer, creator/maintainer of&lt;br /&gt;
SQLAlchemy, Alembic Migrations, and Dogpile caching, to spearhead what is&lt;br /&gt;
essentially an &amp;quot;OpenStack SQLAlchemy 2.0&amp;quot; effort - as I am now full-time&lt;br /&gt;
employed by an OpenStack vendor (Red Hat), I've been hired to produce a strong&lt;br /&gt;
and sustained pass through OpenStack's relational database integration,&lt;br /&gt;
improving areas that have been problematic and establishing a very strong&lt;br /&gt;
set of practices that are specific to OpenStack's needs.&lt;br /&gt;
&lt;br /&gt;
Within this effort, I'd like to stress that from my POV, everything is&lt;br /&gt;
on the table, including new improvements / fixes / features to&lt;br /&gt;
SQLAlchemy and Alembic directly, migration of projects for which&lt;br /&gt;
relational databases aren't suitable to non-relational systems,&lt;br /&gt;
improvement of relational schemas on the OpenStack side, and of course&lt;br /&gt;
improvement of exising relational patterns within OpenStack&lt;br /&gt;
applications, for which oslo.db will serve as the foundation.&lt;br /&gt;
&lt;br /&gt;
As of June 30, 2014, I've been on the job for about a month, so below&lt;br /&gt;
is an outline of most of what I've come across and my thoughts going forward.&lt;br /&gt;
&lt;br /&gt;
My goal here is to share with the community what I've seen and done so far,&lt;br /&gt;
so that interested parties can chime in with further advice and information,&lt;br /&gt;
and most importantly to help steer me towards the initial blueprints that&lt;br /&gt;
I should be starting on, that is, which features are most likely to be&lt;br /&gt;
accepted by the community as well as which are the top priority.  Not to mention, if you're looking&lt;br /&gt;
for something to do, you can help me implement some of these things!&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== OpenStack presence within SQLAlchemy's CI environment (a.k.a. &amp;quot;The Version that shall not be Named&amp;quot;) ==&lt;br /&gt;
&lt;br /&gt;
On Monday, June 23, I got up in the AM with two goals: get a release of SQLAlchemy&lt;br /&gt;
out the door, as it had been several months since the last point release, and then&lt;br /&gt;
do some work learning how Nova's unit tests run and function.&lt;br /&gt;
&lt;br /&gt;
Release 0.9.5 of SQLAlchemy was not particularly different than any of the other&lt;br /&gt;
hundred or so point releases I've put out over nine years, it was a little more overdue&lt;br /&gt;
than some, and out it went.  Later that day, as I was learning to run Nova's test suite, I&lt;br /&gt;
was hitting some failures within the FixedIp/FloatingIp tests for which I couldn't&lt;br /&gt;
find the cause, assuming that I just wasn't setting up my environment correctly,&lt;br /&gt;
as Nova's tests are passing for all the Jenkins servers the vast majority of the time.&lt;br /&gt;
&lt;br /&gt;
As we learned within about 30 minutes of my hitting these problems, it&lt;br /&gt;
turns out 0.9.5 had a bad fix in it; one that didn't fail any of&lt;br /&gt;
SQLAlchemy's 5000 tests but hit a use case within Nova that was not&lt;br /&gt;
very unusual; not the kind of use I usually do myself but nonetheless&lt;br /&gt;
something that was pretty common.   IRC began  jumping that builds&lt;br /&gt;
were failing everywhere and it was due to the 0.9.5 release!   The&lt;br /&gt;
bizarre coiincidence of this was that none of the thirty-odd point&lt;br /&gt;
releases of SQLAlchemy released under OpenStack's lifetime have ever&lt;br /&gt;
really had this effect, only the major jumps saw any issues.&lt;br /&gt;
&lt;br /&gt;
Luckily, as I had just learned 30 minutes earlier how to run Nova's tests I was&lt;br /&gt;
able to drill down to exactly what was going on and SQLAlchemy 0.9.6 was out&lt;br /&gt;
within an hour, but we shall now forever see in requirements.txt, the Version&lt;br /&gt;
that shall Not Be Named (except in requirements.txt):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
SQLAlchemy&amp;gt;=0.7.8,!=0.9.5,&amp;lt;=0.9.99&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In response to this I have now&lt;br /&gt;
[http://jenkins.sqlalchemy.org/job/openstack_sqla_master/ added openstack builds to our Jenkins CI system],&lt;br /&gt;
so far running tests for oslo.db and the SQLAlchemy tests within Nova; I spent&lt;br /&gt;
a couple of days figuring out how to get everything to build and run against&lt;br /&gt;
specific versions of SQLAlchemy and Alembic, even versions above the 0.9.99 in&lt;br /&gt;
all the requirements files as we are set for 1.0 hopefully within the next year.&lt;br /&gt;
I will be adding in as many other OpenStack projects as I can here so that we will no&lt;br /&gt;
longer have a SQLAlchemy release that hasn't been run against a battery of&lt;br /&gt;
basic OpenStack tests.&lt;br /&gt;
&lt;br /&gt;
== ORM Woes ==&lt;br /&gt;
&lt;br /&gt;
After chatting with a decent amount of folks for a month, I can break up&lt;br /&gt;
the take on OpenStack / SQLAlchemy ORM into three camps:&lt;br /&gt;
&lt;br /&gt;
1. We love SQLAlchemy ORM, we know that we can improve how we use it and we're looking forward to getting there (oslo.db, Keystone)&lt;br /&gt;
&lt;br /&gt;
2. Our app uses a relational database but the SQLAlchemy ORM slows us down, we already have our own &amp;quot;orm-like&amp;quot; system (e.g. APIs that shuttle rows into &amp;quot;values&amp;quot; dictionaries, nova.objects, etc.) so we want to ultimately get rid of the ORM part, and probably continue using SQLAlchemy Core to some degree (some Nova developers I've spoken to).&lt;br /&gt;
&lt;br /&gt;
3. Our application has a SQLAlchemy backend that almost nobody uses in production, most users use MongoDB, and the SQLAlchemy backend is only there because MongoDB is AGPL (Ceilometer, Marconi).  We'd like to get a better NoSQL backend without Mongo's licensing issue and do away with relational storage entirely ([http://lists.openstack.org/pipermail/openstack-dev/2014-June/038778.html Marconi]).&lt;br /&gt;
&lt;br /&gt;
Here's my observations / thoughts about these three camps:&lt;br /&gt;
&lt;br /&gt;
1. Great!  Read on, I have some things we can start with.&lt;br /&gt;
&lt;br /&gt;
2. I agree that a lot of the patterns that I've seen in Nova in particular are not well-suited to the approach used by SQLAlchemy's ORM, however I have observed that the applications still rely a lot on very sophisticated and mature features provided by SQLAlchemy (namely &amp;lt;code&amp;gt;relationship()&amp;lt;/code&amp;gt; and it's integration with eager loading and the unit of work features), and throwing it away entirely will incur a lot of reinvention, not just of the features that were lost but also of a totally new crop of stability and performance issues to solve.&lt;br /&gt;
&lt;br /&gt;
To that degree, I'd like to propose a twist to the &amp;quot;we want to write our own ORM&amp;quot; idea (edit: OK nova.objects is really an ORM-like thing on top of their API on top of relational DB, but that's an issue for another day) and say, &amp;quot;let's write our own ORM that's based on the SQLAlchemy ORM primitives&amp;quot;, e.g. a hybrid approach where existing ORM logic that is fine can stay that way, ORM logic that has performance or behavioral issues can be reorganized using systems, some of them new, some of them just underutilized, that bypass unneeded SQLAlchemy features on a case-by-case basis.&lt;br /&gt;
&lt;br /&gt;
As a secondary issue, some devs I've spoken to have referred to the nature&lt;br /&gt;
of OpenStack API-based applications being a part of the problem.&lt;br /&gt;
This refers to the fact that OpenStack apps like Nova&lt;br /&gt;
and Neutron expose fine-grained APIs which end up&lt;br /&gt;
breaking out composed use cases into dozens or hundreds of small API&lt;br /&gt;
operations, each of which invokes within it's own database transaction and&lt;br /&gt;
ORM session state.  This makes it impossible, without introducing some elaborate&lt;br /&gt;
offline state management system, for a larger operation to be&lt;br /&gt;
performed so that larger groups of objects can be loaded and manipulated&lt;br /&gt;
at once, greatly decreasing the number of database round trips present.&lt;br /&gt;
No matter what ORM you use or don't use, the pattern of many small round trips&lt;br /&gt;
is difficult to scale on relational databases.&lt;br /&gt;
&lt;br /&gt;
There are under way projects which aim to apply detailed profiling&lt;br /&gt;
to OpenStack applications so that a breakdown of performance issues due&lt;br /&gt;
to API use and/or database&lt;br /&gt;
access might be plainly reasoned about; see [https://github.com/stackforge/rally Rally]&lt;br /&gt;
and [https://github.com/stackforge/osprofiler OS Profiler] for examples.&lt;br /&gt;
&lt;br /&gt;
On my end, I have no intention of focusing on this second issue for the time&lt;br /&gt;
being, I'm going to stick with leaving OS applications as much as they are right now as possible,&lt;br /&gt;
applying iterative changes to database access code which I hope can optimize&lt;br /&gt;
to a significant degree within the current usage patterns.&lt;br /&gt;
&lt;br /&gt;
3. Projects with alternate MongoDB / MySQL backends (since after all, MySQL is the database here being rejected, SQLAlchemy is just a Python API) should probably do what they're doing and go to an all key/value model.  It is not at all fun to maintain the same datamodel and API against both a KV store as well as relational tables.  I had a detailed look at Ceilometer with its team members early on, and while I'm pretty confident that if it were desireable, I could get it's relational backend to compete with the Mongo backend performance-wise, there's no reason to get into this if the Mongo licensing issue can be solved alone.&lt;br /&gt;
&lt;br /&gt;
== ORM Quick Wins Proof of Concept ==&lt;br /&gt;
&lt;br /&gt;
I've built up a proof of concept at [https://github.com/zzzeek/nova_poc nova_poc] which seeks to demonstrate&lt;br /&gt;
the range of database and ORM performance that can be achieved within&lt;br /&gt;
one particular method of the Nova API.   Naturally, I picked an example that&lt;br /&gt;
is very juicy in this regard; it has a very easily fixable issue with the&lt;br /&gt;
kind of query it emits that has an enormous 10x impact on its performance&lt;br /&gt;
(which I feel is worth illustrating just for how dramatic it is),&lt;br /&gt;
and it then illustrates some new SQLAlchemy extensions that more or less&lt;br /&gt;
may be going into SQLAlchemy directly, which allow the remaining operation&lt;br /&gt;
to complete in less than half the time.  These extensions can be ported to&lt;br /&gt;
oslo.db to work against all supported SQLAlchemy versions prior to the official&lt;br /&gt;
release of these features.&lt;br /&gt;
&lt;br /&gt;
To summarize, we're looking at the API method &amp;lt;code&amp;gt;floating_ip_update()&amp;lt;/code&amp;gt;, and&lt;br /&gt;
I run a series of profiles against a database table 10K rows (e.g. &amp;lt;code&amp;gt;FloatingIp&amp;lt;/code&amp;gt;&lt;br /&gt;
objects), where each &amp;lt;code&amp;gt;FloatingIp&amp;lt;/code&amp;gt; refers to a single &amp;lt;code&amp;gt;FixedIp&amp;lt;/code&amp;gt; that then&lt;br /&gt;
refers to an &amp;lt;code&amp;gt;Instance&amp;lt;/code&amp;gt;.   The profiling focuses on the nature of this&lt;br /&gt;
method as an API feature all it's own, and simulates the case where the API&lt;br /&gt;
method is called thousands of times, not unlike other API methods that seek&lt;br /&gt;
to add some small amount of data each time they are called.&lt;br /&gt;
&lt;br /&gt;
=== Eager load and Column load tuning ===&lt;br /&gt;
&lt;br /&gt;
Turning off an eagerload in the &amp;lt;code&amp;gt;_floating_ip_get_by_address()&amp;lt;/code&amp;gt; function&lt;br /&gt;
is the most dramatic win; while removing this method seems like a reasonable&lt;br /&gt;
fix to remove some unneeded overhead, in profiling it shows itself to&lt;br /&gt;
be enormous:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Running scenario default&lt;br /&gt;
Scenario default, total calls for 1000 operations: 24543047&lt;br /&gt;
Scenario default, total time for 10000 operations: 222&lt;br /&gt;
&lt;br /&gt;
Running scenario default_optimized&lt;br /&gt;
Scenario default_optimized, total calls for 1000 operations: 1881626&lt;br /&gt;
Scenario default_optimized, total time for 10000 operations: 25&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
While not illustrated in the POC code itself, talk with various OpenStack&lt;br /&gt;
devs has revealed that we could probably do a lot more individual column&lt;br /&gt;
fetching as well, that is instead of:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
session.query(FixedIp).all()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We say:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
session.query(FixedIp.address, FixedIp.host, FixedIp.reserved).all()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
By loading indivdual columns, we get read-only objects that are not identity&lt;br /&gt;
managed, bypassing a huge amount of work that is done on instances both&lt;br /&gt;
in instantiating them, populating relationships and doing bookkeeping.&lt;br /&gt;
Any API feature that only returns a simple read-only object or a &amp;quot;values&amp;quot;&lt;br /&gt;
dictionary that does not rely upon relationship loading should be using&lt;br /&gt;
this pattern, or similar, by using a&lt;br /&gt;
[http://docs.sqlalchemy.org/en/rel_0_9/orm/mapper_config.html#column-bundles Column Bundle].&lt;br /&gt;
I will be exploring adding the following feature to oslo.db:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
session.query(FixedIp.bundle).all()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The above will return a fast-loading, read-only Bundle object that otherwise&lt;br /&gt;
expresses the full interface of &amp;lt;code&amp;gt;FixedIp&amp;lt;/code&amp;gt;.   Relationships might be tricky&lt;br /&gt;
here but I can at least get them on board as regular lazy loads (just like&lt;br /&gt;
any other ORM would do anyway).&lt;br /&gt;
&lt;br /&gt;
=== Fast Object Save ===&lt;br /&gt;
&lt;br /&gt;
We explore replacing the unit of work &amp;lt;code&amp;gt;flush()&amp;lt;/code&amp;gt; call used by &amp;lt;code&amp;gt;object.save()&amp;lt;/code&amp;gt; within oslo.db-style applications with a more direct &amp;quot;flush single object&amp;quot; call; this grants us only a 12% savings so far, though it is very simple to use:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Running scenario default_optimized&lt;br /&gt;
Scenario default_optimized, total calls for 1000 operations: 1881626&lt;br /&gt;
Scenario default_optimized, total time for 10000 operations: 25&lt;br /&gt;
&lt;br /&gt;
Running scenario fast_save&lt;br /&gt;
Scenario fast_save, total calls for 1000 operations: 1685221&lt;br /&gt;
Scenario fast_save, total time for 10000 operations: 22&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Baked Queries ===&lt;br /&gt;
&lt;br /&gt;
Something that has been in the works for a long time and has&lt;br /&gt;
recently    seen lots of work in the past months is the &amp;quot;baked query&amp;quot;&lt;br /&gt;
feature; this    pattern is ideal for OpenStack's &amp;quot;many short queries&amp;quot;&lt;br /&gt;
pattern, and allows    caching of the generation of SQL.  Recent&lt;br /&gt;
versions of this pattern have    gotten very slick, and can cache&lt;br /&gt;
virtually everything that happens Python-wise    from the construction&lt;br /&gt;
of the &amp;lt;code&amp;gt;Query&amp;lt;/code&amp;gt; object, to calling all the methods    on the&lt;br /&gt;
query, to the query-objects construction of a Core SQL statement,&lt;br /&gt;
to the compilation of that statement as a string - all of these steps&lt;br /&gt;
are removed from the call-graph after the first such call.  In&lt;br /&gt;
SQLAlchemy 1.0    I've also thrown in the construction of column&lt;br /&gt;
metadata from the result set    too.   The pattern involves a bit more&lt;br /&gt;
verbosity to that of constructing a    query, where here I've built&lt;br /&gt;
off of some of the ideas of the    Pony ORM to use Python function&lt;br /&gt;
information as the source of a cache key.    A query such as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
result = model_query(&lt;br /&gt;
            context, models.FloatingIp, session=session).\&lt;br /&gt;
            filter_by(address=address)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
would be expressed in &amp;quot;baked&amp;quot; form as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# note model_query is using the &amp;quot;baked&amp;quot; process internally as well&lt;br /&gt;
result = model_query(context, models.FloatingIp, session=session)&lt;br /&gt;
&lt;br /&gt;
result.bake(lambda query:&lt;br /&gt;
    query.filter_by(&lt;br /&gt;
        address=bindparam('address'))).params(address=address)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the above form, everything within each lambda is invoked only once,&lt;br /&gt;
the result of which becomes part of a cached value.&lt;br /&gt;
&lt;br /&gt;
For this slight increase in verbosity, we get an improvement like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Running scenario default_optimized&lt;br /&gt;
Scenario default_optimized, total calls for 1000 operations: 1881626&lt;br /&gt;
Scenario default_optimized, total time for 10000 operations: 25&lt;br /&gt;
&lt;br /&gt;
Running scenario baked&lt;br /&gt;
Scenario baked, total calls for 1000 operations: 1052935&lt;br /&gt;
Scenario baked, total time for 10000 operations: 16&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
That is, around a 40% improvement.&lt;br /&gt;
&lt;br /&gt;
Putting together both &amp;quot;fast save&amp;quot; plus &amp;quot;baked&amp;quot; we get down to a full 50%&lt;br /&gt;
improvement vs. the plain optimized version:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Running scenario fast_save_plus_baked&lt;br /&gt;
Scenario fast_save_plus_baked, total calls for 1000 operations: 856035&lt;br /&gt;
Scenario fast_save_plus_baked, total time for 10000 operations: 13&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
13 seconds for 10K operations vs. 222 seconds in the current code, I hope&lt;br /&gt;
I've established that there is hope!&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Migrations ==&lt;br /&gt;
&lt;br /&gt;
Migrations are a huge deal.  Here's where it looks like this is going.&lt;br /&gt;
&lt;br /&gt;
Alembic - &amp;quot;Alembic is the defacto standard going forward&amp;quot; - ultimately&lt;br /&gt;
we want to get everything SQLAlchemy-Migrate onto Alembic.  And in talking&lt;br /&gt;
with Nova devs, they really like that they can test their migrations against&lt;br /&gt;
SQLite.  So from my POV, SQLite migrations on Alembic, we're doing that!&lt;br /&gt;
Unless folks think otherwise.&lt;br /&gt;
&lt;br /&gt;
=== SQLite Support ===&lt;br /&gt;
&lt;br /&gt;
SQLite has extremely limited support for ALTER TABLE, essentially only being able&lt;br /&gt;
to add new columns, and that's about it.  In order to provide the full suite&lt;br /&gt;
of ALTER operations that all other databases provide, tools such as&lt;br /&gt;
SQLAlchemy-Migrate will create a copy of the target table with changes applied,&lt;br /&gt;
copy data from old table to new, then drop the old table and rename the new one.&lt;br /&gt;
I've always been peeved at SQLite's insistence on never providing some better&lt;br /&gt;
pathway to ALTER built in.  But it has been on the Alembic roadmap to add SQLite migrations&lt;br /&gt;
in a style similar to that of Migrate for a long time, emulating this same&lt;br /&gt;
approach in some way.  Alembic will have this!&lt;br /&gt;
&lt;br /&gt;
However in talking with some folks, it appears like some people might actually&lt;br /&gt;
like this &amp;quot;make a new table and switch it&amp;quot; approach for other databases too,&lt;br /&gt;
as a way to work around locking of tables.  I don't know that I agree with this&lt;br /&gt;
long term, the &amp;quot;ALTER locks the database!&amp;quot; issue in my experience has always&lt;br /&gt;
been kind of a MySQL problem alone, but it can be present for other backends&lt;br /&gt;
as well.&lt;br /&gt;
&lt;br /&gt;
So the API I'm looking towards here is going to try to solve a few problems;&lt;br /&gt;
one is that, copying the whole table to a new one each time for *every single&lt;br /&gt;
column* is crazy, and also that the technique we use on SQLite might be nice&lt;br /&gt;
for other databases too, so here's the API:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
with op.batch_alter(&amp;quot;mytable&amp;quot;, recreate=True) as batch_op:&lt;br /&gt;
    batch_op.drop_column(&amp;quot;some_column&amp;quot;)&lt;br /&gt;
    batch_op.alter_column(&amp;quot;q&amp;quot;, type=Integer)&lt;br /&gt;
    batch_op.add_column(Column('y', String(50), nullable=False))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
That is, we assign a whole series of column operations under the umbrella of&lt;br /&gt;
one table, and they are created at once.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;batch_alter()&amp;lt;/code&amp;gt; call can even be conditional, dropping into traditional&lt;br /&gt;
migration behavior for all backends except SQLite and whatever else:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
with op.batch_alter(&amp;quot;mytable&amp;quot;, recreate_on=('sqlite',)) as batch_op:&lt;br /&gt;
    batch_op.drop_column(&amp;quot;some_column&amp;quot;)&lt;br /&gt;
    batch_op.alter_column(&amp;quot;q&amp;quot;, type=Integer)&lt;br /&gt;
    batch_op.add_column(Column('y', String(50), nullable=False))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
SQlite-compatible alterations is [https://bitbucket.org/zzzeek/alembic/issue/21/column-renames-not-supported-on-sqlite issue #21].&lt;br /&gt;
&lt;br /&gt;
=== Finishing up Autogenerate ===&lt;br /&gt;
&lt;br /&gt;
I started writing Alembic long before SQLAlchemy-Migrate had autogenerate.  But by the time&lt;br /&gt;
I got Alembic released and later autogenerate working, Migrate already had it.&lt;br /&gt;
Here's what we need to finish up in autogenerate:&lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;code&amp;gt;ForeignKeyConstraint&amp;lt;/code&amp;gt; change detection - this is an entirely straightforward feature add and has long been on the todo list.&lt;br /&gt;
&lt;br /&gt;
2. MySQL implicit indexes - this is actually done; recent versions of Alembic can navigate around MySQLs goofy production of indexes automatically on foreign key columns, and not accidentally spit them out in autogenerates. It was seriously tough to get index autogen mostly working on all backends, so new issues will continue to be fixed as they are reported.&lt;br /&gt;
&lt;br /&gt;
3. Type comparison - Alembic balks on type comparison by default, because ultimately SQLAlchemy should add comparision features to its type objects natively. However, on Alembic backends like that of MySQL, we can add explicit rules for the very common and annoying ones, namely the detection of MySQL's TINYINT as a SQLAlchemy Boolean.  Alembic already allows user-specified rules in this regard, so they can be part of oslo.db in any case, however Alembic can also add this rule and others in directly.&lt;br /&gt;
&lt;br /&gt;
4. Table order - Alembic's autogenerate should be spitting out table creates and drops in the order of foreign key dependency, though we don't have good test coverage for this yet and it might not be working.  We'll fix that, no biggie!  Alternatively, Alembic could include a mode of operation that includes all the ForeignKeyConstraint objects after all the tables. Input on how we'd like to see this work would be welcome.&lt;br /&gt;
&lt;br /&gt;
=== New Migration Features ===&lt;br /&gt;
&lt;br /&gt;
There's a lot of features I'd like to add to Alembic, and if OpenStack has&lt;br /&gt;
a need for them, that would justify the effort:&lt;br /&gt;
&lt;br /&gt;
1. Full branch support - [https://bitbucket.org/zzzeek/alembic/issue/167/multiple-heads-branch-resolution-support issue 167 multiple heads / branch resolution support] has a detailed architecture laid out for this feature.  Lots of people are looking for it. This would turn Alembic's current &amp;quot;linked list&amp;quot; version model into a full directed acyclic graph (DAG).   Any particular migration can be dependent on any other group of migrations, or none at all; individual branches can be maintained and upgraded along their path, or automatically merged.&lt;br /&gt;
&lt;br /&gt;
2. [https://bitbucket.org/zzzeek/alembic/issue/124/multiple-versions-directories Multiple version directories] - This would allow migration files to be present in more than one place for a single migrations environment.  Currently, you can get this approach by using multiple base directories, but that requires separate &amp;lt;code&amp;gt;env.py&amp;lt;/code&amp;gt; files for each. With this issue, cross-dependent migration files can live in multiple places, working nicely with the multiple heads support of #167.&lt;br /&gt;
&lt;br /&gt;
== Testing ==&lt;br /&gt;
&lt;br /&gt;
I spent quite a bit looking into testing.  The main thing we can see&lt;br /&gt;
in testing is that there may be a desire for &amp;quot;transactional testing&amp;quot;;&lt;br /&gt;
run tests within a fixed schema, that is the schema    is created&lt;br /&gt;
once, many tests are run, each one rolling back their work, the&lt;br /&gt;
schema is only dropped once all tests for that schema are complete.&lt;br /&gt;
&lt;br /&gt;
Transactional testing is described in the SQLAlchemy docs and in some of&lt;br /&gt;
my talks; for OpenStack, we need the version that supports graceful handling&lt;br /&gt;
of tests that actually do a rollback.  This is doable and the&lt;br /&gt;
recipe for that is currently [https://gist.github.com/zzzeek/8443477 a github gist].&lt;br /&gt;
&lt;br /&gt;
In order to accommodate parallelized runs, the system will create N&lt;br /&gt;
temporary schemas for N concurrent runs.&lt;br /&gt;
&lt;br /&gt;
I really want to get into this feature but as I talk to people, I'm&lt;br /&gt;
getting mixed signals on how important this feature is.   So far I've&lt;br /&gt;
only worked with unit tests, which seem to be entirely against SQLite&lt;br /&gt;
in-memory databases, and I haven't gotten my head around to what&lt;br /&gt;
extent other databases besides SQLite might be desireable in unit&lt;br /&gt;
tests, nor have I gotten to fully understand how integration tests&lt;br /&gt;
run, so input is welcome here.&lt;br /&gt;
&lt;br /&gt;
== Improvement of ORM Patterns ==&lt;br /&gt;
&lt;br /&gt;
Looking around oslo.db and other apps, I want to improve upon&lt;br /&gt;
some kinds of verbosity I see, which make the code more repetetive and inconsistent, welds together application-level and business level concepts (e.g. transaction control and exception reporting melded with data manipulation) and makes it harder to reason about how the application interacts with sessions, as there are a variety of ad-hoc patterns present.&lt;br /&gt;
&lt;br /&gt;
=== Transaction Demarcation ===&lt;br /&gt;
&lt;br /&gt;
For the oft-repeated &amp;quot;get_session(); with session.begin(subtransactions=True)&amp;quot; phrase and other variants, I'd like to move that into something more abstracted:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def my_api_thing(x):&lt;br /&gt;
    with transactional() as session:&lt;br /&gt;
    	# do stuff&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I want context managers and/or decorators that are oslo.db specific to replace any kind of containing code that deals with transactions or sessions. That way the containinment logic can be maintained in once place and configured separately from the business logic contained within.  Projects like Keystone are already using a form more like this; it would be nice to nail down a consistent pattern and get it into oslo.db for everyone to use.&lt;br /&gt;
&lt;br /&gt;
=== MySQLdb + eventlet = sad ===&lt;br /&gt;
&lt;br /&gt;
This is the basic thing that happens with these two actors.   We're in an eventlet &amp;quot;thread&amp;quot;.   The code uses MySQLdb to do something ordinary that locks a row in a table - this can happen with a SELECT FOR UPDATE but it really can happen just on any old UPDATE too depending on isolation level (see https://review.openstack.org/#/c/104111/).   If the eventlet &amp;quot;thread&amp;quot; then does *anything* that causes it to defer out, another eventlet thread might run, and if that thread tries to work with MySQLdb and lock the same row, you're done, it's a deadlock.   The simple reason is, MySQLdb doesn't integrate with eventlet, so a simple thing like blocking on a transactional lock, it doesn't defer.   MySQL times out on these so we're getting exceptions instead of permanent freezes, but IMHO this is a really silly problem.  Eventlet and MySQLdb do not work together, we're done.   MySQLdb 1000% either has to go, or be fixed, even if that means forking it.    A good blog post that illustrates both scenarios is [http://www.diamondtin.com/2014/sqlalchemy-gevent-mysql-python-drivers-comparison/ here] - in this post, the author has used &amp;quot;greenify&amp;quot; to patch MySQLdb's blocking, but without this change (which I'm told isn't compatible with eventlet?), MySQLdb is a nonstarter.    Because it is in pure C, it is faster than MySQL-connector when it's been patched, but if it hasn't, it's dog slow.  MySQL-connector IMHO is probably the driver we should get behind [http://dev.mysql.com/doc/connector-python/en/index.html because it's the official MySQL driver]. The various workarounds and hacks (which I guess is why this is under &amp;quot;ORM patterns&amp;quot;) I've seen proposed are all technically viable but IMHO totally unnecessary.   OpenStack should be serious about this, it's a totally solvable problem, let's just solve it.&lt;br /&gt;
&lt;br /&gt;
=== Exception Rewriting ===&lt;br /&gt;
&lt;br /&gt;
Exception rewriting - I notice that the oslo.db pattern includes that DBAPI level exceptions are re-thrown as OpenStack specific;  I'd like to move this into handling using SQLAlchemy events.  The [http://docs.sqlalchemy.org/en/rel_0_9/core/events.html#sqlalchemy.events.ConnectionEvents.dbapi_error dbapi_error] event goes all the way back to 0.7, and [https://bitbucket.org/zzzeek/sqlalchemy/issue/3076/support-reraise-of-custom-exceptions ticket 3076] will allow it to be used to reraise with a new exception.  For older SQLAlchemy versions, I can create a wrapper that will allow it to be used in this way safely for older versions, despite the warnings stated on that document.&lt;br /&gt;
&lt;br /&gt;
* blueprint: https://review.openstack.org/#/c/105306/&lt;br /&gt;
&lt;br /&gt;
=== Deleted Object Selecting ===&lt;br /&gt;
&lt;br /&gt;
There's a need to be able to run queries that automatically de-select for certain rows, particularly rows that have a &amp;quot;deleted&amp;quot; flag set, or that otherwise include various identifiers that are given for a context.  Right now oslo.db has a function `model_query()` for this purpose.   I'd like to streamline `model_query()` and allow for a more flexible pattern, where instead of plainly adding WHERE clauses to a SELECT against a primary table, we can use rules to produce a SQL transformation that will apply the desired criteria to all &amp;quot;FROM&amp;quot; clauses at the point at which they are named, even within a more complex, composed query.   This will allow `model_query()` to be more flexible and ultimately to allow its behavior to be more transparently integrated into the app.&lt;br /&gt;
&lt;br /&gt;
=== Pessimistic Locking - SELECT FOR UPDATE ===&lt;br /&gt;
&lt;br /&gt;
This issue has been coming up for some projects, that they make sometimes great use of SQLAlchemy's &amp;lt;code&amp;gt;with_lockmode()&amp;lt;/code&amp;gt; method, which ultimately emits SELECT..FOR UPDATE, so that selected rows are aggressively locked within a transaction.   The issue is that backends like Galera don't support FOR UPDATE, meaning the locking that is desired is not actually provided.&lt;br /&gt;
&lt;br /&gt;
While I'm not sure that there's a generalized solution to this, in some cases I've been helping developers move to more of an &amp;quot;optimistic&amp;quot; approach, where a particular UPDATE statement targets some kind of timestamp or value counter in the row, then counts the number of rows matched.  If the number of rows matched is not what's expected, it is assumed that the UPDATE failed to match the row, and the operation can either try again or can bail out.   This approach might not be suitable in all cases.  In cases where a large stream of data is affecting a relatively small set of rows, it can perhaps be feasible to serialize those updates by sending them all to a queue, so that they are invoked in isolation without the need for up-front locking.   For very high throughput situations, queues can be constructed to use multiple, concurrent UPDATE streams separated via hashing, or can batch together updates for particular rows over a period of time and emit fewer UPDATE statements than if each source of data did it individually.&lt;br /&gt;
&lt;br /&gt;
=== Encouraging Bundle / Baked Query Use ===&lt;br /&gt;
&lt;br /&gt;
Encouraging Bundle use - as stated earlier, I want to look into bringing the very efficient [http://docs.sqlalchemy.org/en/rel_0_9/orm/mapper_config.html#column-bundles Column Bundle] pattern into the fold for mainstream use, not to mention the baked query pattern illustrated previously.&lt;br /&gt;
&lt;br /&gt;
=== Transparent Retries of Transactions ===&lt;br /&gt;
&lt;br /&gt;
I have ready to go an [https://bitbucket.org/zzzeek/sqlalchemy/issue/3104/transaction-replay-extension already in production extension for transaction replaying], developed for a client with the agreement that the extension is open source.  This extension installs as an event handler using a new series of hooks that were added in 0.9, or alternatively using a series of compatibility hooks that were backported to 0.7 and 0.8.   This extension allows logging of SQL statements as they proceed within a transaction, and allows them to be transparently replayed when a database disconnect error occurs. This means that SQLAlchemy's usual behavior of raising an exception and forcing you to re-try explicitly can be hidden so that it occurs implicitly, without any interruption or special error handling required in application code.  The extension includes a lot of configuration options, such as what statements should be replayed and which shouldn't, as well as what exceptions should be handled this way.   Transaction replaying is an elaborate feature which might introduce lots of new edge cases, namely that the state of the database may have changed since the last transaction failed, so to that extent I'm a little wary of this feature myself.  However I have seen lots of messages alluding to this kind of thing.    The mechanics to pull this into SQLAlchemy as a transparent feature are ready to go, if this is a feature people are looking for (I'm not sure if people are looking for this).&lt;br /&gt;
&lt;br /&gt;
== Contributors ==&lt;br /&gt;
&lt;br /&gt;
I would be remiss if I didn't mention that SQLAlchemy, Alembic, Dogpile and everything else I do are collaborative projects, and while SQLAlchemy in particular might present a daunting codebase, we would love to get new collaborators on board!   Any of the features above are fair game and I can walk someone through the steps to be taken, and additionally everything we add features for always needs lots of tests and review, if you're looking for a way to get into the project I can bring you right in, there's lots of (often very tedious and boring) work to be done.   I'm on all the IRC channels now as zzzeek so ping away.&lt;br /&gt;
&lt;br /&gt;
== Coffee and Cake ==&lt;br /&gt;
&lt;br /&gt;
So! I hope you've enjoyed reading this document, and whether you're a fan or a hater, please know that I'm on board now and I'm here to help, with whatever database kind of thing you want to do - it's all part of my job now.    If we wanted to use/build a whole new ORM, I'd be doing that too right alongside.  OpenStack's lessons in database integration are now my lessons too.  So let me know where I should dig in first!  Have a save drive home.&lt;/div&gt;</summary>
		<author><name>Mike Bayer</name></author>	</entry>

	<entry>
		<id>https://wiki.openstack.org/w/index.php?title=Meetings/Oslo&amp;diff=57804</id>
		<title>Meetings/Oslo</title>
		<link rel="alternate" type="text/html" href="https://wiki.openstack.org/w/index.php?title=Meetings/Oslo&amp;diff=57804"/>
				<updated>2014-07-09T21:53:41Z</updated>
		
		<summary type="html">&lt;p&gt;Mike Bayer: /* Agenda for Next Meeting */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Oslo will hold IRC meetings weekly at the time scheduled below.&lt;br /&gt;
&lt;br /&gt;
If there's an Oslo topic you think warrants a project meeting, please add it to the agenda section below and notify the [http://lists.openstack.org/cgi-bin/mailman/listinfo/openstack-dev openstack-dev@lists.openstack.org] mailing list. Please give everyone at least 24 hours notice.&lt;br /&gt;
&lt;br /&gt;
== Agenda for Next Meeting ==&lt;br /&gt;
&lt;br /&gt;
''Please include your IRC handle along with any agenda items you add, so we know who to call on in the meeting.''&lt;br /&gt;
&lt;br /&gt;
Date: 11 July 2014&lt;br /&gt;
&lt;br /&gt;
* Review action items from previous meeting&lt;br /&gt;
** dhellmann clarify language about when to use _() vs. _LE() - done&lt;br /&gt;
** Project liaisons review app-agnostic-logging-parameters: https://review.openstack.org/95281 - merged&lt;br /&gt;
** Project liaisons review oslo-config-generator: https://review.openstack.org/100946 - merged&lt;br /&gt;
** dhellmann talk to markmc about releasing file encoding fix in fake driver in oslo.messaging - 1.4.0.0a2&lt;br /&gt;
* Report from [[Sprints/ParisJuno2014]]&lt;br /&gt;
* Red flags for/from liaisons&lt;br /&gt;
* Exception filtering proposal - feedback / suggestions (Mike Bayer)&lt;br /&gt;
** https://review.openstack.org/#/c/105306/&lt;br /&gt;
** https://review.openstack.org/#/c/105307/&lt;br /&gt;
** https://review.openstack.org/#/c/105891/&lt;br /&gt;
* Adoption status&lt;br /&gt;
** https://etherpad.openstack.org/p/juno-oslo-adoption-status&lt;br /&gt;
* Spec status&lt;br /&gt;
** oslo.utils - https://review.openstack.org/#/c/98431/ - merged&lt;br /&gt;
** rootwrap daemon mode - https://review.openstack.org/#/c/94613/ - merged&lt;br /&gt;
** taskflow conditional execution - https://review.openstack.org/#/c/98946/ - merged&lt;br /&gt;
* Graduation status&lt;br /&gt;
** oslo.utils - https://blueprints.launchpad.net/oslo/+spec/graduate-oslo-utils&lt;br /&gt;
** oslo.serialization - https://blueprints.launchpad.net/oslo/+spec/graduate-oslo-serialization&lt;br /&gt;
** oslo.concurrency - https://blueprints.launchpad.net/oslo/+spec/graduate-oslo-concurrency&lt;br /&gt;
* Release review&lt;br /&gt;
** oslo.i18n 0.1.0 released - https://etherpad.openstack.org/p/oslo-i18n-1.0.0&lt;br /&gt;
** oslosphinx 2.2.0.0a2 released - https://etherpad.openstack.org/p/oslosphinx-2.2.0&lt;br /&gt;
** pbr 0.9.0 released - http://lists.openstack.org/pipermail/openstack-dev/2014-July/039481.html&lt;br /&gt;
* review priorities for this week&lt;br /&gt;
** db migration test issues (devananda)&lt;br /&gt;
*** https://bugs.launchpad.net/ironic/+bug/1327397 fixed in nova and ironic, not oslo.&lt;br /&gt;
*** https://bugs.launchpad.net/nova/+bug/1328997 partial-fix in nova. incorrectly close in oslo.db.&lt;br /&gt;
&lt;br /&gt;
== General Information ==&lt;br /&gt;
=== Regular Meeting Schedule ===&lt;br /&gt;
* What day: Friday&lt;br /&gt;
* What time: [http://www.timeanddate.com/worldclock/converted.html?iso=20140425T16&amp;amp;p1=0&amp;amp;p2=2133&amp;amp;p3=195&amp;amp;p4=224&amp;amp;p5=43 1600 UTC]&lt;br /&gt;
* Where: #openstack-meeting-alt on freenode&lt;br /&gt;
* Who: All are welcome to participate&lt;br /&gt;
&lt;br /&gt;
=== Notes from Previous Meetings ===&lt;br /&gt;
&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-06-27-16.00.html Jun 27, 2014]&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-06-20-16.01.html Jun 20, 2014] - topics: oslo.db initial release; oslo.messaging good progress in neutron; alpha releases of 5 libraries next week; oslo.db test bugs reported by devananda&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-06-13-16.00.html Jun 13, 2014] - topics: oslo.db alpha release; db migration bug; &lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-06-06-16.00.html Jun 06, 2014] - topics: juno specs, spec approval process&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-05-30-16.00.html May 30, 2014]&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-05-23-16.01.html May 23, 2014] - topics: osprofile (postponed), run_test.sh, juno specs, oslo.test issue in tempest&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-05-09-16.02.html May 09, 2014] - topics: oslo-specs, oslo.messaging, summit prep, oslo.db, oslo.i18n&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-04-25-16.00.html April 24, 2014] - topics: oslotest, oslo.db, oslo.i18n, creating a specs repo&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-02-28-14.00.html Feb 28, 2014] - topics: icehouse feature freeze; syncing cinder &amp;amp; nova; uuidutils&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-02-14-14.01.html Feb 14, 2014] - topics: oslo.db, icehouse-3&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-01-31-14.01.html Jan 31, 2014] - topics: translation, deprecation policy, adopting taskflow, stevedore, and cliff&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2013/oslo.2013-11-15-14.01.html Nov 15, 2013] - topics: translation, pecan/wsme common code, icehouse scheduling&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2013/oslo.2013-10-25-14.00.html Oct 25, 2013] - topics: deprecated decorator and delayed translation implementation plan&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2013/oslo.2013-10-11-14.00.html Oct 11, 2013] - topics: delayed translations&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2013/oslo.2013-08-16-14.00.html Aug 16, 2013] - topic was new messaging API, message security and reject/reque/ack&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2013/oslo.2013-07-19-14.00.html July 19, 2013] - topic was new messaging API, message security, qpid/proton messaging driver and removing logging dependency on eventlet&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2013/oslo.2013-06-07-14.00.html June 7, 2013] - topic was new messaging API and message security&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2013/oslo.2013-05-03-14.01.html May 3, 2013] - topic was new messaging API and message security&lt;br /&gt;
&lt;br /&gt;
(In case the list of notes is not up to date, please consult http://eavesdrop.openstack.org/meetings/oslo/)&lt;/div&gt;</summary>
		<author><name>Mike Bayer</name></author>	</entry>

	<entry>
		<id>https://wiki.openstack.org/w/index.php?title=Meetings/Oslo&amp;diff=57785</id>
		<title>Meetings/Oslo</title>
		<link rel="alternate" type="text/html" href="https://wiki.openstack.org/w/index.php?title=Meetings/Oslo&amp;diff=57785"/>
				<updated>2014-07-09T18:26:40Z</updated>
		
		<summary type="html">&lt;p&gt;Mike Bayer: /* Agenda for Next Meeting */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Oslo will hold IRC meetings weekly at the time scheduled below.&lt;br /&gt;
&lt;br /&gt;
If there's an Oslo topic you think warrants a project meeting, please add it to the agenda section below and notify the [http://lists.openstack.org/cgi-bin/mailman/listinfo/openstack-dev openstack-dev@lists.openstack.org] mailing list. Please give everyone at least 24 hours notice.&lt;br /&gt;
&lt;br /&gt;
== Agenda for Next Meeting ==&lt;br /&gt;
&lt;br /&gt;
''Please include your IRC handle along with any agenda items you add, so we know who to call on in the meeting.''&lt;br /&gt;
&lt;br /&gt;
Date: 11 July 2014&lt;br /&gt;
&lt;br /&gt;
* Review action items from previous meeting&lt;br /&gt;
** dhellmann clarify language about when to use _() vs. _LE() - done&lt;br /&gt;
** Project liaisons review app-agnostic-logging-parameters: https://review.openstack.org/95281 - merged&lt;br /&gt;
** Project liaisons review oslo-config-generator: https://review.openstack.org/100946 - merged&lt;br /&gt;
** dhellmann talk to markmc about releasing file encoding fix in fake driver in oslo.messaging - 1.4.0.0a2&lt;br /&gt;
* Report from [[Sprints/ParisJuno2014]]&lt;br /&gt;
* Red flags for/from liaisons&lt;br /&gt;
* Exception filtering proposal - feedback / suggestions (Mike Bayer)&lt;br /&gt;
** https://review.openstack.org/#/c/105306/&lt;br /&gt;
** https://review.openstack.org/#/c/105307/&lt;br /&gt;
* Adoption status&lt;br /&gt;
** https://etherpad.openstack.org/p/juno-oslo-adoption-status&lt;br /&gt;
* Spec status&lt;br /&gt;
** oslo.utils - https://review.openstack.org/#/c/98431/ - merged&lt;br /&gt;
** rootwrap daemon mode - https://review.openstack.org/#/c/94613/ - merged&lt;br /&gt;
** taskflow conditional execution - https://review.openstack.org/#/c/98946/ - merged&lt;br /&gt;
* Graduation status&lt;br /&gt;
** oslo.utils - https://blueprints.launchpad.net/oslo/+spec/graduate-oslo-utils&lt;br /&gt;
** oslo.serialization - https://blueprints.launchpad.net/oslo/+spec/graduate-oslo-serialization&lt;br /&gt;
** oslo.concurrency - https://blueprints.launchpad.net/oslo/+spec/graduate-oslo-concurrency&lt;br /&gt;
* Release review&lt;br /&gt;
** oslo.i18n 0.1.0 released - https://etherpad.openstack.org/p/oslo-i18n-1.0.0&lt;br /&gt;
** oslosphinx 2.2.0.0a2 released - https://etherpad.openstack.org/p/oslosphinx-2.2.0&lt;br /&gt;
** pbr 0.9.0 released - http://lists.openstack.org/pipermail/openstack-dev/2014-July/039481.html&lt;br /&gt;
* review priorities for this week&lt;br /&gt;
** db migration test issues (devananda)&lt;br /&gt;
*** https://bugs.launchpad.net/ironic/+bug/1327397 fixed in nova and ironic, not oslo.&lt;br /&gt;
*** https://bugs.launchpad.net/nova/+bug/1328997 partial-fix in nova. incorrectly close in oslo.db.&lt;br /&gt;
&lt;br /&gt;
== General Information ==&lt;br /&gt;
=== Regular Meeting Schedule ===&lt;br /&gt;
* What day: Friday&lt;br /&gt;
* What time: [http://www.timeanddate.com/worldclock/converted.html?iso=20140425T16&amp;amp;p1=0&amp;amp;p2=2133&amp;amp;p3=195&amp;amp;p4=224&amp;amp;p5=43 1600 UTC]&lt;br /&gt;
* Where: #openstack-meeting-alt on freenode&lt;br /&gt;
* Who: All are welcome to participate&lt;br /&gt;
&lt;br /&gt;
=== Notes from Previous Meetings ===&lt;br /&gt;
&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-06-27-16.00.html Jun 27, 2014]&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-06-20-16.01.html Jun 20, 2014] - topics: oslo.db initial release; oslo.messaging good progress in neutron; alpha releases of 5 libraries next week; oslo.db test bugs reported by devananda&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-06-13-16.00.html Jun 13, 2014] - topics: oslo.db alpha release; db migration bug; &lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-06-06-16.00.html Jun 06, 2014] - topics: juno specs, spec approval process&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-05-30-16.00.html May 30, 2014]&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-05-23-16.01.html May 23, 2014] - topics: osprofile (postponed), run_test.sh, juno specs, oslo.test issue in tempest&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-05-09-16.02.html May 09, 2014] - topics: oslo-specs, oslo.messaging, summit prep, oslo.db, oslo.i18n&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-04-25-16.00.html April 24, 2014] - topics: oslotest, oslo.db, oslo.i18n, creating a specs repo&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-02-28-14.00.html Feb 28, 2014] - topics: icehouse feature freeze; syncing cinder &amp;amp; nova; uuidutils&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-02-14-14.01.html Feb 14, 2014] - topics: oslo.db, icehouse-3&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2014/oslo.2014-01-31-14.01.html Jan 31, 2014] - topics: translation, deprecation policy, adopting taskflow, stevedore, and cliff&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2013/oslo.2013-11-15-14.01.html Nov 15, 2013] - topics: translation, pecan/wsme common code, icehouse scheduling&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2013/oslo.2013-10-25-14.00.html Oct 25, 2013] - topics: deprecated decorator and delayed translation implementation plan&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2013/oslo.2013-10-11-14.00.html Oct 11, 2013] - topics: delayed translations&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2013/oslo.2013-08-16-14.00.html Aug 16, 2013] - topic was new messaging API, message security and reject/reque/ack&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2013/oslo.2013-07-19-14.00.html July 19, 2013] - topic was new messaging API, message security, qpid/proton messaging driver and removing logging dependency on eventlet&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2013/oslo.2013-06-07-14.00.html June 7, 2013] - topic was new messaging API and message security&lt;br /&gt;
* [http://eavesdrop.openstack.org/meetings/oslo/2013/oslo.2013-05-03-14.01.html May 3, 2013] - topic was new messaging API and message security&lt;br /&gt;
&lt;br /&gt;
(In case the list of notes is not up to date, please consult http://eavesdrop.openstack.org/meetings/oslo/)&lt;/div&gt;</summary>
		<author><name>Mike Bayer</name></author>	</entry>

	<entry>
		<id>https://wiki.openstack.org/w/index.php?title=OpenStack_and_SQLAlchemy&amp;diff=57546</id>
		<title>OpenStack and SQLAlchemy</title>
		<link rel="alternate" type="text/html" href="https://wiki.openstack.org/w/index.php?title=OpenStack_and_SQLAlchemy&amp;diff=57546"/>
				<updated>2014-07-07T16:50:45Z</updated>
		
		<summary type="html">&lt;p&gt;Mike Bayer: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= OpenStack and SQLAlchemy =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
This page describes the ongoing efforts of Mike Bayer, creator/maintainer of&lt;br /&gt;
SQLAlchemy, Alembic Migrations, and Dogpile caching, to spearhead what is&lt;br /&gt;
essentially an &amp;quot;OpenStack SQLAlchemy 2.0&amp;quot; effort - as I am now full-time&lt;br /&gt;
employed by an OpenStack vendor (Red Hat), I've been hired to produce a strong&lt;br /&gt;
and sustained pass through OpenStack's relational database integration,&lt;br /&gt;
improving areas that have been problematic and establishing a very strong&lt;br /&gt;
set of practices that are specific to OpenStack's needs.&lt;br /&gt;
&lt;br /&gt;
Within this effort, I'd like to stress that from my POV, everything is&lt;br /&gt;
on the table, including new improvements / fixes / features to&lt;br /&gt;
SQLAlchemy and Alembic directly, migration of projects for which&lt;br /&gt;
relational databases aren't suitable to non-relational systems,&lt;br /&gt;
improvement of relational schemas on the OpenStack side, and of course&lt;br /&gt;
improvement of exising relational patterns within OpenStack&lt;br /&gt;
applications, for which oslo.db will serve as the foundation.&lt;br /&gt;
&lt;br /&gt;
As of June 30, 2014, I've been on the job for about a month, so below&lt;br /&gt;
is an outline of most of what I've come across and my thoughts going forward.&lt;br /&gt;
&lt;br /&gt;
My goal here is to share with the community what I've seen and done so far,&lt;br /&gt;
so that interested parties can chime in with further advice and information,&lt;br /&gt;
and most importantly to help steer me towards the initial blueprints that&lt;br /&gt;
I should be starting on, that is, which features are most likely to be&lt;br /&gt;
accepted by the community as well as which are the top priority.  Not to mention, if you're looking&lt;br /&gt;
for something to do, you can help me implement some of these things!&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== OpenStack presence within SQLAlchemy's CI environment (a.k.a. &amp;quot;The Version that shall not be Named&amp;quot;) ==&lt;br /&gt;
&lt;br /&gt;
On Monday, June 23, I got up in the AM with two goals: get a release of SQLAlchemy&lt;br /&gt;
out the door, as it had been several months since the last point release, and then&lt;br /&gt;
do some work learning how Nova's unit tests run and function.&lt;br /&gt;
&lt;br /&gt;
Release 0.9.5 of SQLAlchemy was not particularly different than any of the other&lt;br /&gt;
hundred or so point releases I've put out over nine years, it was a little more overdue&lt;br /&gt;
than some, and out it went.  Later that day, as I was learning to run Nova's test suite, I&lt;br /&gt;
was hitting some failures within the FixedIp/FloatingIp tests for which I couldn't&lt;br /&gt;
find the cause, assuming that I just wasn't setting up my environment correctly,&lt;br /&gt;
as Nova's tests are passing for all the Jenkins servers the vast majority of the time.&lt;br /&gt;
&lt;br /&gt;
As we learned within about 30 minutes of my hitting these problems, it&lt;br /&gt;
turns out 0.9.5 had a bad fix in it; one that didn't fail any of&lt;br /&gt;
SQLAlchemy's 5000 tests but hit a use case within Nova that was not&lt;br /&gt;
very unusual; not the kind of use I usually do myself but nonetheless&lt;br /&gt;
something that was pretty common.   IRC began  jumping that builds&lt;br /&gt;
were failing everywhere and it was due to the 0.9.5 release!   The&lt;br /&gt;
bizarre coiincidence of this was that none of the thirty-odd point&lt;br /&gt;
releases of SQLAlchemy released under OpenStack's lifetime have ever&lt;br /&gt;
really had this effect, only the major jumps saw any issues.&lt;br /&gt;
&lt;br /&gt;
Luckily, as I had just learned 30 minutes earlier how to run Nova's tests I was&lt;br /&gt;
able to drill down to exactly what was going on and SQLAlchemy 0.9.6 was out&lt;br /&gt;
within an hour, but we shall now forever see in requirements.txt, the Version&lt;br /&gt;
that shall Not Be Named (except in requirements.txt):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
SQLAlchemy&amp;gt;=0.7.8,!=0.9.5,&amp;lt;=0.9.99&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In response to this I have now&lt;br /&gt;
[http://jenkins.sqlalchemy.org/job/openstack_sqla_master/ added openstack builds to our Jenkins CI system],&lt;br /&gt;
so far running tests for oslo.db and the SQLAlchemy tests within Nova; I spent&lt;br /&gt;
a couple of days figuring out how to get everything to build and run against&lt;br /&gt;
specific versions of SQLAlchemy and Alembic, even versions above the 0.9.99 in&lt;br /&gt;
all the requirements files as we are set for 1.0 hopefully within the next year.&lt;br /&gt;
I will be adding in as many other OpenStack projects as I can here so that we will no&lt;br /&gt;
longer have a SQLAlchemy release that hasn't been run against a battery of&lt;br /&gt;
basic OpenStack tests.&lt;br /&gt;
&lt;br /&gt;
== ORM Woes ==&lt;br /&gt;
&lt;br /&gt;
After chatting with a decent amount of folks for a month, I can break up&lt;br /&gt;
the take on OpenStack / SQLAlchemy ORM into three camps:&lt;br /&gt;
&lt;br /&gt;
1. We love SQLAlchemy ORM, we know that we can improve how we use it and we're looking forward to getting there (oslo.db, Keystone)&lt;br /&gt;
&lt;br /&gt;
2. Our app uses a relational database but the SQLAlchemy ORM slows us down, we already have our own &amp;quot;orm-like&amp;quot; system (e.g. APIs that shuttle rows into &amp;quot;values&amp;quot; dictionaries, nova.objects, etc.) so we want to ultimately get rid of the ORM part, and probably continue using SQLAlchemy Core to some degree (some Nova developers I've spoken to).&lt;br /&gt;
&lt;br /&gt;
3. Our application has a SQLAlchemy backend that almost nobody uses in production, most users use MongoDB, and the SQLAlchemy backend is only there because MongoDB is AGPL (Ceilometer, Marconi).  We'd like to get a better NoSQL backend without Mongo's licensing issue and do away with relational storage entirely ([http://lists.openstack.org/pipermail/openstack-dev/2014-June/038778.html Marconi]).&lt;br /&gt;
&lt;br /&gt;
Here's my observations / thoughts about these three camps:&lt;br /&gt;
&lt;br /&gt;
1. Great!  Read on, I have some things we can start with.&lt;br /&gt;
&lt;br /&gt;
2. I agree that a lot of the patterns that I've seen in Nova in particular are not well-suited to the approach used by SQLAlchemy's ORM, however I have observed that the applications still rely a lot on very sophisticated and mature features provided by SQLAlchemy (namely &amp;lt;code&amp;gt;relationship()&amp;lt;/code&amp;gt; and it's integration with eager loading and the unit of work features), and throwing it away entirely will incur a lot of reinvention, not just of the features that were lost but also of a totally new crop of stability and performance issues to solve.&lt;br /&gt;
&lt;br /&gt;
To that degree, I'd like to propose a twist to the &amp;quot;we want to write our own ORM&amp;quot; idea (edit: OK nova.objects is really an ORM-like thing on top of their API on top of relational DB, but that's an issue for another day) and say, &amp;quot;let's write our own ORM that's based on the SQLAlchemy ORM primitives&amp;quot;, e.g. a hybrid approach where existing ORM logic that is fine can stay that way, ORM logic that has performance or behavioral issues can be reorganized using systems, some of them new, some of them just underutilized, that bypass unneeded SQLAlchemy features on a case-by-case basis.&lt;br /&gt;
&lt;br /&gt;
As a secondary issue, some devs I've spoken to have referred to the nature&lt;br /&gt;
of OpenStack API-based applications being a part of the problem.&lt;br /&gt;
This refers to the fact that OpenStack apps like Nova&lt;br /&gt;
and Neutron expose fine-grained APIs which end up&lt;br /&gt;
breaking out composed use cases into dozens or hundreds of small API&lt;br /&gt;
operations, each of which invokes within it's own database transaction and&lt;br /&gt;
ORM session state.  This makes it impossible, without introducing some elaborate&lt;br /&gt;
offline state management system, for a larger operation to be&lt;br /&gt;
performed so that larger groups of objects can be loaded and manipulated&lt;br /&gt;
at once, greatly decreasing the number of database round trips present.&lt;br /&gt;
No matter what ORM you use or don't use, the pattern of many small round trips&lt;br /&gt;
is difficult to scale on relational databases.&lt;br /&gt;
&lt;br /&gt;
There are under way projects which aim to apply detailed profiling&lt;br /&gt;
to OpenStack applications so that a breakdown of performance issues due&lt;br /&gt;
to API use and/or database&lt;br /&gt;
access might be plainly reasoned about; see [https://github.com/stackforge/rally Rally]&lt;br /&gt;
and [https://github.com/stackforge/osprofiler OS Profiler] for examples.&lt;br /&gt;
&lt;br /&gt;
On my end, I have no intention of focusing on this second issue for the time&lt;br /&gt;
being, I'm going to stick with leaving OS applications as much as they are right now as possible,&lt;br /&gt;
applying iterative changes to database access code which I hope can optimize&lt;br /&gt;
to a significant degree within the current usage patterns.&lt;br /&gt;
&lt;br /&gt;
3. Projects with alternate MongoDB / MySQL backends (since after all, MySQL is the database here being rejected, SQLAlchemy is just a Python API) should probably do what they're doing and go to an all key/value model.  It is not at all fun to maintain the same datamodel and API against both a KV store as well as relational tables.  I had a detailed look at Ceilometer with its team members early on, and while I'm pretty confident that if it were desireable, I could get it's relational backend to compete with the Mongo backend performance-wise, there's no reason to get into this if the Mongo licensing issue can be solved alone.&lt;br /&gt;
&lt;br /&gt;
== ORM Quick Wins Proof of Concept ==&lt;br /&gt;
&lt;br /&gt;
I've built up a proof of concept at [https://github.com/zzzeek/nova_poc nova_poc] which seeks to demonstrate&lt;br /&gt;
the range of database and ORM performance that can be achieved within&lt;br /&gt;
one particular method of the Nova API.   Naturally, I picked an example that&lt;br /&gt;
is very juicy in this regard; it has a very easily fixable issue with the&lt;br /&gt;
kind of query it emits that has an enormous 10x impact on its performance&lt;br /&gt;
(which I feel is worth illustrating just for how dramatic it is),&lt;br /&gt;
and it then illustrates some new SQLAlchemy extensions that more or less&lt;br /&gt;
may be going into SQLAlchemy directly, which allow the remaining operation&lt;br /&gt;
to complete in less than half the time.  These extensions can be ported to&lt;br /&gt;
oslo.db to work against all supported SQLAlchemy versions prior to the official&lt;br /&gt;
release of these features.&lt;br /&gt;
&lt;br /&gt;
To summarize, we're looking at the API method &amp;lt;code&amp;gt;floating_ip_update()&amp;lt;/code&amp;gt;, and&lt;br /&gt;
I run a series of profiles against a database table 10K rows (e.g. &amp;lt;code&amp;gt;FloatingIp&amp;lt;/code&amp;gt;&lt;br /&gt;
objects), where each &amp;lt;code&amp;gt;FloatingIp&amp;lt;/code&amp;gt; refers to a single &amp;lt;code&amp;gt;FixedIp&amp;lt;/code&amp;gt; that then&lt;br /&gt;
refers to an &amp;lt;code&amp;gt;Instance&amp;lt;/code&amp;gt;.   The profiling focuses on the nature of this&lt;br /&gt;
method as an API feature all it's own, and simulates the case where the API&lt;br /&gt;
method is called thousands of times, not unlike other API methods that seek&lt;br /&gt;
to add some small amount of data each time they are called.&lt;br /&gt;
&lt;br /&gt;
=== Eager load and Column load tuning ===&lt;br /&gt;
&lt;br /&gt;
Turning off an eagerload in the &amp;lt;code&amp;gt;_floating_ip_get_by_address()&amp;lt;/code&amp;gt; function&lt;br /&gt;
is the most dramatic win; while removing this method seems like a reasonable&lt;br /&gt;
fix to remove some unneeded overhead, in profiling it shows itself to&lt;br /&gt;
be enormous:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Running scenario default&lt;br /&gt;
Scenario default, total calls for 1000 operations: 24543047&lt;br /&gt;
Scenario default, total time for 10000 operations: 222&lt;br /&gt;
&lt;br /&gt;
Running scenario default_optimized&lt;br /&gt;
Scenario default_optimized, total calls for 1000 operations: 1881626&lt;br /&gt;
Scenario default_optimized, total time for 10000 operations: 25&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
While not illustrated in the POC code itself, talk with various OpenStack&lt;br /&gt;
devs has revealed that we could probably do a lot more individual column&lt;br /&gt;
fetching as well, that is instead of:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
session.query(FixedIp).all()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We say:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
session.query(FixedIp.address, FixedIp.host, FixedIp.reserved).all()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
By loading indivdual columns, we get read-only objects that are not identity&lt;br /&gt;
managed, bypassing a huge amount of work that is done on instances both&lt;br /&gt;
in instantiating them, populating relationships and doing bookkeeping.&lt;br /&gt;
Any API feature that only returns a simple read-only object or a &amp;quot;values&amp;quot;&lt;br /&gt;
dictionary that does not rely upon relationship loading should be using&lt;br /&gt;
this pattern, or similar, by using a&lt;br /&gt;
[http://docs.sqlalchemy.org/en/rel_0_9/orm/mapper_config.html#column-bundles Column Bundle].&lt;br /&gt;
I will be exploring adding the following feature to oslo.db:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
session.query(FixedIp.bundle).all()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The above will return a fast-loading, read-only Bundle object that otherwise&lt;br /&gt;
expresses the full interface of &amp;lt;code&amp;gt;FixedIp&amp;lt;/code&amp;gt;.   Relationships might be tricky&lt;br /&gt;
here but I can at least get them on board as regular lazy loads (just like&lt;br /&gt;
any other ORM would do anyway).&lt;br /&gt;
&lt;br /&gt;
=== Fast Object Save ===&lt;br /&gt;
&lt;br /&gt;
We explore replacing the unit of work &amp;lt;code&amp;gt;flush()&amp;lt;/code&amp;gt; call used by &amp;lt;code&amp;gt;object.save()&amp;lt;/code&amp;gt; within oslo.db-style applications with a more direct &amp;quot;flush single object&amp;quot; call; this grants us only a 12% savings so far, though it is very simple to use:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Running scenario default_optimized&lt;br /&gt;
Scenario default_optimized, total calls for 1000 operations: 1881626&lt;br /&gt;
Scenario default_optimized, total time for 10000 operations: 25&lt;br /&gt;
&lt;br /&gt;
Running scenario fast_save&lt;br /&gt;
Scenario fast_save, total calls for 1000 operations: 1685221&lt;br /&gt;
Scenario fast_save, total time for 10000 operations: 22&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Baked Queries ===&lt;br /&gt;
&lt;br /&gt;
Something that has been in the works for a long time and has&lt;br /&gt;
recently    seen lots of work in the past months is the &amp;quot;baked query&amp;quot;&lt;br /&gt;
feature; this    pattern is ideal for OpenStack's &amp;quot;many short queries&amp;quot;&lt;br /&gt;
pattern, and allows    caching of the generation of SQL.  Recent&lt;br /&gt;
versions of this pattern have    gotten very slick, and can cache&lt;br /&gt;
virtually everything that happens Python-wise    from the construction&lt;br /&gt;
of the &amp;lt;code&amp;gt;Query&amp;lt;/code&amp;gt; object, to calling all the methods    on the&lt;br /&gt;
query, to the query-objects construction of a Core SQL statement,&lt;br /&gt;
to the compilation of that statement as a string - all of these steps&lt;br /&gt;
are removed from the call-graph after the first such call.  In&lt;br /&gt;
SQLAlchemy 1.0    I've also thrown in the construction of column&lt;br /&gt;
metadata from the result set    too.   The pattern involves a bit more&lt;br /&gt;
verbosity to that of constructing a    query, where here I've built&lt;br /&gt;
off of some of the ideas of the    Pony ORM to use Python function&lt;br /&gt;
information as the source of a cache key.    A query such as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
result = model_query(&lt;br /&gt;
            context, models.FloatingIp, session=session).\&lt;br /&gt;
            filter_by(address=address)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
would be expressed in &amp;quot;baked&amp;quot; form as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# note model_query is using the &amp;quot;baked&amp;quot; process internally as well&lt;br /&gt;
result = model_query(context, models.FloatingIp, session=session)&lt;br /&gt;
&lt;br /&gt;
result.bake(lambda query:&lt;br /&gt;
    query.filter_by(&lt;br /&gt;
        address=bindparam('address'))).params(address=address)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the above form, everything within each lambda is invoked only once,&lt;br /&gt;
the result of which becomes part of a cached value.&lt;br /&gt;
&lt;br /&gt;
For this slight increase in verbosity, we get an improvement like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Running scenario default_optimized&lt;br /&gt;
Scenario default_optimized, total calls for 1000 operations: 1881626&lt;br /&gt;
Scenario default_optimized, total time for 10000 operations: 25&lt;br /&gt;
&lt;br /&gt;
Running scenario baked&lt;br /&gt;
Scenario baked, total calls for 1000 operations: 1052935&lt;br /&gt;
Scenario baked, total time for 10000 operations: 16&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
That is, around a 40% improvement.&lt;br /&gt;
&lt;br /&gt;
Putting together both &amp;quot;fast save&amp;quot; plus &amp;quot;baked&amp;quot; we get down to a full 50%&lt;br /&gt;
improvement vs. the plain optimized version:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Running scenario fast_save_plus_baked&lt;br /&gt;
Scenario fast_save_plus_baked, total calls for 1000 operations: 856035&lt;br /&gt;
Scenario fast_save_plus_baked, total time for 10000 operations: 13&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
13 seconds for 10K operations vs. 222 seconds in the current code, I hope&lt;br /&gt;
I've established that there is hope!&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Migrations ==&lt;br /&gt;
&lt;br /&gt;
Migrations are a huge deal.  Here's where it looks like this is going.&lt;br /&gt;
&lt;br /&gt;
Alembic - &amp;quot;Alembic is the defacto standard going forward&amp;quot; - ultimately&lt;br /&gt;
we want to get everything SQLAlchemy-Migrate onto Alembic.  And in talking&lt;br /&gt;
with Nova devs, they really like that they can test their migrations against&lt;br /&gt;
SQLite.  So from my POV, SQLite migrations on Alembic, we're doing that!&lt;br /&gt;
Unless folks think otherwise.&lt;br /&gt;
&lt;br /&gt;
=== SQLite Support ===&lt;br /&gt;
&lt;br /&gt;
SQLite has extremely limited support for ALTER TABLE, essentially only being able&lt;br /&gt;
to add new columns, and that's about it.  In order to provide the full suite&lt;br /&gt;
of ALTER operations that all other databases provide, tools such as&lt;br /&gt;
SQLAlchemy-Migrate will create a copy of the target table with changes applied,&lt;br /&gt;
copy data from old table to new, then drop the old table and rename the new one.&lt;br /&gt;
I've always been peeved at SQLite's insistence on never providing some better&lt;br /&gt;
pathway to ALTER built in.  But it has been on the Alembic roadmap to add SQLite migrations&lt;br /&gt;
in a style similar to that of Migrate for a long time, emulating this same&lt;br /&gt;
approach in some way.  Alembic will have this!&lt;br /&gt;
&lt;br /&gt;
However in talking with some folks, it appears like some people might actually&lt;br /&gt;
like this &amp;quot;make a new table and switch it&amp;quot; approach for other databases too,&lt;br /&gt;
as a way to work around locking of tables.  I don't know that I agree with this&lt;br /&gt;
long term, the &amp;quot;ALTER locks the database!&amp;quot; issue in my experience has always&lt;br /&gt;
been kind of a MySQL problem alone, but it can be present for other backends&lt;br /&gt;
as well.&lt;br /&gt;
&lt;br /&gt;
So the API I'm looking towards here is going to try to solve a few problems;&lt;br /&gt;
one is that, copying the whole table to a new one each time for *every single&lt;br /&gt;
column* is crazy, and also that the technique we use on SQLite might be nice&lt;br /&gt;
for other databases too, so here's the API:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
with op.batch_alter(&amp;quot;mytable&amp;quot;, recreate=True) as batch_op:&lt;br /&gt;
    batch_op.drop_column(&amp;quot;some_column&amp;quot;)&lt;br /&gt;
    batch_op.alter_column(&amp;quot;q&amp;quot;, type=Integer)&lt;br /&gt;
    batch_op.add_column(Column('y', String(50), nullable=False))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
That is, we assign a whole series of column operations under the umbrella of&lt;br /&gt;
one table, and they are created at once.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;batch_alter()&amp;lt;/code&amp;gt; call can even be conditional, dropping into traditional&lt;br /&gt;
migration behavior for all backends except SQLite and whatever else:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
with op.batch_alter(&amp;quot;mytable&amp;quot;, recreate_on=('sqlite',)) as batch_op:&lt;br /&gt;
    batch_op.drop_column(&amp;quot;some_column&amp;quot;)&lt;br /&gt;
    batch_op.alter_column(&amp;quot;q&amp;quot;, type=Integer)&lt;br /&gt;
    batch_op.add_column(Column('y', String(50), nullable=False))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
SQlite-compatible alterations is [https://bitbucket.org/zzzeek/alembic/issue/21/column-renames-not-supported-on-sqlite issue #21].&lt;br /&gt;
&lt;br /&gt;
=== Finishing up Autogenerate ===&lt;br /&gt;
&lt;br /&gt;
I started writing Alembic long before SQLAlchemy-Migrate had autogenerate.  But by the time&lt;br /&gt;
I got Alembic released and later autogenerate working, Migrate already had it.&lt;br /&gt;
Here's what we need to finish up in autogenerate:&lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;code&amp;gt;ForeignKeyConstraint&amp;lt;/code&amp;gt; change detection - this is an entirely straightforward feature add and has long been on the todo list.&lt;br /&gt;
&lt;br /&gt;
2. MySQL implicit indexes - this is actually done; recent versions of Alembic can navigate around MySQLs goofy production of indexes automatically on foreign key columns, and not accidentally spit them out in autogenerates. It was seriously tough to get index autogen mostly working on all backends, so new issues will continue to be fixed as they are reported.&lt;br /&gt;
&lt;br /&gt;
3. Type comparison - Alembic balks on type comparison by default, because ultimately SQLAlchemy should add comparision features to its type objects natively. However, on Alembic backends like that of MySQL, we can add explicit rules for the very common and annoying ones, namely the detection of MySQL's TINYINT as a SQLAlchemy Boolean.  Alembic already allows user-specified rules in this regard, so they can be part of oslo.db in any case, however Alembic can also add this rule and others in directly.&lt;br /&gt;
&lt;br /&gt;
4. Table order - Alembic's autogenerate should be spitting out table creates and drops in the order of foreign key dependency, though we don't have good test coverage for this yet and it might not be working.  We'll fix that, no biggie!  Alternatively, Alembic could include a mode of operation that includes all the ForeignKeyConstraint objects after all the tables. Input on how we'd like to see this work would be welcome.&lt;br /&gt;
&lt;br /&gt;
=== New Migration Features ===&lt;br /&gt;
&lt;br /&gt;
There's a lot of features I'd like to add to Alembic, and if OpenStack has&lt;br /&gt;
a need for them, that would justify the effort:&lt;br /&gt;
&lt;br /&gt;
1. Full branch support - [https://bitbucket.org/zzzeek/alembic/issue/167/multiple-heads-branch-resolution-support issue 167 multiple heads / branch resolution support] has a detailed architecture laid out for this feature.  Lots of people are looking for it. This would turn Alembic's current &amp;quot;linked list&amp;quot; version model into a full directed acyclic graph (DAG).   Any particular migration can be dependent on any other group of migrations, or none at all; individual branches can be maintained and upgraded along their path, or automatically merged.&lt;br /&gt;
&lt;br /&gt;
2. [https://bitbucket.org/zzzeek/alembic/issue/124/multiple-versions-directories Multiple version directories] - This would allow migration files to be present in more than one place for a single migrations environment.  Currently, you can get this approach by using multiple base directories, but that requires separate &amp;lt;code&amp;gt;env.py&amp;lt;/code&amp;gt; files for each. With this issue, cross-dependent migration files can live in multiple places, working nicely with the multiple heads support of #167.&lt;br /&gt;
&lt;br /&gt;
== Testing ==&lt;br /&gt;
&lt;br /&gt;
I spent quite a bit looking into testing.  The main thing we can see&lt;br /&gt;
in testing is that there may be a desire for &amp;quot;transactional testing&amp;quot;;&lt;br /&gt;
run tests within a fixed schema, that is the schema    is created&lt;br /&gt;
once, many tests are run, each one rolling back their work, the&lt;br /&gt;
schema is only dropped once all tests for that schema are complete.&lt;br /&gt;
&lt;br /&gt;
Transactional testing is described in the SQLAlchemy docs and in some of&lt;br /&gt;
my talks; for OpenStack, we need the version that supports graceful handling&lt;br /&gt;
of tests that actually do a rollback.  This is doable and the&lt;br /&gt;
recipe for that is currently [https://gist.github.com/zzzeek/8443477 a github gist].&lt;br /&gt;
&lt;br /&gt;
In order to accommodate parallelized runs, the system will create N&lt;br /&gt;
temporary schemas for N concurrent runs.&lt;br /&gt;
&lt;br /&gt;
I really want to get into this feature but as I talk to people, I'm&lt;br /&gt;
getting mixed signals on how important this feature is.   So far I've&lt;br /&gt;
only worked with unit tests, which seem to be entirely against SQLite&lt;br /&gt;
in-memory databases, and I haven't gotten my head around to what&lt;br /&gt;
extent other databases besides SQLite might be desireable in unit&lt;br /&gt;
tests, nor have I gotten to fully understand how integration tests&lt;br /&gt;
run, so input is welcome here.&lt;br /&gt;
&lt;br /&gt;
== Improvement of ORM Patterns ==&lt;br /&gt;
&lt;br /&gt;
Looking around oslo.db and other apps, I want to improve upon&lt;br /&gt;
some kinds of verbosity I see, which make the code more repetetive and inconsistent, welds together application-level and business level concepts (e.g. transaction control and exception reporting melded with data manipulation) and makes it harder to reason about how the application interacts with sessions, as there are a variety of ad-hoc patterns present.&lt;br /&gt;
&lt;br /&gt;
=== Transaction Demarcation ===&lt;br /&gt;
&lt;br /&gt;
For the oft-repeated &amp;quot;get_session(); with session.begin(subtransactions=True)&amp;quot; phrase and other variants, I'd like to move that into something more abstracted:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def my_api_thing(x):&lt;br /&gt;
    with transactional() as session:&lt;br /&gt;
    	# do stuff&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I want context managers and/or decorators that are oslo.db specific to replace any kind of containing code that deals with transactions or sessions. That way the containinment logic can be maintained in once place and configured separately from the business logic contained within.  Projects like Keystone are already using a form more like this; it would be nice to nail down a consistent pattern and get it into oslo.db for everyone to use.&lt;br /&gt;
&lt;br /&gt;
=== MySQLdb + eventlet = sad ===&lt;br /&gt;
&lt;br /&gt;
This is the basic thing that happens with these two actors.   We're in an eventlet &amp;quot;thread&amp;quot;.   The code uses MySQLdb to do something ordinary that locks a row in a table - this can happen with a SELECT FOR UPDATE but it really can happen just on any old UPDATE too depending on isolation level (see https://review.openstack.org/#/c/104111/).   If the eventlet &amp;quot;thread&amp;quot; then does *anything* that causes it to defer out, another eventlet thread might run, and if that thread tries to work with MySQLdb and lock the same row, you're done, it's a deadlock.   The simple reason is, MySQLdb doesn't integrate with eventlet, so a simple thing like blocking on a transactional lock, it doesn't defer.   MySQL times out on these so we're getting exceptions instead of permanent freezes, but IMHO this is a really silly problem.  Eventlet and MySQLdb do not work together, we're done.   MySQLdb 1000% either has to go, or be fixed, even if that means forking it.    A good blog post that illustrates both scenarios is [http://www.diamondtin.com/2014/sqlalchemy-gevent-mysql-python-drivers-comparison/ here] - in this post, the author has used &amp;quot;greenify&amp;quot; to patch MySQLdb's blocking, but without this change (which I'm told isn't compatible with eventlet?), MySQLdb is a nonstarter.    Because it is in pure C, it is faster than MySQL-connector when it's been patched, but if it hasn't, it's dog slow.  MySQL-connector IMHO is probably the driver we should get behind [http://dev.mysql.com/doc/connector-python/en/index.html because it's the official MySQL driver]. The various workarounds and hacks (which I guess is why this is under &amp;quot;ORM patterns&amp;quot;) I've seen proposed are all technically viable but IMHO totally unnecessary.   OpenStack should be serious about this, it's a totally solvable problem, let's just solve it.&lt;br /&gt;
&lt;br /&gt;
=== Exception Rewriting ===&lt;br /&gt;
&lt;br /&gt;
Exception rewriting - I notice that the oslo.db pattern includes that DBAPI level exceptions are re-thrown as OpenStack specific;  I'd like to move this into handling using SQLAlchemy events.  The [http://docs.sqlalchemy.org/en/rel_0_9/core/events.html#sqlalchemy.events.ConnectionEvents.dbapi_error dbapi_error] event goes all the way back to 0.7, and [https://bitbucket.org/zzzeek/sqlalchemy/issue/3076/support-reraise-of-custom-exceptions ticket 3076] will allow it to be used to reraise with a new exception.  For older SQLAlchemy versions, I can create a wrapper that will allow it to be used in this way safely for older versions, despite the warnings stated on that document.&lt;br /&gt;
&lt;br /&gt;
=== Deleted Object Selecting ===&lt;br /&gt;
&lt;br /&gt;
There's a need to be able to run queries that automatically de-select for certain rows, particularly rows that have a &amp;quot;deleted&amp;quot; flag set, or that otherwise include various identifiers that are given for a context.  Right now oslo.db has a function `model_query()` for this purpose.   I'd like to streamline `model_query()` and allow for a more flexible pattern, where instead of plainly adding WHERE clauses to a SELECT against a primary table, we can use rules to produce a SQL transformation that will apply the desired criteria to all &amp;quot;FROM&amp;quot; clauses at the point at which they are named, even within a more complex, composed query.   This will allow `model_query()` to be more flexible and ultimately to allow its behavior to be more transparently integrated into the app.&lt;br /&gt;
&lt;br /&gt;
=== Pessimistic Locking - SELECT FOR UPDATE ===&lt;br /&gt;
&lt;br /&gt;
This issue has been coming up for some projects, that they make sometimes great use of SQLAlchemy's &amp;lt;code&amp;gt;with_lockmode()&amp;lt;/code&amp;gt; method, which ultimately emits SELECT..FOR UPDATE, so that selected rows are aggressively locked within a transaction.   The issue is that backends like Galera don't support FOR UPDATE, meaning the locking that is desired is not actually provided.&lt;br /&gt;
&lt;br /&gt;
While I'm not sure that there's a generalized solution to this, in some cases I've been helping developers move to more of an &amp;quot;optimistic&amp;quot; approach, where a particular UPDATE statement targets some kind of timestamp or value counter in the row, then counts the number of rows matched.  If the number of rows matched is not what's expected, it is assumed that the UPDATE failed to match the row, and the operation can either try again or can bail out.   This approach might not be suitable in all cases.  In cases where a large stream of data is affecting a relatively small set of rows, it can perhaps be feasible to serialize those updates by sending them all to a queue, so that they are invoked in isolation without the need for up-front locking.   For very high throughput situations, queues can be constructed to use multiple, concurrent UPDATE streams separated via hashing, or can batch together updates for particular rows over a period of time and emit fewer UPDATE statements than if each source of data did it individually.&lt;br /&gt;
&lt;br /&gt;
=== Encouraging Bundle / Baked Query Use ===&lt;br /&gt;
&lt;br /&gt;
Encouraging Bundle use - as stated earlier, I want to look into bringing the very efficient [http://docs.sqlalchemy.org/en/rel_0_9/orm/mapper_config.html#column-bundles Column Bundle] pattern into the fold for mainstream use, not to mention the baked query pattern illustrated previously.&lt;br /&gt;
&lt;br /&gt;
=== Transparent Retries of Transactions ===&lt;br /&gt;
&lt;br /&gt;
I have ready to go an [https://bitbucket.org/zzzeek/sqlalchemy/issue/3104/transaction-replay-extension already in production extension for transaction replaying], developed for a client with the agreement that the extension is open source.  This extension installs as an event handler using a new series of hooks that were added in 0.9, or alternatively using a series of compatibility hooks that were backported to 0.7 and 0.8.   This extension allows logging of SQL statements as they proceed within a transaction, and allows them to be transparently replayed when a database disconnect error occurs. This means that SQLAlchemy's usual behavior of raising an exception and forcing you to re-try explicitly can be hidden so that it occurs implicitly, without any interruption or special error handling required in application code.  The extension includes a lot of configuration options, such as what statements should be replayed and which shouldn't, as well as what exceptions should be handled this way.   Transaction replaying is an elaborate feature which might introduce lots of new edge cases, namely that the state of the database may have changed since the last transaction failed, so to that extent I'm a little wary of this feature myself.  However I have seen lots of messages alluding to this kind of thing.    The mechanics to pull this into SQLAlchemy as a transparent feature are ready to go, if this is a feature people are looking for (I'm not sure if people are looking for this).&lt;br /&gt;
&lt;br /&gt;
== Contributors ==&lt;br /&gt;
&lt;br /&gt;
I would be remiss if I didn't mention that SQLAlchemy, Alembic, Dogpile and everything else I do are collaborative projects, and while SQLAlchemy in particular might present a daunting codebase, we would love to get new collaborators on board!   Any of the features above are fair game and I can walk someone through the steps to be taken, and additionally everything we add features for always needs lots of tests and review, if you're looking for a way to get into the project I can bring you right in, there's lots of (often very tedious and boring) work to be done.   I'm on all the IRC channels now as zzzeek so ping away.&lt;br /&gt;
&lt;br /&gt;
== Coffee and Cake ==&lt;br /&gt;
&lt;br /&gt;
So! I hope you've enjoyed reading this document, and whether you're a fan or a hater, please know that I'm on board now and I'm here to help, with whatever database kind of thing you want to do - it's all part of my job now.    If we wanted to use/build a whole new ORM, I'd be doing that too right alongside.  OpenStack's lessons in database integration are now my lessons too.  So let me know where I should dig in first!  Have a save drive home.&lt;/div&gt;</summary>
		<author><name>Mike Bayer</name></author>	</entry>

	<entry>
		<id>https://wiki.openstack.org/w/index.php?title=OpenStack_and_SQLAlchemy&amp;diff=57277</id>
		<title>OpenStack and SQLAlchemy</title>
		<link rel="alternate" type="text/html" href="https://wiki.openstack.org/w/index.php?title=OpenStack_and_SQLAlchemy&amp;diff=57277"/>
				<updated>2014-07-03T02:06:58Z</updated>
		
		<summary type="html">&lt;p&gt;Mike Bayer: /* MySQLdb + eventlet = sad */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= OpenStack and SQLAlchemy =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
This page describes the ongoing efforts of Mike Bayer, creator/maintainer of&lt;br /&gt;
SQLAlchemy, Alembic Migrations, and Dogpile caching, to spearhead what is&lt;br /&gt;
essentially an &amp;quot;OpenStack SQLAlchemy 2.0&amp;quot; effort - as I am now full-time&lt;br /&gt;
employed by an OpenStack vendor (Red Hat), I've been hired to produce a strong&lt;br /&gt;
and sustained pass through OpenStack's relational database integration,&lt;br /&gt;
improving areas that have been problematic and establishing a very strong&lt;br /&gt;
set of practices that are specific to OpenStack's needs.&lt;br /&gt;
&lt;br /&gt;
Within this effort, I'd like to stress that from my POV, everything is&lt;br /&gt;
on the table, including new improvements / fixes / features to&lt;br /&gt;
SQLAlchemy and Alembic directly, migration of projects for which&lt;br /&gt;
relational databases aren't suitable to non-relational systems,&lt;br /&gt;
improvement of relational schemas on the OpenStack side, and of course&lt;br /&gt;
improvement of exising relational patterns within OpenStack&lt;br /&gt;
applications, for which oslo.db will serve as the foundation.&lt;br /&gt;
&lt;br /&gt;
As of June 30, 2014, I've been on the job for about a month, so below&lt;br /&gt;
is an outline of most of what I've come across and my thoughts going forward.&lt;br /&gt;
&lt;br /&gt;
My goal here is to share with the community what I've seen and done so far,&lt;br /&gt;
so that interested parties can chime in with further advice and information,&lt;br /&gt;
and most importantly to help steer me towards the initial blueprints that&lt;br /&gt;
I should be starting on, that is, which features are most likely to be&lt;br /&gt;
accepted by the community as well as which are the top priority.  Not to mention, if you're looking&lt;br /&gt;
for something to do, you can help me implement some of these things!&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== OpenStack presence within SQLAlchemy's CI environment (a.k.a. &amp;quot;The Version that shall not be Named&amp;quot;) ==&lt;br /&gt;
&lt;br /&gt;
On Monday, June 23, I got up in the AM with two goals: get a release of SQLAlchemy&lt;br /&gt;
out the door, as it had been several months since the last point release, and then&lt;br /&gt;
do some work learning how Nova's unit tests run and function.&lt;br /&gt;
&lt;br /&gt;
Release 0.9.5 of SQLAlchemy was not particularly different than any of the other&lt;br /&gt;
hundred or so point releases I've put out over nine years, it was a little more overdue&lt;br /&gt;
than some, and out it went.  Later that day, as I was learning to run Nova's test suite, I&lt;br /&gt;
was hitting some failures within the FixedIp/FloatingIp tests for which I couldn't&lt;br /&gt;
find the cause, assuming that I just wasn't setting up my environment correctly,&lt;br /&gt;
as Nova's tests are passing for all the Jenkins servers the vast majority of the time.&lt;br /&gt;
&lt;br /&gt;
As we learned within about 30 minutes of my hitting these problems, it&lt;br /&gt;
turns out 0.9.5 had a bad fix in it; one that didn't fail any of&lt;br /&gt;
SQLAlchemy's 5000 tests but hit a use case within Nova that was not&lt;br /&gt;
very unusual; not the kind of use I usually do myself but nonetheless&lt;br /&gt;
something that was pretty common.   IRC began  jumping that builds&lt;br /&gt;
were failing everywhere and it was due to the 0.9.5 release!   The&lt;br /&gt;
bizarre coiincidence of this was that none of the thirty-odd point&lt;br /&gt;
releases of SQLAlchemy released under OpenStack's lifetime have ever&lt;br /&gt;
really had this effect, only the major jumps saw any issues.&lt;br /&gt;
&lt;br /&gt;
Luckily, as I had just learned 30 minutes earlier how to run Nova's tests I was&lt;br /&gt;
able to drill down to exactly what was going on and SQLAlchemy 0.9.6 was out&lt;br /&gt;
within an hour, but we shall now forever see in requirements.txt, the Version&lt;br /&gt;
that shall Not Be Named (except in requirements.txt):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
SQLAlchemy&amp;gt;=0.7.8,!=0.9.5,&amp;lt;=0.9.99&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In response to this I have now&lt;br /&gt;
[http://jenkins.sqlalchemy.org/job/openstack_sqla_master/ added openstack builds to our Jenkins CI system],&lt;br /&gt;
so far running tests for oslo.db and the SQLAlchemy tests within Nova; I spent&lt;br /&gt;
a couple of days figuring out how to get everything to build and run against&lt;br /&gt;
specific versions of SQLAlchemy and Alembic, even versions above the 0.9.99 in&lt;br /&gt;
all the requirements files as we are set for 1.0 hopefully within the next year.&lt;br /&gt;
I will be adding in as many other OpenStack projects as I can here so that we will no&lt;br /&gt;
longer have a SQLAlchemy release that hasn't been run against a battery of&lt;br /&gt;
basic OpenStack tests.&lt;br /&gt;
&lt;br /&gt;
== ORM Woes ==&lt;br /&gt;
&lt;br /&gt;
After chatting with a decent amount of folks for a month, I can break up&lt;br /&gt;
the take on OpenStack / SQLAlchemy ORM into three camps:&lt;br /&gt;
&lt;br /&gt;
1. We love SQLAlchemy ORM, we know that we can improve how we use it and we're looking forward to getting there (oslo.db, Keystone)&lt;br /&gt;
&lt;br /&gt;
2. Our app uses a relational database but the SQLAlchemy ORM slows us down, we already have our own &amp;quot;orm-like&amp;quot; system (e.g. APIs that shuttle rows into &amp;quot;values&amp;quot; dictionaries, nova.objects, etc.) so we want to ultimately get rid of the ORM part, and probably continue using SQLAlchemy Core to some degree (some Nova developers I've spoken to).&lt;br /&gt;
&lt;br /&gt;
3. Our application has a SQLAlchemy backend that almost nobody uses in production, most users use MongoDB, and the SQLAlchemy backend is only there because MongoDB is AGPL (Ceilometer, Marconi).  We'd like to get a better NoSQL backend without Mongo's licensing issue and do away with relational storage entirely ([http://lists.openstack.org/pipermail/openstack-dev/2014-June/038778.html Marconi]).&lt;br /&gt;
&lt;br /&gt;
Here's my observations / thoughts about these three camps:&lt;br /&gt;
&lt;br /&gt;
1. Great!  Read on, I have some things we can start with.&lt;br /&gt;
&lt;br /&gt;
2. I agree that a lot of the patterns that I've seen in Nova in particular are not well-suited to the approach used by SQLAlchemy's ORM, however I have observed that the applications still rely a lot on very sophisticated and mature features provided by SQLAlchemy (namely &amp;lt;code&amp;gt;relationship()&amp;lt;/code&amp;gt; and it's integration with eager loading and the unit of work features), and throwing it away entirely will incur a lot of reinvention, not just of the features that were lost but also of a totally new crop of stability and performance issues to solve.&lt;br /&gt;
&lt;br /&gt;
To that degree, I'd like to propose a twist to the &amp;quot;we want to write our own ORM&amp;quot; idea (edit: OK nova.objects is really an ORM-like thing on top of their API on top of relational DB, but that's an issue for another day) and say, &amp;quot;let's write our own ORM that's based on the SQLAlchemy ORM primitives&amp;quot;, e.g. a hybrid approach where existing ORM logic that is fine can stay that way, ORM logic that has performance or behavioral issues can be reorganized using systems, some of them new, some of them just underutilized, that bypass unneeded SQLAlchemy features on a case-by-case basis.&lt;br /&gt;
&lt;br /&gt;
As a secondary issue, some devs I've spoken to have referred to the nature&lt;br /&gt;
of OpenStack API-based applications being a part of the problem.&lt;br /&gt;
This refers to the fact that OpenStack apps like Nova&lt;br /&gt;
and Neutron expose fine-grained APIs which end up&lt;br /&gt;
breaking out composed use cases into dozens or hundreds of small API&lt;br /&gt;
operations, each of which invokes within it's own database transaction and&lt;br /&gt;
ORM session state.  This makes it impossible, without introducing some elaborate&lt;br /&gt;
offline state management system, for a larger operation to be&lt;br /&gt;
performed so that larger groups of objects can be loaded and manipulated&lt;br /&gt;
at once, greatly decreasing the number of database round trips present.&lt;br /&gt;
No matter what ORM you use or don't use, the pattern of many small round trips&lt;br /&gt;
is difficult to scale on relational databases.&lt;br /&gt;
&lt;br /&gt;
There are under way projects which aim to apply detailed profiling&lt;br /&gt;
to OpenStack applications so that a breakdown of performance issues due&lt;br /&gt;
to API use and/or database&lt;br /&gt;
access might be plainly reasoned about; see [https://github.com/stackforge/rally Rally]&lt;br /&gt;
and [https://github.com/stackforge/osprofiler OS Profiler] for examples.&lt;br /&gt;
&lt;br /&gt;
On my end, I have no intention of focusing on this second issue for the time&lt;br /&gt;
being, I'm going to stick with leaving OS applications as much as they are right now as possible,&lt;br /&gt;
applying iterative changes to database access code which I hope can optimize&lt;br /&gt;
to a significant degree within the current usage patterns.&lt;br /&gt;
&lt;br /&gt;
3. Projects with alternate MongoDB / MySQL backends (since after all, MySQL is the database here being rejected, SQLAlchemy is just a Python API) should probably do what they're doing and go to an all key/value model.  It is not at all fun to maintain the same datamodel and API against both a KV store as well as relational tables.  I had a detailed look at Ceilometer with its team members early on, and while I'm pretty confident that if it were desireable, I could get it's relational backend to compete with the Mongo backend performance-wise, there's no reason to get into this if the Mongo licensing issue can be solved alone.&lt;br /&gt;
&lt;br /&gt;
== ORM Quick Wins Proof of Concept ==&lt;br /&gt;
&lt;br /&gt;
I've built up a proof of concept at [https://github.com/zzzeek/nova_poc nova_poc] which seeks to demonstrate&lt;br /&gt;
the range of database and ORM performance that can be achieved within&lt;br /&gt;
one particular method of the Nova API.   Naturally, I picked an example that&lt;br /&gt;
is very juicy in this regard; it has a very easily fixable issue with the&lt;br /&gt;
kind of query it emits that has an enormous 10x impact on its performance&lt;br /&gt;
(which I feel is worth illustrating just for how dramatic it is),&lt;br /&gt;
and it then illustrates some new SQLAlchemy extensions that more or less&lt;br /&gt;
may be going into SQLAlchemy directly, which allow the remaining operation&lt;br /&gt;
to complete in less than half the time.  These extensions can be ported to&lt;br /&gt;
oslo.db to work against all supported SQLAlchemy versions prior to the official&lt;br /&gt;
release of these features.&lt;br /&gt;
&lt;br /&gt;
To summarize, we're looking at the API method &amp;lt;code&amp;gt;floating_ip_update()&amp;lt;/code&amp;gt;, and&lt;br /&gt;
I run a series of profiles against a database table 10K rows (e.g. &amp;lt;code&amp;gt;FloatingIp&amp;lt;/code&amp;gt;&lt;br /&gt;
objects), where each &amp;lt;code&amp;gt;FloatingIp&amp;lt;/code&amp;gt; refers to a single &amp;lt;code&amp;gt;FixedIp&amp;lt;/code&amp;gt; that then&lt;br /&gt;
refers to an &amp;lt;code&amp;gt;Instance&amp;lt;/code&amp;gt;.   The profiling focuses on the nature of this&lt;br /&gt;
method as an API feature all it's own, and simulates the case where the API&lt;br /&gt;
method is called thousands of times, not unlike other API methods that seek&lt;br /&gt;
to add some small amount of data each time they are called.&lt;br /&gt;
&lt;br /&gt;
=== Eager load and Column load tuning ===&lt;br /&gt;
&lt;br /&gt;
Turning off an eagerload in the &amp;lt;code&amp;gt;_floating_ip_get_by_address()&amp;lt;/code&amp;gt; function&lt;br /&gt;
is the most dramatic win; while removing this method seems like a reasonable&lt;br /&gt;
fix to remove some unneeded overhead, in profiling it shows itself to&lt;br /&gt;
be enormous:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Running scenario default&lt;br /&gt;
Scenario default, total calls for 1000 operations: 24543047&lt;br /&gt;
Scenario default, total time for 10000 operations: 222&lt;br /&gt;
&lt;br /&gt;
Running scenario default_optimized&lt;br /&gt;
Scenario default_optimized, total calls for 1000 operations: 1881626&lt;br /&gt;
Scenario default_optimized, total time for 10000 operations: 25&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
While not illustrated in the POC code itself, talk with various OpenStack&lt;br /&gt;
devs has revealed that we could probably do a lot more individual column&lt;br /&gt;
fetching as well, that is instead of:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
session.query(FixedIp).all()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We say:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
session.query(FixedIp.address, FixedIp.host, FixedIp.reserved).all()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
By loading indivdual columns, we get read-only objects that are not identity&lt;br /&gt;
managed, bypassing a huge amount of work that is done on instances both&lt;br /&gt;
in instantiating them, populating relationships and doing bookkeeping.&lt;br /&gt;
Any API feature that only returns a simple read-only object or a &amp;quot;values&amp;quot;&lt;br /&gt;
dictionary that does not rely upon relationship loading should be using&lt;br /&gt;
this pattern, or similar, by using a&lt;br /&gt;
[http://docs.sqlalchemy.org/en/rel_0_9/orm/mapper_config.html#column-bundles Column Bundle].&lt;br /&gt;
I will be exploring adding the following feature to oslo.db:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
session.query(FixedIp.bundle).all()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The above will return a fast-loading, read-only Bundle object that otherwise&lt;br /&gt;
expresses the full interface of &amp;lt;code&amp;gt;FixedIp&amp;lt;/code&amp;gt;.   Relationships might be tricky&lt;br /&gt;
here but I can at least get them on board as regular lazy loads (just like&lt;br /&gt;
any other ORM would do anyway).&lt;br /&gt;
&lt;br /&gt;
=== Fast Object Save ===&lt;br /&gt;
&lt;br /&gt;
We explore replacing the unit of work &amp;lt;code&amp;gt;flush()&amp;lt;/code&amp;gt; call used by &amp;lt;code&amp;gt;object.save()&amp;lt;/code&amp;gt; within oslo.db-style applications with a more direct &amp;quot;flush single object&amp;quot; call; this grants us only a 12% savings so far, though it is very simple to use:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Running scenario default_optimized&lt;br /&gt;
Scenario default_optimized, total calls for 1000 operations: 1881626&lt;br /&gt;
Scenario default_optimized, total time for 10000 operations: 25&lt;br /&gt;
&lt;br /&gt;
Running scenario fast_save&lt;br /&gt;
Scenario fast_save, total calls for 1000 operations: 1685221&lt;br /&gt;
Scenario fast_save, total time for 10000 operations: 22&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Baked Queries ===&lt;br /&gt;
&lt;br /&gt;
Something that has been in the works for a long time and has&lt;br /&gt;
recently    seen lots of work in the past months is the &amp;quot;baked query&amp;quot;&lt;br /&gt;
feature; this    pattern is ideal for OpenStack's &amp;quot;many short queries&amp;quot;&lt;br /&gt;
pattern, and allows    caching of the generation of SQL.  Recent&lt;br /&gt;
versions of this pattern have    gotten very slick, and can cache&lt;br /&gt;
virtually everything that happens Python-wise    from the construction&lt;br /&gt;
of the &amp;lt;code&amp;gt;Query&amp;lt;/code&amp;gt; object, to calling all the methods    on the&lt;br /&gt;
query, to the query-objects construction of a Core SQL statement,&lt;br /&gt;
to the compilation of that statement as a string - all of these steps&lt;br /&gt;
are removed from the call-graph after the first such call.  In&lt;br /&gt;
SQLAlchemy 1.0    I've also thrown in the construction of column&lt;br /&gt;
metadata from the result set    too.   The pattern involves a bit more&lt;br /&gt;
verbosity to that of constructing a    query, where here I've built&lt;br /&gt;
off of some of the ideas of the    Pony ORM to use Python function&lt;br /&gt;
information as the source of a cache key.    A query such as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
result = model_query(&lt;br /&gt;
            context, models.FloatingIp, session=session).\&lt;br /&gt;
            filter_by(address=address)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
would be expressed in &amp;quot;baked&amp;quot; form as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# note model_query is using the &amp;quot;baked&amp;quot; process internally as well&lt;br /&gt;
result = model_query(context, models.FloatingIp, session=session)&lt;br /&gt;
&lt;br /&gt;
result.bake(lambda query:&lt;br /&gt;
    query.filter_by(&lt;br /&gt;
        address=bindparam('address'))).params(address=address)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the above form, everything within each lambda is invoked only once,&lt;br /&gt;
the result of which becomes part of a cached value.&lt;br /&gt;
&lt;br /&gt;
For this slight increase in verbosity, we get an improvement like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Running scenario default_optimized&lt;br /&gt;
Scenario default_optimized, total calls for 1000 operations: 1881626&lt;br /&gt;
Scenario default_optimized, total time for 10000 operations: 25&lt;br /&gt;
&lt;br /&gt;
Running scenario baked&lt;br /&gt;
Scenario baked, total calls for 1000 operations: 1052935&lt;br /&gt;
Scenario baked, total time for 10000 operations: 16&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
That is, around a 40% improvement.&lt;br /&gt;
&lt;br /&gt;
Putting together both &amp;quot;fast save&amp;quot; plus &amp;quot;baked&amp;quot; we get down to a full 50%&lt;br /&gt;
improvement vs. the plain optimized version:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Running scenario fast_save_plus_baked&lt;br /&gt;
Scenario fast_save_plus_baked, total calls for 1000 operations: 856035&lt;br /&gt;
Scenario fast_save_plus_baked, total time for 10000 operations: 13&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
13 seconds for 10K operations vs. 222 seconds in the current code, I hope&lt;br /&gt;
I've established that there is hope!&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Migrations ==&lt;br /&gt;
&lt;br /&gt;
Migrations are a huge deal.  Here's where it looks like this is going.&lt;br /&gt;
&lt;br /&gt;
Alembic - &amp;quot;Alembic is the defacto standard going forward&amp;quot; - ultimately&lt;br /&gt;
we want to get everything SQLAlchemy-Migrate onto Alembic.  And in talking&lt;br /&gt;
with Nova devs, they really like that they can test their migrations against&lt;br /&gt;
SQLite.  So from my POV, SQLite migrations on Alembic, we're doing that!&lt;br /&gt;
Unless folks think otherwise.&lt;br /&gt;
&lt;br /&gt;
=== SQLite Support ===&lt;br /&gt;
&lt;br /&gt;
SQLite has extremely limited support for ALTER TABLE, essentially only being able&lt;br /&gt;
to add new columns, and that's about it.  In order to provide the full suite&lt;br /&gt;
of ALTER operations that all other databases provide, tools such as&lt;br /&gt;
SQLAlchemy-Migrate will create a copy of the target table with changes applied,&lt;br /&gt;
copy data from old table to new, then drop the old table and rename the new one.&lt;br /&gt;
I've always been peeved at SQLite's insistence on never providing some better&lt;br /&gt;
pathway to ALTER built in.  But it has been on the Alembic roadmap to add SQLite migrations&lt;br /&gt;
in a style similar to that of Migrate for a long time, emulating this same&lt;br /&gt;
approach in some way.  Alembic will have this!&lt;br /&gt;
&lt;br /&gt;
However in talking with some folks, it appears like some people might actually&lt;br /&gt;
like this &amp;quot;make a new table and switch it&amp;quot; approach for other databases too,&lt;br /&gt;
as a way to work around locking of tables.  I don't know that I agree with this&lt;br /&gt;
long term, the &amp;quot;ALTER locks the database!&amp;quot; issue in my experience has always&lt;br /&gt;
been kind of a MySQL problem alone, but it can be present for other backends&lt;br /&gt;
as well.&lt;br /&gt;
&lt;br /&gt;
So the API I'm looking towards here is going to try to solve a few problems;&lt;br /&gt;
one is that, copying the whole table to a new one each time for *every single&lt;br /&gt;
column* is crazy, and also that the technique we use on SQLite might be nice&lt;br /&gt;
for other databases too, so here's the API:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
with op.batch_alter(&amp;quot;mytable&amp;quot;, recreate=True) as batch_op:&lt;br /&gt;
    batch_op.drop_column(&amp;quot;some_column&amp;quot;)&lt;br /&gt;
    batch_op.alter_column(&amp;quot;q&amp;quot;, type=Integer)&lt;br /&gt;
    batch_op.add_column(Column('y', String(50), nullable=False))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
That is, we assign a whole series of column operations under the umbrella of&lt;br /&gt;
one table, and they are created at once.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;batch_alter()&amp;lt;/code&amp;gt; call can even be conditional, dropping into traditional&lt;br /&gt;
migration behavior for all backends except SQLite and whatever else:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
with op.batch_alter(&amp;quot;mytable&amp;quot;, recreate_on=('sqlite',)) as batch_op:&lt;br /&gt;
    batch_op.drop_column(&amp;quot;some_column&amp;quot;)&lt;br /&gt;
    batch_op.alter_column(&amp;quot;q&amp;quot;, type=Integer)&lt;br /&gt;
    batch_op.add_column(Column('y', String(50), nullable=False))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
SQlite-compatible alterations is [https://bitbucket.org/zzzeek/alembic/issue/21/column-renames-not-supported-on-sqlite issue #21].&lt;br /&gt;
&lt;br /&gt;
=== Finishing up Autogenerate ===&lt;br /&gt;
&lt;br /&gt;
I started writing Alembic long before SQLAlchemy-Migrate had autogenerate.  But by the time&lt;br /&gt;
I got Alembic released and later autogenerate working, Migrate already had it.&lt;br /&gt;
Here's what we need to finish up in autogenerate:&lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;code&amp;gt;ForeignKeyConstraint&amp;lt;/code&amp;gt; change detection - this is an entirely straightforward feature add and has long been on the todo list.&lt;br /&gt;
&lt;br /&gt;
2. MySQL implicit indexes - this is actually done; recent versions of Alembic can navigate around MySQLs goofy production of indexes automatically on foreign key columns, and not accidentally spit them out in autogenerates. It was seriously tough to get index autogen mostly working on all backends, so new issues will continue to be fixed as they are reported.&lt;br /&gt;
&lt;br /&gt;
3. Type comparison - Alembic balks on type comparison by default, because ultimately SQLAlchemy should add comparision features to its type objects natively. However, on Alembic backends like that of MySQL, we can add explicit rules for the very common and annoying ones, namely the detection of MySQL's TINYINT as a SQLAlchemy Boolean.  Alembic already allows user-specified rules in this regard, so they can be part of oslo.db in any case, however Alembic can also add this rule and others in directly.&lt;br /&gt;
&lt;br /&gt;
4. Table order - Alembic's autogenerate should be spitting out table creates and drops in the order of foreign key dependency, though we don't have good test coverage for this yet and it might not be working.  We'll fix that, no biggie!  Alternatively, Alembic could include a mode of operation that includes all the ForeignKeyConstraint objects after all the tables. Input on how we'd like to see this work would be welcome.&lt;br /&gt;
&lt;br /&gt;
=== New Migration Features ===&lt;br /&gt;
&lt;br /&gt;
There's a lot of features I'd like to add to Alembic, and if OpenStack has&lt;br /&gt;
a need for them, that would justify the effort:&lt;br /&gt;
&lt;br /&gt;
1. Full branch support - [https://bitbucket.org/zzzeek/alembic/issue/167/multiple-heads-branch-resolution-support issue 167 multiple heads / branch resolution support] has a detailed architecture laid out for this feature.  Lots of people are looking for it. This would turn Alembic's current &amp;quot;linked list&amp;quot; version model into a full directed acyclic graph (DAG).   Any particular migration can be dependent on any other group of migrations, or none at all; individual branches can be maintained and upgraded along their path, or automatically merged.&lt;br /&gt;
&lt;br /&gt;
2. [https://bitbucket.org/zzzeek/alembic/issue/124/multiple-versions-directories Multiple version directories] - This would allow migration files to be present in more than one place for a single migrations environment.  Currently, you can get this approach by using multiple base directories, but that requires separate &amp;lt;code&amp;gt;env.py&amp;lt;/code&amp;gt; files for each. With this issue, cross-dependent migration files can live in multiple places, working nicely with the multiple heads support of #167.&lt;br /&gt;
&lt;br /&gt;
== Testing ==&lt;br /&gt;
&lt;br /&gt;
I spent quite a bit looking into testing.  The main thing we can see&lt;br /&gt;
in testing is that there may be a desire for &amp;quot;transactional testing&amp;quot;;&lt;br /&gt;
run tests within a fixed schema, that is the schema    is created&lt;br /&gt;
once, many tests are run, each one rolling back their work, the&lt;br /&gt;
schema is only dropped once all tests for that schema are complete.&lt;br /&gt;
&lt;br /&gt;
Transactional testing is described in the SQLAlchemy docs and in some of&lt;br /&gt;
my talks; for OpenStack, we need the version that supports graceful handling&lt;br /&gt;
of tests that actually do a rollback.  This is doable and the&lt;br /&gt;
recipe for that is currently [https://gist.github.com/zzzeek/8443477 a github gist].&lt;br /&gt;
&lt;br /&gt;
In order to accommodate parallelized runs, the system will create N&lt;br /&gt;
temporary schemas for N concurrent runs.&lt;br /&gt;
&lt;br /&gt;
I really want to get into this feature but as I talk to people, I'm&lt;br /&gt;
getting mixed signals on how important this feature is.   So far I've&lt;br /&gt;
only worked with unit tests, which seem to be entirely against SQLite&lt;br /&gt;
in-memory databases, and I haven't gotten my head around to what&lt;br /&gt;
extent other databases besides SQLite might be desireable in unit&lt;br /&gt;
tests, nor have I gotten to fully understand how integration tests&lt;br /&gt;
run, so input is welcome here.&lt;br /&gt;
&lt;br /&gt;
== Improvement of ORM Patterns ==&lt;br /&gt;
&lt;br /&gt;
Looking around oslo.db and other apps, I want to improve upon&lt;br /&gt;
some kinds of verbosity I see, which make the code more repetetive and inconsistent, welds together application-level and business level concepts (e.g. transaction control and exception reporting melded with data manipulation) and makes it harder to reason about how the application interacts with sessions, as there are a variety of ad-hoc patterns present.&lt;br /&gt;
&lt;br /&gt;
=== Transaction Demarcation ===&lt;br /&gt;
&lt;br /&gt;
For the oft-repeated &amp;quot;get_session(); with session.begin(subtransactions=True)&amp;quot; phrase and other variants, I'd like to move that into something more abstracted:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def my_api_thing(x):&lt;br /&gt;
    with transactional() as session:&lt;br /&gt;
    	# do stuff&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I want context managers and/or decorators that are oslo.db specific to replace any kind of containing code that deals with transactions or sessions. That way the containinment logic can be maintained in once place and configured separately from the business logic contained within.  Projects like Keystone are already using a form more like this; it would be nice to nail down a consistent pattern and get it into oslo.db for everyone to use.&lt;br /&gt;
&lt;br /&gt;
=== MySQLdb + eventlet = sad ===&lt;br /&gt;
&lt;br /&gt;
This is the basic thing that happens with these two actors.   We're in an eventlet &amp;quot;thread&amp;quot;.   The code uses MySQLdb to do something ordinary that locks a row in a table - this can happen with a SELECT FOR UPDATE but it really can happen just on any old UPDATE too depending on isolation level (see https://review.openstack.org/#/c/104111/).   If the eventlet &amp;quot;thread&amp;quot; then does *anything* that causes it to defer out, another eventlet thread might run, and if that thread tries to work with MySQLdb and lock the same row, you're done, it's a deadlock.   The simple reason is, MySQLdb doesn't integrate with eventlet, so a simple thing like blocking on a transactional lock, it doesn't defer.   MySQL times out on these so we're getting exceptions instead of permanent freezes, but IMHO this is a really silly problem.  Eventlet and MySQLdb do not work together, we're done.   MySQLdb 1000% either has to go, or be fixed, even if that means forking it.    A good blog post that illustrates both scenarios is [http://www.diamondtin.com/2014/sqlalchemy-gevent-mysql-python-drivers-comparison/ here] - in this post, the author has used &amp;quot;greenify&amp;quot; to patch MySQLdb's blocking, but without this change (which I'm told isn't compatible with eventlet?), MySQLdb is a nonstarter.    Because it is in pure C, it is faster than MySQL-connector when it's been patched, but if it hasn't, it's dog slow.  MySQL-connector IMHO is probably the driver we should get behind [http://dev.mysql.com/doc/connector-python/en/index.html because it's the official MySQL driver]. The various workarounds and hacks (which I guess is why this is under &amp;quot;ORM patterns&amp;quot;) I've seen proposed are all technically viable but IMHO totally unnecessary.   OpenStack should be serious about this, it's a totally solvable problem, let's just solve it.&lt;br /&gt;
&lt;br /&gt;
=== Exception Rewriting ===&lt;br /&gt;
&lt;br /&gt;
Exception rewriting - I notice that the oslo.db pattern includes that DBAPI level exceptions are re-thrown as OpenStack specific;  I'd like to move this into handling using SQLAlchemy events.  The [http://docs.sqlalchemy.org/en/rel_0_9/core/events.html#sqlalchemy.events.ConnectionEvents.dbapi_error dbapi_error] event goes all the way back to 0.7, and [https://bitbucket.org/zzzeek/sqlalchemy/issue/3076/support-reraise-of-custom-exceptions ticket 3076] will allow it to be used to reraise with a new exception.  For older SQLAlchemy versions, I can create a wrapper that will allow it to be used in this way safely for older versions, despite the warnings stated on that document.&lt;br /&gt;
&lt;br /&gt;
=== Pessimistic Locking - SELECT FOR UPDATE ===&lt;br /&gt;
&lt;br /&gt;
This issue has been coming up for some projects, that they make sometimes great use of SQLAlchemy's &amp;lt;code&amp;gt;with_lockmode()&amp;lt;/code&amp;gt; method, which ultimately emits SELECT..FOR UPDATE, so that selected rows are aggressively locked within a transaction.   The issue is that backends like Galera don't support FOR UPDATE, meaning the locking that is desired is not actually provided.&lt;br /&gt;
&lt;br /&gt;
While I'm not sure that there's a generalized solution to this, in some cases I've been helping developers move to more of an &amp;quot;optimistic&amp;quot; approach, where a particular UPDATE statement targets some kind of timestamp or value counter in the row, then counts the number of rows matched.  If the number of rows matched is not what's expected, it is assumed that the UPDATE failed to match the row, and the operation can either try again or can bail out.   This approach might not be suitable in all cases.  In cases where a large stream of data is affecting a relatively small set of rows, it can perhaps be feasible to serialize those updates by sending them all to a queue, so that they are invoked in isolation without the need for up-front locking.   For very high throughput situations, queues can be constructed to use multiple, concurrent UPDATE streams separated via hashing, or can batch together updates for particular rows over a period of time and emit fewer UPDATE statements than if each source of data did it individually.&lt;br /&gt;
&lt;br /&gt;
=== Encouraging Bundle / Baked Query Use ===&lt;br /&gt;
&lt;br /&gt;
Encouraging Bundle use - as stated earlier, I want to look into bringing the very efficient [http://docs.sqlalchemy.org/en/rel_0_9/orm/mapper_config.html#column-bundles Column Bundle] pattern into the fold for mainstream use, not to mention the baked query pattern illustrated previously.&lt;br /&gt;
&lt;br /&gt;
=== Transparent Retries of Transactions ===&lt;br /&gt;
&lt;br /&gt;
I have ready to go an [https://bitbucket.org/zzzeek/sqlalchemy/issue/3104/transaction-replay-extension already in production extension for transaction replaying], developed for a client with the agreement that the extension is open source.  This extension installs as an event handler using a new series of hooks that were added in 0.9, or alternatively using a series of compatibility hooks that were backported to 0.7 and 0.8.   This extension allows logging of SQL statements as they proceed within a transaction, and allows them to be transparently replayed when a database disconnect error occurs. This means that SQLAlchemy's usual behavior of raising an exception and forcing you to re-try explicitly can be hidden so that it occurs implicitly, without any interruption or special error handling required in application code.  The extension includes a lot of configuration options, such as what statements should be replayed and which shouldn't, as well as what exceptions should be handled this way.   Transaction replaying is an elaborate feature which might introduce lots of new edge cases, namely that the state of the database may have changed since the last transaction failed, so to that extent I'm a little wary of this feature myself.  However I have seen lots of messages alluding to this kind of thing.    The mechanics to pull this into SQLAlchemy as a transparent feature are ready to go, if this is a feature people are looking for (I'm not sure if people are looking for this).&lt;br /&gt;
&lt;br /&gt;
== Contributors ==&lt;br /&gt;
&lt;br /&gt;
I would be remiss if I didn't mention that SQLAlchemy, Alembic, Dogpile and everything else I do are collaborative projects, and while SQLAlchemy in particular might present a daunting codebase, we would love to get new collaborators on board!   Any of the features above are fair game and I can walk someone through the steps to be taken, and additionally everything we add features for always needs lots of tests and review, if you're looking for a way to get into the project I can bring you right in, there's lots of (often very tedious and boring) work to be done.   I'm on all the IRC channels now as zzzeek so ping away.&lt;br /&gt;
&lt;br /&gt;
== Coffee and Cake ==&lt;br /&gt;
&lt;br /&gt;
So! I hope you've enjoyed reading this document, and whether you're a fan or a hater, please know that I'm on board now and I'm here to help, with whatever database kind of thing you want to do - it's all part of my job now.    If we wanted to use/build a whole new ORM, I'd be doing that too right alongside.  OpenStack's lessons in database integration are now my lessons too.  So let me know where I should dig in first!  Have a save drive home.&lt;/div&gt;</summary>
		<author><name>Mike Bayer</name></author>	</entry>

	<entry>
		<id>https://wiki.openstack.org/w/index.php?title=OpenStack_and_SQLAlchemy&amp;diff=57276</id>
		<title>OpenStack and SQLAlchemy</title>
		<link rel="alternate" type="text/html" href="https://wiki.openstack.org/w/index.php?title=OpenStack_and_SQLAlchemy&amp;diff=57276"/>
				<updated>2014-07-03T01:20:26Z</updated>
		
		<summary type="html">&lt;p&gt;Mike Bayer: /* MySQLdb + eventlet = sad */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= OpenStack and SQLAlchemy =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
This page describes the ongoing efforts of Mike Bayer, creator/maintainer of&lt;br /&gt;
SQLAlchemy, Alembic Migrations, and Dogpile caching, to spearhead what is&lt;br /&gt;
essentially an &amp;quot;OpenStack SQLAlchemy 2.0&amp;quot; effort - as I am now full-time&lt;br /&gt;
employed by an OpenStack vendor (Red Hat), I've been hired to produce a strong&lt;br /&gt;
and sustained pass through OpenStack's relational database integration,&lt;br /&gt;
improving areas that have been problematic and establishing a very strong&lt;br /&gt;
set of practices that are specific to OpenStack's needs.&lt;br /&gt;
&lt;br /&gt;
Within this effort, I'd like to stress that from my POV, everything is&lt;br /&gt;
on the table, including new improvements / fixes / features to&lt;br /&gt;
SQLAlchemy and Alembic directly, migration of projects for which&lt;br /&gt;
relational databases aren't suitable to non-relational systems,&lt;br /&gt;
improvement of relational schemas on the OpenStack side, and of course&lt;br /&gt;
improvement of exising relational patterns within OpenStack&lt;br /&gt;
applications, for which oslo.db will serve as the foundation.&lt;br /&gt;
&lt;br /&gt;
As of June 30, 2014, I've been on the job for about a month, so below&lt;br /&gt;
is an outline of most of what I've come across and my thoughts going forward.&lt;br /&gt;
&lt;br /&gt;
My goal here is to share with the community what I've seen and done so far,&lt;br /&gt;
so that interested parties can chime in with further advice and information,&lt;br /&gt;
and most importantly to help steer me towards the initial blueprints that&lt;br /&gt;
I should be starting on, that is, which features are most likely to be&lt;br /&gt;
accepted by the community as well as which are the top priority.  Not to mention, if you're looking&lt;br /&gt;
for something to do, you can help me implement some of these things!&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== OpenStack presence within SQLAlchemy's CI environment (a.k.a. &amp;quot;The Version that shall not be Named&amp;quot;) ==&lt;br /&gt;
&lt;br /&gt;
On Monday, June 23, I got up in the AM with two goals: get a release of SQLAlchemy&lt;br /&gt;
out the door, as it had been several months since the last point release, and then&lt;br /&gt;
do some work learning how Nova's unit tests run and function.&lt;br /&gt;
&lt;br /&gt;
Release 0.9.5 of SQLAlchemy was not particularly different than any of the other&lt;br /&gt;
hundred or so point releases I've put out over nine years, it was a little more overdue&lt;br /&gt;
than some, and out it went.  Later that day, as I was learning to run Nova's test suite, I&lt;br /&gt;
was hitting some failures within the FixedIp/FloatingIp tests for which I couldn't&lt;br /&gt;
find the cause, assuming that I just wasn't setting up my environment correctly,&lt;br /&gt;
as Nova's tests are passing for all the Jenkins servers the vast majority of the time.&lt;br /&gt;
&lt;br /&gt;
As we learned within about 30 minutes of my hitting these problems, it&lt;br /&gt;
turns out 0.9.5 had a bad fix in it; one that didn't fail any of&lt;br /&gt;
SQLAlchemy's 5000 tests but hit a use case within Nova that was not&lt;br /&gt;
very unusual; not the kind of use I usually do myself but nonetheless&lt;br /&gt;
something that was pretty common.   IRC began  jumping that builds&lt;br /&gt;
were failing everywhere and it was due to the 0.9.5 release!   The&lt;br /&gt;
bizarre coiincidence of this was that none of the thirty-odd point&lt;br /&gt;
releases of SQLAlchemy released under OpenStack's lifetime have ever&lt;br /&gt;
really had this effect, only the major jumps saw any issues.&lt;br /&gt;
&lt;br /&gt;
Luckily, as I had just learned 30 minutes earlier how to run Nova's tests I was&lt;br /&gt;
able to drill down to exactly what was going on and SQLAlchemy 0.9.6 was out&lt;br /&gt;
within an hour, but we shall now forever see in requirements.txt, the Version&lt;br /&gt;
that shall Not Be Named (except in requirements.txt):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
SQLAlchemy&amp;gt;=0.7.8,!=0.9.5,&amp;lt;=0.9.99&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In response to this I have now&lt;br /&gt;
[http://jenkins.sqlalchemy.org/job/openstack_sqla_master/ added openstack builds to our Jenkins CI system],&lt;br /&gt;
so far running tests for oslo.db and the SQLAlchemy tests within Nova; I spent&lt;br /&gt;
a couple of days figuring out how to get everything to build and run against&lt;br /&gt;
specific versions of SQLAlchemy and Alembic, even versions above the 0.9.99 in&lt;br /&gt;
all the requirements files as we are set for 1.0 hopefully within the next year.&lt;br /&gt;
I will be adding in as many other OpenStack projects as I can here so that we will no&lt;br /&gt;
longer have a SQLAlchemy release that hasn't been run against a battery of&lt;br /&gt;
basic OpenStack tests.&lt;br /&gt;
&lt;br /&gt;
== ORM Woes ==&lt;br /&gt;
&lt;br /&gt;
After chatting with a decent amount of folks for a month, I can break up&lt;br /&gt;
the take on OpenStack / SQLAlchemy ORM into three camps:&lt;br /&gt;
&lt;br /&gt;
1. We love SQLAlchemy ORM, we know that we can improve how we use it and we're looking forward to getting there (oslo.db, Keystone)&lt;br /&gt;
&lt;br /&gt;
2. Our app uses a relational database but the SQLAlchemy ORM slows us down, we already have our own &amp;quot;orm-like&amp;quot; system (e.g. APIs that shuttle rows into &amp;quot;values&amp;quot; dictionaries, nova.objects, etc.) so we want to ultimately get rid of the ORM part, and probably continue using SQLAlchemy Core to some degree (some Nova developers I've spoken to).&lt;br /&gt;
&lt;br /&gt;
3. Our application has a SQLAlchemy backend that almost nobody uses in production, most users use MongoDB, and the SQLAlchemy backend is only there because MongoDB is AGPL (Ceilometer, Marconi).  We'd like to get a better NoSQL backend without Mongo's licensing issue and do away with relational storage entirely ([http://lists.openstack.org/pipermail/openstack-dev/2014-June/038778.html Marconi]).&lt;br /&gt;
&lt;br /&gt;
Here's my observations / thoughts about these three camps:&lt;br /&gt;
&lt;br /&gt;
1. Great!  Read on, I have some things we can start with.&lt;br /&gt;
&lt;br /&gt;
2. I agree that a lot of the patterns that I've seen in Nova in particular are not well-suited to the approach used by SQLAlchemy's ORM, however I have observed that the applications still rely a lot on very sophisticated and mature features provided by SQLAlchemy (namely &amp;lt;code&amp;gt;relationship()&amp;lt;/code&amp;gt; and it's integration with eager loading and the unit of work features), and throwing it away entirely will incur a lot of reinvention, not just of the features that were lost but also of a totally new crop of stability and performance issues to solve.&lt;br /&gt;
&lt;br /&gt;
To that degree, I'd like to propose a twist to the &amp;quot;we want to write our own ORM&amp;quot; idea (edit: OK nova.objects is really an ORM-like thing on top of their API on top of relational DB, but that's an issue for another day) and say, &amp;quot;let's write our own ORM that's based on the SQLAlchemy ORM primitives&amp;quot;, e.g. a hybrid approach where existing ORM logic that is fine can stay that way, ORM logic that has performance or behavioral issues can be reorganized using systems, some of them new, some of them just underutilized, that bypass unneeded SQLAlchemy features on a case-by-case basis.&lt;br /&gt;
&lt;br /&gt;
As a secondary issue, some devs I've spoken to have referred to the nature&lt;br /&gt;
of OpenStack API-based applications being a part of the problem.&lt;br /&gt;
This refers to the fact that OpenStack apps like Nova&lt;br /&gt;
and Neutron expose fine-grained APIs which end up&lt;br /&gt;
breaking out composed use cases into dozens or hundreds of small API&lt;br /&gt;
operations, each of which invokes within it's own database transaction and&lt;br /&gt;
ORM session state.  This makes it impossible, without introducing some elaborate&lt;br /&gt;
offline state management system, for a larger operation to be&lt;br /&gt;
performed so that larger groups of objects can be loaded and manipulated&lt;br /&gt;
at once, greatly decreasing the number of database round trips present.&lt;br /&gt;
No matter what ORM you use or don't use, the pattern of many small round trips&lt;br /&gt;
is difficult to scale on relational databases.&lt;br /&gt;
&lt;br /&gt;
There are under way projects which aim to apply detailed profiling&lt;br /&gt;
to OpenStack applications so that a breakdown of performance issues due&lt;br /&gt;
to API use and/or database&lt;br /&gt;
access might be plainly reasoned about; see [https://github.com/stackforge/rally Rally]&lt;br /&gt;
and [https://github.com/stackforge/osprofiler OS Profiler] for examples.&lt;br /&gt;
&lt;br /&gt;
On my end, I have no intention of focusing on this second issue for the time&lt;br /&gt;
being, I'm going to stick with leaving OS applications as much as they are right now as possible,&lt;br /&gt;
applying iterative changes to database access code which I hope can optimize&lt;br /&gt;
to a significant degree within the current usage patterns.&lt;br /&gt;
&lt;br /&gt;
3. Projects with alternate MongoDB / MySQL backends (since after all, MySQL is the database here being rejected, SQLAlchemy is just a Python API) should probably do what they're doing and go to an all key/value model.  It is not at all fun to maintain the same datamodel and API against both a KV store as well as relational tables.  I had a detailed look at Ceilometer with its team members early on, and while I'm pretty confident that if it were desireable, I could get it's relational backend to compete with the Mongo backend performance-wise, there's no reason to get into this if the Mongo licensing issue can be solved alone.&lt;br /&gt;
&lt;br /&gt;
== ORM Quick Wins Proof of Concept ==&lt;br /&gt;
&lt;br /&gt;
I've built up a proof of concept at [https://github.com/zzzeek/nova_poc nova_poc] which seeks to demonstrate&lt;br /&gt;
the range of database and ORM performance that can be achieved within&lt;br /&gt;
one particular method of the Nova API.   Naturally, I picked an example that&lt;br /&gt;
is very juicy in this regard; it has a very easily fixable issue with the&lt;br /&gt;
kind of query it emits that has an enormous 10x impact on its performance&lt;br /&gt;
(which I feel is worth illustrating just for how dramatic it is),&lt;br /&gt;
and it then illustrates some new SQLAlchemy extensions that more or less&lt;br /&gt;
may be going into SQLAlchemy directly, which allow the remaining operation&lt;br /&gt;
to complete in less than half the time.  These extensions can be ported to&lt;br /&gt;
oslo.db to work against all supported SQLAlchemy versions prior to the official&lt;br /&gt;
release of these features.&lt;br /&gt;
&lt;br /&gt;
To summarize, we're looking at the API method &amp;lt;code&amp;gt;floating_ip_update()&amp;lt;/code&amp;gt;, and&lt;br /&gt;
I run a series of profiles against a database table 10K rows (e.g. &amp;lt;code&amp;gt;FloatingIp&amp;lt;/code&amp;gt;&lt;br /&gt;
objects), where each &amp;lt;code&amp;gt;FloatingIp&amp;lt;/code&amp;gt; refers to a single &amp;lt;code&amp;gt;FixedIp&amp;lt;/code&amp;gt; that then&lt;br /&gt;
refers to an &amp;lt;code&amp;gt;Instance&amp;lt;/code&amp;gt;.   The profiling focuses on the nature of this&lt;br /&gt;
method as an API feature all it's own, and simulates the case where the API&lt;br /&gt;
method is called thousands of times, not unlike other API methods that seek&lt;br /&gt;
to add some small amount of data each time they are called.&lt;br /&gt;
&lt;br /&gt;
=== Eager load and Column load tuning ===&lt;br /&gt;
&lt;br /&gt;
Turning off an eagerload in the &amp;lt;code&amp;gt;_floating_ip_get_by_address()&amp;lt;/code&amp;gt; function&lt;br /&gt;
is the most dramatic win; while removing this method seems like a reasonable&lt;br /&gt;
fix to remove some unneeded overhead, in profiling it shows itself to&lt;br /&gt;
be enormous:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Running scenario default&lt;br /&gt;
Scenario default, total calls for 1000 operations: 24543047&lt;br /&gt;
Scenario default, total time for 10000 operations: 222&lt;br /&gt;
&lt;br /&gt;
Running scenario default_optimized&lt;br /&gt;
Scenario default_optimized, total calls for 1000 operations: 1881626&lt;br /&gt;
Scenario default_optimized, total time for 10000 operations: 25&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
While not illustrated in the POC code itself, talk with various OpenStack&lt;br /&gt;
devs has revealed that we could probably do a lot more individual column&lt;br /&gt;
fetching as well, that is instead of:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
session.query(FixedIp).all()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We say:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
session.query(FixedIp.address, FixedIp.host, FixedIp.reserved).all()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
By loading indivdual columns, we get read-only objects that are not identity&lt;br /&gt;
managed, bypassing a huge amount of work that is done on instances both&lt;br /&gt;
in instantiating them, populating relationships and doing bookkeeping.&lt;br /&gt;
Any API feature that only returns a simple read-only object or a &amp;quot;values&amp;quot;&lt;br /&gt;
dictionary that does not rely upon relationship loading should be using&lt;br /&gt;
this pattern, or similar, by using a&lt;br /&gt;
[http://docs.sqlalchemy.org/en/rel_0_9/orm/mapper_config.html#column-bundles Column Bundle].&lt;br /&gt;
I will be exploring adding the following feature to oslo.db:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
session.query(FixedIp.bundle).all()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The above will return a fast-loading, read-only Bundle object that otherwise&lt;br /&gt;
expresses the full interface of &amp;lt;code&amp;gt;FixedIp&amp;lt;/code&amp;gt;.   Relationships might be tricky&lt;br /&gt;
here but I can at least get them on board as regular lazy loads (just like&lt;br /&gt;
any other ORM would do anyway).&lt;br /&gt;
&lt;br /&gt;
=== Fast Object Save ===&lt;br /&gt;
&lt;br /&gt;
We explore replacing the unit of work &amp;lt;code&amp;gt;flush()&amp;lt;/code&amp;gt; call used by &amp;lt;code&amp;gt;object.save()&amp;lt;/code&amp;gt; within oslo.db-style applications with a more direct &amp;quot;flush single object&amp;quot; call; this grants us only a 12% savings so far, though it is very simple to use:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Running scenario default_optimized&lt;br /&gt;
Scenario default_optimized, total calls for 1000 operations: 1881626&lt;br /&gt;
Scenario default_optimized, total time for 10000 operations: 25&lt;br /&gt;
&lt;br /&gt;
Running scenario fast_save&lt;br /&gt;
Scenario fast_save, total calls for 1000 operations: 1685221&lt;br /&gt;
Scenario fast_save, total time for 10000 operations: 22&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Baked Queries ===&lt;br /&gt;
&lt;br /&gt;
Something that has been in the works for a long time and has&lt;br /&gt;
recently    seen lots of work in the past months is the &amp;quot;baked query&amp;quot;&lt;br /&gt;
feature; this    pattern is ideal for OpenStack's &amp;quot;many short queries&amp;quot;&lt;br /&gt;
pattern, and allows    caching of the generation of SQL.  Recent&lt;br /&gt;
versions of this pattern have    gotten very slick, and can cache&lt;br /&gt;
virtually everything that happens Python-wise    from the construction&lt;br /&gt;
of the &amp;lt;code&amp;gt;Query&amp;lt;/code&amp;gt; object, to calling all the methods    on the&lt;br /&gt;
query, to the query-objects construction of a Core SQL statement,&lt;br /&gt;
to the compilation of that statement as a string - all of these steps&lt;br /&gt;
are removed from the call-graph after the first such call.  In&lt;br /&gt;
SQLAlchemy 1.0    I've also thrown in the construction of column&lt;br /&gt;
metadata from the result set    too.   The pattern involves a bit more&lt;br /&gt;
verbosity to that of constructing a    query, where here I've built&lt;br /&gt;
off of some of the ideas of the    Pony ORM to use Python function&lt;br /&gt;
information as the source of a cache key.    A query such as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
result = model_query(&lt;br /&gt;
            context, models.FloatingIp, session=session).\&lt;br /&gt;
            filter_by(address=address)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
would be expressed in &amp;quot;baked&amp;quot; form as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# note model_query is using the &amp;quot;baked&amp;quot; process internally as well&lt;br /&gt;
result = model_query(context, models.FloatingIp, session=session)&lt;br /&gt;
&lt;br /&gt;
result.bake(lambda query:&lt;br /&gt;
    query.filter_by(&lt;br /&gt;
        address=bindparam('address'))).params(address=address)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the above form, everything within each lambda is invoked only once,&lt;br /&gt;
the result of which becomes part of a cached value.&lt;br /&gt;
&lt;br /&gt;
For this slight increase in verbosity, we get an improvement like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Running scenario default_optimized&lt;br /&gt;
Scenario default_optimized, total calls for 1000 operations: 1881626&lt;br /&gt;
Scenario default_optimized, total time for 10000 operations: 25&lt;br /&gt;
&lt;br /&gt;
Running scenario baked&lt;br /&gt;
Scenario baked, total calls for 1000 operations: 1052935&lt;br /&gt;
Scenario baked, total time for 10000 operations: 16&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
That is, around a 40% improvement.&lt;br /&gt;
&lt;br /&gt;
Putting together both &amp;quot;fast save&amp;quot; plus &amp;quot;baked&amp;quot; we get down to a full 50%&lt;br /&gt;
improvement vs. the plain optimized version:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Running scenario fast_save_plus_baked&lt;br /&gt;
Scenario fast_save_plus_baked, total calls for 1000 operations: 856035&lt;br /&gt;
Scenario fast_save_plus_baked, total time for 10000 operations: 13&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
13 seconds for 10K operations vs. 222 seconds in the current code, I hope&lt;br /&gt;
I've established that there is hope!&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Migrations ==&lt;br /&gt;
&lt;br /&gt;
Migrations are a huge deal.  Here's where it looks like this is going.&lt;br /&gt;
&lt;br /&gt;
Alembic - &amp;quot;Alembic is the defacto standard going forward&amp;quot; - ultimately&lt;br /&gt;
we want to get everything SQLAlchemy-Migrate onto Alembic.  And in talking&lt;br /&gt;
with Nova devs, they really like that they can test their migrations against&lt;br /&gt;
SQLite.  So from my POV, SQLite migrations on Alembic, we're doing that!&lt;br /&gt;
Unless folks think otherwise.&lt;br /&gt;
&lt;br /&gt;
=== SQLite Support ===&lt;br /&gt;
&lt;br /&gt;
SQLite has extremely limited support for ALTER TABLE, essentially only being able&lt;br /&gt;
to add new columns, and that's about it.  In order to provide the full suite&lt;br /&gt;
of ALTER operations that all other databases provide, tools such as&lt;br /&gt;
SQLAlchemy-Migrate will create a copy of the target table with changes applied,&lt;br /&gt;
copy data from old table to new, then drop the old table and rename the new one.&lt;br /&gt;
I've always been peeved at SQLite's insistence on never providing some better&lt;br /&gt;
pathway to ALTER built in.  But it has been on the Alembic roadmap to add SQLite migrations&lt;br /&gt;
in a style similar to that of Migrate for a long time, emulating this same&lt;br /&gt;
approach in some way.  Alembic will have this!&lt;br /&gt;
&lt;br /&gt;
However in talking with some folks, it appears like some people might actually&lt;br /&gt;
like this &amp;quot;make a new table and switch it&amp;quot; approach for other databases too,&lt;br /&gt;
as a way to work around locking of tables.  I don't know that I agree with this&lt;br /&gt;
long term, the &amp;quot;ALTER locks the database!&amp;quot; issue in my experience has always&lt;br /&gt;
been kind of a MySQL problem alone, but it can be present for other backends&lt;br /&gt;
as well.&lt;br /&gt;
&lt;br /&gt;
So the API I'm looking towards here is going to try to solve a few problems;&lt;br /&gt;
one is that, copying the whole table to a new one each time for *every single&lt;br /&gt;
column* is crazy, and also that the technique we use on SQLite might be nice&lt;br /&gt;
for other databases too, so here's the API:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
with op.batch_alter(&amp;quot;mytable&amp;quot;, recreate=True) as batch_op:&lt;br /&gt;
    batch_op.drop_column(&amp;quot;some_column&amp;quot;)&lt;br /&gt;
    batch_op.alter_column(&amp;quot;q&amp;quot;, type=Integer)&lt;br /&gt;
    batch_op.add_column(Column('y', String(50), nullable=False))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
That is, we assign a whole series of column operations under the umbrella of&lt;br /&gt;
one table, and they are created at once.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;batch_alter()&amp;lt;/code&amp;gt; call can even be conditional, dropping into traditional&lt;br /&gt;
migration behavior for all backends except SQLite and whatever else:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
with op.batch_alter(&amp;quot;mytable&amp;quot;, recreate_on=('sqlite',)) as batch_op:&lt;br /&gt;
    batch_op.drop_column(&amp;quot;some_column&amp;quot;)&lt;br /&gt;
    batch_op.alter_column(&amp;quot;q&amp;quot;, type=Integer)&lt;br /&gt;
    batch_op.add_column(Column('y', String(50), nullable=False))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
SQlite-compatible alterations is [https://bitbucket.org/zzzeek/alembic/issue/21/column-renames-not-supported-on-sqlite issue #21].&lt;br /&gt;
&lt;br /&gt;
=== Finishing up Autogenerate ===&lt;br /&gt;
&lt;br /&gt;
I started writing Alembic long before SQLAlchemy-Migrate had autogenerate.  But by the time&lt;br /&gt;
I got Alembic released and later autogenerate working, Migrate already had it.&lt;br /&gt;
Here's what we need to finish up in autogenerate:&lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;code&amp;gt;ForeignKeyConstraint&amp;lt;/code&amp;gt; change detection - this is an entirely straightforward feature add and has long been on the todo list.&lt;br /&gt;
&lt;br /&gt;
2. MySQL implicit indexes - this is actually done; recent versions of Alembic can navigate around MySQLs goofy production of indexes automatically on foreign key columns, and not accidentally spit them out in autogenerates. It was seriously tough to get index autogen mostly working on all backends, so new issues will continue to be fixed as they are reported.&lt;br /&gt;
&lt;br /&gt;
3. Type comparison - Alembic balks on type comparison by default, because ultimately SQLAlchemy should add comparision features to its type objects natively. However, on Alembic backends like that of MySQL, we can add explicit rules for the very common and annoying ones, namely the detection of MySQL's TINYINT as a SQLAlchemy Boolean.  Alembic already allows user-specified rules in this regard, so they can be part of oslo.db in any case, however Alembic can also add this rule and others in directly.&lt;br /&gt;
&lt;br /&gt;
4. Table order - Alembic's autogenerate should be spitting out table creates and drops in the order of foreign key dependency, though we don't have good test coverage for this yet and it might not be working.  We'll fix that, no biggie!  Alternatively, Alembic could include a mode of operation that includes all the ForeignKeyConstraint objects after all the tables. Input on how we'd like to see this work would be welcome.&lt;br /&gt;
&lt;br /&gt;
=== New Migration Features ===&lt;br /&gt;
&lt;br /&gt;
There's a lot of features I'd like to add to Alembic, and if OpenStack has&lt;br /&gt;
a need for them, that would justify the effort:&lt;br /&gt;
&lt;br /&gt;
1. Full branch support - [https://bitbucket.org/zzzeek/alembic/issue/167/multiple-heads-branch-resolution-support issue 167 multiple heads / branch resolution support] has a detailed architecture laid out for this feature.  Lots of people are looking for it. This would turn Alembic's current &amp;quot;linked list&amp;quot; version model into a full directed acyclic graph (DAG).   Any particular migration can be dependent on any other group of migrations, or none at all; individual branches can be maintained and upgraded along their path, or automatically merged.&lt;br /&gt;
&lt;br /&gt;
2. [https://bitbucket.org/zzzeek/alembic/issue/124/multiple-versions-directories Multiple version directories] - This would allow migration files to be present in more than one place for a single migrations environment.  Currently, you can get this approach by using multiple base directories, but that requires separate &amp;lt;code&amp;gt;env.py&amp;lt;/code&amp;gt; files for each. With this issue, cross-dependent migration files can live in multiple places, working nicely with the multiple heads support of #167.&lt;br /&gt;
&lt;br /&gt;
== Testing ==&lt;br /&gt;
&lt;br /&gt;
I spent quite a bit looking into testing.  The main thing we can see&lt;br /&gt;
in testing is that there may be a desire for &amp;quot;transactional testing&amp;quot;;&lt;br /&gt;
run tests within a fixed schema, that is the schema    is created&lt;br /&gt;
once, many tests are run, each one rolling back their work, the&lt;br /&gt;
schema is only dropped once all tests for that schema are complete.&lt;br /&gt;
&lt;br /&gt;
Transactional testing is described in the SQLAlchemy docs and in some of&lt;br /&gt;
my talks; for OpenStack, we need the version that supports graceful handling&lt;br /&gt;
of tests that actually do a rollback.  This is doable and the&lt;br /&gt;
recipe for that is currently [https://gist.github.com/zzzeek/8443477 a github gist].&lt;br /&gt;
&lt;br /&gt;
In order to accommodate parallelized runs, the system will create N&lt;br /&gt;
temporary schemas for N concurrent runs.&lt;br /&gt;
&lt;br /&gt;
I really want to get into this feature but as I talk to people, I'm&lt;br /&gt;
getting mixed signals on how important this feature is.   So far I've&lt;br /&gt;
only worked with unit tests, which seem to be entirely against SQLite&lt;br /&gt;
in-memory databases, and I haven't gotten my head around to what&lt;br /&gt;
extent other databases besides SQLite might be desireable in unit&lt;br /&gt;
tests, nor have I gotten to fully understand how integration tests&lt;br /&gt;
run, so input is welcome here.&lt;br /&gt;
&lt;br /&gt;
== Improvement of ORM Patterns ==&lt;br /&gt;
&lt;br /&gt;
Looking around oslo.db and other apps, I want to improve upon&lt;br /&gt;
some kinds of verbosity I see, which make the code more repetetive and inconsistent, welds together application-level and business level concepts (e.g. transaction control and exception reporting melded with data manipulation) and makes it harder to reason about how the application interacts with sessions, as there are a variety of ad-hoc patterns present.&lt;br /&gt;
&lt;br /&gt;
=== Transaction Demarcation ===&lt;br /&gt;
&lt;br /&gt;
For the oft-repeated &amp;quot;get_session(); with session.begin(subtransactions=True)&amp;quot; phrase and other variants, I'd like to move that into something more abstracted:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def my_api_thing(x):&lt;br /&gt;
    with transactional() as session:&lt;br /&gt;
    	# do stuff&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I want context managers and/or decorators that are oslo.db specific to replace any kind of containing code that deals with transactions or sessions. That way the containinment logic can be maintained in once place and configured separately from the business logic contained within.  Projects like Keystone are already using a form more like this; it would be nice to nail down a consistent pattern and get it into oslo.db for everyone to use.&lt;br /&gt;
&lt;br /&gt;
=== MySQLdb + eventlet = sad ===&lt;br /&gt;
&lt;br /&gt;
This is the basic thing that happens with these two actors.   We're in an eventlet &amp;quot;thread&amp;quot;.   The code uses MySQLdb to do something ordinary that locks a row in a table - this can happen with a SELECT FOR UPDATE but it really can happen just on any old UPDATE too depending on isolation level (see https://review.openstack.org/#/c/104111/).   If the eventlet &amp;quot;thread&amp;quot; then does *anything* that causes it to defer out, another eventlet thread might run, and if that thread tries to work with MySQLdb and lock the same row, you're done, it's a deadlock.   The simple reason is, MySQLdb doesn't integrate with eventlet, so a simple thing like blocking on a transactional lock, it doesn't defer.   MySQL times out on these so we're getting exceptions instead of permanent freezes, but IMHO this is a really silly problem.  Eventlet and MySQLdb do not work together, we're done.   MySQLdb 1000% either has to go, or be fixed, even if that means forking it.    A good blog post that illustrates both scenarios is [http://www.diamondtin.com/2014/sqlalchemy-gevent-mysql-python-drivers-comparison/ here] - in this post, the author has used &amp;quot;greenify&amp;quot; to patch MySQLdb's blocking, but without this change (which I'm told isn't compatible with eventlet?), MySQLdb is a nonstarter.    Because it is in pure C, it is faster than MySQL-connector when it's been patched, but if it hasn't, it's dog slow.  MySQL-connector IMHO is probably the driver we should get behind [http://dev.mysql.com/doc/connector-python/en/index.html because it's the official MySQL driver]. The various workarounds and hacks I've seen proposed are all technically viable but IMHO totally unnecessary.   OpenStack should be serious about this, it's a totally solvable problem, let's just solve it.&lt;br /&gt;
&lt;br /&gt;
=== Exception Rewriting ===&lt;br /&gt;
&lt;br /&gt;
Exception rewriting - I notice that the oslo.db pattern includes that DBAPI level exceptions are re-thrown as OpenStack specific;  I'd like to move this into handling using SQLAlchemy events.  The [http://docs.sqlalchemy.org/en/rel_0_9/core/events.html#sqlalchemy.events.ConnectionEvents.dbapi_error dbapi_error] event goes all the way back to 0.7, and [https://bitbucket.org/zzzeek/sqlalchemy/issue/3076/support-reraise-of-custom-exceptions ticket 3076] will allow it to be used to reraise with a new exception.  For older SQLAlchemy versions, I can create a wrapper that will allow it to be used in this way safely for older versions, despite the warnings stated on that document.&lt;br /&gt;
&lt;br /&gt;
=== Pessimistic Locking - SELECT FOR UPDATE ===&lt;br /&gt;
&lt;br /&gt;
This issue has been coming up for some projects, that they make sometimes great use of SQLAlchemy's &amp;lt;code&amp;gt;with_lockmode()&amp;lt;/code&amp;gt; method, which ultimately emits SELECT..FOR UPDATE, so that selected rows are aggressively locked within a transaction.   The issue is that backends like Galera don't support FOR UPDATE, meaning the locking that is desired is not actually provided.&lt;br /&gt;
&lt;br /&gt;
While I'm not sure that there's a generalized solution to this, in some cases I've been helping developers move to more of an &amp;quot;optimistic&amp;quot; approach, where a particular UPDATE statement targets some kind of timestamp or value counter in the row, then counts the number of rows matched.  If the number of rows matched is not what's expected, it is assumed that the UPDATE failed to match the row, and the operation can either try again or can bail out.   This approach might not be suitable in all cases.  In cases where a large stream of data is affecting a relatively small set of rows, it can perhaps be feasible to serialize those updates by sending them all to a queue, so that they are invoked in isolation without the need for up-front locking.   For very high throughput situations, queues can be constructed to use multiple, concurrent UPDATE streams separated via hashing, or can batch together updates for particular rows over a period of time and emit fewer UPDATE statements than if each source of data did it individually.&lt;br /&gt;
&lt;br /&gt;
=== Encouraging Bundle / Baked Query Use ===&lt;br /&gt;
&lt;br /&gt;
Encouraging Bundle use - as stated earlier, I want to look into bringing the very efficient [http://docs.sqlalchemy.org/en/rel_0_9/orm/mapper_config.html#column-bundles Column Bundle] pattern into the fold for mainstream use, not to mention the baked query pattern illustrated previously.&lt;br /&gt;
&lt;br /&gt;
=== Transparent Retries of Transactions ===&lt;br /&gt;
&lt;br /&gt;
I have ready to go an [https://bitbucket.org/zzzeek/sqlalchemy/issue/3104/transaction-replay-extension already in production extension for transaction replaying], developed for a client with the agreement that the extension is open source.  This extension installs as an event handler using a new series of hooks that were added in 0.9, or alternatively using a series of compatibility hooks that were backported to 0.7 and 0.8.   This extension allows logging of SQL statements as they proceed within a transaction, and allows them to be transparently replayed when a database disconnect error occurs. This means that SQLAlchemy's usual behavior of raising an exception and forcing you to re-try explicitly can be hidden so that it occurs implicitly, without any interruption or special error handling required in application code.  The extension includes a lot of configuration options, such as what statements should be replayed and which shouldn't, as well as what exceptions should be handled this way.   Transaction replaying is an elaborate feature which might introduce lots of new edge cases, namely that the state of the database may have changed since the last transaction failed, so to that extent I'm a little wary of this feature myself.  However I have seen lots of messages alluding to this kind of thing.    The mechanics to pull this into SQLAlchemy as a transparent feature are ready to go, if this is a feature people are looking for (I'm not sure if people are looking for this).&lt;br /&gt;
&lt;br /&gt;
== Contributors ==&lt;br /&gt;
&lt;br /&gt;
I would be remiss if I didn't mention that SQLAlchemy, Alembic, Dogpile and everything else I do are collaborative projects, and while SQLAlchemy in particular might present a daunting codebase, we would love to get new collaborators on board!   Any of the features above are fair game and I can walk someone through the steps to be taken, and additionally everything we add features for always needs lots of tests and review, if you're looking for a way to get into the project I can bring you right in, there's lots of (often very tedious and boring) work to be done.   I'm on all the IRC channels now as zzzeek so ping away.&lt;br /&gt;
&lt;br /&gt;
== Coffee and Cake ==&lt;br /&gt;
&lt;br /&gt;
So! I hope you've enjoyed reading this document, and whether you're a fan or a hater, please know that I'm on board now and I'm here to help, with whatever database kind of thing you want to do - it's all part of my job now.    If we wanted to use/build a whole new ORM, I'd be doing that too right alongside.  OpenStack's lessons in database integration are now my lessons too.  So let me know where I should dig in first!  Have a save drive home.&lt;/div&gt;</summary>
		<author><name>Mike Bayer</name></author>	</entry>

	<entry>
		<id>https://wiki.openstack.org/w/index.php?title=OpenStack_and_SQLAlchemy&amp;diff=57275</id>
		<title>OpenStack and SQLAlchemy</title>
		<link rel="alternate" type="text/html" href="https://wiki.openstack.org/w/index.php?title=OpenStack_and_SQLAlchemy&amp;diff=57275"/>
				<updated>2014-07-03T01:20:01Z</updated>
		
		<summary type="html">&lt;p&gt;Mike Bayer: /* MySQLdb + eventlet = sad */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= OpenStack and SQLAlchemy =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
This page describes the ongoing efforts of Mike Bayer, creator/maintainer of&lt;br /&gt;
SQLAlchemy, Alembic Migrations, and Dogpile caching, to spearhead what is&lt;br /&gt;
essentially an &amp;quot;OpenStack SQLAlchemy 2.0&amp;quot; effort - as I am now full-time&lt;br /&gt;
employed by an OpenStack vendor (Red Hat), I've been hired to produce a strong&lt;br /&gt;
and sustained pass through OpenStack's relational database integration,&lt;br /&gt;
improving areas that have been problematic and establishing a very strong&lt;br /&gt;
set of practices that are specific to OpenStack's needs.&lt;br /&gt;
&lt;br /&gt;
Within this effort, I'd like to stress that from my POV, everything is&lt;br /&gt;
on the table, including new improvements / fixes / features to&lt;br /&gt;
SQLAlchemy and Alembic directly, migration of projects for which&lt;br /&gt;
relational databases aren't suitable to non-relational systems,&lt;br /&gt;
improvement of relational schemas on the OpenStack side, and of course&lt;br /&gt;
improvement of exising relational patterns within OpenStack&lt;br /&gt;
applications, for which oslo.db will serve as the foundation.&lt;br /&gt;
&lt;br /&gt;
As of June 30, 2014, I've been on the job for about a month, so below&lt;br /&gt;
is an outline of most of what I've come across and my thoughts going forward.&lt;br /&gt;
&lt;br /&gt;
My goal here is to share with the community what I've seen and done so far,&lt;br /&gt;
so that interested parties can chime in with further advice and information,&lt;br /&gt;
and most importantly to help steer me towards the initial blueprints that&lt;br /&gt;
I should be starting on, that is, which features are most likely to be&lt;br /&gt;
accepted by the community as well as which are the top priority.  Not to mention, if you're looking&lt;br /&gt;
for something to do, you can help me implement some of these things!&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== OpenStack presence within SQLAlchemy's CI environment (a.k.a. &amp;quot;The Version that shall not be Named&amp;quot;) ==&lt;br /&gt;
&lt;br /&gt;
On Monday, June 23, I got up in the AM with two goals: get a release of SQLAlchemy&lt;br /&gt;
out the door, as it had been several months since the last point release, and then&lt;br /&gt;
do some work learning how Nova's unit tests run and function.&lt;br /&gt;
&lt;br /&gt;
Release 0.9.5 of SQLAlchemy was not particularly different than any of the other&lt;br /&gt;
hundred or so point releases I've put out over nine years, it was a little more overdue&lt;br /&gt;
than some, and out it went.  Later that day, as I was learning to run Nova's test suite, I&lt;br /&gt;
was hitting some failures within the FixedIp/FloatingIp tests for which I couldn't&lt;br /&gt;
find the cause, assuming that I just wasn't setting up my environment correctly,&lt;br /&gt;
as Nova's tests are passing for all the Jenkins servers the vast majority of the time.&lt;br /&gt;
&lt;br /&gt;
As we learned within about 30 minutes of my hitting these problems, it&lt;br /&gt;
turns out 0.9.5 had a bad fix in it; one that didn't fail any of&lt;br /&gt;
SQLAlchemy's 5000 tests but hit a use case within Nova that was not&lt;br /&gt;
very unusual; not the kind of use I usually do myself but nonetheless&lt;br /&gt;
something that was pretty common.   IRC began  jumping that builds&lt;br /&gt;
were failing everywhere and it was due to the 0.9.5 release!   The&lt;br /&gt;
bizarre coiincidence of this was that none of the thirty-odd point&lt;br /&gt;
releases of SQLAlchemy released under OpenStack's lifetime have ever&lt;br /&gt;
really had this effect, only the major jumps saw any issues.&lt;br /&gt;
&lt;br /&gt;
Luckily, as I had just learned 30 minutes earlier how to run Nova's tests I was&lt;br /&gt;
able to drill down to exactly what was going on and SQLAlchemy 0.9.6 was out&lt;br /&gt;
within an hour, but we shall now forever see in requirements.txt, the Version&lt;br /&gt;
that shall Not Be Named (except in requirements.txt):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
SQLAlchemy&amp;gt;=0.7.8,!=0.9.5,&amp;lt;=0.9.99&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In response to this I have now&lt;br /&gt;
[http://jenkins.sqlalchemy.org/job/openstack_sqla_master/ added openstack builds to our Jenkins CI system],&lt;br /&gt;
so far running tests for oslo.db and the SQLAlchemy tests within Nova; I spent&lt;br /&gt;
a couple of days figuring out how to get everything to build and run against&lt;br /&gt;
specific versions of SQLAlchemy and Alembic, even versions above the 0.9.99 in&lt;br /&gt;
all the requirements files as we are set for 1.0 hopefully within the next year.&lt;br /&gt;
I will be adding in as many other OpenStack projects as I can here so that we will no&lt;br /&gt;
longer have a SQLAlchemy release that hasn't been run against a battery of&lt;br /&gt;
basic OpenStack tests.&lt;br /&gt;
&lt;br /&gt;
== ORM Woes ==&lt;br /&gt;
&lt;br /&gt;
After chatting with a decent amount of folks for a month, I can break up&lt;br /&gt;
the take on OpenStack / SQLAlchemy ORM into three camps:&lt;br /&gt;
&lt;br /&gt;
1. We love SQLAlchemy ORM, we know that we can improve how we use it and we're looking forward to getting there (oslo.db, Keystone)&lt;br /&gt;
&lt;br /&gt;
2. Our app uses a relational database but the SQLAlchemy ORM slows us down, we already have our own &amp;quot;orm-like&amp;quot; system (e.g. APIs that shuttle rows into &amp;quot;values&amp;quot; dictionaries, nova.objects, etc.) so we want to ultimately get rid of the ORM part, and probably continue using SQLAlchemy Core to some degree (some Nova developers I've spoken to).&lt;br /&gt;
&lt;br /&gt;
3. Our application has a SQLAlchemy backend that almost nobody uses in production, most users use MongoDB, and the SQLAlchemy backend is only there because MongoDB is AGPL (Ceilometer, Marconi).  We'd like to get a better NoSQL backend without Mongo's licensing issue and do away with relational storage entirely ([http://lists.openstack.org/pipermail/openstack-dev/2014-June/038778.html Marconi]).&lt;br /&gt;
&lt;br /&gt;
Here's my observations / thoughts about these three camps:&lt;br /&gt;
&lt;br /&gt;
1. Great!  Read on, I have some things we can start with.&lt;br /&gt;
&lt;br /&gt;
2. I agree that a lot of the patterns that I've seen in Nova in particular are not well-suited to the approach used by SQLAlchemy's ORM, however I have observed that the applications still rely a lot on very sophisticated and mature features provided by SQLAlchemy (namely &amp;lt;code&amp;gt;relationship()&amp;lt;/code&amp;gt; and it's integration with eager loading and the unit of work features), and throwing it away entirely will incur a lot of reinvention, not just of the features that were lost but also of a totally new crop of stability and performance issues to solve.&lt;br /&gt;
&lt;br /&gt;
To that degree, I'd like to propose a twist to the &amp;quot;we want to write our own ORM&amp;quot; idea (edit: OK nova.objects is really an ORM-like thing on top of their API on top of relational DB, but that's an issue for another day) and say, &amp;quot;let's write our own ORM that's based on the SQLAlchemy ORM primitives&amp;quot;, e.g. a hybrid approach where existing ORM logic that is fine can stay that way, ORM logic that has performance or behavioral issues can be reorganized using systems, some of them new, some of them just underutilized, that bypass unneeded SQLAlchemy features on a case-by-case basis.&lt;br /&gt;
&lt;br /&gt;
As a secondary issue, some devs I've spoken to have referred to the nature&lt;br /&gt;
of OpenStack API-based applications being a part of the problem.&lt;br /&gt;
This refers to the fact that OpenStack apps like Nova&lt;br /&gt;
and Neutron expose fine-grained APIs which end up&lt;br /&gt;
breaking out composed use cases into dozens or hundreds of small API&lt;br /&gt;
operations, each of which invokes within it's own database transaction and&lt;br /&gt;
ORM session state.  This makes it impossible, without introducing some elaborate&lt;br /&gt;
offline state management system, for a larger operation to be&lt;br /&gt;
performed so that larger groups of objects can be loaded and manipulated&lt;br /&gt;
at once, greatly decreasing the number of database round trips present.&lt;br /&gt;
No matter what ORM you use or don't use, the pattern of many small round trips&lt;br /&gt;
is difficult to scale on relational databases.&lt;br /&gt;
&lt;br /&gt;
There are under way projects which aim to apply detailed profiling&lt;br /&gt;
to OpenStack applications so that a breakdown of performance issues due&lt;br /&gt;
to API use and/or database&lt;br /&gt;
access might be plainly reasoned about; see [https://github.com/stackforge/rally Rally]&lt;br /&gt;
and [https://github.com/stackforge/osprofiler OS Profiler] for examples.&lt;br /&gt;
&lt;br /&gt;
On my end, I have no intention of focusing on this second issue for the time&lt;br /&gt;
being, I'm going to stick with leaving OS applications as much as they are right now as possible,&lt;br /&gt;
applying iterative changes to database access code which I hope can optimize&lt;br /&gt;
to a significant degree within the current usage patterns.&lt;br /&gt;
&lt;br /&gt;
3. Projects with alternate MongoDB / MySQL backends (since after all, MySQL is the database here being rejected, SQLAlchemy is just a Python API) should probably do what they're doing and go to an all key/value model.  It is not at all fun to maintain the same datamodel and API against both a KV store as well as relational tables.  I had a detailed look at Ceilometer with its team members early on, and while I'm pretty confident that if it were desireable, I could get it's relational backend to compete with the Mongo backend performance-wise, there's no reason to get into this if the Mongo licensing issue can be solved alone.&lt;br /&gt;
&lt;br /&gt;
== ORM Quick Wins Proof of Concept ==&lt;br /&gt;
&lt;br /&gt;
I've built up a proof of concept at [https://github.com/zzzeek/nova_poc nova_poc] which seeks to demonstrate&lt;br /&gt;
the range of database and ORM performance that can be achieved within&lt;br /&gt;
one particular method of the Nova API.   Naturally, I picked an example that&lt;br /&gt;
is very juicy in this regard; it has a very easily fixable issue with the&lt;br /&gt;
kind of query it emits that has an enormous 10x impact on its performance&lt;br /&gt;
(which I feel is worth illustrating just for how dramatic it is),&lt;br /&gt;
and it then illustrates some new SQLAlchemy extensions that more or less&lt;br /&gt;
may be going into SQLAlchemy directly, which allow the remaining operation&lt;br /&gt;
to complete in less than half the time.  These extensions can be ported to&lt;br /&gt;
oslo.db to work against all supported SQLAlchemy versions prior to the official&lt;br /&gt;
release of these features.&lt;br /&gt;
&lt;br /&gt;
To summarize, we're looking at the API method &amp;lt;code&amp;gt;floating_ip_update()&amp;lt;/code&amp;gt;, and&lt;br /&gt;
I run a series of profiles against a database table 10K rows (e.g. &amp;lt;code&amp;gt;FloatingIp&amp;lt;/code&amp;gt;&lt;br /&gt;
objects), where each &amp;lt;code&amp;gt;FloatingIp&amp;lt;/code&amp;gt; refers to a single &amp;lt;code&amp;gt;FixedIp&amp;lt;/code&amp;gt; that then&lt;br /&gt;
refers to an &amp;lt;code&amp;gt;Instance&amp;lt;/code&amp;gt;.   The profiling focuses on the nature of this&lt;br /&gt;
method as an API feature all it's own, and simulates the case where the API&lt;br /&gt;
method is called thousands of times, not unlike other API methods that seek&lt;br /&gt;
to add some small amount of data each time they are called.&lt;br /&gt;
&lt;br /&gt;
=== Eager load and Column load tuning ===&lt;br /&gt;
&lt;br /&gt;
Turning off an eagerload in the &amp;lt;code&amp;gt;_floating_ip_get_by_address()&amp;lt;/code&amp;gt; function&lt;br /&gt;
is the most dramatic win; while removing this method seems like a reasonable&lt;br /&gt;
fix to remove some unneeded overhead, in profiling it shows itself to&lt;br /&gt;
be enormous:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Running scenario default&lt;br /&gt;
Scenario default, total calls for 1000 operations: 24543047&lt;br /&gt;
Scenario default, total time for 10000 operations: 222&lt;br /&gt;
&lt;br /&gt;
Running scenario default_optimized&lt;br /&gt;
Scenario default_optimized, total calls for 1000 operations: 1881626&lt;br /&gt;
Scenario default_optimized, total time for 10000 operations: 25&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
While not illustrated in the POC code itself, talk with various OpenStack&lt;br /&gt;
devs has revealed that we could probably do a lot more individual column&lt;br /&gt;
fetching as well, that is instead of:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
session.query(FixedIp).all()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We say:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
session.query(FixedIp.address, FixedIp.host, FixedIp.reserved).all()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
By loading indivdual columns, we get read-only objects that are not identity&lt;br /&gt;
managed, bypassing a huge amount of work that is done on instances both&lt;br /&gt;
in instantiating them, populating relationships and doing bookkeeping.&lt;br /&gt;
Any API feature that only returns a simple read-only object or a &amp;quot;values&amp;quot;&lt;br /&gt;
dictionary that does not rely upon relationship loading should be using&lt;br /&gt;
this pattern, or similar, by using a&lt;br /&gt;
[http://docs.sqlalchemy.org/en/rel_0_9/orm/mapper_config.html#column-bundles Column Bundle].&lt;br /&gt;
I will be exploring adding the following feature to oslo.db:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
session.query(FixedIp.bundle).all()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The above will return a fast-loading, read-only Bundle object that otherwise&lt;br /&gt;
expresses the full interface of &amp;lt;code&amp;gt;FixedIp&amp;lt;/code&amp;gt;.   Relationships might be tricky&lt;br /&gt;
here but I can at least get them on board as regular lazy loads (just like&lt;br /&gt;
any other ORM would do anyway).&lt;br /&gt;
&lt;br /&gt;
=== Fast Object Save ===&lt;br /&gt;
&lt;br /&gt;
We explore replacing the unit of work &amp;lt;code&amp;gt;flush()&amp;lt;/code&amp;gt; call used by &amp;lt;code&amp;gt;object.save()&amp;lt;/code&amp;gt; within oslo.db-style applications with a more direct &amp;quot;flush single object&amp;quot; call; this grants us only a 12% savings so far, though it is very simple to use:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Running scenario default_optimized&lt;br /&gt;
Scenario default_optimized, total calls for 1000 operations: 1881626&lt;br /&gt;
Scenario default_optimized, total time for 10000 operations: 25&lt;br /&gt;
&lt;br /&gt;
Running scenario fast_save&lt;br /&gt;
Scenario fast_save, total calls for 1000 operations: 1685221&lt;br /&gt;
Scenario fast_save, total time for 10000 operations: 22&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Baked Queries ===&lt;br /&gt;
&lt;br /&gt;
Something that has been in the works for a long time and has&lt;br /&gt;
recently    seen lots of work in the past months is the &amp;quot;baked query&amp;quot;&lt;br /&gt;
feature; this    pattern is ideal for OpenStack's &amp;quot;many short queries&amp;quot;&lt;br /&gt;
pattern, and allows    caching of the generation of SQL.  Recent&lt;br /&gt;
versions of this pattern have    gotten very slick, and can cache&lt;br /&gt;
virtually everything that happens Python-wise    from the construction&lt;br /&gt;
of the &amp;lt;code&amp;gt;Query&amp;lt;/code&amp;gt; object, to calling all the methods    on the&lt;br /&gt;
query, to the query-objects construction of a Core SQL statement,&lt;br /&gt;
to the compilation of that statement as a string - all of these steps&lt;br /&gt;
are removed from the call-graph after the first such call.  In&lt;br /&gt;
SQLAlchemy 1.0    I've also thrown in the construction of column&lt;br /&gt;
metadata from the result set    too.   The pattern involves a bit more&lt;br /&gt;
verbosity to that of constructing a    query, where here I've built&lt;br /&gt;
off of some of the ideas of the    Pony ORM to use Python function&lt;br /&gt;
information as the source of a cache key.    A query such as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
result = model_query(&lt;br /&gt;
            context, models.FloatingIp, session=session).\&lt;br /&gt;
            filter_by(address=address)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
would be expressed in &amp;quot;baked&amp;quot; form as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# note model_query is using the &amp;quot;baked&amp;quot; process internally as well&lt;br /&gt;
result = model_query(context, models.FloatingIp, session=session)&lt;br /&gt;
&lt;br /&gt;
result.bake(lambda query:&lt;br /&gt;
    query.filter_by(&lt;br /&gt;
        address=bindparam('address'))).params(address=address)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the above form, everything within each lambda is invoked only once,&lt;br /&gt;
the result of which becomes part of a cached value.&lt;br /&gt;
&lt;br /&gt;
For this slight increase in verbosity, we get an improvement like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Running scenario default_optimized&lt;br /&gt;
Scenario default_optimized, total calls for 1000 operations: 1881626&lt;br /&gt;
Scenario default_optimized, total time for 10000 operations: 25&lt;br /&gt;
&lt;br /&gt;
Running scenario baked&lt;br /&gt;
Scenario baked, total calls for 1000 operations: 1052935&lt;br /&gt;
Scenario baked, total time for 10000 operations: 16&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
That is, around a 40% improvement.&lt;br /&gt;
&lt;br /&gt;
Putting together both &amp;quot;fast save&amp;quot; plus &amp;quot;baked&amp;quot; we get down to a full 50%&lt;br /&gt;
improvement vs. the plain optimized version:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Running scenario fast_save_plus_baked&lt;br /&gt;
Scenario fast_save_plus_baked, total calls for 1000 operations: 856035&lt;br /&gt;
Scenario fast_save_plus_baked, total time for 10000 operations: 13&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
13 seconds for 10K operations vs. 222 seconds in the current code, I hope&lt;br /&gt;
I've established that there is hope!&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Migrations ==&lt;br /&gt;
&lt;br /&gt;
Migrations are a huge deal.  Here's where it looks like this is going.&lt;br /&gt;
&lt;br /&gt;
Alembic - &amp;quot;Alembic is the defacto standard going forward&amp;quot; - ultimately&lt;br /&gt;
we want to get everything SQLAlchemy-Migrate onto Alembic.  And in talking&lt;br /&gt;
with Nova devs, they really like that they can test their migrations against&lt;br /&gt;
SQLite.  So from my POV, SQLite migrations on Alembic, we're doing that!&lt;br /&gt;
Unless folks think otherwise.&lt;br /&gt;
&lt;br /&gt;
=== SQLite Support ===&lt;br /&gt;
&lt;br /&gt;
SQLite has extremely limited support for ALTER TABLE, essentially only being able&lt;br /&gt;
to add new columns, and that's about it.  In order to provide the full suite&lt;br /&gt;
of ALTER operations that all other databases provide, tools such as&lt;br /&gt;
SQLAlchemy-Migrate will create a copy of the target table with changes applied,&lt;br /&gt;
copy data from old table to new, then drop the old table and rename the new one.&lt;br /&gt;
I've always been peeved at SQLite's insistence on never providing some better&lt;br /&gt;
pathway to ALTER built in.  But it has been on the Alembic roadmap to add SQLite migrations&lt;br /&gt;
in a style similar to that of Migrate for a long time, emulating this same&lt;br /&gt;
approach in some way.  Alembic will have this!&lt;br /&gt;
&lt;br /&gt;
However in talking with some folks, it appears like some people might actually&lt;br /&gt;
like this &amp;quot;make a new table and switch it&amp;quot; approach for other databases too,&lt;br /&gt;
as a way to work around locking of tables.  I don't know that I agree with this&lt;br /&gt;
long term, the &amp;quot;ALTER locks the database!&amp;quot; issue in my experience has always&lt;br /&gt;
been kind of a MySQL problem alone, but it can be present for other backends&lt;br /&gt;
as well.&lt;br /&gt;
&lt;br /&gt;
So the API I'm looking towards here is going to try to solve a few problems;&lt;br /&gt;
one is that, copying the whole table to a new one each time for *every single&lt;br /&gt;
column* is crazy, and also that the technique we use on SQLite might be nice&lt;br /&gt;
for other databases too, so here's the API:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
with op.batch_alter(&amp;quot;mytable&amp;quot;, recreate=True) as batch_op:&lt;br /&gt;
    batch_op.drop_column(&amp;quot;some_column&amp;quot;)&lt;br /&gt;
    batch_op.alter_column(&amp;quot;q&amp;quot;, type=Integer)&lt;br /&gt;
    batch_op.add_column(Column('y', String(50), nullable=False))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
That is, we assign a whole series of column operations under the umbrella of&lt;br /&gt;
one table, and they are created at once.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;batch_alter()&amp;lt;/code&amp;gt; call can even be conditional, dropping into traditional&lt;br /&gt;
migration behavior for all backends except SQLite and whatever else:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
with op.batch_alter(&amp;quot;mytable&amp;quot;, recreate_on=('sqlite',)) as batch_op:&lt;br /&gt;
    batch_op.drop_column(&amp;quot;some_column&amp;quot;)&lt;br /&gt;
    batch_op.alter_column(&amp;quot;q&amp;quot;, type=Integer)&lt;br /&gt;
    batch_op.add_column(Column('y', String(50), nullable=False))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
SQlite-compatible alterations is [https://bitbucket.org/zzzeek/alembic/issue/21/column-renames-not-supported-on-sqlite issue #21].&lt;br /&gt;
&lt;br /&gt;
=== Finishing up Autogenerate ===&lt;br /&gt;
&lt;br /&gt;
I started writing Alembic long before SQLAlchemy-Migrate had autogenerate.  But by the time&lt;br /&gt;
I got Alembic released and later autogenerate working, Migrate already had it.&lt;br /&gt;
Here's what we need to finish up in autogenerate:&lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;code&amp;gt;ForeignKeyConstraint&amp;lt;/code&amp;gt; change detection - this is an entirely straightforward feature add and has long been on the todo list.&lt;br /&gt;
&lt;br /&gt;
2. MySQL implicit indexes - this is actually done; recent versions of Alembic can navigate around MySQLs goofy production of indexes automatically on foreign key columns, and not accidentally spit them out in autogenerates. It was seriously tough to get index autogen mostly working on all backends, so new issues will continue to be fixed as they are reported.&lt;br /&gt;
&lt;br /&gt;
3. Type comparison - Alembic balks on type comparison by default, because ultimately SQLAlchemy should add comparision features to its type objects natively. However, on Alembic backends like that of MySQL, we can add explicit rules for the very common and annoying ones, namely the detection of MySQL's TINYINT as a SQLAlchemy Boolean.  Alembic already allows user-specified rules in this regard, so they can be part of oslo.db in any case, however Alembic can also add this rule and others in directly.&lt;br /&gt;
&lt;br /&gt;
4. Table order - Alembic's autogenerate should be spitting out table creates and drops in the order of foreign key dependency, though we don't have good test coverage for this yet and it might not be working.  We'll fix that, no biggie!  Alternatively, Alembic could include a mode of operation that includes all the ForeignKeyConstraint objects after all the tables. Input on how we'd like to see this work would be welcome.&lt;br /&gt;
&lt;br /&gt;
=== New Migration Features ===&lt;br /&gt;
&lt;br /&gt;
There's a lot of features I'd like to add to Alembic, and if OpenStack has&lt;br /&gt;
a need for them, that would justify the effort:&lt;br /&gt;
&lt;br /&gt;
1. Full branch support - [https://bitbucket.org/zzzeek/alembic/issue/167/multiple-heads-branch-resolution-support issue 167 multiple heads / branch resolution support] has a detailed architecture laid out for this feature.  Lots of people are looking for it. This would turn Alembic's current &amp;quot;linked list&amp;quot; version model into a full directed acyclic graph (DAG).   Any particular migration can be dependent on any other group of migrations, or none at all; individual branches can be maintained and upgraded along their path, or automatically merged.&lt;br /&gt;
&lt;br /&gt;
2. [https://bitbucket.org/zzzeek/alembic/issue/124/multiple-versions-directories Multiple version directories] - This would allow migration files to be present in more than one place for a single migrations environment.  Currently, you can get this approach by using multiple base directories, but that requires separate &amp;lt;code&amp;gt;env.py&amp;lt;/code&amp;gt; files for each. With this issue, cross-dependent migration files can live in multiple places, working nicely with the multiple heads support of #167.&lt;br /&gt;
&lt;br /&gt;
== Testing ==&lt;br /&gt;
&lt;br /&gt;
I spent quite a bit looking into testing.  The main thing we can see&lt;br /&gt;
in testing is that there may be a desire for &amp;quot;transactional testing&amp;quot;;&lt;br /&gt;
run tests within a fixed schema, that is the schema    is created&lt;br /&gt;
once, many tests are run, each one rolling back their work, the&lt;br /&gt;
schema is only dropped once all tests for that schema are complete.&lt;br /&gt;
&lt;br /&gt;
Transactional testing is described in the SQLAlchemy docs and in some of&lt;br /&gt;
my talks; for OpenStack, we need the version that supports graceful handling&lt;br /&gt;
of tests that actually do a rollback.  This is doable and the&lt;br /&gt;
recipe for that is currently [https://gist.github.com/zzzeek/8443477 a github gist].&lt;br /&gt;
&lt;br /&gt;
In order to accommodate parallelized runs, the system will create N&lt;br /&gt;
temporary schemas for N concurrent runs.&lt;br /&gt;
&lt;br /&gt;
I really want to get into this feature but as I talk to people, I'm&lt;br /&gt;
getting mixed signals on how important this feature is.   So far I've&lt;br /&gt;
only worked with unit tests, which seem to be entirely against SQLite&lt;br /&gt;
in-memory databases, and I haven't gotten my head around to what&lt;br /&gt;
extent other databases besides SQLite might be desireable in unit&lt;br /&gt;
tests, nor have I gotten to fully understand how integration tests&lt;br /&gt;
run, so input is welcome here.&lt;br /&gt;
&lt;br /&gt;
== Improvement of ORM Patterns ==&lt;br /&gt;
&lt;br /&gt;
Looking around oslo.db and other apps, I want to improve upon&lt;br /&gt;
some kinds of verbosity I see, which make the code more repetetive and inconsistent, welds together application-level and business level concepts (e.g. transaction control and exception reporting melded with data manipulation) and makes it harder to reason about how the application interacts with sessions, as there are a variety of ad-hoc patterns present.&lt;br /&gt;
&lt;br /&gt;
=== Transaction Demarcation ===&lt;br /&gt;
&lt;br /&gt;
For the oft-repeated &amp;quot;get_session(); with session.begin(subtransactions=True)&amp;quot; phrase and other variants, I'd like to move that into something more abstracted:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def my_api_thing(x):&lt;br /&gt;
    with transactional() as session:&lt;br /&gt;
    	# do stuff&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I want context managers and/or decorators that are oslo.db specific to replace any kind of containing code that deals with transactions or sessions. That way the containinment logic can be maintained in once place and configured separately from the business logic contained within.  Projects like Keystone are already using a form more like this; it would be nice to nail down a consistent pattern and get it into oslo.db for everyone to use.&lt;br /&gt;
&lt;br /&gt;
=== MySQLdb + eventlet = sad ===&lt;br /&gt;
&lt;br /&gt;
This is the basic thing that happens with these two actors.   We're in an eventlet &amp;quot;thread&amp;quot;.   The code uses MySQLdb to do something ordinary that locks a row in a table - this can happen with a SELECT FOR UPDATE but it really can happen just on any old UPDATE too depending on isolation level (see https://review.openstack.org/#/c/104111/).   If the eventlet &amp;quot;thread&amp;quot; then does *anything* that causes it to defer out, another eventlet thread might run, and if that thread tries to work with MySQLdb and lock the same row, you're done, it's a deadlock.   The simple reason is, MySQLdb doesn't integrate with eventlet, so a simple thing like blocking on a table, it doesn't defer.   MySQL times out on these so we're getting exceptions instead of permanent freezes, but IMHO this is a really silly problem.  Eventlet and MySQLdb do not work together, we're done.   MySQLdb 1000% either has to go, or be fixed, even if that means forking it.    A good blog post that illustrates both scenarios is [http://www.diamondtin.com/2014/sqlalchemy-gevent-mysql-python-drivers-comparison/ here] - in this post, the author has used &amp;quot;greenify&amp;quot; to patch MySQLdb's blocking, but without this change (which I'm told isn't compatible with eventlet?), MySQLdb is a nonstarter.    Because it is in pure C, it is faster than MySQL-connector when it's been patched, but if it hasn't, it's dog slow.  MySQL-connector IMHO is probably the driver we should get behind [http://dev.mysql.com/doc/connector-python/en/index.html because it's the official MySQL driver]. The various workarounds and hacks I've seen proposed are all technically viable but IMHO totally unnecessary.   OpenStack should be serious about this, it's a totally solvable problem, let's just solve it.&lt;br /&gt;
&lt;br /&gt;
=== Exception Rewriting ===&lt;br /&gt;
&lt;br /&gt;
Exception rewriting - I notice that the oslo.db pattern includes that DBAPI level exceptions are re-thrown as OpenStack specific;  I'd like to move this into handling using SQLAlchemy events.  The [http://docs.sqlalchemy.org/en/rel_0_9/core/events.html#sqlalchemy.events.ConnectionEvents.dbapi_error dbapi_error] event goes all the way back to 0.7, and [https://bitbucket.org/zzzeek/sqlalchemy/issue/3076/support-reraise-of-custom-exceptions ticket 3076] will allow it to be used to reraise with a new exception.  For older SQLAlchemy versions, I can create a wrapper that will allow it to be used in this way safely for older versions, despite the warnings stated on that document.&lt;br /&gt;
&lt;br /&gt;
=== Pessimistic Locking - SELECT FOR UPDATE ===&lt;br /&gt;
&lt;br /&gt;
This issue has been coming up for some projects, that they make sometimes great use of SQLAlchemy's &amp;lt;code&amp;gt;with_lockmode()&amp;lt;/code&amp;gt; method, which ultimately emits SELECT..FOR UPDATE, so that selected rows are aggressively locked within a transaction.   The issue is that backends like Galera don't support FOR UPDATE, meaning the locking that is desired is not actually provided.&lt;br /&gt;
&lt;br /&gt;
While I'm not sure that there's a generalized solution to this, in some cases I've been helping developers move to more of an &amp;quot;optimistic&amp;quot; approach, where a particular UPDATE statement targets some kind of timestamp or value counter in the row, then counts the number of rows matched.  If the number of rows matched is not what's expected, it is assumed that the UPDATE failed to match the row, and the operation can either try again or can bail out.   This approach might not be suitable in all cases.  In cases where a large stream of data is affecting a relatively small set of rows, it can perhaps be feasible to serialize those updates by sending them all to a queue, so that they are invoked in isolation without the need for up-front locking.   For very high throughput situations, queues can be constructed to use multiple, concurrent UPDATE streams separated via hashing, or can batch together updates for particular rows over a period of time and emit fewer UPDATE statements than if each source of data did it individually.&lt;br /&gt;
&lt;br /&gt;
=== Encouraging Bundle / Baked Query Use ===&lt;br /&gt;
&lt;br /&gt;
Encouraging Bundle use - as stated earlier, I want to look into bringing the very efficient [http://docs.sqlalchemy.org/en/rel_0_9/orm/mapper_config.html#column-bundles Column Bundle] pattern into the fold for mainstream use, not to mention the baked query pattern illustrated previously.&lt;br /&gt;
&lt;br /&gt;
=== Transparent Retries of Transactions ===&lt;br /&gt;
&lt;br /&gt;
I have ready to go an [https://bitbucket.org/zzzeek/sqlalchemy/issue/3104/transaction-replay-extension already in production extension for transaction replaying], developed for a client with the agreement that the extension is open source.  This extension installs as an event handler using a new series of hooks that were added in 0.9, or alternatively using a series of compatibility hooks that were backported to 0.7 and 0.8.   This extension allows logging of SQL statements as they proceed within a transaction, and allows them to be transparently replayed when a database disconnect error occurs. This means that SQLAlchemy's usual behavior of raising an exception and forcing you to re-try explicitly can be hidden so that it occurs implicitly, without any interruption or special error handling required in application code.  The extension includes a lot of configuration options, such as what statements should be replayed and which shouldn't, as well as what exceptions should be handled this way.   Transaction replaying is an elaborate feature which might introduce lots of new edge cases, namely that the state of the database may have changed since the last transaction failed, so to that extent I'm a little wary of this feature myself.  However I have seen lots of messages alluding to this kind of thing.    The mechanics to pull this into SQLAlchemy as a transparent feature are ready to go, if this is a feature people are looking for (I'm not sure if people are looking for this).&lt;br /&gt;
&lt;br /&gt;
== Contributors ==&lt;br /&gt;
&lt;br /&gt;
I would be remiss if I didn't mention that SQLAlchemy, Alembic, Dogpile and everything else I do are collaborative projects, and while SQLAlchemy in particular might present a daunting codebase, we would love to get new collaborators on board!   Any of the features above are fair game and I can walk someone through the steps to be taken, and additionally everything we add features for always needs lots of tests and review, if you're looking for a way to get into the project I can bring you right in, there's lots of (often very tedious and boring) work to be done.   I'm on all the IRC channels now as zzzeek so ping away.&lt;br /&gt;
&lt;br /&gt;
== Coffee and Cake ==&lt;br /&gt;
&lt;br /&gt;
So! I hope you've enjoyed reading this document, and whether you're a fan or a hater, please know that I'm on board now and I'm here to help, with whatever database kind of thing you want to do - it's all part of my job now.    If we wanted to use/build a whole new ORM, I'd be doing that too right alongside.  OpenStack's lessons in database integration are now my lessons too.  So let me know where I should dig in first!  Have a save drive home.&lt;/div&gt;</summary>
		<author><name>Mike Bayer</name></author>	</entry>

	<entry>
		<id>https://wiki.openstack.org/w/index.php?title=OpenStack_and_SQLAlchemy&amp;diff=57274</id>
		<title>OpenStack and SQLAlchemy</title>
		<link rel="alternate" type="text/html" href="https://wiki.openstack.org/w/index.php?title=OpenStack_and_SQLAlchemy&amp;diff=57274"/>
				<updated>2014-07-03T01:18:40Z</updated>
		
		<summary type="html">&lt;p&gt;Mike Bayer: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= OpenStack and SQLAlchemy =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
This page describes the ongoing efforts of Mike Bayer, creator/maintainer of&lt;br /&gt;
SQLAlchemy, Alembic Migrations, and Dogpile caching, to spearhead what is&lt;br /&gt;
essentially an &amp;quot;OpenStack SQLAlchemy 2.0&amp;quot; effort - as I am now full-time&lt;br /&gt;
employed by an OpenStack vendor (Red Hat), I've been hired to produce a strong&lt;br /&gt;
and sustained pass through OpenStack's relational database integration,&lt;br /&gt;
improving areas that have been problematic and establishing a very strong&lt;br /&gt;
set of practices that are specific to OpenStack's needs.&lt;br /&gt;
&lt;br /&gt;
Within this effort, I'd like to stress that from my POV, everything is&lt;br /&gt;
on the table, including new improvements / fixes / features to&lt;br /&gt;
SQLAlchemy and Alembic directly, migration of projects for which&lt;br /&gt;
relational databases aren't suitable to non-relational systems,&lt;br /&gt;
improvement of relational schemas on the OpenStack side, and of course&lt;br /&gt;
improvement of exising relational patterns within OpenStack&lt;br /&gt;
applications, for which oslo.db will serve as the foundation.&lt;br /&gt;
&lt;br /&gt;
As of June 30, 2014, I've been on the job for about a month, so below&lt;br /&gt;
is an outline of most of what I've come across and my thoughts going forward.&lt;br /&gt;
&lt;br /&gt;
My goal here is to share with the community what I've seen and done so far,&lt;br /&gt;
so that interested parties can chime in with further advice and information,&lt;br /&gt;
and most importantly to help steer me towards the initial blueprints that&lt;br /&gt;
I should be starting on, that is, which features are most likely to be&lt;br /&gt;
accepted by the community as well as which are the top priority.  Not to mention, if you're looking&lt;br /&gt;
for something to do, you can help me implement some of these things!&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== OpenStack presence within SQLAlchemy's CI environment (a.k.a. &amp;quot;The Version that shall not be Named&amp;quot;) ==&lt;br /&gt;
&lt;br /&gt;
On Monday, June 23, I got up in the AM with two goals: get a release of SQLAlchemy&lt;br /&gt;
out the door, as it had been several months since the last point release, and then&lt;br /&gt;
do some work learning how Nova's unit tests run and function.&lt;br /&gt;
&lt;br /&gt;
Release 0.9.5 of SQLAlchemy was not particularly different than any of the other&lt;br /&gt;
hundred or so point releases I've put out over nine years, it was a little more overdue&lt;br /&gt;
than some, and out it went.  Later that day, as I was learning to run Nova's test suite, I&lt;br /&gt;
was hitting some failures within the FixedIp/FloatingIp tests for which I couldn't&lt;br /&gt;
find the cause, assuming that I just wasn't setting up my environment correctly,&lt;br /&gt;
as Nova's tests are passing for all the Jenkins servers the vast majority of the time.&lt;br /&gt;
&lt;br /&gt;
As we learned within about 30 minutes of my hitting these problems, it&lt;br /&gt;
turns out 0.9.5 had a bad fix in it; one that didn't fail any of&lt;br /&gt;
SQLAlchemy's 5000 tests but hit a use case within Nova that was not&lt;br /&gt;
very unusual; not the kind of use I usually do myself but nonetheless&lt;br /&gt;
something that was pretty common.   IRC began  jumping that builds&lt;br /&gt;
were failing everywhere and it was due to the 0.9.5 release!   The&lt;br /&gt;
bizarre coiincidence of this was that none of the thirty-odd point&lt;br /&gt;
releases of SQLAlchemy released under OpenStack's lifetime have ever&lt;br /&gt;
really had this effect, only the major jumps saw any issues.&lt;br /&gt;
&lt;br /&gt;
Luckily, as I had just learned 30 minutes earlier how to run Nova's tests I was&lt;br /&gt;
able to drill down to exactly what was going on and SQLAlchemy 0.9.6 was out&lt;br /&gt;
within an hour, but we shall now forever see in requirements.txt, the Version&lt;br /&gt;
that shall Not Be Named (except in requirements.txt):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
SQLAlchemy&amp;gt;=0.7.8,!=0.9.5,&amp;lt;=0.9.99&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In response to this I have now&lt;br /&gt;
[http://jenkins.sqlalchemy.org/job/openstack_sqla_master/ added openstack builds to our Jenkins CI system],&lt;br /&gt;
so far running tests for oslo.db and the SQLAlchemy tests within Nova; I spent&lt;br /&gt;
a couple of days figuring out how to get everything to build and run against&lt;br /&gt;
specific versions of SQLAlchemy and Alembic, even versions above the 0.9.99 in&lt;br /&gt;
all the requirements files as we are set for 1.0 hopefully within the next year.&lt;br /&gt;
I will be adding in as many other OpenStack projects as I can here so that we will no&lt;br /&gt;
longer have a SQLAlchemy release that hasn't been run against a battery of&lt;br /&gt;
basic OpenStack tests.&lt;br /&gt;
&lt;br /&gt;
== ORM Woes ==&lt;br /&gt;
&lt;br /&gt;
After chatting with a decent amount of folks for a month, I can break up&lt;br /&gt;
the take on OpenStack / SQLAlchemy ORM into three camps:&lt;br /&gt;
&lt;br /&gt;
1. We love SQLAlchemy ORM, we know that we can improve how we use it and we're looking forward to getting there (oslo.db, Keystone)&lt;br /&gt;
&lt;br /&gt;
2. Our app uses a relational database but the SQLAlchemy ORM slows us down, we already have our own &amp;quot;orm-like&amp;quot; system (e.g. APIs that shuttle rows into &amp;quot;values&amp;quot; dictionaries, nova.objects, etc.) so we want to ultimately get rid of the ORM part, and probably continue using SQLAlchemy Core to some degree (some Nova developers I've spoken to).&lt;br /&gt;
&lt;br /&gt;
3. Our application has a SQLAlchemy backend that almost nobody uses in production, most users use MongoDB, and the SQLAlchemy backend is only there because MongoDB is AGPL (Ceilometer, Marconi).  We'd like to get a better NoSQL backend without Mongo's licensing issue and do away with relational storage entirely ([http://lists.openstack.org/pipermail/openstack-dev/2014-June/038778.html Marconi]).&lt;br /&gt;
&lt;br /&gt;
Here's my observations / thoughts about these three camps:&lt;br /&gt;
&lt;br /&gt;
1. Great!  Read on, I have some things we can start with.&lt;br /&gt;
&lt;br /&gt;
2. I agree that a lot of the patterns that I've seen in Nova in particular are not well-suited to the approach used by SQLAlchemy's ORM, however I have observed that the applications still rely a lot on very sophisticated and mature features provided by SQLAlchemy (namely &amp;lt;code&amp;gt;relationship()&amp;lt;/code&amp;gt; and it's integration with eager loading and the unit of work features), and throwing it away entirely will incur a lot of reinvention, not just of the features that were lost but also of a totally new crop of stability and performance issues to solve.&lt;br /&gt;
&lt;br /&gt;
To that degree, I'd like to propose a twist to the &amp;quot;we want to write our own ORM&amp;quot; idea (edit: OK nova.objects is really an ORM-like thing on top of their API on top of relational DB, but that's an issue for another day) and say, &amp;quot;let's write our own ORM that's based on the SQLAlchemy ORM primitives&amp;quot;, e.g. a hybrid approach where existing ORM logic that is fine can stay that way, ORM logic that has performance or behavioral issues can be reorganized using systems, some of them new, some of them just underutilized, that bypass unneeded SQLAlchemy features on a case-by-case basis.&lt;br /&gt;
&lt;br /&gt;
As a secondary issue, some devs I've spoken to have referred to the nature&lt;br /&gt;
of OpenStack API-based applications being a part of the problem.&lt;br /&gt;
This refers to the fact that OpenStack apps like Nova&lt;br /&gt;
and Neutron expose fine-grained APIs which end up&lt;br /&gt;
breaking out composed use cases into dozens or hundreds of small API&lt;br /&gt;
operations, each of which invokes within it's own database transaction and&lt;br /&gt;
ORM session state.  This makes it impossible, without introducing some elaborate&lt;br /&gt;
offline state management system, for a larger operation to be&lt;br /&gt;
performed so that larger groups of objects can be loaded and manipulated&lt;br /&gt;
at once, greatly decreasing the number of database round trips present.&lt;br /&gt;
No matter what ORM you use or don't use, the pattern of many small round trips&lt;br /&gt;
is difficult to scale on relational databases.&lt;br /&gt;
&lt;br /&gt;
There are under way projects which aim to apply detailed profiling&lt;br /&gt;
to OpenStack applications so that a breakdown of performance issues due&lt;br /&gt;
to API use and/or database&lt;br /&gt;
access might be plainly reasoned about; see [https://github.com/stackforge/rally Rally]&lt;br /&gt;
and [https://github.com/stackforge/osprofiler OS Profiler] for examples.&lt;br /&gt;
&lt;br /&gt;
On my end, I have no intention of focusing on this second issue for the time&lt;br /&gt;
being, I'm going to stick with leaving OS applications as much as they are right now as possible,&lt;br /&gt;
applying iterative changes to database access code which I hope can optimize&lt;br /&gt;
to a significant degree within the current usage patterns.&lt;br /&gt;
&lt;br /&gt;
3. Projects with alternate MongoDB / MySQL backends (since after all, MySQL is the database here being rejected, SQLAlchemy is just a Python API) should probably do what they're doing and go to an all key/value model.  It is not at all fun to maintain the same datamodel and API against both a KV store as well as relational tables.  I had a detailed look at Ceilometer with its team members early on, and while I'm pretty confident that if it were desireable, I could get it's relational backend to compete with the Mongo backend performance-wise, there's no reason to get into this if the Mongo licensing issue can be solved alone.&lt;br /&gt;
&lt;br /&gt;
== ORM Quick Wins Proof of Concept ==&lt;br /&gt;
&lt;br /&gt;
I've built up a proof of concept at [https://github.com/zzzeek/nova_poc nova_poc] which seeks to demonstrate&lt;br /&gt;
the range of database and ORM performance that can be achieved within&lt;br /&gt;
one particular method of the Nova API.   Naturally, I picked an example that&lt;br /&gt;
is very juicy in this regard; it has a very easily fixable issue with the&lt;br /&gt;
kind of query it emits that has an enormous 10x impact on its performance&lt;br /&gt;
(which I feel is worth illustrating just for how dramatic it is),&lt;br /&gt;
and it then illustrates some new SQLAlchemy extensions that more or less&lt;br /&gt;
may be going into SQLAlchemy directly, which allow the remaining operation&lt;br /&gt;
to complete in less than half the time.  These extensions can be ported to&lt;br /&gt;
oslo.db to work against all supported SQLAlchemy versions prior to the official&lt;br /&gt;
release of these features.&lt;br /&gt;
&lt;br /&gt;
To summarize, we're looking at the API method &amp;lt;code&amp;gt;floating_ip_update()&amp;lt;/code&amp;gt;, and&lt;br /&gt;
I run a series of profiles against a database table 10K rows (e.g. &amp;lt;code&amp;gt;FloatingIp&amp;lt;/code&amp;gt;&lt;br /&gt;
objects), where each &amp;lt;code&amp;gt;FloatingIp&amp;lt;/code&amp;gt; refers to a single &amp;lt;code&amp;gt;FixedIp&amp;lt;/code&amp;gt; that then&lt;br /&gt;
refers to an &amp;lt;code&amp;gt;Instance&amp;lt;/code&amp;gt;.   The profiling focuses on the nature of this&lt;br /&gt;
method as an API feature all it's own, and simulates the case where the API&lt;br /&gt;
method is called thousands of times, not unlike other API methods that seek&lt;br /&gt;
to add some small amount of data each time they are called.&lt;br /&gt;
&lt;br /&gt;
=== Eager load and Column load tuning ===&lt;br /&gt;
&lt;br /&gt;
Turning off an eagerload in the &amp;lt;code&amp;gt;_floating_ip_get_by_address()&amp;lt;/code&amp;gt; function&lt;br /&gt;
is the most dramatic win; while removing this method seems like a reasonable&lt;br /&gt;
fix to remove some unneeded overhead, in profiling it shows itself to&lt;br /&gt;
be enormous:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Running scenario default&lt;br /&gt;
Scenario default, total calls for 1000 operations: 24543047&lt;br /&gt;
Scenario default, total time for 10000 operations: 222&lt;br /&gt;
&lt;br /&gt;
Running scenario default_optimized&lt;br /&gt;
Scenario default_optimized, total calls for 1000 operations: 1881626&lt;br /&gt;
Scenario default_optimized, total time for 10000 operations: 25&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
While not illustrated in the POC code itself, talk with various OpenStack&lt;br /&gt;
devs has revealed that we could probably do a lot more individual column&lt;br /&gt;
fetching as well, that is instead of:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
session.query(FixedIp).all()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We say:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
session.query(FixedIp.address, FixedIp.host, FixedIp.reserved).all()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
By loading indivdual columns, we get read-only objects that are not identity&lt;br /&gt;
managed, bypassing a huge amount of work that is done on instances both&lt;br /&gt;
in instantiating them, populating relationships and doing bookkeeping.&lt;br /&gt;
Any API feature that only returns a simple read-only object or a &amp;quot;values&amp;quot;&lt;br /&gt;
dictionary that does not rely upon relationship loading should be using&lt;br /&gt;
this pattern, or similar, by using a&lt;br /&gt;
[http://docs.sqlalchemy.org/en/rel_0_9/orm/mapper_config.html#column-bundles Column Bundle].&lt;br /&gt;
I will be exploring adding the following feature to oslo.db:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
session.query(FixedIp.bundle).all()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The above will return a fast-loading, read-only Bundle object that otherwise&lt;br /&gt;
expresses the full interface of &amp;lt;code&amp;gt;FixedIp&amp;lt;/code&amp;gt;.   Relationships might be tricky&lt;br /&gt;
here but I can at least get them on board as regular lazy loads (just like&lt;br /&gt;
any other ORM would do anyway).&lt;br /&gt;
&lt;br /&gt;
=== Fast Object Save ===&lt;br /&gt;
&lt;br /&gt;
We explore replacing the unit of work &amp;lt;code&amp;gt;flush()&amp;lt;/code&amp;gt; call used by &amp;lt;code&amp;gt;object.save()&amp;lt;/code&amp;gt; within oslo.db-style applications with a more direct &amp;quot;flush single object&amp;quot; call; this grants us only a 12% savings so far, though it is very simple to use:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Running scenario default_optimized&lt;br /&gt;
Scenario default_optimized, total calls for 1000 operations: 1881626&lt;br /&gt;
Scenario default_optimized, total time for 10000 operations: 25&lt;br /&gt;
&lt;br /&gt;
Running scenario fast_save&lt;br /&gt;
Scenario fast_save, total calls for 1000 operations: 1685221&lt;br /&gt;
Scenario fast_save, total time for 10000 operations: 22&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Baked Queries ===&lt;br /&gt;
&lt;br /&gt;
Something that has been in the works for a long time and has&lt;br /&gt;
recently    seen lots of work in the past months is the &amp;quot;baked query&amp;quot;&lt;br /&gt;
feature; this    pattern is ideal for OpenStack's &amp;quot;many short queries&amp;quot;&lt;br /&gt;
pattern, and allows    caching of the generation of SQL.  Recent&lt;br /&gt;
versions of this pattern have    gotten very slick, and can cache&lt;br /&gt;
virtually everything that happens Python-wise    from the construction&lt;br /&gt;
of the &amp;lt;code&amp;gt;Query&amp;lt;/code&amp;gt; object, to calling all the methods    on the&lt;br /&gt;
query, to the query-objects construction of a Core SQL statement,&lt;br /&gt;
to the compilation of that statement as a string - all of these steps&lt;br /&gt;
are removed from the call-graph after the first such call.  In&lt;br /&gt;
SQLAlchemy 1.0    I've also thrown in the construction of column&lt;br /&gt;
metadata from the result set    too.   The pattern involves a bit more&lt;br /&gt;
verbosity to that of constructing a    query, where here I've built&lt;br /&gt;
off of some of the ideas of the    Pony ORM to use Python function&lt;br /&gt;
information as the source of a cache key.    A query such as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
result = model_query(&lt;br /&gt;
            context, models.FloatingIp, session=session).\&lt;br /&gt;
            filter_by(address=address)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
would be expressed in &amp;quot;baked&amp;quot; form as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# note model_query is using the &amp;quot;baked&amp;quot; process internally as well&lt;br /&gt;
result = model_query(context, models.FloatingIp, session=session)&lt;br /&gt;
&lt;br /&gt;
result.bake(lambda query:&lt;br /&gt;
    query.filter_by(&lt;br /&gt;
        address=bindparam('address'))).params(address=address)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the above form, everything within each lambda is invoked only once,&lt;br /&gt;
the result of which becomes part of a cached value.&lt;br /&gt;
&lt;br /&gt;
For this slight increase in verbosity, we get an improvement like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Running scenario default_optimized&lt;br /&gt;
Scenario default_optimized, total calls for 1000 operations: 1881626&lt;br /&gt;
Scenario default_optimized, total time for 10000 operations: 25&lt;br /&gt;
&lt;br /&gt;
Running scenario baked&lt;br /&gt;
Scenario baked, total calls for 1000 operations: 1052935&lt;br /&gt;
Scenario baked, total time for 10000 operations: 16&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
That is, around a 40% improvement.&lt;br /&gt;
&lt;br /&gt;
Putting together both &amp;quot;fast save&amp;quot; plus &amp;quot;baked&amp;quot; we get down to a full 50%&lt;br /&gt;
improvement vs. the plain optimized version:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Running scenario fast_save_plus_baked&lt;br /&gt;
Scenario fast_save_plus_baked, total calls for 1000 operations: 856035&lt;br /&gt;
Scenario fast_save_plus_baked, total time for 10000 operations: 13&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
13 seconds for 10K operations vs. 222 seconds in the current code, I hope&lt;br /&gt;
I've established that there is hope!&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Migrations ==&lt;br /&gt;
&lt;br /&gt;
Migrations are a huge deal.  Here's where it looks like this is going.&lt;br /&gt;
&lt;br /&gt;
Alembic - &amp;quot;Alembic is the defacto standard going forward&amp;quot; - ultimately&lt;br /&gt;
we want to get everything SQLAlchemy-Migrate onto Alembic.  And in talking&lt;br /&gt;
with Nova devs, they really like that they can test their migrations against&lt;br /&gt;
SQLite.  So from my POV, SQLite migrations on Alembic, we're doing that!&lt;br /&gt;
Unless folks think otherwise.&lt;br /&gt;
&lt;br /&gt;
=== SQLite Support ===&lt;br /&gt;
&lt;br /&gt;
SQLite has extremely limited support for ALTER TABLE, essentially only being able&lt;br /&gt;
to add new columns, and that's about it.  In order to provide the full suite&lt;br /&gt;
of ALTER operations that all other databases provide, tools such as&lt;br /&gt;
SQLAlchemy-Migrate will create a copy of the target table with changes applied,&lt;br /&gt;
copy data from old table to new, then drop the old table and rename the new one.&lt;br /&gt;
I've always been peeved at SQLite's insistence on never providing some better&lt;br /&gt;
pathway to ALTER built in.  But it has been on the Alembic roadmap to add SQLite migrations&lt;br /&gt;
in a style similar to that of Migrate for a long time, emulating this same&lt;br /&gt;
approach in some way.  Alembic will have this!&lt;br /&gt;
&lt;br /&gt;
However in talking with some folks, it appears like some people might actually&lt;br /&gt;
like this &amp;quot;make a new table and switch it&amp;quot; approach for other databases too,&lt;br /&gt;
as a way to work around locking of tables.  I don't know that I agree with this&lt;br /&gt;
long term, the &amp;quot;ALTER locks the database!&amp;quot; issue in my experience has always&lt;br /&gt;
been kind of a MySQL problem alone, but it can be present for other backends&lt;br /&gt;
as well.&lt;br /&gt;
&lt;br /&gt;
So the API I'm looking towards here is going to try to solve a few problems;&lt;br /&gt;
one is that, copying the whole table to a new one each time for *every single&lt;br /&gt;
column* is crazy, and also that the technique we use on SQLite might be nice&lt;br /&gt;
for other databases too, so here's the API:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
with op.batch_alter(&amp;quot;mytable&amp;quot;, recreate=True) as batch_op:&lt;br /&gt;
    batch_op.drop_column(&amp;quot;some_column&amp;quot;)&lt;br /&gt;
    batch_op.alter_column(&amp;quot;q&amp;quot;, type=Integer)&lt;br /&gt;
    batch_op.add_column(Column('y', String(50), nullable=False))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
That is, we assign a whole series of column operations under the umbrella of&lt;br /&gt;
one table, and they are created at once.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;batch_alter()&amp;lt;/code&amp;gt; call can even be conditional, dropping into traditional&lt;br /&gt;
migration behavior for all backends except SQLite and whatever else:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
with op.batch_alter(&amp;quot;mytable&amp;quot;, recreate_on=('sqlite',)) as batch_op:&lt;br /&gt;
    batch_op.drop_column(&amp;quot;some_column&amp;quot;)&lt;br /&gt;
    batch_op.alter_column(&amp;quot;q&amp;quot;, type=Integer)&lt;br /&gt;
    batch_op.add_column(Column('y', String(50), nullable=False))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
SQlite-compatible alterations is [https://bitbucket.org/zzzeek/alembic/issue/21/column-renames-not-supported-on-sqlite issue #21].&lt;br /&gt;
&lt;br /&gt;
=== Finishing up Autogenerate ===&lt;br /&gt;
&lt;br /&gt;
I started writing Alembic long before SQLAlchemy-Migrate had autogenerate.  But by the time&lt;br /&gt;
I got Alembic released and later autogenerate working, Migrate already had it.&lt;br /&gt;
Here's what we need to finish up in autogenerate:&lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;code&amp;gt;ForeignKeyConstraint&amp;lt;/code&amp;gt; change detection - this is an entirely straightforward feature add and has long been on the todo list.&lt;br /&gt;
&lt;br /&gt;
2. MySQL implicit indexes - this is actually done; recent versions of Alembic can navigate around MySQLs goofy production of indexes automatically on foreign key columns, and not accidentally spit them out in autogenerates. It was seriously tough to get index autogen mostly working on all backends, so new issues will continue to be fixed as they are reported.&lt;br /&gt;
&lt;br /&gt;
3. Type comparison - Alembic balks on type comparison by default, because ultimately SQLAlchemy should add comparision features to its type objects natively. However, on Alembic backends like that of MySQL, we can add explicit rules for the very common and annoying ones, namely the detection of MySQL's TINYINT as a SQLAlchemy Boolean.  Alembic already allows user-specified rules in this regard, so they can be part of oslo.db in any case, however Alembic can also add this rule and others in directly.&lt;br /&gt;
&lt;br /&gt;
4. Table order - Alembic's autogenerate should be spitting out table creates and drops in the order of foreign key dependency, though we don't have good test coverage for this yet and it might not be working.  We'll fix that, no biggie!  Alternatively, Alembic could include a mode of operation that includes all the ForeignKeyConstraint objects after all the tables. Input on how we'd like to see this work would be welcome.&lt;br /&gt;
&lt;br /&gt;
=== New Migration Features ===&lt;br /&gt;
&lt;br /&gt;
There's a lot of features I'd like to add to Alembic, and if OpenStack has&lt;br /&gt;
a need for them, that would justify the effort:&lt;br /&gt;
&lt;br /&gt;
1. Full branch support - [https://bitbucket.org/zzzeek/alembic/issue/167/multiple-heads-branch-resolution-support issue 167 multiple heads / branch resolution support] has a detailed architecture laid out for this feature.  Lots of people are looking for it. This would turn Alembic's current &amp;quot;linked list&amp;quot; version model into a full directed acyclic graph (DAG).   Any particular migration can be dependent on any other group of migrations, or none at all; individual branches can be maintained and upgraded along their path, or automatically merged.&lt;br /&gt;
&lt;br /&gt;
2. [https://bitbucket.org/zzzeek/alembic/issue/124/multiple-versions-directories Multiple version directories] - This would allow migration files to be present in more than one place for a single migrations environment.  Currently, you can get this approach by using multiple base directories, but that requires separate &amp;lt;code&amp;gt;env.py&amp;lt;/code&amp;gt; files for each. With this issue, cross-dependent migration files can live in multiple places, working nicely with the multiple heads support of #167.&lt;br /&gt;
&lt;br /&gt;
== Testing ==&lt;br /&gt;
&lt;br /&gt;
I spent quite a bit looking into testing.  The main thing we can see&lt;br /&gt;
in testing is that there may be a desire for &amp;quot;transactional testing&amp;quot;;&lt;br /&gt;
run tests within a fixed schema, that is the schema    is created&lt;br /&gt;
once, many tests are run, each one rolling back their work, the&lt;br /&gt;
schema is only dropped once all tests for that schema are complete.&lt;br /&gt;
&lt;br /&gt;
Transactional testing is described in the SQLAlchemy docs and in some of&lt;br /&gt;
my talks; for OpenStack, we need the version that supports graceful handling&lt;br /&gt;
of tests that actually do a rollback.  This is doable and the&lt;br /&gt;
recipe for that is currently [https://gist.github.com/zzzeek/8443477 a github gist].&lt;br /&gt;
&lt;br /&gt;
In order to accommodate parallelized runs, the system will create N&lt;br /&gt;
temporary schemas for N concurrent runs.&lt;br /&gt;
&lt;br /&gt;
I really want to get into this feature but as I talk to people, I'm&lt;br /&gt;
getting mixed signals on how important this feature is.   So far I've&lt;br /&gt;
only worked with unit tests, which seem to be entirely against SQLite&lt;br /&gt;
in-memory databases, and I haven't gotten my head around to what&lt;br /&gt;
extent other databases besides SQLite might be desireable in unit&lt;br /&gt;
tests, nor have I gotten to fully understand how integration tests&lt;br /&gt;
run, so input is welcome here.&lt;br /&gt;
&lt;br /&gt;
== Improvement of ORM Patterns ==&lt;br /&gt;
&lt;br /&gt;
Looking around oslo.db and other apps, I want to improve upon&lt;br /&gt;
some kinds of verbosity I see, which make the code more repetetive and inconsistent, welds together application-level and business level concepts (e.g. transaction control and exception reporting melded with data manipulation) and makes it harder to reason about how the application interacts with sessions, as there are a variety of ad-hoc patterns present.&lt;br /&gt;
&lt;br /&gt;
=== Transaction Demarcation ===&lt;br /&gt;
&lt;br /&gt;
For the oft-repeated &amp;quot;get_session(); with session.begin(subtransactions=True)&amp;quot; phrase and other variants, I'd like to move that into something more abstracted:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def my_api_thing(x):&lt;br /&gt;
    with transactional() as session:&lt;br /&gt;
    	# do stuff&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I want context managers and/or decorators that are oslo.db specific to replace any kind of containing code that deals with transactions or sessions. That way the containinment logic can be maintained in once place and configured separately from the business logic contained within.  Projects like Keystone are already using a form more like this; it would be nice to nail down a consistent pattern and get it into oslo.db for everyone to use.&lt;br /&gt;
&lt;br /&gt;
=== MySQLdb + eventlet = sad ===&lt;br /&gt;
&lt;br /&gt;
This is the basic thing that happens with these two actors.   We're in an eventlet &amp;quot;thread&amp;quot;.   The code uses MySQLdb to do something ordinary that locks a row in a table - this can happen with a SELECT FOR UPDATE but it really can happen just on any old UPDATE too depending on isolation level.   If the eventlet &amp;quot;thread&amp;quot; then does *anything* that causes it to defer out, another eventlet thread might run, and if that thread tries to work with MySQLdb and lock the same row, you're done, it's a deadlock.   The simple reason is, MySQLdb doesn't integrate with eventlet, so a simple thing like blocking on a table, it doesn't defer.   MySQL times out on these so we're getting exceptions instead of permanent freezes, but IMHO this is a really silly problem.  Eventlet and MySQLdb do not work together, we're done.   MySQLdb 1000% either has to go, or be fixed, even if that means forking it.    A good blog post that illustrates both scenarios is [http://www.diamondtin.com/2014/sqlalchemy-gevent-mysql-python-drivers-comparison/ here] - in this post, the author has used &amp;quot;greenify&amp;quot; to patch MySQLdb's blocking, but without this change (which I'm told isn't compatible with eventlet?), MySQLdb is a nonstarter.    Because it is in pure C, it is faster than MySQL-connector when it's been patched, but if it hasn't, it's dog slow.  MySQL-connector IMHO is probably the driver we should get behind [http://dev.mysql.com/doc/connector-python/en/index.html because it's the official MySQL driver]. The various workarounds and hacks I've seen proposed are all technically viable but IMHO totally unnecessary.   OpenStack should be serious about this, it's a totally solvable problem, let's just solve it.&lt;br /&gt;
&lt;br /&gt;
=== Exception Rewriting ===&lt;br /&gt;
&lt;br /&gt;
Exception rewriting - I notice that the oslo.db pattern includes that DBAPI level exceptions are re-thrown as OpenStack specific;  I'd like to move this into handling using SQLAlchemy events.  The [http://docs.sqlalchemy.org/en/rel_0_9/core/events.html#sqlalchemy.events.ConnectionEvents.dbapi_error dbapi_error] event goes all the way back to 0.7, and [https://bitbucket.org/zzzeek/sqlalchemy/issue/3076/support-reraise-of-custom-exceptions ticket 3076] will allow it to be used to reraise with a new exception.  For older SQLAlchemy versions, I can create a wrapper that will allow it to be used in this way safely for older versions, despite the warnings stated on that document.&lt;br /&gt;
&lt;br /&gt;
=== Pessimistic Locking - SELECT FOR UPDATE ===&lt;br /&gt;
&lt;br /&gt;
This issue has been coming up for some projects, that they make sometimes great use of SQLAlchemy's &amp;lt;code&amp;gt;with_lockmode()&amp;lt;/code&amp;gt; method, which ultimately emits SELECT..FOR UPDATE, so that selected rows are aggressively locked within a transaction.   The issue is that backends like Galera don't support FOR UPDATE, meaning the locking that is desired is not actually provided.&lt;br /&gt;
&lt;br /&gt;
While I'm not sure that there's a generalized solution to this, in some cases I've been helping developers move to more of an &amp;quot;optimistic&amp;quot; approach, where a particular UPDATE statement targets some kind of timestamp or value counter in the row, then counts the number of rows matched.  If the number of rows matched is not what's expected, it is assumed that the UPDATE failed to match the row, and the operation can either try again or can bail out.   This approach might not be suitable in all cases.  In cases where a large stream of data is affecting a relatively small set of rows, it can perhaps be feasible to serialize those updates by sending them all to a queue, so that they are invoked in isolation without the need for up-front locking.   For very high throughput situations, queues can be constructed to use multiple, concurrent UPDATE streams separated via hashing, or can batch together updates for particular rows over a period of time and emit fewer UPDATE statements than if each source of data did it individually.&lt;br /&gt;
&lt;br /&gt;
=== Encouraging Bundle / Baked Query Use ===&lt;br /&gt;
&lt;br /&gt;
Encouraging Bundle use - as stated earlier, I want to look into bringing the very efficient [http://docs.sqlalchemy.org/en/rel_0_9/orm/mapper_config.html#column-bundles Column Bundle] pattern into the fold for mainstream use, not to mention the baked query pattern illustrated previously.&lt;br /&gt;
&lt;br /&gt;
=== Transparent Retries of Transactions ===&lt;br /&gt;
&lt;br /&gt;
I have ready to go an [https://bitbucket.org/zzzeek/sqlalchemy/issue/3104/transaction-replay-extension already in production extension for transaction replaying], developed for a client with the agreement that the extension is open source.  This extension installs as an event handler using a new series of hooks that were added in 0.9, or alternatively using a series of compatibility hooks that were backported to 0.7 and 0.8.   This extension allows logging of SQL statements as they proceed within a transaction, and allows them to be transparently replayed when a database disconnect error occurs. This means that SQLAlchemy's usual behavior of raising an exception and forcing you to re-try explicitly can be hidden so that it occurs implicitly, without any interruption or special error handling required in application code.  The extension includes a lot of configuration options, such as what statements should be replayed and which shouldn't, as well as what exceptions should be handled this way.   Transaction replaying is an elaborate feature which might introduce lots of new edge cases, namely that the state of the database may have changed since the last transaction failed, so to that extent I'm a little wary of this feature myself.  However I have seen lots of messages alluding to this kind of thing.    The mechanics to pull this into SQLAlchemy as a transparent feature are ready to go, if this is a feature people are looking for (I'm not sure if people are looking for this).&lt;br /&gt;
&lt;br /&gt;
== Contributors ==&lt;br /&gt;
&lt;br /&gt;
I would be remiss if I didn't mention that SQLAlchemy, Alembic, Dogpile and everything else I do are collaborative projects, and while SQLAlchemy in particular might present a daunting codebase, we would love to get new collaborators on board!   Any of the features above are fair game and I can walk someone through the steps to be taken, and additionally everything we add features for always needs lots of tests and review, if you're looking for a way to get into the project I can bring you right in, there's lots of (often very tedious and boring) work to be done.   I'm on all the IRC channels now as zzzeek so ping away.&lt;br /&gt;
&lt;br /&gt;
== Coffee and Cake ==&lt;br /&gt;
&lt;br /&gt;
So! I hope you've enjoyed reading this document, and whether you're a fan or a hater, please know that I'm on board now and I'm here to help, with whatever database kind of thing you want to do - it's all part of my job now.    If we wanted to use/build a whole new ORM, I'd be doing that too right alongside.  OpenStack's lessons in database integration are now my lessons too.  So let me know where I should dig in first!  Have a save drive home.&lt;/div&gt;</summary>
		<author><name>Mike Bayer</name></author>	</entry>

	<entry>
		<id>https://wiki.openstack.org/w/index.php?title=OpenStack_and_SQLAlchemy&amp;diff=57068</id>
		<title>OpenStack and SQLAlchemy</title>
		<link rel="alternate" type="text/html" href="https://wiki.openstack.org/w/index.php?title=OpenStack_and_SQLAlchemy&amp;diff=57068"/>
				<updated>2014-07-01T01:10:31Z</updated>
		
		<summary type="html">&lt;p&gt;Mike Bayer: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= OpenStack and SQLAlchemy =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
This page describes the ongoing efforts of Mike Bayer, creator/maintainer of&lt;br /&gt;
SQLAlchemy, Alembic Migrations, and Dogpile caching, to spearhead what is&lt;br /&gt;
essentially an &amp;quot;OpenStack SQLAlchemy 2.0&amp;quot; effort - as I am now full-time&lt;br /&gt;
employed by an OpenStack vendor (Red Hat), I've been hired to produce a strong&lt;br /&gt;
and sustained pass through OpenStack's relational database integration,&lt;br /&gt;
improving areas that have been problematic and establishing a very strong&lt;br /&gt;
set of practices that are specific to OpenStack's needs.&lt;br /&gt;
&lt;br /&gt;
Within this effort, I'd like to stress that from my POV, everything is&lt;br /&gt;
on the table, including new improvements / fixes / features to&lt;br /&gt;
SQLAlchemy and Alembic directly, migration of projects for which&lt;br /&gt;
relational databases aren't suitable to non-relational systems,&lt;br /&gt;
improvement of relational schemas on the OpenStack side, and of course&lt;br /&gt;
improvement of exising relational patterns within OpenStack&lt;br /&gt;
applications, for which oslo.db will serve as the foundation.&lt;br /&gt;
&lt;br /&gt;
As of June 30, 2014, I've been on the job for about a month, so below&lt;br /&gt;
is an outline of most of what I've come across and my thoughts going forward.&lt;br /&gt;
&lt;br /&gt;
My goal here is to share with the community what I've seen and done so far,&lt;br /&gt;
so that interested parties can chime in with further advice and information,&lt;br /&gt;
and most importantly to help steer me towards the initial blueprints that&lt;br /&gt;
I should be starting on, that is, which features are most likely to be&lt;br /&gt;
accepted by the community as well as which are the top priority.  Not to mention, if you're looking&lt;br /&gt;
for something to do, you can help me implement some of these things!&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== OpenStack presence within SQLAlchemy's CI environment (a.k.a. &amp;quot;The Version that shall not be Named&amp;quot;) ==&lt;br /&gt;
&lt;br /&gt;
On Monday, June 23, I got up in the AM with two goals: get a release of SQLAlchemy&lt;br /&gt;
out the door, as it had been several months since the last point release, and then&lt;br /&gt;
do some work learning how Nova's unit tests run and function.&lt;br /&gt;
&lt;br /&gt;
Release 0.9.5 of SQLAlchemy was not particularly different than any of the other&lt;br /&gt;
hundred or so point releases I've put out over nine years, it was a little more overdue&lt;br /&gt;
than some, and out it went.  Later that day, as I was learning to run Nova's test suite, I&lt;br /&gt;
was hitting some failures within the FixedIp/FloatingIp tests for which I couldn't&lt;br /&gt;
find the cause, assuming that I just wasn't setting up my environment correctly,&lt;br /&gt;
as Nova's tests are passing for all the Jenkins servers the vast majority of the time.&lt;br /&gt;
&lt;br /&gt;
As we learned within about 30 minutes of my hitting these problems, it&lt;br /&gt;
turns out 0.9.5 had a bad fix in it; one that didn't fail any of&lt;br /&gt;
SQLAlchemy's 5000 tests but hit a use case within Nova that was not&lt;br /&gt;
very unusual; not the kind of use I usually do myself but nonetheless&lt;br /&gt;
something that was pretty common.   IRC began  jumping that builds&lt;br /&gt;
were failing everywhere and it was due to the 0.9.5 release!   The&lt;br /&gt;
bizarre coiincidence of this was that none of the thirty-odd point&lt;br /&gt;
releases of SQLAlchemy released under OpenStack's lifetime have ever&lt;br /&gt;
really had this effect, only the major jumps saw any issues.&lt;br /&gt;
&lt;br /&gt;
Luckily, as I had just learned 30 minutes earlier how to run Nova's tests I was&lt;br /&gt;
able to drill down to exactly what was going on and SQLAlchemy 0.9.6 was out&lt;br /&gt;
within an hour, but we shall now forever see in requirements.txt, the Version&lt;br /&gt;
that shall Not Be Named (except in requirements.txt):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
SQLAlchemy&amp;gt;=0.7.8,!=0.9.5,&amp;lt;=0.9.99&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In response to this I have now&lt;br /&gt;
[http://jenkins.sqlalchemy.org/job/openstack_sqla_master/ added openstack builds to our Jenkins CI system],&lt;br /&gt;
so far running tests for oslo.db and the SQLAlchemy tests within Nova; I spent&lt;br /&gt;
a couple of days figuring out how to get everything to build and run against&lt;br /&gt;
specific versions of SQLAlchemy and Alembic, even versions above the 0.9.99 in&lt;br /&gt;
all the requirements files as we are set for 1.0 hopefully within the next year.&lt;br /&gt;
I will be adding in as many other OpenStack projects as I can here so that we will no&lt;br /&gt;
longer have a SQLAlchemy release that hasn't been run against a battery of&lt;br /&gt;
basic OpenStack tests.&lt;br /&gt;
&lt;br /&gt;
== ORM Woes ==&lt;br /&gt;
&lt;br /&gt;
After chatting with a decent amount of folks for a month, I can break up&lt;br /&gt;
the take on OpenStack / SQLAlchemy ORM into three camps:&lt;br /&gt;
&lt;br /&gt;
1. We love SQLAlchemy ORM, we know that we can improve how we use it and we're looking forward to getting there (oslo.db, Keystone)&lt;br /&gt;
&lt;br /&gt;
2. Our app uses a relational database but the SQLAlchemy ORM slows us down, we already have our own &amp;quot;orm-like&amp;quot; system (e.g. APIs that shuttle rows into &amp;quot;values&amp;quot; dictionaries, nova.objects, etc.) so we want to ultimately get rid of the ORM part, and probably continue using SQLAlchemy Core to some degree (some Nova developers I've spoken to).&lt;br /&gt;
&lt;br /&gt;
3. Our application has a SQLAlchemy backend that almost nobody uses in production, most users use MongoDB, and the SQLAlchemy backend is only there because MongoDB is AGPL (Ceilometer, Marconi).  We'd like to get a better NoSQL backend without Mongo's licensing issue and do away with relational storage entirely ([http://lists.openstack.org/pipermail/openstack-dev/2014-June/038778.html Marconi]).&lt;br /&gt;
&lt;br /&gt;
Here's my observations / thoughts about these three camps:&lt;br /&gt;
&lt;br /&gt;
1. Great!  Read on, I have some things we can start with.&lt;br /&gt;
&lt;br /&gt;
2. I agree that a lot of the patterns that I've seen in Nova in particular are not well-suited to the approach used by SQLAlchemy's ORM, however I have observed that the applications still rely a lot on very sophisticated and mature features provided by SQLAlchemy (namely &amp;lt;code&amp;gt;relationship()&amp;lt;/code&amp;gt; and it's integration with eager loading and the unit of work features), and throwing it away entirely will incur a lot of reinvention, not just of the features that were lost but also of a totally new crop of stability and performance issues to solve.&lt;br /&gt;
&lt;br /&gt;
To that degree, I'd like to propose a twist to the &amp;quot;we want to write our own ORM&amp;quot; idea (edit: OK nova.objects is really an ORM-like thing on top of their API on top of relational DB, but that's an issue for another day) and say, &amp;quot;let's write our own ORM that's based on the SQLAlchemy ORM primitives&amp;quot;, e.g. a hybrid approach where existing ORM logic that is fine can stay that way, ORM logic that has performance or behavioral issues can be reorganized using systems, some of them new, some of them just underutilized, that bypass unneeded SQLAlchemy features on a case-by-case basis.&lt;br /&gt;
&lt;br /&gt;
As a secondary issue, some devs I've spoken to have referred to the nature&lt;br /&gt;
of OpenStack API-based applications being a part of the problem.&lt;br /&gt;
This refers to the fact that OpenStack apps like Nova&lt;br /&gt;
and Neutron expose fine-grained APIs which end up&lt;br /&gt;
breaking out composed use cases into dozens or hundreds of small API&lt;br /&gt;
operations, each of which invokes within it's own database transaction and&lt;br /&gt;
ORM session state.  This makes it impossible, without introducing some elaborate&lt;br /&gt;
offline state management system, for a larger operation to be&lt;br /&gt;
performed so that larger groups of objects can be loaded and manipulated&lt;br /&gt;
at once, greatly decreasing the number of database round trips present.&lt;br /&gt;
No matter what ORM you use or don't use, the pattern of many small round trips&lt;br /&gt;
is difficult to scale on relational databases.&lt;br /&gt;
&lt;br /&gt;
There are under way projects which aim to apply detailed profiling&lt;br /&gt;
to OpenStack applications so that a breakdown of performance issues due&lt;br /&gt;
to API use and/or database&lt;br /&gt;
access might be plainly reasoned about; see [https://github.com/stackforge/rally Rally]&lt;br /&gt;
and [https://github.com/stackforge/osprofiler OS Profiler] for examples.&lt;br /&gt;
&lt;br /&gt;
On my end, I have no intention of focusing on this second issue for the time&lt;br /&gt;
being, I'm going to stick with leaving OS applications as much as they are right now as possible,&lt;br /&gt;
applying iterative changes to database access code which I hope can optimize&lt;br /&gt;
to a significant degree within the current usage patterns.&lt;br /&gt;
&lt;br /&gt;
3. Projects with alternate MongoDB / MySQL backends (since after all, MySQL is the database here being rejected, SQLAlchemy is just a Python API) should probably do what they're doing and go to an all key/value model.  It is not at all fun to maintain the same datamodel and API against both a KV store as well as relational tables.  I had a detailed look at Ceilometer with its team members early on, and while I'm pretty confident that if it were desireable, I could get it's relational backend to compete with the Mongo backend performance-wise, there's no reason to get into this if the Mongo licensing issue can be solved alone.&lt;br /&gt;
&lt;br /&gt;
== ORM Quick Wins Proof of Concept ==&lt;br /&gt;
&lt;br /&gt;
I've built up a proof of concept at [https://github.com/zzzeek/nova_poc nova_poc] which seeks to demonstrate&lt;br /&gt;
the range of database and ORM performance that can be achieved within&lt;br /&gt;
one particular method of the Nova API.   Naturally, I picked an example that&lt;br /&gt;
is very juicy in this regard; it has a very easily fixable issue with the&lt;br /&gt;
kind of query it emits that has an enormous 10x impact on its performance&lt;br /&gt;
(which I feel is worth illustrating just for how dramatic it is),&lt;br /&gt;
and it then illustrates some new SQLAlchemy extensions that more or less&lt;br /&gt;
may be going into SQLAlchemy directly, which allow the remaining operation&lt;br /&gt;
to complete in less than half the time.  These extensions can be ported to&lt;br /&gt;
oslo.db to work against all supported SQLAlchemy versions prior to the official&lt;br /&gt;
release of these features.&lt;br /&gt;
&lt;br /&gt;
To summarize, we're looking at the API method &amp;lt;code&amp;gt;floating_ip_update()&amp;lt;/code&amp;gt;, and&lt;br /&gt;
I run a series of profiles against a database table 10K rows (e.g. &amp;lt;code&amp;gt;FloatingIp&amp;lt;/code&amp;gt;&lt;br /&gt;
objects), where each &amp;lt;code&amp;gt;FloatingIp&amp;lt;/code&amp;gt; refers to a single &amp;lt;code&amp;gt;FixedIp&amp;lt;/code&amp;gt; that then&lt;br /&gt;
refers to an &amp;lt;code&amp;gt;Instance&amp;lt;/code&amp;gt;.   The profiling focuses on the nature of this&lt;br /&gt;
method as an API feature all it's own, and simulates the case where the API&lt;br /&gt;
method is called thousands of times, not unlike other API methods that seek&lt;br /&gt;
to add some small amount of data each time they are called.&lt;br /&gt;
&lt;br /&gt;
=== Eager load and Column load tuning ===&lt;br /&gt;
&lt;br /&gt;
Turning off an eagerload in the &amp;lt;code&amp;gt;_floating_ip_get_by_address()&amp;lt;/code&amp;gt; function&lt;br /&gt;
is the most dramatic win; while removing this method seems like a reasonable&lt;br /&gt;
fix to remove some unneeded overhead, in profiling it shows itself to&lt;br /&gt;
be enormous:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Running scenario default&lt;br /&gt;
Scenario default, total calls for 1000 operations: 24543047&lt;br /&gt;
Scenario default, total time for 10000 operations: 222&lt;br /&gt;
&lt;br /&gt;
Running scenario default_optimized&lt;br /&gt;
Scenario default_optimized, total calls for 1000 operations: 1881626&lt;br /&gt;
Scenario default_optimized, total time for 10000 operations: 25&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
While not illustrated in the POC code itself, talk with various OpenStack&lt;br /&gt;
devs has revealed that we could probably do a lot more individual column&lt;br /&gt;
fetching as well, that is instead of:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
session.query(FixedIp).all()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We say:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
session.query(FixedIp.address, FixedIp.host, FixedIp.reserved).all()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
By loading indivdual columns, we get read-only objects that are not identity&lt;br /&gt;
managed, bypassing a huge amount of work that is done on instances both&lt;br /&gt;
in instantiating them, populating relationships and doing bookkeeping.&lt;br /&gt;
Any API feature that only returns a simple read-only object or a &amp;quot;values&amp;quot;&lt;br /&gt;
dictionary that does not rely upon relationship loading should be using&lt;br /&gt;
this pattern, or similar, by using a&lt;br /&gt;
[http://docs.sqlalchemy.org/en/rel_0_9/orm/mapper_config.html#column-bundles Column Bundle].&lt;br /&gt;
I will be exploring adding the following feature to oslo.db:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
session.query(FixedIp.bundle).all()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The above will return a fast-loading, read-only Bundle object that otherwise&lt;br /&gt;
expresses the full interface of &amp;lt;code&amp;gt;FixedIp&amp;lt;/code&amp;gt;.   Relationships might be tricky&lt;br /&gt;
here but I can at least get them on board as regular lazy loads (just like&lt;br /&gt;
any other ORM would do anyway).&lt;br /&gt;
&lt;br /&gt;
=== Fast Object Save ===&lt;br /&gt;
&lt;br /&gt;
We explore replacing the unit of work &amp;lt;code&amp;gt;flush()&amp;lt;/code&amp;gt; call used by &amp;lt;code&amp;gt;object.save()&amp;lt;/code&amp;gt; within oslo.db-style applications with a more direct &amp;quot;flush single object&amp;quot; call; this grants us only a 12% savings so far, though it is very simple to use:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Running scenario default_optimized&lt;br /&gt;
Scenario default_optimized, total calls for 1000 operations: 1881626&lt;br /&gt;
Scenario default_optimized, total time for 10000 operations: 25&lt;br /&gt;
&lt;br /&gt;
Running scenario fast_save&lt;br /&gt;
Scenario fast_save, total calls for 1000 operations: 1685221&lt;br /&gt;
Scenario fast_save, total time for 10000 operations: 22&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Baked Queries ===&lt;br /&gt;
&lt;br /&gt;
Something that has been in the works for a long time and has&lt;br /&gt;
recently    seen lots of work in the past months is the &amp;quot;baked query&amp;quot;&lt;br /&gt;
feature; this    pattern is ideal for OpenStack's &amp;quot;many short queries&amp;quot;&lt;br /&gt;
pattern, and allows    caching of the generation of SQL.  Recent&lt;br /&gt;
versions of this pattern have    gotten very slick, and can cache&lt;br /&gt;
virtually everything that happens Python-wise    from the construction&lt;br /&gt;
of the &amp;lt;code&amp;gt;Query&amp;lt;/code&amp;gt; object, to calling all the methods    on the&lt;br /&gt;
query, to the query-objects construction of a Core SQL statement,&lt;br /&gt;
to the compilation of that statement as a string - all of these steps&lt;br /&gt;
are removed from the call-graph after the first such call.  In&lt;br /&gt;
SQLAlchemy 1.0    I've also thrown in the construction of column&lt;br /&gt;
metadata from the result set    too.   The pattern involves a bit more&lt;br /&gt;
verbosity to that of constructing a    query, where here I've built&lt;br /&gt;
off of some of the ideas of the    Pony ORM to use Python function&lt;br /&gt;
information as the source of a cache key.    A query such as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
result = model_query(&lt;br /&gt;
            context, models.FloatingIp, session=session).\&lt;br /&gt;
            filter_by(address=address)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
would be expressed in &amp;quot;baked&amp;quot; form as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# note model_query is using the &amp;quot;baked&amp;quot; process internally as well&lt;br /&gt;
result = model_query(context, models.FloatingIp, session=session)&lt;br /&gt;
&lt;br /&gt;
result.bake(lambda query:&lt;br /&gt;
    query.filter_by(&lt;br /&gt;
        address=bindparam('address'))).params(address=address)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the above form, everything within each lambda is invoked only once,&lt;br /&gt;
the result of which becomes part of a cached value.&lt;br /&gt;
&lt;br /&gt;
For this slight increase in verbosity, we get an improvement like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Running scenario default_optimized&lt;br /&gt;
Scenario default_optimized, total calls for 1000 operations: 1881626&lt;br /&gt;
Scenario default_optimized, total time for 10000 operations: 25&lt;br /&gt;
&lt;br /&gt;
Running scenario baked&lt;br /&gt;
Scenario baked, total calls for 1000 operations: 1052935&lt;br /&gt;
Scenario baked, total time for 10000 operations: 16&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
That is, around a 40% improvement.&lt;br /&gt;
&lt;br /&gt;
Putting together both &amp;quot;fast save&amp;quot; plus &amp;quot;baked&amp;quot; we get down to a full 50%&lt;br /&gt;
improvement vs. the plain optimized version:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Running scenario fast_save_plus_baked&lt;br /&gt;
Scenario fast_save_plus_baked, total calls for 1000 operations: 856035&lt;br /&gt;
Scenario fast_save_plus_baked, total time for 10000 operations: 13&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
13 seconds for 10K operations vs. 222 seconds in the current code, I hope&lt;br /&gt;
I've established that there is hope!&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Migrations ==&lt;br /&gt;
&lt;br /&gt;
Migrations are a huge deal.  Here's where it looks like this is going.&lt;br /&gt;
&lt;br /&gt;
Alembic - &amp;quot;Alembic is the defacto standard going forward&amp;quot; - ultimately&lt;br /&gt;
we want to get everything SQLAlchemy-Migrate onto Alembic.  And in talking&lt;br /&gt;
with Nova devs, they really like that they can test their migrations against&lt;br /&gt;
SQLite.  So from my POV, SQLite migrations on Alembic, we're doing that!&lt;br /&gt;
Unless folks think otherwise.&lt;br /&gt;
&lt;br /&gt;
=== SQLite Support ===&lt;br /&gt;
&lt;br /&gt;
SQLite has extremely limited support for ALTER TABLE, essentially only being able&lt;br /&gt;
to add new columns, and that's about it.  In order to provide the full suite&lt;br /&gt;
of ALTER operations that all other databases provide, tools such as&lt;br /&gt;
SQLAlchemy-Migrate will create a copy of the target table with changes applied,&lt;br /&gt;
copy data from old table to new, then drop the old table and rename the new one.&lt;br /&gt;
I've always been peeved at SQLite's insistence on never providing some better&lt;br /&gt;
pathway to ALTER built in.  But it has been on the Alembic roadmap to add SQLite migrations&lt;br /&gt;
in a style similar to that of Migrate for a long time, emulating this same&lt;br /&gt;
approach in some way.  Alembic will have this!&lt;br /&gt;
&lt;br /&gt;
However in talking with some folks, it appears like some people might actually&lt;br /&gt;
like this &amp;quot;make a new table and switch it&amp;quot; approach for other databases too,&lt;br /&gt;
as a way to work around locking of tables.  I don't know that I agree with this&lt;br /&gt;
long term, the &amp;quot;ALTER locks the database!&amp;quot; issue in my experience has always&lt;br /&gt;
been kind of a MySQL problem alone, but it can be present for other backends&lt;br /&gt;
as well.&lt;br /&gt;
&lt;br /&gt;
So the API I'm looking towards here is going to try to solve a few problems;&lt;br /&gt;
one is that, copying the whole table to a new one each time for *every single&lt;br /&gt;
column* is crazy, and also that the technique we use on SQLite might be nice&lt;br /&gt;
for other databases too, so here's the API:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
with op.batch_alter(&amp;quot;mytable&amp;quot;, recreate=True) as batch_op:&lt;br /&gt;
    batch_op.drop_column(&amp;quot;some_column&amp;quot;)&lt;br /&gt;
    batch_op.alter_column(&amp;quot;q&amp;quot;, type=Integer)&lt;br /&gt;
    batch_op.add_column(Column('y', String(50), nullable=False))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
That is, we assign a whole series of column operations under the umbrella of&lt;br /&gt;
one table, and they are created at once.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;batch_alter()&amp;lt;/code&amp;gt; call can even be conditional, dropping into traditional&lt;br /&gt;
migration behavior for all backends except SQLite and whatever else:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
with op.batch_alter(&amp;quot;mytable&amp;quot;, recreate_on=('sqlite',)) as batch_op:&lt;br /&gt;
    batch_op.drop_column(&amp;quot;some_column&amp;quot;)&lt;br /&gt;
    batch_op.alter_column(&amp;quot;q&amp;quot;, type=Integer)&lt;br /&gt;
    batch_op.add_column(Column('y', String(50), nullable=False))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
SQlite-compatible alterations is [https://bitbucket.org/zzzeek/alembic/issue/21/column-renames-not-supported-on-sqlite issue #21].&lt;br /&gt;
&lt;br /&gt;
=== Finishing up Autogenerate ===&lt;br /&gt;
&lt;br /&gt;
I started writing Alembic long before SQLAlchemy-Migrate had autogenerate.  But by the time&lt;br /&gt;
I got Alembic released and later autogenerate working, Migrate already had it.&lt;br /&gt;
Here's what we need to finish up in autogenerate:&lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;code&amp;gt;ForeignKeyConstraint&amp;lt;/code&amp;gt; change detection - this is an entirely straightforward feature add and has long been on the todo list.&lt;br /&gt;
&lt;br /&gt;
2. MySQL implicit indexes - this is actually done; recent versions of Alembic can navigate around MySQLs goofy production of indexes automatically on foreign key columns, and not accidentally spit them out in autogenerates. It was seriously tough to get index autogen mostly working on all backends, so new issues will continue to be fixed as they are reported.&lt;br /&gt;
&lt;br /&gt;
3. Type comparison - Alembic balks on type comparison by default, because ultimately SQLAlchemy should add comparision features to its type objects natively. However, on Alembic backends like that of MySQL, we can add explicit rules for the very common and annoying ones, namely the detection of MySQL's TINYINT as a SQLAlchemy Boolean.  Alembic already allows user-specified rules in this regard, so they can be part of oslo.db in any case, however Alembic can also add this rule and others in directly.&lt;br /&gt;
&lt;br /&gt;
4. Table order - Alembic's autogenerate should be spitting out table creates and drops in the order of foreign key dependency, though we don't have good test coverage for this yet and it might not be working.  We'll fix that, no biggie!  Alternatively, Alembic could include a mode of operation that includes all the ForeignKeyConstraint objects after all the tables. Input on how we'd like to see this work would be welcome.&lt;br /&gt;
&lt;br /&gt;
=== New Migration Features ===&lt;br /&gt;
&lt;br /&gt;
There's a lot of features I'd like to add to Alembic, and if OpenStack has&lt;br /&gt;
a need for them, that would justify the effort:&lt;br /&gt;
&lt;br /&gt;
1. Full branch support - [https://bitbucket.org/zzzeek/alembic/issue/167/multiple-heads-branch-resolution-support issue 167 multiple heads / branch resolution support] has a detailed architecture laid out for this feature.  Lots of people are looking for it. This would turn Alembic's current &amp;quot;linked list&amp;quot; version model into a full directed acyclic graph (DAG).   Any particular migration can be dependent on any other group of migrations, or none at all; individual branches can be maintained and upgraded along their path, or automatically merged.&lt;br /&gt;
&lt;br /&gt;
2. [https://bitbucket.org/zzzeek/alembic/issue/124/multiple-versions-directories Multiple version directories] - This would allow migration files to be present in more than one place for a single migrations environment.  Currently, you can get this approach by using multiple base directories, but that requires separate &amp;lt;code&amp;gt;env.py&amp;lt;/code&amp;gt; files for each. With this issue, cross-dependent migration files can live in multiple places, working nicely with the multiple heads support of #167.&lt;br /&gt;
&lt;br /&gt;
== Testing ==&lt;br /&gt;
&lt;br /&gt;
I spent quite a bit looking into testing.  The main thing we can see&lt;br /&gt;
in testing is that there may be a desire for &amp;quot;transactional testing&amp;quot;;&lt;br /&gt;
run tests within a fixed schema, that is the schema    is created&lt;br /&gt;
once, many tests are run, each one rolling back their work, the&lt;br /&gt;
schema is only dropped once all tests for that schema are complete.&lt;br /&gt;
&lt;br /&gt;
Transactional testing is described in the SQLAlchemy docs and in some of&lt;br /&gt;
my talks; for OpenStack, we need the version that supports graceful handling&lt;br /&gt;
of tests that actually do a rollback.  This is doable and the&lt;br /&gt;
recipe for that is currently [https://gist.github.com/zzzeek/8443477 a github gist].&lt;br /&gt;
&lt;br /&gt;
In order to accommodate parallelized runs, the system will create N&lt;br /&gt;
temporary schemas for N concurrent runs.&lt;br /&gt;
&lt;br /&gt;
I really want to get into this feature but as I talk to people, I'm&lt;br /&gt;
getting mixed signals on how important this feature is.   So far I've&lt;br /&gt;
only worked with unit tests, which seem to be entirely against SQLite&lt;br /&gt;
in-memory databases, and I haven't gotten my head around to what&lt;br /&gt;
extent other databases besides SQLite might be desireable in unit&lt;br /&gt;
tests, nor have I gotten to fully understand how integration tests&lt;br /&gt;
run, so input is welcome here.&lt;br /&gt;
&lt;br /&gt;
== Improvement of ORM Patterns ==&lt;br /&gt;
&lt;br /&gt;
Looking around oslo.db and other apps, I want to improve upon&lt;br /&gt;
some kinds of verbosity I see, which make the code more repetetive and inconsistent, welds together application-level and business level concepts (e.g. transaction control and exception reporting melded with data manipulation) and makes it harder to reason about how the application interacts with sessions, as there are a variety of ad-hoc patterns present.&lt;br /&gt;
&lt;br /&gt;
=== Transaction Demarcation ===&lt;br /&gt;
&lt;br /&gt;
For the oft-repeated &amp;quot;get_session(); with session.begin(subtransactions=True)&amp;quot; phrase and other variants, I'd like to move that into something more abstracted:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def my_api_thing(x):&lt;br /&gt;
    with transactional() as session:&lt;br /&gt;
    	# do stuff&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I want context managers and/or decorators that are oslo.db specific to replace any kind of containing code that deals with transactions or sessions. That way the containinment logic can be maintained in once place and configured separately from the business logic contained within.  Projects like Keystone are already using a form more like this; it would be nice to nail down a consistent pattern and get it into oslo.db for everyone to use.&lt;br /&gt;
&lt;br /&gt;
=== Exception Rewriting ===&lt;br /&gt;
&lt;br /&gt;
Exception rewriting - I notice that the oslo.db pattern includes that DBAPI level exceptions are re-thrown as OpenStack specific;  I'd like to move this into handling using SQLAlchemy events.  The [http://docs.sqlalchemy.org/en/rel_0_9/core/events.html#sqlalchemy.events.ConnectionEvents.dbapi_error dbapi_error] event goes all the way back to 0.7, and [https://bitbucket.org/zzzeek/sqlalchemy/issue/3076/support-reraise-of-custom-exceptions ticket 3076] will allow it to be used to reraise with a new exception.  For older SQLAlchemy versions, I can create a wrapper that will allow it to be used in this way safely for older versions, despite the warnings stated on that document.&lt;br /&gt;
&lt;br /&gt;
=== Pessimistic Locking - SELECT FOR UPDATE ===&lt;br /&gt;
&lt;br /&gt;
This issue has been coming up for some projects, that they make sometimes great use of SQLAlchemy's &amp;lt;code&amp;gt;with_lockmode()&amp;lt;/code&amp;gt; method, which ultimately emits SELECT..FOR UPDATE, so that selected rows are aggressively locked within a transaction.   The issue is that backends like Galera don't support FOR UPDATE, meaning the locking that is desired is not actually provided.&lt;br /&gt;
&lt;br /&gt;
While I'm not sure that there's a generalized solution to this, in some cases I've been helping developers move to more of an &amp;quot;optimistic&amp;quot; approach, where a particular UPDATE statement targets some kind of timestamp or value counter in the row, then counts the number of rows matched.  If the number of rows matched is not what's expected, it is assumed that the UPDATE failed to match the row, and the operation can either try again or can bail out.   This approach might not be suitable in all cases.  In cases where a large stream of data is affecting a relatively small set of rows, it can perhaps be feasible to serialize those updates by sending them all to a queue, so that they are invoked in isolation without the need for up-front locking.   For very high throughput situations, queues can be constructed to use multiple, concurrent UPDATE streams separated via hashing, or can batch together updates for particular rows over a period of time and emit fewer UPDATE statements than if each source of data did it individually.&lt;br /&gt;
&lt;br /&gt;
=== Encouraging Bundle / Baked Query Use ===&lt;br /&gt;
&lt;br /&gt;
Encouraging Bundle use - as stated earlier, I want to look into bringing the very efficient [http://docs.sqlalchemy.org/en/rel_0_9/orm/mapper_config.html#column-bundles Column Bundle] pattern into the fold for mainstream use, not to mention the baked query pattern illustrated previously.&lt;br /&gt;
&lt;br /&gt;
=== Transparent Retries of Transactions ===&lt;br /&gt;
&lt;br /&gt;
I have ready to go an [https://bitbucket.org/zzzeek/sqlalchemy/issue/3104/transaction-replay-extension already in production extension for transaction replaying], developed for a client with the agreement that the extension is open source.  This extension installs as an event handler using a new series of hooks that were added in 0.9, or alternatively using a series of compatibility hooks that were backported to 0.7 and 0.8.   This extension allows logging of SQL statements as they proceed within a transaction, and allows them to be transparently replayed when a database disconnect error occurs. This means that SQLAlchemy's usual behavior of raising an exception and forcing you to re-try explicitly can be hidden so that it occurs implicitly, without any interruption or special error handling required in application code.  The extension includes a lot of configuration options, such as what statements should be replayed and which shouldn't, as well as what exceptions should be handled this way.   Transaction replaying is an elaborate feature which might introduce lots of new edge cases, namely that the state of the database may have changed since the last transaction failed, so to that extent I'm a little wary of this feature myself.  However I have seen lots of messages alluding to this kind of thing.    The mechanics to pull this into SQLAlchemy as a transparent feature are ready to go, if this is a feature people are looking for (I'm not sure if people are looking for this).&lt;br /&gt;
&lt;br /&gt;
== Contributors ==&lt;br /&gt;
&lt;br /&gt;
I would be remiss if I didn't mention that SQLAlchemy, Alembic, Dogpile and everything else I do are collaborative projects, and while SQLAlchemy in particular might present a daunting codebase, we would love to get new collaborators on board!   Any of the features above are fair game and I can walk someone through the steps to be taken, and additionally everything we add features for always needs lots of tests and review, if you're looking for a way to get into the project I can bring you right in, there's lots of (often very tedious and boring) work to be done.   I'm on all the IRC channels now as zzzeek so ping away.&lt;br /&gt;
&lt;br /&gt;
== Coffee and Cake ==&lt;br /&gt;
&lt;br /&gt;
So! I hope you've enjoyed reading this document, and whether you're a fan or a hater, please know that I'm on board now and I'm here to help, with whatever database kind of thing you want to do - it's all part of my job now.    If we wanted to use/build a whole new ORM, I'd be doing that too right alongside.  OpenStack's lessons in database integration are now my lessons too.  So let me know where I should dig in first!  Have a save drive home.&lt;/div&gt;</summary>
		<author><name>Mike Bayer</name></author>	</entry>

	<entry>
		<id>https://wiki.openstack.org/w/index.php?title=OpenStack_and_SQLAlchemy&amp;diff=57067</id>
		<title>OpenStack and SQLAlchemy</title>
		<link rel="alternate" type="text/html" href="https://wiki.openstack.org/w/index.php?title=OpenStack_and_SQLAlchemy&amp;diff=57067"/>
				<updated>2014-07-01T01:09:25Z</updated>
		
		<summary type="html">&lt;p&gt;Mike Bayer: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= OpenStack and SQLAlchemy =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
This page describes the ongoing efforts of Mike Bayer, creator/maintainer of&lt;br /&gt;
SQLAlchemy, Alembic Migrations, and Dogpile caching, to spearhead what is&lt;br /&gt;
essentially an &amp;quot;OpenStack SQLAlchemy 2.0&amp;quot; effort - as I am now full-time&lt;br /&gt;
employed by an OpenStack vendor (Red Hat), I've been hired to produce a strong&lt;br /&gt;
and sustained pass through OpenStack's relational database integration,&lt;br /&gt;
improving areas that have been problematic and establishing a very strong&lt;br /&gt;
set of practices that are specific to OpenStack's needs.&lt;br /&gt;
&lt;br /&gt;
Within this effort, I'd like to stress that from my POV, everything is&lt;br /&gt;
on the table, including new improvements / fixes / features to&lt;br /&gt;
SQLAlchemy and Alembic directly, migration of projects for which&lt;br /&gt;
relational databases aren't suitable to non-relational systems,&lt;br /&gt;
improvement of relational schemas on the OpenStack side, and of course&lt;br /&gt;
improvement of exising relational patterns within OpenStack&lt;br /&gt;
applications, for which oslo.db will serve as the foundation.&lt;br /&gt;
&lt;br /&gt;
As of June 30, 2014, I've been on the job for about a month, so below&lt;br /&gt;
is an outline of most of what I've come across and my thoughts going forward.&lt;br /&gt;
&lt;br /&gt;
My goal here is to share with the community what I've seen and done so far,&lt;br /&gt;
so that interested parties can chime in with further advice and information,&lt;br /&gt;
and most importantly to help steer me towards the initial blueprints that&lt;br /&gt;
I should be starting on, that is, which features are most likely to be&lt;br /&gt;
accepted by the community as well as which are the top priority.  Not to mention, if you're looking&lt;br /&gt;
for something to do, you can help me implement some of these things!&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== OpenStack presence within SQLAlchemy's CI environment (a.k.a. &amp;quot;The Version that shall not be Named&amp;quot;) ==&lt;br /&gt;
&lt;br /&gt;
On Monday, June 23, I got up in the AM with two goals: get a release of SQLAlchemy&lt;br /&gt;
out the door, as it had been several months since the last point release, and then&lt;br /&gt;
do some work learning how Nova's unit tests run and function.&lt;br /&gt;
&lt;br /&gt;
Release 0.9.5 of SQLAlchemy was not particularly different than any of the other&lt;br /&gt;
hundred or so point releases I've put out over nine years, it was a little more overdue&lt;br /&gt;
than some, and out it went.  Later that day, as I was learning to run Nova's test suite, I&lt;br /&gt;
was hitting some failures within the FixedIp/FloatingIp tests for which I couldn't&lt;br /&gt;
find the cause, assuming that I just wasn't setting up my environment correctly,&lt;br /&gt;
as Nova's tests are passing for all the Jenkins servers the vast majority of the time.&lt;br /&gt;
&lt;br /&gt;
As we learned within about 30 minutes of my hitting these problems, it&lt;br /&gt;
turns out 0.9.5 had a bad fix in it; one that didn't fail any of&lt;br /&gt;
SQLAlchemy's 5000 tests but hit a use case within Nova that was not&lt;br /&gt;
very unusual; not the kind of use I usually do myself but nonetheless&lt;br /&gt;
something that was pretty common.   IRC began  jumping that builds&lt;br /&gt;
were failing everywhere and it was due to the 0.9.5 release!   The&lt;br /&gt;
bizarre coiincidence of this was that none of the thirty-odd point&lt;br /&gt;
releases of SQLAlchemy released under OpenStack's lifetime have ever&lt;br /&gt;
really had this effect, only the major jumps saw any issues.&lt;br /&gt;
&lt;br /&gt;
Luckily, as I had just learned 30 minutes earlier how to run Nova's tests I was&lt;br /&gt;
able to drill down to exactly what was going on and SQLAlchemy 0.9.6 was out&lt;br /&gt;
within an hour, but we shall now forever see in requirements.txt, the Version&lt;br /&gt;
that shall Not Be Named (except in requirements.txt):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
SQLAlchemy&amp;gt;=0.7.8,!=0.9.5,&amp;lt;=0.9.99&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In response to this I have now&lt;br /&gt;
[http://jenkins.sqlalchemy.org/job/openstack_sqla_master/ added openstack builds to our Jenkins CI system],&lt;br /&gt;
so far running tests for oslo.db and the SQLAlchemy tests within Nova; I spent&lt;br /&gt;
a couple of days figuring out how to get everything to build and run against&lt;br /&gt;
specific versions of SQLAlchemy and Alembic, even versions above the 0.9.99 in&lt;br /&gt;
all the requirements files as we are set for 1.0 hopefully within the next year.&lt;br /&gt;
I will be adding in as many other OpenStack projects as I can here so that we will no&lt;br /&gt;
longer have a SQLAlchemy release that hasn't been run against a battery of&lt;br /&gt;
basic OpenStack tests.&lt;br /&gt;
&lt;br /&gt;
== ORM Woes ==&lt;br /&gt;
&lt;br /&gt;
After chatting with a decent amount of folks for a month, I can break up&lt;br /&gt;
the take on OpenStack / SQLAlchemy ORM into three camps:&lt;br /&gt;
&lt;br /&gt;
1. We love SQLAlchemy ORM, we know that we can improve how we use it and we're looking forward to getting there (oslo.db, Keystone)&lt;br /&gt;
&lt;br /&gt;
2. Our app uses a relational database but the SQLAlchemy ORM slows us down, we already have our own &amp;quot;orm-like&amp;quot; system (e.g. APIs that shuttle rows into &amp;quot;values&amp;quot; dictionaries, nova.objects, etc.) so we want to ultimately get rid of the ORM part, and probably continue using SQLAlchemy Core to some degree (some Nova developers I've spoken to).&lt;br /&gt;
&lt;br /&gt;
3. Our application has a SQLAlchemy backend that almost nobody uses in production, most users use MongoDB, and the SQLAlchemy backend is only there because MongoDB is AGPL (Ceilometer, Marconi).  We'd like to get a better NoSQL backend without Mongo's licensing issue and do away with relational storage entirely ([http://lists.openstack.org/pipermail/openstack-dev/2014-June/038778.html Marconi]).&lt;br /&gt;
&lt;br /&gt;
Here's my observations / thoughts about these three camps:&lt;br /&gt;
&lt;br /&gt;
1. Great!  Read on, I have some things we can start with.&lt;br /&gt;
&lt;br /&gt;
2. I agree that a lot of the patterns that I've seen in Nova in particular are not well-suited to the approach used by SQLAlchemy's ORM, however I have observed that the applications still rely a lot on very sophisticated and mature features provided by SQLAlchemy (namely &amp;lt;code&amp;gt;relationship()&amp;lt;/code&amp;gt; and it's integration with eager loading and the unit of work features), and throwing it away entirely will incur a lot of reinvention, not just of the features that were lost but also of a totally new crop of stability and performance issues to solve.&lt;br /&gt;
&lt;br /&gt;
To that degree, I'd like to propose a twist to the &amp;quot;we want to write our own ORM&amp;quot; idea and say, &amp;quot;let's write our own ORM that's based on the SQLAlchemy ORM primitives&amp;quot;, e.g. a hybrid approach where existing ORM logic that is fine can stay that way, ORM logic that has performance or behavioral issues can be reorganized using systems, some of them new, some of them just underutilized, that bypass unneeded SQLAlchemy features on a case-by-case basis.&lt;br /&gt;
&lt;br /&gt;
As a secondary issue, some devs I've spoken to have referred to the nature&lt;br /&gt;
of OpenStack API-based applications being a part of the problem.&lt;br /&gt;
This refers to the fact that OpenStack apps like Nova&lt;br /&gt;
and Neutron expose fine-grained APIs which end up&lt;br /&gt;
breaking out composed use cases into dozens or hundreds of small API&lt;br /&gt;
operations, each of which invokes within it's own database transaction and&lt;br /&gt;
ORM session state.  This makes it impossible, without introducing some elaborate&lt;br /&gt;
offline state management system, for a larger operation to be&lt;br /&gt;
performed so that larger groups of objects can be loaded and manipulated&lt;br /&gt;
at once, greatly decreasing the number of database round trips present.&lt;br /&gt;
No matter what ORM you use or don't use, the pattern of many small round trips&lt;br /&gt;
is difficult to scale on relational databases.&lt;br /&gt;
&lt;br /&gt;
There are under way projects which aim to apply detailed profiling&lt;br /&gt;
to OpenStack applications so that a breakdown of performance issues due&lt;br /&gt;
to API use and/or database&lt;br /&gt;
access might be plainly reasoned about; see [https://github.com/stackforge/rally Rally]&lt;br /&gt;
and [https://github.com/stackforge/osprofiler OS Profiler] for examples.&lt;br /&gt;
&lt;br /&gt;
On my end, I have no intention of focusing on this second issue for the time&lt;br /&gt;
being, I'm going to stick with leaving OS applications as much as they are right now as possible,&lt;br /&gt;
applying iterative changes to database access code which I hope can optimize&lt;br /&gt;
to a significant degree within the current usage patterns.&lt;br /&gt;
&lt;br /&gt;
3. Projects with alternate MongoDB / MySQL backends (since after all, MySQL is the database here being rejected, SQLAlchemy is just a Python API) should probably do what they're doing and go to an all key/value model.  It is not at all fun to maintain the same datamodel and API against both a KV store as well as relational tables.  I had a detailed look at Ceilometer with its team members early on, and while I'm pretty confident that if it were desireable, I could get it's relational backend to compete with the Mongo backend performance-wise, there's no reason to get into this if the Mongo licensing issue can be solved alone.&lt;br /&gt;
&lt;br /&gt;
== ORM Quick Wins Proof of Concept ==&lt;br /&gt;
&lt;br /&gt;
I've built up a proof of concept at [https://github.com/zzzeek/nova_poc nova_poc] which seeks to demonstrate&lt;br /&gt;
the range of database and ORM performance that can be achieved within&lt;br /&gt;
one particular method of the Nova API.   Naturally, I picked an example that&lt;br /&gt;
is very juicy in this regard; it has a very easily fixable issue with the&lt;br /&gt;
kind of query it emits that has an enormous 10x impact on its performance&lt;br /&gt;
(which I feel is worth illustrating just for how dramatic it is),&lt;br /&gt;
and it then illustrates some new SQLAlchemy extensions that more or less&lt;br /&gt;
may be going into SQLAlchemy directly, which allow the remaining operation&lt;br /&gt;
to complete in less than half the time.  These extensions can be ported to&lt;br /&gt;
oslo.db to work against all supported SQLAlchemy versions prior to the official&lt;br /&gt;
release of these features.&lt;br /&gt;
&lt;br /&gt;
To summarize, we're looking at the API method &amp;lt;code&amp;gt;floating_ip_update()&amp;lt;/code&amp;gt;, and&lt;br /&gt;
I run a series of profiles against a database table 10K rows (e.g. &amp;lt;code&amp;gt;FloatingIp&amp;lt;/code&amp;gt;&lt;br /&gt;
objects), where each &amp;lt;code&amp;gt;FloatingIp&amp;lt;/code&amp;gt; refers to a single &amp;lt;code&amp;gt;FixedIp&amp;lt;/code&amp;gt; that then&lt;br /&gt;
refers to an &amp;lt;code&amp;gt;Instance&amp;lt;/code&amp;gt;.   The profiling focuses on the nature of this&lt;br /&gt;
method as an API feature all it's own, and simulates the case where the API&lt;br /&gt;
method is called thousands of times, not unlike other API methods that seek&lt;br /&gt;
to add some small amount of data each time they are called.&lt;br /&gt;
&lt;br /&gt;
=== Eager load and Column load tuning ===&lt;br /&gt;
&lt;br /&gt;
Turning off an eagerload in the &amp;lt;code&amp;gt;_floating_ip_get_by_address()&amp;lt;/code&amp;gt; function&lt;br /&gt;
is the most dramatic win; while removing this method seems like a reasonable&lt;br /&gt;
fix to remove some unneeded overhead, in profiling it shows itself to&lt;br /&gt;
be enormous:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Running scenario default&lt;br /&gt;
Scenario default, total calls for 1000 operations: 24543047&lt;br /&gt;
Scenario default, total time for 10000 operations: 222&lt;br /&gt;
&lt;br /&gt;
Running scenario default_optimized&lt;br /&gt;
Scenario default_optimized, total calls for 1000 operations: 1881626&lt;br /&gt;
Scenario default_optimized, total time for 10000 operations: 25&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
While not illustrated in the POC code itself, talk with various OpenStack&lt;br /&gt;
devs has revealed that we could probably do a lot more individual column&lt;br /&gt;
fetching as well, that is instead of:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
session.query(FixedIp).all()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We say:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
session.query(FixedIp.address, FixedIp.host, FixedIp.reserved).all()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
By loading indivdual columns, we get read-only objects that are not identity&lt;br /&gt;
managed, bypassing a huge amount of work that is done on instances both&lt;br /&gt;
in instantiating them, populating relationships and doing bookkeeping.&lt;br /&gt;
Any API feature that only returns a simple read-only object or a &amp;quot;values&amp;quot;&lt;br /&gt;
dictionary that does not rely upon relationship loading should be using&lt;br /&gt;
this pattern, or similar, by using a&lt;br /&gt;
[http://docs.sqlalchemy.org/en/rel_0_9/orm/mapper_config.html#column-bundles Column Bundle].&lt;br /&gt;
I will be exploring adding the following feature to oslo.db:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
session.query(FixedIp.bundle).all()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The above will return a fast-loading, read-only Bundle object that otherwise&lt;br /&gt;
expresses the full interface of &amp;lt;code&amp;gt;FixedIp&amp;lt;/code&amp;gt;.   Relationships might be tricky&lt;br /&gt;
here but I can at least get them on board as regular lazy loads (just like&lt;br /&gt;
any other ORM would do anyway).&lt;br /&gt;
&lt;br /&gt;
=== Fast Object Save ===&lt;br /&gt;
&lt;br /&gt;
We explore replacing the unit of work &amp;lt;code&amp;gt;flush()&amp;lt;/code&amp;gt; call used by &amp;lt;code&amp;gt;object.save()&amp;lt;/code&amp;gt; within oslo.db-style applications with a more direct &amp;quot;flush single object&amp;quot; call; this grants us only a 12% savings so far, though it is very simple to use:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Running scenario default_optimized&lt;br /&gt;
Scenario default_optimized, total calls for 1000 operations: 1881626&lt;br /&gt;
Scenario default_optimized, total time for 10000 operations: 25&lt;br /&gt;
&lt;br /&gt;
Running scenario fast_save&lt;br /&gt;
Scenario fast_save, total calls for 1000 operations: 1685221&lt;br /&gt;
Scenario fast_save, total time for 10000 operations: 22&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Baked Queries ===&lt;br /&gt;
&lt;br /&gt;
Something that has been in the works for a long time and has&lt;br /&gt;
recently    seen lots of work in the past months is the &amp;quot;baked query&amp;quot;&lt;br /&gt;
feature; this    pattern is ideal for OpenStack's &amp;quot;many short queries&amp;quot;&lt;br /&gt;
pattern, and allows    caching of the generation of SQL.  Recent&lt;br /&gt;
versions of this pattern have    gotten very slick, and can cache&lt;br /&gt;
virtually everything that happens Python-wise    from the construction&lt;br /&gt;
of the &amp;lt;code&amp;gt;Query&amp;lt;/code&amp;gt; object, to calling all the methods    on the&lt;br /&gt;
query, to the query-objects construction of a Core SQL statement,&lt;br /&gt;
to the compilation of that statement as a string - all of these steps&lt;br /&gt;
are removed from the call-graph after the first such call.  In&lt;br /&gt;
SQLAlchemy 1.0    I've also thrown in the construction of column&lt;br /&gt;
metadata from the result set    too.   The pattern involves a bit more&lt;br /&gt;
verbosity to that of constructing a    query, where here I've built&lt;br /&gt;
off of some of the ideas of the    Pony ORM to use Python function&lt;br /&gt;
information as the source of a cache key.    A query such as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
result = model_query(&lt;br /&gt;
            context, models.FloatingIp, session=session).\&lt;br /&gt;
            filter_by(address=address)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
would be expressed in &amp;quot;baked&amp;quot; form as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# note model_query is using the &amp;quot;baked&amp;quot; process internally as well&lt;br /&gt;
result = model_query(context, models.FloatingIp, session=session)&lt;br /&gt;
&lt;br /&gt;
result.bake(lambda query:&lt;br /&gt;
    query.filter_by(&lt;br /&gt;
        address=bindparam('address'))).params(address=address)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the above form, everything within each lambda is invoked only once,&lt;br /&gt;
the result of which becomes part of a cached value.&lt;br /&gt;
&lt;br /&gt;
For this slight increase in verbosity, we get an improvement like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Running scenario default_optimized&lt;br /&gt;
Scenario default_optimized, total calls for 1000 operations: 1881626&lt;br /&gt;
Scenario default_optimized, total time for 10000 operations: 25&lt;br /&gt;
&lt;br /&gt;
Running scenario baked&lt;br /&gt;
Scenario baked, total calls for 1000 operations: 1052935&lt;br /&gt;
Scenario baked, total time for 10000 operations: 16&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
That is, around a 40% improvement.&lt;br /&gt;
&lt;br /&gt;
Putting together both &amp;quot;fast save&amp;quot; plus &amp;quot;baked&amp;quot; we get down to a full 50%&lt;br /&gt;
improvement vs. the plain optimized version:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Running scenario fast_save_plus_baked&lt;br /&gt;
Scenario fast_save_plus_baked, total calls for 1000 operations: 856035&lt;br /&gt;
Scenario fast_save_plus_baked, total time for 10000 operations: 13&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
13 seconds for 10K operations vs. 222 seconds in the current code, I hope&lt;br /&gt;
I've established that there is hope!&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Migrations ==&lt;br /&gt;
&lt;br /&gt;
Migrations are a huge deal.  Here's where it looks like this is going.&lt;br /&gt;
&lt;br /&gt;
Alembic - &amp;quot;Alembic is the defacto standard going forward&amp;quot; - ultimately&lt;br /&gt;
we want to get everything SQLAlchemy-Migrate onto Alembic.  And in talking&lt;br /&gt;
with Nova devs, they really like that they can test their migrations against&lt;br /&gt;
SQLite.  So from my POV, SQLite migrations on Alembic, we're doing that!&lt;br /&gt;
Unless folks think otherwise.&lt;br /&gt;
&lt;br /&gt;
=== SQLite Support ===&lt;br /&gt;
&lt;br /&gt;
SQLite has extremely limited support for ALTER TABLE, essentially only being able&lt;br /&gt;
to add new columns, and that's about it.  In order to provide the full suite&lt;br /&gt;
of ALTER operations that all other databases provide, tools such as&lt;br /&gt;
SQLAlchemy-Migrate will create a copy of the target table with changes applied,&lt;br /&gt;
copy data from old table to new, then drop the old table and rename the new one.&lt;br /&gt;
I've always been peeved at SQLite's insistence on never providing some better&lt;br /&gt;
pathway to ALTER built in.  But it has been on the Alembic roadmap to add SQLite migrations&lt;br /&gt;
in a style similar to that of Migrate for a long time, emulating this same&lt;br /&gt;
approach in some way.  Alembic will have this!&lt;br /&gt;
&lt;br /&gt;
However in talking with some folks, it appears like some people might actually&lt;br /&gt;
like this &amp;quot;make a new table and switch it&amp;quot; approach for other databases too,&lt;br /&gt;
as a way to work around locking of tables.  I don't know that I agree with this&lt;br /&gt;
long term, the &amp;quot;ALTER locks the database!&amp;quot; issue in my experience has always&lt;br /&gt;
been kind of a MySQL problem alone, but it can be present for other backends&lt;br /&gt;
as well.&lt;br /&gt;
&lt;br /&gt;
So the API I'm looking towards here is going to try to solve a few problems;&lt;br /&gt;
one is that, copying the whole table to a new one each time for *every single&lt;br /&gt;
column* is crazy, and also that the technique we use on SQLite might be nice&lt;br /&gt;
for other databases too, so here's the API:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
with op.batch_alter(&amp;quot;mytable&amp;quot;, recreate=True) as batch_op:&lt;br /&gt;
    batch_op.drop_column(&amp;quot;some_column&amp;quot;)&lt;br /&gt;
    batch_op.alter_column(&amp;quot;q&amp;quot;, type=Integer)&lt;br /&gt;
    batch_op.add_column(Column('y', String(50), nullable=False))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
That is, we assign a whole series of column operations under the umbrella of&lt;br /&gt;
one table, and they are created at once.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;batch_alter()&amp;lt;/code&amp;gt; call can even be conditional, dropping into traditional&lt;br /&gt;
migration behavior for all backends except SQLite and whatever else:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
with op.batch_alter(&amp;quot;mytable&amp;quot;, recreate_on=('sqlite',)) as batch_op:&lt;br /&gt;
    batch_op.drop_column(&amp;quot;some_column&amp;quot;)&lt;br /&gt;
    batch_op.alter_column(&amp;quot;q&amp;quot;, type=Integer)&lt;br /&gt;
    batch_op.add_column(Column('y', String(50), nullable=False))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
SQlite-compatible alterations is [https://bitbucket.org/zzzeek/alembic/issue/21/column-renames-not-supported-on-sqlite issue #21].&lt;br /&gt;
&lt;br /&gt;
=== Finishing up Autogenerate ===&lt;br /&gt;
&lt;br /&gt;
I started writing Alembic long before SQLAlchemy-Migrate had autogenerate.  But by the time&lt;br /&gt;
I got Alembic released and later autogenerate working, Migrate already had it.&lt;br /&gt;
Here's what we need to finish up in autogenerate:&lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;code&amp;gt;ForeignKeyConstraint&amp;lt;/code&amp;gt; change detection - this is an entirely straightforward feature add and has long been on the todo list.&lt;br /&gt;
&lt;br /&gt;
2. MySQL implicit indexes - this is actually done; recent versions of Alembic can navigate around MySQLs goofy production of indexes automatically on foreign key columns, and not accidentally spit them out in autogenerates. It was seriously tough to get index autogen mostly working on all backends, so new issues will continue to be fixed as they are reported.&lt;br /&gt;
&lt;br /&gt;
3. Type comparison - Alembic balks on type comparison by default, because ultimately SQLAlchemy should add comparision features to its type objects natively. However, on Alembic backends like that of MySQL, we can add explicit rules for the very common and annoying ones, namely the detection of MySQL's TINYINT as a SQLAlchemy Boolean.  Alembic already allows user-specified rules in this regard, so they can be part of oslo.db in any case, however Alembic can also add this rule and others in directly.&lt;br /&gt;
&lt;br /&gt;
4. Table order - Alembic's autogenerate should be spitting out table creates and drops in the order of foreign key dependency, though we don't have good test coverage for this yet and it might not be working.  We'll fix that, no biggie!  Alternatively, Alembic could include a mode of operation that includes all the ForeignKeyConstraint objects after all the tables. Input on how we'd like to see this work would be welcome.&lt;br /&gt;
&lt;br /&gt;
=== New Migration Features ===&lt;br /&gt;
&lt;br /&gt;
There's a lot of features I'd like to add to Alembic, and if OpenStack has&lt;br /&gt;
a need for them, that would justify the effort:&lt;br /&gt;
&lt;br /&gt;
1. Full branch support - [https://bitbucket.org/zzzeek/alembic/issue/167/multiple-heads-branch-resolution-support issue 167 multiple heads / branch resolution support] has a detailed architecture laid out for this feature.  Lots of people are looking for it. This would turn Alembic's current &amp;quot;linked list&amp;quot; version model into a full directed acyclic graph (DAG).   Any particular migration can be dependent on any other group of migrations, or none at all; individual branches can be maintained and upgraded along their path, or automatically merged.&lt;br /&gt;
&lt;br /&gt;
2. [https://bitbucket.org/zzzeek/alembic/issue/124/multiple-versions-directories Multiple version directories] - This would allow migration files to be present in more than one place for a single migrations environment.  Currently, you can get this approach by using multiple base directories, but that requires separate &amp;lt;code&amp;gt;env.py&amp;lt;/code&amp;gt; files for each. With this issue, cross-dependent migration files can live in multiple places, working nicely with the multiple heads support of #167.&lt;br /&gt;
&lt;br /&gt;
== Testing ==&lt;br /&gt;
&lt;br /&gt;
I spent quite a bit looking into testing.  The main thing we can see&lt;br /&gt;
in testing is that there may be a desire for &amp;quot;transactional testing&amp;quot;;&lt;br /&gt;
run tests within a fixed schema, that is the schema    is created&lt;br /&gt;
once, many tests are run, each one rolling back their work, the&lt;br /&gt;
schema is only dropped once all tests for that schema are complete.&lt;br /&gt;
&lt;br /&gt;
Transactional testing is described in the SQLAlchemy docs and in some of&lt;br /&gt;
my talks; for OpenStack, we need the version that supports graceful handling&lt;br /&gt;
of tests that actually do a rollback.  This is doable and the&lt;br /&gt;
recipe for that is currently [https://gist.github.com/zzzeek/8443477 a github gist].&lt;br /&gt;
&lt;br /&gt;
In order to accommodate parallelized runs, the system will create N&lt;br /&gt;
temporary schemas for N concurrent runs.&lt;br /&gt;
&lt;br /&gt;
I really want to get into this feature but as I talk to people, I'm&lt;br /&gt;
getting mixed signals on how important this feature is.   So far I've&lt;br /&gt;
only worked with unit tests, which seem to be entirely against SQLite&lt;br /&gt;
in-memory databases, and I haven't gotten my head around to what&lt;br /&gt;
extent other databases besides SQLite might be desireable in unit&lt;br /&gt;
tests, nor have I gotten to fully understand how integration tests&lt;br /&gt;
run, so input is welcome here.&lt;br /&gt;
&lt;br /&gt;
== Improvement of ORM Patterns ==&lt;br /&gt;
&lt;br /&gt;
Looking around oslo.db and other apps, I want to improve upon&lt;br /&gt;
some kinds of verbosity I see, which make the code more repetetive and inconsistent, welds together application-level and business level concepts (e.g. transaction control and exception reporting melded with data manipulation) and makes it harder to reason about how the application interacts with sessions, as there are a variety of ad-hoc patterns present.&lt;br /&gt;
&lt;br /&gt;
=== Transaction Demarcation ===&lt;br /&gt;
&lt;br /&gt;
For the oft-repeated &amp;quot;get_session(); with session.begin(subtransactions=True)&amp;quot; phrase and other variants, I'd like to move that into something more abstracted:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def my_api_thing(x):&lt;br /&gt;
    with transactional() as session:&lt;br /&gt;
    	# do stuff&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I want context managers and/or decorators that are oslo.db specific to replace any kind of containing code that deals with transactions or sessions. That way the containinment logic can be maintained in once place and configured separately from the business logic contained within.  Projects like Keystone are already using a form more like this; it would be nice to nail down a consistent pattern and get it into oslo.db for everyone to use.&lt;br /&gt;
&lt;br /&gt;
=== Exception Rewriting ===&lt;br /&gt;
&lt;br /&gt;
Exception rewriting - I notice that the oslo.db pattern includes that DBAPI level exceptions are re-thrown as OpenStack specific;  I'd like to move this into handling using SQLAlchemy events.  The [http://docs.sqlalchemy.org/en/rel_0_9/core/events.html#sqlalchemy.events.ConnectionEvents.dbapi_error dbapi_error] event goes all the way back to 0.7, and [https://bitbucket.org/zzzeek/sqlalchemy/issue/3076/support-reraise-of-custom-exceptions ticket 3076] will allow it to be used to reraise with a new exception.  For older SQLAlchemy versions, I can create a wrapper that will allow it to be used in this way safely for older versions, despite the warnings stated on that document.&lt;br /&gt;
&lt;br /&gt;
=== Pessimistic Locking - SELECT FOR UPDATE ===&lt;br /&gt;
&lt;br /&gt;
This issue has been coming up for some projects, that they make sometimes great use of SQLAlchemy's &amp;lt;code&amp;gt;with_lockmode()&amp;lt;/code&amp;gt; method, which ultimately emits SELECT..FOR UPDATE, so that selected rows are aggressively locked within a transaction.   The issue is that backends like Galera don't support FOR UPDATE, meaning the locking that is desired is not actually provided.&lt;br /&gt;
&lt;br /&gt;
While I'm not sure that there's a generalized solution to this, in some cases I've been helping developers move to more of an &amp;quot;optimistic&amp;quot; approach, where a particular UPDATE statement targets some kind of timestamp or value counter in the row, then counts the number of rows matched.  If the number of rows matched is not what's expected, it is assumed that the UPDATE failed to match the row, and the operation can either try again or can bail out.   This approach might not be suitable in all cases.  In cases where a large stream of data is affecting a relatively small set of rows, it can perhaps be feasible to serialize those updates by sending them all to a queue, so that they are invoked in isolation without the need for up-front locking.   For very high throughput situations, queues can be constructed to use multiple, concurrent UPDATE streams separated via hashing, or can batch together updates for particular rows over a period of time and emit fewer UPDATE statements than if each source of data did it individually.&lt;br /&gt;
&lt;br /&gt;
=== Encouraging Bundle / Baked Query Use ===&lt;br /&gt;
&lt;br /&gt;
Encouraging Bundle use - as stated earlier, I want to look into bringing the very efficient [http://docs.sqlalchemy.org/en/rel_0_9/orm/mapper_config.html#column-bundles Column Bundle] pattern into the fold for mainstream use, not to mention the baked query pattern illustrated previously.&lt;br /&gt;
&lt;br /&gt;
=== Transparent Retries of Transactions ===&lt;br /&gt;
&lt;br /&gt;
I have ready to go an [https://bitbucket.org/zzzeek/sqlalchemy/issue/3104/transaction-replay-extension already in production extension for transaction replaying], developed for a client with the agreement that the extension is open source.  This extension installs as an event handler using a new series of hooks that were added in 0.9, or alternatively using a series of compatibility hooks that were backported to 0.7 and 0.8.   This extension allows logging of SQL statements as they proceed within a transaction, and allows them to be transparently replayed when a database disconnect error occurs. This means that SQLAlchemy's usual behavior of raising an exception and forcing you to re-try explicitly can be hidden so that it occurs implicitly, without any interruption or special error handling required in application code.  The extension includes a lot of configuration options, such as what statements should be replayed and which shouldn't, as well as what exceptions should be handled this way.   Transaction replaying is an elaborate feature which might introduce lots of new edge cases, namely that the state of the database may have changed since the last transaction failed, so to that extent I'm a little wary of this feature myself.  However I have seen lots of messages alluding to this kind of thing.    The mechanics to pull this into SQLAlchemy as a transparent feature are ready to go, if this is a feature people are looking for (I'm not sure if people are looking for this).&lt;br /&gt;
&lt;br /&gt;
== Contributors ==&lt;br /&gt;
&lt;br /&gt;
I would be remiss if I didn't mention that SQLAlchemy, Alembic, Dogpile and everything else I do are collaborative projects, and while SQLAlchemy in particular might present a daunting codebase, we would love to get new collaborators on board!   Any of the features above are fair game and I can walk someone through the steps to be taken, and additionally everything we add features for always needs lots of tests and review, if you're looking for a way to get into the project I can bring you right in, there's lots of (often very tedious and boring) work to be done.   I'm on all the IRC channels now as zzzeek so ping away.&lt;br /&gt;
&lt;br /&gt;
== Coffee and Cake ==&lt;br /&gt;
&lt;br /&gt;
So! I hope you've enjoyed reading this document, and whether you're a fan or a hater, please know that I'm on board now and I'm here to help, with whatever database kind of thing you want to do - it's all part of my job now.    If we wanted to use/build a whole new ORM, I'd be doing that too right alongside.  OpenStack's lessons in database integration are now my lessons too.  So let me know where I should dig in first!  Have a save drive home.&lt;/div&gt;</summary>
		<author><name>Mike Bayer</name></author>	</entry>

	<entry>
		<id>https://wiki.openstack.org/w/index.php?title=OpenStack_and_SQLAlchemy&amp;diff=57013</id>
		<title>OpenStack and SQLAlchemy</title>
		<link rel="alternate" type="text/html" href="https://wiki.openstack.org/w/index.php?title=OpenStack_and_SQLAlchemy&amp;diff=57013"/>
				<updated>2014-06-30T17:24:33Z</updated>
		
		<summary type="html">&lt;p&gt;Mike Bayer: Mike Bayer moved page Openstack and SQLAlchemy to OpenStack and SQLAlchemy: spelling for OpenStack&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= OpenStack and SQLAlchemy =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
This page describes the ongoing efforts of Mike Bayer, creator/maintainer of&lt;br /&gt;
SQLAlchemy, Alembic Migrations, and Dogpile caching, to spearhead what is&lt;br /&gt;
essentially an &amp;quot;OpenStack SQLAlchemy 2.0&amp;quot; effort - as I am now full-time&lt;br /&gt;
employed by an OpenStack vendor (Red Hat), I've been hired to produce a strong&lt;br /&gt;
and sustained pass through OpenStack's relational database integration,&lt;br /&gt;
improving areas that have been problematic and establishing a very strong&lt;br /&gt;
set of practices that are specific to OpenStack's needs.&lt;br /&gt;
&lt;br /&gt;
Within this effort, I'd like to stress that from my POV, everything is&lt;br /&gt;
on the table, including new improvements / fixes / features to&lt;br /&gt;
SQLAlchemy and Alembic directly, migration of projects for which&lt;br /&gt;
relational databases aren't suitable to non-relational systems,&lt;br /&gt;
improvement of relational schemas on the OpenStack side, and of course&lt;br /&gt;
improvement of exising relational patterns within OpenStack&lt;br /&gt;
applications, for which oslo.db will serve as the foundation.&lt;br /&gt;
&lt;br /&gt;
As of June 30, 2014, I've been on the job for about a month, so below&lt;br /&gt;
is an outline of most of what I've come across and my thoughts going forward.&lt;br /&gt;
&lt;br /&gt;
My goal here is to share with the community what I've seen and done so far,&lt;br /&gt;
so that interested parties can chime in with further advice and information,&lt;br /&gt;
and most importantly to help steer me towards the initial blueprints that&lt;br /&gt;
I should be starting on, that is, which features are most likely to be&lt;br /&gt;
accepted by the community as well as which are the top priority.  Not to mention, if you're looking&lt;br /&gt;
for something to do, you can help me implement some of these things!&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== OpenStack presence within SQLAlchemy's CI environment (a.k.a. &amp;quot;The Version that shall not be Named&amp;quot;) ==&lt;br /&gt;
&lt;br /&gt;
On Monday, June 23, I got up in the AM with two goals: get a release of SQLAlchemy&lt;br /&gt;
out the door, as it had been several months since the last point release, and then&lt;br /&gt;
do some work learning how Nova's unit tests run and function.&lt;br /&gt;
&lt;br /&gt;
Release 0.9.5 of SQLAlchemy was not particularly different than any of the other&lt;br /&gt;
hundred or so point releases I've put out over nine years, it was a little more overdue&lt;br /&gt;
than some, and out it went.  Later that day, as I was learning to run Nova's test suite, I&lt;br /&gt;
was hitting some failures within the FixedIp/FloatingIp tests for which I couldn't&lt;br /&gt;
find the cause, assuming that I just wasn't setting up my environment correctly,&lt;br /&gt;
as Nova's tests are passing for all the Jenkins servers the vast majority of the time.&lt;br /&gt;
&lt;br /&gt;
As we learned within about 30 minutes of my hitting these problems, it&lt;br /&gt;
turns out 0.9.5 had a bad fix in it; one that didn't fail any of&lt;br /&gt;
SQLAlchemy's 5000 tests but hit a use case within Nova that was not&lt;br /&gt;
very unusual; not the kind of use I usually do myself but nonetheless&lt;br /&gt;
something that was pretty common.   IRC began  jumping that builds&lt;br /&gt;
were failing everywhere and it was due to the 0.9.5 release!   The&lt;br /&gt;
bizarre coiincidence of this was that none of the thirty-odd point&lt;br /&gt;
releases of SQLAlchemy released under OpenStack's lifetime have ever&lt;br /&gt;
really had this effect, only the major jumps saw any issues.&lt;br /&gt;
&lt;br /&gt;
Luckily, as I had just learned 30 minutes earlier how to run Nova's tests I was&lt;br /&gt;
able to drill down to exactly what was going on and SQLAlchemy 0.9.6 was out&lt;br /&gt;
within an hour, but we shall now forever see in requirements.txt, the Version&lt;br /&gt;
that shall Not Be Named (except in requirements.txt):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
SQLAlchemy&amp;gt;=0.7.8,!=0.9.5,&amp;lt;=0.9.99&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In response to this I have now&lt;br /&gt;
[http://jenkins.sqlalchemy.org/job/openstack_sqla_master/ added openstack builds to our Jenkins CI system],&lt;br /&gt;
so far running tests for oslo.db and the SQLAlchemy tests within Nova; I spent&lt;br /&gt;
a couple of days figuring out how to get everything to build and run against&lt;br /&gt;
specific versions of SQLAlchemy and Alembic, even versions above the 0.9.99 in&lt;br /&gt;
all the requirements files as we are set for 1.0 hopefully within the next year.&lt;br /&gt;
I will be adding in as many other OpenStack projects as I can here so that we will no&lt;br /&gt;
longer have a SQLAlchemy release that hasn't been run against a battery of&lt;br /&gt;
basic OpenStack tests.&lt;br /&gt;
&lt;br /&gt;
== ORM Woes ==&lt;br /&gt;
&lt;br /&gt;
After chatting with a decent amount of folks for a month, I can break up&lt;br /&gt;
the take on OpenStack / SQLAlchemy ORM into three camps:&lt;br /&gt;
&lt;br /&gt;
1. We love SQLAlchemy ORM, we know that we can improve how we use it and we're looking forward to getting there (oslo.db, Keystone)&lt;br /&gt;
&lt;br /&gt;
2. Our app uses a relational database but the SQLAlchemy ORM slows us down, we already have our own &amp;quot;orm-like&amp;quot; system (e.g. APIs that shuttle rows into &amp;quot;values&amp;quot; dictionaries) so we want to ultimately get rid of the ORM part, and probably continue using SQLAlchemy Core to some degree (some Nova developers I've spoken to).&lt;br /&gt;
&lt;br /&gt;
3. Our application has a SQLAlchemy backend that almost nobody uses in production, most users use MongoDB, and the SQLAlchemy backend is only there because MongoDB is AGPL (Ceilometer, Marconi).  We'd like to get a better NoSQL backend without Mongo's licensing issue and do away with relational storage entirely ([http://lists.openstack.org/pipermail/openstack-dev/2014-June/038778.html Marconi]).&lt;br /&gt;
&lt;br /&gt;
Here's my observations / thoughts about these three camps:&lt;br /&gt;
&lt;br /&gt;
1. Great!  Read on, I have some things we can start with.&lt;br /&gt;
&lt;br /&gt;
2. I agree that a lot of the patterns that I've seen in Nova in particular are not well-suited to the approach used by SQLAlchemy's ORM, however I have observed that the applications still rely a lot on very sophisticated and mature features provided by SQLAlchemy (namely &amp;lt;code&amp;gt;relationship()&amp;lt;/code&amp;gt; and it's integration with eager loading and the unit of work features), and throwing it away entirely will incur a lot of reinvention, not just of the features that were lost but also of a totally new crop of stability and performance issues to solve.&lt;br /&gt;
&lt;br /&gt;
To that degree, I'd like to propose a twist to the &amp;quot;we want to write our own ORM&amp;quot; idea and say, &amp;quot;let's write our own ORM that's based on the SQLAlchemy ORM primitives&amp;quot;, e.g. a hybrid approach where existing ORM logic that is fine can stay that way, ORM logic that has performance or behavioral issues can be reorganized using systems, some of them new, some of them just underutilized, that bypass unneeded SQLAlchemy features on a case-by-case basis.&lt;br /&gt;
&lt;br /&gt;
As a secondary issue, some devs I've spoken to have referred to the nature&lt;br /&gt;
of OpenStack API-based applications being a part of the problem.&lt;br /&gt;
This refers to the fact that OpenStack apps like Nova&lt;br /&gt;
and Neutron expose fine-grained APIs which end up&lt;br /&gt;
breaking out composed use cases into dozens or hundreds of small API&lt;br /&gt;
operations, each of which invokes within it's own database transaction and&lt;br /&gt;
ORM session state.  This makes it impossible, without introducing some elaborate&lt;br /&gt;
offline state management system, for a larger operation to be&lt;br /&gt;
performed so that larger groups of objects can be loaded and manipulated&lt;br /&gt;
at once, greatly decreasing the number of database round trips present.&lt;br /&gt;
No matter what ORM you use or don't use, the pattern of many small round trips&lt;br /&gt;
is difficult to scale on relational databases.&lt;br /&gt;
&lt;br /&gt;
There are under way projects which aim to apply detailed profiling&lt;br /&gt;
to OpenStack applications so that a breakdown of performance issues due&lt;br /&gt;
to API use and/or database&lt;br /&gt;
access might be plainly reasoned about; see [https://github.com/stackforge/rally Rally]&lt;br /&gt;
and [https://github.com/stackforge/osprofiler OS Profiler] for examples.&lt;br /&gt;
&lt;br /&gt;
On my end, I have no intention of focusing on this second issue for the time&lt;br /&gt;
being, I'm going to stick with leaving OS applications as much as they are right now as possible,&lt;br /&gt;
applying iterative changes to database access code which I hope can optimize&lt;br /&gt;
to a significant degree within the current usage patterns.&lt;br /&gt;
&lt;br /&gt;
3. Projects with alternate MongoDB / MySQL backends (since after all, MySQL is the database here being rejected, SQLAlchemy is just a Python API) should probably do what they're doing and go to an all key/value model.  It is not at all fun to maintain the same datamodel and API against both a KV store as well as relational tables.  I had a detailed look at Ceilometer with its team members early on, and while I'm pretty confident that if it were desireable, I could get it's relational backend to compete with the Mongo backend performance-wise, there's no reason to get into this if the Mongo licensing issue can be solved alone.&lt;br /&gt;
&lt;br /&gt;
== ORM Quick Wins Proof of Concept ==&lt;br /&gt;
&lt;br /&gt;
I've built up a proof of concept at [https://github.com/zzzeek/nova_poc nova_poc] which seeks to demonstrate&lt;br /&gt;
the range of database and ORM performance that can be achieved within&lt;br /&gt;
one particular method of the Nova API.   Naturally, I picked an example that&lt;br /&gt;
is very juicy in this regard; it has a very easily fixable issue with the&lt;br /&gt;
kind of query it emits that has an enormous 10x impact on its performance&lt;br /&gt;
(which I feel is worth illustrating just for how dramatic it is),&lt;br /&gt;
and it then illustrates some new SQLAlchemy extensions that more or less&lt;br /&gt;
may be going into SQLAlchemy directly, which allow the remaining operation&lt;br /&gt;
to complete in less than half the time.  These extensions can be ported to&lt;br /&gt;
oslo.db to work against all supported SQLAlchemy versions prior to the official&lt;br /&gt;
release of these features.&lt;br /&gt;
&lt;br /&gt;
To summarize, we're looking at the API method &amp;lt;code&amp;gt;floating_ip_update()&amp;lt;/code&amp;gt;, and&lt;br /&gt;
I run a series of profiles against a database table 10K rows (e.g. &amp;lt;code&amp;gt;FloatingIp&amp;lt;/code&amp;gt;&lt;br /&gt;
objects), where each &amp;lt;code&amp;gt;FloatingIp&amp;lt;/code&amp;gt; refers to a single &amp;lt;code&amp;gt;FixedIp&amp;lt;/code&amp;gt; that then&lt;br /&gt;
refers to an &amp;lt;code&amp;gt;Instance&amp;lt;/code&amp;gt;.   The profiling focuses on the nature of this&lt;br /&gt;
method as an API feature all it's own, and simulates the case where the API&lt;br /&gt;
method is called thousands of times, not unlike other API methods that seek&lt;br /&gt;
to add some small amount of data each time they are called.&lt;br /&gt;
&lt;br /&gt;
=== Eager load and Column load tuning ===&lt;br /&gt;
&lt;br /&gt;
Turning off an eagerload in the &amp;lt;code&amp;gt;_floating_ip_get_by_address()&amp;lt;/code&amp;gt; function&lt;br /&gt;
is the most dramatic win; while removing this method seems like a reasonable&lt;br /&gt;
fix to remove some unneeded overhead, in profiling it shows itself to&lt;br /&gt;
be enormous:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Running scenario default&lt;br /&gt;
Scenario default, total calls for 1000 operations: 24543047&lt;br /&gt;
Scenario default, total time for 10000 operations: 222&lt;br /&gt;
&lt;br /&gt;
Running scenario default_optimized&lt;br /&gt;
Scenario default_optimized, total calls for 1000 operations: 1881626&lt;br /&gt;
Scenario default_optimized, total time for 10000 operations: 25&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
While not illustrated in the POC code itself, talk with various OpenStack&lt;br /&gt;
devs has revealed that we could probably do a lot more individual column&lt;br /&gt;
fetching as well, that is instead of:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
session.query(FixedIp).all()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We say:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
session.query(FixedIp.address, FixedIp.host, FixedIp.reserved).all()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
By loading indivdual columns, we get read-only objects that are not identity&lt;br /&gt;
managed, bypassing a huge amount of work that is done on instances both&lt;br /&gt;
in instantiating them, populating relationships and doing bookkeeping.&lt;br /&gt;
Any API feature that only returns a simple read-only object or a &amp;quot;values&amp;quot;&lt;br /&gt;
dictionary that does not rely upon relationship loading should be using&lt;br /&gt;
this pattern, or similar, by using a&lt;br /&gt;
[http://docs.sqlalchemy.org/en/rel_0_9/orm/mapper_config.html#column-bundles Column Bundle].&lt;br /&gt;
I will be exploring adding the following feature to oslo.db:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
session.query(FixedIp.bundle).all()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The above will return a fast-loading, read-only Bundle object that otherwise&lt;br /&gt;
expresses the full interface of &amp;lt;code&amp;gt;FixedIp&amp;lt;/code&amp;gt;.   Relationships might be tricky&lt;br /&gt;
here but I can at least get them on board as regular lazy loads (just like&lt;br /&gt;
any other ORM would do anyway).&lt;br /&gt;
&lt;br /&gt;
=== Fast Object Save ===&lt;br /&gt;
&lt;br /&gt;
We explore replacing the unit of work &amp;lt;code&amp;gt;flush()&amp;lt;/code&amp;gt; call used by &amp;lt;code&amp;gt;object.save()&amp;lt;/code&amp;gt; within oslo.db-style applications with a more direct &amp;quot;flush single object&amp;quot; call; this grants us only a 12% savings so far, though it is very simple to use:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Running scenario default_optimized&lt;br /&gt;
Scenario default_optimized, total calls for 1000 operations: 1881626&lt;br /&gt;
Scenario default_optimized, total time for 10000 operations: 25&lt;br /&gt;
&lt;br /&gt;
Running scenario fast_save&lt;br /&gt;
Scenario fast_save, total calls for 1000 operations: 1685221&lt;br /&gt;
Scenario fast_save, total time for 10000 operations: 22&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Baked Queries ===&lt;br /&gt;
&lt;br /&gt;
Something that has been in the works for a long time and has&lt;br /&gt;
recently    seen lots of work in the past months is the &amp;quot;baked query&amp;quot;&lt;br /&gt;
feature; this    pattern is ideal for OpenStack's &amp;quot;many short queries&amp;quot;&lt;br /&gt;
pattern, and allows    caching of the generation of SQL.  Recent&lt;br /&gt;
versions of this pattern have    gotten very slick, and can cache&lt;br /&gt;
virtually everything that happens Python-wise    from the construction&lt;br /&gt;
of the &amp;lt;code&amp;gt;Query&amp;lt;/code&amp;gt; object, to calling all the methods    on the&lt;br /&gt;
query, to the query-objects construction of a Core SQL statement,&lt;br /&gt;
to the compilation of that statement as a string - all of these steps&lt;br /&gt;
are removed from the call-graph after the first such call.  In&lt;br /&gt;
SQLAlchemy 1.0    I've also thrown in the construction of column&lt;br /&gt;
metadata from the result set    too.   The pattern involves a bit more&lt;br /&gt;
verbosity to that of constructing a    query, where here I've built&lt;br /&gt;
off of some of the ideas of the    Pony ORM to use Python function&lt;br /&gt;
information as the source of a cache key.    A query such as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
result = model_query(&lt;br /&gt;
            context, models.FloatingIp, session=session).\&lt;br /&gt;
            filter_by(address=address)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
would be expressed in &amp;quot;baked&amp;quot; form as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# note model_query is using the &amp;quot;baked&amp;quot; process internally as well&lt;br /&gt;
result = model_query(context, models.FloatingIp, session=session)&lt;br /&gt;
&lt;br /&gt;
result.bake(lambda query:&lt;br /&gt;
    query.filter_by(&lt;br /&gt;
        address=bindparam('address'))).params(address=address)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the above form, everything within each lambda is invoked only once,&lt;br /&gt;
the result of which becomes part of a cached value.&lt;br /&gt;
&lt;br /&gt;
For this slight increase in verbosity, we get an improvement like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Running scenario default_optimized&lt;br /&gt;
Scenario default_optimized, total calls for 1000 operations: 1881626&lt;br /&gt;
Scenario default_optimized, total time for 10000 operations: 25&lt;br /&gt;
&lt;br /&gt;
Running scenario baked&lt;br /&gt;
Scenario baked, total calls for 1000 operations: 1052935&lt;br /&gt;
Scenario baked, total time for 10000 operations: 16&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
That is, around a 40% improvement.&lt;br /&gt;
&lt;br /&gt;
Putting together both &amp;quot;fast save&amp;quot; plus &amp;quot;baked&amp;quot; we get down to a full 50%&lt;br /&gt;
improvement vs. the plain optimized version:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Running scenario fast_save_plus_baked&lt;br /&gt;
Scenario fast_save_plus_baked, total calls for 1000 operations: 856035&lt;br /&gt;
Scenario fast_save_plus_baked, total time for 10000 operations: 13&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
13 seconds for 10K operations vs. 222 seconds in the current code, I hope&lt;br /&gt;
I've established that there is hope!&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Migrations ==&lt;br /&gt;
&lt;br /&gt;
Migrations are a huge deal.  Here's where it looks like this is going.&lt;br /&gt;
&lt;br /&gt;
Alembic - &amp;quot;Alembic is the defacto standard going forward&amp;quot; - ultimately&lt;br /&gt;
we want to get everything SQLAlchemy-Migrate onto Alembic.  And in talking&lt;br /&gt;
with Nova devs, they really like that they can test their migrations against&lt;br /&gt;
SQLite.  So from my POV, SQLite migrations on Alembic, we're doing that!&lt;br /&gt;
Unless folks think otherwise.&lt;br /&gt;
&lt;br /&gt;
=== SQLite Support ===&lt;br /&gt;
&lt;br /&gt;
SQLite has extremely limited support for ALTER TABLE, essentially only being able&lt;br /&gt;
to add new columns, and that's about it.  In order to provide the full suite&lt;br /&gt;
of ALTER operations that all other databases provide, tools such as&lt;br /&gt;
SQLAlchemy-Migrate will create a copy of the target table with changes applied,&lt;br /&gt;
copy data from old table to new, then drop the old table and rename the new one.&lt;br /&gt;
I've always been peeved at SQLite's insistence on never providing some better&lt;br /&gt;
pathway to ALTER built in.  But it has been on the Alembic roadmap to add SQLite migrations&lt;br /&gt;
in a style similar to that of Migrate for a long time, emulating this same&lt;br /&gt;
approach in some way.  Alembic will have this!&lt;br /&gt;
&lt;br /&gt;
However in talking with some folks, it appears like some people might actually&lt;br /&gt;
like this &amp;quot;make a new table and switch it&amp;quot; approach for other databases too,&lt;br /&gt;
as a way to work around locking of tables.  I don't know that I agree with this&lt;br /&gt;
long term, the &amp;quot;ALTER locks the database!&amp;quot; issue in my experience has always&lt;br /&gt;
been kind of a MySQL problem alone, but it can be present for other backends&lt;br /&gt;
as well.&lt;br /&gt;
&lt;br /&gt;
So the API I'm looking towards here is going to try to solve a few problems;&lt;br /&gt;
one is that, copying the whole table to a new one each time for *every single&lt;br /&gt;
column* is crazy, and also that the technique we use on SQLite might be nice&lt;br /&gt;
for other databases too, so here's the API:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
with op.batch_alter(&amp;quot;mytable&amp;quot;, recreate=True) as batch_op:&lt;br /&gt;
    batch_op.drop_column(&amp;quot;some_column&amp;quot;)&lt;br /&gt;
    batch_op.alter_column(&amp;quot;q&amp;quot;, type=Integer)&lt;br /&gt;
    batch_op.add_column(Column('y', String(50), nullable=False))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
That is, we assign a whole series of column operations under the umbrella of&lt;br /&gt;
one table, and they are created at once.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;batch_alter()&amp;lt;/code&amp;gt; call can even be conditional, dropping into traditional&lt;br /&gt;
migration behavior for all backends except SQLite and whatever else:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
with op.batch_alter(&amp;quot;mytable&amp;quot;, recreate_on=('sqlite',)) as batch_op:&lt;br /&gt;
    batch_op.drop_column(&amp;quot;some_column&amp;quot;)&lt;br /&gt;
    batch_op.alter_column(&amp;quot;q&amp;quot;, type=Integer)&lt;br /&gt;
    batch_op.add_column(Column('y', String(50), nullable=False))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
SQlite-compatible alterations is [https://bitbucket.org/zzzeek/alembic/issue/21/column-renames-not-supported-on-sqlite issue #21].&lt;br /&gt;
&lt;br /&gt;
=== Finishing up Autogenerate ===&lt;br /&gt;
&lt;br /&gt;
I started writing Alembic long before SQLAlchemy-Migrate had autogenerate.  But by the time&lt;br /&gt;
I got Alembic released and later autogenerate working, Migrate already had it.&lt;br /&gt;
Here's what we need to finish up in autogenerate:&lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;code&amp;gt;ForeignKeyConstraint&amp;lt;/code&amp;gt; change detection - this is an entirely straightforward feature add and has long been on the todo list.&lt;br /&gt;
&lt;br /&gt;
2. MySQL implicit indexes - this is actually done; recent versions of Alembic can navigate around MySQLs goofy production of indexes automatically on foreign key columns, and not accidentally spit them out in autogenerates. It was seriously tough to get index autogen mostly working on all backends, so new issues will continue to be fixed as they are reported.&lt;br /&gt;
&lt;br /&gt;
3. Type comparison - Alembic balks on type comparison by default, because ultimately SQLAlchemy should add comparision features to its type objects natively. However, on Alembic backends like that of MySQL, we can add explicit rules for the very common and annoying ones, namely the detection of MySQL's TINYINT as a SQLAlchemy Boolean.  Alembic already allows user-specified rules in this regard, so they can be part of oslo.db in any case, however Alembic can also add this rule and others in directly.&lt;br /&gt;
&lt;br /&gt;
4. Table order - Alembic's autogenerate should be spitting out table creates and drops in the order of foreign key dependency, though we don't have good test coverage for this yet and it might not be working.  We'll fix that, no biggie!  Alternatively, Alembic could include a mode of operation that includes all the ForeignKeyConstraint objects after all the tables. Input on how we'd like to see this work would be welcome.&lt;br /&gt;
&lt;br /&gt;
=== New Migration Features ===&lt;br /&gt;
&lt;br /&gt;
There's a lot of features I'd like to add to Alembic, and if OpenStack has&lt;br /&gt;
a need for them, that would justify the effort:&lt;br /&gt;
&lt;br /&gt;
1. Full branch support - [https://bitbucket.org/zzzeek/alembic/issue/167/multiple-heads-branch-resolution-support issue 167 multiple heads / branch resolution support] has a detailed architecture laid out for this feature.  Lots of people are looking for it. This would turn Alembic's current &amp;quot;linked list&amp;quot; version model into a full directed acyclic graph (DAG).   Any particular migration can be dependent on any other group of migrations, or none at all; individual branches can be maintained and upgraded along their path, or automatically merged.&lt;br /&gt;
&lt;br /&gt;
2. [https://bitbucket.org/zzzeek/alembic/issue/124/multiple-versions-directories Multiple version directories] - This would allow migration files to be present in more than one place for a single migrations environment.  Currently, you can get this approach by using multiple base directories, but that requires separate &amp;lt;code&amp;gt;env.py&amp;lt;/code&amp;gt; files for each. With this issue, cross-dependent migration files can live in multiple places, working nicely with the multiple heads support of #167.&lt;br /&gt;
&lt;br /&gt;
== Testing ==&lt;br /&gt;
&lt;br /&gt;
I spent quite a bit looking into testing.  The main thing we can see&lt;br /&gt;
in testing is that there may be a desire for &amp;quot;transactional testing&amp;quot;;&lt;br /&gt;
run tests within a fixed schema, that is the schema    is created&lt;br /&gt;
once, many tests are run, each one rolling back their work, the&lt;br /&gt;
schema is only dropped once all tests for that schema are complete.&lt;br /&gt;
&lt;br /&gt;
Transactional testing is described in the SQLAlchemy docs and in some of&lt;br /&gt;
my talks; for OpenStack, we need the version that supports graceful handling&lt;br /&gt;
of tests that actually do a rollback.  This is doable and the&lt;br /&gt;
recipe for that is currently [https://gist.github.com/zzzeek/8443477 a github gist].&lt;br /&gt;
&lt;br /&gt;
In order to accommodate parallelized runs, the system will create N&lt;br /&gt;
temporary schemas for N concurrent runs.&lt;br /&gt;
&lt;br /&gt;
I really want to get into this feature but as I talk to people, I'm&lt;br /&gt;
getting mixed signals on how important this feature is.   So far I've&lt;br /&gt;
only worked with unit tests, which seem to be entirely against SQLite&lt;br /&gt;
in-memory databases, and I haven't gotten my head around to what&lt;br /&gt;
extent other databases besides SQLite might be desireable in unit&lt;br /&gt;
tests, nor have I gotten to fully understand how integration tests&lt;br /&gt;
run, so input is welcome here.&lt;br /&gt;
&lt;br /&gt;
== Improvement of ORM Patterns ==&lt;br /&gt;
&lt;br /&gt;
Looking around oslo.db and other apps, I want to improve upon&lt;br /&gt;
some kinds of verbosity I see, which make the code more repetetive and inconsistent, welds together application-level and business level concepts (e.g. transaction control and exception reporting melded with data manipulation) and makes it harder to reason about how the application interacts with sessions, as there are a variety of ad-hoc patterns present.&lt;br /&gt;
&lt;br /&gt;
=== Transaction Demarcation ===&lt;br /&gt;
&lt;br /&gt;
For the oft-repeated &amp;quot;get_session(); with session.begin(subtransactions=True)&amp;quot; phrase and other variants, I'd like to move that into something more abstracted:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def my_api_thing(x):&lt;br /&gt;
    with transactional() as session:&lt;br /&gt;
    	# do stuff&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I want context managers and/or decorators that are oslo.db specific to replace any kind of containing code that deals with transactions or sessions. That way the containinment logic can be maintained in once place and configured separately from the business logic contained within.  Projects like Keystone are already using a form more like this; it would be nice to nail down a consistent pattern and get it into oslo.db for everyone to use.&lt;br /&gt;
&lt;br /&gt;
=== Exception Rewriting ===&lt;br /&gt;
&lt;br /&gt;
Exception rewriting - I notice that the oslo.db pattern includes that DBAPI level exceptions are re-thrown as OpenStack specific;  I'd like to move this into handling using SQLAlchemy events.  The [http://docs.sqlalchemy.org/en/rel_0_9/core/events.html#sqlalchemy.events.ConnectionEvents.dbapi_error dbapi_error] event goes all the way back to 0.7, and [https://bitbucket.org/zzzeek/sqlalchemy/issue/3076/support-reraise-of-custom-exceptions ticket 3076] will allow it to be used to reraise with a new exception.  For older SQLAlchemy versions, I can create a wrapper that will allow it to be used in this way safely for older versions, despite the warnings stated on that document.&lt;br /&gt;
&lt;br /&gt;
=== Pessimistic Locking - SELECT FOR UPDATE ===&lt;br /&gt;
&lt;br /&gt;
This issue has been coming up for some projects, that they make sometimes great use of SQLAlchemy's &amp;lt;code&amp;gt;with_lockmode()&amp;lt;/code&amp;gt; method, which ultimately emits SELECT..FOR UPDATE, so that selected rows are aggressively locked within a transaction.   The issue is that backends like Galera don't support FOR UPDATE, meaning the locking that is desired is not actually provided.&lt;br /&gt;
&lt;br /&gt;
While I'm not sure that there's a generalized solution to this, in some cases I've been helping developers move to more of an &amp;quot;optimistic&amp;quot; approach, where a particular UPDATE statement targets some kind of timestamp or value counter in the row, then counts the number of rows matched.  If the number of rows matched is not what's expected, it is assumed that the UPDATE failed to match the row, and the operation can either try again or can bail out.   This approach might not be suitable in all cases.  In cases where a large stream of data is affecting a relatively small set of rows, it can perhaps be feasible to serialize those updates by sending them all to a queue, so that they are invoked in isolation without the need for up-front locking.   For very high throughput situations, queues can be constructed to use multiple, concurrent UPDATE streams separated via hashing, or can batch together updates for particular rows over a period of time and emit fewer UPDATE statements than if each source of data did it individually.&lt;br /&gt;
&lt;br /&gt;
=== Encouraging Bundle / Baked Query Use ===&lt;br /&gt;
&lt;br /&gt;
Encouraging Bundle use - as stated earlier, I want to look into bringing the very efficient [http://docs.sqlalchemy.org/en/rel_0_9/orm/mapper_config.html#column-bundles Column Bundle] pattern into the fold for mainstream use, not to mention the baked query pattern illustrated previously.&lt;br /&gt;
&lt;br /&gt;
=== Transparent Retries of Transactions ===&lt;br /&gt;
&lt;br /&gt;
I have ready to go an [https://bitbucket.org/zzzeek/sqlalchemy/issue/3104/transaction-replay-extension already in production extension for transaction replaying], developed for a client with the agreement that the extension is open source.  This extension installs as an event handler using a new series of hooks that were added in 0.9, or alternatively using a series of compatibility hooks that were backported to 0.7 and 0.8.   This extension allows logging of SQL statements as they proceed within a transaction, and allows them to be transparently replayed when a database disconnect error occurs. This means that SQLAlchemy's usual behavior of raising an exception and forcing you to re-try explicitly can be hidden so that it occurs implicitly, without any interruption or special error handling required in application code.  The extension includes a lot of configuration options, such as what statements should be replayed and which shouldn't, as well as what exceptions should be handled this way.   Transaction replaying is an elaborate feature which might introduce lots of new edge cases, namely that the state of the database may have changed since the last transaction failed, so to that extent I'm a little wary of this feature myself.  However I have seen lots of messages alluding to this kind of thing.    The mechanics to pull this into SQLAlchemy as a transparent feature are ready to go, if this is a feature people are looking for (I'm not sure if people are looking for this).&lt;br /&gt;
&lt;br /&gt;
== Contributors ==&lt;br /&gt;
&lt;br /&gt;
I would be remiss if I didn't mention that SQLAlchemy, Alembic, Dogpile and everything else I do are collaborative projects, and while SQLAlchemy in particular might present a daunting codebase, we would love to get new collaborators on board!   Any of the features above are fair game and I can walk someone through the steps to be taken, and additionally everything we add features for always needs lots of tests and review, if you're looking for a way to get into the project I can bring you right in, there's lots of (often very tedious and boring) work to be done.   I'm on all the IRC channels now as zzzeek so ping away.&lt;br /&gt;
&lt;br /&gt;
== Coffee and Cake ==&lt;br /&gt;
&lt;br /&gt;
So! I hope you've enjoyed reading this document, and whether you're a fan or a hater, please know that I'm on board now and I'm here to help, with whatever database kind of thing you want to do - it's all part of my job now.    If we wanted to use/build a whole new ORM, I'd be doing that too right alongside.  OpenStack's lessons in database integration are now my lessons too.  So let me know where I should dig in first!  Have a save drive home.&lt;/div&gt;</summary>
		<author><name>Mike Bayer</name></author>	</entry>

	<entry>
		<id>https://wiki.openstack.org/w/index.php?title=Openstack_and_SQLAlchemy&amp;diff=57014</id>
		<title>Openstack and SQLAlchemy</title>
		<link rel="alternate" type="text/html" href="https://wiki.openstack.org/w/index.php?title=Openstack_and_SQLAlchemy&amp;diff=57014"/>
				<updated>2014-06-30T17:24:33Z</updated>
		
		<summary type="html">&lt;p&gt;Mike Bayer: Mike Bayer moved page Openstack and SQLAlchemy to OpenStack and SQLAlchemy: spelling for OpenStack&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;#REDIRECT [[OpenStack and SQLAlchemy]]&lt;/div&gt;</summary>
		<author><name>Mike Bayer</name></author>	</entry>

	<entry>
		<id>https://wiki.openstack.org/w/index.php?title=OpenStack_and_SQLAlchemy&amp;diff=57012</id>
		<title>OpenStack and SQLAlchemy</title>
		<link rel="alternate" type="text/html" href="https://wiki.openstack.org/w/index.php?title=OpenStack_and_SQLAlchemy&amp;diff=57012"/>
				<updated>2014-06-30T17:23:58Z</updated>
		
		<summary type="html">&lt;p&gt;Mike Bayer: Openstack -&amp;gt; OpenStack&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= OpenStack and SQLAlchemy =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
This page describes the ongoing efforts of Mike Bayer, creator/maintainer of&lt;br /&gt;
SQLAlchemy, Alembic Migrations, and Dogpile caching, to spearhead what is&lt;br /&gt;
essentially an &amp;quot;OpenStack SQLAlchemy 2.0&amp;quot; effort - as I am now full-time&lt;br /&gt;
employed by an OpenStack vendor (Red Hat), I've been hired to produce a strong&lt;br /&gt;
and sustained pass through OpenStack's relational database integration,&lt;br /&gt;
improving areas that have been problematic and establishing a very strong&lt;br /&gt;
set of practices that are specific to OpenStack's needs.&lt;br /&gt;
&lt;br /&gt;
Within this effort, I'd like to stress that from my POV, everything is&lt;br /&gt;
on the table, including new improvements / fixes / features to&lt;br /&gt;
SQLAlchemy and Alembic directly, migration of projects for which&lt;br /&gt;
relational databases aren't suitable to non-relational systems,&lt;br /&gt;
improvement of relational schemas on the OpenStack side, and of course&lt;br /&gt;
improvement of exising relational patterns within OpenStack&lt;br /&gt;
applications, for which oslo.db will serve as the foundation.&lt;br /&gt;
&lt;br /&gt;
As of June 30, 2014, I've been on the job for about a month, so below&lt;br /&gt;
is an outline of most of what I've come across and my thoughts going forward.&lt;br /&gt;
&lt;br /&gt;
My goal here is to share with the community what I've seen and done so far,&lt;br /&gt;
so that interested parties can chime in with further advice and information,&lt;br /&gt;
and most importantly to help steer me towards the initial blueprints that&lt;br /&gt;
I should be starting on, that is, which features are most likely to be&lt;br /&gt;
accepted by the community as well as which are the top priority.  Not to mention, if you're looking&lt;br /&gt;
for something to do, you can help me implement some of these things!&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== OpenStack presence within SQLAlchemy's CI environment (a.k.a. &amp;quot;The Version that shall not be Named&amp;quot;) ==&lt;br /&gt;
&lt;br /&gt;
On Monday, June 23, I got up in the AM with two goals: get a release of SQLAlchemy&lt;br /&gt;
out the door, as it had been several months since the last point release, and then&lt;br /&gt;
do some work learning how Nova's unit tests run and function.&lt;br /&gt;
&lt;br /&gt;
Release 0.9.5 of SQLAlchemy was not particularly different than any of the other&lt;br /&gt;
hundred or so point releases I've put out over nine years, it was a little more overdue&lt;br /&gt;
than some, and out it went.  Later that day, as I was learning to run Nova's test suite, I&lt;br /&gt;
was hitting some failures within the FixedIp/FloatingIp tests for which I couldn't&lt;br /&gt;
find the cause, assuming that I just wasn't setting up my environment correctly,&lt;br /&gt;
as Nova's tests are passing for all the Jenkins servers the vast majority of the time.&lt;br /&gt;
&lt;br /&gt;
As we learned within about 30 minutes of my hitting these problems, it&lt;br /&gt;
turns out 0.9.5 had a bad fix in it; one that didn't fail any of&lt;br /&gt;
SQLAlchemy's 5000 tests but hit a use case within Nova that was not&lt;br /&gt;
very unusual; not the kind of use I usually do myself but nonetheless&lt;br /&gt;
something that was pretty common.   IRC began  jumping that builds&lt;br /&gt;
were failing everywhere and it was due to the 0.9.5 release!   The&lt;br /&gt;
bizarre coiincidence of this was that none of the thirty-odd point&lt;br /&gt;
releases of SQLAlchemy released under OpenStack's lifetime have ever&lt;br /&gt;
really had this effect, only the major jumps saw any issues.&lt;br /&gt;
&lt;br /&gt;
Luckily, as I had just learned 30 minutes earlier how to run Nova's tests I was&lt;br /&gt;
able to drill down to exactly what was going on and SQLAlchemy 0.9.6 was out&lt;br /&gt;
within an hour, but we shall now forever see in requirements.txt, the Version&lt;br /&gt;
that shall Not Be Named (except in requirements.txt):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
SQLAlchemy&amp;gt;=0.7.8,!=0.9.5,&amp;lt;=0.9.99&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In response to this I have now&lt;br /&gt;
[http://jenkins.sqlalchemy.org/job/openstack_sqla_master/ added openstack builds to our Jenkins CI system],&lt;br /&gt;
so far running tests for oslo.db and the SQLAlchemy tests within Nova; I spent&lt;br /&gt;
a couple of days figuring out how to get everything to build and run against&lt;br /&gt;
specific versions of SQLAlchemy and Alembic, even versions above the 0.9.99 in&lt;br /&gt;
all the requirements files as we are set for 1.0 hopefully within the next year.&lt;br /&gt;
I will be adding in as many other OpenStack projects as I can here so that we will no&lt;br /&gt;
longer have a SQLAlchemy release that hasn't been run against a battery of&lt;br /&gt;
basic OpenStack tests.&lt;br /&gt;
&lt;br /&gt;
== ORM Woes ==&lt;br /&gt;
&lt;br /&gt;
After chatting with a decent amount of folks for a month, I can break up&lt;br /&gt;
the take on OpenStack / SQLAlchemy ORM into three camps:&lt;br /&gt;
&lt;br /&gt;
1. We love SQLAlchemy ORM, we know that we can improve how we use it and we're looking forward to getting there (oslo.db, Keystone)&lt;br /&gt;
&lt;br /&gt;
2. Our app uses a relational database but the SQLAlchemy ORM slows us down, we already have our own &amp;quot;orm-like&amp;quot; system (e.g. APIs that shuttle rows into &amp;quot;values&amp;quot; dictionaries) so we want to ultimately get rid of the ORM part, and probably continue using SQLAlchemy Core to some degree (some Nova developers I've spoken to).&lt;br /&gt;
&lt;br /&gt;
3. Our application has a SQLAlchemy backend that almost nobody uses in production, most users use MongoDB, and the SQLAlchemy backend is only there because MongoDB is AGPL (Ceilometer, Marconi).  We'd like to get a better NoSQL backend without Mongo's licensing issue and do away with relational storage entirely ([http://lists.openstack.org/pipermail/openstack-dev/2014-June/038778.html Marconi]).&lt;br /&gt;
&lt;br /&gt;
Here's my observations / thoughts about these three camps:&lt;br /&gt;
&lt;br /&gt;
1. Great!  Read on, I have some things we can start with.&lt;br /&gt;
&lt;br /&gt;
2. I agree that a lot of the patterns that I've seen in Nova in particular are not well-suited to the approach used by SQLAlchemy's ORM, however I have observed that the applications still rely a lot on very sophisticated and mature features provided by SQLAlchemy (namely &amp;lt;code&amp;gt;relationship()&amp;lt;/code&amp;gt; and it's integration with eager loading and the unit of work features), and throwing it away entirely will incur a lot of reinvention, not just of the features that were lost but also of a totally new crop of stability and performance issues to solve.&lt;br /&gt;
&lt;br /&gt;
To that degree, I'd like to propose a twist to the &amp;quot;we want to write our own ORM&amp;quot; idea and say, &amp;quot;let's write our own ORM that's based on the SQLAlchemy ORM primitives&amp;quot;, e.g. a hybrid approach where existing ORM logic that is fine can stay that way, ORM logic that has performance or behavioral issues can be reorganized using systems, some of them new, some of them just underutilized, that bypass unneeded SQLAlchemy features on a case-by-case basis.&lt;br /&gt;
&lt;br /&gt;
As a secondary issue, some devs I've spoken to have referred to the nature&lt;br /&gt;
of OpenStack API-based applications being a part of the problem.&lt;br /&gt;
This refers to the fact that OpenStack apps like Nova&lt;br /&gt;
and Neutron expose fine-grained APIs which end up&lt;br /&gt;
breaking out composed use cases into dozens or hundreds of small API&lt;br /&gt;
operations, each of which invokes within it's own database transaction and&lt;br /&gt;
ORM session state.  This makes it impossible, without introducing some elaborate&lt;br /&gt;
offline state management system, for a larger operation to be&lt;br /&gt;
performed so that larger groups of objects can be loaded and manipulated&lt;br /&gt;
at once, greatly decreasing the number of database round trips present.&lt;br /&gt;
No matter what ORM you use or don't use, the pattern of many small round trips&lt;br /&gt;
is difficult to scale on relational databases.&lt;br /&gt;
&lt;br /&gt;
There are under way projects which aim to apply detailed profiling&lt;br /&gt;
to OpenStack applications so that a breakdown of performance issues due&lt;br /&gt;
to API use and/or database&lt;br /&gt;
access might be plainly reasoned about; see [https://github.com/stackforge/rally Rally]&lt;br /&gt;
and [https://github.com/stackforge/osprofiler OS Profiler] for examples.&lt;br /&gt;
&lt;br /&gt;
On my end, I have no intention of focusing on this second issue for the time&lt;br /&gt;
being, I'm going to stick with leaving OS applications as much as they are right now as possible,&lt;br /&gt;
applying iterative changes to database access code which I hope can optimize&lt;br /&gt;
to a significant degree within the current usage patterns.&lt;br /&gt;
&lt;br /&gt;
3. Projects with alternate MongoDB / MySQL backends (since after all, MySQL is the database here being rejected, SQLAlchemy is just a Python API) should probably do what they're doing and go to an all key/value model.  It is not at all fun to maintain the same datamodel and API against both a KV store as well as relational tables.  I had a detailed look at Ceilometer with its team members early on, and while I'm pretty confident that if it were desireable, I could get it's relational backend to compete with the Mongo backend performance-wise, there's no reason to get into this if the Mongo licensing issue can be solved alone.&lt;br /&gt;
&lt;br /&gt;
== ORM Quick Wins Proof of Concept ==&lt;br /&gt;
&lt;br /&gt;
I've built up a proof of concept at [https://github.com/zzzeek/nova_poc nova_poc] which seeks to demonstrate&lt;br /&gt;
the range of database and ORM performance that can be achieved within&lt;br /&gt;
one particular method of the Nova API.   Naturally, I picked an example that&lt;br /&gt;
is very juicy in this regard; it has a very easily fixable issue with the&lt;br /&gt;
kind of query it emits that has an enormous 10x impact on its performance&lt;br /&gt;
(which I feel is worth illustrating just for how dramatic it is),&lt;br /&gt;
and it then illustrates some new SQLAlchemy extensions that more or less&lt;br /&gt;
may be going into SQLAlchemy directly, which allow the remaining operation&lt;br /&gt;
to complete in less than half the time.  These extensions can be ported to&lt;br /&gt;
oslo.db to work against all supported SQLAlchemy versions prior to the official&lt;br /&gt;
release of these features.&lt;br /&gt;
&lt;br /&gt;
To summarize, we're looking at the API method &amp;lt;code&amp;gt;floating_ip_update()&amp;lt;/code&amp;gt;, and&lt;br /&gt;
I run a series of profiles against a database table 10K rows (e.g. &amp;lt;code&amp;gt;FloatingIp&amp;lt;/code&amp;gt;&lt;br /&gt;
objects), where each &amp;lt;code&amp;gt;FloatingIp&amp;lt;/code&amp;gt; refers to a single &amp;lt;code&amp;gt;FixedIp&amp;lt;/code&amp;gt; that then&lt;br /&gt;
refers to an &amp;lt;code&amp;gt;Instance&amp;lt;/code&amp;gt;.   The profiling focuses on the nature of this&lt;br /&gt;
method as an API feature all it's own, and simulates the case where the API&lt;br /&gt;
method is called thousands of times, not unlike other API methods that seek&lt;br /&gt;
to add some small amount of data each time they are called.&lt;br /&gt;
&lt;br /&gt;
=== Eager load and Column load tuning ===&lt;br /&gt;
&lt;br /&gt;
Turning off an eagerload in the &amp;lt;code&amp;gt;_floating_ip_get_by_address()&amp;lt;/code&amp;gt; function&lt;br /&gt;
is the most dramatic win; while removing this method seems like a reasonable&lt;br /&gt;
fix to remove some unneeded overhead, in profiling it shows itself to&lt;br /&gt;
be enormous:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Running scenario default&lt;br /&gt;
Scenario default, total calls for 1000 operations: 24543047&lt;br /&gt;
Scenario default, total time for 10000 operations: 222&lt;br /&gt;
&lt;br /&gt;
Running scenario default_optimized&lt;br /&gt;
Scenario default_optimized, total calls for 1000 operations: 1881626&lt;br /&gt;
Scenario default_optimized, total time for 10000 operations: 25&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
While not illustrated in the POC code itself, talk with various OpenStack&lt;br /&gt;
devs has revealed that we could probably do a lot more individual column&lt;br /&gt;
fetching as well, that is instead of:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
session.query(FixedIp).all()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We say:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
session.query(FixedIp.address, FixedIp.host, FixedIp.reserved).all()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
By loading indivdual columns, we get read-only objects that are not identity&lt;br /&gt;
managed, bypassing a huge amount of work that is done on instances both&lt;br /&gt;
in instantiating them, populating relationships and doing bookkeeping.&lt;br /&gt;
Any API feature that only returns a simple read-only object or a &amp;quot;values&amp;quot;&lt;br /&gt;
dictionary that does not rely upon relationship loading should be using&lt;br /&gt;
this pattern, or similar, by using a&lt;br /&gt;
[http://docs.sqlalchemy.org/en/rel_0_9/orm/mapper_config.html#column-bundles Column Bundle].&lt;br /&gt;
I will be exploring adding the following feature to oslo.db:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
session.query(FixedIp.bundle).all()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The above will return a fast-loading, read-only Bundle object that otherwise&lt;br /&gt;
expresses the full interface of &amp;lt;code&amp;gt;FixedIp&amp;lt;/code&amp;gt;.   Relationships might be tricky&lt;br /&gt;
here but I can at least get them on board as regular lazy loads (just like&lt;br /&gt;
any other ORM would do anyway).&lt;br /&gt;
&lt;br /&gt;
=== Fast Object Save ===&lt;br /&gt;
&lt;br /&gt;
We explore replacing the unit of work &amp;lt;code&amp;gt;flush()&amp;lt;/code&amp;gt; call used by &amp;lt;code&amp;gt;object.save()&amp;lt;/code&amp;gt; within oslo.db-style applications with a more direct &amp;quot;flush single object&amp;quot; call; this grants us only a 12% savings so far, though it is very simple to use:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Running scenario default_optimized&lt;br /&gt;
Scenario default_optimized, total calls for 1000 operations: 1881626&lt;br /&gt;
Scenario default_optimized, total time for 10000 operations: 25&lt;br /&gt;
&lt;br /&gt;
Running scenario fast_save&lt;br /&gt;
Scenario fast_save, total calls for 1000 operations: 1685221&lt;br /&gt;
Scenario fast_save, total time for 10000 operations: 22&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Baked Queries ===&lt;br /&gt;
&lt;br /&gt;
Something that has been in the works for a long time and has&lt;br /&gt;
recently    seen lots of work in the past months is the &amp;quot;baked query&amp;quot;&lt;br /&gt;
feature; this    pattern is ideal for OpenStack's &amp;quot;many short queries&amp;quot;&lt;br /&gt;
pattern, and allows    caching of the generation of SQL.  Recent&lt;br /&gt;
versions of this pattern have    gotten very slick, and can cache&lt;br /&gt;
virtually everything that happens Python-wise    from the construction&lt;br /&gt;
of the &amp;lt;code&amp;gt;Query&amp;lt;/code&amp;gt; object, to calling all the methods    on the&lt;br /&gt;
query, to the query-objects construction of a Core SQL statement,&lt;br /&gt;
to the compilation of that statement as a string - all of these steps&lt;br /&gt;
are removed from the call-graph after the first such call.  In&lt;br /&gt;
SQLAlchemy 1.0    I've also thrown in the construction of column&lt;br /&gt;
metadata from the result set    too.   The pattern involves a bit more&lt;br /&gt;
verbosity to that of constructing a    query, where here I've built&lt;br /&gt;
off of some of the ideas of the    Pony ORM to use Python function&lt;br /&gt;
information as the source of a cache key.    A query such as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
result = model_query(&lt;br /&gt;
            context, models.FloatingIp, session=session).\&lt;br /&gt;
            filter_by(address=address)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
would be expressed in &amp;quot;baked&amp;quot; form as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# note model_query is using the &amp;quot;baked&amp;quot; process internally as well&lt;br /&gt;
result = model_query(context, models.FloatingIp, session=session)&lt;br /&gt;
&lt;br /&gt;
result.bake(lambda query:&lt;br /&gt;
    query.filter_by(&lt;br /&gt;
        address=bindparam('address'))).params(address=address)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the above form, everything within each lambda is invoked only once,&lt;br /&gt;
the result of which becomes part of a cached value.&lt;br /&gt;
&lt;br /&gt;
For this slight increase in verbosity, we get an improvement like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Running scenario default_optimized&lt;br /&gt;
Scenario default_optimized, total calls for 1000 operations: 1881626&lt;br /&gt;
Scenario default_optimized, total time for 10000 operations: 25&lt;br /&gt;
&lt;br /&gt;
Running scenario baked&lt;br /&gt;
Scenario baked, total calls for 1000 operations: 1052935&lt;br /&gt;
Scenario baked, total time for 10000 operations: 16&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
That is, around a 40% improvement.&lt;br /&gt;
&lt;br /&gt;
Putting together both &amp;quot;fast save&amp;quot; plus &amp;quot;baked&amp;quot; we get down to a full 50%&lt;br /&gt;
improvement vs. the plain optimized version:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Running scenario fast_save_plus_baked&lt;br /&gt;
Scenario fast_save_plus_baked, total calls for 1000 operations: 856035&lt;br /&gt;
Scenario fast_save_plus_baked, total time for 10000 operations: 13&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
13 seconds for 10K operations vs. 222 seconds in the current code, I hope&lt;br /&gt;
I've established that there is hope!&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Migrations ==&lt;br /&gt;
&lt;br /&gt;
Migrations are a huge deal.  Here's where it looks like this is going.&lt;br /&gt;
&lt;br /&gt;
Alembic - &amp;quot;Alembic is the defacto standard going forward&amp;quot; - ultimately&lt;br /&gt;
we want to get everything SQLAlchemy-Migrate onto Alembic.  And in talking&lt;br /&gt;
with Nova devs, they really like that they can test their migrations against&lt;br /&gt;
SQLite.  So from my POV, SQLite migrations on Alembic, we're doing that!&lt;br /&gt;
Unless folks think otherwise.&lt;br /&gt;
&lt;br /&gt;
=== SQLite Support ===&lt;br /&gt;
&lt;br /&gt;
SQLite has extremely limited support for ALTER TABLE, essentially only being able&lt;br /&gt;
to add new columns, and that's about it.  In order to provide the full suite&lt;br /&gt;
of ALTER operations that all other databases provide, tools such as&lt;br /&gt;
SQLAlchemy-Migrate will create a copy of the target table with changes applied,&lt;br /&gt;
copy data from old table to new, then drop the old table and rename the new one.&lt;br /&gt;
I've always been peeved at SQLite's insistence on never providing some better&lt;br /&gt;
pathway to ALTER built in.  But it has been on the Alembic roadmap to add SQLite migrations&lt;br /&gt;
in a style similar to that of Migrate for a long time, emulating this same&lt;br /&gt;
approach in some way.  Alembic will have this!&lt;br /&gt;
&lt;br /&gt;
However in talking with some folks, it appears like some people might actually&lt;br /&gt;
like this &amp;quot;make a new table and switch it&amp;quot; approach for other databases too,&lt;br /&gt;
as a way to work around locking of tables.  I don't know that I agree with this&lt;br /&gt;
long term, the &amp;quot;ALTER locks the database!&amp;quot; issue in my experience has always&lt;br /&gt;
been kind of a MySQL problem alone, but it can be present for other backends&lt;br /&gt;
as well.&lt;br /&gt;
&lt;br /&gt;
So the API I'm looking towards here is going to try to solve a few problems;&lt;br /&gt;
one is that, copying the whole table to a new one each time for *every single&lt;br /&gt;
column* is crazy, and also that the technique we use on SQLite might be nice&lt;br /&gt;
for other databases too, so here's the API:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
with op.batch_alter(&amp;quot;mytable&amp;quot;, recreate=True) as batch_op:&lt;br /&gt;
    batch_op.drop_column(&amp;quot;some_column&amp;quot;)&lt;br /&gt;
    batch_op.alter_column(&amp;quot;q&amp;quot;, type=Integer)&lt;br /&gt;
    batch_op.add_column(Column('y', String(50), nullable=False))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
That is, we assign a whole series of column operations under the umbrella of&lt;br /&gt;
one table, and they are created at once.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;batch_alter()&amp;lt;/code&amp;gt; call can even be conditional, dropping into traditional&lt;br /&gt;
migration behavior for all backends except SQLite and whatever else:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
with op.batch_alter(&amp;quot;mytable&amp;quot;, recreate_on=('sqlite',)) as batch_op:&lt;br /&gt;
    batch_op.drop_column(&amp;quot;some_column&amp;quot;)&lt;br /&gt;
    batch_op.alter_column(&amp;quot;q&amp;quot;, type=Integer)&lt;br /&gt;
    batch_op.add_column(Column('y', String(50), nullable=False))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
SQlite-compatible alterations is [https://bitbucket.org/zzzeek/alembic/issue/21/column-renames-not-supported-on-sqlite issue #21].&lt;br /&gt;
&lt;br /&gt;
=== Finishing up Autogenerate ===&lt;br /&gt;
&lt;br /&gt;
I started writing Alembic long before SQLAlchemy-Migrate had autogenerate.  But by the time&lt;br /&gt;
I got Alembic released and later autogenerate working, Migrate already had it.&lt;br /&gt;
Here's what we need to finish up in autogenerate:&lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;code&amp;gt;ForeignKeyConstraint&amp;lt;/code&amp;gt; change detection - this is an entirely straightforward feature add and has long been on the todo list.&lt;br /&gt;
&lt;br /&gt;
2. MySQL implicit indexes - this is actually done; recent versions of Alembic can navigate around MySQLs goofy production of indexes automatically on foreign key columns, and not accidentally spit them out in autogenerates. It was seriously tough to get index autogen mostly working on all backends, so new issues will continue to be fixed as they are reported.&lt;br /&gt;
&lt;br /&gt;
3. Type comparison - Alembic balks on type comparison by default, because ultimately SQLAlchemy should add comparision features to its type objects natively. However, on Alembic backends like that of MySQL, we can add explicit rules for the very common and annoying ones, namely the detection of MySQL's TINYINT as a SQLAlchemy Boolean.  Alembic already allows user-specified rules in this regard, so they can be part of oslo.db in any case, however Alembic can also add this rule and others in directly.&lt;br /&gt;
&lt;br /&gt;
4. Table order - Alembic's autogenerate should be spitting out table creates and drops in the order of foreign key dependency, though we don't have good test coverage for this yet and it might not be working.  We'll fix that, no biggie!  Alternatively, Alembic could include a mode of operation that includes all the ForeignKeyConstraint objects after all the tables. Input on how we'd like to see this work would be welcome.&lt;br /&gt;
&lt;br /&gt;
=== New Migration Features ===&lt;br /&gt;
&lt;br /&gt;
There's a lot of features I'd like to add to Alembic, and if OpenStack has&lt;br /&gt;
a need for them, that would justify the effort:&lt;br /&gt;
&lt;br /&gt;
1. Full branch support - [https://bitbucket.org/zzzeek/alembic/issue/167/multiple-heads-branch-resolution-support issue 167 multiple heads / branch resolution support] has a detailed architecture laid out for this feature.  Lots of people are looking for it. This would turn Alembic's current &amp;quot;linked list&amp;quot; version model into a full directed acyclic graph (DAG).   Any particular migration can be dependent on any other group of migrations, or none at all; individual branches can be maintained and upgraded along their path, or automatically merged.&lt;br /&gt;
&lt;br /&gt;
2. [https://bitbucket.org/zzzeek/alembic/issue/124/multiple-versions-directories Multiple version directories] - This would allow migration files to be present in more than one place for a single migrations environment.  Currently, you can get this approach by using multiple base directories, but that requires separate &amp;lt;code&amp;gt;env.py&amp;lt;/code&amp;gt; files for each. With this issue, cross-dependent migration files can live in multiple places, working nicely with the multiple heads support of #167.&lt;br /&gt;
&lt;br /&gt;
== Testing ==&lt;br /&gt;
&lt;br /&gt;
I spent quite a bit looking into testing.  The main thing we can see&lt;br /&gt;
in testing is that there may be a desire for &amp;quot;transactional testing&amp;quot;;&lt;br /&gt;
run tests within a fixed schema, that is the schema    is created&lt;br /&gt;
once, many tests are run, each one rolling back their work, the&lt;br /&gt;
schema is only dropped once all tests for that schema are complete.&lt;br /&gt;
&lt;br /&gt;
Transactional testing is described in the SQLAlchemy docs and in some of&lt;br /&gt;
my talks; for OpenStack, we need the version that supports graceful handling&lt;br /&gt;
of tests that actually do a rollback.  This is doable and the&lt;br /&gt;
recipe for that is currently [https://gist.github.com/zzzeek/8443477 a github gist].&lt;br /&gt;
&lt;br /&gt;
In order to accommodate parallelized runs, the system will create N&lt;br /&gt;
temporary schemas for N concurrent runs.&lt;br /&gt;
&lt;br /&gt;
I really want to get into this feature but as I talk to people, I'm&lt;br /&gt;
getting mixed signals on how important this feature is.   So far I've&lt;br /&gt;
only worked with unit tests, which seem to be entirely against SQLite&lt;br /&gt;
in-memory databases, and I haven't gotten my head around to what&lt;br /&gt;
extent other databases besides SQLite might be desireable in unit&lt;br /&gt;
tests, nor have I gotten to fully understand how integration tests&lt;br /&gt;
run, so input is welcome here.&lt;br /&gt;
&lt;br /&gt;
== Improvement of ORM Patterns ==&lt;br /&gt;
&lt;br /&gt;
Looking around oslo.db and other apps, I want to improve upon&lt;br /&gt;
some kinds of verbosity I see, which make the code more repetetive and inconsistent, welds together application-level and business level concepts (e.g. transaction control and exception reporting melded with data manipulation) and makes it harder to reason about how the application interacts with sessions, as there are a variety of ad-hoc patterns present.&lt;br /&gt;
&lt;br /&gt;
=== Transaction Demarcation ===&lt;br /&gt;
&lt;br /&gt;
For the oft-repeated &amp;quot;get_session(); with session.begin(subtransactions=True)&amp;quot; phrase and other variants, I'd like to move that into something more abstracted:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def my_api_thing(x):&lt;br /&gt;
    with transactional() as session:&lt;br /&gt;
    	# do stuff&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I want context managers and/or decorators that are oslo.db specific to replace any kind of containing code that deals with transactions or sessions. That way the containinment logic can be maintained in once place and configured separately from the business logic contained within.  Projects like Keystone are already using a form more like this; it would be nice to nail down a consistent pattern and get it into oslo.db for everyone to use.&lt;br /&gt;
&lt;br /&gt;
=== Exception Rewriting ===&lt;br /&gt;
&lt;br /&gt;
Exception rewriting - I notice that the oslo.db pattern includes that DBAPI level exceptions are re-thrown as OpenStack specific;  I'd like to move this into handling using SQLAlchemy events.  The [http://docs.sqlalchemy.org/en/rel_0_9/core/events.html#sqlalchemy.events.ConnectionEvents.dbapi_error dbapi_error] event goes all the way back to 0.7, and [https://bitbucket.org/zzzeek/sqlalchemy/issue/3076/support-reraise-of-custom-exceptions ticket 3076] will allow it to be used to reraise with a new exception.  For older SQLAlchemy versions, I can create a wrapper that will allow it to be used in this way safely for older versions, despite the warnings stated on that document.&lt;br /&gt;
&lt;br /&gt;
=== Pessimistic Locking - SELECT FOR UPDATE ===&lt;br /&gt;
&lt;br /&gt;
This issue has been coming up for some projects, that they make sometimes great use of SQLAlchemy's &amp;lt;code&amp;gt;with_lockmode()&amp;lt;/code&amp;gt; method, which ultimately emits SELECT..FOR UPDATE, so that selected rows are aggressively locked within a transaction.   The issue is that backends like Galera don't support FOR UPDATE, meaning the locking that is desired is not actually provided.&lt;br /&gt;
&lt;br /&gt;
While I'm not sure that there's a generalized solution to this, in some cases I've been helping developers move to more of an &amp;quot;optimistic&amp;quot; approach, where a particular UPDATE statement targets some kind of timestamp or value counter in the row, then counts the number of rows matched.  If the number of rows matched is not what's expected, it is assumed that the UPDATE failed to match the row, and the operation can either try again or can bail out.   This approach might not be suitable in all cases.  In cases where a large stream of data is affecting a relatively small set of rows, it can perhaps be feasible to serialize those updates by sending them all to a queue, so that they are invoked in isolation without the need for up-front locking.   For very high throughput situations, queues can be constructed to use multiple, concurrent UPDATE streams separated via hashing, or can batch together updates for particular rows over a period of time and emit fewer UPDATE statements than if each source of data did it individually.&lt;br /&gt;
&lt;br /&gt;
=== Encouraging Bundle / Baked Query Use ===&lt;br /&gt;
&lt;br /&gt;
Encouraging Bundle use - as stated earlier, I want to look into bringing the very efficient [http://docs.sqlalchemy.org/en/rel_0_9/orm/mapper_config.html#column-bundles Column Bundle] pattern into the fold for mainstream use, not to mention the baked query pattern illustrated previously.&lt;br /&gt;
&lt;br /&gt;
=== Transparent Retries of Transactions ===&lt;br /&gt;
&lt;br /&gt;
I have ready to go an [https://bitbucket.org/zzzeek/sqlalchemy/issue/3104/transaction-replay-extension already in production extension for transaction replaying], developed for a client with the agreement that the extension is open source.  This extension installs as an event handler using a new series of hooks that were added in 0.9, or alternatively using a series of compatibility hooks that were backported to 0.7 and 0.8.   This extension allows logging of SQL statements as they proceed within a transaction, and allows them to be transparently replayed when a database disconnect error occurs. This means that SQLAlchemy's usual behavior of raising an exception and forcing you to re-try explicitly can be hidden so that it occurs implicitly, without any interruption or special error handling required in application code.  The extension includes a lot of configuration options, such as what statements should be replayed and which shouldn't, as well as what exceptions should be handled this way.   Transaction replaying is an elaborate feature which might introduce lots of new edge cases, namely that the state of the database may have changed since the last transaction failed, so to that extent I'm a little wary of this feature myself.  However I have seen lots of messages alluding to this kind of thing.    The mechanics to pull this into SQLAlchemy as a transparent feature are ready to go, if this is a feature people are looking for (I'm not sure if people are looking for this).&lt;br /&gt;
&lt;br /&gt;
== Contributors ==&lt;br /&gt;
&lt;br /&gt;
I would be remiss if I didn't mention that SQLAlchemy, Alembic, Dogpile and everything else I do are collaborative projects, and while SQLAlchemy in particular might present a daunting codebase, we would love to get new collaborators on board!   Any of the features above are fair game and I can walk someone through the steps to be taken, and additionally everything we add features for always needs lots of tests and review, if you're looking for a way to get into the project I can bring you right in, there's lots of (often very tedious and boring) work to be done.   I'm on all the IRC channels now as zzzeek so ping away.&lt;br /&gt;
&lt;br /&gt;
== Coffee and Cake ==&lt;br /&gt;
&lt;br /&gt;
So! I hope you've enjoyed reading this document, and whether you're a fan or a hater, please know that I'm on board now and I'm here to help, with whatever database kind of thing you want to do - it's all part of my job now.    If we wanted to use/build a whole new ORM, I'd be doing that too right alongside.  OpenStack's lessons in database integration are now my lessons too.  So let me know where I should dig in first!  Have a save drive home.&lt;/div&gt;</summary>
		<author><name>Mike Bayer</name></author>	</entry>

	</feed>