Python & Btrieve 2 on Windows: Accessing Zen Data With NoSQL
Actian Corporation
February 2, 2018
In my previous blog (“Programming the Easy Way: Accessing a PSQL Zen Database with Python and ODBC“), I showed how to easily access an Actian Zen database with Python and ODBC. My next project was to use the new Btrieve 2 feature with Python to directly interface with the data files without using any SQL.
Long-time Btrieve/PSQL/Zen customers are familiar with the old Btrieve function, a classic 3GL programming interface that hasn’t really changed in over 30 years. It provides direct access to the engine so that you can manipulate data files with options like Create, Open, Insert, Update, Delete, GetFirst, GetNext, GetEqual, etc. Now, with Actian Zen v13 we have Btrieve 2, which offers a simplified, more intuitive object-oriented interface covering the full set of Btrieve calls to the database engine. This interface is provided for C/C++ developers, but we also supply SWIG (Simplified Wrapper & Interface Generator) files so the interface can be used by Python, Perl, and PHP developers.
Getting setup to use Btrieve 2 with Python requires a few steps:
- Prepare your Windows environment:
- Install the Actian Zen v13 database engine
- Install Python for Windows – I used v3.6 64-bit from python.org
- Download & unzip Swig for Windows – I used Swigwin-3.0.12 from swig.org
- Download & unzip the Btrieve 2 SDK
- Visual Studio 2015 or later is also required; you can install a Community Version from Microsoft if you don’t already have one from here.
- Create a MyPrograms folder for your Python code, and copy in the following files from the Btrieve 2 SDK:
- btrieveCpp.lib & btrieveC.lib from win64
- btrieveC.h & btrieveCpp.h from include
- btrievePython.swig & btrieveSwig.swig from swig
- Open a command prompt and change to your MyPrograms folder, and enter the following command:
<swigwin_location>swig -c++ -D_WIN64 -python btrievePython.swig
This creates the following files: btrievePython.py & btrievePython_wrap.cxx - Create a text file in MyPrograms called setup.py containing the following:
from distutils.core import setup, Extension btrievePython_module = Extension('_btrievePython', sources=['btrievePython_wrap.cxx'], library_dirs=['<MyPrograms location>'], libraries=['btrieveCpp'], ) setup (name='btrievePython', version='1.0', author='Actian', description="""Compile Btrieve 2 Python module""", ext_modules=[btrievePython_module], py_modules=["btrievePython"], )
- In a command prompt, in the MyPrograms folder, run this command to build the btrievePython module:
python setup.py build_ext --plat-name="win-amd64"
This creates the compiled Python file buildlib.win-amd64-3.6_btrievePython.cp36-win_amd64.pyd - Rename the .pyd file to just _btrievePython.pyd and copy it to MyPrograms or the DLLs folder under your Python installation.
Once these steps are completed, you should be able to “import btrievePython” in a Python environment or program.
Once the btrievePython import library is setup, you are ready to write Btrieve 2 applications in Python! Here’s an example that performs the same database operations as the ODBC version from my previous blog – create the same file, insert records, and retrieve the total count inserted. This code is about twice as long as the ODBC version, but it accesses the file directly without going through the SQL layer. (Note – only minimal error checking has been included):
import os import sys import struct import btrievePython as btrv btrieveFileName = "Test_Table.mkd" recordFormat = "<iB32sBBBH" recordLength = 42 keyFormat = "<i" # Create a session: btrieveClient = btrv.BtrieveClient(0x4232, 0) # ServiceAgent=B2 # Specify FileAttributes for the new file: btrieveFileAttributes = btrv.BtrieveFileAttributes() rc = btrieveFileAttributes.SetFixedRecordLength(recordLength) # Specify Key 0 as an autoinc: btrieveKeySegment = btrv.BtrieveKeySegment() rc = btrieveKeySegment.SetField(0, 4, btrv.Btrieve.DATA_TYPE_AUTOINCREMENT) btrieveIndexAttributes = btrv.BtrieveIndexAttributes() rc = btrieveIndexAttributes.AddKeySegment(btrieveKeySegment) rc = btrieveIndexAttributes.SetDuplicateMode(False) rc = btrieveIndexAttributes.SetModifiable(True) # Create the file: rc = btrieveClient.FileCreate(btrieveFileAttributes, btrieveIndexAttributes, btrieveFileName, btrv.Btrieve.CREATE_MODE_OVERWRITE) if (rc == btrv.Btrieve.STATUS_CODE_NO_ERROR): print('nFile "' + btrieveFileName + '" created successfully!') else: print('nFile "' + btrieveFileName + '" not created; error: ', rc) # Allocate a file object: btrieveFile = btrv.BtrieveFile() # Open the file: rc = btrieveClient.FileOpen(btrieveFile, btrieveFileName, None, btrv.Btrieve.OPEN_MODE_NORMAL) if (rc == btrv.Btrieve.STATUS_CODE_NO_ERROR): print('File open successful!n') else: print('File open failed - status: ', rc, 'n') # Insert records: iinserting = True while iinserting: new_name = input('Insert name (Q to quit): ' ) if new_name.lower() == 'q': iinserting = False else: record = struct.pack(recordFormat, 0, 0, new_name.ljust(32).encode('UTF-8'), 0, 22, 1, 2018) rc = btrieveFile.RecordCreate(record) if (rc == btrv.Btrieve.STATUS_CODE_NO_ERROR): print(' Insert successful!') else: print(' Insert failed - status: ', rc) # Get Record count: btrieveFileInfo = btrv.BtrieveFileInformation() rc = btrv.BtrieveFile.GetInformation(btrieveFile, btrieveFileInfo) print('nTotal Records inserted =', btrieveFileInfo.GetRecordCount()) # Close the file: rc = btrieveClient.FileClose(btrieveFile) if (rc == btrv.Btrieve.STATUS_CODE_NO_ERROR): print('File closed successful!') else: print('File close failed - status: ', rc)
Since the above example doesn’t actually do any data reads, I went back and added a little more code before the closing the file to demonstrate a file scan that looks for a name:
# Look up record by name ireading = True while ireading: find_name = input('nFind name (Q to quit): ' ) if find_name.lower() == 'q': ireading = False else: foundOne = False record = struct.pack(recordFormat, 0, 0, ' '.ljust(32).encode('UTF-8'), 0, 0, 0, 0) readLength = btrieveFile.RecordRetrieveFirst(btrv.Btrieve.INDEX_NONE, record, 0) while (readLength > 0): recordUnpacked = struct.unpack(recordFormat, record) if (recordUnpacked[2] == find_name.ljust(32).encode('UTF-8')): print(' Matching record found: ID:', recordUnpacked[0], ' Name:', recordUnpacked[2].decode()) foundOne = True readLength = btrieveFile.RecordRetrieveNext(record, 0) if (foundOne == False): print(' No record found matching "'+find_name+'"') status = btrieveFile.GetLastStatusCode() if (status != btrv.Btrieve.STATUS_CODE_END_OF_FILE): print(' Read error: ', status, btrv.Btrieve.StatusCodeToString(status))
The simplicity of Python programming combined with the new Btrieve 2 development interface will allow for the fast turn-around of new Zen applications!
If you have any questions about Zen or other Actian products please feel free to ask in our community forums.
Subscribe to the Actian Blog
Subscribe to Actian’s blog to get data insights delivered right to you.
- Stay in the know – Get the latest in data analytics pushed directly to your inbox
- Never miss a post – You’ll receive automatic email updates to let you know when new posts are live
- It’s all up to you – Change your delivery preferences to suit your needs