~ubuntu-branches/debian/sid/pyro/sid

« back to all changes in this revision

Viewing changes to Pyro/core.py

  • Committer: Bazaar Package Importer
  • Author(s): Carl Chenet, Carl Chenet, Jakub Wilk
  • Date: 2010-09-14 01:04:28 UTC
  • Revision ID: james.westby@ubuntu.com-20100914010428-02r7p1rzr7jvw94z
Tags: 1:3.9.1-2
[Carl Chenet]
* revert to 3.9.1-1 package because of the development status 
  of the 4.1 package is unsuitable for stable use
  DPMT svn #8557 revision (Closes: #589172) 
* added debian/source
* added debian/source/format
* package is now 3.0 (quilt) source format
* debian/control
  - Bump Standards-Version to 3.9.1

[Jakub Wilk]
* Add ‘XS-Python-Version: >= 2.5’ to prevent bytecompilation with python2.4
  (closes: #589053).

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#############################################################################
 
2
#  
 
3
#       $Id: core.py,v 2.104.2.25 2009/03/29 20:19:27 irmen Exp $
 
4
#       Pyro Core Library
 
5
#
 
6
#       This is part of "Pyro" - Python Remote Objects
 
7
#       which is (c) Irmen de Jong - irmen@users.sourceforge.net
 
8
#
 
9
#############################################################################
 
10
 
 
11
import sys, time, re, os, weakref
 
12
import imp, marshal, new, socket
 
13
from pickle import PicklingError
 
14
import Pyro.constants, Pyro.util, Pyro.protocol, Pyro.errors
 
15
from Pyro.errors import *
 
16
from types import UnboundMethodType, MethodType, BuiltinMethodType, TupleType, StringType, UnicodeType
 
17
if Pyro.util.supports_multithreading():
 
18
        import threading
 
19
 
 
20
Log=Pyro.util.Log
 
21
 
 
22
 
 
23
def _checkInit(pyrotype="client"):
 
24
        if not getattr(Pyro.config, Pyro.constants.CFGITEM_PYRO_INITIALIZED):
 
25
                # If Pyro has not been initialized explicitly, do it automatically.
 
26
                if pyrotype=="server":
 
27
                        initServer()
 
28
                else:
 
29
                        initClient()
 
30
 
 
31
 
 
32
#############################################################################
 
33
#
 
34
#       ObjBase         - Server-side object implementation base class
 
35
#                     or master class with the actual object as delegate
 
36
#
 
37
#       SynchronizedObjBase - Just the same, but with synchronized method
 
38
#                         calls (thread-safe).
 
39
#
 
40
#############################################################################
 
41
 
 
42
class ObjBase:
 
43
        def __init__(self):
 
44
                self.objectGUID=Pyro.util.getGUID()
 
45
                self.delegate=None
 
46
                self.lastUsed=time.time()               # for later reaping unused objects
 
47
                if Pyro.config.PYRO_MOBILE_CODE:
 
48
                        self.codeValidator=lambda n,m,a: 1  # always accept
 
49
        def GUID(self):
 
50
                return self.objectGUID
 
51
        def setGUID(self, guid):                        # used with persistent name server
 
52
                self.objectGUID = guid
 
53
        def delegateTo(self,delegate):
 
54
                self.delegate=delegate
 
55
        def setPyroDaemon(self, daemon):
 
56
                # This will usually introduce a cyclic reference between the
 
57
                # object and the daemon. Use a weak ref if available.
 
58
                # NOTE: if you correctly clean up the object (that is, disconnect it from the daemon)
 
59
                # the cyclic reference is cleared correctly, and no problem occurs.
 
60
                # NOTE: you have to make sure your original daemon object doesn't get garbage collected
 
61
                # if you still want to use the objects! You have to keep a ref. to the daemon somewhere.
 
62
                if daemon:
 
63
                        self.daemon=weakref.proxy(daemon)
 
64
                else:
 
65
                        self.daemon=None
 
66
        def setCodeValidator(self, v):
 
67
                if not callable(v):
 
68
                        raise TypeError("codevalidator must be a callable object")
 
69
                self.codeValidator=v
 
70
        def getDaemon(self):
 
71
                return self.daemon
 
72
        def getLocalStorage(self):
 
73
                return self.daemon.getLocalStorage()
 
74
        def _gotReaped(self):
 
75
                # Called when daemon reaps this object due to unaccessed time
 
76
                # Override this method if needed; to act on this event
 
77
                pass
 
78
        def getProxy(self):
 
79
                return self.daemon.getProxyForObj(self)
 
80
        def getAttrProxy(self):
 
81
                return self.daemon.getAttrProxyForObj(self)
 
82
        def Pyro_dyncall(self, method, flags, args):
 
83
                # update the timestamp
 
84
                self.lastUsed=time.time()
 
85
                # find the method in this object, and call it with the supplied args.
 
86
                keywords={}
 
87
                if flags & Pyro.constants.RIF_Keywords:
 
88
                        # reconstruct the varargs from a tuple like
 
89
                        #  (a,b,(va1,va2,va3...),{kw1:?,...})
 
90
                        keywords=args[-1]
 
91
                        args=args[:-1]
 
92
                if flags & Pyro.constants.RIF_Varargs:
 
93
                        # reconstruct the varargs from a tuple like (a,b,(va1,va2,va3...))
 
94
                        args=args[:-1]+args[-1]
 
95
                if keywords and type(keywords.iterkeys().next()) is unicode and sys.platform!="cli":
 
96
                        # IronPython sends all strings as unicode, but apply() doesn't grok unicode keywords.
 
97
                        # So we need to rebuild the keywords dict with str keys... 
 
98
                        keywords = dict([(str(k),v) for k,v in keywords.iteritems()])
 
99
                # If the method is part of ObjBase, never call the delegate object because
 
100
                # that object doesn't implement that method. If you don't check this,
 
101
                # remote attributes won't work with delegates for instance, because the
 
102
                # delegate object doesn't implement _r_xa. (remote_xxxattr)
 
103
                if method in dir(ObjBase):
 
104
                        return getattr(self,method) (*args,**keywords)
 
105
                else:
 
106
                        # try..except to deal with obsoleted string exceptions (raise "blahblah")
 
107
                        try :
 
108
                                return getattr(self.delegate or self,method) (*args,**keywords)
 
109
                        except :
 
110
                                exc_info = sys.exc_info()
 
111
                                try:
 
112
                                        if type(exc_info[0]) == StringType :
 
113
                                                if exc_info[1] == None :
 
114
                                                        raise Exception, exc_info[0], exc_info[2]
 
115
                                                else :
 
116
                                                        raise Exception, "%s: %s" % (exc_info[0], exc_info[1]), exc_info[2]
 
117
                                        else :
 
118
                                                raise
 
119
                                finally:
 
120
                                        del exc_info   # delete frame to allow proper GC
 
121
 
 
122
        # remote getattr/setattr support:
 
123
        def _r_ha(self, attr):
 
124
                try:
 
125
                        attr = getattr(self.delegate or self,attr)
 
126
                        if type(attr) in (UnboundMethodType, MethodType, BuiltinMethodType):
 
127
                                return 1 # method
 
128
                except:
 
129
                        pass
 
130
                return 2 # attribute
 
131
        def _r_ga(self, attr):
 
132
                return getattr(self.delegate or self, attr)
 
133
        def _r_sa(self, attr, value):
 
134
                setattr(self.delegate or self, attr, value)
 
135
        # remote code downloading support (server downloads from client):
 
136
        def remote_supply_code(self, name, module, sourceaddr):
 
137
                # XXX this is nasty code, and also duplicated in protocol.py _retrieveCode()
 
138
                if Pyro.config.PYRO_MOBILE_CODE and self.codeValidator(name,module,sourceaddr):
 
139
                        try:
 
140
                                imp.acquire_lock()   # threadsafe imports
 
141
                                if name in sys.modules and getattr(sys.modules[name],'_PYRO_bytecode',None):
 
142
                                        # already have this module, don't import again
 
143
                                        # we checked for the _PYRO_bytecode attribute because that is only
 
144
                                        # present when all loading code below completed successfully
 
145
                                        return
 
146
                                Log.msg('ObjBase','loading supplied code: ',name,'from',str(sourceaddr))
 
147
                                if module[0:4]!=imp.get_magic():
 
148
                                        # compile source code
 
149
                                        code=compile(module,'<downloaded>','exec')
 
150
                                else:
 
151
                                        # read bytecode from the client
 
152
                                        code=marshal.loads(module[8:])
 
153
 
 
154
                                # make the module hierarchy and add all names to sys.modules
 
155
                                name=name.split('.')
 
156
                                path=''
 
157
                                mod=new.module("pyro-agent-context")
 
158
                                for m in name:
 
159
                                        path+='.'+m
 
160
                                        # use already loaded modules instead of overwriting them
 
161
                                        real_path = path[1:]
 
162
                                        if sys.modules.has_key(real_path):
 
163
                                                mod = sys.modules[real_path]
 
164
                                        else:
 
165
                                                setattr(mod,m,new.module(path[1:]))
 
166
                                                mod=getattr(mod,m)
 
167
                                                sys.modules[path[1:]]=mod
 
168
                                # execute the module code in the right module.
 
169
                                exec code in mod.__dict__
 
170
                                # store the bytecode for possible later reference if we need to pass it on
 
171
                                mod.__dict__['_PYRO_bytecode'] = module
 
172
                        finally:
 
173
                                imp.release_lock()
 
174
                else:
 
175
                        Log.warn('ObjBase','attempt to supply code denied: ',name,'from',str(sourceaddr))
 
176
                        raise PyroError('attempt to supply code denied')
 
177
 
 
178
        # remote code retrieve support (client retrieves from server):
 
179
        def remote_retrieve_code(self, name):
 
180
                # XXX codeValidator: can we somehow get the client's address it is sent to?
 
181
                # XXX this code is ugly. And duplicated in protocol.py remoteInvocation.
 
182
                if Pyro.config.PYRO_MOBILE_CODE and self.codeValidator(name,None,None):
 
183
                        Log.msg("ObjBase","supplying code: ",name)
 
184
                        try:
 
185
                                importmodule=new.module("pyro-server-import")
 
186
                                try:
 
187
                                        exec "import " + name in importmodule.__dict__
 
188
                                except ImportError:
 
189
                                        Log.error("ObjBase","Client wanted a non-existing module:", name)
 
190
                                        raise PyroError("Client wanted a non-existing module", name)
 
191
                                m=eval("importmodule."+name)
 
192
                                # try to load the module's compiled source, or the real .py source if that fails.
 
193
                                # note that the source code (.py) is opened with universal newline mode
 
194
                                (filebase,ext)=os.path.splitext(m.__file__)
 
195
                                if ext.startswith(".PY"):
 
196
                                        exts = ( (".PYO","rb"), (".PYC","rb"), (".PY","rU") )   # uppercase
 
197
                                else:
 
198
                                        exts = ( (".pyo","rb"), (".pyc","rb"), (".py","rU") )   # lowercase
 
199
                                for ext,mode in exts:
 
200
                                        try:
 
201
                                                m=open(filebase+ext, mode).read()
 
202
                                                return m  # supply the module to the client!
 
203
                                        except:
 
204
                                                pass
 
205
                                Log.error("ObjBase","cannot read module source code for module:", name)
 
206
                                raise PyroError("cannot read module source code")
 
207
                        finally:
 
208
                                del importmodule
 
209
                else:
 
210
                        Log.error("ObjBase","attempt to retrieve code denied:", name)
 
211
                        raise PyroError("attempt to retrieve code denied")
 
212
 
 
213
 
 
214
class SynchronizedObjBase(ObjBase):
 
215
    def __init__(self):
 
216
        ObjBase.__init__(self)
 
217
        self.synlock=Pyro.util.getLockObject()
 
218
    def Pyro_dyncall(self, method, flags, args):
 
219
        self.synlock.acquire()  # synchronize method invocation
 
220
        try:
 
221
            return ObjBase.Pyro_dyncall(self, method,flags,args)
 
222
        finally:
 
223
            self.synlock.release()
 
224
 
 
225
 
 
226
# Use this class instead if you're using callback objects and you
 
227
# want to see local exceptions. (otherwise they go back to the calling server...)
 
228
class CallbackObjBase(ObjBase):
 
229
        def __init__(self):
 
230
                ObjBase.__init__(self)
 
231
        def Pyro_dyncall(self, method, flags, args):
 
232
                try:
 
233
                        return ObjBase.Pyro_dyncall(self,method,flags,args)
 
234
                except Exception,x:
 
235
                        # catch all errors
 
236
                        Log.warn('CallbackObjBase','Exception in callback object: ',x)
 
237
                        raise PyroExceptionCapsule(x,str(x))
 
238
 
 
239
 
 
240
#############################################################################
 
241
#
 
242
#       PyroURI         - Pyro Universal Resource Identifier
 
243
#
 
244
#       This class represents a Pyro URI (which consists of four parts,
 
245
#       a protocol identifier, an IP address, a portnumber, and an object ID.
 
246
#       
 
247
#       The URI can be converted to a string representation (str converter).
 
248
#       The URI can also be read back from such a string (reinitFromString).
 
249
#       The URI can be initialised from its parts (init).
 
250
#       The URI can be initialised from a string directly, if the init
 
251
#        code detects a ':' and '/' in the host argument (which is then
 
252
#        assumed to be a string URI, not a host name/ IP address).
 
253
#
 
254
#############################################################################
 
255
 
 
256
class PyroURI:
 
257
        def __init__(self,host,objectID=0,port=0,prtcol='PYRO'):
 
258
                # if the 'host' arg is a PyroURI, copy contents
 
259
                if isinstance(host, PyroURI):
 
260
                        self.init(host.address, host.objectID, host.port, host.protocol)
 
261
                else:
 
262
                        # If the 'host' arg contains '://', assume it's an URI string.
 
263
                        if host.find('://')>0:
 
264
                                self.reinitFromString(host)
 
265
                        else:
 
266
                                if not objectID:
 
267
                                        raise URIError('invalid URI format')
 
268
                                self.init(host, objectID, port, prtcol)
 
269
        def __str__(self):
 
270
                return self.protocol+'://'+self.address+':'+str(self.port)+'/'+self.objectID
 
271
        def __repr__(self):
 
272
                return '<PyroURI \''+str(self)+'\'>'
 
273
        def __hash__(self):
 
274
                # XXX this is handy but not safe. If the URI changes, the object will be in the wrong hash bucket.
 
275
                return hash(str(self))
 
276
        def __cmp__(self, o):
 
277
                return cmp(str(self), str(o))
 
278
        def clone(self):
 
279
                return PyroURI(self)
 
280
        def init(self,host,objectID,port=0,prtcol='PYRO'):
 
281
                if '/' in host:
 
282
                        raise URIError('malformed hostname')
 
283
                if Pyro.config.PYRO_DNS_URI:
 
284
                        self.address = host
 
285
                else:
 
286
                        self.address=Pyro.protocol.getIPAddress(host)
 
287
                        if not self.address:
 
288
                                raise URIError('unknown host')
 
289
                if port:
 
290
                        if type(port)==type(1):
 
291
                                self.port=port
 
292
                        else:
 
293
                                raise TypeError("port must be integer")
 
294
                else:
 
295
                        self.port=Pyro.config.PYRO_PORT
 
296
                self.protocol=prtcol
 
297
                self.objectID=objectID
 
298
        def reinitFromString(self,arg):
 
299
                if arg.startswith('PYROLOC') or arg.startswith('PYRONAME'):
 
300
                        uri=processStringURI(arg)
 
301
                        self.init(uri.address,uri.objectID,uri.port,uri.protocol)
 
302
                        return
 
303
                x=re.match(r'(?P<protocol>[^\s:/]+)://(?P<hostname>[^\s:]+):?(?P<port>\d+)?/(?P<id>\S*)',arg)
 
304
                if x:
 
305
                        port=None
 
306
                        if x.group('port'):
 
307
                                port=int(x.group('port'))
 
308
                        self.init(x.group('hostname'), x.group('id'), port, x.group('protocol'))
 
309
                        return
 
310
                Log.error('PyroURI','invalid URI format passed: '+arg)
 
311
                raise URIError('invalid URI format')
 
312
        def getProxy(self):
 
313
                return DynamicProxy(self)
 
314
        def getAttrProxy(self):
 
315
                return DynamicProxyWithAttrs(self)
 
316
 
 
317
 
 
318
#
 
319
#       This method takes a string representation of a Pyro URI
 
320
#       and parses it. If it's a meta-protocol URI such as
 
321
#       PYRONAME://.... it will do what is needed to make
 
322
#       a regular PYRO:// URI out of it (resolve names etc).
 
323
#
 
324
def processStringURI(URI):
 
325
        # PYRONAME(SSL)://[hostname[:port]/]objectname
 
326
        x=re.match(r'(?P<protocol>PYRONAME|PYRONAMESSL)://(((?P<hostname>[^\s:]+):(?P<port>\d+)/)|((?P<onlyhostname>[^\s:]+)/))?(?P<name>\S*)',URI)
 
327
        if x:
 
328
                protocol=x.group('protocol')
 
329
                if protocol=="PYRONAMESSL":
 
330
                        raise ProtocolError("NOT SUPPORTED YET: "+protocol) # XXX obviously, this should be implemented
 
331
                hostname=x.group('hostname') or x.group('onlyhostname')
 
332
                port=x.group('port')
 
333
                name=x.group('name')
 
334
                import Pyro.naming
 
335
                loc=Pyro.naming.NameServerLocator()
 
336
                if port:
 
337
                        port=int(port)
 
338
                NS=loc.getNS(host=hostname,port=port)
 
339
                return NS.resolve(name)
 
340
        # PYROLOC(SSL)://hostname[:port]/objectname
 
341
        x=re.match(r'(?P<protocol>PYROLOC|PYROLOCSSL)://(?P<hostname>[^\s:]+):?(?P<port>\d+)?/(?P<name>\S*)',URI)
 
342
        if x:
 
343
                protocol=x.group('protocol')
 
344
                hostname=x.group('hostname')
 
345
                port=x.group('port')
 
346
                if port:
 
347
                        port=int(port)
 
348
                else:
 
349
                        port=0
 
350
                name=x.group('name')
 
351
                return PyroURI(hostname,name,port,protocol)
 
352
        if URI.startswith('PYROLOC') or URI.startswith('PYRONAME'):
 
353
                # hmm should have matched above. Likely invalid.
 
354
                raise URIError('invalid URI format')
 
355
        # It's not a meta-protocol such as PYROLOC or PYRONAME,
 
356
        # let the normal Pyro URI deal with it.
 
357
        # (it can deal with regular PYRO: and PYROSSL: protocols)
 
358
        return PyroURI(URI)
 
359
 
 
360
 
 
361
#############################################################################
 
362
#
 
363
#       DynamicProxy    - dynamic Pyro proxy
 
364
#
 
365
#       Can be used by clients to invoke objects for which they have no
 
366
#       precompiled proxy.
 
367
#
 
368
#############################################################################
 
369
 
 
370
def getProxyForURI(URI):
 
371
        return DynamicProxy(URI)
 
372
def getAttrProxyForURI(URI):
 
373
        return DynamicProxyWithAttrs(URI)
 
374
 
 
375
class _RemoteMethod:
 
376
        # method call abstraction, adapted from Python's xmlrpclib
 
377
        # it would be rather easy to add nested method calls, but
 
378
        # that is not compatible with the way that Pyro's method
 
379
        # calls are defined to work ( no nested calls ) 
 
380
        def __init__(self, send, name):
 
381
                self.__send = send
 
382
                self.__name = name
 
383
        def __call__(self, *args, **kwargs):
 
384
                return self.__send(self.__name, args, kwargs)
 
385
 
 
386
class DynamicProxy:
 
387
        def __init__(self, URI):
 
388
                _checkInit() # init required
 
389
                if type(URI) in (StringType,UnicodeType):
 
390
                        URI=processStringURI(URI)
 
391
                self.URI = URI
 
392
                self.objectID = URI.objectID
 
393
                # Delay adapter binding to enable transporting of proxies.
 
394
                # We just create an adapter, and don't connect it...
 
395
                self.adapter = Pyro.protocol.getProtocolAdapter(self.URI.protocol)
 
396
                # ---- don't forget to register local vars with DynamicProxyWithAttrs, see below
 
397
        def __del__(self):
 
398
                try:
 
399
                        self.adapter.release(nolog=1)
 
400
                except (AttributeError, RuntimeError):
 
401
                        pass
 
402
        def _setIdentification(self, ident):
 
403
                self.adapter.setIdentification(ident)
 
404
        def _setNewConnectionValidator(self, validator):
 
405
                self.adapter.setNewConnectionValidator(validator)
 
406
        def _setOneway(self, methods):
 
407
                if type(methods) not in (type([]), type((0,))):
 
408
                        methods=(methods,)
 
409
                self.adapter.setOneway(methods)
 
410
        def _setTimeout(self,timeout):
 
411
                self.adapter.setTimeout(timeout)
 
412
        def _transferThread(self, newOwnerThread=None):
 
413
                pass # dummy function to retain API compatibility with Pyro 3.7
 
414
        def _release(self):
 
415
                if self.adapter:
 
416
                        self.adapter.release()
 
417
        def _local(self):
 
418
                return self.URI._local()
 
419
        def _islocal(self):
 
420
                return self.URI._islocal()
 
421
        def __copy__(self):                     # create copy of current proxy object
 
422
                proxyCopy = DynamicProxy(self.URI)
 
423
                proxyCopy.adapter.setIdentification(self.adapter.getIdentification(), munge=False)   # copy identification info
 
424
                proxyCopy._setTimeout(self.adapter.timeout)
 
425
                proxyCopy._setOneway(self.adapter.onewayMethods)
 
426
                proxyCopy._setNewConnectionValidator(self.adapter.getNewConnectionValidator())
 
427
                return proxyCopy
 
428
        def __deepcopy__(self, arg):
 
429
                raise PyroError("cannot deepcopy a proxy")
 
430
        def __getattr__(self, name):
 
431
                if name=="__getinitargs__":                     # allows it to be safely pickled
 
432
                        raise AttributeError()
 
433
                return _RemoteMethod(self._invokePYRO, name)
 
434
        def __repr__(self):
 
435
                return "<"+self.__class__.__name__+" for "+str(self.URI)+">"
 
436
        def __str__(self):
 
437
                return repr(self)
 
438
        def __hash__(self):
 
439
                # makes it possible to use this class as a key in a dict
 
440
                return hash(self.objectID)
 
441
        def __eq__(self,other):
 
442
                # makes it possible to compare two proxies using objectID
 
443
                return hasattr(other,"objectID") and self.objectID==other.objectID
 
444
        def __ne__(self,other):
 
445
                # makes it possible to compare two proxies using objectID
 
446
                return not hasattr(other,"objectID") or self.objectID!=other.objectID
 
447
        def __nonzero__(self):
 
448
                return 1
 
449
        def __coerce__(self,other):
 
450
                # makes it possible to compare two proxies using objectID (cmp)
 
451
                if hasattr(other,"objectID"):
 
452
                        return (self.objectID, other.objectID)
 
453
                return None
 
454
 
 
455
        def _invokePYRO(self, name, vargs, kargs):
 
456
                if not self.adapter.connected():
 
457
                        # rebind here, don't do it from inside the remoteInvocation because deadlock will occur
 
458
                        self.adapter.bindToURI(self.URI)
 
459
                return self.adapter.remoteInvocation(name, Pyro.constants.RIF_VarargsAndKeywords, vargs, kargs)
 
460
 
 
461
        # Pickling support, otherwise pickle uses __getattr__:
 
462
        def __getstate__(self):
 
463
                # for pickling, return a non-connected copy of ourselves:
 
464
                copy = self.__copy__()
 
465
                copy._release()
 
466
                return copy.__dict__
 
467
        def __setstate__(self, args):
 
468
                # this appears to be necessary otherwise pickle won't work
 
469
                self.__dict__=args
 
470
 
 
471
        
 
472
class DynamicProxyWithAttrs(DynamicProxy):
 
473
        def __init__(self, URI):
 
474
                # first set the list of 'local' attrs for __setattr__
 
475
                self.__dict__["_local_attrs"] = ("_local_attrs","URI", "objectID", "adapter", "_name", "_attr_cache")
 
476
                self._attr_cache = {}
 
477
                DynamicProxy.__init__(self, URI)
 
478
        def _r_ga(self, attr, value=0):
 
479
                if value:
 
480
                        return _RemoteMethod(self._invokePYRO, "_r_ga") (attr)  # getattr
 
481
                else:
 
482
                        return _RemoteMethod(self._invokePYRO, "_r_ha") (attr)  # hasattr
 
483
        def findattr(self, attr):
 
484
                if attr in self._attr_cache.keys():
 
485
                        return self._attr_cache[attr]
 
486
                # look it up and cache the value
 
487
                self._attr_cache[attr] = self._r_ga(attr)
 
488
                return self._attr_cache[attr]
 
489
        def __copy__(self):             # create copy of current proxy object
 
490
                return DynamicProxyWithAttrs(self.URI)
 
491
        def __setattr__(self, attr, value):
 
492
                if attr in self.__dict__["_local_attrs"]:
 
493
                        self.__dict__[attr]=value
 
494
                else:
 
495
                        result = self.findattr(attr)
 
496
                        if result==2: # attribute
 
497
                                return _RemoteMethod(self._invokePYRO, "_r_sa") (attr,value)
 
498
                        else:
 
499
                                raise AttributeError('not an attribute')
 
500
        def __getattr__(self, attr):
 
501
                # allows it to be safely pickled
 
502
                if attr not in ("__getinitargs__", "__hash__","__eq__","__ne__") and attr not in self.__dict__["_local_attrs"]:
 
503
                        result=self.findattr(attr)
 
504
                        if result==1: # method
 
505
                                return _RemoteMethod(self._invokePYRO, attr)
 
506
                        elif result:
 
507
                                return self._r_ga(attr, 1)
 
508
                raise AttributeError
 
509
                
 
510
 
 
511
#############################################################################
 
512
#
 
513
#       Daemon          - server-side Pyro daemon
 
514
#
 
515
#       Accepts and dispatches incoming Pyro method calls.
 
516
#
 
517
#############################################################################
 
518
 
 
519
# The pyro object that represents the daemon.
 
520
# The daemon is not directly remotely accessible, for security reasons.
 
521
class DaemonServant(ObjBase):
 
522
        def __init__(self, daemon):
 
523
                ObjBase.__init__(self)
 
524
                self.daemon=weakref.proxy(daemon)
 
525
        def getRegistered(self):
 
526
                return self.daemon.getRegistered()
 
527
        def ResolvePYROLOC(self, name):
 
528
                return self.daemon.ResolvePYROLOC(name)
 
529
                
 
530
# The daemon itself:
 
531
class Daemon(Pyro.protocol.TCPServer, ObjBase):
 
532
        def __init__(self,prtcol='PYRO',host=None,port=0,norange=0,publishhost=None):
 
533
                ObjBase.__init__(self)
 
534
                self.NameServer = None
 
535
                self.connections=[]
 
536
                _checkInit("server") # init required
 
537
                self.setGUID(Pyro.constants.INTERNAL_DAEMON_GUID)
 
538
                self.implementations={Pyro.constants.INTERNAL_DAEMON_GUID:(DaemonServant(self),'__PYRO_Internal_Daemon')}
 
539
                self.persistentConnectedObjs=[] # guids
 
540
                self.transientsCleanupAge=0
 
541
                self.transientsMutex=Pyro.util.getLockObject()
 
542
                self.nscallMutex=Pyro.util.getLockObject()
 
543
                if host is None:
 
544
                        host=Pyro.config.PYRO_HOST
 
545
                if publishhost is None:
 
546
                        publishhost=Pyro.config.PYRO_PUBLISHHOST
 
547
 
 
548
                # Determine range scanning or random port allocation
 
549
                if norange:
 
550
                        # Fixed or random port allocation
 
551
                        # If port is zero, OS will randomly assign, otherwise,
 
552
                        # attempt to use the provided port value
 
553
                        self.port = port
 
554
                        portrange = 1
 
555
                else:
 
556
                        # Scanning port allocation
 
557
                        if port:
 
558
                                self.port = port
 
559
                        else:
 
560
                                self.port = Pyro.config.PYRO_PORT
 
561
                        portrange=Pyro.config.PYRO_PORT_RANGE
 
562
 
 
563
                if not publishhost:
 
564
                        publishhost=host
 
565
                errormsg=''
 
566
                for i in range(portrange):
 
567
                        try:
 
568
                                Pyro.protocol.TCPServer.__init__(self, self.port, host, Pyro.config.PYRO_MULTITHREADED,prtcol)
 
569
                                if not self.port:
 
570
                                        # If we bound to an OS provided port, report it
 
571
                                        self.port = self.sock.getsockname()[1]
 
572
                                self.hostname = publishhost or Pyro.protocol.getHostname()
 
573
                                self.protocol = prtcol
 
574
                                self.adapter = Pyro.protocol.getProtocolAdapter(prtcol)
 
575
                                self.validateHostnameAndIP()  # ignore any result message... it's in the log already.
 
576
                                return
 
577
                        except ProtocolError,msg:
 
578
                                errormsg=msg
 
579
                                self.port+=1
 
580
                Log.error('Daemon','Couldn\'t start Pyro daemon: ' +str(errormsg))
 
581
                raise DaemonError('Couldn\'t start Pyro daemon: ' +str(errormsg))
 
582
        
 
583
        # to be called to stop all connections and shut down.
 
584
        def shutdown(self, disconnect=False):
 
585
                Pyro.protocol.TCPServer.shutdown(self)
 
586
                if disconnect:
 
587
                        self.__disconnectObjects()
 
588
        def __disconnectObjects(self):
 
589
                # server shutting down, unregister all known objects in the NS
 
590
                if self.NameServer and Pyro and Pyro.constants:
 
591
                        self.nscallMutex.acquire()
 
592
                        try:
 
593
                                if Pyro.constants.INTERNAL_DAEMON_GUID in self.implementations:
 
594
                                        del self.implementations[Pyro.constants.INTERNAL_DAEMON_GUID]
 
595
                                if self.implementations:
 
596
                                        Log.warn('Daemon','Shutting down but there are still',len(self.implementations),'objects connected - disconnecting them')
 
597
                                for guid in self.implementations.keys():
 
598
                                        if guid not in self.persistentConnectedObjs:
 
599
                                                (obj,name)=self.implementations[guid]
 
600
                                                if name:
 
601
                                                        try:
 
602
                                                                self.NameServer.unregister(name)
 
603
                                                        except Exception,x:
 
604
                                                                Log.warn('Daemon','Error while unregistering object during shutdown:',x)
 
605
                                self.implementations={}
 
606
                        finally:
 
607
                                self.nscallMutex.release()
 
608
 
 
609
        def __del__(self):
 
610
                self.__disconnectObjects() # unregister objects
 
611
                try:
 
612
                        del self.adapter
 
613
                        Pyro.protocol.TCPServer.__del__(self)
 
614
                except (AttributeError, RuntimeError):
 
615
                        pass
 
616
 
 
617
        def __str__(self):
 
618
                return '<Pyro Daemon on '+self.hostname+':'+str(self.port)+'>'
 
619
        def __getstate__(self):
 
620
                raise PicklingError('no access to the daemon')
 
621
 
 
622
        def validateHostnameAndIP(self):
 
623
                # Checks if hostname is sensible. Returns None if it is, otherwise a message
 
624
                # telling what's wrong if it isn't too serious. If things are really bad,
 
625
                # expect an exception to be raised. Things are logged too.
 
626
                if not self.hostname:
 
627
                        Log.error("Daemon","no hostname known")
 
628
                        raise socket.error("no hostname known for daemon")
 
629
                if self.hostname!="localhost":
 
630
                        ip = Pyro.protocol.getIPAddress(self.hostname)
 
631
                        if ip is None:
 
632
                                Log.error("Daemon","no IP address known")
 
633
                                raise socket.error("no IP address known for daemon")
 
634
                        if not ip.startswith("127.0."):
 
635
                                return None  # this is good!
 
636
                # 127.0.x.x or 'localhost' is a warning situation!
 
637
                msg="daemon bound on hostname that resolves to loopback address 127.0.x.x"
 
638
                Log.warn("Daemon",msg)
 
639
                Log.warn("Daemon","hostname="+self.hostname)
 
640
                return msg
 
641
        
 
642
        def useNameServer(self,NS):
 
643
                self.NameServer=NS
 
644
        def getNameServer(self):
 
645
                return self.NameServer
 
646
        def setTimeout(self, timeout):
 
647
                self.adapter.setTimeout(timeout)
 
648
        def setAllowedIdentifications(self, ids):
 
649
                self.getNewConnectionValidator().setAllowedIdentifications(ids)
 
650
        def setTransientsCleanupAge(self, secs):
 
651
                self.transientsCleanupAge=secs
 
652
                if self.threaded:
 
653
                        Log.msg('Daemon','creating Grim Reaper thread for transients, timeout=',secs)
 
654
                        reaper=threading.Thread(target=self._grimReaper)
 
655
                        reaper.setDaemon(1)   # thread must exit at program termination.
 
656
                        reaper.start()
 
657
        def _grimReaper(self):
 
658
                # this runs in a thread.
 
659
                while self.transientsCleanupAge>0:
 
660
                        time.sleep(self.transientsCleanupAge/5)
 
661
                        self.reapUnusedTransients()
 
662
 
 
663
        def getProxyForObj(self, obj):
 
664
                return DynamicProxy( PyroURI(self.hostname,
 
665
                                obj.GUID(), prtcol=self.protocol, port=self.port) )
 
666
        def getAttrProxyForObj(self, obj):
 
667
                return DynamicProxyWithAttrs( PyroURI(self.hostname,
 
668
                                obj.GUID(), prtcol=self.protocol, port=self.port) )
 
669
 
 
670
        def connectPersistent(self, obj, name=None):
 
671
                # when a persistent entry is found in the NS, that URI is
 
672
                # used instead of the supplied one, if the address matches.
 
673
                if name and self.NameServer:
 
674
                        self.nscallMutex.acquire()
 
675
                        try:
 
676
                                try:
 
677
                                        newURI = PyroURI(self.hostname, obj.GUID(), prtcol=self.protocol, port=self.port)
 
678
                                        URI=self.NameServer.resolve(name)
 
679
                                        if (URI.protocol,URI.address,URI.port)==(newURI.protocol,newURI.address,newURI.port):
 
680
                                                # reuse the previous object ID
 
681
                                                obj.setGUID(URI.objectID)
 
682
                                                # enter the (object,name) in the known impl. dictionary
 
683
                                                self.implementations[obj.GUID()]=(obj,name)
 
684
                                                self.persistentConnectedObjs.append(obj.GUID())
 
685
                                                obj.setPyroDaemon(self)
 
686
                                                return URI
 
687
                                        else:
 
688
                                                # name exists, but address etc. is wrong. Remove it.
 
689
                                                # then continue so it wil be re-registered.
 
690
                                                try: self.NameServer.unregister(name)
 
691
                                                except NamingError: pass
 
692
                                except NamingError:
 
693
                                        pass
 
694
                        finally:
 
695
                                self.nscallMutex.release()
 
696
                # Register normally.            
 
697
                self.persistentConnectedObjs.append(obj.GUID())
 
698
                return self.connect(obj, name)
 
699
 
 
700
        def connect(self, obj, name=None):
 
701
                URI = PyroURI(self.hostname, obj.GUID(), prtcol=self.protocol, port=self.port)
 
702
                # if not transient, register the object with the NS
 
703
                if name:
 
704
                        self.nscallMutex.acquire()
 
705
                        try:
 
706
                                if self.NameServer:
 
707
                                        self.NameServer.register(name, URI)
 
708
                                else:
 
709
                                        Log.warn('Daemon','connecting object without name server specified:',name)
 
710
                        finally:
 
711
                                self.nscallMutex.release()
 
712
                # enter the (object,name) in the known implementations dictionary
 
713
                self.implementations[obj.GUID()]=(obj,name)
 
714
                obj.setPyroDaemon(self)
 
715
                return URI
 
716
 
 
717
        def disconnect(self,obj):
 
718
                try:
 
719
                        if self.NameServer and self.implementations[obj.GUID()][1]:
 
720
                                self.nscallMutex.acquire()
 
721
                                try:
 
722
                                        # only unregister with NS if it had a name (was not transient)
 
723
                                        self.NameServer.unregister(self.implementations[obj.GUID()][1])
 
724
                                finally:
 
725
                                        self.nscallMutex.release()
 
726
                        del self.implementations[obj.GUID()]
 
727
                        if obj.GUID() in self.persistentConnectedObjs:
 
728
                                self.persistentConnectedObjs.remove(obj.GUID())
 
729
                        # XXX Clean up connections/threads to this object?
 
730
                        #     Can't be done because thread/socket is not associated with single object 
 
731
                finally:
 
732
                        obj.setPyroDaemon(None)
 
733
 
 
734
        def getRegistered(self):
 
735
                r={}
 
736
                for guid in self.implementations.keys():
 
737
                        r[guid]=self.implementations[guid][1]   # keep only the names
 
738
                return r
 
739
 
 
740
        def handleInvocation(self, conn):       # overridden from TCPServer
 
741
                # called in both single- and multithreaded mode
 
742
                self.getLocalStorage().caller=conn
 
743
                self.getAdapter().handleInvocation(self, conn)
 
744
                self.reapUnusedTransients()
 
745
 
 
746
        def reapUnusedTransients(self):
 
747
                if not self.transientsCleanupAge: return
 
748
                now=time.time()
 
749
                self.transientsMutex.acquire()
 
750
                try:
 
751
                        for (obj,name) in self.implementations.values()[:]:   # use copy of list
 
752
                                if not name:
 
753
                                        # object is transient, reap it if timeout requires so.
 
754
                                        if (now-obj.lastUsed)>self.transientsCleanupAge:
 
755
                                                self.disconnect(obj)
 
756
                                                obj._gotReaped()
 
757
                finally:
 
758
                        self.transientsMutex.release()
 
759
 
 
760
        def handleError(self,conn,onewaycall=False):                    # overridden from TCPServer
 
761
                try:
 
762
                        (exc_type, exc_value, exc_trb) = sys.exc_info()
 
763
                        if exc_type==ProtocolError:
 
764
                                # Problem with the communication protocol, shut down the connection
 
765
                                # XXX is shutting down what we want???
 
766
                                Log.error('Daemon','protocol error occured:',exc_value)
 
767
                                Log.error('Daemon','Due to network error: shutting down connection with',conn)
 
768
                                self.removeConnection(conn)
 
769
                        else:
 
770
                                exclist = Pyro.util.formatTraceback(exc_type, exc_value, exc_trb)
 
771
                                out =''.join(exclist)
 
772
                                Log.warn('Daemon', 'Exception during processing of request from',
 
773
                                        conn,' type',exc_type,
 
774
                                        '\n--- traceback of this exception follows:\n',
 
775
                                        out,'\n--- end of traceback')
 
776
                                if exc_type==PyroExceptionCapsule:
 
777
                                        sys.stdout.flush()
 
778
                                        # This is a capsuled exception, used with callback objects.
 
779
                                        # That means we are actually the daemon on the client.
 
780
                                        # Return the error to the other side and raise exception locally once more.
 
781
                                        # (with a normal exception, it is not raised locally again!)
 
782
                                        # only send the exception object if it's not a oneway call.
 
783
                                        if not onewaycall:
 
784
                                                self.adapter.returnException(conn,exc_value.excObj,0,exclist) # don't shutdown
 
785
                                        exc_value.raiseEx()
 
786
                                else:
 
787
                                        # normal exception, only return exception object if it's not a oneway call
 
788
                                        if not onewaycall:
 
789
                                                self.adapter.returnException(conn,exc_value,0,exclist) # don't shutdown connection
 
790
 
 
791
                finally:
 
792
                        # clean up circular references to traceback info to allow proper GC
 
793
                        del exc_type, exc_value, exc_trb
 
794
 
 
795
        def getAdapter(self):
 
796
                # overridden from TCPServer
 
797
                return self.adapter
 
798
 
 
799
        def getLocalObject(self, guid):
 
800
                # return a local object registered with the given guid
 
801
                return self.implementations[guid][0]
 
802
        def getLocalObjectForProxy(self, proxy):
 
803
                # return a local object registered with the guid to which the given proxy points
 
804
                return self.implementations[proxy.objectID][0]
 
805
 
 
806
        def ResolvePYROLOC(self, name):
 
807
                # this gets called from the protocol adapter when
 
808
                # it wants the daemon to resolve a local object name (PYROLOC: protocol)
 
809
                Log.msg('Daemon','resolving PYROLOC name: ',name)
 
810
                for o in self.implementations.keys():
 
811
                        if self.implementations[o][1]==name:
 
812
                                return o
 
813
                raise NamingError('no object found by this name',name)
 
814
 
 
815
 
 
816
#############################################################################
 
817
#
 
818
#       Client/Server Init code
 
819
#
 
820
#############################################################################
 
821
 
 
822
# Has init been performed already?
 
823
_init_server_done=0
 
824
_init_client_done=0
 
825
_init_generic_done=0
 
826
 
 
827
def _initGeneric_pre():
 
828
        global _init_generic_done
 
829
        if _init_generic_done:
 
830
                return
 
831
        if Pyro.config.PYRO_TRACELEVEL == 0: return
 
832
        try:
 
833
                out='\n'+'-'*60+' NEW SESSION\n'+time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(time.time()))+ \
 
