Py-Enscribe

digital data network background. abstract technology, big data and big data background. 3 d rendering.
Photo by your_photo@outlook.co.th on Deposit Photos

INTRODUCTION

py_enscribe is a python package developed to perform Enscribe File operations. It was released in SPR T0993L01^AAK. The design of this package was influenced by JEnscribe (Enscribe operations for Java). Internally py_enscribe uses cython to call Guardian APIs to achieve the Enscribe related operations. It has limited support for unstructured files and works well with other types of Enscribe Files.


GETTING STARTED

py_enscribe has different modules to achieve the desired functionalities. The modules have classes in them that have the same name as the modules themselves. Let us look at some of these, EnscribeFile module has EnscribeFile which provides access to Enscribe files through methods that perform standard Guardian operations.

EnscribeFileAttributes provides access to the more static information (attributes) about any Enscribe file. This information is obtained by a call to the Guardian procedure FILE_GETINFOLISTBYNAME_. There is no requirement that the file be open to use EnscribeFileAttributes. EnscribeFileAttributes also provides methods to change attributes when you want to alter the characteristics of an existing Enscribe file. These methods are comparable to the parameters of FILE_ALTERLIST_.

The AlternateKeyAlterAttributes class handles attributes that describe alternate key information for structured files. An AlternateKeyAlterAttribute object is returned from EnscribeFileAttributes.getAlternateKeyAttributes(). To change the alternate key attributes, use the set methods of this object, then pass this object as a parameter to the EnscribeFileAttributes.updateAlternateKeyAttributes() method. After you have made all the changes that you want to the attributes of a file, pass the EnscribeFileAttributes object to the EnscribeFile.alter() method.

The AlternateKeyDescriptor handles attributes that describe a specific alternate key. This class can be passed as a parameter when creating or altering a structured file. It is also returned when information is requested about a structured file.

EnscribeFileException exception class indicates that an error has occurred while trying an Enscribe File operation.

There are other classes which complement these functionalities.

CODE EXAMPLE

Let us look at a code example of creation of key sequenced file along with its alternate key file. Additionally let’s look at how to insert records and retrieve records.

"""
A sample program to create and write records to a 
key sequenced file
"""
from py_enscribe.EnscribeFile import EnscribeFile
from py_enscribe.EnscribeFileException import EnscribeFileException
from py_enscribe.KeySequencedCreationAttributes import KeySequencedCreationAttributes
from py_enscribe.AlternateKeyDescriptor import AlternateKeyDescriptor
from py_enscribe.EnscribeOpenOptions import EnscribeOpenOptions
from py_enscribe.EnscribeKeyPositionOptions import EnscribeKeyPositionOptions

filename        =  "$oss.enscribe.sample"
altFileName =  "$oss.enscribe.altfile"
record_list    = [ 
                  "01BobbieChapman        700000Java Development Group   05",
                  "02Faye Brackett        780000Java Development Group   05",
                  "03Frank Butera         790000Java HotSpot Group       06",
                  "04David Schorow       1000000Java Guru Group          07",
                  "05Alan Joseph          700000Java HotSpot Group       06",
                  "06Emile Roth           800000Java HotSpot Group       06",
                  "07Bill Blosen          900000Java Management Group    09",
                  "08Ken Holt            1000000Java Management Group    09",
                  "09Dave Velasco         800000Java Pthreads Group      08",
                  "10Last Guy              50000Last Guy Group           99"]

