Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

283

284

285

286

287

288

289

290

291

292

293

294

295

296

297

298

299

300

301

302

303

304

305

306

307

308

309

310

311

312

313

314

315

316

317

318

319

320

321

322

323

324

325

326

327

328

329

330

331

332

333

334

335

336

337

#!/usr/bin/env python 

""" 

@file ion/core/object/object_utils.py 

@brief Tools and utilities for object management  

@author David Stuebe 

""" 

 

from ion.util.cache import memoize 

 

from net.ooici.core.type import type_pb2 

 

import hashlib 

import struct 

import os 

from google.protobuf import message 

from google.protobuf.internal import containers 

 

import ion.util.ionlog 

log = ion.util.ionlog.getLogger(__name__) 

 

# Globals 

gpb_id_to_class = {} 

 

class ObjectUtilException(Exception): 

    """ Exceptions specific to Object Utilities. """ 

    pass 

 

class OOIObjectError(Exception): 

    """ 

    An exception class for errors that occur in the Object Wrapper class 

    """ 

 

 

def __eq__gpbtype(self, other): 

    ''' Improve performance on GPBType comparisons enormously. ''' 

    if self is other: return True 

    if self is None or other is None: return False 

    return self.object_id == other.object_id and self.version == other.version 

setattr(type_pb2.GPBType, '__eq__', __eq__gpbtype) 

 

def __hash_by_id(self): 

    return id(self) 

    #return hash(repr(self)) 

 

setattr(containers.BaseContainer, '__hash__', __hash_by_id) 

setattr(message.Message, '__hash__', __hash_by_id) 

#setattr(containers.RepeatedCompositeFieldContainer, '__hash__', __hash_by_id) 

#setattr(containers.RepeatedScalarFieldContainer, '__hash__', __hash_by_id) 

 

def sha1hex(val): 

    return hashlib.sha1(val).hexdigest().upper() 

 

def sha1bin(val): 

    return hashlib.sha1(val).digest() 

 

""" This method is deprecated 

def sha1(val, bin=True): 

    if isinstance(val, BaseObject): 

        val = val.value 

    if bin: 

        return sha1bin(val) 

    return sha1hex(val) 

""" 

 

def sha1_to_hex(bytes): 

    """binary form (20 bytes) of sha1 digest to hex string (40 char) 

    """ 

    hex_bytes = struct.unpack('!20B', bytes) 

    almosthex = map(hex, hex_bytes) 

    return ''.join([y[-2:] for y in [x.replace('x', '0') for x in almosthex]]).upper() 

 

@memoize(0) 

def get_enum_from_descriptor(descriptor): 

    """ 

    @TODO Needs cleaning up - should be more robust + get the version 

    """ 

 

    ENUM_NAME = '_MessageTypeIdentifier' 

    ENUM_ID_NAME = '_ID' 

 

    if hasattr(descriptor, 'enum_types'): 

        for enum_type in descriptor.enum_types: 

            if enum_type.name == ENUM_NAME: 

                for val in enum_type.values: 

                    if val.name == ENUM_ID_NAME: 

                        return val 

 

    raise ObjectUtilException(\ 

        '''This object has no Message Type Identifier enum: %s'''\ 

        % (str(descriptor.name))) 

 

@memoize(0) 

def get_type_from_descriptor(descriptor): 

    """     

    Operates on instances and classes of gpb messages! 

    @TODO Needs cleaning up - should be more robust + get the version  

    """ 

 

    val = get_enum_from_descriptor(descriptor) 

    obj_type = create_type_identifier(val.number, 1) 

    return obj_type 

 

def get_type_from_obj(obj): 

    return get_type_from_descriptor(obj.DESCRIPTOR) 

 

def set_type_from_obj(obj, type): 

    if isinstance(type, message.Message): 

        gpb_type = type 

    else: 

        gpb_type = object.__getattribute__(type, 'GPBMessage') 

 

    new_type = get_type_from_obj(obj) 

    gpb_type.CopyFrom(new_type) 

 

@memoize(0) 

