This document describes functionality pertaining to what is commonly understood as (.Net/Java) Gateway. There are three main parts:
- Execute Function
- Proxyless Gateway
- Proxy Gateway
Executes function by name. This API consists of two methods:
ExecuteFunction
ExecuteFunctionArgs
The difference between them is caller signature. ExecuteFunction
accepts %List, %Collection.AbstractArray and JSON object separated into positional and keyword arguments. ExecuteFunctionArgs
accepts args...
and parses them into positional and keyword arguments. After that ExecuteFunctionArgs
calls ExecuteFunction
.
It is caller responsibility to escape argument values. Use isc.py.util.Converter
class to escape:
- string
- boolean
- date
- time
- timestamp
ExecuteFunction
method from isc.py.Main
class. Signature:
function
- name of function to invoke. Can be nested, i.e.random.randint
variable
- name of python variable to write result to.positionalArguments
- positional arguments for Python function. Can be one of:$lb(val1, val2, ..., valN)
%Collection.AbstractIterator
object- JSON array
keywordArguments
- keyword arguments for Python function. Can be one of:$lb($lb(name1, val1), $lb(name2, val2), ..., $lb(nameN, valN))
%Collection.AbstractArray
object- flat JSON object
serialization
- how to serialize resultresult
- write result into this variable
All arguments besides function
are optional.
Here's an example of how it works:
set sc = ##class(isc.py.Main).ImportModule("random", ,.random)
set posList = $lb(1, 100)
set posCollection = ##class(%ListOfDataTypes).%New()
do posCollection.Insert(1)
do posCollection.Insert(100)
set posDynamic = [1, 100]
for positionalArguments = posList,posCollection,posDynamic {
set sc = ##class(isc.py.Main).ExecuteFunction(random _ ".randint", positionalArguments,,,.result)
write result,!
}
set kwList = $lb($lb("a", 1), $lb("b", 100))
set kwCollection = ##class(%ArrayOfDataTypes).%New()
do kwCollection.SetAt(1, "a")
do kwCollection.SetAt(100, "b")
set kwDynamic = { "a": 1, "b": 100}
for kwArguments = kwList,kwCollection,kwDynamic {
set sc = ##class(isc.py.Main).ExecuteFunction(random _ ".randint", ,kwArguments,,.result)
write result,!
}
set posList = $lb(1)
set kwDynamic = {"b": 100}
set sc = ##class(isc.py.Main).ExecuteFunction(random _ ".randint", posList, kwDynamic,,.result)
write result,!
set posList = ##class(isc.py.util.Converter).EscapeStringList($lb("Positional: {0} {1}! Keyword: {name}, {name2}", "Hello", "World"))
set kwDynamic = {"name":(##class(isc.py.util.Converter).EscapeString("Alice")),
"name2":(##class(isc.py.util.Converter).EscapeString("Bob"))}
set sc = ##class(isc.py.Main).ExecuteFunction("str.format", posList, kwDynamic,,.result)
write result,!
ExecuteFunctionArgs
method from isc.py.Main
class. Signature:
function
- name of function to invoke. Can be nested, i.e.random.randint
variable
- name of python variable to write result to.serialization
- how to serialize resultresult
- write result into this variableargs...
- function arguments.
ExecuteFunctionArgs
attempts to determine correct positional and keyword arguments from function signature (if available). It is recommended to call ExecuteFunction
directly if ExecuteFunctionArgs
is unable to construct a correct argument spec (and open an issue). Example:
set sc = ##class(isc.py.Main).ImportModule("random", ,.random)
set sc = ##class(isc.py.Main).ExecuteFunctionArgs(random _ ".randint", , ,.result, 1, 100)
write result,!
set string = ##class(isc.py.util.Converter).EscapeString("Positional: {0}, {1}, {2}, {3}")
set arg1 = ##class(isc.py.util.Converter).EscapeString("Hello")
set arg2 = ##class(isc.py.util.Converter).EscapeString("World")
set arg3 = ##class(isc.py.util.Converter).EscapeString("Alice")
set arg4 = ##class(isc.py.util.Converter).EscapeString("Bob")
set sc = ##class(isc.py.Main).ExecuteFunctionArgs("str.format",,,.result, string, arg1, arg2, arg3, arg4)
write result,!
set string = ##class(isc.py.util.Converter).EscapeString("Positional: {0} {1}! Keyword: {name}, {name2}")
set arg1 = ##class(isc.py.util.Converter).EscapeString("Hello")
set arg2 = ##class(isc.py.util.Converter).EscapeString("World")
set kwargs = "**" _ {"name":"Alice","name2":"Bob"}.%ToJSON()
set sc = ##class(isc.py.Main).ExecuteFunctionArgs("str.format",,, .result, string, arg1, arg2, kwargs)
write result,!
Proxyless gateway allows user to bind Python variables to InterSystems IRIS variables. This allows user to:
- Get/Set object properties
- Call object methods
- Serialize variable to: Str, Repr, Pickle, Dill, JSON, Dynamic Object.
Example.
- Load Python class
Person
:do ##class(isc.py.init.Test).Initialize(,1)
Note: here's Person
class definition for reference:
class Person(object):
def __init__(self, name, age, city):
self.name = name
self.age = age
self.city = city
def getAge(self):
return self.age
def getAgePlus(self, add):
return self.age + add
-
Create Proxy variable:
set obj = ##class(isc.py.gw.DynamicObject).%New("Person", "p1", "'Ed'", "25", "'Test'")
In this call we create Python variablep1
ofPerson
class and pass three methods to constructor'Ed'
,25
and'Test'
. -
Now we can interact with the object, let's get and set some properties:
write obj.name
set obj.name="Bob"
write obj.name
write obj.age
- We can set some new properties too (unlike
ExecuteFunction
values are escaped automatically if%EscapeOnSet
property is 1, which is default. You can also set properties to other dynamic objects. In that case unescaped python variable name would be used):
set obj.pet = "Dog"
write obj.pet
- And we can call object methods:
write obj.getAge()
write obj.getAgePlus(10)
- Finally we can convert object:
set sc = obj.%ToJSON(.json)
set sc = obj.%ToDynObj(.dynObj)
set sc = obj.%ToPickle(.pickle)
set sc = obj.%ToStream(.stream)
To create proxy object from existing proxy object just skip type argument:
kill obj
set p1 = ##class(isc.py.gw.DynamicObject).%New(, "p1")
Module objects can be proxied this way too:
set module = "random"
set sc = ##class(isc.py.Main).ImportModule(module)
set random = ##class(isc.py.gw.DynamicObject).%New(,module)
write random.randint(1,100)
Now for a more complex example. In case of primitives (int, bool, str, float) proxy object returns a serialized value. Otherwise (if method call or variable get returns complex type) it returns another proxy object pointing to that result.
set sc = ##class(isc.py.Main).ImportModule("numpy",,"np")
set np = ##class(isc.py.gw.DynamicObject).%New(,"np")
set arr = ##class(isc.py.gw.DynamicObject).%New("np.array", "arr","[[1.5,2],[4,5]]")
set exp = np.exp(arr)
write $replace(exp.%GetString(),$c(10), $c(13,10))
And here's an example of setting property to proxy object:
do ##class(isc.py.init.Test).Initialize(,1)
set obj = ##class(isc.py.gw.DynamicObject).%New("Person", "p1", "'Ed'", "25", "'Test'")
set obj2 = ##class(isc.py.gw.DynamicObject).%New("Person", "p2", "'Bob'", "22", "'Test2'")
write obj.%GetJSON()
set obj.relative = obj2
set obj3 = obj.relative
write obj3.%GetJSON()
You can use %EscapeOnSet
and %EscapeOnCall
properties and %IsPrimitive
method to affect default serialization behaviour.
Create proxy ObjectScript classes.
Fastest way to generate classes is to run this command:
set modules = $lb("module1", ..., "moduleN") // or string in case there's only one module
set sc = ##class(isc.py.gw.Generator).Generate(modules)
It generates classes with all defaults. If you want more control you can instantiate generator object and finetune it's behavior by setting these properties:
LogLevel
- Display logging info. Increasing the number outputs more messages. Does not affect generation.Package
- Base package for generated classes. Defaults topy
. Defaults to 1, maximum is 3.BaseClass
- Base class for all python classes. Defaults toisc.py.gw.DynamicObject
.FixClass
- Class that fixes Method/Classmethod and Signatures. Defaults toisc.py.util.Generator
. Must implement interfacesGetArguments(type, method, ByRef arguments)
andIsClassMethod(type, method) As %Boolean
.*Mask
- White/Black masks for classes, methods and properties.
- Create generator object:
set generator = ##class(isc.py.gw.Generator).%New()
- Set
generator
object properties. - Call
GenerateModule
method to generate classes for one module (and all related classes). - After all modules were generated, call
Compile
method.
- It's extremely recommended to do class generation from a clean process (without imported modules).
- Module generator does not support module aliases. Use canonical names only.
- After generation it is user's responsibility to load modules before calling corresponding methods.
- There's no distinction between class methods and instance methods in Python. That's why there's a number of heuristics implemented to determine the correct case. You can use Fixer class to deal with corner cases.
- If there were compile errors,
Compile
method would try to produce fixes for some of them. Add them to yourFixClass
or open an issue. - All packages should be generated using the same
Package
. isc.py.util.Generator
contains a list of Modules import of which on default settings should succeed.
- Let's generate
random
module:
set module = "random"
set sc = ##class(isc.py.gw.Generator).Generate(module)
- Now let's import the module:
set sc = ##class(isc.py.Main).ImportModule(module)
- We immediately can call functions of
random
module:write ##class(py.random).randint(1,100)
- Let's init a
random.Random
object:set rr = ##class(py.random.Random).%New()
- And call instance method:
write rr.betavariate(1, 3)
- Methods documentation is imported too - refer to it to know how to call the methods.
- To pass positional arguments or keyword arguments just pass their value.
- All arguments accept Python proxy objects and Python dynamic objects.
%Variable
is passed to function. - It is caller responsibility to escape the values.
- To pass *args pass either %List or Dynamic Array.
- To pass **kwargs pass either: %List of %List(key, value) or Flat dynamic object.
Consider this function:
def allargs(parg, *args, name=1, **kwargs):
print('first positional arg:', parg)
for arg in args:
print('arg from *argv:', arg)
print('first keyword arg name:', name)
for key, value in kwargs.items():
print('kwarg: {0} = {1}'.format(key, value))
It is a part of the ed
module. As you can see it accepts all types of arguments.
- Install the module:
pip install ed
. - Generate classes:
set sc = ##class(isc.py.gw.Generator).Generate("ed")
- Import the module:
set sc = ##class(isc.py.Main).ImportModule("ed")
- As
allargs
function just prints to stdout and does not return anything we need to redirect stdout:
set redirect = ##class(isc.py.util.Redirect).%New()
do redirect.Init()
do redirect.Enable(1)
- Here are different ways of calling
ed.allargs
function:
set parg = 1
set parg = ##class(isc.py.gw.DynamicObject).%New("int",, 1)
set args = 3
set args = $lb(2, 3)
set args = [2, 3]
set kwarg = 4
set kwarg = ##class(isc.py.gw.DynamicObject).%New("int",, 4)
set kwargs = { "a":5, "b":6 }
set kwargs = $lb($lb("a", 5), $lb("b", 6))
set sc = ##class(py.ed).allargs(parg, args, kwarg, kwargs)
zwrite sc
write redirect.GetString()