# First create the file
try:
    sample = EnscribeFile(fileName)
    altfile = EnscribeFile(altFileName)

    # If the file exists, purge it
    try:
        if(sample.isExistingFile()):
            sample.purge();
    except EnscribeFileException as pe:
        print("Shouldn't get an exception on the purge because we check to see if it exists first "+pe.getMessage())

    # If the alternate key file exists, purge it
    try:
        if(altfile.isExistingFile()):
            altfile.purge();
    except EnscribeFileException as pe:
        print("Shouldn't get an exception on the purge because we check to see if it exists first "+pe.getMessage());

    # Create the primary file
    creationAttr = KeySequencedCreationAttributes();
    
    # Set general file attributes
    creationAttr.setRecordLength(56);
    creationAttr.setBlockLength(4096);
    creationAttr.setKeyLength(2);
    creationAttr.setKeyOffset(0);

    # Set alternate key attributes
    ak = [0];
    ak[0] = AlternateKeyDescriptor();
    ak[0].setKeyOffset(54);
    ak[0].setKeyLength(2);
    ak[0].setKeySpecifier("DP");
    ak[0].setAlternateKeyFileName(altFileName);
    creationAttr.setAlternateKeyDescriptors(ak);
    sample.create(creationAttr);
    sample.createAltKeyFiles();

    print(">>Created File")

    # Obtain the attributes of the newly created file
    print(">>Attributes of new file: ")
    myAttr = sample.getFileInfo()
    print("   File code = ",myAttr.getFileCode())
    print("   File security = ",myAttr.getGuardianSecurityString());
    print("   Primary keyoffset = ",myAttr.getKeyOffset());
    print("   Maximum extents = ",myAttr.getMaximumExtents());
    print("   Alternate Key Attributes: ");
    altdesc = myAttr.getAlternateKeyAttributes();
    # Only one, since that is how we created the file
    print("      Number of alt keys = ",myAttr.getNumberOfAltkeys());
    print("      Alternate key file name = ",altdesc[0].getAlternateKeyFileName());
    print("      Key Specifier is ",altdesc[0].getKeySpecifierString());
    
    # Open the file and write some data
    options = EnscribeOpenOptions()
    options.setAccess(EnscribeOpenOptions.READ_WRITE)
    sample.open(options,EnscribeOpenOptions.SHARED);
    print(">>Opened file. ");
    for i in range(0, len(record_list)):
        sample.write(record_list[i]);
    print(">>Wrote data to the file");
    print(" ");

    # Going to position on alternate key value 06
    print(">>Reading records with alternate key value of 06");
    kpa           = EnscribeKeyPositionOptions();
    keySpec = altdesc[0].getKeySpecifierString();
    kpa.setKeySpecifier(keySpec);
    keyvalue = "06"
    kpa.setKeyValue(keyvalue);
    kpa.setPositioningMode(EnscribeKeyPositionOptions.POSITION_GENERIC);
    kpa.setKeyLength(2);
    kpa.setCompareLength(2);

    sample.keyPosition(kpa);
    maxRead = sample.getMaxReadCount();

    for i in range(0,3):
        buf, count = sample.read(maxRead);
        print("   ",buf);

    # Now read in the reverse direction
    print("");
    print(">>Reading same records in reverse direction");
    kpa.setReadReversed(True,True);
    sample.keyPosition(kpa);
    record, count = sample.read(maxRead)
    print("   ",record)
    while count!= -1:
        record, count = sample.read(maxRead)
        print("   ",record);

    sample.close();
    
except EnscribeFileException as ee:
    print(ee.getMessage());

py_enscribe also helps in file_search using the EnscribeFileSearch module, internally it makes use of APIs like FILENAME_FINDSTART_, FILENAME_FINDNEXT_ and FILENAME_FINDFINISH_. 

UNDER THE HOOD

The item codes and values for a GPC are interpreted as a list in the python layer. Any value item that has more than 2 bytes is to be split into successive values in intervals of two bytes in the value_list.

Let us take the item code 104, that is the names of alternate key files. Let’s say we have an alternate file with file name “altfile”, we need to map this to the value list as:
 [“al”, “tf”, “il”, “e”]

The strings are internally converted to bytes strings before passing to cython .pyx file, and then strings are interpreted into integers using bit logic,

def bytes_to_int(bytes byte_string):
    """
    this is used to convert a bytes object containing two or one 
    character into corresponding int. This is analogous to C types
    cast of char * to short.
    for ex: short key = (short)('DT'); /* key specifier */
    """
    cdef int result = 0
    cdef int length = len(byte_string)
    cdef int i

    if length == 2:
        for i in range(length):
            result = (result << 8) | byte_string[i]

    elif length == 1:
        result = byte_string[0] << 8

Once you have item-list and value-list ready you can pass it to a function defined in cython .pyx file. The desired function in cython .pyx file can import a GPC and call the respective GPC. The template goes as follows,

.pyx file contents

cdef extern from "cextdecs.h":
    short FILE_CLOSE_(short filenum, short tape_disposition);
def file_close(filenum, tape_disposition=0):
    """ closes open file with wait i/o """
    error = FILE_CLOSE_(filenum, tape_disposition);
    return error

In a .py file you can import the module and use file_close as a normal python function.

WRITING AND READING A PYTHON OBJECT FROM ENSCRIBE FILE

We can read and write a python class with all its methods to an Enscribe file. This will help with structured data storage and retrieval.