834
                        '   Pyro Initializing, version '+Pyro.constants.VERSION+'\n'
 
835
                Log.raw(out)
 
836
        except IOError,e:
 
837
                sys.stderr.write('PYRO: Can\'t write the tracefile '+Pyro.config.PYRO_LOGFILE+'\n'+str(e))
 
838
 
 
839
def _initGeneric_post():
 
840
        global _init_generic_done
 
841
        setattr(Pyro.config, Pyro.constants.CFGITEM_PYRO_INITIALIZED,1)
 
842
        if Pyro.config.PYRO_TRACELEVEL == 0: return
 
843
        try:
 
844
                if not _init_generic_done:
 
845
                        out='Configuration settings are as follows:\n'
 
846
                        for item in dir(Pyro.config):
 
847
                                if item[0:4] =='PYRO':
 
848
                                        out+=item+' = '+str(Pyro.config.__dict__[item])+'\n'
 
849
                        Log.raw(out)
 
850
                Log.raw('Init done.\n'+'-'*70+'\n')
 
851
        except IOError:
 
852
                pass
 
853
        _init_generic_done=1    
 
854
 
 
855
 
 
856
def initClient(banner=0):
 
857
        global _init_client_done
 
858
        if _init_client_done: return
 
859
        _initGeneric_pre()
 
860
        if Pyro.config.PYRO_TRACELEVEL >0: Log.raw('This is initClient.\n')
 
861
        Pyro.config.finalizeConfig_Client()
 
862
        _initGeneric_post()
 
863
        if banner:
 
864
                print 'Pyro Client Initialized. Using Pyro V'+Pyro.constants.VERSION
 
865
        _init_client_done=1
 
866
        
 
867
def initServer(banner=0, storageCheck=1):
 
868
        global _init_server_done
 
869
        if _init_server_done: return
 
870
        _initGeneric_pre()
 
871
        if Pyro.config.PYRO_TRACELEVEL >0: Log.raw('This is initServer.\n')
 
872
        Pyro.config.finalizeConfig_Server(storageCheck=storageCheck)
 
873
        _initGeneric_post()
 
874
        if banner:
 
875
                print 'Pyro Server Initialized. Using Pyro V'+Pyro.constants.VERSION
 
876
        _init_server_done=1
 
877
 
 
878
 
 
879
if __name__=="__main__":
 
880
        print "Pyro version:",Pyro.constants.VERSION