Settings Configuration
The migration file is the central configuration file for applying Python IOP
configuration to IRIS. It is commonly named settings.py, but it can be any
Python file, such as a single-file production named demo.py. It defines
classes, productions, schemas, and remote connection settings.
In this documentation, migrate means applying the file to IRIS. Bind or register means creating an IRIS proxy class for a Python component. Unbind or unregister means removing that generated proxy class. Unbind does not delete Python source files or production items.
Quick Start
Create a migration file in your project root:
from iop import BusinessOperation, Production
class MyBusinessOperation(BusinessOperation):
def on_message(self, request):
self.log_info("Hello from IoP")
return request
prod = Production("Demo.Production", testing_enabled=True)
prod.operation("MyBusinessOperation", MyBusinessOperation)
PRODUCTIONS = [prod]
Apply the migration file:
iop --migrate /path/to/your/project/settings.py
# or
iop --migrate /path/to/your/project/demo.py
If you bind a Python class to the wrong IRIS proxy class name, remove that binding with:
iop --unbind Python.MyBusinessOperation
Configuration Sections
The migration file supports four main sections:
| Section | Purpose |
|---|---|
PRODUCTIONS |
Define Python-authored production graphs with Production objects |
CLASSES |
Define standalone component bindings and native PersistentMessage classes |
SCHEMAS |
Register message schemas for DTL |
REMOTE_SETTINGS |
Configure remote IRIS connections |
CLASSES Section
Use CLASSES for standalone component bindings that are not declared in a
Python Production graph. New Python-authored productions usually do not need
component entries in CLASSES; migration registers component classes from the
Production graph.
Basic Usage
import bo
from bs import RedditService
CLASSES = {
'Python.RedditService': RedditService,
'Python.FileOperation': bo.FileOperation,
}
CLASSES also registers native PersistentMessage schemas. In this case, the key is the generated IRIS message body classname:
from msg import OrderMessage
CLASSES = {
"Demo.Msg.OrderMessage": OrderMessage,
}
For Python-authored productions, you can declare native messages on the
Production object instead:
from iop import Field, PersistentMessage, Production
class OrderMessage(PersistentMessage):
OrderId: str = Field(required=True)
prod = Production("Demo.Production")
prod.message("Demo.Msg.OrderMessage", OrderMessage)
PRODUCTIONS = [prod]
Regular Message and PydanticMessage classes do not go in CLASSES or
native message registration. Add them to SCHEMAS only when you need DTL schema
support.
PRODUCTIONS Section
Define complete production configurations with multiple components.
Pythonic Production
You can define productions with Production objects instead of raw dictionaries.
This is the authoring DSL for Python-defined topology: items, ports, settings,
and connections are declared in Python, then migration emits the same IRIS
production definition shape as the legacy dictionary format.
Python Production is the source of truth for Python-authored topology. IRIS
remains the runtime source of truth. Imported graphs are operational
reconstructions until metadata persistence makes round-trip fidelity possible.
The topology is modeled as a directed multigraph of possible communication routes. A route edge is not a DAG execution dependency; it says that a component may communicate with another component through a port or runtime route.
from dataclasses import dataclass
from iop import BusinessOperation, Message, PollingBusinessService, Production, target
@dataclass
class OrderRequest(Message):
order_id: str = ""
class FileService(PollingBusinessService):
Output = target()
def on_poll(self):
return self.send_request_sync(
self.Output,
OrderRequest(order_id="777"),
)
class OrderOperation(BusinessOperation):
def on_message(self, request):
return request
prod = Production("Demo.Production", testing_enabled=True)
file = prod.service("FileInput", FileService)
orders = prod.operation(OrderOperation)
prod.connect(file.Output, orders)
PRODUCTIONS = [prod]
target() declares an outbound target setting on the component class.
prod.connect() sets that setting to the destination component and
records graph edges available to Python.
You can also write the same production as a class declaration. The class is a
template; put an instance in PRODUCTIONS.
from dataclasses import dataclass
from iop import (
BusinessOperation,
Message,
OperationItem,
PollingBusinessService,
Production,
Route,
ServiceItem,
target,
)
@dataclass
class OrderRequest(Message):
order_id: str = ""
class FileService(PollingBusinessService):
Output = target()
def on_poll(self):
return self.send_request_sync(
self.Output,
OrderRequest(order_id="777"),
)
class OrderOperation(BusinessOperation):
def on_message(self, request):
return request
ORDER_OPERATION = OperationItem("OrderOperation", OrderOperation)
class DemoProduction(Production):
name = "Demo.Production"
testing_enabled = True
services = (
ServiceItem(
"FileInput",
FileService,
routes=(Route(FileService.Output, ORDER_OPERATION),),
),
)
operations = (ORDER_OPERATION,)
PRODUCTIONS = [DemoProduction()]
For existing IRIS classes, use the IRIS class name string:
FILE_OUT = OperationItem("FileOut", "EnsLib.File.PassthroughOperation")
class FileProduction(Production):
name = "Demo.FileProduction"
services = (
ServiceItem(
"FileIn",
"EnsLib.File.PassthroughService",
adapter_settings={"FilePath": "/tmp/in"},
routes=(Route("TargetConfigNames", FILE_OUT),),
),
)
operations = (FILE_OUT,)
PRODUCTIONS = [FileProduction()]
target() declares the outbound target setting on the Python component class.
Route(FileService.Output, ORDER_OPERATION) wires that setting to a production
item. This is the class-style equivalent of prod.connect(file.Output, orders).
For Python component classes, prefer the descriptor form
Route(FileService.Output, ...). For existing IRIS classes, use the IRIS
setting name string, for example Route("TargetConfigNames", "FileOut").
Route(port, targets) owns the route Host setting and records graph edges.
targets can be an item declaration, a string item name, or a sequence for
fan-out.
Use settings or host_settings only for non-route Host settings. If a route
port is also present in Host settings, migration raises an error so the route
stays explicit.
Use tuples for class-level services, processes, operations, and route
lists. Lists still work, but tuples avoid accidental mutation of shared class
attributes.
You can also author the same topology progressively:
prod = (
Production("Demo.Production")
.testing()
.actor_pool(2)
.describe("Order ingestion")
)
orders = prod.operation(OrderOperation)
file = (
prod.service("FileInput", FileService)
.pool(2)
.host_setting("Limit", 10)
.adapter_setting("Charset", "utf-8")
.connect("Output", orders)
)
PRODUCTIONS = [prod]
Progressive methods mutate the same Production and ComponentRef objects.
There is no separate public builder.
You can also reference existing ObjectScript or built-in IRIS components by class name. These classes are not registered as Python components.
prod = Production("Demo.Production")
file = prod.service("FileIn", class_name="EnsLib.File.PassthroughService")
out = prod.operation("FileOut", class_name="EnsLib.File.PassthroughOperation")
prod.connect(file.port("TargetConfigNames"), out)
ComponentRef.component_class is the Python business host implementation class.
Adapter metadata is separate and exposed through adapter_class_name.
For Python adapter classes that should be registered automatically, pass
adapter_class=... when declaring the production item. The business host class
must still declare the adapter type with get_adapter_type() because IRIS stores
that adapter on the generated host class, not in production item XML.
Existing IRIS productions can be fetched back into the Python model:
prod = Production.from_iris("Demo.Production")
print(prod.graph())
from_iris() reads the exported production definition and uses IRIS
OnGetConnections data when available. If runtime connection data cannot be
read, it falls back to Host settings whose value names another production item.
If runtime discovery returns no targets for an item, Host settings are still
used to draw inferred possible routes.
When available, runtime connection export also supplies adapter metadata such
as adapter_class_name; for loaded Python host classes this can also be
inferred from get_adapter_type().
The fetched graph is an operational reconstruction, not a replacement for the
Python source. Python class objects are not recoverable from IRIS unless they
are also present in the Python source.
Runtime queue counters are separate from the authoring graph:
prod = Production.from_iris("Demo.Production")
queues = prod.queue()
Queue data is point-in-time runtime information from IRIS. It is not serialized
by prod.to_dict() and does not change migration output. prod.queue_info()
remains available as a compatibility alias.
Diffing is explicit and directional:
delta = prod.diff()
print(delta)
With no argument, diff() imports the deployed IRIS production and reports the
changes needed to make that runtime reconstruction match the Python
Production object. You can also pass another Production or an exported
production dictionary: prod.diff(other_prod).
diff() compares the deployable production shape and ignores route import
metadata when the IRIS settings are equivalent. Use prod.graph_diff(...) to
compare graph metadata such as whether a route was authored in Python, imported
from runtime OnGetConnections, or inferred from Host settings.
prod.test_component("Item.Port", message) resolves from the current
Production object graph only. If you want to test a production that already
exists in IRIS, use Production.from_iris(...) first and call
test_component() on the imported operational reconstruction. prod.test(...)
remains available as a compatibility alias.
Lifecycle methods are scoped to the production object. prod.stop(),
prod.restart(), prod.kill(), and prod.update() verify that IRIS currently
points at the same production before invoking the underlying IRIS lifecycle
operation.
Component-level runtime management is also available:
orders = prod.component_ref("OrderOperation")
info = orders.inspect()
orders.stop()
orders.start()
orders.restart()
orders.test(OrderRequest(order_id="123"))
# equivalent production-level calls
info = prod.inspect_component("OrderOperation")
prod.stop_component("OrderOperation")
prod.start_component("OrderOperation")
prod.restart_component(file.Output)
inspect_component(...) returns the component definition, incoming/outgoing
routes, queue counters when available, and the current runtime production
status. Component start/stop/restart is scoped to the same production object.
ComponentRef is a Python handle to a production item, not the live IRIS host
instance.
Legacy Production Dictionaries
Raw production dictionaries are still accepted for compatibility and export
round trips. Prefer Production objects for new Python-authored topology.
Minimal Legacy Dictionary
PRODUCTIONS = [
{
'MyApp.Production': {
"@Name": "MyApp.Production",
"Item": [
{
"@Name": "FileProcessor",
"@ClassName": "Python.FileOperation",
},
{
"@Name": "EmailSender",
"@ClassName": "Python.EmailOperation"
}
]
}
}
]
Full Legacy Dictionary
import os
from bo import FileOperation
PRODUCTIONS = [
{
'Demo.Production': {
"@Name": "Demo.Production",
"@TestingEnabled": "true",
"@LogGeneralTraceEvents": "false",
"Description": "Sample production for demonstration",
"ActorPoolSize": "2",
"Item": [
{
"@Name": "FileProcessor",
"@ClassName": "Python.FileOperation",
"@PoolSize": "1",
"@Enabled": "true",
"@LogTraceEvents": "true",
"Setting": {
"@Target": "Host",
"@Name": "%settings",
"#text": f"path={os.environ.get('DATA_PATH', '/tmp')}"
}
}
]
}
}
]
Production Attributes:
- @Name: Production display name
- @TestingEnabled: Enable testing mode ("true"/"false")
- @LogGeneralTraceEvents: Enable general logging
- ActorPoolSize: Number of concurrent actors
Item Attributes:
- @Name: Component instance name
- @ClassName: IRIS class name or class reference
- @PoolSize: Component pool size
- @Enabled: Enable/disable component
- @LogTraceEvents: Enable component-specific logging
- Setting: Component configuration settings
SCHEMAS Section
Register message schemas for Data Transformation Language (DTL) operations.
from msg import RedditPost, UserProfile
SCHEMAS = [RedditPost, UserProfile]
REMOTE_SETTINGS Section
Configure connections to remote IRIS instances for component migration.
REMOTE_SETTINGS = {
"url": "http://localhost:8080", # Required
"username": "admin", # Optional
"password": "password", # Optional
"namespace": "IRISAPP", # Optional (default: "USER")
"remote_folder": "", # Optional (default: folder of the routine database)
"package": "python", # Optional (default: "python")
"verify_ssl": True # Optional (default: True)
}
Configuration Options:
- url: Remote IRIS instance URL (required)
- username: Authentication username
- password: Authentication password
- namespace: Target namespace for components
- remote_folder: Remote storage folder
- package: Package name for components
- verify_ssl: Enable/disable SSL verification
Complete Example
import os
from dataclasses import dataclass
from iop import BusinessOperation, Message, PollingBusinessService, Production, target
@dataclass
class RedditPost(Message):
title: str = ""
url: str = ""
class RedditService(PollingBusinessService):
Output = target()
Limit: int = 10
def on_poll(self):
self.send_request_async(
self.Output,
RedditPost(title="Latest post", url="https://example.invalid/post"),
)
class FileOperation(BusinessOperation):
def on_message(self, request: RedditPost):
self.log_info(f"Exporting {request.title}")
return request
# Remote connection settings
REMOTE_SETTINGS = {
"url": "http://iris-server:8080",
"username": "admin",
"password": "password",
"namespace": "IRISAPP"
}
# Optional DTL schema registration
SCHEMAS = [RedditPost]
# Production graph
prod = (
Production("Reddit.Production", testing_enabled=True)
.actor_pool(3)
.describe("Reddit processing pipeline")
)
feed = (
prod.service("RedditFeed", RedditService)
.host_setting("Limit", int(os.environ.get("REDDIT_LIMIT", "10")))
)
exporter = prod.operation("FileExporter", FileOperation)
prod.connect(feed.Output, exporter)
PRODUCTIONS = [prod]
Best Practices
- Use descriptive names for components and productions
- Import modules at the top of your settings file
- Use environment variables for sensitive data and paths
- Group related components in the same production
- Enable logging during development and testing
- Document complex productions with clear descriptions