import base64
import pickle
from py_enscribe.EnscribeFile import EnscribeFile
from py_enscribe.EnscribeFileException import EnscribeFileException
from py_enscribe.KeySequencedCreationAttributes import KeySequencedCreationAttributes
from py_enscribe.AlternateKeyDescriptor import AlternateKeyDescriptor
from py_enscribe.EnscribeOpenOptions import EnscribeOpenOptions
from py_enscribe.EnscribeKeyPositionOptions import EnscribeKeyPositionOptions

fileName     = "$oss.enscribe.samplc"
altFileName  = "$oss.enscribe.altfilc"

class Employee:
    def __init__(self, name, experience, company):
        self.name       = name
        self.experience = experience
        self.company    = company
    def __repr__(self):
        return f"Employee(name={self.name}, company={self.company})"
    def calc_salary(self, sal):
        print("Employee salary is ", sal)

# Create an object
obj = Employee("John", 4, "HPE")

# Serialize (pack) into a byte string
byte_data = base64.b64encode(pickle.dumps(obj)).decode("utf-8")
print(f"Packed bytes: {byte_data}, data len is ", len(byte_data))

try:
    sample      = EnscribeFile(fileName)
    altfile     = EnscribeFile(altFileName)

    # If the file exists,purge it
    try:
        if(sample.isExistingFile()):
            sample.purge();
    except EnscribeFileException as pe:
        print("Shouldn't get an exception on the purge because we check to see if it exists first "+pe.getMessage())

    # If the alternate key file exists, purge it
    try:
        if(altfile.isExistingFile()):
            altfile.purge();
    except EnscribeFileException as pe:
        print("Shouldn't get an exception on the purge because we check to see if it exists first "+pe.getMessage());

    # Create the primary file
    creationAttr = KeySequencedCreationAttributes();

    # Set general file attributes
    creationAttr.setRecordLength(1024);
    creationAttr.setBlockLength(4096);
    creationAttr.setKeyLength(2);
    creationAttr.setKeyOffset(0);

    # Set alternate key attributes
    ak    = [0];
    ak[0] = AlternateKeyDescriptor();
    ak[0].setKeyOffset(54);
    ak[0].setKeyLength(2);
    ak[0].setKeySpecifier("DP");
    ak[0].setAlternateKeyFileName(altFileName);
    creationAttr.setAlternateKeyDescriptors(ak);
    sample.create(creationAttr);
    sample.createAltKeyFiles();

    print(">>Created File")
    options = EnscribeOpenOptions()
    options.setAccess(EnscribeOpenOptions.READ_WRITE)
    sample.open(options,EnscribeOpenOptions.SHARED);
    print(">>Opened File ");
    sample.write(byte_data)

    print(">>Wrote data to the File");
    print(" ");

    print("Reading Data")

    stored_str, _ = sample.read(1024)
    restored_obj  = pickle.loads(base64.b64decode(stored_str.encode("utf-8")))

    print("Restored Object ",restored_obj)
    print("Employee Name ", restored_obj.name)
    print("Company Name ",restored_obj.company)
    restored_obj.calc_salary(500)

    sample.close()

except EnscribeFileException as ee:
    print(ee.getMessage())

The above program outputs the following,

Packed bytes: gASVTgAAAAAAAACMCF9fbWFpbl9flIwIRW1wbG95ZWWUk5QpgZR9lCiMBG5hbWWUjARKb2hulIwKZXhwZXJpZW5jZZRLBIwHY29tcGFueZSMA0hQRZR1Yi4=, data len is  120
>>Created File
>>Opened File
>>Wrote data to the File

Reading Data
Restored Object  Employee(name=John, company=HPE)
Employee Name  John
Company Name  HPE
Employee salary is  500

ADVANTAGES

It is easier to call GPCs using cython. Creating C extensions leads to faster execution time rather than having to load the desired dll using ctypes. Remembering the item descriptions is easier than mapping item codes to values for a GPC. The py_enscribe takes care of corner cases and issues that can arise due to an Enscribe operation using the EnscribeFileException module.

Author

  • 20250624_202503

    Mohammed Zuhaib is a Systems Development Engineer at Hewlett Packard Enterprise, working on porting Python to the Nonstop platform. He specializes in Python internals, API development, and systems software.
    View all posts Software Engineer II

Be the first to comment

Leave a Reply

Your email address will not be published.


*


This site uses Akismet to reduce spam. Learn how your comment data is processed.