Skip to content

Commit 8ae9d4b

Browse files
authored
Merge pull request #5 from grongierisc/master
Add python experience on datapipe flows
2 parents 5a5ba8b + 8eaf6ab commit 8ae9d4b

28 files changed

+827
-49
lines changed

.gitignore

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,15 @@
11
*.log
2-
data/input/*.hl7
2+
data/input/*.hl7
3+
data/output/
4+
# Python stuff:
5+
*.pyc
6+
*.pyo
7+
# virtualenv
8+
.venv
9+
# dist
10+
dist
11+
# build
12+
build
13+
# eggs
14+
*.egg
15+
*.egg-info

Dockerfile

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
ARG IMAGE=intersystemsdc/irishealth-community:2022.2.0.368.0-zpm
1+
ARG IMAGE=intersystemsdc/irishealth-community:latest
22
FROM $IMAGE
33

44
USER root
@@ -24,7 +24,17 @@ COPY --chown=irisowner:irisowner src src
2424
COPY --chown=irisowner:irisowner module.xml module.xml
2525
COPY --chown=irisowner:irisowner Installer.cls Installer.cls
2626

27+
ENV PATH "/home/irisowner/.local/bin:/usr/irissys/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/home/irisowner/bin"
28+
29+
RUN pip install iris-pex-embedded-python
30+
31+
## Python stuff
32+
ENV IRISUSERNAME "SuperUser"
33+
ENV IRISPASSWORD "SYS"
34+
ENV IRISNAMESPACE "DPIPE"
35+
2736
# run iris.script
2837
RUN iris start IRIS \
2938
&& iris session IRIS < /opt/irisapp/iris.script \
39+
&& iop -m /opt/irisapp/src/python/Demo/settings.py \
3040
&& iris stop IRIS quietly

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ docker compose up -d
1111

1212
* Log-in to the system using `superuser` / `SYS`.
1313

14+
* Start an inteoperability production in *Interoperability > List > Productions*:
15+
* `DataPipe.Test.Production` - regular objectscript DataPipe demo production
16+
* `DataPipe.Python.Production` - Python implemented DataPiepe demo production
17+
1418
* Generate sample data using [WebTerminal](http://localhost:52773/terminal/)
1519
```objectscript
1620
do ##class(DataPipe.Test.HL7.Helper).GenerateFilesHL7ADT(100)

docker-compose.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ services:
99
container_name: datapipe
1010
init: true
1111
ports:
12-
- "1972:1972"
13-
- "52773:52773"
12+
- 1972:1972
13+
- 52773:52773
1414
volumes:
1515
- .:/app
1616
networks:

iris.script

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@
33
set $namespace = "%SYS"
44
do ##class(Security.Users).UnExpireUserPasswords("*")
55

6+
//Service for embedded python
7+
do ##class(Security.Services).Get("%Service_CallIn",.prop)
8+
set prop("Enabled")=1
9+
set prop("AutheEnabled")=48
10+
do ##class(Security.Services).Modify("%Service_CallIn",.prop)
11+
612
// create ns for dev environment
713
do $SYSTEM.OBJ.Load("/opt/irisapp/Installer.cls", "ck")
814

@@ -24,6 +30,5 @@
2430
// auto start interop production
2531
set production = "DataPipe.Test.Production"
2632
set ^Ens.Configuration("csp","LastProduction") = production
27-
do ##class(Ens.Director).SetAutoStart(production)
2833

2934
halt

module.xml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<Document name="iris-datapipe.ZPM">
44
<Module>
55
<Name>iris-datapipe</Name>
6-
<Version>0.0.1</Version>
6+
<Version>0.0.2</Version>
77
<Description>DataPipe an interoperability framework to ingest data in InterSystems IRIS in a flexible way.</Description>
88
<Keywords>datapipe ingestion staging validation</Keywords>
99
<Author>
@@ -19,6 +19,13 @@
1919
<Version>>=1.0.8</Version>
2020
</ModuleReference>
2121
</Dependencies>
22+
<Dependencies>
23+
<ModuleReference>
24+
<Name>pex-embbeded-python</Name>
25+
<Version>>=2.0.0</Version>
26+
</ModuleReference>
27+
</Dependencies>
28+
<FileCopy Name="src/python/DataPipe" Target="${libdir}python/DataPipe/"/>
2229
<SourcesRoot>src</SourcesRoot>
2330
<Resource Name="DataPipe.PKG"/>
2431
<CSPApplication

pyproject.toml

Whitespace-only changes.

setup.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# Licensed under the MIT License
2+
# https://github.com/grongierisc/iris_datapipe/blob/main/LICENSE
3+
4+
import os
5+
6+
from setuptools import setup
7+
8+
def package_files(directory):
9+
paths = []
10+
for (path, directories, filenames) in os.walk(directory):
11+
for filename in filenames:
12+
paths.append(os.path.join('.', path, filename))
13+
return paths
14+
15+
extra_files = package_files('src/python/')
16+
17+
def main():
18+
# Read the readme for use as the long description
19+
with open(os.path.join(os.path.abspath(os.path.dirname(__file__)),
20+
'README.md'), encoding='utf-8') as readme_file:
21+
long_description = readme_file.read()
22+
23+
# Do the setup
24+
setup(
25+
name='iris_datapipe',
26+
description='iris_datapipe',
27+
long_description=long_description,
28+
long_description_content_type='text/markdown',
29+
version='0.0.2',
30+
author='grongier',
31+
author_email='guillaume.rongier@intersystems.com',
32+
keywords='iris_datapipe',
33+
url='https://github.com/grongierisc/iris-datapipe',
34+
license='MIT',
35+
classifiers=[
36+
'Development Status :: 5 - Production/Stable',
37+
'Intended Audience :: Developers',
38+
'License :: OSI Approved :: MIT License',
39+
'Operating System :: OS Independent',
40+
'Programming Language :: Python :: 3.6',
41+
'Programming Language :: Python :: 3.7',
42+
'Programming Language :: Python :: 3.8',
43+
'Programming Language :: Python :: 3.9',
44+
'Programming Language :: Python :: 3.10',
45+
'Programming Language :: Python :: 3.11',
46+
'Topic :: Utilities'
47+
],
48+
package_dir={'': 'src/python'},
49+
packages=['DataPipe'],
50+
51+
python_requires='>=3.6',
52+
install_requires=[
53+
"iris-pex-embedded-python>=2.0.0"
54+
]
55+
)
56+
57+
58+
if __name__ == '__main__':
59+
main()

src/DataPipe/Data/Inbox.cls

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,9 @@ ClassMethod queryFIND() As %String
8888

8989
/// Get an Inbox record, querying by key attributes
9090
/// This method is used to determine if an Inbox record can be re-used
91-
ClassMethod GetByKeyAttributes(att As InboxAttributes, Output obj As Inbox) As %Status
91+
ClassMethod GetByKeyAttributes(
92+
att As InboxAttributes,
93+
Output obj As Inbox) As %Status
9294
{
9395
set ret = $$$OK
9496
try {
@@ -115,7 +117,10 @@ ClassMethod GetByKeyAttributes(att As InboxAttributes, Output obj As Inbox) As %
115117

116118
/// Calculate a SQL field given an Inbox ID
117119
/// This method is used by calculated SQL fields
118-
ClassMethod CalcSqlFieldById(field As %String, default As %String, id As %String) As %String
120+
ClassMethod CalcSqlFieldById(
121+
field As %String,
122+
default As %String,
123+
id As %String) As %String
119124
{
120125
set ret = default
121126
try {

src/DataPipe/Data/Ingestion.cls

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,17 @@ Parameter FORMORDERBY As %String = "Id";
1212
/// *Calculated* Id. %ID value projected to JSON
1313
Property Id As %Integer(%JSONINCLUDE = "OUTPUTONLY") [ Calculated, SqlComputeCode = { set {*}={%%ID}}, SqlComputed ];
1414

15+
/// Model Is Python
16+
Property ModelIsPython As %Boolean(%JSONINCLUDE = "NONE") [ InitialExpression = 0 ];
17+
18+
/// Model class name
19+
Property ModelModule As %String(%JSONINCLUDE = "NONE", MAXLEN = "");
20+
21+
/// Model Module path
22+
Property ModelClassPath As %String(%JSONINCLUDE = "NONE", MAXLEN = "");
23+
1524
/// Model class name
16-
Property ModelName As %String(MAXLEN="");
25+
Property ModelName As %String(MAXLEN = "");
1726

1827
/// Model serialized data
1928
Property ModelData As %Stream.GlobalCharacter;
@@ -81,6 +90,15 @@ Storage Default
8190
<Value name="12">
8291
<Value>HeaderId</Value>
8392
</Value>
93+
<Value name="13">
94+
<Value>ModelIsPython</Value>
95+
</Value>
96+
<Value name="14">
97+
<Value>ModelModule</Value>
98+
</Value>
99+
<Value name="15">
100+
<Value>ModelClassPath</Value>
101+
</Value>
84102
</Data>
85103
<DataLocation>^DataPipe.Data.IngestionD</DataLocation>
86104
<DefaultData>IngestionDefaultData</DefaultData>

src/DataPipe/Data/Staging.cls

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,9 @@ Method SerializeValidationErrors() As %Status
5757
try {
5858
set count = ..ValidationErrors.Count()
5959
for i=1:1:count {
60-
set code = ..ValidationErrors.GetAt(i).Code
61-
set desc = ..ValidationErrors.GetAt(i).Desc
60+
set err = ..ValidationErrors.GetAt(i)
61+
set code = err.Code
62+
set desc = err.Desc
6263
set jsonError = code_": "_desc
6364
do jsonArr.%Push(jsonError)
6465
}

src/DataPipe/Helper.cls

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
Class DataPipe.Helper Extends %RegisteredObject
2+
{
3+
4+
ClassMethod SetPythonPath(pClasspaths)
5+
{
6+
set sys = ##class(%SYS.Python).Import("sys")
7+
do sys.path.append(pClasspaths)
8+
}
9+
10+
ClassMethod GetPythonInstance(
11+
pModule,
12+
pRemoteClassname) As %SYS.Python
13+
{
14+
set importlib = ##class(%SYS.Python).Import("importlib")
15+
set builtins = ##class(%SYS.Python).Import("builtins")
16+
set module = importlib."import_module"(pModule)
17+
set class = builtins.getattr(module, pRemoteClassname)
18+
return class."__new__"(class)
19+
}
20+
21+
}

src/DataPipe/Model.cls

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ Method Serialize(Output stream) As %Status
66
Quit $$$ERROR($$$NotImplemented)
77
}
88

9-
ClassMethod Deserialize(stream As %Stream.Object, Output obj) As %Status
9+
ClassMethod Deserialize(
10+
stream As %Stream.Object,
11+
Output obj) As %Status
1012
{
1113
Quit $$$ERROR($$$NotImplemented)
1214
}
@@ -26,7 +28,10 @@ Method GetOperation() As %Status
2628
Quit $$$ERROR($$$NotImplemented)
2729
}
2830

29-
Method RunOperation(Output errorList As %List, Output log As %Stream.Object, bOperation As Ens.BusinessOperation = "") As %Status
31+
Method RunOperation(
32+
Output errorList As %List,
33+
Output log As %Stream.Object,
34+
bOperation As Ens.BusinessOperation = "") As %Status
3035
{
3136
Quit $$$ERROR($$$NotImplemented)
3237
}

src/DataPipe/Oper/BO/OperationHandler.cls

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ Parameter ADAPTER;
55

66
Parameter INVOCATION = "Queue";
77

8-
Method OperationHandler(pRequest As DataPipe.Msg.OperReq, Output pResponse As Ens.Response) As %Status
8+
Method OperationHandler(
9+
pRequest As DataPipe.Msg.OperReq,
10+
Output pResponse As Ens.Response) As %Status
911
{
1012
set ret = $$$OK
1113
try {
@@ -19,10 +21,20 @@ Method OperationHandler(pRequest As DataPipe.Msg.OperReq, Output pResponse As En
1921
$$$ThrowOnError(operObj.%Save())
2022

2123
// deserialize model
22-
set modelData = pRequest.data.ModelNormData
23-
set modelName = pRequest.data.Ingestion.ModelName
24-
set sc = $classmethod(modelName, "Deserialize", modelData, .modelObj)
25-
$$$ThrowOnError(sc)
24+
if pRequest.data.Ingestion.ModelIsPython {
25+
// Set python path
26+
do ##class(DataPipe.Helper).SetPythonPath(pRequest.data.Ingestion.ModelClassPath)
27+
set modelObj = ##class(DataPipe.Helper).GetPythonInstance(pRequest.data.Ingestion.ModelModule, pRequest.data.Ingestion.ModelName)
28+
do modelObj.Deserialize(pRequest.data.ModelNormData)
29+
set errorList = ""
30+
set operLog = ""
31+
32+
} else {
33+
set modelData = pRequest.data.ModelNormData
34+
set modelName = pRequest.data.Ingestion.ModelName
35+
set sc = $classmethod(modelName, "Deserialize", modelData, .modelObj)
36+
$$$ThrowOnError(sc)
37+
}
2638

2739
set operLog = ""
2840
set operationSC = $$$OK
@@ -34,15 +46,37 @@ Method OperationHandler(pRequest As DataPipe.Msg.OperReq, Output pResponse As En
3446

3547
// not ignored - perfom operation
3648
} else {
37-
set operationSC = modelObj.RunOperation(.errorList, .operLog, ##this)
49+
if pRequest.data.Ingestion.ModelIsPython {
50+
// Python doesn't support byref parameters, so we return error list and oper log as strings
51+
try {
52+
set operationSC = $$$OK
53+
set pythonList = modelObj.RunOperation(##this)
54+
set errorList = ##class(%ListOfDataTypes).%New()
55+
set operLog = ##class(%Stream.GlobalCharacter).%New()
56+
for i=1:1:pythonList.Count() {
57+
set err = pythonList.GetAt(i)
58+
if err.Code = "OPERATION"
59+
{
60+
set operationSC = $$$ERROR($$$GeneralError, err.Desc)
61+
} if err.Code = "OPERLOG" {
62+
do operLog.WriteLine(err.Desc)
63+
} else {
64+
do errorList.Insert(err)
65+
}
66+
}
67+
} catch ex {
68+
set operationSC = ex.AsStatus()
69+
}
70+
} else {
71+
set operationSC = modelObj.RunOperation(.errorList, .operLog, ##this)
72+
}
3873
set operObj.Retries = operObj.Retries + 1
3974
if $$$ISOK(operationSC) {
4075
set operObj.Status = "PROCESSED"
4176
set inboxObj.Status = "DONE"
4277
} else {
4378
set operObj.Status = "ERROR"
4479
set inboxObj.Status = "ERROR-OPERATING"
45-
4680
// serialize errors
4781
set operObj.OperErrors = errorList
4882
$$$ThrowOnError(operObj.SerializeOperErrors())

src/DataPipe/Oper/BP/OperManagerContext.cls

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,19 @@ Method DeserializeModel() As %Status
4141
{
4242
set ret = $$$OK
4343
try {
44-
set modelData = ..Oper.Staging.ModelNormData
45-
set modelName = ..Oper.Staging.Ingestion.ModelName
46-
set sc = $classmethod(modelName, "Deserialize", modelData, .obj)
47-
$$$ThrowOnError(sc)
48-
set ..Model = obj
44+
if ..Oper.Staging.Ingestion.ModelIsPython {
45+
// Set python path
46+
do ##class(DataPipe.Helper).SetPythonPath(..Oper.Staging.Ingestion.ModelClassPath)
47+
set ..Model = ##class(DataPipe.Helper).GetPythonInstance(..Oper.Staging.Ingestion.ModelModule, ..Oper.Staging.Ingestion.ModelName)
48+
do ..Model.Deserialize(..Oper.Staging.ModelNormData)
49+
50+
} else {
51+
set modelData = ..Oper.Staging.ModelNormData
52+
set modelName = ..Oper.Staging.Ingestion.ModelName
53+
set sc = $classmethod(modelName, "Deserialize", modelData, .obj)
54+
$$$ThrowOnError(sc)
55+
set ..Model = obj
56+
}
4957

5058
} catch ex {
5159
set ret = ex.AsStatus()

src/DataPipe/Staging/BP/StagingManager.cls

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ Property TargetConfigName As %String;
66

77
Parameter SETTINGS = "TargetConfigName:Basic:selector?context={Ens.ContextSearch/ProductionItems?targets=1&productionName=@productionId}";
88

9-
ClassMethod OnGetConnections(ByRef pArray As %String, pItem As Ens.Config.Item)
9+
ClassMethod OnGetConnections(
10+
ByRef pArray As %String,
11+
pItem As Ens.Config.Item)
1012
{
1113
Do pItem.PopulateModifiedSettings()
1214
Set (tValue,tIndex)="" For {

0 commit comments

Comments
 (0)