Article
· Feb 6 7m read

About the idea of Using Python Class Definition Syntax to create IRIS classes

Introduction

Not so long ago, I came across the idea of using Python Class Definition Syntax to create IRIS classes on the InterSystems Ideas Portal. It caught my attention since integrating as many syntaxes as possible gives visibility to InterSystems’s products for programmers with experience in many languages.

The author of that idea pointed out the possibility of creating classes using Python’s syntax, in addition to the currently available ones on IRIS. This concept inspired me to write this article, exploring the possibilities of accessing the full InterSystems power employing only Python.

It is worth noticing that one of the precursors of all this power is the multi-model capability of IRIS. It allows you to access the same data through Globals (a multidimensional storage structure). Also, it is the underlying structure for all other models. Likewise, you can access the above-mentioned data by objects. In the scope of this article, accessing data by object means using JSON or XML adapters for connecting IRIS and Python. Besides, you can access data by SQL utilizing JDBC or ODBC connections. Lastly, you can employ DocDB (Document database) that also operates JSON syntax to get the same result.

Create an IRIS database from a Python class using SQL

To start, we should discuss the main point of the idea: how to use Python’s class definition syntax to create an IRIS class. It is possible to achieve that, but the complexity of this implementation will depend entirely on the sophistication of the relationships defined in your class. You can click here to look at my studies on this solution. Do not hesitate to contact me if you wish to discuss it more!

If you only need the properties of a class defined in Python, the easiest approach will be to use SQLAlchemy’s mapper() function to create your class.

You will require the following imports to do that:
 

from sqlalchemy import create_engine, MetaData
from sqlalchemy.schema import Table, Column
from sqlalchemy.orm import mapper

Of course, you should begin by defining your engine, and only then creating your table. Optionally, you may define a primary key column if your class does not have it already.

engine = create_engine("iris://" + user + ":" + password + "@" + host + ":" + port + "/" + namespace)
metadata = MetaData()
table = Table(class_name, metadata, Column('id', Integer,  primary_key = True))

Additionally, for each property in your class, remember to append a Column() in your table with its name, SQL type from sqlalchemy.sql.sqltypes, and optionally a default value. You may want to check out this very useful link with a Python to SQL dictionary to simplify your task.

Finally, you should call the mapper with your class and the table as arguments, and create them with your engine.
 

mapper(my_class, table)
metadata.create_all(engine)

If you nonetheless desire to develop methods, you can access your newly created .cls file, define a method with ObjectScript definition, and set its language to Python.

Yet, you may still avoid this single line of ObjectScript when creating your class. The way to do it is to use InterSystems Native SDK for Python to connect Python to Iris and utilize it to access %Dictionary classes. Those classes will help you build your .cls file. You will need a schema, a class name, property names and types, and methods implementations to accomplish this task.

If you have a class defined in Python, you can easily access that information. Define the schema as you like and access the class name in one line of code, as shown below:

class_name = your_class.__class__.__name__

Next, the built-in function dir() will give you every attribute within a class. You can use the callable() method to check whether it is a property or a method. Finally, you can import the module inspect and utilize its functions getsource() and getfullargspec() to access every piece of information from your methods.

implementation = i.getsource(function) # this gets the written code as a string
inspection = i.getfullargspec(function) 
defaults = inspection.defaults
annotation = inspection.annotations
arguments = inspection[0]

Bear in mind that if the first argument in a class function from Python is “self”, it is reflected in ObjectScript as a Method. Otherwise, it will be represented as a ClassMethod.

With those elements, you can open your class and modify it with the %Dictionary classes. 
 

new_class = irispy.classMethodObject("%Dictionary.ClassDefinition", "%Open", class_name)
new_method = irispy.classMethodObject("%Dictionary.MethodDefinition", "%New", class_name + ":" + method_name)
new_method.set("Language", "python")
new_method.get("Implementation").invoke("Write", implementation)
new_method.set("ClassMethod", isClassMethod)
new_method.set("FormalSpec", formal_spec)
new_class.get("Methods").invoke("Insert", new_method)

