Tutorials for writing database modules in Python

Source: Internet
Author: User
In a web app, all data, including user information, published logs, comments, and so on, are stored in the database. In Awesome-python-app, we chose MySQL as the database.

There are many places in the Web app to access the database. Accessing the database requires creating a database connection, a cursor object, then executing the SQL statement, finally handling the exception, and cleaning up the resource. The code that accesses the database, if scattered among the functions, is bound to be impossible to maintain and not conducive to code reuse.

In addition, in a Web app, multiple users are simultaneously accessed, and the system processes each user's request in a multi-process or multithreaded mode. Assuming that in the case of multithreading, each thread must create its own connection when accessing the database, and it will not be visible to other threads, otherwise it would cause confusion in database operations.

So, we're going to create a simple and reliable database access model that allows you to operate the database safely and simply in one thread.

Why not choose SQLAlchemy? The SQLAlchemy is too large, and the overly object-oriented design causes the API to be too complex.

So we decided to design a DB module that encapsulates the basic select, INSERT, update, and delete operations: Transwarp.db.
Designing the DB interface

The principle of designing the underlying module is to design an easy-to-use API interface based on the upper-level caller and then implement the module's internal code.

Assuming that the Transwarp.db module has been written, we want to invoke it in this way:

First, initialize the database connection information through the Create_engine () function:

From Transwarp import dbdb.create_engine (user= ' root ', password= ' password ', database= ' test ', host= ' 127.0.0.1 ', port= 3306)

Then, you can manipulate the SQL directly.

If you need to make a query, you can call the Select () method directly, return the list, and each element is the corresponding line represented by Dict:

Users = Db.select (' select * from user ') # users =># [#   {"id": 1, "name": "Michael"},#   {"id": 2, "name": "Bob"} , #   {"id": 3, "name": "Adam"}#]

If you are performing an INSERT, update, or delete operation, execute the update () method, returning the number of rows affected:

n = db.update (' INSERT into user (ID, name) VALUES (?,?) ', 4, ' Jack ')

The update () function signature is:

Update (SQL, *args)

Unified use? As a placeholder and pass in a mutable parameter to bind, fundamentally avoiding SQL injection attacks.

Each select () or update () call implicitly automatically opens and closes the database connection so that the upper-level caller does not have to care about the database underlying connection at all.

But what if you want to execute multiple SQL statements in a database connection? We implement with a with statement:

With Db.connection (): Db.select (' ... ') db.update (' ... ')  db.update (' ... ')

What if you want to execute multiple SQL statements in a database transaction? We also implement with a with statement:

With Db.transaction (): Db.select (' ... ') db.update (' ... ')  db.update (' ... ')

Implementing the DB Module

Because the module is a global object, the module variable is a globally unique variable, so there are two important module variables:

# db.py# Database Engine object: Class _engine (object):  def __init__ (self, connect):    self._connect = connect  def connect ( Self):    return self._connect () engine = none# holds the context object for the database connection: Class _dbctx (threading.local):  def __init__ (self):    self.connection = None    self.transactions = 0  def is_init (self):    return isn't self.connection is None  def init (self):    self.connection = _lasyconnection ()    self.transactions = 0  def cleanup (self):    self.connection.cleanup ()    self.connection = None  def cursor (self):    return Self.connection.cursor () _db_ctx = _dbctx ()

Because _db_ctx is a Threadlocal object, it holds a database connection that is different for each thread that it sees. No one thread can access a database connection that is held by another thread.

With these two global variables, we continue to implement the context of the database connection in order to obtain and release the connection automatically:

Class _connectionctx (object):  def __enter__ (self):    global _db_ctx    self.should_cleanup = False    if not _db_ctx.is_init ():      _db_ctx.init ()      self.should_cleanup = True    return self  -def __exit__ (self, exctype , Excvalue, Traceback):    global _db_ctx    if Self.should_cleanup:      _db_ctx.cleanup () def connection ():  return _connectionctx ()

An object that defines __enter__ () and __exit__ () can be used with the WITH statement to ensure that the __exit__ () method can be called in any case.

The scope of the _CONNECTIONCTX action to a function call, you can write:

With connection ():  do_some_db_operation ()
But a simpler notation is to write a @decorator:

@with_connectiondef do_some_db_operation ():  Pass


This makes it easier for us to implement the Select (), Update () method:

@with_connectiondef Select (SQL, *args):  pass@with_connectiondef Update (SQL, *args):  Pass

Note that the connection object is stored in the _dbctx threadlocal object, so nesting with connection () is not a problem. _dbctx always detects whether a connection is currently present, if it exists, is used directly, and if it does not, a new connection is opened.

Similar to transaction, with transaction () defines a database transaction:

With Db.transaction (): Db.select (' ... ') db.update (' ... ')  db.update (' ... ')

Function-scoped transactions also have a simplified @decorator:

@with_transactiondef do_in_transaction ():  Pass

Transactions can also be nested, and the inner transaction is automatically merged into the outer transaction, a transaction model sufficient to meet the 99% requirement.

Transaction nesting is a bit more complicated than connection nesting, because transaction nesting needs to be counted, each level of nesting is 1, leaving a layer nested on-1, and the last to 0 o'clock commits the transaction:

Class _transactionctx (object):  def __enter__ (self):    global _db_ctx    self.should_close_conn = False    If not _db_ctx.is_init ():      _db_ctx.init ()      self.should_close_conn = True    _db_ctx.transactions = _db_ Ctx.transactions + 1    return self  -def __exit__ (self, exctype, Excvalue, Traceback):    global _db_ctx    _db _ctx.transactions = _db_ctx.transactions-1    try:      if _db_ctx.transactions==0:        if Exctype is None:          Self.commit ()        Else:          self.rollback ()    finally:      if self.should_close_conn:        _db_ Ctx.cleanup ()  def commit (self):    global _db_ctx    try:      _db_ctx.connection.commit ()    except:      _db_ctx.connection.rollback ()      raise  def rollback (self):    global _db_ctx    _db_ Ctx.connection.rollback ()

Finally, the Select () and update () methods are implemented, and the DB module is completed.

  • Contact Us

    The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

    If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

    A Free Trial That Lets You Build Big!

    Start building with 50+ products and up to 12 months usage for Elastic Compute Service

    • Sales Support

      1 on 1 presale consultation

    • After-Sales Support

      24/7 Technical Support 6 Free Tickets per Quarter Faster Response

    • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.