This is a poor excuse for a user manual, but it does cover some important
topics in depth.
When an exception is raised from within a remote method, the DOPY server code
catches the exception and passes it back to the client. On the client side, the
proxy code re-raises the exception object so that it appears to the client as if
the exception were raised seamlessly across the remote method invocation.
The only problem with this approach is that traceback information (which is
not associated with the exception object itself) gets lost when the exception is
passed back to the client environment.
To remedy this, the DOPY server stores the traceback information in the
exception in an attribute called "dopy_traceback". This attribute contains the
traceback information in the form returned by the traceback.extract_tb().
The information in "dopy_traceback" is automatically appended to the "real"
traceback if you use the errorText() function in the dopy.tb module:
The DOPY naming service is intended to provide the same functionality as a
CORBA naming service through a Python dictionary interface. The basic classes
of this service are housed in the dopy.naming module.
At this time, there is no dedicated "name server": any server can become a
name server by registering an instance of StandardNamingContext:
We recommend the convention of using the object key "naming" for the name
service.
At this time, the naming service is really just a dictionary. In fact, from
a purely functional point of view, there is no reason why we could not have
replaced the lines in which the StandardNamingContext was created and
registered with the following:
It is only appropriate to store RemoteObject instances in the name
server: however, there is nothing to prevent you from storing any kind of
object in the name server. A copy of whatever is stored under a particular key
will be returned to the client, so a naming service can (to some extent) be used
as a storage repository. An attempt to store an object implementation in the
naming service will produce unexpected results: retrieval of the object will
return a copy of the object, not a proxy.
This brings up the broader issue of what the naming service really is.
To some extent, the hub's registry is a naming service: it maps keys to object
implementations. Likewise, the persistence service is a naming service that
mirrors the directory tree. It seems as though there should be a more generic
way of representing the tree of objects served by a particular server. At this
point the system is still very much in flux, so there is no reason not to expect
such a thing to evolve.
The dopy.pos module is similar to the dopy.naming module
described above, only instead of a tree of object references, the
persistence system represents a directory tree full of pickled python object
instances.
Use of the system is extremely simple: just create an instance of
FileSysDirectory pointed at the root directory of the file system that
you want to publish:
In the example above, /home/mike/etc/pickled is the root directory
of the pickled object tree, and 'pos' is the object key of the persistence
repository (again, we recommend the key 'pos' just for this purpose). The
object key passed into the FileSysDirectory constructor must be
the same as the key used in the hub.addObject() call.
Clients can store and retrieve copies of objects using the standard list
operators. To manipulate them remotely, use the getRemote() method:
Note that in the example above, localFoo is not remoteFoo. The
remoteFoo object is a proxy object for the foo instance stored on the
server. The localFoo object is a copy of the object stored on the
server. When we call the setValue() method, we are changing the local
value; this does not effect the remote instance.
Normal, DOPY does not impose any constraints upon the objects that it
provides access to. However, in the case of persistence, the system must be
able to tell the object when it is done calling a method so that the object can
save itself. To deal with this, it is recommended that persistent objects
derive from dopy.pos.PersistentObject. This class implements three
special methods, dopy_beforeMethod(), dopy_afterMethod() and
dopy_setPath(), which are used to automatically write the file after
any method is called and to manage a single instance in memory.
Users may wish to provide their own versions of these methods to gain greater
control over when objects are written: for example, it is not necessary for
methods that do not modify an object to cause the object to be written.
However, they should still derive from PersistentObject and call the
_acquire() and _release() methods, which manage the object
instance in a cache for persistent objects.
The persistence system is smart enough to maintain a single instance of an
object in memory: if two different clients call a method on the same object, it
will not be loaded twice. However, this system is not smart enough to take into
account things like overlapping directory trees and symbolic links. For this
reason, we recommend the following:
If you use multiple root-level persistence repositories and one is a subtree
of the other, make sure that the common root is specified in exactly the same
way. i.e. don't do this:
In the example above, the system won't be able to recognize that
pos0/gherkin/BigUn is the same file as pos1/BigUn because the
root is mike/pickle in one case and /home/mike/pickle in the
other.
For systems in which file names are not case sensitive, be consistent in your
use of case.
Do not use symbolic links between two persistence repositories.
In summary, the system epects that a file be known by only one name.
The object locking mechanism is still rather flimsy in other respects: when
an object is initially loaded, it is given a "use count" of 0. This use count
is incremented prior to method invocation and decremented after method
invocation. If the use count is zero after method invocation, the object is
deleted and removed from the cache. Obviously, this creates a region of
exposure between the point at which the object is loaded and the begining of the
invocation of the method for which it has been loaded.
DOPY objects may have the following special methods:
Called before a remote method invocation. If an exception occurs, it
is returned to the caller.
Called after a remote method invocation. If an exception occurs, it
is not returned to the caller. It is ignored.
If the object is stored using the persistence service, this method will be
called when the object is loaded so that the source file's path name can be
given to it.
Getting Tracebacks Into the Server Code
import dopy.tb
try:
# call a method on a remote object
remoteObject.someMethod()
except Exception, ex:
# print out the _full_ traceback and the exception info.
print dopy.tb.errorText(ex)
The DOPY Naming Service
import dopy
from dopy.naming import StandardNamingContext
hub = dopy.getHub()
nameService = StandardNamingContext()
hub.addObject('naming', nameService)
hub.addObject('naming', {})
The DOPY Persistence System
import dopy
from dopy.pos import FileSysDirectory
hub = dopy.getHub()
pos = FileSysDirectory('/home/mike/etc/pickled', 'pos')
hub.addObject('pos', pos)
# get the persistence repository
pos = dopy.tcp.remote('somehost.bogus.net', 9600, 'pos')
# store an object in the repository
pos['foo'] = Foo()
# get the remote instance and modify it
remoteFoo = pos.getRemote('foo')
remoteFoo.setValue(100)
# get a local copy of foo and modify it
localFoo = pos['foo']
localFoo.setValue(200)
# assuming current directory is '/home'
hub.addObject('pos0', FileSysDirectory('mike/pickle', 'pos0'))
hub.addObject('pos1', FileSysDirectory('/home/mike/pickle/gherkin',
'pos1'
)
)
Special Methods