From edf7cce32927c3fd204e6541ad88f827f118dd15 Mon Sep 17 00:00:00 2001 From: Kristian Bendiksen Date: Wed, 12 Jul 2023 11:42:17 +0200 Subject: [PATCH] Python: add type hinting to python code. Types are checked using mypy. Fixes #10394. --- .github/workflows/AppFwkUnitTest.yml | 12 +- .github/workflows/ResInsightWithCache.yml | 8 + .../Adm/RiaVersionInfo.py.cmake | 8 +- .../Annotations/RimTextAnnotation.cpp | 3 +- .../Completions/RimPerforationInterval.cpp | 3 +- .../Completions/RimWellPathFracture.cpp | 3 +- .../ProjectDataModel/RimColorLegend.cpp | 3 +- .../ProjectDataModel/RimRoffCase.cpp | 3 +- CMakeLists.txt | 1 - Fwk/AppFwk/cafPdmScripting/CMakeLists.txt | 16 +- .../cafPdmScripting/cafPdmPythonGenerator.cpp | 143 +++++++++++---- Fwk/CMakeLists.txt | 21 +++ GrpcInterface/CMakeLists.txt | 1 + GrpcInterface/Python/requirements.txt | 3 +- GrpcInterface/Python/rips/__init__.py | 4 +- GrpcInterface/Python/rips/case.py | 50 +++--- GrpcInterface/Python/rips/contour_map.py | 20 +-- GrpcInterface/Python/rips/grid.py | 36 ++-- GrpcInterface/Python/rips/instance.py | 104 ++++++----- GrpcInterface/Python/rips/mypy.ini | 65 +++++++ GrpcInterface/Python/rips/pdmobject.py | 167 ++++++++++++------ GrpcInterface/Python/rips/project.py | 20 ++- GrpcInterface/Python/rips/py.typed | 0 GrpcInterface/Python/rips/retry_policy.py | 39 ++-- GrpcInterface/Python/rips/simulation_well.py | 26 ++- GrpcInterface/Python/rips/tests/test_cases.py | 12 -- GrpcInterface/Python/rips/tests/test_grids.py | 6 +- .../Python/rips/tests/test_project.py | 4 - .../Python/rips/tests/test_surfaces.py | 4 - GrpcInterface/Python/rips/well_log_plot.py | 25 +-- GrpcInterface/Python/setup.py.cmake | 6 +- 31 files changed, 523 insertions(+), 293 deletions(-) create mode 100644 GrpcInterface/Python/rips/mypy.ini create mode 100644 GrpcInterface/Python/rips/py.typed diff --git a/.github/workflows/AppFwkUnitTest.yml b/.github/workflows/AppFwkUnitTest.yml index 668a59cb10..358d2a0740 100644 --- a/.github/workflows/AppFwkUnitTest.yml +++ b/.github/workflows/AppFwkUnitTest.yml @@ -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 diff --git a/.github/workflows/ResInsightWithCache.yml b/.github/workflows/ResInsightWithCache.yml index 00a3a12f3d..5d2bb583c7 100644 --- a/.github/workflows/ResInsightWithCache.yml +++ b/.github/workflows/ResInsightWithCache.yml @@ -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: diff --git a/ApplicationLibCode/Adm/RiaVersionInfo.py.cmake b/ApplicationLibCode/Adm/RiaVersionInfo.py.cmake index dbb3ea3a6e..4c0a38a2b4 100644 --- a/ApplicationLibCode/Adm/RiaVersionInfo.py.cmake +++ b/ApplicationLibCode/Adm/RiaVersionInfo.py.cmake @@ -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@" \ No newline at end of file +PYTHON_GRPC_PROTOC_VERSION : str = "@PYTHON_GRPC_PROTOC_VERSION@" diff --git a/ApplicationLibCode/ProjectDataModel/Annotations/RimTextAnnotation.cpp b/ApplicationLibCode/ProjectDataModel/Annotations/RimTextAnnotation.cpp index 3c8b4c7e37..df8fa68b69 100644 --- a/ApplicationLibCode/ProjectDataModel/Annotations/RimTextAnnotation.cpp +++ b/ApplicationLibCode/ProjectDataModel/Annotations/RimTextAnnotation.cpp @@ -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" ); diff --git a/ApplicationLibCode/ProjectDataModel/Completions/RimPerforationInterval.cpp b/ApplicationLibCode/ProjectDataModel/Completions/RimPerforationInterval.cpp index a9c79c1a7b..b3290f341a 100644 --- a/ApplicationLibCode/ProjectDataModel/Completions/RimPerforationInterval.cpp +++ b/ApplicationLibCode/ProjectDataModel/Completions/RimPerforationInterval.cpp @@ -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" ); diff --git a/ApplicationLibCode/ProjectDataModel/Completions/RimWellPathFracture.cpp b/ApplicationLibCode/ProjectDataModel/Completions/RimWellPathFracture.cpp index 2ae52d9f78..3a19cb5c80 100644 --- a/ApplicationLibCode/ProjectDataModel/Completions/RimWellPathFracture.cpp +++ b/ApplicationLibCode/ProjectDataModel/Completions/RimWellPathFracture.cpp @@ -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() ); diff --git a/ApplicationLibCode/ProjectDataModel/RimColorLegend.cpp b/ApplicationLibCode/ProjectDataModel/RimColorLegend.cpp index ee3a470826..fa06f0c9db 100644 --- a/ApplicationLibCode/ProjectDataModel/RimColorLegend.cpp +++ b/ApplicationLibCode/ProjectDataModel/RimColorLegend.cpp @@ -24,6 +24,7 @@ #include "RimColorLegendItem.h" #include "cafPdmFieldReorderCapability.h" +#include "cafPdmObjectScriptingCapability.h" #include @@ -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" ); diff --git a/ApplicationLibCode/ProjectDataModel/RimRoffCase.cpp b/ApplicationLibCode/ProjectDataModel/RimRoffCase.cpp index 0a662892d0..b1aa62727d 100644 --- a/ApplicationLibCode/ProjectDataModel/RimRoffCase.cpp +++ b/ApplicationLibCode/ProjectDataModel/RimRoffCase.cpp @@ -34,6 +34,7 @@ #include "RimEclipseInputPropertyCollection.h" #include "RimReservoirCellResultsStorage.h" +#include "cafPdmObjectScriptingCapability.h" #include "cafProgressInfo.h" #include @@ -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" ); } //-------------------------------------------------------------------------------------------------- diff --git a/CMakeLists.txt b/CMakeLists.txt index 41bce06d42..4d3c1d65bc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/Fwk/AppFwk/cafPdmScripting/CMakeLists.txt b/Fwk/AppFwk/cafPdmScripting/CMakeLists.txt index 8e15f67069..c173cc2b42 100644 --- a/Fwk/AppFwk/cafPdmScripting/CMakeLists.txt +++ b/Fwk/AppFwk/cafPdmScripting/CMakeLists.txt @@ -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}) diff --git a/Fwk/AppFwk/cafPdmScripting/cafPdmPythonGenerator.cpp b/Fwk/AppFwk/cafPdmScripting/cafPdmPythonGenerator.cpp index 39ae4ff2b1..0cf1a94ad4 100644 --- a/Fwk/AppFwk/cafPdmScripting/cafPdmPythonGenerator.cpp +++ b/Fwk/AppFwk/cafPdmScripting/cafPdmPythonGenerator.cpp @@ -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( 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,11 +176,13 @@ 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" ) - .arg( snake_field_name ) - .arg( fullComment ) - .arg( scriptability->scriptFieldName() ); + 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 )] = fieldCode; } @@ -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,20 +238,22 @@ QString caf::PdmPythonGenerator::generate( PdmObjectFactory* factory, std::vecto if ( pdmChildField ) { - QString fieldCode = QString( " def %1(self):\n%2\n children = " - "self.children(\"%3\", %4)\n return children[0] if " - "len(children) > 0 else None\n" ) - .arg( snake_field_name ) - .arg( fullComment ) - .arg( scriptability->scriptFieldName() ) - .arg( scriptDataType ); + 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 ) + .arg( fullComment ) + .arg( scriptability->scriptFieldName() ) + .arg( scriptDataType ); classMethodsGenerated[field->ownerClass()][snake_field_name] = fieldCode; } 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 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 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(); if ( scriptability && !scriptability->enumScriptTexts().empty() ) return "str"; - QString dataType = xmlObj->dataTypeName(); - - std::map 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 dataType = PdmObjectScriptingCapabilityRegister::scriptClassNameFromClassKeyword( xmlObj->dataTypeName() ); + + std::map 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() ), "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 ).name() ), "List[float]" }, + }; bool foundBuiltin = false; for ( auto builtin : builtins ) diff --git a/Fwk/CMakeLists.txt b/Fwk/CMakeLists.txt index 14fb8a8d66..70d1aea4cb 100644 --- a/Fwk/CMakeLists.txt +++ b/Fwk/CMakeLists.txt @@ -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) diff --git a/GrpcInterface/CMakeLists.txt b/GrpcInterface/CMakeLists.txt index 229172e770..64fcfc6f6c 100644 --- a/GrpcInterface/CMakeLists.txt +++ b/GrpcInterface/CMakeLists.txt @@ -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}" diff --git a/GrpcInterface/Python/requirements.txt b/GrpcInterface/Python/requirements.txt index 3074c1134c..3d9be27a42 100644 --- a/GrpcInterface/Python/requirements.txt +++ b/GrpcInterface/Python/requirements.txt @@ -1,4 +1,5 @@ grpcio grpcio-tools protobuf -wheel \ No newline at end of file +wheel +typing-extensions diff --git a/GrpcInterface/Python/rips/__init__.py b/GrpcInterface/Python/rips/__init__.py index e15cea007d..d2806e7f71 100644 --- a/GrpcInterface/Python/rips/__init__.py +++ b/GrpcInterface/Python/rips/__init__.py @@ -17,7 +17,9 @@ 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) diff --git a/GrpcInterface/Python/rips/case.py b/GrpcInterface/Python/rips/case.py index d97dcd504c..695ec6b22b 100644 --- a/GrpcInterface/Python/rips/case.py +++ b/GrpcInterface/Python/rips/case.py @@ -37,6 +37,7 @@ 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 ` @@ -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 ` @@ -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 ` @@ -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 ` @@ -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 ` @@ -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 ` @@ -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 ` @@ -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 ` @@ -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 ` @@ -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 ` @@ -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.. diff --git a/GrpcInterface/Python/rips/contour_map.py b/GrpcInterface/Python/rips/contour_map.py index 451d642186..6d4e9d3db3 100644 --- a/GrpcInterface/Python/rips/contour_map.py +++ b/GrpcInterface/Python/rips/contour_map.py @@ -10,11 +10,11 @@ @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 diff --git a/GrpcInterface/Python/rips/grid.py b/GrpcInterface/Python/rips/grid.py index 41317718a7..e4f669f1e8 100644 --- a/GrpcInterface/Python/rips/grid.py +++ b/GrpcInterface/Python/rips/grid.py @@ -8,6 +8,12 @@ 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 @@ def cell_centers_async(self): 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 @@ def cell_corners(self): 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 @@ def property_data_index_from_ijk(self, i, j, k): """ 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 @@ def cell_count(self): """ 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 diff --git a/GrpcInterface/Python/rips/instance.py b/GrpcInterface/Python/rips/instance.py index 475f364f12..225e1ec9bf 100644 --- a/GrpcInterface/Python/rips/instance.py +++ b/GrpcInterface/Python/rips/instance.py @@ -5,6 +5,7 @@ creating connections to ResInsight """ +from __future__ import annotations import os import socket import logging @@ -27,6 +28,9 @@ 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 @@ def __is_valid_port(port): 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 @@ def __read_port_number_from_file(file_path): return -1 @staticmethod - def __kill_process(pid): + def __kill_process(pid: int) -> None: """ Kill the process with a given pid. """ @@ -88,11 +92,11 @@ def __kill_process(pid): @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 @@ def launch( 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 @@ def launch( 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 @@ def launch( 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 @@ def find(start_port=50051, end_port=50071): 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 @@ def __check_version(self): 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 @@ def __init__(self, port=50051, launched=False): 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 @@ def _check_connection_and_version(self, channel, launched, location): 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 @@ def set_start_dir(self, path): 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 @@ def set_export_folder(self, export_type, path, create_folder=False): ) ) - 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 @@ def set_main_window_size(self, width, height): ) ) - 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 @@ def set_plot_window_size(self, width, height): ) ) - 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 @@ def version_string(self): + 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 @@ def exit(self): 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") + ) diff --git a/GrpcInterface/Python/rips/mypy.ini b/GrpcInterface/Python/rips/mypy.ini new file mode 100644 index 0000000000..c344f13b0c --- /dev/null +++ b/GrpcInterface/Python/rips/mypy.ini @@ -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 diff --git a/GrpcInterface/Python/rips/pdmobject.py b/GrpcInterface/Python/rips/pdmobject.py index 89d2c9404e..877a2ccb4b 100644 --- a/GrpcInterface/Python/rips/pdmobject.py +++ b/GrpcInterface/Python/rips/pdmobject.py @@ -17,27 +17,39 @@ 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 @@ def _execute_command(self, **command_params): 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 @@ def __init__(self, pb2_object, channel): ) 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 @@ def copy_from(self, object): 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 @@ def __copy_to_pb2(self): 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 @@ def address(self): 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 @@ def print_object_info(self): 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 @@ def __convert_from_grpc_value(self, value): 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 @@ def __convert_to_grpc_value(self, value): 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 @@ def set_value(self, snake_keyword, value): 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 @@ def __makelist(self, list_string): 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 @@ def __from_pb2_to_resinsight_classes(self, pb2_object_list, super_class_definiti 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 @@ def descendants(self, class_definition): 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 @@ def children(self, child_field, class_definition): 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 @@ def add_new_object(self, class_definition, child_field=""): 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 @@ def ancestor(self, class_definition): 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 @@ def __generate_set_method_chunks(self, array, method_request): 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 @@ def _call_set_method(self, method_name, values): 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 @@ def _call_pdm_method(self, method_name, **kwargs): 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: diff --git a/GrpcInterface/Python/rips/project.py b/GrpcInterface/Python/rips/project.py index 408d2f066b..663f676632 100644 --- a/GrpcInterface/Python/rips/project.py +++ b/GrpcInterface/Python/rips/project.py @@ -17,16 +17,18 @@ 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: diff --git a/GrpcInterface/Python/rips/py.typed b/GrpcInterface/Python/rips/py.typed new file mode 100644 index 0000000000..e69de29bb2 diff --git a/GrpcInterface/Python/rips/retry_policy.py b/GrpcInterface/Python/rips/retry_policy.py index a2fe047286..b5349fa297 100644 --- a/GrpcInterface/Python/rips/retry_policy.py +++ b/GrpcInterface/Python/rips/retry_policy.py @@ -6,7 +6,7 @@ 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 @@ def sleep(self, retry_num): 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 @@ def num_retries(self): 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 @@ def __init__(self, min_backoff=200, max_backoff=10000, max_num_retries=20): :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 @@ def sleep(self, retry_num): ) 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 diff --git a/GrpcInterface/Python/rips/simulation_well.py b/GrpcInterface/Python/rips/simulation_well.py index 8f4d491c83..abe100d70f 100644 --- a/GrpcInterface/Python/rips/simulation_well.py +++ b/GrpcInterface/Python/rips/simulation_well.py @@ -8,21 +8,27 @@ 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) diff --git a/GrpcInterface/Python/rips/tests/test_cases.py b/GrpcInterface/Python/rips/tests/test_cases.py index 90e0c04155..8da2b405c9 100644 --- a/GrpcInterface/Python/rips/tests/test_cases.py +++ b/GrpcInterface/Python/rips/tests/test_cases.py @@ -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) diff --git a/GrpcInterface/Python/rips/tests/test_grids.py b/GrpcInterface/Python/rips/tests/test_grids.py index c8c211b8e2..d4a86e0fd3 100644 --- a/GrpcInterface/Python/rips/tests/test_grids.py +++ b/GrpcInterface/Python/rips/tests/test_grids.py @@ -3,8 +3,6 @@ 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") diff --git a/GrpcInterface/Python/rips/tests/test_project.py b/GrpcInterface/Python/rips/tests/test_project.py index a2c3654ac6..000e3e509a 100644 --- a/GrpcInterface/Python/rips/tests/test_project.py +++ b/GrpcInterface/Python/rips/tests/test_project.py @@ -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") diff --git a/GrpcInterface/Python/rips/tests/test_surfaces.py b/GrpcInterface/Python/rips/tests/test_surfaces.py index 96930cd599..36f533b3db 100644 --- a/GrpcInterface/Python/rips/tests/test_surfaces.py +++ b/GrpcInterface/Python/rips/tests/test_surfaces.py @@ -10,10 +10,6 @@ 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) diff --git a/GrpcInterface/Python/rips/well_log_plot.py b/GrpcInterface/Python/rips/well_log_plot.py index 0e87f12582..9263dd2777 100644 --- a/GrpcInterface/Python/rips/well_log_plot.py +++ b/GrpcInterface/Python/rips/well_log_plot.py @@ -8,17 +8,19 @@ 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: diff --git a/GrpcInterface/Python/setup.py.cmake b/GrpcInterface/Python/setup.py.cmake index 9e0db3466e..40d68d421f 100644 --- a/GrpcInterface/Python/setup.py.cmake +++ b/GrpcInterface/Python/setup.py.cmake @@ -4,7 +4,7 @@ with open('README.md') as f: readme = f.read() with open('LICENSE') as f: - license = f.read() + license = f.read() RIPS_DIST_VERSION = '2' @@ -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'] -) \ No newline at end of file +)