def create_type_identifier(object_id='', version=1): 

    """ 

    This returns an unwrapped GPB object to the application level 

    """ 

 

    # Temporary restriction to version == 1. Temporary sanity check! 

    if version != 1: 

        msg = '''Protocol Buffer Object VERSION in the MessageTypeIdentifier should be 1. \n''' 

        msg += '''Explicit versioning is not yet supported.\n''' 

        msg +='''Arguments to create_type_identifier: object_id - "%s"; version - "%s"'''\ 

            % (str(object_id), str(version)) 

        raise ObjectUtilException(msg) 

 

    try: 

        #ObjectType = type_pb2.GPBType(int(object_id), int(version)) 

        ObjectType = type_pb2.GPBType() 

        ObjectType.object_id = int(object_id) 

        ObjectType.version = int(version) 

    except ValueError, ex: 

        raise ObjectUtilException(\ 

            '''Protocol Buffer Object IDs must be integers:object_id - "%s"; version - "%s"'''\ 

            % (str(object_id), str(version))) 

 

    return ObjectType 

 

def build_gpb_lookup(rootpath): 

    """ 

    To be called once on package initialization. 

    The given package must include a list named "protos" specifying which protocol buffer files to import. 

    @param rootpath The full path of the package to import the Protocol Buffers classes from. 

    """ 

 

    ENUM_NAME = '_MessageTypeIdentifier' 

    ENUM_ID_NAME = '_ID' 

    ENUM_VERSION_NAME = '_VERSION' 

 

    global gpb_id_to_class 

    gpb_id_to_class = {} 

 

    root = __import__(rootpath) 

    protos = root.protos 

    for proto in protos: 

        protopath = '%s.%s' % (rootpath, proto) 

        m = __import__(protopath) 

 

    msg_classes = message.Message.__subclasses__() 

    for msg_class in msg_classes: 

        if msg_class.__module__.startswith(rootpath): 

            if hasattr(msg_class, 'DESCRIPTOR'): 

                descriptor = msg_class.DESCRIPTOR 

                if hasattr(descriptor, 'enum_types'): 

                    for enum_type in descriptor.enum_types: 

                        if enum_type.name == ENUM_NAME: 

                            for val in enum_type.values: 

                                if val.name == ENUM_ID_NAME: 

                                    if gpb_id_to_class.has_key(val.number): 

                                        old_def = str(gpb_id_to_class[val.number].__module__) 

                                        new_def = str(msg_class.__module__) 

                                        gpb_num = str(val.number) 

                                        raise ObjectUtilException('Duplicate _MessageTypeIdentifier for '\ 

                                                                      + 'ID# %s in %s; original definition in %s' \ 

                                                                      % (gpb_num, new_def, old_def)) 

 

                                    gpb_id_to_class[val.number] = msg_class 

                                elif val.name == ENUM_VERSION_NAME: 

                                    # Eventually this will implement versioning... 

                                    # For now return an error if the version is not 1 

                                    if val.number != 1: 

                                        msg = '''Protocol Buffer Object VERSION in the MessageTypeIdentifier should be 1. \n''' 

                                        msg += '''Explicit versioning is not yet supported.\n''' 

                                        msg +='''Invalid Object Class: "%s"'''\ 

                                            % (str(msg_class.__name__)) 

                                        raise ObjectUtilException(msg) 

 

def get_gpb_class_from_type_id(typeid): 

    """ 

    Get a callable google.protobuf.message.Message subclass with the given MessageTypeIdentifier enum id. 

    @param id The type id object 

    @retval msg_class The class for the given id. 

    @throws ObjectUtilException 

    """ 

    try: 

        if isinstance(typeid, int): 

            return gpb_id_to_class[typeid] 

        else: 

            return gpb_id_to_class[typeid.object_id] 

    except AttributeError, ex: 

        raise ObjectUtilException('The type argument is not a valid type identifier object: "%s, type: %s "' % (str(typeid), type(typeid))) 

    except KeyError, ex: 

        raise ObjectUtilException('No Protocol Buffer Message class found for id "%s"' % (str(typeid))) 

 

type_name_cache = None 

def find_type_ids(query): 

    """ 

    Fuzzy search for a GPB-defined type with the given text in the name. 

    Assumes that build_gpb_lookup() was called on package init. 

    """ 

    import difflib 

 

    global type_name_cache 

    if type_name_cache is None: 

        type_name_cache = dict((cls.__name__.lower(), cls) for cls in gpb_id_to_class.itervalues()) 

 

    matches = difflib.get_close_matches(query.lower(), type_name_cache.iterkeys(), cutoff=0.5) 

    clses = (type_name_cache[match] for match in matches) 

    return dict((cls._ID, cls) for cls in clses) 

 

