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

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

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")
}
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.

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")

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

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?