:orphan: Database Programming with PostgreSQL ==================================== This chapter covers `PostgreSQL `__ database programming from WAMP application components written in Python. You will need some Python packages to do so. Depending on the Python implementation and the underlying network framework you plan to use, this is what we recommend: +---------------------------------------------+---------------------------------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------------------------------+ | Python / Networking Framework | `Twisted `__ | `asyncio `__ | +=============================================+===========================================================================================================================+=================================================================================================================+ | `CPython `__ | `psycopg2 `__ + `txpostgres `__ | `psycopg2 `__ + `aiopg `__ | +---------------------------------------------+---------------------------------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------------------------------+ | `PyPy `__ | `psycopg2cffi `__ + `txpostgres `__ | `psycopg2cffi `__ + `aiopg `__ | +---------------------------------------------+---------------------------------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------------------------------+ Above libraries provide a classic cursor and SQL based API to the database (like Python DBI 2.0). If you are looking for an object-relational database adapter, there obviously is SQLAlchemy. However, the latter is exposing a synchronous API and does not blend well with the asynchronous frameworks. However, there is `Twistar `__, a completely new project for Twisted which can be used with any Twisted supported relational database and provides a object-relational API. Drivers ------- To access PostgreSQL from Python, you will need a **database driver**. There are multiple drivers (e.g. see `here `__ and `here `__), however, the most commonly used is `Psycopg `__. **Psycopg** can be used to access PostgreSQL from WAMP application components written in Python, and running under Twisted or asyncio. **Psycopg** wraps the native PostgreSQL C client library `libpq `__ as a Python module. It is written as a so-called CPython extension. This Python extension system has issues when running on PyPy, which is the reason for a variant `Psycopg2cffi `__. You probably should use this on PyPy. It is using `CFFI `__, which is the recommended way to access C libraries from PyPy. Asyncio ------- Psycopg can be used under asyncio via `aiopg `__, a wrapper which builds on Psycopg, exposing an asynchronous interface to the database. For an introduction, please see the `aiopg documentation `__. aiopg will run the underlying PostgreSQL database connection in "asynchronous connection mode" and hence does NOT run a background thread pool (since it doesn't need to overcome the blocking nature of PostgreSQL connection which do not run in asynchronous mode). It is comparable to **txpostgres** - which is for Twisted. Twisted ------- This chapter describes accessing PostgreSQL databases from Python/Twisted-based WAMP application components. Approaches ~~~~~~~~~~ Assuming you have settled on `psycopg2 `__ (or `Psycopg2cffi `__ when running on PyPy) as your underlying database driver, there are two options with Twisted: 1. `twisted.enterprise.adbapi `__ 2. `txpostgres `__ The first one is running (blocking) database connections on a background thread pool and exposes an asynchronous API to applications. It is very stable, has been around forever and comes built into Twisted. However, it is using threads. The second one is running PostgreSQL database connections in "asynchronous mode". Note that the word "asynchronous" in this case refers to a PostgreSQL client library / database protocol feature. This means there will be no threads! Which is great. Less overhead, less stuff that can go wield. Twisted adbapi ~~~~~~~~~~~~~~ - `Twisted RDBMS support `__ - `twisted.enterprise.adbapi.ConnectionPool `__ You'll be interacting with the database via a database connection from the database connection pool created by Twisted, and run on a background pool of worker threads. Ther three main functions to use are: 1. `runQuery `__: Use this to run a single SQL query and get the result. 2. `runOperation `__: Use this to run a single SQL statement that does not return anything (such as an ``INSERT``, ``DELETE`` or ``UPDATE`` ). 3. `runInteraction `__: Use this to run a series of SQL statements in one SQL transaction. Any modifications done from within the interaction will be part of the (single) transaction, and either be committed or rolled back completely. A adbapi based database component ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ This example uses Twisted adbapi. Name it hello.py remembering that whatever you name it, both the filename and the derived class within must match exactly the value of "classname" dict, a required option of "components" under the "container" section of config.json. .. code:: python import psycopg2 from twisted.enterprise import adbapi from twisted.internet.defer import inlineCallbacks, returnValue from autobahn import wamp from autobahn.twisted.wamp import ApplicationSession class MyDatabaseComponent(ApplicationSession): @inlineCallbacks def onJoin(self, details): ## create a new database connection pool. connections are created lazy (as needed) ## def onPoolConnectionCreated(conn): ## callback fired when Twisted adds a new database connection to the pool. ## use this to do any app specific configuration / setup on the connection pid = conn.get_backend_pid() print("New DB connection created (backend PID {})".format(pid)) pool = adbapi.ConnectionPool("psycopg2", host = '127.0.0.1', port = 5432, database = 'test', user = 'testuser', password = 'testuser', cp_min = 3, cp_max = 10, cp_noisy = True, cp_openfun = onPoolConnectionCreated, cp_reconnect = True, cp_good_sql = "SELECT 1") ## we'll be doing all database access via this database connection pool ## self.db = pool ## register all procedures on this class which have been ## decorated to register them for remoting. ## regs = yield self.register(self) print("registered {} procedures".format(len(regs))) @wamp.register(u'com.example.now.v1') def get_dbnow(self): ## this variant demonstrates basic usage for running queries d = self.db.runQuery("SELECT now()") def got(rows): res = "{0}".format(rows[0][0]) return res d.addCallback(got) return d @wamp.register(u'com.example.now.v2') @inlineCallbacks def get_dbnow_inline(self): ## this variant is using inline callbacks which makes code "look synchronous", ## nevertheless run asynchronous under the hood rows = yield self.db.runQuery("SELECT now()") res = "{0}".format(rows[0][0]) returnValue(res) @wamp.register(u'com.example.now.v3') def get_dbnow_interaction(self): ## this variant runs the query inside a transaction (which might do more, ## and still be atomically committed/rolled back) def run(txn): txn.execute("SELECT now()") rows = txn.fetchall() res = "{0}".format(rows[0][0]) return res return self.db.runInteraction(run) if __name__ == '__main__': from autobahn.twisted.wamp import ApplicationRunner runner = ApplicationRunner(url = "ws://127.0.0.1:8080/ws", realm = "realm1") runner.run(MyDatabaseComponent) For testing the database component, use the following AutobahnJS based WAMP client and name it "index.htm". Make sure to place "index.htm" in a folder named "web" alongside your ".crossbar" folder. The example config.json will then find and serve index.htm and call all procedures of component. When running, you should see the current database time printed to the JavaScript console three times. .. code-block:: console

Hello, my fair database

txpostgres ~~~~~~~~~~ A txpostgres based database component ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ This example is using txpostgres, but provides the same functionality as the Twisted adbapi example component. You can use the same AutobahnJS based client "index.htm" from above but change "...for (var i = 1; i < 4; ++i) {..." to "...for (var i = 1; i < 5; ++i) {..." as it has a bonus procedure! Name it hello.py so config.json can find it (detailed reasons discussed in the adbabi example above). .. code:: python from txpostgres import txpostgres from twisted.internet.defer import inlineCallbacks, returnValue from autobahn import wamp from autobahn.twisted.wamp import ApplicationSession class MyDatabaseComponent(ApplicationSession): @inlineCallbacks def onJoin(self, details): ## create a new database connection pool. connections are created lazy (as needed) ## see: https://twistedmatrix.com/documents/current/api/twisted.enterprise.adbapi.ConnectionPool.html ## pool = txpostgres.ConnectionPool(None, host = '127.0.0.1', port = 5432, database = 'test', user = 'testuser', password = 'testuser') yield pool.start() print("DB connection pool started") ## we'll be doing all database access via this database connection pool ## self.db = pool ## register all procedures on this class which have been ## decorated to register them for remoting. ## regs = yield self.register(self) print("registered {} procedures".format(len(regs))) @wamp.register(u'com.example.now.v1') def get_dbnow(self): ## this variant demonstrates basic usage for running queries d = self.db.runQuery("SELECT now()") def got(rows): res = "{0}".format(rows[0][0]) return res d.addCallback(got) return d @wamp.register(u'com.example.now.v2') @inlineCallbacks def get_dbnow_inline(self): ## this variant is using inline callbacks which makes code "look synchronous", ## nevertheless run asynchronous under the hood rows = yield self.db.runQuery("SELECT now()") res = "{0}".format(rows[0][0]) returnValue(res) @wamp.register(u'com.example.now.v3') def get_dbnow_interaction(self): ## this variant runs the query inside a transaction (which might do more, ## and still be atomically committed/rolled back) def run(txn): d = txn.execute("SELECT now()") def on_cursor_ready(cur): rows = cur.fetchall() res = "{0}".format(rows[0][0]) return res d.addCallback(on_cursor_ready) return d return self.db.runInteraction(run) @wamp.register(u'com.example.now.v4') def get_dbnow_interaction_coroutine(self): ## this variant runs the query inside a transaction (which might do more, ## and still be atomically committed/rolled back). Further, we are using ## a co-routine based coding style here. @inlineCallbacks def run(txn): cur = yield txn.execute("SELECT now()") rows = cur.fetchall() res = "{0}".format(rows[0][0]) returnValue(res) return self.db.runInteraction(run) if __name__ == '__main__': from autobahn.twisted.wamp import ApplicationRunner runner = ApplicationRunner(url = "ws://127.0.0.1:8080/ws", realm = "realm1") runner.run(MyDatabaseComponent) Test config.json assumes your local copy of autobahn.min.js resides in "/srv/_shared-web-resources/autobahn": .. code:: javascript { "version": 2, "controller": { }, "workers": [ { "type": "router", "realms": [ { "name": "realm1", "roles": [ { "name": "anonymous", "permissions": [ { "uri": "com.", "match":"prefix", "allow": { "call": true, "register": true, "publish": true, "subscribe": true }, "disclose": { "caller": false, "publisher": false }, "cache": true } ] } ] } ], "transports": [ { "type": "web", "endpoint": { "type": "tcp", "port": 8080 }, "paths": { "/": { "type": "static", "directory": "../web" }, "shared": { "type": "static", "directory": "/srv/_shared-web-resources/autobahn" "ws": { "type": "websocket", "debug": true } } } ] }, { "type": "container", "options": { "pythonpath": [".."] }, "components": [ { "type": "class", "classname": "hello.MyDatabaseComponent", "realm": "realm1", "transport": { "type": "websocket", "endpoint": { "type": "tcp", "host": "127.0.0.1", "port": 8080 }, "url": "ws://127.0.0.1:8080/ws" } } ] } ] }