Python: add type hinting to python code.

Types are checked using mypy.

Fixes #10394.
This commit is contained in:
Kristian Bendiksen 2023-07-12 11:42:17 +02:00
parent b1157436fe
commit 7aabe8c4a8
31 changed files with 522 additions and 292 deletions

View File

@ -46,7 +46,7 @@ jobs:
run: |
execute_process(
COMMAND cmake
-S Fwk/AppFwk
-S Fwk
-B cmakebuild
-G Ninja
RESULT_VARIABLE result
@ -70,13 +70,13 @@ jobs:
- name: Run Unit Tests
shell: bash
run: |
cmakebuild/cafProjectDataModel/cafPdmCore/cafPdmCore_UnitTests/cafPdmCore_UnitTests
cmakebuild/cafProjectDataModel/cafPdmXml/cafPdmXml_UnitTests/cafPdmXml_UnitTests
cmakebuild/cafProjectDataModel/cafProjectDataModel_UnitTests/cafProjectDataModel_UnitTests
cmakebuild/cafPdmScripting/cafPdmScripting_UnitTests/cafPdmScripting_UnitTests
cmakebuild/AppFwk/cafProjectDataModel/cafPdmCore/cafPdmCore_UnitTests/cafPdmCore_UnitTests
cmakebuild/AppFwk/cafProjectDataModel/cafPdmXml/cafPdmXml_UnitTests/cafPdmXml_UnitTests
cmakebuild/AppFwk/cafProjectDataModel/cafProjectDataModel_UnitTests/cafProjectDataModel_UnitTests
cmakebuild/AppFwk/cafPdmScripting/cafPdmScripting_UnitTests/cafPdmScripting_UnitTests
- name: Run Unit Tests Windows (does not work on Linux)
if: contains( matrix.os, 'windows')
shell: bash
run: |
cmakebuild/cafUserInterface/cafUserInterface_UnitTests/cafUserInterface_UnitTests
cmakebuild/AppFwk/cafUserInterface/cafUserInterface_UnitTests/cafUserInterface_UnitTests

View File

@ -230,6 +230,14 @@ jobs:
run: |
cmakebuild/ApplicationExeCode/ResInsight --unittest
- name: (Python) Check types using mypy
if: matrix.config.build-python-module
shell: bash
run: |
${{ steps.python-path.outputs.PYTHON_EXECUTABLE }} -m pip install mypy types-protobuf
cd GrpcInterface/Python/rips
${{ steps.python-path.outputs.PYTHON_EXECUTABLE }} -m mypy *.py generated/generated_classes.py
- name: Run pytest
if: matrix.config.build-python-module
env:

View File

@ -16,8 +16,8 @@
# Python version of RiaVersionInfo.h
# Just sets version constants
RESINSIGHT_MAJOR_VERSION = "@RESINSIGHT_MAJOR_VERSION@"
RESINSIGHT_MINOR_VERSION = "@RESINSIGHT_MINOR_VERSION@"
RESINSIGHT_PATCH_VERSION = "@RESINSIGHT_PATCH_VERSION@"
RESINSIGHT_MAJOR_VERSION : str = "@RESINSIGHT_MAJOR_VERSION@"
RESINSIGHT_MINOR_VERSION : str = "@RESINSIGHT_MINOR_VERSION@"
RESINSIGHT_PATCH_VERSION : str = "@RESINSIGHT_PATCH_VERSION@"
PYTHON_GRPC_PROTOC_VERSION = "@PYTHON_GRPC_PROTOC_VERSION@"
PYTHON_GRPC_PROTOC_VERSION : str = "@PYTHON_GRPC_PROTOC_VERSION@"

View File

@ -29,6 +29,7 @@
#include "RicVec3dPickEventHandler.h"
#include "cafCmdFeatureManager.h"
#include "cafPdmObjectScriptingCapability.h"
#include "cafPdmUiPickableLineEditor.h"
#include "cafPdmUiPushButtonEditor.h"
#include "cafPdmUiTextEditor.h"
@ -44,7 +45,7 @@ CAF_PDM_SOURCE_INIT( RimTextAnnotation, "RimTextAnnotation" );
//--------------------------------------------------------------------------------------------------
RimTextAnnotation::RimTextAnnotation()
{
CAF_PDM_InitObject( "TextAnnotation", ":/TextAnnotation16x16.png" );
CAF_PDM_InitScriptableObject( "TextAnnotation", ":/TextAnnotation16x16.png" );
setUi3dEditorTypeName( RicTextAnnotation3dEditor::uiEditorTypeName() );
CAF_PDM_InitField( &m_anchorPointXyd, "AnchorPointXyd", Vec3d::ZERO, "Anchor Point" );

View File

@ -32,6 +32,7 @@
#include "RimWellPath.h"
#include "RimWellPathValve.h"
#include "cafPdmObjectScriptingCapability.h"
#include "cafPdmUiDateEditor.h"
#include "cafPdmUiDoubleSliderEditor.h"
@ -42,7 +43,7 @@ CAF_PDM_SOURCE_INIT( RimPerforationInterval, "Perforation" );
//--------------------------------------------------------------------------------------------------
RimPerforationInterval::RimPerforationInterval()
{
CAF_PDM_InitObject( "Perforation", ":/PerforationInterval16x16.png" );
CAF_PDM_InitScriptableObject( "Perforation", ":/PerforationInterval16x16.png" );
CAF_PDM_InitField( &m_startMD, "StartMeasuredDepth", 0.0, "Start MD" );
CAF_PDM_InitField( &m_endMD, "EndMeasuredDepth", 0.0, "End MD" );

View File

@ -26,6 +26,7 @@
#include "RimWellPath.h"
#include "RimWellPathFractureCollection.h"
#include "cafPdmObjectScriptingCapability.h"
#include "cafPdmUiDoubleSliderEditor.h"
CAF_PDM_SOURCE_INIT( RimWellPathFracture, "WellPathFracture" );
@ -35,7 +36,7 @@ CAF_PDM_SOURCE_INIT( RimWellPathFracture, "WellPathFracture" );
//--------------------------------------------------------------------------------------------------
RimWellPathFracture::RimWellPathFracture()
{
CAF_PDM_InitObject( "Fracture", ":/FractureSymbol16x16.png" );
CAF_PDM_InitScriptableObject( "Fracture", ":/FractureSymbol16x16.png" );
CAF_PDM_InitField( &m_measuredDepth, "MeasuredDepth", 0.0f, "Measured Depth Location" );
m_measuredDepth.uiCapability()->setUiEditorTypeName( caf::PdmUiDoubleSliderEditor::uiEditorTypeName() );

View File

@ -24,6 +24,7 @@
#include "RimColorLegendItem.h"
#include "cafPdmFieldReorderCapability.h"
#include "cafPdmObjectScriptingCapability.h"
#include <algorithm>
@ -34,7 +35,7 @@ CAF_PDM_SOURCE_INIT( RimColorLegend, "ColorLegend" );
//--------------------------------------------------------------------------------------------------
RimColorLegend::RimColorLegend()
{
CAF_PDM_InitObject( "ColorLegend", ":/Legend.png" );
CAF_PDM_InitScriptableObject( "ColorLegend", ":/Legend.png" );
CAF_PDM_InitField( &m_colorLegendName, "ColorLegendName", QString( "" ), "Color Legend Name" );

View File

@ -34,6 +34,7 @@
#include "RimEclipseInputPropertyCollection.h"
#include "RimReservoirCellResultsStorage.h"
#include "cafPdmObjectScriptingCapability.h"
#include "cafProgressInfo.h"
#include <QDir>
@ -46,7 +47,7 @@ CAF_PDM_SOURCE_INIT( RimRoffCase, "RimRoffCase" );
RimRoffCase::RimRoffCase()
: RimEclipseCase()
{
CAF_PDM_InitObject( "RimRoffCase", ":/EclipseInput48x48.png" );
CAF_PDM_InitScriptableObject( "RimRoffCase", ":/EclipseInput48x48.png" );
}
//--------------------------------------------------------------------------------------------------

View File

@ -703,7 +703,6 @@ add_subdirectory(Fwk/AppFwk/cafPdmCvf)
add_subdirectory(Fwk/AppFwk/CommonCode)
add_subdirectory(Fwk/AppFwk/cafVizExtensions)
option(CAF_CVF_SCRIPTING "" ON)
add_subdirectory(Fwk/AppFwk/cafPdmScripting)
add_subdirectory(Fwk/AppFwk/cafCommandFeatures)

View File

@ -1,7 +1,5 @@
project(cafPdmScripting)
option(CAF_CVF_SCRIPTING "Enable CVF-data support in Scripting" OFF)
# Unity Build
if(CAF_ENABLE_UNITY_BUILD)
message("Cmake Unity build is enabled on : ${PROJECT_NAME}")
@ -30,21 +28,13 @@ set(PROJECT_FILES
cafPdmMarkdownGenerator.cpp
cafPdmMarkdownBuilder.h
cafPdmMarkdownBuilder.cpp
)
set(CAF_LIBS cafProjectDataModel)
if(CAF_CVF_SCRIPTING)
list(
APPEND
PROJECT_FILES
cafPdmFieldScriptingCapabilityCvfColor3.h
cafPdmFieldScriptingCapabilityCvfColor3.cpp
cafPdmFieldScriptingCapabilityCvfVec3d.h
cafPdmFieldScriptingCapabilityCvfVec3d.cpp
)
list(APPEND CAF_LIBS cafPdmCvf)
endif()
)
set(CAF_LIBS cafProjectDataModel cafPdmCvf)
add_library(${PROJECT_NAME} ${PROJECT_FILES})

View File

@ -38,6 +38,8 @@
#include "cafPdmAbstractFieldScriptingCapability.h"
#include "cafPdmChildArrayField.h"
#include "cafPdmChildField.h"
#include "cafPdmFieldScriptingCapabilityCvfColor3.h"
#include "cafPdmFieldScriptingCapabilityCvfVec3d.h"
#include "cafPdmObject.h"
#include "cafPdmObjectFactory.h"
#include "cafPdmObjectMethod.h"
@ -135,10 +137,10 @@ QString caf::PdmPythonGenerator::generate( PdmObjectFactory* factory, std::vecto
auto pdmChildArrayField = dynamic_cast<const PdmChildArrayFieldHandle*>( field );
if ( pdmValueField )
{
QString dataType = PdmPythonGenerator::dataTypeString( field, true );
QString dataType = PdmPythonGenerator::dataTypeString( field, false );
if ( field->xmlCapability()->isVectorField() )
{
dataType = QString( "List of %1" ).arg( dataType );
dataType = QString( "List[%1]" ).arg( dataType );
}
bool shouldBeMethod = false;
@ -159,9 +161,10 @@ QString caf::PdmPythonGenerator::generate( PdmObjectFactory* factory, std::vecto
.arg( comment )
.arg( dataType );
QString fieldCode = QString( " def %1(self):\n%2\n return "
"self._call_get_method(\"%3\")\n" )
QString fieldCode = QString( " def %1(self) -> %2:\n%3\n return "
"self._call_get_method(\"%4\")\n" )
.arg( snake_field_name )
.arg( dataType )
.arg( fullComment )
.arg( scriptability->scriptFieldName() );
classMethodsGenerated[field->ownerClass()][snake_field_name] = fieldCode;
@ -173,9 +176,11 @@ QString caf::PdmPythonGenerator::generate( PdmObjectFactory* factory, std::vecto
.arg( comment )
.arg( dataType );
QString fieldCode = QString( " def set_%1(self, values):\n%2\n "
"self._call_set_method(\"%3\", values)\n" )
QString fieldCode =
QString( " def set_%1(self, values : %2) -> None:\n%3\n "
"self._call_set_method(\"%4\", values)\n" )
.arg( snake_field_name )
.arg( dataType )
.arg( fullComment )
.arg( scriptability->scriptFieldName() );
classMethodsGenerated[field->ownerClass()][QString( "set_%1" ).arg( snake_field_name )] =
@ -186,8 +191,13 @@ QString caf::PdmPythonGenerator::generate( PdmObjectFactory* factory, std::vecto
{
QString valueString = getDefaultValue( field );
if ( valueString == "None" )
{
dataType = QString( "Optional[%1]" ).arg( dataType );
}
QString fieldCode =
QString( " self.%1 = %2\n" ).arg( snake_field_name ).arg( valueString );
QString( " self.%1: %2 = %3\n" ).arg( snake_field_name ).arg( dataType ).arg( valueString );
QString fullComment;
{
@ -218,7 +228,7 @@ QString caf::PdmPythonGenerator::generate( PdmObjectFactory* factory, std::vecto
dataTypesInChildFields.insert( scriptDataType );
QString commentDataType = field->xmlCapability()->isVectorField()
? QString( "List of %1" ).arg( scriptDataType )
? QString( "List[%1]" ).arg( scriptDataType )
: scriptDataType;
QString fullComment =
@ -228,7 +238,8 @@ QString caf::PdmPythonGenerator::generate( PdmObjectFactory* factory, std::vecto
if ( pdmChildField )
{
QString fieldCode = QString( " def %1(self):\n%2\n children = "
QString fieldCode =
QString( " def %1(self) -> Optional[%4]:\n%2\n children = "
"self.children(\"%3\", %4)\n return children[0] if "
"len(children) > 0 else None\n" )
.arg( snake_field_name )
@ -239,9 +250,10 @@ QString caf::PdmPythonGenerator::generate( PdmObjectFactory* factory, std::vecto
}
else
{
QString fieldCode = QString( " def %1(self):\n%2\n return "
"self.children(\"%3\", %4)\n" )
QString fieldCode = QString( " def %1(self) -> List[%2]:\n%3\n return "
"self.children(\"%4\", %5)\n" )
.arg( snake_field_name )
.arg( scriptDataType )
.arg( fullComment )
.arg( scriptability->scriptFieldName() )
.arg( scriptDataType );
@ -269,8 +281,22 @@ QString caf::PdmPythonGenerator::generate( PdmObjectFactory* factory, std::vecto
QStringList argumentComments;
outputArgumentStrings.push_back( QString( "\"%1\"" ).arg( methodName ) );
QString returnDataType = "None";
QString returnComment;
if ( method->defaultResult() ) returnComment = method->defaultResult()->xmlCapability()->classKeyword();
if ( method->defaultResult() )
{
QString classKeyword = method->defaultResult()->xmlCapability()->classKeyword();
returnComment = classKeyword;
returnDataType = PdmObjectScriptingCapabilityRegister::scriptClassNameFromClassKeyword( classKeyword );
outputArgumentStrings.push_back( QString( "%1" ).arg( returnDataType ) );
if ( method->isNullptrValidResult() )
{
returnDataType = QString( "Optional[%1]" ).arg( returnDataType );
}
}
for ( auto field : arguments )
{
@ -279,9 +305,13 @@ QString caf::PdmPythonGenerator::generate( PdmObjectFactory* factory, std::vecto
auto dataType = dataTypeString( field, false );
bool isList = field->xmlCapability()->isVectorField();
if ( isList ) dataType = "List of " + dataType;
if ( isList ) dataType = QString( "List[%1]" ).arg( dataType );
QString defaultValue = getDefaultValue( field );
if ( defaultValue == "None" )
{
dataType = QString( "Optional[%1]" ).arg( dataType );
}
QString commentOrEnumDescription = field->uiCapability()->uiWhatsThis();
@ -293,7 +323,8 @@ QString caf::PdmPythonGenerator::generate( PdmObjectFactory* factory, std::vecto
commentOrEnumDescription = "One of [" + enumTexts.join( ", " ) + "]";
}
inputArgumentStrings.push_back( QString( "%1=%2" ).arg( argumentName ).arg( defaultValue ) );
inputArgumentStrings.push_back(
QString( "%1: %2=%3" ).arg( argumentName ).arg( dataType ).arg( defaultValue ) );
outputArgumentStrings.push_back( QString( "%1=%1" ).arg( argumentName ) );
argumentComments.push_back(
QString( "%1 (%2): %3" ).arg( argumentName ).arg( dataType ).arg( commentOrEnumDescription ) );
@ -304,12 +335,27 @@ QString caf::PdmPythonGenerator::generate( PdmObjectFactory* factory, std::vecto
.arg( argumentComments.join( "\n " ) )
.arg( returnComment );
QString methodCode = QString( " def %1(self, %2):\n%3\n return "
"self._call_pdm_method(%4)\n" )
QString methodBody = QString( "self._call_pdm_method_void(%1)" ).arg( outputArgumentStrings.join( ", " ) );
if ( returnDataType != "None" )
{
if ( method->isNullptrValidResult() )
{
methodBody = QString( "return self._call_pdm_method_return_optional_value(%1)" )
.arg( outputArgumentStrings.join( ", " ) );
}
else
{
methodBody =
QString( "return self._call_pdm_method_return_value(%1)" ).arg( outputArgumentStrings.join( ", " ) );
}
}
QString methodCode = QString( " def %1(self, %2) -> %3:\n%4\n %5\n" )
.arg( snake_method_name )
.arg( inputArgumentStrings.join( ", " ) )
.arg( returnDataType )
.arg( fullComment )
.arg( outputArgumentStrings.join( ", " ) );
.arg( methodBody );
classMethodsGenerated[classKeyword][snake_method_name] = methodCode;
}
@ -320,7 +366,12 @@ QString caf::PdmPythonGenerator::generate( PdmObjectFactory* factory, std::vecto
std::set<QString> classesWritten;
classesWritten.insert( "PdmObjectBase" );
out << "from __future__ import annotations\n";
out << "from rips.pdmobject import PdmObjectBase\n";
out << "import PdmObject_pb2\n";
out << "import grpc\n";
out << "from typing import Optional, Dict, List, Type\n";
out << "\n";
for ( std::shared_ptr<PdmObject> object : dummyObjects )
{
@ -367,7 +418,9 @@ QString caf::PdmPythonGenerator::generate( PdmObjectFactory* factory, std::vecto
classCode +=
QString( " __custom_init__ = None #: Assign a custom init routine to be run at __init__\n\n" );
classCode += QString( " def __init__(self, pb2_object=None, channel=None):\n" );
classCode +=
QString( " def __init__(self, pb2_object: Optional[PdmObject_pb2.PdmObject]=None, channel: "
"Optional[grpc.Channel]=None) -> None:\n" );
if ( !scriptSuperClassNames.empty() )
{
// Own attributes. This initializes a lot of attributes to None.
@ -398,15 +451,16 @@ QString caf::PdmPythonGenerator::generate( PdmObjectFactory* factory, std::vecto
scriptSuperClassNames.push_back( scriptClassName );
}
}
out << "def class_dict():\n";
out << " classes = {}\n";
out << "def class_dict() -> Dict[str, Type[PdmObjectBase]]:\n";
out << " classes : Dict[str, Type[PdmObjectBase]] = {}\n";
for ( QString classKeyword : classesWritten )
{
out << QString( " classes['%1'] = %1\n" ).arg( classKeyword );
}
out << " return classes\n\n";
out << "def class_from_keyword(class_keyword):\n";
out << "def class_from_keyword(class_keyword : str) -> Optional[Type[PdmObjectBase]]:\n";
out << " all_classes = class_dict()\n";
out << " if class_keyword in all_classes.keys():\n";
out << " return all_classes[class_keyword]\n";
@ -449,7 +503,12 @@ QString PdmPythonGenerator::getDefaultValue( PdmFieldHandle* field )
QTextStream valueStream( &valueString );
scriptability->readFromField( valueStream, true, true );
}
if ( valueString.isEmpty() ) valueString = QString( "\"\"" );
if ( valueString.isEmpty() )
{
valueString = defaultValue;
}
valueString = pythonifyDataValue( valueString );
defaultValue = valueString;
@ -482,14 +541,20 @@ QString PdmPythonGenerator::dataTypeString( const PdmFieldHandle* field, bool us
auto scriptability = field->capability<PdmAbstractFieldScriptingCapability>();
if ( scriptability && !scriptability->enumScriptTexts().empty() ) return "str";
QString dataType = xmlObj->dataTypeName();
QString dataType = PdmObjectScriptingCapabilityRegister::scriptClassNameFromClassKeyword( xmlObj->dataTypeName() );
std::map<QString, QString> builtins = { { QString::fromStdString( typeid( double ).name() ), "float" },
std::map<QString, QString> builtins = {
{ QString::fromStdString( typeid( double ).name() ), "float" },
{ QString::fromStdString( typeid( float ).name() ), "float" },
{ QString::fromStdString( typeid( int ).name() ), "int" },
{ QString::fromStdString( typeid( bool ).name() ), "bool" },
{ QString::fromStdString( typeid( time_t ).name() ), "time" },
{ QString::fromStdString( typeid( QString ).name() ), "str" } };
{ QString::fromStdString( typeid( time_t ).name() ), "int" },
{ QString::fromStdString( typeid( QString ).name() ), "str" },
{ QString::fromStdString( typeid( cvf::Vec3d ).name() ), "List[float]" },
{ QString::fromStdString( typeid( cvf::Color3f ).name() ), "str" },
{ QString::fromStdString( typeid( caf::FilePath ).name() ), "str" },
{ QString::fromStdString( typeid( std::vector<double> ).name() ), "List[float]" },
};
bool foundBuiltin = false;
for ( auto builtin : builtins )

View File

@ -12,6 +12,27 @@ set(CMAKE_CXX_STANDARD 17)
project (TestCafAndVizFwk)
# ##############################################################################
# Setup the main platform defines
# ##############################################################################
if(${CMAKE_SYSTEM_NAME} MATCHES "Linux")
add_definitions(-DCVF_LINUX)
elseif(${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
add_definitions(-DCVF_OSX)
elseif(MSVC)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP")
set(_HAS_STD_BYTE 0)
endif()
if(${CMAKE_SYSTEM_NAME} MATCHES "Linux")
set(CMAKE_CXX_FLAGS
"-DCVF_LINUX -pipe -Wextra -Woverloaded-virtual -Wformat -Wno-unused-parameter"
)
set(CMAKE_CXX_FLAGS_DEBUG "-ggdb -g3 -O0 -DDEBUG -D_DEBUG")
set(CMAKE_CXX_FLAGS_RELEASE "-O2 -DNO_DEBUG")
endif()
find_package(Qt5 COMPONENTS REQUIRED Core Gui OpenGL Widgets)
set(QT_LIBRARIES Qt5::Core Qt5::Gui Qt5::OpenGL Qt5::Widgets)

View File

@ -184,6 +184,7 @@ foreach(rips_proto ${GRPC_PROTO_FILES})
${RESINSIGHT_GRPC_PYTHON_EXECUTABLE} ARGS -m grpc_tools.protoc -I
"${rips_proto_path}" --python_out
"${GRPC_PYTHON_SOURCE_PATH}/rips/generated" --grpc_python_out
"${GRPC_PYTHON_SOURCE_PATH}/rips/generated" --pyi_out
"${GRPC_PYTHON_SOURCE_PATH}/rips/generated" "${rips_proto}"
DEPENDS "${rips_proto}"
COMMENT "Generating ${rips_proto_python} and ${rips_grpc_python}"

View File

@ -2,3 +2,4 @@ grpcio
grpcio-tools
protobuf
wheel
typing-extensions

View File

@ -17,7 +17,9 @@ from .contour_map import EclipseContourMap, GeoMechContourMap
from .well_log_plot import WellLogPlot
from .simulation_well import SimulationWell
__all__ = []
from typing import List
__all__: List[str] = []
for key in class_dict():
__all__.append(key)

View File

@ -37,6 +37,7 @@ result
import builtins
import grpc
from typing import List, Tuple
import Case_pb2
import Case_pb2_grpc
@ -74,7 +75,7 @@ def __custom_init__(self, pb2_object, channel):
@add_method(Case)
def __grid_count(self):
def __grid_count(self) -> int:
"""Get number of grids in the case"""
try:
return self.__case_stub.GetGridCount(self.__request()).count
@ -125,7 +126,7 @@ def __generate_property_input_chunks(self, array, parameters):
@add_method(Case)
def grid(self, index=0):
def grid(self, index: int = 0) -> Grid:
"""Get Grid of a given index
Arguments:
@ -138,7 +139,7 @@ def grid(self, index=0):
@add_method(Case)
def grids(self):
def grids(self) -> List[Grid]:
"""Get a list of all rips Grid objects in the case
Returns:
@ -166,7 +167,7 @@ def replace(self, new_grid_file):
@add_method(Case)
def cell_count(self, porosity_model="MATRIX_MODEL"):
def cell_count(self, porosity_model: str = "MATRIX_MODEL") -> int:
"""Get a cell count object containing number of active cells and total number of cells
Arguments:
@ -193,7 +194,7 @@ def cell_count(self, porosity_model="MATRIX_MODEL"):
@add_method(Case)
def cell_info_for_active_cells_async(self, porosity_model="MATRIX_MODEL"):
def cell_info_for_active_cells_async(self, porosity_model: str = "MATRIX_MODEL"):
"""Get Stream of cell info objects for current case
Arguments:
@ -213,7 +214,7 @@ def cell_info_for_active_cells_async(self, porosity_model="MATRIX_MODEL"):
@add_method(Case)
def cell_info_for_active_cells(self, porosity_model="MATRIX_MODEL"):
def cell_info_for_active_cells(self, porosity_model: str = "MATRIX_MODEL"):
"""Get list of cell info objects for current case
Arguments:
@ -298,7 +299,7 @@ def reservoir_boundingbox(self):
@add_method(Case)
def reservoir_depth_range(self):
def reservoir_depth_range(self) -> Tuple[float, float]:
"""Get the reservoir depth range
Returns:
@ -604,7 +605,7 @@ def export_flow_characteristics(
@add_method(Case)
def available_properties(self, property_type, porosity_model="MATRIX_MODEL"):
def available_properties(self, property_type, porosity_model: str = "MATRIX_MODEL"):
"""Get a list of available properties
For argument details, see :ref:`Result Definition <result-definition-label>`
@ -626,7 +627,7 @@ def available_properties(self, property_type, porosity_model="MATRIX_MODEL"):
@add_method(Case)
def active_cell_property_async(
self, property_type, property_name, time_step, porosity_model="MATRIX_MODEL"
self, property_type, property_name, time_step, porosity_model: str = "MATRIX_MODEL"
):
"""Get a cell property for all active cells. Async, so returns an iterator. For argument details, see :ref:`Result Definition <result-definition-label>`
@ -655,7 +656,7 @@ def active_cell_property_async(
@add_method(Case)
def active_cell_property(
self, property_type, property_name, time_step, porosity_model="MATRIX_MODEL"
self, property_type, property_name, time_step, porosity_model: str = "MATRIX_MODEL"
):
"""Get a cell property for all active cells. Sync, so returns a list. For argument details, see :ref:`Result Definition <result-definition-label>`
@ -681,7 +682,7 @@ def active_cell_property(
@add_method(Case)
def selected_cell_property_async(
self, property_type, property_name, time_step, porosity_model="MATRIX_MODEL"
self, property_type, property_name, time_step, porosity_model: str = "MATRIX_MODEL"
):
"""Get a cell property for all selected cells. Async, so returns an iterator. For argument details, see :ref:`Result Definition <result-definition-label>`
@ -710,7 +711,7 @@ def selected_cell_property_async(
@add_method(Case)
def selected_cell_property(
self, property_type, property_name, time_step, porosity_model="MATRIX_MODEL"
self, property_type, property_name, time_step, porosity_model: str = "MATRIX_MODEL"
):
"""Get a cell property for all selected cells. Sync, so returns a list. For argument details, see :ref:`Result Definition <result-definition-label>`
@ -741,7 +742,7 @@ def grid_property_async(
property_name,
time_step,
grid_index=0,
porosity_model="MATRIX_MODEL",
porosity_model: str = "MATRIX_MODEL",
):
"""Get a cell property for all grid cells. Async, so returns an iterator. For argument details, see :ref:`Result Definition <result-definition-label>`
@ -777,7 +778,7 @@ def grid_property(
property_name,
time_step,
grid_index=0,
porosity_model="MATRIX_MODEL",
porosity_model: str = "MATRIX_MODEL",
):
"""Get a cell property for all grid cells. Synchronous, so returns a list. For argument details, see :ref:`Result Definition <result-definition-label>`
@ -808,7 +809,7 @@ def set_active_cell_property_async(
property_type,
property_name,
time_step,
porosity_model="MATRIX_MODEL",
porosity_model: str = "MATRIX_MODEL",
):
"""Set cell property for all active cells Async. Takes an iterator to the input values. For argument details, see :ref:`Result Definition <result-definition-label>`
@ -835,7 +836,12 @@ def set_active_cell_property_async(
@add_method(Case)
def set_active_cell_property(
self, values, property_type, property_name, time_step, porosity_model="MATRIX_MODEL"
self,
values,
property_type,
property_name,
time_step,
porosity_model: str = "MATRIX_MODEL",
):
"""Set a cell property for all active cells. For argument details, see :ref:`Result Definition <result-definition-label>`
@ -869,7 +875,7 @@ def set_grid_property(
property_name,
time_step,
grid_index=0,
porosity_model="MATRIX_MODEL",
porosity_model: str = "MATRIX_MODEL",
):
"""Set a cell property for all grid cells. For argument details, see :ref:`Result Definition <result-definition-label>`
@ -989,7 +995,7 @@ def simulation_wells(self):
@add_method(Case)
def active_cell_centers_async(self, porosity_model="MATRIX_MODEL"):
def active_cell_centers_async(self, porosity_model: str = "MATRIX_MODEL"):
"""Get a cell centers for all active cells. Async, so returns an iterator
Arguments:
@ -1007,7 +1013,7 @@ def active_cell_centers_async(self, porosity_model="MATRIX_MODEL"):
@add_method(Case)
def active_cell_centers(self, porosity_model="MATRIX_MODEL"):
def active_cell_centers(self, porosity_model: str = "MATRIX_MODEL"):
"""Get a cell centers for all active cells. Synchronous, so returns a list.
Arguments:
@ -1025,7 +1031,7 @@ def active_cell_centers(self, porosity_model="MATRIX_MODEL"):
@add_method(Case)
def active_cell_corners_async(self, porosity_model="MATRIX_MODEL"):
def active_cell_corners_async(self, porosity_model: str = "MATRIX_MODEL"):
"""Get a cell corners for all active cells. Async, so returns an iterator
Arguments:
@ -1043,7 +1049,7 @@ def active_cell_corners_async(self, porosity_model="MATRIX_MODEL"):
@add_method(Case)
def active_cell_corners(self, porosity_model="MATRIX_MODEL"):
def active_cell_corners(self, porosity_model: str = "MATRIX_MODEL"):
"""Get a cell corners for all active cells. Synchronous, so returns a list.
Arguments:
@ -1287,7 +1293,7 @@ def __generate_nnc_property_input_chunks(self, array, parameters):
@add_method(Case)
def set_nnc_connections_values(
self, values, property_name, time_step, porosity_model="MATRIX_MODEL"
self, values, property_name, time_step, porosity_model: str = "MATRIX_MODEL"
):
"""Set nnc connection values for all connections..

View File

@ -10,11 +10,11 @@ from .resinsight_classes import EclipseContourMap, GeoMechContourMap
@add_method(EclipseContourMap)
def export_to_text(
self,
export_file_name="",
export_local_coordinates=False,
undefined_value_label="NaN",
exclude_undefined_values=False,
self: EclipseContourMap,
export_file_name: str = "",
export_local_coordinates: bool = False,
undefined_value_label: str = "NaN",
exclude_undefined_values: bool = False,
):
"""Export snapshot for the current view
@ -37,11 +37,11 @@ def export_to_text(
@add_method(GeoMechContourMap)
def export_to_text(
self,
export_file_name="",
export_local_coordinates=False,
undefined_value_label="NaN",
exclude_undefined_values=False,
self: GeoMechContourMap,
export_file_name: str = "",
export_local_coordinates: bool = False,
undefined_value_label: str = "NaN",
exclude_undefined_values: bool = False,
):
"""Export snapshot for the current view

View File

@ -8,6 +8,12 @@ about Case grids.
import Case_pb2
import Grid_pb2
import Grid_pb2_grpc
import Definitions_pb2
from typing import Tuple, Optional, List
from grpc import Channel
from .case import Case
class Grid:
@ -16,15 +22,15 @@ class Grid:
:meth:`rips.case.grids()`
"""
def __init__(self, index, case, channel):
def __init__(self, index: int, case: Case, channel: Channel) -> None:
self.__channel = channel
self.__stub = Grid_pb2_grpc.GridStub(self.__channel)
self.case = case
self.index = index
self.case: Case = case
self.index: int = index
self.cached_dimensions = None
def dimensions(self):
def dimensions(self) -> Optional[Definitions_pb2.Vec3i]:
"""The dimensions in i, j, k direction
Returns:
@ -52,7 +58,7 @@ class Grid:
for chunk in chunks:
yield chunk
def cell_centers(self):
def cell_centers(self) -> List[Definitions_pb2.Vec3d]:
"""The cell center for all cells in given grid
Returns:
@ -92,7 +98,7 @@ class Grid:
corners.append(center)
return corners
def property_data_index_from_ijk(self, i, j, k):
def property_data_index_from_ijk(self, i: int, j: int, k: int) -> int:
"""Compute property index from 1-based IJK cell address. Cell Property Result data is organized by I, J and K.
property_data_index = dims.i * dims.j * (k - 1) + dims.i * (j - 1) + (i - 1)
@ -102,12 +108,12 @@ class Grid:
"""
dims = self.dimensions()
if dims:
return int(dims.i * dims.j * (k - 1) + dims.i * (j - 1) + (i - 1))
else:
return -1
property_data_index = dims.i * dims.j * (k - 1) + dims.i * (j - 1) + (i - 1)
return property_data_index
def cell_count(self):
def cell_count(self) -> int:
"""Cell count in grid
Returns:
@ -115,7 +121,7 @@ class Grid:
"""
dims = self.dimensions()
count = dims.i * dims.j * dims.k
return count
if dims:
return int(dims.i * dims.j * dims.k)
else:
return 0

View File

@ -5,6 +5,7 @@ The Instance class contained have static methods launch and find for
creating connections to ResInsight
"""
from __future__ import annotations
import os
import socket
import logging
@ -27,6 +28,9 @@ from .retry_policy import ExponentialBackoffRetryPolicy
from .grpc_retry_interceptor import RetryOnRpcErrorClientInterceptor
from .generated.generated_classes import CommandRouter
from typing import List, Optional, Tuple
from typing_extensions import Self
class Instance:
"""The ResInsight Instance class. Use to launch or find existing ResInsight instances
@ -40,13 +44,13 @@ class Instance:
"""
@staticmethod
def __is_port_in_use(port):
def __is_port_in_use(port: int) -> bool:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as my_socket:
my_socket.settimeout(0.2)
return my_socket.connect_ex(("localhost", port)) == 0
@staticmethod
def __is_valid_port(port):
def __is_valid_port(port: int) -> bool:
location = "localhost:" + str(port)
channel = grpc.insecure_channel(
location, options=[("grpc.enable_http_proxy", False)]
@ -59,7 +63,7 @@ class Instance:
return True
@staticmethod
def __read_port_number_from_file(file_path):
def __read_port_number_from_file(file_path: str) -> int:
retry_count = 0
while not os.path.exists(file_path) and retry_count < 60:
time.sleep(1)
@ -75,7 +79,7 @@ class Instance:
return -1
@staticmethod
def __kill_process(pid):
def __kill_process(pid: int) -> None:
"""
Kill the process with a given pid.
"""
@ -88,11 +92,11 @@ class Instance:
@staticmethod
def launch(
resinsight_executable="",
console=False,
launch_port=-1,
command_line_parameters=None,
):
resinsight_executable: str = "",
console: bool = False,
launch_port: int = -1,
command_line_parameters: List[str] = [],
) -> Optional[Instance]:
"""Launch a new Instance of ResInsight. This requires the environment variable
RESINSIGHT_EXECUTABLE to be set or the parameter resinsight_executable to be provided.
The RESINSIGHT_GRPC_PORT environment variable can be set to an alternative port number.
@ -110,7 +114,7 @@ class Instance:
Instance: an instance object if it worked. None if not.
"""
requested_port = 50051
requested_port: int = 50051
port_env = os.environ.get("RESINSIGHT_GRPC_PORT")
if port_env:
requested_port = int(port_env)
@ -118,29 +122,25 @@ class Instance:
requested_port = launch_port
if not resinsight_executable:
resinsight_executable = os.environ.get("RESINSIGHT_EXECUTABLE")
if not resinsight_executable:
resinsight_executable_from_env = os.environ.get("RESINSIGHT_EXECUTABLE")
if not resinsight_executable_from_env:
print(
"ERROR: Could not launch ResInsight because the environment variable"
" RESINSIGHT_EXECUTABLE is not set"
)
return None
else:
resinsight_executable = resinsight_executable_from_env
print("Trying to launch", resinsight_executable)
if command_line_parameters is None:
command_line_parameters = []
elif isinstance(command_line_parameters, str):
command_line_parameters = [str]
with tempfile.TemporaryDirectory() as tmp_dir_path:
port_number_file = tmp_dir_path + "/portnumber.txt"
parameters = [
parameters: List[str] = [
"ResInsight",
"--server",
requested_port,
str(requested_port),
"--portnumberfile",
port_number_file,
str(port_number_file),
] + command_line_parameters
if console:
print("Launching as console app")
@ -163,7 +163,7 @@ class Instance:
return None
@staticmethod
def find(start_port=50051, end_port=50071):
def find(start_port: int = 50051, end_port: int = 50071) -> Optional[Instance]:
"""Search for an existing Instance of ResInsight by testing ports.
By default we search from port 50051 to 50071 or if the environment
@ -198,7 +198,7 @@ class Instance:
def __execute_command(self, **command_params):
return self.commands.Execute(Commands_pb2.CommandParams(**command_params))
def __check_version(self):
def __check_version(self) -> Tuple[bool, bool]:
try:
major_version_ok = self.major_version() == int(
RiaVersionInfo.RESINSIGHT_MAJOR_VERSION
@ -210,14 +210,14 @@ class Instance:
except grpc.RpcError:
return False, False
def __init__(self, port=50051, launched=False):
"""Attempts to connect to ResInsight at aa specific port on localhost
def __init__(self, port: int = 50051, launched: bool = False) -> None:
"""Attempts to connect to ResInsight at a specific port on localhost
Args:
port(int): port number
"""
logging.basicConfig()
self.location = "localhost:" + str(port)
self.location: str = "localhost:" + str(port)
self.channel = grpc.insecure_channel(
self.location, options=[("grpc.enable_http_proxy", False)]
@ -256,7 +256,9 @@ class Instance:
path = os.getcwd()
self.set_start_dir(path=path)
def _check_connection_and_version(self, channel, launched, location):
def _check_connection_and_version(
self, channel: grpc.Channel, launched: bool, location: str
) -> None:
connection_ok = False
version_ok = False
@ -288,10 +290,10 @@ class Instance:
self.client_version_string(),
)
def __version_message(self):
def __version_message(self) -> App_pb2.Version:
return self.app.GetVersion(Empty())
def set_start_dir(self, path):
def set_start_dir(self, path: str):
"""Set current start directory
Arguments:
@ -302,7 +304,9 @@ class Instance:
setStartDir=Commands_pb2.FilePathRequest(path=path)
)
def set_export_folder(self, export_type, path, create_folder=False):
def set_export_folder(
self, export_type: str, path: str, create_folder: bool = False
):
"""
Set the export folder used for all export functions
@ -330,7 +334,7 @@ class Instance:
)
)
def set_main_window_size(self, width, height):
def set_main_window_size(self, width: int, height: int):
"""
Set the main window size in pixels
@ -348,7 +352,7 @@ class Instance:
)
)
def set_plot_window_size(self, width, height):
def set_plot_window_size(self, width: int, height: int):
"""
Set the plot window size in pixels
@ -365,19 +369,19 @@ class Instance:
)
)
def major_version(self):
def major_version(self) -> int:
"""Get an integer with the major version number"""
return self.__version_message().major_version
return int(self.__version_message().major_version)
def minor_version(self):
def minor_version(self) -> int:
"""Get an integer with the minor version number"""
return self.__version_message().minor_version
return int(self.__version_message().minor_version)
def patch_version(self):
def patch_version(self) -> int:
"""Get an integer with the patch version number"""
return self.__version_message().patch_version
return int(self.__version_message().patch_version)
def version_string(self):
def version_string(self) -> str:
"""Get a full version string, i.e. 2019.04.01"""
return (
str(self.major_version())
@ -387,9 +391,9 @@ class Instance:
+ str(self.patch_version())
)
def client_version_string(self):
def client_version_string(self) -> str:
"""Get a full version string, i.e. 2019.04.01"""
version_string = RiaVersionInfo.RESINSIGHT_MAJOR_VERSION + "."
version_string: str = RiaVersionInfo.RESINSIGHT_MAJOR_VERSION + "."
version_string += RiaVersionInfo.RESINSIGHT_MINOR_VERSION + "."
version_string += RiaVersionInfo.RESINSIGHT_PATCH_VERSION
return version_string
@ -399,14 +403,16 @@ class Instance:
print("Telling ResInsight to Exit")
return self.app.Exit(Empty())
def is_console(self):
def is_console(self) -> bool:
"""Returns true if the connected ResInsight instance is a console app"""
return self.app.GetRuntimeInfo(
Empty()
).app_type == App_pb2.ApplicationTypeEnum.Value("CONSOLE_APPLICATION")
return bool(
self.app.GetRuntimeInfo(Empty()).app_type
== App_pb2.ApplicationTypeEnum.Value("CONSOLE_APPLICATION")
)
def is_gui(self):
def is_gui(self) -> bool:
"""Returns true if the connected ResInsight instance is a GUI app"""
return self.app.GetRuntimeInfo(
Empty()
).app_type == App_pb2.ApplicationTypeEnum.Value("GUI_APPLICATION")
return bool(
self.app.GetRuntimeInfo(Empty()).app_type
== App_pb2.ApplicationTypeEnum.Value("GUI_APPLICATION")
)

View File

@ -0,0 +1,65 @@
[mypy]
# General configuration
python_version = 3.8
show_error_codes = True
show_column_numbers = True
# Try to make mypy as strict as possible
strict = True
ignore_missing_imports = True
implicit_reexport = True
allow_redefinition = False
strict_optional = True
warn_no_return = True
warn_unused_configs = True
disallow_any_generics = True
disallow_subclassing_any = True
disallow_untyped_calls = True
disallow_incomplete_defs = True
disallow_untyped_decorators = True
no_implicit_optional = True
warn_redundant_casts = True
warn_return_any = True
strict_equality = True
# Explicit exceptions where type definitions are incomplete
[mypy-rips.grpc_retry_interceptor.*]
disable_error_code = misc, no-untyped-def, no-untyped-call
[mypy-rips.pdmobject.*]
disable_error_code = assignment, return-value, call-arg, no-untyped-def, no-untyped-call, no-any-return
[mypy-rips.case.*]
disable_error_code = no-untyped-def, no-any-return
[mypy-rips.grid.*]
disable_error_code = no-untyped-def, no-untyped-call
[mypy-rips.gridcasegroup.*]
disable_error_code = no-any-return, no-untyped-def
[mypy-rips.project.*]
disable_error_code = no-untyped-def, no-any-return, attr-defined
[mypy-rips.well_log_plot.*]
disable_error_code = no-any-return
[mypy-rips.contour_map.*]
disable_error_code = no-redef
[mypy-rips.plot.*]
disable_error_code = no-untyped-def
[mypy-rips.view.*]
disable_error_code = no-untyped-def
[mypy-rips.instance.*]
disable_error_code = no-untyped-def, no-untyped-call, attr-defined
[mypy-rips.simulation_well.*]
disable_error_code = no-any-return, attr-defined
[mypy-rips.generated.generated_classes.*]
disable_error_code = no-any-return

View File

@ -17,27 +17,39 @@ import Commands_pb2
import Commands_pb2_grpc
def camel_to_snake(name):
from typing import Any, Callable, TypeVar, Tuple, cast, Union, List, Optional, Type
from typing_extensions import ParamSpec, Self
def camel_to_snake(name: str) -> str:
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):
def snake_to_camel(name: str) -> str:
return "".join(word.title() for word in name.split("_"))
def add_method(cls):
def decorator(func):
F = TypeVar("F", bound=Callable[..., Any])
C = TypeVar("C")
def add_method(cls: C) -> Callable[[F], F]:
def decorator(func: F) -> F:
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):
P = ParamSpec("P")
T = TypeVar("T")
def add_static_method(cls: C) -> Callable[[Callable[P, T]], Callable[P, T]]:
def decorator(func: Callable[P, T]) -> Callable[P, T]:
@wraps(func)
def wrapper(*args, **kwargs):
def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
return func(*args, **kwargs)
setattr(cls, func.__name__, wrapper)
@ -52,7 +64,9 @@ class PdmObjectBase:
The ResInsight base class for the Project Data Model
"""
def _execute_command(self, **command_params):
__custom_init__ = None
def _execute_command(self, **command_params) -> Any:
self.__warnings = []
response, call = self._commands.Execute.with_call(
Commands_pb2.CommandParams(**command_params)
@ -64,7 +78,11 @@ class PdmObjectBase:
return response
def __init__(self, pb2_object, channel):
def __init__(
self,
pb2_object: Optional[PdmObject_pb2.PdmObject],
channel: Optional[grpc.Channel],
) -> None:
self.__warnings = []
self.__chunk_size = 8160
@ -91,11 +109,11 @@ class PdmObjectBase:
)
self.__copy_to_pb2()
def copy_from(self, object):
def copy_from(self, obj: object) -> None:
"""Copy attribute values from object to self"""
for attribute in dir(object):
for attribute in dir(obj):
if not attribute.startswith("__"):
value = getattr(object, attribute)
value = getattr(obj, attribute)
# This is crucial to avoid overwriting methods
if not callable(value):
setattr(self, attribute, value)
@ -103,13 +121,13 @@ class PdmObjectBase:
self.__custom_init__(self._pb2_object, self._channel)
self.update()
def warnings(self):
def warnings(self) -> List[str]:
return self.__warnings
def has_warnings(self):
def has_warnings(self) -> bool:
return len(self.__warnings) > 0
def __copy_to_pb2(self):
def __copy_to_pb2(self) -> None:
if self._pb2_object is not None:
for snake_kw in dir(self):
if not snake_kw.startswith("_"):
@ -119,15 +137,15 @@ class PdmObjectBase:
camel_kw = snake_to_camel(snake_kw)
self.__set_grpc_value(camel_kw, value)
def pb2_object(self):
def pb2_object(self) -> PdmObject_pb2.PdmObject:
"""Private method"""
return self._pb2_object
def channel(self):
def channel(self) -> grpc.Channel:
"""Private method"""
return self._channel
def address(self):
def address(self) -> Any:
"""Get the unique address of the PdmObject
Returns:
@ -136,15 +154,15 @@ class PdmObjectBase:
return self._pb2_object.address
def set_visible(self, visible):
def set_visible(self, visible: bool) -> None:
"""Set the visibility of the object in the ResInsight project tree"""
self._pb2_object.visible = visible
def visible(self):
def visible(self) -> bool:
"""Get the visibility of the object in the ResInsight project tree"""
return self._pb2_object.visible
return bool(self._pb2_object.visible)
def print_object_info(self):
def print_object_info(self) -> None:
"""Print the structure and data content of the PdmObject"""
print("=========== " + self.__class__.__name__ + " =================")
print("Object Attributes: ")
@ -164,7 +182,10 @@ class PdmObjectBase:
if not snake_kw.startswith("_") and callable(getattr(self, snake_kw)):
print(" " + snake_kw)
def __convert_from_grpc_value(self, value):
Value = Union[bool, str, float, int, "ValueArray"]
ValueArray = List[Value]
def __convert_from_grpc_value(self, value: str) -> Value:
if value.lower() == "false":
return False
if value.lower() == "true":
@ -183,7 +204,7 @@ class PdmObjectBase:
return self.__makelist(value)
return value
def __convert_to_grpc_value(self, value):
def __convert_to_grpc_value(self, value: Any) -> str:
if isinstance(value, bool):
if value:
return "true"
@ -197,15 +218,15 @@ class PdmObjectBase:
return "[" + ", ".join(list_of_values) + "]"
return str(value)
def __get_grpc_value(self, camel_keyword):
def __get_grpc_value(self, camel_keyword: str) -> Value:
return self.__convert_from_grpc_value(
self._pb2_object.parameters[camel_keyword]
)
def __set_grpc_value(self, camel_keyword, value):
def __set_grpc_value(self, camel_keyword: str, value: str) -> None:
self._pb2_object.parameters[camel_keyword] = self.__convert_to_grpc_value(value)
def set_value(self, snake_keyword, value):
def set_value(self, snake_keyword: str, value: object) -> None:
"""Set the value associated with the provided keyword and updates ResInsight
Arguments:
@ -217,10 +238,10 @@ class PdmObjectBase:
setattr(self, snake_keyword, value)
self.update()
def __islist(self, value):
def __islist(self, value: str) -> bool:
return value.startswith("[") and value.endswith("]")
def __makelist(self, list_string):
def __makelist(self, list_string: str) -> Value:
list_string = list_string.lstrip("[")
list_string = list_string.rstrip("]")
if not list_string:
@ -232,7 +253,13 @@ class PdmObjectBase:
values.append(self.__convert_from_grpc_value(string))
return values
def __from_pb2_to_resinsight_classes(self, pb2_object_list, super_class_definition):
D = TypeVar("D")
def __from_pb2_to_resinsight_classes(
self,
pb2_object_list: List[PdmObject_pb2.PdmObject],
super_class_definition: Type[D],
) -> List[D]:
pdm_object_list = []
from .generated.generated_classes import class_from_keyword
@ -241,13 +268,15 @@ class PdmObjectBase:
if child_class_definition is None:
child_class_definition = super_class_definition
assert child_class_definition is not None
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):
def descendants(self, class_definition: Type[D]) -> List[D]:
"""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
@ -269,7 +298,7 @@ class PdmObjectBase:
return [] # Valid empty result
raise e
def children(self, child_field, class_definition):
def children(self, child_field: str, class_definition: Type[D]) -> List[D]:
"""Get a list of all direct project tree children inside the provided child_field
Arguments:
child_field[str]: A field name
@ -287,7 +316,9 @@ class PdmObjectBase:
return []
raise e
def add_new_object(self, class_definition, child_field=""):
def add_new_object(
self, class_definition: Type[D], child_field: str = ""
) -> Optional[D]:
"""Create and add an object to the specified child field
Arguments:
class_definition[class]: Class definition of the object to create
@ -311,18 +342,18 @@ class PdmObjectBase:
child_class_definition = class_from_keyword(pb2_object.class_keyword)
if child_class_definition is None:
child_class_definition = class_keyword
child_class_definition = class_definition
assert child_class_definition.__name__ == class_definition.__name__
pdm_object = class_definition(pb2_object=pb2_object, channel=self.channel())
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):
def ancestor(self, class_definition: Type[D]) -> Optional[D]:
"""Find the first ancestor that matches the provided class_keyword
Arguments:
class_definition[class]: A class definition matching the type of class wanted
@ -330,35 +361,27 @@ class PdmObjectBase:
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()
)
pdm_object = 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):
def _call_get_method_async(self, method_name: str):
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):
def _call_get_method(self, method_name: str):
all_values = []
generator = self._call_get_method_async(method_name)
for chunk in generator:
@ -413,7 +436,7 @@ class PdmObjectBase:
chunk = PdmObject_pb2.PdmObjectSetterChunk()
yield chunk
def _call_set_method(self, method_name, values):
def _call_set_method(self, method_name: str, values) -> None:
method_request = PdmObject_pb2.PdmObjectGetterRequest(
object=self._pb2_object, method=method_name
)
@ -422,7 +445,42 @@ class PdmObjectBase:
if reply.accepted_value_count < len(values):
raise IndexError
def _call_pdm_method(self, method_name, **kwargs):
def _call_pdm_method_void(self, method_name: str, **kwargs: Any) -> None:
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
)
self._pdm_object_stub.CallPdmObjectMethod(request)
X = TypeVar("X")
def _call_pdm_method_return_value(
self, method_name: str, class_definition: Type[X], **kwargs: Any
) -> X:
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)
pdm_object = class_definition(pb2_object=pb2_object, channel=self.channel())
return pdm_object
O = TypeVar("O")
def _call_pdm_method_return_optional_value(
self, method_name: str, class_definition: Type[O], **kwargs: Any
) -> Optional[O]:
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(
@ -440,12 +498,11 @@ class PdmObjectBase:
if child_class_definition is None:
return None
pdm_object = child_class_definition(
pb2_object=pb2_object, channel=self.channel()
)
assert class_definition.__name__ == child_class_definition.__name__
pdm_object = class_definition(pb2_object=pb2_object, channel=self.channel())
return pdm_object
def update(self):
def update(self) -> None:
"""Sync all fields from the Python Object to ResInsight"""
self.__copy_to_pb2()
if self._pdm_object_stub is not None:

View File

@ -17,16 +17,18 @@ from Definitions_pb2 import Empty
import Project_pb2_grpc
import Project_pb2
import PdmObject_pb2
from .resinsight_classes import Project, PlotWindow, WellPath, SummaryCase
from .resinsight_classes import Project, PlotWindow, WellPath, SummaryCase, Reservoir
from typing import Optional, List
@add_method(Project)
def __custom_init__(self, pb2_object, channel):
def __custom_init__(self, pb2_object, channel: grpc.Channel) -> None:
self._project_stub = Project_pb2_grpc.ProjectStub(self._channel)
@add_static_method(Project)
def create(channel):
def create(channel: grpc.Channel) -> Project:
project_stub = Project_pb2_grpc.ProjectStub(channel)
pb2_object = project_stub.GetPdmObject(Empty())
return Project(pb2_object, channel)
@ -56,13 +58,13 @@ def save(self, path=""):
@add_method(Project)
def close(self):
def close(self) -> None:
"""Close the current project (and open new blank project)"""
self._execute_command(closeProject=Empty())
@add_method(Project)
def load_case(self, path, grid_only=False):
def load_case(self: Project, path: str, grid_only: bool = False) -> Reservoir:
"""Load a new grid case from the given file path
Arguments:
@ -77,7 +79,7 @@ def load_case(self, path, grid_only=False):
@add_method(Project)
def selected_cases(self):
def selected_cases(self) -> List[Case]:
"""Get a list of all grid cases selected in the project tree
Returns:
@ -91,17 +93,17 @@ def selected_cases(self):
@add_method(Project)
def cases(self):
def cases(self: Project) -> List[Reservoir]:
"""Get a list of all grid cases in the project
Returns:
A list of :class:`rips.generated.generated_classes.Case`
"""
return self.descendants(Case)
return self.descendants(Reservoir)
@add_method(Project)
def case(self, case_id):
def case(self: Project, case_id: int) -> Optional[Reservoir]:
"""Get a specific grid case from the provided case Id
Arguments:

View File

View File

@ -6,7 +6,7 @@ import random
class RetryPolicy(abc.ABC):
@abc.abstractmethod
def sleep(self, retry_num):
def sleep(self, retry_num: int) -> None:
"""
How long to sleep in milliseconds.
:param retry_num: the number of retry (starting from zero)
@ -14,14 +14,14 @@ class RetryPolicy(abc.ABC):
assert retry_num >= 0
@abc.abstractmethod
def time_out_message(self):
def time_out_message(self) -> str:
"""
Generate a error message for user on time out.
"""
pass
@abc.abstractmethod
def num_retries(self):
def num_retries(self) -> int:
"""
Max number retries.
"""
@ -29,29 +29,34 @@ class RetryPolicy(abc.ABC):
class FixedRetryPolicy(RetryPolicy):
def __init__(self, sleep_time=1000, max_num_retries=10):
def __init__(self, sleep_time: int = 1000, max_num_retries: int = 10):
"""
Create a fixed time retry policy.
:param sleep_time: time to sleep in milliseconds.
:param max_num_retries: max number of retries.
"""
self.sleep_time = sleep_time
self.max_num_retries = max_num_retries
self.sleep_time: int = sleep_time
self.max_num_retries: int = max_num_retries
def sleep(self, retry_num):
def sleep(self, retry_num: int) -> None:
time.sleep(self.sleep_time / 1000)
def time_out_message(self):
def time_out_message(self) -> str:
return "Tried {} times with {} milliseconds apart.".format(
self.max_num_retries, self.sleep_time
)
def num_retries(self):
def num_retries(self) -> int:
return self.max_num_retries
class ExponentialBackoffRetryPolicy(RetryPolicy):
def __init__(self, min_backoff=200, max_backoff=10000, max_num_retries=20):
def __init__(
self,
min_backoff: int = 200,
max_backoff: int = 10000,
max_num_retries: int = 20,
):
"""
Create a truncated exponential backoff policy.
See: https://en.wikipedia.org/wiki/Exponential_backoff
@ -59,12 +64,12 @@ class ExponentialBackoffRetryPolicy(RetryPolicy):
:param max_backoff: maximum time to sleep in milliseconds.
:param max_num_retries: max number of retries.
"""
self.min_backoff = min_backoff
self.max_backoff = max_backoff
self.max_num_retries = max_num_retries
self.multiplier = 2
self.min_backoff: int = min_backoff
self.max_backoff: int = max_backoff
self.max_num_retries: int = max_num_retries
self.multiplier: int = 2
def sleep(self, retry_num):
def sleep(self, retry_num: int) -> None:
# Add a random component to avoid synchronized retries
wiggle = random.randint(0, 100)
sleep_ms = min(
@ -72,12 +77,12 @@ class ExponentialBackoffRetryPolicy(RetryPolicy):
)
time.sleep(sleep_ms / 1000)
def time_out_message(self):
def time_out_message(self) -> str:
return (
"Tried {} times with increasing delay (from {} to {} milliseconds).".format(
self.max_num_retries, self.min_backoff, self.max_backoff
)
)
def num_retries(self):
def num_retries(self) -> int:
return self.max_num_retries

View File

@ -8,21 +8,27 @@ import SimulationWell_pb2_grpc
import Properties_pb2
import Properties_pb2_grpc
import PdmObject_pb2
from .resinsight_classes import SimulationWell
from .case import Case
from .pdmobject import PdmObjectBase, add_method
import rips.case
from typing import List, Optional
@add_method(SimulationWell)
def __custom_init__(self, pb2_object, channel):
self._simulation_well_stub = SimulationWell_pb2_grpc.SimulationWellStub(channel)
def __custom_init__(
self: SimulationWell, pb2_object: PdmObject_pb2.PdmObject, channel: grpc.Channel
) -> None:
self.__simulation_well_stub = SimulationWell_pb2_grpc.SimulationWellStub(channel)
@add_method(SimulationWell)
def status(self, timestep):
def status(
self: SimulationWell, timestep: int
) -> List[SimulationWell_pb2.SimulationWellStatus]:
"""Get simulation well status
**SimulationWellStatus class description**::
@ -39,11 +45,13 @@ def status(self, timestep):
sim_well_request = SimulationWell_pb2.SimulationWellRequest(
case_id=self.case().id, well_name=self.name, timestep=timestep
)
return self._simulation_well_stub.GetSimulationWellStatus(sim_well_request)
return self.__simulation_well_stub.GetSimulationWellStatus(sim_well_request)
@add_method(SimulationWell)
def cells(self, timestep):
def cells(
self: SimulationWell, timestep: int
) -> List[SimulationWell_pb2.SimulationWellCellInfo]:
"""Get reservoir cells the simulation well is defined for
**SimulationWellCellInfo class description**::
@ -66,9 +74,9 @@ def cells(self, timestep):
sim_well_request = SimulationWell_pb2.SimulationWellRequest(
case_id=self.case().id, well_name=self.name, timestep=timestep
)
return self._simulation_well_stub.GetSimulationWellCells(sim_well_request).data
return self.__simulation_well_stub.GetSimulationWellCells(sim_well_request).data
@add_method(SimulationWell)
def case(self):
return self.ancestor(rips.case.Case)
def case(self: SimulationWell) -> Optional[Case]:
return self.ancestor(Case)

View File

@ -140,10 +140,6 @@ def test_PdmObject(rips_instance, initialize_test):
assert case.__class__.__name__ == "EclipseCase"
@pytest.mark.skipif(
sys.platform.startswith("linux"),
reason="Brugge is currently exceptionally slow on Linux",
)
def test_brugge_0010(rips_instance, initialize_test):
case_path = dataroot.PATH + "/Case_with_10_timesteps/Real10/BRUGGE_0010.EGRID"
case = rips_instance.project.load_case(path=case_path)
@ -157,10 +153,6 @@ def test_brugge_0010(rips_instance, initialize_test):
assert len(days_since_start) == 11
@pytest.mark.skipif(
sys.platform.startswith("linux"),
reason="Brugge is currently exceptionally slow on Linux",
)
def test_replaceCase(rips_instance, initialize_test):
project = rips_instance.project.open(
dataroot.PATH + "/TEST10K_FLT_LGR_NNC/10KWithWellLog.rsp"
@ -192,10 +184,6 @@ def test_loadNonExistingCase(rips_instance, initialize_test):
assert rips_instance.project.load_case(case_path)
@pytest.mark.skipif(
sys.platform.startswith("linux"),
reason="Brugge is currently exceptionally slow on Linux",
)
def test_exportFlowCharacteristics(rips_instance, initialize_test):
case_path = dataroot.PATH + "/Case_with_10_timesteps/Real0/BRUGGE_0000.EGRID"
case = rips_instance.project.load_case(case_path)

View File

@ -3,8 +3,6 @@ import os
from typing import Any, Dict, List, TypedDict
import math
from generated.generated_classes import Case
sys.path.insert(1, os.path.join(sys.path[0], "../../"))
import rips
@ -69,7 +67,7 @@ def test_10k(rips_instance, initialize_test):
check_corner(cell_corners[cell_index].c7, expected_corners[7])
def check_reek_grid_box(case: Case):
def check_reek_grid_box(case: rips.Case):
assert len(case.grids()) == 1
grid = case.grid(index=0)
@ -107,7 +105,7 @@ def test_load_grdecl_grid(rips_instance, initialize_test):
def verify_load_grid_and_separate_properties(
case: Case, property_name_and_paths: NameAndPath
case: rips.Reservoir, property_name_and_paths: NameAndPath
):
# Load case without properties
available_properties = case.available_properties("INPUT_PROPERTY")

View File

@ -54,10 +54,6 @@ def test_well_log_plots(rips_instance, initialize_test):
assert plot2.depth_type == "TRUE_VERTICAL_DEPTH_RKB"
@pytest.mark.skipif(
sys.platform.startswith("linux"),
reason="Brugge is currently exceptionally slow on Linux",
)
def test_loadGridCaseGroup(rips_instance, initialize_test):
case_paths = []
case_paths.append(dataroot.PATH + "/Case_with_10_timesteps/Real0/BRUGGE_0000.EGRID")

View File

@ -10,10 +10,6 @@ import rips
import dataroot
@pytest.mark.skipif(
sys.platform.startswith("linux"),
reason="Brugge is currently exceptionally slow on Linux",
)
def test_create_and_export_surface(rips_instance, initialize_test):
case_path = dataroot.PATH + "/Case_with_10_timesteps/Real0/BRUGGE_0000.EGRID"
case = rips_instance.project.load_case(path=case_path)

View File

@ -8,17 +8,19 @@ from .plot import Plot
from .pdmobject import PdmObjectBase, add_method
from .resinsight_classes import WellLogPlot
from typing import List
@add_method(WellLogPlot)
def export_data_as_las(
self,
export_folder,
file_prefix="",
export_tvdrkb=False,
capitalize_file_names=False,
resample_interval=0.0,
convert_to_standard_units=False,
):
self: WellLogPlot,
export_folder: str,
file_prefix: str = "",
export_tvdrkb: bool = False,
capitalize_file_names: bool = False,
resample_interval: float = 0.0,
convert_to_standard_units: bool = False,
) -> List[str]:
"""Export LAS file(s) for the current plot
Arguments:
@ -48,8 +50,11 @@ def export_data_as_las(
@add_method(WellLogPlot)
def export_data_as_ascii(
self, export_folder, file_prefix="", capitalize_file_names=False
):
self: WellLogPlot,
export_folder: str,
file_prefix: str = "",
capitalize_file_names: bool = False,
) -> List[str]:
"""Export LAS file(s) for the current plot
Arguments:

View File

@ -18,6 +18,6 @@ setup(
url='http://www.resinsight.org',
license=license,
packages=['rips'],
package_data={'rips': ['*.py', 'generated/*.py', 'PythonExamples/*.py', 'tests/*.py']},
package_data={'rips': ['py.typed', '*.py', 'generated/*.py', 'PythonExamples/*.py', 'tests/*.py']},
install_requires=['grpcio', 'protobuf', 'wheel']
)