omniORBpy adheres to the standard Python mapping [OMG01b], so there is no need to describe the mapping here. This chapter outlines a number of issues which are not addressed by the standard (or are optional), and how they are resolved in omniORBpy.
As explained in chapter 2, whenever you receive an object reference declared to be base CORBA::Object, such as from NamingContext::resolve() or ORB::string_to_object(), you should narrow the reference to the type you require. You might think that since Python is a dynamically typed language, narrowing should never be necessary. Unfortunately, although omniORBpy often generates object references with the right types, it cannot do so in all circumstances.
The rules which govern when narrowing is required are quite complex. To be totally safe, you can always narrow object references to the type you are expecting. The advantages of this approach are that it is simple and that it is guaranteed to work with all Python ORBs.
The disadvantage with calling narrow for all received object references is that much of the time it is guaranteed not to be necessary. If you understand the situations in which narrowing is necessary, you can avoid spurious narrowing.
When object references are transmitted (or stored in stringified IORs), they contain a single type identifier string, termed the repository id. Normally, the repository id represents the most derived interface of the object. However, it is also permitted to be the empty string, or to refer to an interface higher up the inheritance hierarchy. To give a concrete example, suppose there are two IDL files:
A reference to an object with interface B will normally contain the repository id ‘IDL:M2/B:1.0’1. It is also permitted to have an empty repository id, or the id ‘IDL:M1/A:1.0’. ‘IDL:M1/A:1.0’ is unlikely unless the server is being deliberately obtuse.
Whenever omniORBpy receives an object reference from somewhere—either as a return value or as an operation argument—it has a particular target interface in mind, which it compares with the repository id it has received. A target of base CORBA::Object is just one (common) case. For example, in the following IDL:
the target interface for getObj’s return value is CORBA::Object; the target interface for getA’s return value is M1::A.
omniORBpy uses the result of comparing the received and target repository ids to determine the type of the object reference it creates. The object reference has either the type of the received reference, or the target type, according to this table:
|1.||The received id is the same as the target id||received|
|2.||The received id is not the same as the target id, but the ORB knows that the received interface is derived from the target interface||received|
|3.||The received id is unknown to the ORB||target|
|4.||The received id is not the same as the target id, and the ORB knows that the received interface is not derived from the target interface||target|
Cases 1 and 2 are the most common. Case 2 explains why it is not necessary to narrow the result of calling resolve_initial_references("RootPOA"): the return is always of the known type PortableServer.POA, which is derived from the target type of CORBA.Object.
Case 3 is also quite common. Suppose a client knows about IDL modules M1 and M3 from above, but not module M2. When it calls getA() on an instance of M3::C, the return value may validly be of type M2::B, which it does not know. By creating an object reference of type M1::A in this case, the client is still able to call the object’s opA() operation. On the other hand, if getObj() returns an object of type M2::B, the ORB will create a reference to base CORBA::Object, since that is the target type.
Note that the ORB never rejects an object reference due to it having the wrong type. Even if it knows that the received id is not derived from the target interface (case 4), it might be the case that the object actually has a more derived interface, which is derived from both the type it is claiming to be and the target type. That is, of course, extremely unlikely.
In cases 3 and 4, the ORB confirms the type of the object by calling _is_a() just before the first invocation on the object. If it turns out that the object is not of the right type after all, the CORBA.INV_OBJREF exception is raised. The alternative to this approach would be to check the types of object references when they were received, rather than waiting until the first invocation. That would be inefficient, however, since it is quite possible that a received object reference will never be used. It may also cause objects to be activated earlier than expected.
In summary, whenever your code receives an object reference, you should bear in mind what omniORBpy’s idea of the target type is. You must not assume that the ORB will always correctly figure out a more derived type than the target. One consequence of this is that you must always narrow a plain CORBA::Object to a more specific type before invoking on it2. You can assume that the object reference you receive is of the target type, or something derived from it, although the object it refers to may turn out to be invalid. The fact that omniORBpy often is able figure out a more derived type than the target is only useful when using the Python interactive command line.
In statically typed languages, such as C++, Anys can only be used with built-in types and IDL-declared types for which stubs have been generated. If, for example, a C++ program receives an Any containing a struct for which it does not have static knowledge, it cannot easily extract the struct contents. The only solution is to use the inconvenient DynAny interface.
Since Python is a dynamically typed language, it does not have this difficulty. When omniORBpy receives an Any containing types it does not know, it is able to create new Python types which behave exactly as if there were statically generated stubs available. Note that this behaviour is not required by the Python mapping specification, so other Python ORBs may not be so accommodating.
The equivalent of DynAny creation can be achieved by dynamically writing and importing new IDL, as described in section 4.9.
There is, however, a minor fly in the ointment when it comes to receiving Anys. When an Any is transmitted, it is sent as a TypeCode followed by the actual value. Normally, the TypeCodes for entities with names—members of structs, for example—contain those names as strings. That permits omniORBpy to create types with the corresponding names. Unfortunately, the GIOP specification permits TypeCodes to be sent with empty strings where the names would normally be. In this situation, the types which omniORBpy creates cannot be given the correct names. The contents of all types except structs and exceptions can be accessed without having to know their names, through the standard interfaces. Unknown structs, exceptions and valuetypes received by omniORBpy have an attribute named ‘_values’ which contains a sequence of the member values. This attribute is omniORBpy specific.
Similarly, TypeCodes for constructed types such as structs and unions normally contain the repository ids of those types. This means that omniORBpy can use types statically declared in the stubs when they are available. Once again, the specification permits the repository id strings to be empty3. This means that even if stubs for a type received in an Any are available, it may not be able to create a Python value with the right type. For example, with a struct definition such as:
The transmitted TypeCode for M::S may contain only the information that it is a structure containing a string followed by a long, not that it is type M::S, or what the member names are.
To cope with this situation, omniORBpy has an extension to the standard interface which allows you to coerce an Any value to a known type. Calling an Any’s value() method with a TypeCode argument returns either a value of the requested type, or None if the requested TypeCode is not equivalent to the Any’s TypeCode. The following code is guaranteed to be safe, but is not standard:
omniORBpy provides an alternative, non-standard way of constructing and deconstructing Anys that is often more convenient to use in Python programs. It uses Python’s own dynamic typing to infer the TypeCodes to use. The omniORB.any module contains two functions, to_any() and from_any().
to_any() takes a Python object and tries to return it inside an Any. It uses the following rules:
All other Python types result in a CORBA.BAD_PARAM exception.
The from_any() function works in reverse. It takes an Any as its argument and extracts its contents using the same rules as to_any(). By default, CORBA structs are extracted to dictionaries; if the optional keep_structs argument is set true, they are instead left as instances of the CORBA struct classes.
The Interface Repository interfaces are declared in IDL module CORBA so, according to the Python mapping, the stubs for them should appear in the Python CORBA module, along with all the other CORBA definitions. However, since the stubs are extremely large, omniORBpy does not include them by default. To do so would unnecessarily increase the memory footprint and start-up time.
The Interface Repository stubs are automatically included if you define the OMNIORBPY_IMPORT_IR_STUBS environment variable. Alternatively, you can import the stubs at run-time by calling the omniORB.importIRStubs() function. In both cases, the stubs become available in the Python CORBA module.
The Python language mapping says that IDL attributes map to methods with names prefixed _get_ and _set_. omniORBpy adheres to this standard mapping, but additionally supports access using properties:
Servant classes can similarly provide attribute values using simple Python object attributes or properties. When invoking an attribute getter or setter, omniORBpy first looks for a method with the standard _get_ or _set_ name. If that is not found, a simple attribute lookup is performed. This is therefore a complete implementation of the M::Example interface: