unitypredict-engines 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,110 @@
1
+ from enum import Enum
2
+ import json
3
+ import attr
4
+
5
+ class EngineResults:
6
+ OutcomeValues: dict = {}
7
+ Outcomes: dict = {}
8
+
9
+ def __init__(self):
10
+ self.OutcomeValues = dict()
11
+ self.Outcomes = dict()
12
+
13
+ def toJSON(self):
14
+ return json.dumps(self, default=lambda o: o.__dict__, sort_keys=True, indent=4)
15
+
16
+ class OutcomePrediction:
17
+ Probability = 0.0
18
+ Value = None
19
+
20
+ def __init__(self):
21
+ self.Probability = 0.0
22
+ self.Value = None
23
+
24
+ class DataTypes(str, Enum):
25
+ Boolean = 'Boolean'
26
+ Integer = 'Integer'
27
+ Float = 'Float'
28
+ String = 'String'
29
+ File = 'File'
30
+ Tensor = 'Tensor'
31
+
32
+ class InputInfo:
33
+ Name: str = ''
34
+ InputType: DataTypes = DataTypes.Integer
35
+
36
+ class OutcomeInfo:
37
+ Name: str = ''
38
+ OutcomeType: DataTypes = DataTypes.Integer
39
+
40
+ @attr.s(auto_attribs=True)
41
+ class BasePredictEngineConfig:
42
+ # Inputs: list[InputInfo] = None
43
+ # Outcomes: list[OutcomeInfo] = None
44
+ Inputs: list
45
+ Outcomes: list
46
+
47
+ @attr.s(auto_attribs=True)
48
+ class InferenceContext:
49
+ ContextId: str = ''
50
+ StoredMeta: dict = {}
51
+
52
+ def toJSON(self):
53
+ return json.dumps(self, default=lambda o: o.__dict__, sort_keys=True, indent=4)
54
+
55
+ @attr.s(auto_attribs=True)
56
+ class EngineInputs:
57
+ InputValues: dict
58
+ DesiredOutcomes: list
59
+
60
+ # Note: these models will be different for every engine type
61
+ ###############################################################################################################
62
+ @attr.s(auto_attribs=True)
63
+ class AppEngineInferenceOptions:
64
+ pass
65
+
66
+ @attr.s(auto_attribs=True)
67
+ class AIEngineConfiguration (BasePredictEngineConfig):
68
+ InferenceOptions: AppEngineInferenceOptions = None
69
+
70
+ @attr.s(auto_attribs=True)
71
+ class AppEngineRequest:
72
+ RequestId: str = ''
73
+ EngineId: str = ''
74
+ RequestInputFiles: bool = False
75
+ RequestOutputFiles: bool = False
76
+ RequestFilesFolderPath: str = False
77
+ PackagesFolderPath: str = ''
78
+ PackagesFolderPath: str = ''
79
+ SourcesFolderPath: str = ''
80
+ ModelFilesFolderPath: str = ''
81
+ EngineApiKey: str = ''
82
+ PredictEndpoint: str = ''
83
+ Context: InferenceContext = None
84
+ CallbackQueue: str = ''
85
+ EngineInputData: EngineInputs = None
86
+ EngineConfig: AIEngineConfiguration = None
87
+
88
+ def toJSON(self):
89
+ return json.dumps(self, default=lambda o: o.__dict__, sort_keys=True, indent=4)
90
+
91
+ ###############################################################################################################
92
+
93
+ class UnityPredictEngineResponse:
94
+ RequestId: str = ''
95
+ ErrorMessages: str = ''
96
+ LogMessages: str = ''
97
+ AdditionalInferenceCosts: float = 0.0
98
+ EngineOutputs: EngineResults = None
99
+ Context: InferenceContext = None
100
+
101
+ def __init__(self):
102
+ self.RequestId = ''
103
+ self.ErrorMessages = ''
104
+ self.LogMessages = ''
105
+ self.AdditionalInferenceCosts = 0.0
106
+ self.EngineOutputs = None
107
+ self.Context = None
108
+
109
+ def toJSON(self):
110
+ return json.dumps(self, default=lambda o: o.__dict__, sort_keys=True, indent=4)
@@ -0,0 +1,109 @@
1
+ from abc import ABCMeta, abstractmethod
2
+ from io import BufferedReader, IOBase
3
+
4
+ class OutcomeValue:
5
+ Probability = 0.0
6
+ Value = None
7
+
8
+ def __init__(self, value: any = '', probability: float = 0.0):
9
+ self.Probability = probability
10
+ self.Value = value
11
+
12
+ class InferenceContextData:
13
+ StoredMeta: dict = {}
14
+
15
+ def __init__(self):
16
+ self.StoredMeta = {}
17
+
18
+ class InferenceRequest:
19
+ InputValues: dict
20
+ DesiredOutcomes: list
21
+ Context: InferenceContextData = None
22
+ def __init__(self):
23
+ self.Context = InferenceContextData()
24
+ self.InputValues = {}
25
+ self.DesiredOutcomes = []
26
+
27
+ class InferenceResponse:
28
+ ErrorMessages: str = ''
29
+ AdditionalInferenceCosts: float = 0.0
30
+ Context: InferenceContextData = None
31
+ OutcomeValues: dict = {}
32
+ Outcomes: dict = {}
33
+
34
+ def __init__(self):
35
+ self.ErrorMessages = ''
36
+ self.AdditionalInferenceCosts = 0.0
37
+ self.Context = InferenceContextData()
38
+ self.OutcomeValues = {}
39
+ self.Outcomes = {}
40
+
41
+ class ChainedInferenceRequest:
42
+ ContextId: str = ''
43
+ InputValues: dict
44
+ DesiredOutcomes: list
45
+
46
+ def __init__(self):
47
+ self.ContextId = ''
48
+ self.InputValues = {}
49
+ self.DesiredOutcomes = []
50
+
51
+
52
+ class ChainedInferenceResponse:
53
+ ContextId: str = ''
54
+ RequestId: str = ''
55
+ ErrorMessages: str = ''
56
+ ComputeCost: float = 0.0
57
+ OutcomeValues: dict = {}
58
+ Outcomes: dict = {}
59
+
60
+ def __init__(self):
61
+ self.ContextId = ''
62
+ self.RequestId = ''
63
+ self.ErrorMessages = ''
64
+ self.ComputeCost = 0.0
65
+ self.OutcomeValues = {}
66
+ self.Outcomes = {}
67
+
68
+ class FileTransmissionObj:
69
+ FileName: str = ''
70
+ FileHandle: IOBase = None
71
+
72
+ def __init__(self, fileName, fileHandle):
73
+ self.FileName = fileName
74
+ self.FileHandle = fileHandle
75
+
76
+ class FileReceivedObj:
77
+ FileName: str = ''
78
+ LocalFilePath: str = ''
79
+
80
+ def __init__(self, fileName, localFilePath):
81
+ self.FileName = fileName
82
+ self.LocalFilePath = localFilePath
83
+
84
+ class IPlatform:
85
+ __metaclass__ = ABCMeta
86
+
87
+ @classmethod
88
+ def version(self): return "1.0"
89
+
90
+ @abstractmethod
91
+ def getModelsFolderPath(self) -> str: raise NotImplementedError
92
+
93
+ @abstractmethod
94
+ def getModelFile(self, modelFileName: str, mode: str = 'rb') -> IOBase: raise NotImplementedError
95
+
96
+ @abstractmethod
97
+ def getRequestFile(self, modelFileName: str, mode: str = 'rb') -> IOBase: raise NotImplementedError
98
+
99
+ @abstractmethod
100
+ def saveRequestFile(self, modelFileName: str, mode: str = 'wb') -> IOBase: raise NotImplementedError
101
+
102
+ @abstractmethod
103
+ def getLocalTempFolderPath(self) -> str: raise NotImplementedError
104
+
105
+ @abstractmethod
106
+ def logMsg(self, msg: str): raise NotImplementedError
107
+
108
+ @abstractmethod
109
+ def invokeUnityPredictModel(self, modelId: str, request: ChainedInferenceRequest) -> ChainedInferenceResponse: raise NotImplementedError
@@ -0,0 +1,460 @@
1
+ import importlib
2
+ from io import BufferedReader, IOBase, StringIO
3
+
4
+ import json
5
+ import os, sys
6
+ import shutil
7
+ import datetime
8
+ import uuid
9
+ import filecmp
10
+ import attr
11
+ import cattr
12
+
13
+ import requests
14
+ from .Models import AppEngineRequest, EngineResults, InferenceContext, UnityPredictEngineResponse
15
+ from .Platform import ChainedInferenceRequest, ChainedInferenceResponse, FileReceivedObj, FileTransmissionObj, IPlatform, InferenceRequest,InferenceResponse, InferenceContextData
16
+
17
+ @attr.s(auto_attribs=True)
18
+ class AppEngineConfig:
19
+ TempDirPath: str = ''
20
+ ModelsDirPath: str = ''
21
+ RequestFilesDirPath: str = ''
22
+
23
+ # TEMP_EXEC_DIR: str = os.getcwd()
24
+ UPT_API_KEY: str = ""
25
+ # MODEL_DIR: str = os.path.join(os.getcwd(), "models")
26
+ # REQUEST_DIR: str = os.path.join(os.getcwd(), "requests")
27
+ SAVE_CONTEXT: bool = True
28
+
29
+ def toJSON(self):
30
+ return json.dumps(self, default=lambda o: o.__dict__, sort_keys=True, indent=4)
31
+
32
+ class UnityPredictHost(IPlatform):
33
+
34
+ Initialized = False
35
+ isConfigInit = False
36
+ LoadedEngineId = None
37
+ CurrentRequest: AppEngineRequest = None
38
+ logMsgs: str = ''
39
+
40
+ configFile = os.path.join(os.getcwd(), "config.json")
41
+
42
+ appEngineConfig: AppEngineConfig = AppEngineConfig()
43
+
44
+ # execTempDir = "execTmp"
45
+ # execRequestFolder: str = "requests"
46
+ # execModelFolder: str = "models"
47
+ # execOutputFolder: str = "outputs"
48
+
49
+ def __init__(self) -> None:
50
+
51
+ config_dict : dict = {}
52
+ if not os.path.exists(self.configFile):
53
+
54
+ print ("Config file not detected, creating templated config file: {}".format(self.configFile))
55
+
56
+ # self.appEngineConfig.TEMP_EXEC_DIR = os.getcwd()
57
+ self.appEngineConfig = AppEngineConfig()
58
+ self.appEngineConfig.ModelsDirPath = os.path.join(os.getcwd(), "unitypredict_mocktool", "models")
59
+ self.appEngineConfig.TempDirPath = os.path.join(os.getcwd(), "unitypredict_mocktool", "tmp")
60
+ self.appEngineConfig.RequestFilesDirPath = os.path.join(os.getcwd(), "unitypredict_mocktool", "requests")
61
+
62
+ with open(self.configFile, "w+") as confFile:
63
+ confFile.write(self.appEngineConfig.toJSON())
64
+
65
+ print ("Config file detected, loading data from: {}".format(self.configFile))
66
+ with open (self.configFile, "r+") as confFile:
67
+ config_dict = json.load(confFile)
68
+
69
+ self.appEngineConfig = cattr.structure(config_dict, AppEngineConfig)
70
+
71
+ if not os.path.exists(self.appEngineConfig.ModelsDirPath):
72
+ os.makedirs(self.appEngineConfig.ModelsDirPath)
73
+
74
+ if not os.path.exists(self.appEngineConfig.TempDirPath):
75
+ os.makedirs(self.appEngineConfig.TempDirPath)
76
+
77
+ if not os.path.exists(self.appEngineConfig.RequestFilesDirPath):
78
+ os.makedirs(self.appEngineConfig.RequestFilesDirPath)
79
+
80
+ self.CurrentRequest = AppEngineRequest()
81
+
82
+ # self.workingDir = os.path.join(self.appEngineConfig.TEMP_EXEC_DIR, self.execTempDir)
83
+ self.CurrentRequest.EngineApiKey = self.appEngineConfig.UPT_API_KEY
84
+
85
+
86
+ # self.CurrentRequest.ModelFilesFolderPath = os.path.join(self.workingDir, self.execModelFolder)
87
+ # self.CurrentRequest.RequestFilesFolderPath = os.path.join(self.workingDir, self.execRequestFolder)
88
+
89
+
90
+ self.isConfigInit = True
91
+
92
+ return None
93
+
94
+ def isConfigInitialized(self) -> bool:
95
+
96
+ return self.isConfigInit
97
+
98
+ def isPlatformInitialized(self) -> bool:
99
+
100
+ return self.Initialized
101
+
102
+
103
+ def getModelsFolderPath(self) -> str:
104
+ return self.appEngineConfig.ModelsDirPath
105
+
106
+ def getModelFile(self, modelFileName: str, mode: str = 'rb') -> IOBase:
107
+ absFilePath = os.path.join(self.getModelsFolderPath(), modelFileName)
108
+ fileHandler = open(absFilePath, mode)
109
+
110
+ return fileHandler
111
+
112
+
113
+ def getLocalTempFolderPath(self) -> str:
114
+ return self.appEngineConfig.TempDirPath
115
+
116
+ def getRequestFolderPath(self) -> str:
117
+ return self.appEngineConfig.RequestFilesDirPath
118
+
119
+
120
+ def getRequestFile(self, requestFileName: str, mode: str = 'rb') -> IOBase:
121
+ absFilePath = os.path.join(self.getRequestFolderPath(), requestFileName)
122
+ fileHandler = open(absFilePath, mode)
123
+
124
+ return fileHandler
125
+
126
+ def saveRequestFile(self, requestFileName: str, mode: str = 'wb') -> IOBase:
127
+ absFilePath = os.path.join(self.getRequestFolderPath(), requestFileName)
128
+ fileHandler = open(absFilePath, mode)
129
+
130
+ return fileHandler
131
+
132
+
133
+ def logMsg(self, msg: str):
134
+ print("\n{}\n".format(msg))
135
+ self.logMsgs += "\n{}\n".format(msg)
136
+
137
+ def errorMsg(self, msg: str):
138
+ self.errorMsgs += "\n{}\n".format(msg)
139
+
140
+
141
+ # def syncDirectories(self, source: str, destination: str, create_dest_if_not_present: bool = False):
142
+
143
+ # if not os.path.exists(source) or not os.path.isdir(source):
144
+ # print(f"Source directory '{source}' does not exist or is not a directory.")
145
+ # return
146
+
147
+ # if create_dest_if_not_present:
148
+ # if not os.path.exists(destination):
149
+ # os.makedirs(destination)
150
+ # else:
151
+ # if not os.path.exists(destination) or not os.path.isdir(source):
152
+ # print(f"Destination directory '{destination}' does not exist or is not a directory.")
153
+ # return
154
+
155
+ # # Copy or update files from source to destination
156
+ # for src_dir, _, files in os.walk(source):
157
+ # dst_dir = src_dir.replace(source, destination, 1)
158
+ # if not os.path.exists(dst_dir):
159
+ # os.makedirs(dst_dir)
160
+
161
+ # for file in files:
162
+ # src_file = os.path.join(src_dir, file)
163
+ # dst_file = os.path.join(dst_dir, file)
164
+
165
+ # if not os.path.exists(dst_file) or not filecmp.cmp(src_file, dst_file, shallow=False):
166
+ # shutil.copy2(src_file, dst_file) # Preserve metadata with copy2
167
+ # print(f"Copied: {src_file} to {dst_file}")
168
+ # else:
169
+ # print(f"Skipped: {src_file} is already up to date.")
170
+
171
+ # # Remove files and directories from destination that are not in source
172
+ # for dst_dir, _, files in os.walk(destination, topdown=False):
173
+ # src_dir = dst_dir.replace(destination, source, 1)
174
+
175
+ # if not os.path.exists(src_dir):
176
+ # shutil.rmtree(dst_dir)
177
+ # print(f"Removed directory: {dst_dir}")
178
+ # else:
179
+ # for file in files:
180
+ # dst_file = os.path.join(dst_dir, file)
181
+ # src_file = os.path.join(src_dir, file)
182
+
183
+ # if not os.path.exists(src_file):
184
+ # os.remove(dst_file)
185
+ # print(f"Removed file: {dst_file}")
186
+
187
+ # def createPlatform(self, request: AppEngineRequest) -> bool:
188
+ # if not self.isConfigInit:
189
+ # return False
190
+
191
+ # # Creating output folder based on RequestID
192
+ # if (request.RequestId == ""):
193
+
194
+ # self.errorMsg("Please provide a proper request id\n")
195
+
196
+ # return False
197
+
198
+ # # Create the folders
199
+ # if not os.path.exists(self.workingDir):
200
+ # os.mkdir(self.workingDir)
201
+
202
+ # if not os.path.exists(self.CurrentRequest.ModelFilesFolderPath):
203
+ # os.mkdir(self.CurrentRequest.ModelFilesFolderPath)
204
+
205
+ # # Create request folder having the name of the current Timestamp
206
+ # currentExecTime = datetime.datetime.now()
207
+ # currentExecTime = currentExecTime.strftime("%d-%m-%YT%H-%M-%S")
208
+
209
+ # self.execRequestFolder = "{}_{}__{}".format(self.execRequestFolder, currentExecTime, request.RequestId)
210
+ # self.CurrentRequest.RequestFilesFolderPath = os.path.join(self.workingDir, self.execRequestFolder)
211
+
212
+ # if not os.path.exists(self.CurrentRequest.RequestFilesFolderPath):
213
+ # os.mkdir(self.CurrentRequest.RequestFilesFolderPath)
214
+
215
+
216
+ # # Create output folder based on the request Id
217
+ # self.execOutputFolder = "{}_{}__{}".format(self.execOutputFolder, currentExecTime, request.RequestId)
218
+ # self.execOutputFolder = os.path.join(self.workingDir, self.execOutputFolder)
219
+ # if not os.path.exists(self.execOutputFolder):
220
+ # os.mkdir(self.execOutputFolder)
221
+
222
+
223
+ # # TODO: Copy user configured folder contents to respective exec folders
224
+ # # Sync Request Files
225
+ # self.syncDirectories(self.appEngineConfig.REQUEST_DIR, self.CurrentRequest.RequestFilesFolderPath)
226
+ # # Sync Model Files
227
+ # self.syncDirectories(self.appEngineConfig.MODEL_DIR, self.CurrentRequest.ModelFilesFolderPath)
228
+
229
+ # self.Initialized = True
230
+
231
+ # return True
232
+
233
+ def run_engine(self, request: AppEngineRequest) -> UnityPredictEngineResponse:
234
+
235
+ self.logMsgs: str = ''
236
+ self.MaxlogMsgBuffer: int = 3000
237
+ self.NegMaxlogMsgBuffer: int = -1 * self.MaxlogMsgBuffer
238
+
239
+ self.errorMsgs: str = ''
240
+ self.MaxErrorMsgsBuffer: int = 3000
241
+ self.NegMaxErrorMsgsBuffer: int = -1 * self.MaxErrorMsgsBuffer
242
+
243
+ toreturn: UnityPredictEngineResponse = UnityPredictEngineResponse()
244
+ try:
245
+
246
+ # # Create the platform for execution
247
+ # # 1) Prepare temp folders on local directories for A) Request Files B) Model Files C) Temp Files
248
+ # # 2) Put the files into the right folders
249
+ # if not self.createPlatform(request=request):
250
+
251
+ # self.errorMsg("Unable to create the platform")
252
+ # toreturn.ErrorMessages = self.errorMsgs[self.NegMaxErrorMsgsBuffer:] # limit length of the logs
253
+ # toreturn.LogMessages = self.logMsgs[self.NegMaxlogMsgBuffer:] # limit length of the logs
254
+
255
+ # return toreturn
256
+
257
+
258
+ # 3) Store/Restore Context (probably using some local json file) if requested by user
259
+ # Initialize context
260
+
261
+ if (self.appEngineConfig.SAVE_CONTEXT):
262
+
263
+ contextJson = os.path.join(self.getLocalTempFolderPath(), "context.json")
264
+
265
+ context = {}
266
+ if os.path.exists(contextJson):
267
+ with open(contextJson, "r+") as ctxtf:
268
+ context = json.load(ctxtf)
269
+
270
+ request.Context = InferenceContext(**context)
271
+
272
+ # 4) Convert the AppEngineRequest to the InferenceRequest that the model needs
273
+
274
+ inferReq: InferenceRequest = InferenceRequest()
275
+ inferReq.InputValues = request.EngineInputData.InputValues
276
+ inferReq.DesiredOutcomes = request.EngineInputData.DesiredOutcomes
277
+
278
+ inferReq.Context = InferenceContextData()
279
+
280
+ if (request.Context != None):
281
+ inferReq.Context.StoredMeta = request.Context.StoredMeta
282
+ else:
283
+ inferReq.Context.StoredMeta = {}
284
+
285
+
286
+ # 5) Run EntryPoint.py
287
+ entryPoint = importlib.import_module("EntryPoint")
288
+ inferResp: InferenceResponse = entryPoint.run_local_engine(inferReq, self)
289
+
290
+ # 6) Copy InferenceResponse to UnityPredictEngineResponse
291
+
292
+ toreturn.AdditionalInferenceCosts = inferResp.AdditionalInferenceCosts
293
+ toreturn.EngineOutputs = EngineResults()
294
+
295
+ if (inferResp.Outcomes != None):
296
+ toreturn.EngineOutputs.Outcomes = inferResp.Outcomes
297
+ else:
298
+ toreturn.EngineOutputs.Outcomes = {}
299
+
300
+ if (inferResp.OutcomeValues != None):
301
+ toreturn.EngineOutputs.OutcomeValues = inferResp.OutcomeValues
302
+ else:
303
+ toreturn.EngineOutputs.OutcomeValues = {}
304
+
305
+
306
+ toreturn.EngineOutputs.OutcomeValues = inferResp.OutcomeValues
307
+ toreturn.ErrorMessages = inferResp.ErrorMessages
308
+
309
+ if (self.appEngineConfig.SAVE_CONTEXT):
310
+
311
+ toreturn.Context = InferenceContext(ContextId="")
312
+ if (request.Context != None):
313
+ toreturn.Context.ContextId = request.Context.ContextId
314
+ if (inferResp.Context == None or inferResp.Context.StoredMeta == None or inferResp.Context.StoredMeta == {}):
315
+
316
+ if (request.Context != None):
317
+ toreturn.Context.StoredMeta = inferReq.Context.StoredMeta
318
+ else:
319
+ toreturn.Context.StoredMeta = {}
320
+
321
+ else:
322
+ toreturn.Context.StoredMeta = inferResp.Context.StoredMeta
323
+
324
+ if toreturn.Context.ContextId == "":
325
+ toreturn.Context.ContextId = str(uuid.uuid4())
326
+
327
+
328
+ with open(contextJson, "w+") as ctxtf:
329
+ ctxtf.write(toreturn.Context.toJSON())
330
+
331
+
332
+ toreturn.LogMessages = self.logMsgs[self.NegMaxlogMsgBuffer:] # limit length of the logs
333
+
334
+ except Exception as e:
335
+
336
+ print ("Error occured: {}".format(str(e)))
337
+ self.errorMsg(str(e))
338
+ toreturn.ErrorMessages += self.errorMsgs[self.NegMaxErrorMsgsBuffer:] # limit length of the logs
339
+ toreturn.LogMessages = self.logMsgs[self.NegMaxlogMsgBuffer:] # limit length of the logs
340
+
341
+ return toreturn
342
+
343
+
344
+ def invokeUnityPredictModel(self, modelId: str, request: ChainedInferenceRequest) -> ChainedInferenceResponse:
345
+ results = ChainedInferenceResponse()
346
+
347
+ apiKey = self.CurrentRequest.EngineApiKey
348
+ apiBaseUrl = "https://api.prod.unitypredict.com/api/predict"
349
+
350
+ needFileUpload: bool = False
351
+
352
+ #####
353
+ # The request can contain file objects so we need to change those to file names before sending out
354
+ #####
355
+ # first get a list of file that we'll need to upload later & update the POST obj
356
+ filesToUpload = {}
357
+ for xvarName in request.InputValues:
358
+ if isinstance(request.InputValues.get(xvarName), FileTransmissionObj):
359
+ needFileUpload = True
360
+ break
361
+
362
+ finalResponseJson: any = ''
363
+
364
+ response: requests.Response = None
365
+ if not needFileUpload:
366
+ # serialize the POST obj
367
+ jsonBody = json.dumps(request, default=vars)
368
+
369
+ # there are no files to upload so just post normally
370
+ response = requests.post(url = "{}/{}".format(apiBaseUrl, modelId), data=jsonBody, headers={"Authorization": "Bearer APIKEY@{}".format(apiKey)})
371
+
372
+ if response.status_code != 200:
373
+ results.ErrorMessages = 'Error from server: {}'.format(response.status_code)
374
+ return results
375
+
376
+ finalResponseJson = response.json()
377
+ else:
378
+ # we need to initialize first
379
+ response = requests.post(url = "{}/initialize/{}".format(apiBaseUrl, modelId), data="", headers={"Authorization": "Bearer APIKEY@{}".format(apiKey)})
380
+
381
+ requestId: str = response.json().get('requestId')
382
+
383
+ if response.status_code != 200:
384
+ results.ErrorMessages = 'Error from server: {}'.format(response.status_code)
385
+ return results
386
+
387
+ # upload the files
388
+ for xvarName in request.InputValues:
389
+ if isinstance(request.InputValues.get(xvarName), FileTransmissionObj):
390
+ fileToUpload: FileTransmissionObj = request.InputValues.get(xvarName)
391
+ response = requests.get(url = "{}/upload/{}/{}".format(apiBaseUrl, requestId, fileToUpload.FileName), headers={"Authorization": "Bearer APIKEY@{}".format(apiKey)})
392
+ uploadLink = response.json().get('uploadLink')
393
+ fileName = response.json().get('fileName')
394
+ request.InputValues[xvarName] = fileName # make sure that only the filename is in the request that we are going to POST
395
+ requests.put(url = uploadLink, data=fileToUpload.FileHandle)
396
+
397
+ jsonBody = json.dumps(request, default=vars)
398
+
399
+ response = requests.post(url = "{}/{}/{}".format(apiBaseUrl, modelId, requestId), data=jsonBody, headers={"Authorization": "Bearer APIKEY@{}".format(apiKey)})
400
+
401
+ finalResponseJson = response.json()
402
+
403
+
404
+ print(finalResponseJson)
405
+
406
+ while (finalResponseJson.get('status') == 'Processing'):
407
+ statusUrl: str = finalResponseJson.get('statusUrl')
408
+
409
+ response = requests.get(url = statusUrl, headers={"Authorization": "Bearer APIKEY@{}".format(apiKey)})
410
+ finalResponseJson = response.json()
411
+
412
+
413
+ finalResponseRequestId: str = finalResponseJson.get('requestId')
414
+
415
+ tempOutputFolder: str = os.path.join(self.getLocalTempFolderPath(), "chainedResults", finalResponseRequestId)
416
+ if not os.path.exists(tempOutputFolder):
417
+ os.makedirs(tempOutputFolder)
418
+
419
+ outcomeValues = finalResponseJson.get('outcomeValues')
420
+ for outputVarName in outcomeValues:
421
+ outcome: dict = outcomeValues.get(outputVarName)
422
+ if outcome.get('dataType') == 'File':
423
+ fileName = outcome.get('value')
424
+
425
+ tempFilePath = os.path.join(tempOutputFolder, fileName)
426
+ response = requests.get(url = "{}/download/{}/{}".format(apiBaseUrl, finalResponseRequestId, fileName), headers={"Authorization": "Bearer APIKEY@{}".format(apiKey)})
427
+ with open(tempFilePath, 'wb') as f:
428
+ f.write(response.content)
429
+
430
+ fileReceived: FileReceivedObj = FileReceivedObj(fileName, tempFilePath)
431
+ outcome['value'] = fileReceived
432
+
433
+ outcomes = finalResponseJson.get('outcomes')
434
+ for outputVarName in outcomes:
435
+ outcome: list = outcomes.get(outputVarName)
436
+ for outcomeItem in outcome:
437
+ if outcomeItem.get('dataType') == 'File':
438
+ fileName = outcomeItem.get('value')
439
+
440
+ tempFilePath = os.path.join(tempOutputFolder, fileName)
441
+ response = requests.get(url = "{}/download/{}/{}".format(apiBaseUrl, finalResponseRequestId, fileName), headers={"Authorization": "Bearer APIKEY@{}".format(apiKey)})
442
+ with open(tempFilePath, 'wb') as f:
443
+ f.write(response.content)
444
+
445
+ fileReceived: FileReceivedObj = FileReceivedObj(fileName, tempFilePath)
446
+ outcomeItem['value'] = fileReceived
447
+
448
+
449
+ try:
450
+ results.ComputeCost = finalResponseJson.get('computeCost')
451
+ results.OutcomeValues = outcomeValues
452
+ results.Outcomes = outcomes
453
+ results.RequestId = finalResponseRequestId
454
+ results.ContextId = finalResponseJson.get('contextId')
455
+ results.ErrorMessages = finalResponseJson.get('errorMessages')
456
+
457
+ except Exception as e:
458
+ print(e)
459
+
460
+ return results
@@ -0,0 +1 @@
1
+ from .UnityPredictLocalHost import UnityPredictHost
@@ -0,0 +1,5 @@
1
+ import sys
2
+
3
+ def main():
4
+ cmd = sys.argv[1:]
5
+ print(f'Command = {cmd}')
@@ -0,0 +1,194 @@
1
+ Metadata-Version: 2.1
2
+ Name: unitypredict-engines
3
+ Version: 0.0.1
4
+ Description-Content-Type: text/markdown
5
+
6
+ # UnityPredict Local App Engine Creator
7
+
8
+ ## Introduction
9
+
10
+ The `UnityPredict` python sdk is designed to help accelerate the testing and debugging of **App Engines** for deployment on the `UnityPredict` platform.
11
+
12
+ On `UnityPredict`, **"Engines"** are the underlying compute framework that is executed, at scale, to perform inference or run business logic. In contrast, **"Models"** define the interface for these Engines. **Every Engine must be connected to a "Model"** because the Model serves as the interface that defines how `UnityPredict` communicates with the Engine. The Model specifies variable names and data types for inputs and outputs. Additionally, `UnityPredict` uses the Model definition to auto-generate APIs and user interfaces.
13
+
14
+ **"App Engines"** are specialized extensions of `UnityPredict` Engines that allow developers to write custom Python code, which the platform will execute at scale. These custom-defined Engines offer developers the flexibility needed to create complex applications. Within an App Engine, developers can access various platform features through code. For instance, **App Engine code can easily invoke other models (aka. chaining) or define cost calculations**. App Engines also enable developers to choose specific hardware types for running their code.
15
+
16
+ This guide focuses on the local development and testing of custom App Engine code.
17
+
18
+ For a full guide on how to use App Engine(s) on UnityPredict, please visit our complete help documentation here [UnityPredict Docs](https://console.unitypredict.com).
19
+
20
+ ## Installation
21
+ * You can use pip to install the ```unitypredict``` library.
22
+ ```bash
23
+ pip install unitypredict
24
+ ```
25
+
26
+ ## Usage
27
+
28
+ ### EntryPoint.py
29
+
30
+ #### What is `EntryPoint.py`
31
+
32
+ `EntryPoint.py` is the script created by the **user** to provide the platform a well-defined function that can be invoked as the **starting point** for the **inference code**. It also acts as a gateway to access the **"platform"** object containing various features provided by UnityPredict
33
+
34
+ ### Create an example `EntryPoint.py`
35
+
36
+ Create a custom entrypoint of your App Engine under the file **`EntryPoint.py`** containing the inference logic.
37
+
38
+ **NOTE:** The name of the file should be **`EntryPoint.py`**. If not followed, during the actual deployment on `UnityPredict` repository, the created file might not get invoked due to anincorrect name.
39
+
40
+ Given below is an example of a simple **`EntryPoint.py`**
41
+
42
+
43
+ ```python
44
+ from unitypredict.Platform import IPlatform, InferenceRequest, InferenceResponse, OutcomeValue, InferenceContextData
45
+ import datetime
46
+
47
+
48
+
49
+ def run_engine(request: InferenceRequest, platform: IPlatform) -> InferenceResponse:
50
+
51
+
52
+
53
+ platform.logMsg("Running User Code...")
54
+ response = InferenceResponse()
55
+
56
+ try:
57
+ prompt = request.InputValues['InputMessage']
58
+
59
+ currentExecTime = datetime.datetime.now()
60
+ currentExecTime = currentExecTime.strftime("%d-%m-%YT%H-%M-%S")
61
+ resp_message = "Echo message: {} Time:: {}".format(prompt, currentExecTime)
62
+
63
+ response.Outcomes['OutputMessage'] = [OutcomeValue(value=resp_message, probability=1.0)]
64
+
65
+ except Exception as e:
66
+ response.ErrorMessages = "Entrypoint Exception Occured: {}".format(str(e))
67
+
68
+ print("Finished Running User Code...")
69
+ return response
70
+
71
+ ```
72
+
73
+ ### Running the `EntryPoint.py`
74
+
75
+ In order to run the `EntryPoint.py` locally, add the following `main` section to the file itself.
76
+
77
+ ```python
78
+ #### For Local testing ###
79
+
80
+ if __name__ == "__main__":
81
+
82
+ from unitypredict import UnityPredictHost
83
+
84
+ # Initialize the UnityPredict Platform
85
+ platform = UnityPredictHost()
86
+
87
+ testRequest = InferenceRequest()
88
+ testRequest.InputValues = {}
89
+ testRequest.InputValues["InputMessage"] = "Hi, this is an echo message"
90
+ results: InferenceResponse = run_engine(testRequest, platform)
91
+
92
+ # Print Outputs
93
+ if (results.Outcomes != None):
94
+ for outcomKeys, outcomeValues in results.Outcomes.items():
95
+ print ("\n\nOutcome Key: {}".format(outcomKeys))
96
+ for values in outcomeValues:
97
+ infVal: OutcomeValue = values
98
+ print ("Outcome Value: \n{}\n\n".format(infVal.Value))
99
+ print ("Outcome Probability: \n{}\n\n".format(infVal.Probability))
100
+
101
+ # Print Error Messages (if any)
102
+ print ("Error Messages: {}".format(results.ErrorMessages))
103
+
104
+ ```
105
+ Now run the `EntryPoint.py`
106
+
107
+ ```bash
108
+ python EntryPoint.py
109
+ ```
110
+
111
+ ### Output
112
+
113
+ The output should be the message added as the `InputMessage` in the main, appended with the timestamp of the execution.
114
+
115
+ ```bash
116
+ Config file detected, loading data from: D:\Documents\SelfProjects\UPTAzure\unitypredict-sdks\mainFolder\config.json
117
+
118
+ Running User Code...
119
+
120
+ Finished Running User Code...
121
+
122
+
123
+ Outcome Key: OutputMessage
124
+ Outcome Value:
125
+ Echo message: Hi, this is an echo message Time:: 18-08-2024T18-56-33
126
+
127
+
128
+ Outcome Probability:
129
+ 1.0
130
+ ```
131
+
132
+
133
+ While running the script for the **first time**, you may also encounter a log message as follows.
134
+
135
+ ```bash
136
+ Config file not detected, creating templated config file: {project-current-folder}\config.json
137
+ ```
138
+ This marks the auto-creation of the config file template `config.json`, along with the tree-structure following the templated config of the project which is the **mock building block** for the APIs of `UnityPredict` used under `EntryPoint.py`.
139
+
140
+
141
+
142
+ ### Config File and the project tree structure
143
+
144
+ The templated `config.json` looks like this:
145
+ ```json
146
+ {
147
+ "ModelsDirPath": "{userPWD}/unitypredict_mocktool/models",
148
+ "RequestFilesDirPath": "{userPWD}/unitypredict_mocktool/requests",
149
+ "SAVE_CONTEXT": true,
150
+ "TempDirPath": "{userPWD}/unitypredict_mocktool/tmp",
151
+ "UPT_API_KEY": ""
152
+ }
153
+ ```
154
+
155
+ And the tree structure of the generated directory will be:
156
+ ```plaintext
157
+ {userPWD}/
158
+ ├── unitypredict_mocktool/
159
+ | ├── models/
160
+ | ├── requests/
161
+ | └── tmp/
162
+ └── EntryPoint.py
163
+ └── config.json
164
+ ```
165
+
166
+ Details of the JSON settings:
167
+
168
+ * **ModelsDirPath**: Local model files/binaries to be uploaded to AppEngine for using during the execution can be added under the specified **ModelsDirPath**
169
+
170
+ * **RequestFilesDirPath**: Files or Folders to be uploaded to AppEngine for using during the execution can be added under the specified **RequestFilesDirPath**
171
+
172
+ * **SAVE_CONTEXT**: Retains context across multiple requests. Disable it using ```"SAVE_CONTEXT" : false```
173
+
174
+ * **UPT_API_KEY**: API Key token generated from the UnityPredict profile of the user.
175
+
176
+ * **TempDirPath**: Temporary directory which can be used for writing or reading extra files or folders depending on the requirements of the App Engine
177
+
178
+
179
+
180
+
181
+ ## License
182
+ Copyright 2013 Mir Ikram Uddin
183
+
184
+ Licensed under the Apache License, Version 2.0 (the "License");
185
+ you may not use this file except in compliance with the License.
186
+ You may obtain a copy of the License at
187
+
188
+ http://www.apache.org/licenses/LICENSE-2.0
189
+
190
+ Unless required by applicable law or agreed to in writing, software
191
+ distributed under the License is distributed on an "AS IS" BASIS,
192
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
193
+ See the License for the specific language governing permissions and
194
+ limitations under the License.
@@ -0,0 +1,10 @@
1
+ unitypredict_engines/Models.py,sha256=hwndzMFoFKHmkRpcxPDcjdV9WV1PHAPvzq6TCYLyFxw,3153
2
+ unitypredict_engines/Platform.py,sha256=QIImp7Xilhn7YvQsoGniO7N-mn05OLatkXWhuWc05y0,3052
3
+ unitypredict_engines/UnityPredictLocalHost.py,sha256=Ta1_c8IXPe2KTS6s7cw9qwO832Zx3Mn8XNO1n6-qhaY,19580
4
+ unitypredict_engines/__init__.py,sha256=ZVud1XT9QzEZQhcxtFU2I4NVAhof7K5jCFM3fd9fC3M,51
5
+ unitypredict_engines/scripts.py,sha256=LcHSImPxZVucaPasc5ZOySS1GlrU-FOLg_uR561aF7o,78
6
+ unitypredict_engines-0.0.1.dist-info/METADATA,sha256=ko0zzwA0XFnPR6GX8t_zOvGYMvFxGvq82PWt3pdUGXM,7595
7
+ unitypredict_engines-0.0.1.dist-info/WHEEL,sha256=2wepM1nk4DS4eFpYrW1TTqPcoGNfHhhO_i5m4cOimbo,92
8
+ unitypredict_engines-0.0.1.dist-info/entry_points.txt,sha256=JlCMnQrC6MFXk3OZWL9FG01fjSkMzlkqtZaz5njuqHg,75
9
+ unitypredict_engines-0.0.1.dist-info/top_level.txt,sha256=u73Hru2uPFzda_gdgAAwcaVTBYZKBjDcWkhgTt209TA,21
10
+ unitypredict_engines-0.0.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: bdist_wheel (0.38.4)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ unitypredict-engines = unitypredict_engines.scripts:main
@@ -0,0 +1 @@
1
+ unitypredict_engines