go to post Guillaume Rongier · Jul 27, 2023 Thanks and fixed. BTW, now the new version of this project is in python : import time import os import json import iris from FhirInteraction import Interaction, Strategy, OAuthInteraction from google.oauth2 import id_token from google.auth.transport import requests import requests as rq # The following is an example of a custom OAuthInteraction class that class CustomOAuthInteraction(OAuthInteraction): client_id = None last_time_verified = None time_interval = 5 def clear_instance(self): self.token_string = None self.oauth_client = None self.base_url = None self.username = None self.token_obj = None self.scopes = None self.verify_search_results = None def set_instance(self, token:str,oauth_client:str,base_url:str,username:str): self.clear_instance() if not token or not oauth_client: # the token or oauth client is not set, skip the verification return global_time = iris.gref('^FHIR.OAuth2.Time') if global_time[token[0:50]]: self.last_time_verified = global_time[token[0:50]] if self.last_time_verified and (time.time() - self.last_time_verified) < self.time_interval: # the token was verified less than 5 seconds ago, skip the verification return self.token_string = token self.oauth_client = oauth_client self.base_url = base_url self.username = username # try to set the client id try: # first get the var env GOOGLE_CLIENT_ID is not set then None self.client_id = os.environ.get('GOOGLE_CLIENT_ID') # if not set, then by the secret.json file if not self.client_id: with open(os.environ.get('ISC_OAUTH_SECRET_PATH'),encoding='utf-8') as f: data = json.load(f) self.client_id = data['web']['client_id'] except FileNotFoundError: pass try: self.verify_token(token) except Exception as e: self.clear_instance() raise e # token is valid, set the last time verified to now global_time[token[0:50]]=time.time() def verify_token(self,token:str): # check if the token is an access token or an id token if token.startswith('ya29.'): self.verify_access_token(token) else: self.verify_id_token(token) def verify_access_token(self,token:str): # verify the access token is valid # get with a timeout of 5 seconds response = rq.get(f"https://www.googleapis.com/oauth2/v3/tokeninfo?access_token={token}",timeout=5) try: response.raise_for_status() except rq.exceptions.HTTPError as e: # the token is not valid raise e def verify_id_token(self,token:str): # Verify the token and get the user info idinfo = id_token.verify_oauth2_token(token, requests.Request(), self.client_id) if idinfo['iss'] not in ['accounts.google.com', 'https://accounts.google.com']: raise ValueError('Wrong issuer.') def get_introspection(self)->dict: return {} def get_user_info(self,basic_auth_username:str,basic_auth_roles:str)->dict: return {"Username":basic_auth_username,"Roles":basic_auth_roles} def verify_resource_id_request(self,resource_type:str,resource_id:str,required_privilege:str): pass def verify_resource_content(self,resource_dict:dict,required_privilege:str,allow_shared_resource:bool): pass def verify_history_instance_response(self,resource_type:str,resource_dict:dict,required_privilege:str): pass def verify_delete_request(self,resource_type:str,resource_id:str,required_privilege:str): pass def verify_search_request(self, resource_type:str, compartment_resource_type:str, compartment_resource_id:str, parameters:'iris.HS.FHIRServer.API.Data.QueryParameters', required_privilege:str): pass def verify_system_level_request(self): pass class CustomStrategy(Strategy): def on_get_capability_statement(self, capability_statement): # Example : del resources Account capability_statement['rest'][0]['resource'] = [resource for resource in capability_statement['rest'][0]['resource'] if resource['type'] != 'Account'] return capability_statement class CustomInteraction(Interaction): def on_before_request(self, fhir_service, fhir_request, body, timeout): #Extract the user and roles for this request #so consent can be evaluated. self.requesting_user = fhir_request.Username self.requesting_roles = fhir_request.Roles def on_after_request(self, fhir_service, fhir_request, fhir_response, body): #Clear the user and roles between requests. self.requesting_user = "" self.requesting_roles = "" def post_process_read(self, fhir_object): #Evaluate consent based on the resource and user/roles. #Returning 0 indicates this resource shouldn't be displayed - a 404 Not Found #will be returned to the user. return self.consent(fhir_object['resourceType'], self.requesting_user, self.requesting_roles) def post_process_search(self, rs, resource_type): #Iterate through each resource in the search set and evaluate #consent based on the resource and user/roles. #Each row marked as deleted and saved will be excluded from the Bundle. rs._SetIterator(0) while rs._Next(): if not self.consent(rs.ResourceType, self.requesting_user, self.requesting_roles): #Mark the row as deleted and save it. rs.MarkAsDeleted() rs._SaveRow() def consent(self, resource_type, user, roles): #Example consent logic - only allow users with the role '%All' to see #Observation resources. if resource_type == 'Observation': if '%All' in roles: return True else: return False else: return True Based on https://community.intersystems.com/post/iris-fhir-python-strategy
go to post Guillaume Rongier · Jul 13, 2023 That how i read Stream and Write stream with Embedded Python : Read Stream : def stream_to_string(stream)-> str: string = "" stream.Rewind() while not stream.AtEnd: string += stream.Read(1024) return string Write Stream : def string_to_stream(string:str): stream = iris.cls('%Stream.GlobalCharacter')._New() n = 1024 chunks = [string[i:i+n] for i in range(0, len(string), n)] for chunk in chunks: stream.Write(chunk) return stream
go to post Guillaume Rongier · Jul 13, 2023 Here is an example code to modify or create a production XData block, i guess you can adapt to you needs : ClassMethod CreateProduction( package As %String = "test", name As %String = "AutoCreatedProduction", xdata As %CharacterStream) As %Status { #Dim produtionClassName As %String = package _ "." _ name If ('$ZName(produtionClassName, 4)) { Return $System.Status.Error(5001, "Invalid Production package or name.") } #Dim productionDefinition As %Dictionary.ClassDefinition // Check if the production already exists If (##class(%Dictionary.ClassDefinition).%ExistsId(produtionClassName)) { // Open the production set productionDefinition = ##class(%Dictionary.ClassDefinition).%OpenId(produtionClassName) } Else { // Create the production definition set productionDefinition = ##Class(%Dictionary.ClassDefinition).%New() } // Set productionDefinition.Name = produtionClassName Set productionDefinition.Super = "Ens.Production" Set productionDefinition.ClassVersion = 25 // // Check if the XData Definition already exists If (##Class(%Dictionary.XDataDefinition).%ExistsId(produtionClassName_"||ProductionDefinition")) { // delete the XData Definition $$$ThrowOnError(##Class(%Dictionary.XDataDefinition).%DeleteId(produtionClassName_"||ProductionDefinition")) } #Dim xdataDefinition As %Dictionary.XDataDefinition = ##Class(%Dictionary.XDataDefinition).%New() // Set xdataDefinition.Name = "ProductionDefinition" // Do xdataDefinition.Data.CopyFrom(xdata) // // Insert XData Definition into Production Definition Do productionDefinition.XDatas.Insert(xdataDefinition) // #Dim statusCode As %Status = productionDefinition.%Save() // If ($System.Status.IsError(statusCode)) { Return statusCode } // Compile the production class return $System.OBJ.Compile(produtionClassName,"k-d") }
go to post Guillaume Rongier · Jul 3, 2023 SELECT COUNT(*) from Ens.MessageHeader WHERE SourceConfigName = 'EPIC_SIU_IN' AND TO_NUMBER(SessionId) = %ID AND %ID >= (SELECT TOP 1 %ID FROM Ens.MessageHeader a WHERE TimeCreated >= '2016-07-27 05:00:00.000' ORDER BY TimeCreated ASC) AND %ID <= (SELECT TOP 1 %ID FROM Ens.MessageHeader a WHERE TimeCreated < '2016-07-28 05:00:00.000' ORDER BY TimeCreated DESC) Just change the date here and see how fast it is. The idea is to use index and the fastest index in MessageHeader is %ID.
go to post Guillaume Rongier · Jul 3, 2023 🎉, great news ! I join Sergei on latest tag in addition of latest-cd and latest-em.
go to post Guillaume Rongier · Jun 30, 2023 What I have done on one project, is to copy past the code and use it. I haven't used ZPM. Here is an example : https://github.com/grongierisc/RestToDicom
go to post Guillaume Rongier · Jun 29, 2023 If you want mock ObjectScript classes use this : https://github.com/GendronAC/InterSystems-UnitTest-Mocking I had used it, it work quiet well.
go to post Guillaume Rongier · Jun 29, 2023 If you want mock ObjectScript classes use this : https://github.com/GendronAC/InterSystems-UnitTest-Mocking I had used it, it work quiet well.
go to post Guillaume Rongier · Jun 26, 2023 Hi, Have a look at this class (https://github.com/grongierisc/RestToDicom/blob/master/src/RestToDicom/P...). It's from ENSDEMO, it's kind of a mock that fake an DICOM modality and create a fake response for ECHO and C-FIND-RQ. It can be a good start.
go to post Guillaume Rongier · Jun 26, 2023 Hi @Evgeny Shvarov Yes without any issue, you can mix it as much as you want it. You have a full example here : https://github.com/grongierisc/RestToDicom/
go to post Guillaume Rongier · Jun 13, 2023 What a neat article, very useful and with a lot of details. Thanks :)
go to post Guillaume Rongier · Jun 12, 2023 Hi, Do you know that every component in a production has a OnInit() method that is called when the component starts? You can use this method to set the value of the parameter. For example: Method OnInit() As %Status { Set ..#Token = $System.Util.GetEnviron("Token") Quit $$$OK } May be it's more elegant to do so than update the parameter in the Production file. FYI, that what i do in IOP (Interoperability On Python) to set the value of the parameter of any component in a production. def on_init(self): self.my_param = os.environ.get("MY_PARAM", "default_value")
go to post Guillaume Rongier · Jun 7, 2023 Good idea, it environment variable is not found switch to settings value.
go to post Guillaume Rongier · Jun 5, 2023 Hi Evegny, You can do it in the following way: * in the production settings, with DefaultSettings docs here * but default settings is a kind of replacement for environment variables * you can to it in the code : * ObjectScript: $system.Util.GetEnviron("MY_ENV_VAR") * Python: os.environ['MY_ENV_VAR'] * you can use iop (interoperabilty on python) to import production in python way : settings.py import os PRODUCTIONS = [ { 'shvarov.i14y.Production': { "Item": [ { "@Name": "shvarov.i14y.ChatOperation", "@ClassName": "Telegram.BusinessOperation", "@Category": "Reddit", "@PoolSize": "1", "@Enabled": "true", "@Foreground": "false", "@Comment": "", "@LogTraceEvents": "false", "@Schedule": "", "Setting": [ { "@Target": "Adapter", "@Name": "SSLConfig", "#text": "default" }, { "@Target": "Adapter", "@Name": "Token", "#text": os.environ['TELEGRAM_TOKEN'] } ] } ] } } ] and then in the terminal: $ iop -M settings.py More information about iop and settings.py here
go to post Guillaume Rongier · Jun 4, 2023 Hi Evgeny, Thanks for your feedback. The -x option is for status, because -s is already used to start a production. The -e option is for export, and the export can be imported with the -m option. -m option stand for migrate, because I took the inspiration from the migrate command form django. I hope it's more clear now. But if you think, -i for import and -e for export is more clear, I can change it. Same for -s and -x. What do you think can be short for status?
go to post Guillaume Rongier · Apr 19, 2023 Awesome, how do you position this tool compared to irissqlcli if it had a web version?
go to post Guillaume Rongier · Apr 18, 2023 I love it <3, for the more you are using the community driver DB-API :)
go to post Guillaume Rongier · Apr 14, 2023 Wow, what a useful thread! Thank you. I especially like the fact that you offer solution in each IRIS language : SQL, Python, ObjectScript.
go to post Guillaume Rongier · Apr 3, 2023 SELECT {fn WEEK('2004-02-25')} AS Week, {fn DAY('2004-02-25')} as Day,{fn DAYOFWEEK('2004-02-25')} as DayOfWeek,{fn Year('2004-02-25')} as Year,{fn Quarter('2004-02-25')} as Quarter,{fn DAYOFYEAR('2004-02-25')} as DayOfYear Doc : https://docs.intersystems.com/iris20223/csp/documatic/%25CSP.Documatic.c... not all your requirements but it's a start