def return_proto_file(typeid): 

    """ 

    Similar to open_proto, but instead return the contents of the proto file as a 

    simple string. 

    """ 

    gpb_root = os.path.join('..', 'ion-object-definitions', 'net') 

    cls = get_gpb_class_from_type_id(typeid) 

    proto_pieces = cls.__module__.split('.')[1:] 

    filename = proto_pieces.pop() 

    proto = '%s.proto' % (filename.replace('_pb2', '')) 

    proto_dir = os.sep.join(proto_pieces) 

    proto_path = os.path.join(gpb_root, proto_dir, proto) 

    if os.path.exists(proto_path): 

        return open(proto_path).read() 

    else: 

        py_dir = __import__(cls.__module__).__path__[0] 

        py_file = '%s.py' % (filename) 

        py_path = os.path.join(py_dir, proto_dir, py_file) 

        return open(py_path).read() 

 

def open_proto(typeid): 

    """ Developer utility to open either the .proto if found, or the generated .py, for a GPB typeid. """ 

 

    def launch(path): 

        import platform 

 

        if platform.system() == 'Windows': 

            os.system('notepad %s' % path) 

        else: 

            os.system('open -t %s' % path) 

 

 

    gpb_root = os.path.join('..', 'ion-object-definitions', 'net') 

    cls = get_gpb_class_from_type_id(typeid) 

    proto_pieces = cls.__module__.split('.')[1:] 

    filename = proto_pieces.pop() 

    proto = '%s.proto' % (filename.replace('_pb2', '')) 

    proto_dir = os.sep.join(proto_pieces) 

    proto_path = os.path.join(gpb_root, proto_dir, proto) 

    if os.path.exists(proto_path): 

        launch(proto_path) 

    else: 

        py_dir = __import__(cls.__module__).__path__[0] 

        py_file = '%s.py' % (filename) 

        py_path = os.path.join(py_dir, proto_dir, py_file) 

        launch(py_path) 

 

 

 

def _gpb_source(func): 

 

    def call_func(self, *args, **kwargs): 

 

        func_name = func.__name__ 

        ''' 

        print 'GPB SOURCE' 

        print 'func name', func_name, func 

        print 'args', args 

        print 'kwargs', kwargs 

        ''' 

        source = self._source 

        if source._invalid: 

            log.error(source.Debug()) 

            raise OOIObjectError('Can not access Invalidated Object in function "%s"' % func_name) 

 

        return func(source, *args, **kwargs) 

 

    return call_func 

 

def _gpb_source_root(func): 

 

        def call_func(self, *args, **kwargs): 

 

            func_name = func.__name__ 

 

            ''' 

            print 'GPB SOURCE ROOT' 

 

            print 'func name', func_name, func 

            print 'args', args 

            print 'kwargs', kwargs 

            ''' 

            source = self._source 

            if source._invalid: 

                log.error(source.Debug()) 

                raise OOIObjectError('Can not access Invalidated Object in function "%s"' % func_name) 

 

            source_root = source._root 

 

            return func(source_root, *args, **kwargs) 

 

 

        return call_func 

 

 

 

 

# Build the lookup table on first import 

build_gpb_lookup('net') 

 

# Build the CDM TYPES for import  

CDM_GROUP_TYPE = create_type_identifier(object_id=10020, version=1) 

CDM_DATASET_TYPE = create_type_identifier(object_id=10001, version=1) 

CDM_VARIABLE_TYPE = create_type_identifier(object_id=10024, version=1) 

CDM_DIMENSION_TYPE = create_type_identifier(object_id=10018, version=1) 

CDM_ATTRIBUTE_TYPE = create_type_identifier(object_id=10017, version=1) 

ARRAY_STRUCTURE_TYPE = create_type_identifier(object_id=10025, version=1) 

CDM_ARRAY_INT32_TYPE = create_type_identifier(object_id=10009, version=1) 

CDM_ARRAY_UINT32_TYPE = create_type_identifier(object_id=10010, version=1) 

CDM_ARRAY_INT64_TYPE = create_type_identifier(object_id=10011, version=1) 

CDM_ARRAY_UINT64_TYPE = create_type_identifier(object_id=10012, version=1) 

CDM_ARRAY_FLOAT32_TYPE = create_type_identifier(object_id=10013, version=1) 

CDM_ARRAY_FLOAT64_TYPE = create_type_identifier(object_id=10014, version=1) 

CDM_ARRAY_STRING_TYPE = create_type_identifier(object_id=10015, version=1) 

CDM_ARRAY_OPAQUE_TYPE = create_type_identifier(object_id=10016, version=1)