# pylint: disable=no-self-use """ ResInsight caf::PdmObject connection module """ from functools import partial, wraps import grpc import re import builtins import importlib import inspect import sys import PdmObject_pb2 import PdmObject_pb2_grpc import Commands_pb2 import Commands_pb2_grpc def camel_to_snake(name): s1 = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", name) return re.sub("([a-z0-9])([A-Z])", r"\1_\2", s1).lower() def snake_to_camel(name): return "".join(word.title() for word in name.split("_")) def add_method(cls): def decorator(func): setattr(cls, func.__name__, func) return func # returning func means func can still be used normally return decorator def add_static_method(cls): def decorator(func): @wraps(func) def wrapper(*args, **kwargs): return func(*args, **kwargs) setattr(cls, func.__name__, wrapper) # Note we are not binding func, but wrapper which accepts self but does exactly the same as func return func # returning func means func can still be used normally return decorator class PdmObjectBase: """ The ResInsight base class for the Project Data Model """ def _execute_command(self, **command_params): self.__warnings = [] response, call = self._commands.Execute.with_call( Commands_pb2.CommandParams(**command_params) ) for key, value in call.trailing_metadata(): value = value.replace(";;", "\n") if key == "warning": self.__warnings.append(value) return response def __init__(self, pb2_object, channel): self.__warnings = [] self.__chunk_size = 8160 self._channel = channel # Create stubs if self._channel: self._pdm_object_stub = PdmObject_pb2_grpc.PdmObjectServiceStub( self._channel ) self._commands = Commands_pb2_grpc.CommandsStub(self._channel) if pb2_object is not None: # Copy parameters from ResInsight assert isinstance(pb2_object, PdmObject_pb2.PdmObject) self._pb2_object = pb2_object for camel_keyword in self._pb2_object.parameters: snake_keyword = camel_to_snake(camel_keyword) setattr(self, snake_keyword, self.__get_grpc_value(camel_keyword)) else: # Copy parameters from PdmObject defaults self._pb2_object = PdmObject_pb2.PdmObject( class_keyword=self.__class__.__name__ ) self.__copy_to_pb2() def copy_from(self, object): """Copy attribute values from object to self""" for attribute in dir(object): if not attribute.startswith("__"): value = getattr(object, attribute) # This is crucial to avoid overwriting methods if not callable(value): setattr(self, attribute, value) if self.__custom_init__ is not None: self.__custom_init__(self._pb2_object, self._channel) self.update() def warnings(self): return self.__warnings def has_warnings(self): return len(self.__warnings) > 0 def __copy_to_pb2(self): if self._pb2_object is not None: for snake_kw in dir(self): if not snake_kw.startswith("_"): value = getattr(self, snake_kw) # This is crucial to avoid overwriting methods if not callable(value): camel_kw = snake_to_camel(snake_kw) self.__set_grpc_value(camel_kw, value) def pb2_object(self): """Private method""" return self._pb2_object def channel(self): """Private method""" return self._channel def address(self): """Get the unique address of the PdmObject Returns: A 64-bit unsigned integer address """ return self._pb2_object.address def set_visible(self, visible): """Set the visibility of the object in the ResInsight project tree""" self._pb2_object.visible = visible def visible(self): """Get the visibility of the object in the ResInsight project tree""" return self._pb2_object.visible def print_object_info(self): """Print the structure and data content of the PdmObject""" print("=========== " + self.__class__.__name__ + " =================") print("Object Attributes: ") for snake_kw in dir(self): if not snake_kw.startswith("_") and not callable(getattr(self, snake_kw)): camel_kw = snake_to_camel(snake_kw) print( " " + snake_kw + " [" + type(getattr(self, snake_kw)).__name__ + "]: " + str(getattr(self, snake_kw)) ) print("Object Methods:") for snake_kw in dir(self): if not snake_kw.startswith("_") and callable(getattr(self, snake_kw)): print(" " + snake_kw) def __convert_from_grpc_value(self, value): if value.lower() == "false": return False if value.lower() == "true": return True try: int_val = int(value) return int_val except ValueError: try: float_val = float(value) return float_val except ValueError: # We may have a string. Strip internal start and end quotes value = value.strip('"') if self.__islist(value): return self.__makelist(value) return value def __convert_to_grpc_value(self, value): if isinstance(value, bool): if value: return "true" return "false" if isinstance(value, PdmObjectBase): return value.__class__.__name__ + ":" + str(value.address()) if isinstance(value, list): list_of_values = [] for val in value: list_of_values.append(self.__convert_to_grpc_value(val)) return "[" + ", ".join(list_of_values) + "]" return str(value) def __get_grpc_value(self, camel_keyword): return self.__convert_from_grpc_value( self._pb2_object.parameters[camel_keyword] ) def __set_grpc_value(self, camel_keyword, value): self._pb2_object.parameters[camel_keyword] = self.__convert_to_grpc_value(value) def set_value(self, snake_keyword, value): """Set the value associated with the provided keyword and updates ResInsight Arguments: keyword(str): A string containing the parameter keyword value(varying): A value matching the type of the parameter. See keyword documentation and/or print_object_info() to find the correct data type. """ setattr(self, snake_keyword, value) self.update() def __islist(self, value): return value.startswith("[") and value.endswith("]") def __makelist(self, list_string): list_string = list_string.lstrip("[") list_string = list_string.rstrip("]") strings = list_string.split(", ") values = [] for string in strings: values.append(self.__convert_from_grpc_value(string)) return values def __from_pb2_to_resinsight_classes(self, pb2_object_list, super_class_definition): pdm_object_list = [] from .generated.generated_classes import class_from_keyword for pb2_object in pb2_object_list: child_class_definition = class_from_keyword(pb2_object.class_keyword) if child_class_definition is None: child_class_definition = super_class_definition pdm_object = child_class_definition( pb2_object=pb2_object, channel=self.channel() ) pdm_object_list.append(pdm_object) return pdm_object_list def descendants(self, class_definition): """Get a list of all project tree descendants matching the class keyword Arguments: class_definition[class]: A class definition matching the type of class wanted Returns: A list of PdmObjects matching the class_definition """ assert inspect.isclass(class_definition) class_keyword = class_definition.__name__ try: request = PdmObject_pb2.PdmDescendantObjectRequest( object=self._pb2_object, child_keyword=class_keyword ) object_list = self._pdm_object_stub.GetDescendantPdmObjects(request).objects return self.__from_pb2_to_resinsight_classes(object_list, class_definition) except grpc.RpcError as e: if e.code() == grpc.StatusCode.NOT_FOUND: return [] # Valid empty result raise e def children(self, child_field, class_definition): """Get a list of all direct project tree children inside the provided child_field Arguments: child_field[str]: A field name Returns: A list of PdmObjects inside the child_field """ request = PdmObject_pb2.PdmChildObjectRequest( object=self._pb2_object, child_field=child_field ) try: object_list = self._pdm_object_stub.GetChildPdmObjects(request).objects return self.__from_pb2_to_resinsight_classes(object_list, class_definition) except grpc.RpcError as e: if e.code() == grpc.StatusCode.NOT_FOUND: return [] raise e def add_new_object(self, class_definition, child_field=""): """Create and add an object to the specified child field Arguments: class_definition[class]: Class definition of the object to create child_field[str]: The keyword for the field to create a new object in. If empty, the first child array field is used. Returns: The created PdmObject inside the child_field """ from .generated.generated_classes import class_from_keyword assert inspect.isclass(class_definition) class_keyword = class_definition.__name__ request = PdmObject_pb2.CreatePdmChildObjectRequest( object=self._pb2_object, child_field=child_field, class_keyword=class_keyword, ) try: pb2_object = self._pdm_object_stub.CreateChildPdmObject(request) child_class_definition = class_from_keyword(pb2_object.class_keyword) if child_class_definition is None: child_class_definition = class_keyword pdm_object = child_class_definition( pb2_object=pb2_object, channel=self.channel() ) return pdm_object except grpc.RpcError as e: if e.code() == grpc.StatusCode.NOT_FOUND: return None raise e def ancestor(self, class_definition): """Find the first ancestor that matches the provided class_keyword Arguments: class_definition[class]: A class definition matching the type of class wanted """ assert inspect.isclass(class_definition) class_keyword = class_definition.__name__ from .generated.generated_classes import class_from_keyword request = PdmObject_pb2.PdmParentObjectRequest( object=self._pb2_object, parent_keyword=class_keyword ) try: pb2_object = self._pdm_object_stub.GetAncestorPdmObject(request) child_class_definition = class_from_keyword(pb2_object.class_keyword) if child_class_definition is None: child_class_definition = class_definition pdm_object = child_class_definition( pb2_object=pb2_object, channel=self.channel() ) return pdm_object except grpc.RpcError as e: if e.code() == grpc.StatusCode.NOT_FOUND: return None raise e def _call_get_method_async(self, method_name): request = PdmObject_pb2.PdmObjectGetterRequest( object=self._pb2_object, method=method_name ) for chunk in self._pdm_object_stub.CallPdmObjectGetter(request): yield chunk def _call_get_method(self, method_name): all_values = [] generator = self._call_get_method_async(method_name) for chunk in generator: data = getattr(chunk, chunk.WhichOneof("data")) for value in data.data: all_values.append(value) return all_values def __generate_set_method_chunks(self, array, method_request): index = -1 while index < len(array): chunk = PdmObject_pb2.PdmObjectSetterChunk() if index == -1: chunk.set_request.CopyFrom( PdmObject_pb2.PdmObjectSetterRequest( request=method_request, data_count=len(array) ) ) index += 1 else: actual_chunk_size = min(len(array) - index + 1, self.__chunk_size) if isinstance(array[0], float): chunk.CopyFrom( PdmObject_pb2.PdmObjectSetterChunk( doubles=PdmObject_pb2.DoubleArray( data=array[index : index + actual_chunk_size] ) ) ) elif isinstance(array[0], int): chunk.CopyFrom( PdmObject_pb2.PdmObjectSetterChunk( ints=PdmObject_pb2.IntArray( data=array[index : index + actual_chunk_size] ) ) ) elif isinstance(array[0], str): chunk.CopyFrom( PdmObject_pb2.PdmObjectSetterChunk( strings=PdmObject_pb2.StringArray( data=array[index : index + actual_chunk_size] ) ) ) else: raise Exception("Wrong data type for set method") index += actual_chunk_size yield chunk # Final empty message to signal completion chunk = PdmObject_pb2.PdmObjectSetterChunk() yield chunk def _call_set_method(self, method_name, values): method_request = PdmObject_pb2.PdmObjectGetterRequest( object=self._pb2_object, method=method_name ) request_iterator = self.__generate_set_method_chunks(values, method_request) reply = self._pdm_object_stub.CallPdmObjectSetter(request_iterator) if reply.accepted_value_count < len(values): raise IndexError def _call_pdm_method(self, method_name, **kwargs): pb2_params = PdmObject_pb2.PdmObject(class_keyword=method_name) for key, value in kwargs.items(): pb2_params.parameters[snake_to_camel(key)] = self.__convert_to_grpc_value( value ) request = PdmObject_pb2.PdmObjectMethodRequest( object=self._pb2_object, method=method_name, params=pb2_params ) pb2_object = self._pdm_object_stub.CallPdmObjectMethod(request) from .generated.generated_classes import class_from_keyword child_class_definition = class_from_keyword(pb2_object.class_keyword) if child_class_definition is None: return None pdm_object = child_class_definition( pb2_object=pb2_object, channel=self.channel() ) return pdm_object def update(self): """Sync all fields from the Python Object to ResInsight""" self.__copy_to_pb2() if self._pdm_object_stub is not None: self._pdm_object_stub.UpdateExistingPdmObject(self._pb2_object) else: raise Exception( "Object is not connected to GRPC service so cannot update ResInsight" )