iris-pex-embedded-python


Nameiris-pex-embedded-python JSON
Version 2.3.27 PyPI version JSON
download
home_pageNone
SummaryIris Interoperability based on Embedded Python
upload_time2024-04-09 17:52:32
maintainerNone
docs_urlNone
authorNone
requires_pythonNone
licenseMIT License Copyright (c) 2019 InterSystems Developer Community Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
keywords iris intersystems python embedded
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # 1. interoperability-embedded-python

[![PyPI - Status](https://img.shields.io/pypi/status/iris-pex-embedded-python)](https://pypi.org/project/iris-pex-embedded-python/)
[![PyPI](https://img.shields.io/pypi/v/iris-pex-embedded-python)](https://pypi.org/project/iris-pex-embedded-python/)
[![PyPI - Downloads](https://img.shields.io/pypi/dm/iris-pex-embedded-python)](https://pypi.org/project/iris-pex-embedded-python/)
[![PyPI - License](https://img.shields.io/pypi/l/iris-pex-embedded-python)](https://pypi.org/project/iris-pex-embedded-python/)
![GitHub last commit](https://img.shields.io/github/last-commit/grongierisc/interoperability-embedded-python)

This proof of concept aims to show how the **iris interoperability framework** can be use with **embedded python**.

## 1.1. Table of Contents

- [1. interoperability-embedded-python](#1-interoperability-embedded-python)
  - [1.1. Table of Contents](#11-table-of-contents)
  - [1.2. Example](#12-example)
  - [1.3. Register a component](#13-register-a-component)
- [2. Demo](#2-demo)
- [3. Installation](#3-installation)
  - [3.1. With ZPM](#31-with-zpm)
  - [3.2. With PyPI](#32-with-pypi)
    - [3.2.1. Known issues](#321-known-issues)
- [4. How to Run the Sample](#4-how-to-run-the-sample)
  - [4.1. Docker containers](#41-docker-containers)
  - [4.2. Management Portal and VSCode](#42-management-portal-and-vscode)
  - [4.3. Open the production](#43-open-the-production)
- [5. What's inside the repository](#5-whats-inside-the-repository)
  - [5.1. Dockerfile](#51-dockerfile)
  - [5.2. .vscode/settings.json](#52-vscodesettingsjson)
  - [5.3. .vscode/launch.json](#53-vscodelaunchjson)
  - [5.4. .vscode/extensions.json](#54-vscodeextensionsjson)
  - [5.5. src folder](#55-src-folder)
- [6. How it works](#6-how-it-works)
  - [6.1. The `__init__.py`file](#61-the-__init__pyfile)
  - [6.2. The `common` class](#62-the-common-class)
  - [6.3. The `business_host` class](#63-the-business_host-class)
  - [6.4. The `inbound_adapter` class](#64-the-inbound_adapter-class)
  - [6.5. The `outbound_adapter` class](#65-the-outbound_adapter-class)
  - [6.6. The `business_service` class](#66-the-business_service-class)
  - [6.7. The `business_process` class](#67-the-business_process-class)
  - [6.8. The `business_operation` class](#68-the-business_operation-class)
    - [6.8.1. The dispacth system](#681-the-dispacth-system)
    - [6.8.2. The methods](#682-the-methods)
  - [6.9. The `director` class](#69-the-director-class)
  - [6.10. The `objects`](#610-the-objects)
  - [6.11. The `messages`](#611-the-messages)
  - [6.12. How to regsiter a component](#612-how-to-regsiter-a-component)
    - [6.12.1. register\_component](#6121-register_component)
    - [6.12.2. register\_file](#6122-register_file)
    - [6.12.3. register\_folder](#6123-register_folder)
    - [6.12.4. migrate](#6124-migrate)
      - [6.12.4.1. setting.py file](#61241-settingpy-file)
        - [6.12.4.1.1. CLASSES section](#612411-classes-section)
        - [6.12.4.1.2. Productions section](#612412-productions-section)
  - [6.13. Direct use of Grongier.PEX](#613-direct-use-of-grongierpex)
- [7. Command line](#7-command-line)
  - [7.1. help](#71-help)
  - [7.2. default](#72-default)
  - [7.3. lists](#73-lists)
  - [7.4. start](#74-start)
  - [7.5. kill](#75-kill)
  - [7.6. stop](#76-stop)
  - [7.7. restart](#77-restart)
  - [7.8. migrate](#78-migrate)
  - [7.9. export](#79-export)
  - [7.10. status](#710-status)
  - [7.11. version](#711-version)
  - [7.12. log](#712-log)
- [8. Credits](#8-credits)

## 1.2. Example

bo.py
```python
from grongier.pex import BusinessOperation,Message

class MyBusinessOperation(BusinessOperation):
    
    def on_init(self):
        #This method is called when the component is becoming active in the production

        self.log_info("[Python] ...MyBusinessOperation:on_init() is called")

        return

    def on_teardown(self):
        #This method is called when the component is becoming inactive in the production

        self.log_info("[Python] ...MyBusinessOperation:on_teardown() is called")

        return

    def on_message(self, message_input:MyRequest):
        # called from service/process/operation, message is of type MyRequest with property request_string

        self.log_info("[Python] ...MyBusinessOperation:on_message() is called with message:"+message_input.request_string)

        response = MyResponse("...MyBusinessOperation:on_message() echos")

        return response

@dataclass
class MyRequest(Message):

    request_string:str = None

@dataclass
class MyResponse(Message):

    my_string:str = None

```

## 1.3. Register a component 

To register a component, you need to create a `setting.py` file in the root of your project.<br>

This file will be used to register your classes and productions.<br>

e.g.:
setting.py
```python
from grongier.pex import Utils

import bo

CLASSES = {
    "Python.MyBusinessOperation": bo.MyBusinessOperation
}
```

Then you can use the `iop` command line to register your component.

```sh
iop --migrate /path/to/your/project/setting.py
```

# 2. Demo

You can find a demo in the [src/python/demo](src/python/demo) folder.

Other demos are aviailable in those repositories:

  - [training](https://github.com/grongierisc/formation-template-python)
  - [template](https://github.com/grongierisc/iris-python-interoperability-template)
  - [falsk](https://github.com/grongierisc/iris-python-flask-api-template)
  - [rest-to-dicom](https://github.com/grongierisc/RestToDicom)

# 3. Installation

## 3.1. With ZPM 

```objectscript
zpm "install pex-embbeded-python" 
```

## 3.2. With PyPI

```sh
pip3 install iris-pex-embedded-python
```

Import the ObjectScript classes, open an embedded python shell and run :

```python
from grongier.pex import Utils
Utils.setup()
```

or use `iop` command line :

```sh
iop --init
```

### 3.2.1. Known issues

If the module is not updated, make sure to remove the old version :

```sh
pip3 uninstall iris-pex-embedded-python
```

or manually remove the `grongier` folder in `<iris_installation>/lib/python/`

or force the installation with pip :

```sh
pip3 install --upgrade iris-pex-embedded-python --target <iris_installation>/lib/python/
```

# 4. How to Run the Sample

## 4.1. Docker containers


In order to have access to the InterSystems images, we need to go to the following url: http://container.intersystems.com. After connecting with our InterSystems credentials, we will get our password to connect to the registry. In the docker VScode addon, in the image tab, by pressing connect registry and entering the same url as before (http://container.intersystems.com) as a generic registry, we will be asked to give our credentials. The login is the usual one but the password is the one we got from the website.

From there, we should be able to build and compose our containers (with the `docker-compose.yml` and `Dockerfile` files given).

## 4.2. Management Portal and VSCode

This repository is ready for [VS Code](https://code.visualstudio.com/).

Open the locally-cloned `interoperability-embedeed-python` folder in VS Code.

If prompted (bottom right corner), install the recommended extensions.

**IMPORTANT**: When prompted, reopen the folder inside the container so you will be able to use the python components within it. The first time you do this it may take several minutes while the container is readied.

By opening the folder remote you enable VS Code and any terminals you open within it to use the python components within the container. Configure these to use `/usr/irissys/bin/irispython`

<img width="1614" alt="PythonInterpreter" src="https://user-images.githubusercontent.com/47849411/145864423-2de24aaa-036c-4beb-bda0-3a73fe15ccbd.png">

## 4.3. Open the production
To open the production you can go to [production](http://localhost:52773/csp/irisapp/EnsPortal.ProductionConfig.zen?PRODUCTION=PEX.Production).<br>
You can also click on the bottom on the `127.0.0.1:52773[IRISAPP]` button and select `Open Management Portal` then, click on [Interoperability] and [Configure] menus then click [productions] and [Go].

The production already has some code sample.

Here we can see the production and our pure python services and operations:
<img width="1177" alt="interop-screenshot" src="https://user-images.githubusercontent.com/47849411/131305197-d19511fd-6e05-4aec-a525-c88e6ebd0971.png">

<br>

New json trace for python native messages :
<img width="910" alt="json-message-trace" src="https://user-images.githubusercontent.com/47849411/131305211-b8beb2c0-438d-4afc-a6d2-f94d854373ae.png">

# 5. What's inside the repository

## 5.1. Dockerfile

A dockerfile which install some python dependancies (pip, venv) and sudo in the container for conviencies.
Then it create the dev directory and copy in it this git repository.

It starts IRIS and activates **%Service_CallIn** for **Python Shell**.
Use the related docker-compose.yml to easily setup additional parametes like port number and where you map keys and host folders.

This dockerfile ends with the installation of requirements for python modules.

Use .env/ file to adjust the dockerfile being used in docker-compose.

## 5.2. .vscode/settings.json

Settings file to let you immedietly code in VSCode with [VSCode ObjectScript plugin](https://marketplace.visualstudio.com/items?itemName=daimor.vscode-objectscript)

## 5.3. .vscode/launch.json
Config file if you want to debug with VSCode ObjectScript

[Read about all the files in this article](https://community.intersystems.com/post/dockerfile-and-friends-or-how-run-and-collaborate-objectscript-projects-intersystems-iris)

## 5.4. .vscode/extensions.json
Recommendation file to add extensions if you want to run with VSCode in the container.

[More information here](https://code.visualstudio.com/docs/remote/containers)

This is very useful to work with embedded python.

## 5.5. src folder

```
src
├── Grongier
│   └── PEX // ObjectScript classes that wrap python code
│       ├── BusinessOperation.cls
│       ├── BusinessProcess.cls
│       ├── BusinessService.cls
│       ├── Common.cls
│       ├── Director.cls
│       ├── InboundAdapter.cls
│       ├── Message.cls
│       ├── OutboundAdapter.cls
│       ├── Python.cls
│       ├── Test.cls
│       └── _utils.cls
├── PEX // Some example of wrapped classes
│   └── Production.cls
└── python
    ├── demo // Actual python code to run this demo
    |   `-- reddit
    |       |-- adapter.py
    |       |-- bo.py
    |       |-- bp.py
    |       |-- bs.py
    |       |-- message.py
    |       `-- obj.py
    ├── dist // Wheel used to implement python interoperability components
    │   └── grongier_pex-1.2.4-py3-none-any.whl
    ├── grongier
    │   └── pex // Helper classes to implement interoperability components
    │       ├── _business_host.py
    │       ├── _business_operation.py
    │       ├── _business_process.py
    │       ├── _business_service.py
    │       ├── _common.py
    │       ├── _director.py
    │       ├── _inbound_adapter.py
    │       ├── _message.py
    │       ├── _outbound_adapter.py
    │       ├── __init__.py
    │       └── _utils.py
    └── setup.py // setup to build the wheel
```
# 6. How it works

## 6.1. The `__init__.py`file
This file will allow us to create the classes to import in the code.<br>
It gets from the multiple files seen earlier the classes and make them into callable classes.
That way, when you wish to create a business operation, for example, you can just do:
```python
from grongier.pex import BusinessOperation
```

## 6.2. The `common` class
The common class shouldn't be called by the user, it defines almost all the other classes.<br>
This class defines:

`on_init`: The on_init() method is called when the component is started.<br> Use the on_init() method to initialize any structures needed by the component.

`on_tear_down`: Called before the component is terminated.<br> Use it to free any structures.

`on_connected`: The on_connected() method is called when the component is connected or reconnected after being disconnected.<br>Use the on_connected() method to initialize any structures needed by the component.

`log_info`: Write a log entry of type "info". :log entries can be viewed in the management portal.

`log_alert`: Write a log entry of type "alert". :log entries can be viewed in the management portal.

`log_warning`: Write a log entry of type "warning". :log entries can be viewed in the management portal.

`log_error`: Write a log entry of type "error". :log entries can be viewed in the management portal.

## 6.3. The `business_host` class
The business host class shouldn't be called by the user, it is the base class for all the business classes.<br>
This class defines:

`send_request_sync`: Send the specified message to the target business process or business operation synchronously.            
**Parameters**:<br>
- **target**: a string that specifies the name of the business process or operation to receive the request. <br>
    The target is the name of the component as specified in the Item Name property in the production definition, not the class name of the component.
- **request**: specifies the message to send to the target. The request is either an instance of a class that is a subclass of Message class or of IRISObject class.<br>
    If the target is a build-in ObjectScript component, you should use the IRISObject class. The IRISObject class enables the PEX framework to convert the message to a class supported by the target.
- **timeout**: an optional integer that specifies the number of seconds to wait before treating the send request as a failure. The default value is -1, which means wait forever.<br>
description: an optional string parameter that sets a description property in the message header. The default is None.

**Returns**:
    the response object from target.

**Raises**:
TypeError: if request is not of type Message or IRISObject.

<br><br>

`send_request_async`: Send the specified message to the target business process or business operation asynchronously.
**Parameters**:<br>
- **target**: a string that specifies the name of the business process or operation to receive the request. <br>
    The target is the name of the component as specified in the Item Name property in the production definition, not the class name of the component.
- **request**: specifies the message to send to the target. The request is an instance of IRISObject or of a subclass of Message.<br>
    If the target is a built-in ObjectScript component, you should use the IRISObject class. The IRISObject class enables the PEX framework to convert the message to a class supported by the target.
- **description**: an optional string parameter that sets a description property in the message header. The default is None.

**Raises**:
TypeError: if request is not of type Message or IRISObject.

<br><br>

`get_adapter_type`: Name of the registred Adapter.


## 6.4. The `inbound_adapter` class
Inbound Adapter in Python are subclass from grongier.pex.InboundAdapter in Python, that inherit from all the functions of the [common class](#72-the-common-class).<br>
This class is responsible for receiving the data from the external system, validating the data, and sending it to the business service by calling the BusinessHost process_input method.
This class defines:

`on_task`: Called by the production framework at intervals determined by the business service CallInterval property.<br>
The message can have any structure agreed upon by the inbound adapter and the business service.

Example of an inbound adapter ( situated in the src/python/demo/reddit/adapter.py file ):
```python
from grongier.pex import InboundAdapter
import requests
import iris
import json

class RedditInboundAdapter(InboundAdapter):
    """
    This adapter use requests to fetch self.limit posts as data from the reddit
    API before calling process_input for each post.
    """
    def on_init(self):
        
        if not hasattr(self,'feed'):
            self.feed = "/new/"
        
        if self.limit is None:
            raise TypeError('no Limit field')
        
        self.last_post_name = ""
        
        return 1

    def on_task(self):
        self.log_info(f"LIMIT:{self.limit}")
        if self.feed == "" :
            return 1
        
        tSC = 1
        # HTTP Request
        try:
            server = "https://www.reddit.com"
            request_string = self.feed+".json?before="+self.last_post_name+"&limit="+self.limit
            self.log_info(server+request_string)
            response = requests.get(server+request_string)
            response.raise_for_status()

            data = response.json()
            updateLast = 0

            for key, value in enumerate(data['data']['children']):
                if value['data']['selftext']=="":
                    continue
                post = iris.cls('dc.Reddit.Post')._New()
                post._JSONImport(json.dumps(value['data']))
                post.OriginalJSON = json.dumps(value)
                if not updateLast:
                    self.LastPostName = value['data']['name']
                    updateLast = 1
                response = self.BusinessHost.ProcessInput(post)
        except requests.exceptions.HTTPError as err:
            if err.response.status_code == 429:
                self.log_warning(err.__str__())
            else:
                raise err
        except Exception as err: 
            self.log_error(err.__str__())
            raise err

        return tSC
```

## 6.5. The `outbound_adapter` class
Outbound Adapter in Python are subclass from grongier.pex.OutboundAdapter in Python, that inherit from all the functions of the [common class](#72-the-common-class).<br>
This class is responsible for sending the data to the external system.

The Outbound Adapter gives the Operation the possibility to have a heartbeat notion.
To activate this option, the CallInterval parameter of the adapter must be strictly greater than 0.

<img width="301" alt="image" src="https://user-images.githubusercontent.com/47849411/178230243-39806602-a63d-4a89-9563-fcf6836d0515.png">

Example of an outbound adapter ( situated in the src/python/demo/reddit/adapter.py file ):

```python
class TestHeartBeat(OutboundAdapter):

    def on_keepalive(self):
        self.log_info('beep')

    def on_task(self):
        self.log_info('on_task')
```

## 6.6. The `business_service` class
This class is responsible for receiving the data from external system and sending it to business processes or business operations in the production.<br>
The business service can use an adapter to access the external system, which is specified overriding the get_adapter_type method.<br>
There are three ways of implementing a business service:<br>
- Polling business service with an adapter - The production framework at regular intervals calls the adapter’s OnTask() method, 
    which sends the incoming data to the the business service ProcessInput() method, which, in turn calls the OnProcessInput method with your code.

- Polling business service that uses the default adapter - In this case, the framework calls the default adapter's OnTask method with no data. 
    The OnProcessInput() method then performs the role of the adapter and is responsible for accessing the external system and receiving the data.

- Nonpolling business service - The production framework does not initiate the business service. Instead custom code in either a long-running process 
    or one that is started at regular intervals initiates the business service by calling the Director.CreateBusinessService() method.

Business service in Python are subclass from grongier.pex.BusinessService in Python, that inherit from all the functions of the [business host](#73-the-business_host-class).<br>
This class defines:

`on_process_input`: Receives the message from the inbond adapter via the PRocessInput method and is responsible for forwarding it to target business processes or operations.<br>
If the business service does not specify an adapter, then the default adapter calls this method with no message and the business service is responsible for receiving the data from the external system and validating it.
**Parameters**:<br>
- **message_input**: an instance of IRISObject or subclass of Message containing the data that the inbound adapter passes in.<br>
The message can have any structure agreed upon by the inbound adapter and the business service. 

<br><br>

Example of a business service ( situated in the src/python/demo/reddit/bs.py file ):
```python
from grongier.pex import BusinessService

import iris

from message import PostMessage
from obj import PostClass

class RedditServiceWithPexAdapter(BusinessService):
    """
    This service use our python Python.RedditInboundAdapter to receive post
    from reddit and call the FilterPostRoutingRule process.
    """
    def get_adapter_type():
        """
        Name of the registred Adapter
        """
        return "Python.RedditInboundAdapter"

    def on_process_input(self, message_input):
        msg = iris.cls("dc.Demo.PostMessage")._New()
        msg.Post = message_input
        return self.send_request_sync(self.target,msg)

    def on_init(self):
        
        if not hasattr(self,'target'):
            self.target = "Python.FilterPostRoutingRule"
        
        return
```


## 6.7. The `business_process` class
Typically contains most of the logic in a production.<br>
A business process can receive messages from a business service, another business process, or a business operation.<br>
It can modify the message, convert it to a different format, or route it based on the message contents.<br>
The business process can route a message to a business operation or another business process.<br>
Business processes in Python are subclass from grongier.pex.BusinessProcess in Python, that inherit from all the functions of the [business host](#73-the-business_host-class).<br>
This class defines:

`on_request`: Handles requests sent to the business process. A production calls this method whenever an initial request for a specific business process arrives on the appropriate queue and is assigned a job in which to execute.<br>
**Parameters**:<br>
- **request**: An instance of IRISObject or subclass of Message that contains the request message sent to the business process.

**Returns**:
An instance of IRISObject or subclass of Message that contains the response message that this business process can return
to the production component that sent the initial message.

<br><br>

`on_response`: Handles responses sent to the business process in response to messages that it sent to the target.<br>
A production calls this method whenever a response for a specific business process arrives on the appropriate queue and is assigned a job in which to execute.<br>
Typically this is a response to an asynchronous request made by the business process where the responseRequired parameter has a true value.<br>
**Parameters**:<br>
- **request**: An instance of IRISObject or subclass of Message that contains the initial request message sent to the business process.
- **response**: An instance of IRISObject or subclass of Message that contains the response message that this business process can return to the production component that sent the initial message.
- **callRequest**: An instance of IRISObject or subclass of Message that contains the request that the business process sent to its target.
- **callResponse**: An instance of IRISObject or subclass of Message that contains the incoming response.
- **completionKey**: A string that contains the completionKey specified in the completionKey parameter of the outgoing SendAsync() method.

**Returns**:
An instance of IRISObject or subclass of Message that contains the response message that this business process can return
to the production component that sent the initial message.

<br><br>

`on_complete`: Called after the business process has received and handled all responses to requests it has sent to targets.<br>
**Parameters**: 
- **request**: An instance of IRISObject or subclass of Message that contains the initial request message sent to the business process.<br>
- **response**: An instance of IRISObject or subclass of Message that contains the response message that this business process can return to the production component that sent the initial message.

**Returns**:
An instance of IRISObject or subclass of Message that contains the response message that this business process can return to the production component that sent the initial message.

<br><br>

Example of a business process ( situated in the src/python/demo/reddit/bp.py file ):
```python
from grongier.pex import BusinessProcess

from message import PostMessage
from obj import PostClass

class FilterPostRoutingRule(BusinessProcess):
    """
    This process receive a PostMessage containing a reddit post.
    It then understand if the post is about a dog or a cat or nothing and
    fill the right infomation inside the PostMessage before sending it to
    the FileOperation operation.
    """
    def on_init(self):
        
        if not hasattr(self,'target'):
            self.target = "Python.FileOperation"
        
        return

    def on_request(self, request):

        if 'dog'.upper() in request.post.selftext.upper():
            request.to_email_address = 'dog@company.com'
            request.found = 'Dog'
        if 'cat'.upper() in request.post.selftext.upper():
            request.to_email_address = 'cat@company.com'
            request.found = 'Cat'

        if request.found is not None:
            return self.send_request_sync(self.target,request)
        else:
            return
```

## 6.8. The `business_operation` class
This class is responsible for sending the data to an external system or a local system such as an iris database.<br>
The business operation can optionally use an adapter to handle the outgoing message which is specified overriding the get_adapter_type method.<br>
If the business operation has an adapter, it uses the adapter to send the message to the external system.<br>
The adapter can either be a PEX adapter, an ObjectScript adapter or a [python adapter](#75-the-outbound_adapter-class).<br>
Business operation in Python are subclass from grongier.pex.BusinessOperation in Python, that inherit from all the functions of the [business host](#73-the-business_host-class).<br>

### 6.8.1. The dispacth system
In a business operation it is possbile to create any number of function [similar to the on_message method](#782-the-methods) that will take as argument a [typed request](#711-the-messages) like this `my_special_message_method(self,request: MySpecialMessage)`.

The dispatch system will automatically analyze any request arriving to the operation and dispacth the requests depending of their type. If the type of the request is not recognized or is not specified in any **on_message like function**, the dispatch system will send it to the `on_message` function.

### 6.8.2. The methods
This class defines:

`on_message`: Called when the business operation receives a message from another production component [that can not be dispatched to another function](#781-the-dispacth-system).<br>
Typically, the operation will either send the message to the external system or forward it to a business process or another business operation.
If the operation has an adapter, it uses the Adapter.invoke() method to call the method on the adapter that sends the message to the external system.
If the operation is forwarding the message to another production component, it uses the SendRequestAsync() or the SendRequestSync() method.<br>
**Parameters**:
- **request**: An instance of either a subclass of Message or of IRISObject containing the incoming message for the business operation.

**Returns**:
The response object

Example of a business operation ( situated in the src/python/demo/reddit/bo.py file ):
```python
from grongier.pex import BusinessOperation

from message import MyRequest,MyMessage

import iris

import os
import datetime
import smtplib
from email.mime.text import MIMEText

class EmailOperation(BusinessOperation):
    """
    This operation receive a PostMessage and send an email with all the
    important information to the concerned company ( dog or cat company )
    """

    def my_message(self,request:MyMessage):
        sender = 'admin@example.com'
        receivers = 'toto@example.com'
        port = 1025
        msg = MIMEText(request.toto)

        msg['Subject'] = 'MyMessage'
        msg['From'] = sender
        msg['To'] = receivers

        with smtplib.SMTP('localhost', port) as server:
            server.sendmail(sender, receivers, msg.as_string())
            print("Successfully sent email")

    def on_message(self, request):

        sender = 'admin@example.com'
        receivers = [ request.to_email_address ]


        port = 1025
        msg = MIMEText('This is test mail')

        msg['Subject'] = request.found+" found"
        msg['From'] = 'admin@example.com'
        msg['To'] = request.to_email_address

        with smtplib.SMTP('localhost', port) as server:
            
            # server.login('username', 'password')
            server.sendmail(sender, receivers, msg.as_string())
            print("Successfully sent email")

```
If this operation is called using a MyRequest message, the my_message function will be called thanks to the dispatcher, otherwise the on_message function will be called.

## 6.9. The `director` class
The Director class is used for nonpolling business services, that is, business services which are not automatically called by the production framework (through the inbound adapter) at the call interval.<br>
Instead these business services are created by a custom application by calling the Director.create_business_service() method.<br>
This class defines:

`create_business_service`: The create_business_service() method initiates the specified business service.<br>
**Parameters**:
- **connection**: an IRISConnection object that specifies the connection to an IRIS instance for Java.
- **target**: a string that specifies the name of the business service in the production definition.

**Returns**:
an object that contains an instance of IRISBusinessService

`start_production`: The start_production() method starts the production.<br>
**Parameters**:
- **production_name**: a string that specifies the name of the production to start.

`stop_production`: The stop_production() method stops the production.<br>
**Parameters**:
- **production_name**: a string that specifies the name of the production to stop.

`restart_production`: The restart_production() method restarts the production.<br>
**Parameters**:
- **production_name**: a string that specifies the name of the production to restart.

`list_productions`: The list_productions() method returns a dictionary of the names of the productions that are currently running.<br>

## 6.10. The `objects`
We will use `dataclass` to hold information in our [messages](#711-the-messages) in a `obj.py` file.

Example of an object ( situated in the src/python/demo/reddit/obj.py file ):
```python
from dataclasses import dataclass

@dataclass
class PostClass:
    title: str
    selftext : str
    author: str
    url: str
    created_utc: float = None
    original_json: str = None
```

## 6.11. The `messages`
The messages will contain one or more [objects](#710-the-objects), located in the `obj.py` file.<br>
Messages, requests and responses all inherit from the `grongier.pex.Message` class.

These messages will allow us to transfer information between any business service/process/operation.

Example of a message ( situated in the src/python/demo/reddit/message.py file ):
```python
from grongier.pex import Message

from dataclasses import dataclass

from obj import PostClass

@dataclass
class PostMessage(Message):
    post:PostClass = None
    to_email_address:str = None
    found:str = None
```

WIP It is to be noted that it is needed to use types when you define an object or a message.

## 6.12. How to regsiter a component 

You can register a component to iris in many way :
* Only one component with `register_component` 
* All the component in a file with `register_file` 
* All the component in a folder with `register_folder` 

### 6.12.1. register_component

Start an embedded python shell :

```sh
/usr/irissys/bin/irispython
```

Then use this class method to add a new py file to the component list for interoperability.

```python
from grongier.pex import Utils
Utils.register_component(<ModuleName>,<ClassName>,<PathToPyFile>,<OverWrite>,<NameOfTheComponent>)
```

e.g :
```python
from grongier.pex import Utils
Utils.register_component("MyCombinedBusinessOperation","MyCombinedBusinessOperation","/irisdev/app/src/python/demo/",1,"PEX.MyCombinedBusinessOperation")
```

### 6.12.2. register_file

Start an embedded python shell :

```sh
/usr/irissys/bin/irispython
```

Then use this class method to add a new py file to the component list for interoperability.

```python
from grongier.pex import Utils
Utils.register_file(<File>,<OverWrite>,<PackageName>)
```

e.g :
```python
from grongier.pex import Utils
Utils.register_file("/irisdev/app/src/python/demo/bo.py",1,"PEX")
```

### 6.12.3. register_folder

Start an embedded python shell :

```sh
/usr/irissys/bin/irispython
```

Then use this class method to add a new py file to the component list for interoperability.

```python
from grongier.pex import Utils
Utils.register_folder(<Path>,<OverWrite>,<PackageName>)
```

e.g :
```python
from grongier.pex import Utils
Utils.register_folder("/irisdev/app/src/python/demo/",1,"PEX")
```

### 6.12.4. migrate

Start an embedded python shell :

```sh
/usr/irissys/bin/irispython
```

Then use this static method to migrate the settings file to the iris framework.

```python
from grongier.pex import Utils
Utils.migrate()
```

#### 6.12.4.1. setting.py file

This file is used to store the settings of the interoperability components.

It has two sections :
* `CLASSES` : This section is used to store the classes of the interoperability components.
* `PRODUCTIONS` : This section is used to store the productions of the interoperability components.

e.g :
```python
import bp
from bo import *
from bs import *

CLASSES = {
    'Python.RedditService': RedditService,
    'Python.FilterPostRoutingRule': bp.FilterPostRoutingRule,
    'Python.FileOperation': FileOperation,
    'Python.FileOperationWithIrisAdapter': FileOperationWithIrisAdapter,
}

PRODUCTIONS = [
    {
        'dc.Python.Production': {
        "@Name": "dc.Demo.Production",
        "@TestingEnabled": "true",
        "@LogGeneralTraceEvents": "false",
        "Description": "",
        "ActorPoolSize": "2",
        "Item": [
            {
                "@Name": "Python.FileOperation",
                "@Category": "",
                "@ClassName": "Python.FileOperation",
                "@PoolSize": "1",
                "@Enabled": "true",
                "@Foreground": "false",
                "@Comment": "",
                "@LogTraceEvents": "true",
                "@Schedule": "",
                "Setting": {
                    "@Target": "Host",
                    "@Name": "%settings",
                    "#text": "path=/tmp"
                }
            },
            {
                "@Name": "Python.RedditService",
                "@Category": "",
                "@ClassName": "Python.RedditService",
                "@PoolSize": "1",
                "@Enabled": "true",
                "@Foreground": "false",
                "@Comment": "",
                "@LogTraceEvents": "false",
                "@Schedule": "",
                "Setting": [
                    {
                        "@Target": "Host",
                        "@Name": "%settings",
                        "#text": "limit=10\nother<10"
                    }
                ]
            },
            {
                "@Name": "Python.FilterPostRoutingRule",
                "@Category": "",
                "@ClassName": "Python.FilterPostRoutingRule",
                "@PoolSize": "1",
                "@Enabled": "true",
                "@Foreground": "false",
                "@Comment": "",
                "@LogTraceEvents": "false",
                "@Schedule": ""
            }
        ]
    }
    }
]
```

##### 6.12.4.1.1. CLASSES section

This section is used to store the classes of the interoperability components.

It aims to help to register the components.

This dictionary has the following structure :
* Key : The name of the component
* Value : 
  * The class of the component (you have to import it before)
  * The module of the component (you have to import it before)
  * Another dictionary with the following structure :
    * `module` : Name of the module of the component (optional)
    * `class` : Name of the class of the component (optional)
    * `path` : The path of the component (mandatory)

e.g :

When Value is a class or a module:
```python
import bo
import bp
from bs import RedditService

CLASSES = {
    'Python.RedditService': RedditService,
    'Python.FilterPostRoutingRule': bp.FilterPostRoutingRule,
    'Python.FileOperation': bo,
}
```

When Value is a dictionary :
```python
CLASSES = {
    'Python.RedditService': {
        'module': 'bs',
        'class': 'RedditService',
        'path': '/irisdev/app/src/python/demo/'
    },
    'Python.Module': {
        'module': 'bp',
        'path': '/irisdev/app/src/python/demo/'
    },
    'Python.Package': {
        'path': '/irisdev/app/src/python/demo/'
    },
}
```

##### 6.12.4.1.2. Productions section

This section is used to store the productions of the interoperability components.

It aims to help to register a production.

This list has the following structure :
* A list of dictionary with the following structure :
  * `dc.Python.Production` : The name of the production
    * `@Name` : The name of the production
    * `@TestingEnabled` : The testing enabled of the production
    * `@LogGeneralTraceEvents` : The log general trace events of the production
    * `Description` : The description of the production
    * `ActorPoolSize` : The actor pool size of the production
    * `Item` : The list of the items of the production
      * `@Name` : The name of the item
      * `@Category` : The category of the item
      * `@ClassName` : The class name of the item
      * `@PoolSize` : The pool size of the item
      * `@Enabled` : The enabled of the item
      * `@Foreground` : The foreground of the item
      * `@Comment` : The comment of the item
      * `@LogTraceEvents` : The log trace events of the item
      * `@Schedule` : The schedule of the item
      * `Setting` : The list of the settings of the item
        * `@Target` : The target of the setting
        * `@Name` : The name of the setting
        * `#text` : The value of the setting

The minimum structure of a production is :
```python
PRODUCTIONS = [
        {
            'UnitTest.Production': {
                "Item": [
                    {
                        "@Name": "Python.FileOperation",
                        "@ClassName": "Python.FileOperation",
                    },
                    {
                        "@Name": "Python.EmailOperation",
                        "@ClassName": "UnitTest.Package.EmailOperation"
                    }
                ]
            }
        } 
    ]
```

You can also set in `@ClassName` an item from the CLASSES section.

e.g :
```python
from bo import FileOperation
PRODUCTIONS = [
        {
            'UnitTest.Production': {
                "Item": [
                    {
                        "@Name": "Python.FileOperation",
                        "@ClassName": FileOperation,
                    }
                ]
            }
        } 
    ]
```

As the production is a dictionary, you can add in value of the production dictionary an environment variable.

e.g :
```python
import os

PRODUCTIONS = [
        {
            'UnitTest.Production': {
                "Item": [
                    {
                        "@Name": "Python.FileOperation",
                        "@ClassName": "Python.FileOperation",
                        "Setting": {
                            "@Target": "Host",
                            "@Name": "%settings",
                            "#text": os.environ['SETTINGS']
                        }
                    }
                ]
            }
        } 
    ]
```

## 6.13. Direct use of Grongier.PEX

If you don't want to use the register_component util. You can add a Grongier.PEX.BusinessService component directly into the management portal and configure the properties :
- %module :
  - Module name of your python code
- %classname :
  - Classname of you component
- %classpaths
  - Path where you component is.
    - This can one or more Classpaths (separated by '|' character) needed in addition to PYTHON_PATH

e.g :
<img width="800" alt="component-config" src="https://user-images.githubusercontent.com/47849411/131316308-e1898b19-11df-433b-b1c6-7f69d5cc9974.png">

# 7. Command line

Since version 2.3.1, you can use the command line to register your components and productions.

To use it, you have to use the following command :
```bash
iop 
```

output :
```bash
usage: python3 -m grongier.pex [-h] [-d DEFAULT] [-l] [-s START] [-k] [-S] [-r] [-M MIGRATE] [-e EXPORT] [-x] [-v] [-L]
optional arguments:
  -h, --help            display help and default production name
  -d DEFAULT, --default DEFAULT
                        set the default production
  -l, --lists           list productions
  -s START, --start START
                        start a production
  -k, --kill            kill a production (force stop)
  -S, --stop            stop a production
  -r, --restart         restart a production
  -M MIGRATE, --migrate MIGRATE
                        migrate production and classes with settings file
  -e EXPORT, --export EXPORT
                        export a production
  -x, --status          status a production
  -v, --version         display version
  -L, --log             display log

default production: PEX.Production
```

## 7.1. help

The help command display the help and the default production name.

```bash
iop -h
```

output :
```bash
usage: python3 -m grongier.pex [-h] [-d DEFAULT] [-l] [-s START] [-k] [-S] [-r] [-M MIGRATE] [-e EXPORT] [-x] [-v] [-L]
...
default production: PEX.Production
```

## 7.2. default

The default command set the default production.

With no argument, it display the default production.

```bash
iop -d
```

output :
```bash
default production: PEX.Production
```

With an argument, it set the default production.

```bash
iop -d PEX.Production
```

## 7.3. lists

The lists command list productions.

```bash
iop -l
```

output :
```bash
{
    "PEX.Production": {
        "Status": "Stopped",
        "LastStartTime": "2023-05-31 11:13:51.000",
        "LastStopTime": "2023-05-31 11:13:54.153",
        "AutoStart": 0
    }
}
```

## 7.4. start

The start command start a production.

To exit the command, you have to press CTRL+C.

```bash
iop -s PEX.Production
```

output :
```bash
2021-08-30 15:13:51.000 [PEX.Production] INFO: Starting production
2021-08-30 15:13:51.000 [PEX.Production] INFO: Starting item Python.FileOperation
2021-08-30 15:13:51.000 [PEX.Production] INFO: Starting item Python.EmailOperation
...
```

## 7.5. kill

The kill command kill a production (force stop).

Kill command is the same as stop command but with a force stop.

Kill command doesn't take an argument because only one production can be running.

```bash
iop -k 
```

## 7.6. stop

The stop command stop a production.

Stop command doesn't take an argument because only one production can be running.

```bash
iop -S 
```

## 7.7. restart

The restart command restart a production.

Restart command doesn't take an argument because only one production can be running.

```bash
iop -r 
```

## 7.8. migrate

The migrate command migrate a production and classes with settings file.

Migrate command must take the absolute path of the settings file.

Settings file must be in the same folder as the python code.

```bash
iop -M /tmp/settings.py
```

## 7.9. export

The export command export a production.

If no argument is given, the export command export the default production.

```bash
iop -e
```

If an argument is given, the export command export the production given in argument.

```bash
iop -e PEX.Production
```

output :
```bash
{
    "Production": {
        "@Name": "PEX.Production",
        "@TestingEnabled": "true",
        "@LogGeneralTraceEvents": "false",
        "Description": "",
        "ActorPoolSize": "2",
        "Item": [
            {
                "@Name": "Python.FileOperation",
                "@Category": "",
                "@ClassName": "Python.FileOperation",
                "@PoolSize": "1",
                "@Enabled": "true",
                "@Foreground": "false",
                "@Comment": "",
                "@LogTraceEvents": "true",
                "@Schedule": "",
                "Setting": [
                    {
                        "@Target": "Adapter",
                        "@Name": "Charset",
                        "#text": "utf-8"
                    },
                    {
                        "@Target": "Adapter",
                        "@Name": "FilePath",
                        "#text": "/irisdev/app/output/"
                    },
                    {
                        "@Target": "Host",
                        "@Name": "%settings",
                        "#text": "path=/irisdev/app/output/"
                    }
                ]
            }
        ]
    }
}
```

## 7.10. status

The status command status a production.

Status command doesn't take an argument because only one production can be running.

```bash
iop -x 
```

output :
```bash
{
    "Production": "PEX.Production",
    "Status": "stopped"
}
```

Status can be :
- stopped
- running
- suspended
- troubled

## 7.11. version

The version command display the version.

```bash
iop -v
```

output :
```bash
2.3.0
```

## 7.12. log

The log command display the log.

To exit the command, you have to press CTRL+C.

```bash
iop -L
```

output :
```bash
2021-08-30 15:13:51.000 [PEX.Production] INFO: Starting production
2021-08-30 15:13:51.000 [PEX.Production] INFO: Starting item Python.FileOperation
2021-08-30 15:13:51.000 [PEX.Production] INFO: Starting item Python.EmailOperation
...
```

# 8. Credits

Most of the code came from PEX for Python by Mo Cheng and Summer Gerry.

Works only on IRIS 2021.2 +

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "iris-pex-embedded-python",
    "maintainer": null,
    "docs_url": null,
    "requires_python": null,
    "maintainer_email": null,
    "keywords": "iris, intersystems, python, embedded",
    "author": null,
    "author_email": "grongier <guillaume.rongier@intersystems.com>",
    "download_url": "https://files.pythonhosted.org/packages/7f/6d/6e45cff878884cf8552cfd1d6ab5241820b592e3f53d8a589c6983302612/iris_pex_embedded_python-2.3.27.tar.gz",
    "platform": null,
    "description": "# 1. interoperability-embedded-python\n\n[![PyPI - Status](https://img.shields.io/pypi/status/iris-pex-embedded-python)](https://pypi.org/project/iris-pex-embedded-python/)\n[![PyPI](https://img.shields.io/pypi/v/iris-pex-embedded-python)](https://pypi.org/project/iris-pex-embedded-python/)\n[![PyPI - Downloads](https://img.shields.io/pypi/dm/iris-pex-embedded-python)](https://pypi.org/project/iris-pex-embedded-python/)\n[![PyPI - License](https://img.shields.io/pypi/l/iris-pex-embedded-python)](https://pypi.org/project/iris-pex-embedded-python/)\n![GitHub last commit](https://img.shields.io/github/last-commit/grongierisc/interoperability-embedded-python)\n\nThis proof of concept aims to show how the **iris interoperability framework** can be use with **embedded python**.\n\n## 1.1. Table of Contents\n\n- [1. interoperability-embedded-python](#1-interoperability-embedded-python)\n  - [1.1. Table of Contents](#11-table-of-contents)\n  - [1.2. Example](#12-example)\n  - [1.3. Register a component](#13-register-a-component)\n- [2. Demo](#2-demo)\n- [3. Installation](#3-installation)\n  - [3.1. With ZPM](#31-with-zpm)\n  - [3.2. With PyPI](#32-with-pypi)\n    - [3.2.1. Known issues](#321-known-issues)\n- [4. How to Run the Sample](#4-how-to-run-the-sample)\n  - [4.1. Docker containers](#41-docker-containers)\n  - [4.2. Management Portal and VSCode](#42-management-portal-and-vscode)\n  - [4.3. Open the production](#43-open-the-production)\n- [5. What's inside the repository](#5-whats-inside-the-repository)\n  - [5.1. Dockerfile](#51-dockerfile)\n  - [5.2. .vscode/settings.json](#52-vscodesettingsjson)\n  - [5.3. .vscode/launch.json](#53-vscodelaunchjson)\n  - [5.4. .vscode/extensions.json](#54-vscodeextensionsjson)\n  - [5.5. src folder](#55-src-folder)\n- [6. How it works](#6-how-it-works)\n  - [6.1. The `__init__.py`file](#61-the-__init__pyfile)\n  - [6.2. The `common` class](#62-the-common-class)\n  - [6.3. The `business_host` class](#63-the-business_host-class)\n  - [6.4. The `inbound_adapter` class](#64-the-inbound_adapter-class)\n  - [6.5. The `outbound_adapter` class](#65-the-outbound_adapter-class)\n  - [6.6. The `business_service` class](#66-the-business_service-class)\n  - [6.7. The `business_process` class](#67-the-business_process-class)\n  - [6.8. The `business_operation` class](#68-the-business_operation-class)\n    - [6.8.1. The dispacth system](#681-the-dispacth-system)\n    - [6.8.2. The methods](#682-the-methods)\n  - [6.9. The `director` class](#69-the-director-class)\n  - [6.10. The `objects`](#610-the-objects)\n  - [6.11. The `messages`](#611-the-messages)\n  - [6.12. How to regsiter a component](#612-how-to-regsiter-a-component)\n    - [6.12.1. register\\_component](#6121-register_component)\n    - [6.12.2. register\\_file](#6122-register_file)\n    - [6.12.3. register\\_folder](#6123-register_folder)\n    - [6.12.4. migrate](#6124-migrate)\n      - [6.12.4.1. setting.py file](#61241-settingpy-file)\n        - [6.12.4.1.1. CLASSES section](#612411-classes-section)\n        - [6.12.4.1.2. Productions section](#612412-productions-section)\n  - [6.13. Direct use of Grongier.PEX](#613-direct-use-of-grongierpex)\n- [7. Command line](#7-command-line)\n  - [7.1. help](#71-help)\n  - [7.2. default](#72-default)\n  - [7.3. lists](#73-lists)\n  - [7.4. start](#74-start)\n  - [7.5. kill](#75-kill)\n  - [7.6. stop](#76-stop)\n  - [7.7. restart](#77-restart)\n  - [7.8. migrate](#78-migrate)\n  - [7.9. export](#79-export)\n  - [7.10. status](#710-status)\n  - [7.11. version](#711-version)\n  - [7.12. log](#712-log)\n- [8. Credits](#8-credits)\n\n## 1.2. Example\n\nbo.py\n```python\nfrom grongier.pex import BusinessOperation,Message\n\nclass MyBusinessOperation(BusinessOperation):\n    \n    def on_init(self):\n        #This method is called when the component is becoming active in the production\n\n        self.log_info(\"[Python] ...MyBusinessOperation:on_init() is called\")\n\n        return\n\n    def on_teardown(self):\n        #This method is called when the component is becoming inactive in the production\n\n        self.log_info(\"[Python] ...MyBusinessOperation:on_teardown() is called\")\n\n        return\n\n    def on_message(self, message_input:MyRequest):\n        # called from service/process/operation, message is of type MyRequest with property request_string\n\n        self.log_info(\"[Python] ...MyBusinessOperation:on_message() is called with message:\"+message_input.request_string)\n\n        response = MyResponse(\"...MyBusinessOperation:on_message() echos\")\n\n        return response\n\n@dataclass\nclass MyRequest(Message):\n\n    request_string:str = None\n\n@dataclass\nclass MyResponse(Message):\n\n    my_string:str = None\n\n```\n\n## 1.3. Register a component \n\nTo register a component, you need to create a `setting.py` file in the root of your project.<br>\n\nThis file will be used to register your classes and productions.<br>\n\ne.g.:\nsetting.py\n```python\nfrom grongier.pex import Utils\n\nimport bo\n\nCLASSES = {\n    \"Python.MyBusinessOperation\": bo.MyBusinessOperation\n}\n```\n\nThen you can use the `iop` command line to register your component.\n\n```sh\niop --migrate /path/to/your/project/setting.py\n```\n\n# 2. Demo\n\nYou can find a demo in the [src/python/demo](src/python/demo) folder.\n\nOther demos are aviailable in those repositories:\n\n  - [training](https://github.com/grongierisc/formation-template-python)\n  - [template](https://github.com/grongierisc/iris-python-interoperability-template)\n  - [falsk](https://github.com/grongierisc/iris-python-flask-api-template)\n  - [rest-to-dicom](https://github.com/grongierisc/RestToDicom)\n\n# 3. Installation\n\n## 3.1. With ZPM \n\n```objectscript\nzpm \"install pex-embbeded-python\" \n```\n\n## 3.2. With PyPI\n\n```sh\npip3 install iris-pex-embedded-python\n```\n\nImport the ObjectScript classes, open an embedded python shell and run :\n\n```python\nfrom grongier.pex import Utils\nUtils.setup()\n```\n\nor use `iop` command line :\n\n```sh\niop --init\n```\n\n### 3.2.1. Known issues\n\nIf the module is not updated, make sure to remove the old version :\n\n```sh\npip3 uninstall iris-pex-embedded-python\n```\n\nor manually remove the `grongier` folder in `<iris_installation>/lib/python/`\n\nor force the installation with pip :\n\n```sh\npip3 install --upgrade iris-pex-embedded-python --target <iris_installation>/lib/python/\n```\n\n# 4. How to Run the Sample\n\n## 4.1. Docker containers\n\n\nIn order to have access to the InterSystems images, we need to go to the following url: http://container.intersystems.com. After connecting with our InterSystems credentials, we will get our password to connect to the registry. In the docker VScode addon, in the image tab, by pressing connect registry and entering the same url as before (http://container.intersystems.com) as a generic registry, we will be asked to give our credentials. The login is the usual one but the password is the one we got from the website.\n\nFrom there, we should be able to build and compose our containers (with the `docker-compose.yml` and `Dockerfile` files given).\n\n## 4.2. Management Portal and VSCode\n\nThis repository is ready for [VS Code](https://code.visualstudio.com/).\n\nOpen the locally-cloned `interoperability-embedeed-python` folder in VS Code.\n\nIf prompted (bottom right corner), install the recommended extensions.\n\n**IMPORTANT**: When prompted, reopen the folder inside the container so you will be able to use the python components within it. The first time you do this it may take several minutes while the container is readied.\n\nBy opening the folder remote you enable VS Code and any terminals you open within it to use the python components within the container. Configure these to use `/usr/irissys/bin/irispython`\n\n<img width=\"1614\" alt=\"PythonInterpreter\" src=\"https://user-images.githubusercontent.com/47849411/145864423-2de24aaa-036c-4beb-bda0-3a73fe15ccbd.png\">\n\n## 4.3. Open the production\nTo open the production you can go to [production](http://localhost:52773/csp/irisapp/EnsPortal.ProductionConfig.zen?PRODUCTION=PEX.Production).<br>\nYou can also click on the bottom on the `127.0.0.1:52773[IRISAPP]` button and select `Open Management Portal` then, click on [Interoperability] and [Configure] menus then click [productions] and [Go].\n\nThe production already has some code sample.\n\nHere we can see the production and our pure python services and operations:\n<img width=\"1177\" alt=\"interop-screenshot\" src=\"https://user-images.githubusercontent.com/47849411/131305197-d19511fd-6e05-4aec-a525-c88e6ebd0971.png\">\n\n<br>\n\nNew json trace for python native messages :\n<img width=\"910\" alt=\"json-message-trace\" src=\"https://user-images.githubusercontent.com/47849411/131305211-b8beb2c0-438d-4afc-a6d2-f94d854373ae.png\">\n\n# 5. What's inside the repository\n\n## 5.1. Dockerfile\n\nA dockerfile which install some python dependancies (pip, venv) and sudo in the container for conviencies.\nThen it create the dev directory and copy in it this git repository.\n\nIt starts IRIS and activates **%Service_CallIn** for **Python Shell**.\nUse the related docker-compose.yml to easily setup additional parametes like port number and where you map keys and host folders.\n\nThis dockerfile ends with the installation of requirements for python modules.\n\nUse .env/ file to adjust the dockerfile being used in docker-compose.\n\n## 5.2. .vscode/settings.json\n\nSettings file to let you immedietly code in VSCode with [VSCode ObjectScript plugin](https://marketplace.visualstudio.com/items?itemName=daimor.vscode-objectscript)\n\n## 5.3. .vscode/launch.json\nConfig file if you want to debug with VSCode ObjectScript\n\n[Read about all the files in this article](https://community.intersystems.com/post/dockerfile-and-friends-or-how-run-and-collaborate-objectscript-projects-intersystems-iris)\n\n## 5.4. .vscode/extensions.json\nRecommendation file to add extensions if you want to run with VSCode in the container.\n\n[More information here](https://code.visualstudio.com/docs/remote/containers)\n\nThis is very useful to work with embedded python.\n\n## 5.5. src folder\n\n```\nsrc\n\u251c\u2500\u2500 Grongier\n\u2502   \u2514\u2500\u2500 PEX // ObjectScript classes that wrap python code\n\u2502       \u251c\u2500\u2500 BusinessOperation.cls\n\u2502       \u251c\u2500\u2500 BusinessProcess.cls\n\u2502       \u251c\u2500\u2500 BusinessService.cls\n\u2502       \u251c\u2500\u2500 Common.cls\n\u2502       \u251c\u2500\u2500 Director.cls\n\u2502       \u251c\u2500\u2500 InboundAdapter.cls\n\u2502       \u251c\u2500\u2500 Message.cls\n\u2502       \u251c\u2500\u2500 OutboundAdapter.cls\n\u2502       \u251c\u2500\u2500 Python.cls\n\u2502       \u251c\u2500\u2500 Test.cls\n\u2502       \u2514\u2500\u2500 _utils.cls\n\u251c\u2500\u2500 PEX // Some example of wrapped classes\n\u2502   \u2514\u2500\u2500 Production.cls\n\u2514\u2500\u2500 python\n    \u251c\u2500\u2500 demo // Actual python code to run this demo\n    |   `-- reddit\n    |       |-- adapter.py\n    |       |-- bo.py\n    |       |-- bp.py\n    |       |-- bs.py\n    |       |-- message.py\n    |       `-- obj.py\n    \u251c\u2500\u2500 dist // Wheel used to implement python interoperability components\n    \u2502   \u2514\u2500\u2500 grongier_pex-1.2.4-py3-none-any.whl\n    \u251c\u2500\u2500 grongier\n    \u2502   \u2514\u2500\u2500 pex // Helper classes to implement interoperability components\n    \u2502       \u251c\u2500\u2500 _business_host.py\n    \u2502       \u251c\u2500\u2500 _business_operation.py\n    \u2502       \u251c\u2500\u2500 _business_process.py\n    \u2502       \u251c\u2500\u2500 _business_service.py\n    \u2502       \u251c\u2500\u2500 _common.py\n    \u2502       \u251c\u2500\u2500 _director.py\n    \u2502       \u251c\u2500\u2500 _inbound_adapter.py\n    \u2502       \u251c\u2500\u2500 _message.py\n    \u2502       \u251c\u2500\u2500 _outbound_adapter.py\n    \u2502       \u251c\u2500\u2500 __init__.py\n    \u2502       \u2514\u2500\u2500 _utils.py\n    \u2514\u2500\u2500 setup.py // setup to build the wheel\n```\n# 6. How it works\n\n## 6.1. The `__init__.py`file\nThis file will allow us to create the classes to import in the code.<br>\nIt gets from the multiple files seen earlier the classes and make them into callable classes.\nThat way, when you wish to create a business operation, for example, you can just do:\n```python\nfrom grongier.pex import BusinessOperation\n```\n\n## 6.2. The `common` class\nThe common class shouldn't be called by the user, it defines almost all the other classes.<br>\nThis class defines:\n\n`on_init`: The on_init() method is called when the component is started.<br> Use the on_init() method to initialize any structures needed by the component.\n\n`on_tear_down`: Called before the component is terminated.<br> Use it to free any structures.\n\n`on_connected`: The on_connected() method is called when the component is connected or reconnected after being disconnected.<br>Use the on_connected() method to initialize any structures needed by the component.\n\n`log_info`: Write a log entry of type \"info\". :log entries can be viewed in the management portal.\n\n`log_alert`: Write a log entry of type \"alert\". :log entries can be viewed in the management portal.\n\n`log_warning`: Write a log entry of type \"warning\". :log entries can be viewed in the management portal.\n\n`log_error`: Write a log entry of type \"error\". :log entries can be viewed in the management portal.\n\n## 6.3. The `business_host` class\nThe business host class shouldn't be called by the user, it is the base class for all the business classes.<br>\nThis class defines:\n\n`send_request_sync`: Send the specified message to the target business process or business operation synchronously.            \n**Parameters**:<br>\n- **target**: a string that specifies the name of the business process or operation to receive the request. <br>\n    The target is the name of the component as specified in the Item Name property in the production definition, not the class name of the component.\n- **request**: specifies the message to send to the target. The request is either an instance of a class that is a subclass of Message class or of IRISObject class.<br>\n    If the target is a build-in ObjectScript component, you should use the IRISObject class. The IRISObject class enables the PEX framework to convert the message to a class supported by the target.\n- **timeout**: an optional integer that specifies the number of seconds to wait before treating the send request as a failure. The default value is -1, which means wait forever.<br>\ndescription: an optional string parameter that sets a description property in the message header. The default is None.\n\n**Returns**:\n    the response object from target.\n\n**Raises**:\nTypeError: if request is not of type Message or IRISObject.\n\n<br><br>\n\n`send_request_async`: Send the specified message to the target business process or business operation asynchronously.\n**Parameters**:<br>\n- **target**: a string that specifies the name of the business process or operation to receive the request. <br>\n    The target is the name of the component as specified in the Item Name property in the production definition, not the class name of the component.\n- **request**: specifies the message to send to the target. The request is an instance of IRISObject or of a subclass of Message.<br>\n    If the target is a built-in ObjectScript component, you should use the IRISObject class. The IRISObject class enables the PEX framework to convert the message to a class supported by the target.\n- **description**: an optional string parameter that sets a description property in the message header. The default is None.\n\n**Raises**:\nTypeError: if request is not of type Message or IRISObject.\n\n<br><br>\n\n`get_adapter_type`: Name of the registred Adapter.\n\n\n## 6.4. The `inbound_adapter` class\nInbound Adapter in Python are subclass from grongier.pex.InboundAdapter in Python, that inherit from all the functions of the [common class](#72-the-common-class).<br>\nThis class is responsible for receiving the data from the external system, validating the data, and sending it to the business service by calling the BusinessHost process_input method.\nThis class defines:\n\n`on_task`: Called by the production framework at intervals determined by the business service CallInterval property.<br>\nThe message can have any structure agreed upon by the inbound adapter and the business service.\n\nExample of an inbound adapter ( situated in the src/python/demo/reddit/adapter.py file ):\n```python\nfrom grongier.pex import InboundAdapter\nimport requests\nimport iris\nimport json\n\nclass RedditInboundAdapter(InboundAdapter):\n    \"\"\"\n    This adapter use requests to fetch self.limit posts as data from the reddit\n    API before calling process_input for each post.\n    \"\"\"\n    def on_init(self):\n        \n        if not hasattr(self,'feed'):\n            self.feed = \"/new/\"\n        \n        if self.limit is None:\n            raise TypeError('no Limit field')\n        \n        self.last_post_name = \"\"\n        \n        return 1\n\n    def on_task(self):\n        self.log_info(f\"LIMIT:{self.limit}\")\n        if self.feed == \"\" :\n            return 1\n        \n        tSC = 1\n        # HTTP Request\n        try:\n            server = \"https://www.reddit.com\"\n            request_string = self.feed+\".json?before=\"+self.last_post_name+\"&limit=\"+self.limit\n            self.log_info(server+request_string)\n            response = requests.get(server+request_string)\n            response.raise_for_status()\n\n            data = response.json()\n            updateLast = 0\n\n            for key, value in enumerate(data['data']['children']):\n                if value['data']['selftext']==\"\":\n                    continue\n                post = iris.cls('dc.Reddit.Post')._New()\n                post._JSONImport(json.dumps(value['data']))\n                post.OriginalJSON = json.dumps(value)\n                if not updateLast:\n                    self.LastPostName = value['data']['name']\n                    updateLast = 1\n                response = self.BusinessHost.ProcessInput(post)\n        except requests.exceptions.HTTPError as err:\n            if err.response.status_code == 429:\n                self.log_warning(err.__str__())\n            else:\n                raise err\n        except Exception as err: \n            self.log_error(err.__str__())\n            raise err\n\n        return tSC\n```\n\n## 6.5. The `outbound_adapter` class\nOutbound Adapter in Python are subclass from grongier.pex.OutboundAdapter in Python, that inherit from all the functions of the [common class](#72-the-common-class).<br>\nThis class is responsible for sending the data to the external system.\n\nThe Outbound Adapter gives the Operation the possibility to have a heartbeat notion.\nTo activate this option, the CallInterval parameter of the adapter must be strictly greater than 0.\n\n<img width=\"301\" alt=\"image\" src=\"https://user-images.githubusercontent.com/47849411/178230243-39806602-a63d-4a89-9563-fcf6836d0515.png\">\n\nExample of an outbound adapter ( situated in the src/python/demo/reddit/adapter.py file ):\n\n```python\nclass TestHeartBeat(OutboundAdapter):\n\n    def on_keepalive(self):\n        self.log_info('beep')\n\n    def on_task(self):\n        self.log_info('on_task')\n```\n\n## 6.6. The `business_service` class\nThis class is responsible for receiving the data from external system and sending it to business processes or business operations in the production.<br>\nThe business service can use an adapter to access the external system, which is specified overriding the get_adapter_type method.<br>\nThere are three ways of implementing a business service:<br>\n- Polling business service with an adapter - The production framework at regular intervals calls the adapter\u2019s OnTask() method, \n    which sends the incoming data to the the business service ProcessInput() method, which, in turn calls the OnProcessInput method with your code.\n\n- Polling business service that uses the default adapter - In this case, the framework calls the default adapter's OnTask method with no data. \n    The OnProcessInput() method then performs the role of the adapter and is responsible for accessing the external system and receiving the data.\n\n- Nonpolling business service - The production framework does not initiate the business service. Instead custom code in either a long-running process \n    or one that is started at regular intervals initiates the business service by calling the Director.CreateBusinessService() method.\n\nBusiness service in Python are subclass from grongier.pex.BusinessService in Python, that inherit from all the functions of the [business host](#73-the-business_host-class).<br>\nThis class defines:\n\n`on_process_input`: Receives the message from the inbond adapter via the PRocessInput method and is responsible for forwarding it to target business processes or operations.<br>\nIf the business service does not specify an adapter, then the default adapter calls this method with no message and the business service is responsible for receiving the data from the external system and validating it.\n**Parameters**:<br>\n- **message_input**: an instance of IRISObject or subclass of Message containing the data that the inbound adapter passes in.<br>\nThe message can have any structure agreed upon by the inbound adapter and the business service. \n\n<br><br>\n\nExample of a business service ( situated in the src/python/demo/reddit/bs.py file ):\n```python\nfrom grongier.pex import BusinessService\n\nimport iris\n\nfrom message import PostMessage\nfrom obj import PostClass\n\nclass RedditServiceWithPexAdapter(BusinessService):\n    \"\"\"\n    This service use our python Python.RedditInboundAdapter to receive post\n    from reddit and call the FilterPostRoutingRule process.\n    \"\"\"\n    def get_adapter_type():\n        \"\"\"\n        Name of the registred Adapter\n        \"\"\"\n        return \"Python.RedditInboundAdapter\"\n\n    def on_process_input(self, message_input):\n        msg = iris.cls(\"dc.Demo.PostMessage\")._New()\n        msg.Post = message_input\n        return self.send_request_sync(self.target,msg)\n\n    def on_init(self):\n        \n        if not hasattr(self,'target'):\n            self.target = \"Python.FilterPostRoutingRule\"\n        \n        return\n```\n\n\n## 6.7. The `business_process` class\nTypically contains most of the logic in a production.<br>\nA business process can receive messages from a business service, another business process, or a business operation.<br>\nIt can modify the message, convert it to a different format, or route it based on the message contents.<br>\nThe business process can route a message to a business operation or another business process.<br>\nBusiness processes in Python are subclass from grongier.pex.BusinessProcess in Python, that inherit from all the functions of the [business host](#73-the-business_host-class).<br>\nThis class defines:\n\n`on_request`: Handles requests sent to the business process. A production calls this method whenever an initial request for a specific business process arrives on the appropriate queue and is assigned a job in which to execute.<br>\n**Parameters**:<br>\n- **request**: An instance of IRISObject or subclass of Message that contains the request message sent to the business process.\n\n**Returns**:\nAn instance of IRISObject or subclass of Message that contains the response message that this business process can return\nto the production component that sent the initial message.\n\n<br><br>\n\n`on_response`: Handles responses sent to the business process in response to messages that it sent to the target.<br>\nA production calls this method whenever a response for a specific business process arrives on the appropriate queue and is assigned a job in which to execute.<br>\nTypically this is a response to an asynchronous request made by the business process where the responseRequired parameter has a true value.<br>\n**Parameters**:<br>\n- **request**: An instance of IRISObject or subclass of Message that contains the initial request message sent to the business process.\n- **response**: An instance of IRISObject or subclass of Message that contains the response message that this business process can return to the production component that sent the initial message.\n- **callRequest**: An instance of IRISObject or subclass of Message that contains the request that the business process sent to its target.\n- **callResponse**: An instance of IRISObject or subclass of Message that contains the incoming response.\n- **completionKey**: A string that contains the completionKey specified in the completionKey parameter of the outgoing SendAsync() method.\n\n**Returns**:\nAn instance of IRISObject or subclass of Message that contains the response message that this business process can return\nto the production component that sent the initial message.\n\n<br><br>\n\n`on_complete`: Called after the business process has received and handled all responses to requests it has sent to targets.<br>\n**Parameters**: \n- **request**: An instance of IRISObject or subclass of Message that contains the initial request message sent to the business process.<br>\n- **response**: An instance of IRISObject or subclass of Message that contains the response message that this business process can return to the production component that sent the initial message.\n\n**Returns**:\nAn instance of IRISObject or subclass of Message that contains the response message that this business process can return to the production component that sent the initial message.\n\n<br><br>\n\nExample of a business process ( situated in the src/python/demo/reddit/bp.py file ):\n```python\nfrom grongier.pex import BusinessProcess\n\nfrom message import PostMessage\nfrom obj import PostClass\n\nclass FilterPostRoutingRule(BusinessProcess):\n    \"\"\"\n    This process receive a PostMessage containing a reddit post.\n    It then understand if the post is about a dog or a cat or nothing and\n    fill the right infomation inside the PostMessage before sending it to\n    the FileOperation operation.\n    \"\"\"\n    def on_init(self):\n        \n        if not hasattr(self,'target'):\n            self.target = \"Python.FileOperation\"\n        \n        return\n\n    def on_request(self, request):\n\n        if 'dog'.upper() in request.post.selftext.upper():\n            request.to_email_address = 'dog@company.com'\n            request.found = 'Dog'\n        if 'cat'.upper() in request.post.selftext.upper():\n            request.to_email_address = 'cat@company.com'\n            request.found = 'Cat'\n\n        if request.found is not None:\n            return self.send_request_sync(self.target,request)\n        else:\n            return\n```\n\n## 6.8. The `business_operation` class\nThis class is responsible for sending the data to an external system or a local system such as an iris database.<br>\nThe business operation can optionally use an adapter to handle the outgoing message which is specified overriding the get_adapter_type method.<br>\nIf the business operation has an adapter, it uses the adapter to send the message to the external system.<br>\nThe adapter can either be a PEX adapter, an ObjectScript adapter or a [python adapter](#75-the-outbound_adapter-class).<br>\nBusiness operation in Python are subclass from grongier.pex.BusinessOperation in Python, that inherit from all the functions of the [business host](#73-the-business_host-class).<br>\n\n### 6.8.1. The dispacth system\nIn a business operation it is possbile to create any number of function [similar to the on_message method](#782-the-methods) that will take as argument a [typed request](#711-the-messages) like this `my_special_message_method(self,request: MySpecialMessage)`.\n\nThe dispatch system will automatically analyze any request arriving to the operation and dispacth the requests depending of their type. If the type of the request is not recognized or is not specified in any **on_message like function**, the dispatch system will send it to the `on_message` function.\n\n### 6.8.2. The methods\nThis class defines:\n\n`on_message`: Called when the business operation receives a message from another production component [that can not be dispatched to another function](#781-the-dispacth-system).<br>\nTypically, the operation will either send the message to the external system or forward it to a business process or another business operation.\nIf the operation has an adapter, it uses the Adapter.invoke() method to call the method on the adapter that sends the message to the external system.\nIf the operation is forwarding the message to another production component, it uses the SendRequestAsync() or the SendRequestSync() method.<br>\n**Parameters**:\n- **request**: An instance of either a subclass of Message or of IRISObject containing the incoming message for the business operation.\n\n**Returns**:\nThe response object\n\nExample of a business operation ( situated in the src/python/demo/reddit/bo.py file ):\n```python\nfrom grongier.pex import BusinessOperation\n\nfrom message import MyRequest,MyMessage\n\nimport iris\n\nimport os\nimport datetime\nimport smtplib\nfrom email.mime.text import MIMEText\n\nclass EmailOperation(BusinessOperation):\n    \"\"\"\n    This operation receive a PostMessage and send an email with all the\n    important information to the concerned company ( dog or cat company )\n    \"\"\"\n\n    def my_message(self,request:MyMessage):\n        sender = 'admin@example.com'\n        receivers = 'toto@example.com'\n        port = 1025\n        msg = MIMEText(request.toto)\n\n        msg['Subject'] = 'MyMessage'\n        msg['From'] = sender\n        msg['To'] = receivers\n\n        with smtplib.SMTP('localhost', port) as server:\n            server.sendmail(sender, receivers, msg.as_string())\n            print(\"Successfully sent email\")\n\n    def on_message(self, request):\n\n        sender = 'admin@example.com'\n        receivers = [ request.to_email_address ]\n\n\n        port = 1025\n        msg = MIMEText('This is test mail')\n\n        msg['Subject'] = request.found+\" found\"\n        msg['From'] = 'admin@example.com'\n        msg['To'] = request.to_email_address\n\n        with smtplib.SMTP('localhost', port) as server:\n            \n            # server.login('username', 'password')\n            server.sendmail(sender, receivers, msg.as_string())\n            print(\"Successfully sent email\")\n\n```\nIf this operation is called using a MyRequest message, the my_message function will be called thanks to the dispatcher, otherwise the on_message function will be called.\n\n## 6.9. The `director` class\nThe Director class is used for nonpolling business services, that is, business services which are not automatically called by the production framework (through the inbound adapter) at the call interval.<br>\nInstead these business services are created by a custom application by calling the Director.create_business_service() method.<br>\nThis class defines:\n\n`create_business_service`: The create_business_service() method initiates the specified business service.<br>\n**Parameters**:\n- **connection**: an IRISConnection object that specifies the connection to an IRIS instance for Java.\n- **target**: a string that specifies the name of the business service in the production definition.\n\n**Returns**:\nan object that contains an instance of IRISBusinessService\n\n`start_production`: The start_production() method starts the production.<br>\n**Parameters**:\n- **production_name**: a string that specifies the name of the production to start.\n\n`stop_production`: The stop_production() method stops the production.<br>\n**Parameters**:\n- **production_name**: a string that specifies the name of the production to stop.\n\n`restart_production`: The restart_production() method restarts the production.<br>\n**Parameters**:\n- **production_name**: a string that specifies the name of the production to restart.\n\n`list_productions`: The list_productions() method returns a dictionary of the names of the productions that are currently running.<br>\n\n## 6.10. The `objects`\nWe will use `dataclass` to hold information in our [messages](#711-the-messages) in a `obj.py` file.\n\nExample of an object ( situated in the src/python/demo/reddit/obj.py file ):\n```python\nfrom dataclasses import dataclass\n\n@dataclass\nclass PostClass:\n    title: str\n    selftext : str\n    author: str\n    url: str\n    created_utc: float = None\n    original_json: str = None\n```\n\n## 6.11. The `messages`\nThe messages will contain one or more [objects](#710-the-objects), located in the `obj.py` file.<br>\nMessages, requests and responses all inherit from the `grongier.pex.Message` class.\n\nThese messages will allow us to transfer information between any business service/process/operation.\n\nExample of a message ( situated in the src/python/demo/reddit/message.py file ):\n```python\nfrom grongier.pex import Message\n\nfrom dataclasses import dataclass\n\nfrom obj import PostClass\n\n@dataclass\nclass PostMessage(Message):\n    post:PostClass = None\n    to_email_address:str = None\n    found:str = None\n```\n\nWIP It is to be noted that it is needed to use types when you define an object or a message.\n\n## 6.12. How to regsiter a component \n\nYou can register a component to iris in many way :\n* Only one component with `register_component` \n* All the component in a file with `register_file` \n* All the component in a folder with `register_folder` \n\n### 6.12.1. register_component\n\nStart an embedded python shell :\n\n```sh\n/usr/irissys/bin/irispython\n```\n\nThen use this class method to add a new py file to the component list for interoperability.\n\n```python\nfrom grongier.pex import Utils\nUtils.register_component(<ModuleName>,<ClassName>,<PathToPyFile>,<OverWrite>,<NameOfTheComponent>)\n```\n\ne.g :\n```python\nfrom grongier.pex import Utils\nUtils.register_component(\"MyCombinedBusinessOperation\",\"MyCombinedBusinessOperation\",\"/irisdev/app/src/python/demo/\",1,\"PEX.MyCombinedBusinessOperation\")\n```\n\n### 6.12.2. register_file\n\nStart an embedded python shell :\n\n```sh\n/usr/irissys/bin/irispython\n```\n\nThen use this class method to add a new py file to the component list for interoperability.\n\n```python\nfrom grongier.pex import Utils\nUtils.register_file(<File>,<OverWrite>,<PackageName>)\n```\n\ne.g :\n```python\nfrom grongier.pex import Utils\nUtils.register_file(\"/irisdev/app/src/python/demo/bo.py\",1,\"PEX\")\n```\n\n### 6.12.3. register_folder\n\nStart an embedded python shell :\n\n```sh\n/usr/irissys/bin/irispython\n```\n\nThen use this class method to add a new py file to the component list for interoperability.\n\n```python\nfrom grongier.pex import Utils\nUtils.register_folder(<Path>,<OverWrite>,<PackageName>)\n```\n\ne.g :\n```python\nfrom grongier.pex import Utils\nUtils.register_folder(\"/irisdev/app/src/python/demo/\",1,\"PEX\")\n```\n\n### 6.12.4. migrate\n\nStart an embedded python shell :\n\n```sh\n/usr/irissys/bin/irispython\n```\n\nThen use this static method to migrate the settings file to the iris framework.\n\n```python\nfrom grongier.pex import Utils\nUtils.migrate()\n```\n\n#### 6.12.4.1. setting.py file\n\nThis file is used to store the settings of the interoperability components.\n\nIt has two sections :\n* `CLASSES` : This section is used to store the classes of the interoperability components.\n* `PRODUCTIONS` : This section is used to store the productions of the interoperability components.\n\ne.g :\n```python\nimport bp\nfrom bo import *\nfrom bs import *\n\nCLASSES = {\n    'Python.RedditService': RedditService,\n    'Python.FilterPostRoutingRule': bp.FilterPostRoutingRule,\n    'Python.FileOperation': FileOperation,\n    'Python.FileOperationWithIrisAdapter': FileOperationWithIrisAdapter,\n}\n\nPRODUCTIONS = [\n    {\n        'dc.Python.Production': {\n        \"@Name\": \"dc.Demo.Production\",\n        \"@TestingEnabled\": \"true\",\n        \"@LogGeneralTraceEvents\": \"false\",\n        \"Description\": \"\",\n        \"ActorPoolSize\": \"2\",\n        \"Item\": [\n            {\n                \"@Name\": \"Python.FileOperation\",\n                \"@Category\": \"\",\n                \"@ClassName\": \"Python.FileOperation\",\n                \"@PoolSize\": \"1\",\n                \"@Enabled\": \"true\",\n                \"@Foreground\": \"false\",\n                \"@Comment\": \"\",\n                \"@LogTraceEvents\": \"true\",\n                \"@Schedule\": \"\",\n                \"Setting\": {\n                    \"@Target\": \"Host\",\n                    \"@Name\": \"%settings\",\n                    \"#text\": \"path=/tmp\"\n                }\n            },\n            {\n                \"@Name\": \"Python.RedditService\",\n                \"@Category\": \"\",\n                \"@ClassName\": \"Python.RedditService\",\n                \"@PoolSize\": \"1\",\n                \"@Enabled\": \"true\",\n                \"@Foreground\": \"false\",\n                \"@Comment\": \"\",\n                \"@LogTraceEvents\": \"false\",\n                \"@Schedule\": \"\",\n                \"Setting\": [\n                    {\n                        \"@Target\": \"Host\",\n                        \"@Name\": \"%settings\",\n                        \"#text\": \"limit=10\\nother<10\"\n                    }\n                ]\n            },\n            {\n                \"@Name\": \"Python.FilterPostRoutingRule\",\n                \"@Category\": \"\",\n                \"@ClassName\": \"Python.FilterPostRoutingRule\",\n                \"@PoolSize\": \"1\",\n                \"@Enabled\": \"true\",\n                \"@Foreground\": \"false\",\n                \"@Comment\": \"\",\n                \"@LogTraceEvents\": \"false\",\n                \"@Schedule\": \"\"\n            }\n        ]\n    }\n    }\n]\n```\n\n##### 6.12.4.1.1. CLASSES section\n\nThis section is used to store the classes of the interoperability components.\n\nIt aims to help to register the components.\n\nThis dictionary has the following structure :\n* Key : The name of the component\n* Value : \n  * The class of the component (you have to import it before)\n  * The module of the component (you have to import it before)\n  * Another dictionary with the following structure :\n    * `module` : Name of the module of the component (optional)\n    * `class` : Name of the class of the component (optional)\n    * `path` : The path of the component (mandatory)\n\ne.g :\n\nWhen Value is a class or a module:\n```python\nimport bo\nimport bp\nfrom bs import RedditService\n\nCLASSES = {\n    'Python.RedditService': RedditService,\n    'Python.FilterPostRoutingRule': bp.FilterPostRoutingRule,\n    'Python.FileOperation': bo,\n}\n```\n\nWhen Value is a dictionary :\n```python\nCLASSES = {\n    'Python.RedditService': {\n        'module': 'bs',\n        'class': 'RedditService',\n        'path': '/irisdev/app/src/python/demo/'\n    },\n    'Python.Module': {\n        'module': 'bp',\n        'path': '/irisdev/app/src/python/demo/'\n    },\n    'Python.Package': {\n        'path': '/irisdev/app/src/python/demo/'\n    },\n}\n```\n\n##### 6.12.4.1.2. Productions section\n\nThis section is used to store the productions of the interoperability components.\n\nIt aims to help to register a production.\n\nThis list has the following structure :\n* A list of dictionary with the following structure :\n  * `dc.Python.Production` : The name of the production\n    * `@Name` : The name of the production\n    * `@TestingEnabled` : The testing enabled of the production\n    * `@LogGeneralTraceEvents` : The log general trace events of the production\n    * `Description` : The description of the production\n    * `ActorPoolSize` : The actor pool size of the production\n    * `Item` : The list of the items of the production\n      * `@Name` : The name of the item\n      * `@Category` : The category of the item\n      * `@ClassName` : The class name of the item\n      * `@PoolSize` : The pool size of the item\n      * `@Enabled` : The enabled of the item\n      * `@Foreground` : The foreground of the item\n      * `@Comment` : The comment of the item\n      * `@LogTraceEvents` : The log trace events of the item\n      * `@Schedule` : The schedule of the item\n      * `Setting` : The list of the settings of the item\n        * `@Target` : The target of the setting\n        * `@Name` : The name of the setting\n        * `#text` : The value of the setting\n\nThe minimum structure of a production is :\n```python\nPRODUCTIONS = [\n        {\n            'UnitTest.Production': {\n                \"Item\": [\n                    {\n                        \"@Name\": \"Python.FileOperation\",\n                        \"@ClassName\": \"Python.FileOperation\",\n                    },\n                    {\n                        \"@Name\": \"Python.EmailOperation\",\n                        \"@ClassName\": \"UnitTest.Package.EmailOperation\"\n                    }\n                ]\n            }\n        } \n    ]\n```\n\nYou can also set in `@ClassName` an item from the CLASSES section.\n\ne.g :\n```python\nfrom bo import FileOperation\nPRODUCTIONS = [\n        {\n            'UnitTest.Production': {\n                \"Item\": [\n                    {\n                        \"@Name\": \"Python.FileOperation\",\n                        \"@ClassName\": FileOperation,\n                    }\n                ]\n            }\n        } \n    ]\n```\n\nAs the production is a dictionary, you can add in value of the production dictionary an environment variable.\n\ne.g :\n```python\nimport os\n\nPRODUCTIONS = [\n        {\n            'UnitTest.Production': {\n                \"Item\": [\n                    {\n                        \"@Name\": \"Python.FileOperation\",\n                        \"@ClassName\": \"Python.FileOperation\",\n                        \"Setting\": {\n                            \"@Target\": \"Host\",\n                            \"@Name\": \"%settings\",\n                            \"#text\": os.environ['SETTINGS']\n                        }\n                    }\n                ]\n            }\n        } \n    ]\n```\n\n## 6.13. Direct use of Grongier.PEX\n\nIf you don't want to use the register_component util. You can add a Grongier.PEX.BusinessService component directly into the management portal and configure the properties :\n- %module :\n  - Module name of your python code\n- %classname :\n  - Classname of you component\n- %classpaths\n  - Path where you component is.\n    - This can one or more Classpaths (separated by '|' character) needed in addition to PYTHON_PATH\n\ne.g :\n<img width=\"800\" alt=\"component-config\" src=\"https://user-images.githubusercontent.com/47849411/131316308-e1898b19-11df-433b-b1c6-7f69d5cc9974.png\">\n\n# 7. Command line\n\nSince version 2.3.1, you can use the command line to register your components and productions.\n\nTo use it, you have to use the following command :\n```bash\niop \n```\n\noutput :\n```bash\nusage: python3 -m grongier.pex [-h] [-d DEFAULT] [-l] [-s START] [-k] [-S] [-r] [-M MIGRATE] [-e EXPORT] [-x] [-v] [-L]\noptional arguments:\n  -h, --help            display help and default production name\n  -d DEFAULT, --default DEFAULT\n                        set the default production\n  -l, --lists           list productions\n  -s START, --start START\n                        start a production\n  -k, --kill            kill a production (force stop)\n  -S, --stop            stop a production\n  -r, --restart         restart a production\n  -M MIGRATE, --migrate MIGRATE\n                        migrate production and classes with settings file\n  -e EXPORT, --export EXPORT\n                        export a production\n  -x, --status          status a production\n  -v, --version         display version\n  -L, --log             display log\n\ndefault production: PEX.Production\n```\n\n## 7.1. help\n\nThe help command display the help and the default production name.\n\n```bash\niop -h\n```\n\noutput :\n```bash\nusage: python3 -m grongier.pex [-h] [-d DEFAULT] [-l] [-s START] [-k] [-S] [-r] [-M MIGRATE] [-e EXPORT] [-x] [-v] [-L]\n...\ndefault production: PEX.Production\n```\n\n## 7.2. default\n\nThe default command set the default production.\n\nWith no argument, it display the default production.\n\n```bash\niop -d\n```\n\noutput :\n```bash\ndefault production: PEX.Production\n```\n\nWith an argument, it set the default production.\n\n```bash\niop -d PEX.Production\n```\n\n## 7.3. lists\n\nThe lists command list productions.\n\n```bash\niop -l\n```\n\noutput :\n```bash\n{\n    \"PEX.Production\": {\n        \"Status\": \"Stopped\",\n        \"LastStartTime\": \"2023-05-31 11:13:51.000\",\n        \"LastStopTime\": \"2023-05-31 11:13:54.153\",\n        \"AutoStart\": 0\n    }\n}\n```\n\n## 7.4. start\n\nThe start command start a production.\n\nTo exit the command, you have to press CTRL+C.\n\n```bash\niop -s PEX.Production\n```\n\noutput :\n```bash\n2021-08-30 15:13:51.000 [PEX.Production] INFO: Starting production\n2021-08-30 15:13:51.000 [PEX.Production] INFO: Starting item Python.FileOperation\n2021-08-30 15:13:51.000 [PEX.Production] INFO: Starting item Python.EmailOperation\n...\n```\n\n## 7.5. kill\n\nThe kill command kill a production (force stop).\n\nKill command is the same as stop command but with a force stop.\n\nKill command doesn't take an argument because only one production can be running.\n\n```bash\niop -k \n```\n\n## 7.6. stop\n\nThe stop command stop a production.\n\nStop command doesn't take an argument because only one production can be running.\n\n```bash\niop -S \n```\n\n## 7.7. restart\n\nThe restart command restart a production.\n\nRestart command doesn't take an argument because only one production can be running.\n\n```bash\niop -r \n```\n\n## 7.8. migrate\n\nThe migrate command migrate a production and classes with settings file.\n\nMigrate command must take the absolute path of the settings file.\n\nSettings file must be in the same folder as the python code.\n\n```bash\niop -M /tmp/settings.py\n```\n\n## 7.9. export\n\nThe export command export a production.\n\nIf no argument is given, the export command export the default production.\n\n```bash\niop -e\n```\n\nIf an argument is given, the export command export the production given in argument.\n\n```bash\niop -e PEX.Production\n```\n\noutput :\n```bash\n{\n    \"Production\": {\n        \"@Name\": \"PEX.Production\",\n        \"@TestingEnabled\": \"true\",\n        \"@LogGeneralTraceEvents\": \"false\",\n        \"Description\": \"\",\n        \"ActorPoolSize\": \"2\",\n        \"Item\": [\n            {\n                \"@Name\": \"Python.FileOperation\",\n                \"@Category\": \"\",\n                \"@ClassName\": \"Python.FileOperation\",\n                \"@PoolSize\": \"1\",\n                \"@Enabled\": \"true\",\n                \"@Foreground\": \"false\",\n                \"@Comment\": \"\",\n                \"@LogTraceEvents\": \"true\",\n                \"@Schedule\": \"\",\n                \"Setting\": [\n                    {\n                        \"@Target\": \"Adapter\",\n                        \"@Name\": \"Charset\",\n                        \"#text\": \"utf-8\"\n                    },\n                    {\n                        \"@Target\": \"Adapter\",\n                        \"@Name\": \"FilePath\",\n                        \"#text\": \"/irisdev/app/output/\"\n                    },\n                    {\n                        \"@Target\": \"Host\",\n                        \"@Name\": \"%settings\",\n                        \"#text\": \"path=/irisdev/app/output/\"\n                    }\n                ]\n            }\n        ]\n    }\n}\n```\n\n## 7.10. status\n\nThe status command status a production.\n\nStatus command doesn't take an argument because only one production can be running.\n\n```bash\niop -x \n```\n\noutput :\n```bash\n{\n    \"Production\": \"PEX.Production\",\n    \"Status\": \"stopped\"\n}\n```\n\nStatus can be :\n- stopped\n- running\n- suspended\n- troubled\n\n## 7.11. version\n\nThe version command display the version.\n\n```bash\niop -v\n```\n\noutput :\n```bash\n2.3.0\n```\n\n## 7.12. log\n\nThe log command display the log.\n\nTo exit the command, you have to press CTRL+C.\n\n```bash\niop -L\n```\n\noutput :\n```bash\n2021-08-30 15:13:51.000 [PEX.Production] INFO: Starting production\n2021-08-30 15:13:51.000 [PEX.Production] INFO: Starting item Python.FileOperation\n2021-08-30 15:13:51.000 [PEX.Production] INFO: Starting item Python.EmailOperation\n...\n```\n\n# 8. Credits\n\nMost of the code came from PEX for Python by Mo Cheng and Summer Gerry.\n\nWorks only on IRIS 2021.2 +\n",
    "bugtrack_url": null,
    "license": "MIT License  Copyright (c) 2019 InterSystems Developer Community  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:  The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.  THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ",
    "summary": "Iris Interoperability based on Embedded Python",
    "version": "2.3.27",
    "project_urls": {
        "documentation": "https://github.com/grongierisc/interoperability-embedded-python/blob/master/README.md",
        "homepage": "https://github.com/grongierisc/interoperability-embedded-python",
        "issues": "https://github.com/grongierisc/interoperability-embedded-python/issues",
        "repository": "https://github.com/grongierisc/interoperability-embedded-python"
    },
    "split_keywords": [
        "iris",
        " intersystems",
        " python",
        " embedded"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "c347b120e31f186adca1ec0408d65b890398feedc7872b299055afc84197bb76",
                "md5": "3cb61fec3ff345f11654587c039f105a",
                "sha256": "adb1e89d374e081bd4db44153987899a9479eede73adcbdcf3f4f7af8598f55c"
            },
            "downloads": -1,
            "filename": "iris_pex_embedded_python-2.3.27-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "3cb61fec3ff345f11654587c039f105a",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": null,
            "size": 187514,
            "upload_time": "2024-04-09T17:52:29",
            "upload_time_iso_8601": "2024-04-09T17:52:29.577589Z",
            "url": "https://files.pythonhosted.org/packages/c3/47/b120e31f186adca1ec0408d65b890398feedc7872b299055afc84197bb76/iris_pex_embedded_python-2.3.27-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "7f6d6e45cff878884cf8552cfd1d6ab5241820b592e3f53d8a589c6983302612",
                "md5": "6072a851a21179adef1f6af8eefe171e",
                "sha256": "fb9bc0d09ee7118421768111cc43926ebec23b1cdf94b4bc6b3442cd495f5092"
            },
            "downloads": -1,
            "filename": "iris_pex_embedded_python-2.3.27.tar.gz",
            "has_sig": false,
            "md5_digest": "6072a851a21179adef1f6af8eefe171e",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": null,
            "size": 170047,
            "upload_time": "2024-04-09T17:52:32",
            "upload_time_iso_8601": "2024-04-09T17:52:32.039141Z",
            "url": "https://files.pythonhosted.org/packages/7f/6d/6e45cff878884cf8552cfd1d6ab5241820b592e3f53d8a589c6983302612/iris_pex_embedded_python-2.3.27.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-04-09 17:52:32",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "grongierisc",
    "github_project": "interoperability-embedded-python",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "requirements": [],
    "lcname": "iris-pex-embedded-python"
}
        
Elapsed time: 0.29923s