In the above-written code, the irispy object was generated by connecting the IRIS instance with the method createIRIS() from the IRIS module. You can discover more details about establishing the connection in the official documentation or one of my previous articles. Yet, right now, the code illustrated below is all you need to know:

import iris

# Open a connection to the server
args = {'hostname':'127.0.0.1', 'port':52773,
    'namespace':'USER', 'username':'_SYSTEM', 'password':'SYS'
}
conn = iris.connect(**args)
# Create an iris object
irispy = iris.createIRIS(conn)

You can create your formal_spec with the annotation from the inspection.

For the last step, remember to invoke %Save on this definition.
 

sc = new_class.invoke("%Save")

 

Create an IRIS database from a Python class using DocDB

First, you should enable your instance to receive this type of REST request. To do that, go to System Administration > Security > Service, select %Service_DocDB, choose “Service enabled”, and click save. Next, your system may require installing the Python requests module with pip install requests. 

If you are not familiar with REST APIs with Python, you will still be able to follow this tutorial since everything that will be used here is quite intuitive.

We will only need to send a few URLs to the %Api.DocDB.v1, which is a built-in option in IRIS. Just import requests in your Python environment and write a URL with the pattern demonstrated below:

url = http://HOST:PORT/api/docdb/v1/NAMESPACE/db/DBNAME?type=cls

Then, send a POST request to this URL with request.post(url). Ready! Now you should be able to see a class called CLASSNAME in the default schema ISC_DM, in the selected namespace.

After creating the class, you can add properties by sending more POST requests with the following URL pattern:

http://HOST:PORT/api/docdb/v1/NAMESPACE/prop/DBNAME/PROPERTYNAME?type=%String&path=PROPERTYPATH

You can perceive the property path as the path you would use to get this property value in case it were stored in a dynamic object built from JSON. For instance, imagine that you can represent your object like the code snippet below.
 

{
    "person": {
        "name":"heloisa tambara",
        "country":"brazil"
    }
}

In this case, the path for the name property would be person.name.

Finally, you can start populating this table with one more URL pattern:

http://HOST:PORT/api/docdb/v1/NAMESPACE/doc/DBNAME/ID

If you want to create a new document, use the POST method and leave the ID field empty. On the other hand, to replace an existing document, utilize the PUT method and specify the ID you wish to modify.

Write the body of your request according to the property paths you have employed. For example, if you have created some properties with the paths “person.name”, “person.location.country”, “person.location.state”,“person.location.city” you can add a document by sending the JSON below in the body of your request.
 

{
    "person": {
        "name":"Heloisa Tambara",
        "location": {
            "country":"Brasil",
            "state":"Pernambuco",
            "city":"Recife"
        }
    }
}

This section was designed to give you just a bit of a taste of what DocDB can do. In the documentation you can access by clicking here, you can uncover all available methods in more detail.

 

Going further: use IRIS interoperability without ObjectScript

For most implementations, you can almost abolish the use of COS classes. Suppose you are looking for a full interoperability production built only with Python. In that case, you can try PEX (Production EXtension framework) and limit the use of .cls files for the production class only. The best practice for utilizing PEX is to employ Guillaume Rongier’s application (interoperability-embedded-python), which bridges the gap between the InterSystems’s architecture for interoperability and Python. If you are not familiar with that structure yet, please take a look at the 30-minute-long course dedicated to Integration Architecture.

If that is your goal, I recommend you skim through the article about Full Python IRIS production: DataTransformation of a CSV to a FHIR server by Lucas Enard. The GitHub page related to the OpenExchange application described in the text shows a great example of how to achieve mostly Python development.

 

There is always more…

As you can see, there are many ways of creating, manipulating, and populating classes in IRIS. Have you already acknowledged all of those described above? Which one would you like me to write more about? What other ways do you know that I have not talked about yet? Please let me know your thoughts!
 

Discussion (3)2
Log in or sign up to continue

Many thanks for your article, Heloisa!

I guess that we have the same goal: to make IRIS more accessible to Python developers.

Do you know that IRIS 2024.1 will have a native support of WSGI :)

I'm looking forward to your next article!

If you have time can you have a look at my article about feedback using embedded python daily for more than 2 years and tell me what do you think about it?