Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions python/engine/PythonEngine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,39 @@ int PythonEngine::numberOfArguments(ScriptObject& methodObject, std::string_view
return numberOfArguments;
}

bool PythonEngine::hasMethod(ScriptObject& methodObject, std::string_view methodName, bool overriden_only) {
auto val = std::any_cast<PythonObject>(methodObject.object);
if (PyObject_HasAttrString(val.obj_, methodName.data()) == 0) {
return false;
}
PyObject* method = PyObject_GetAttrString(val.obj_, methodName.data()); // New reference
if (PyMethod_Check(method) == 0) {
// Should never happen with modelOutputRequests since the Base class (C++) has it
return false;
}
Py_DECREF(method);
if (!overriden_only) {
return true;
}

// equivalent to getattr(instance_obj.__class__, method_name) == getattr(instance_obj.__class__.__bases__[0], method_name)
PyTypeObject* class_type = Py_TYPE(val.obj_); // PyObject_Type returns a strong (New) reference, not needed for us
// cppcheck-suppress cstyleCast
PyObject* class_method = PyObject_GetAttrString((PyObject*)class_type, methodName.data()); // New reference

assert(class_type->tp_base != nullptr);
// cppcheck-suppress cstyleCast
auto* base = (PyTypeObject*)class_type->tp_base;
// cppcheck-suppress cstyleCast
PyObject* base_method = PyObject_GetAttrString((PyObject*)base, methodName.data()); // New reference

bool result = class_method != base_method;
Py_DECREF(class_method);
Py_DECREF(base_method);

return result;
}
Comment on lines +416 to +447
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here the python hasMethod version


} // namespace openstudio

extern "C"
Expand Down
2 changes: 2 additions & 0 deletions python/engine/PythonEngine.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ class PythonEngine final : public ScriptEngine

virtual int numberOfArguments(ScriptObject& methodObject, std::string_view methodName) override;

virtual bool hasMethod(ScriptObject& methodObject, std::string_view methodName, bool overriden_only) override;

protected:
void* getAs_impl(ScriptObject& obj, const std::type_info&) override;

Expand Down
19 changes: 19 additions & 0 deletions python/engine/test/PythonEngine_GTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -174,3 +174,22 @@ TEST_F(PythonEngineFixture, AlfalfaMeasure) {
measurePtr->run(model, runner, arguments);
EXPECT_EQ(5, runner.alfalfa().points().size());
}

TEST_F(PythonEngineFixture, hasMethod) {
{
const std::string classAndDirName = "ReportingMeasureWithoutModelOutputs";
const auto scriptPath = getScriptPath(classAndDirName);
auto measureScriptObject = (*thisEngine)->loadMeasure(scriptPath, classAndDirName);
EXPECT_FALSE((*thisEngine)->hasMethod(measureScriptObject, "doesNotExists"));
EXPECT_TRUE((*thisEngine)->hasMethod(measureScriptObject, "modelOutputRequests", false)); // overriden_only = false
EXPECT_FALSE((*thisEngine)->hasMethod(measureScriptObject, "modelOutputRequests"));
}
{
const std::string classAndDirName = "ReportingMeasureWithModelOutputs";
const auto scriptPath = getScriptPath(classAndDirName);
auto measureScriptObject = (*thisEngine)->loadMeasure(scriptPath, classAndDirName);
EXPECT_FALSE((*thisEngine)->hasMethod(measureScriptObject, "doesNotExists"));
EXPECT_TRUE((*thisEngine)->hasMethod(measureScriptObject, "modelOutputRequests", false)); // overriden_only = false
EXPECT_TRUE((*thisEngine)->hasMethod(measureScriptObject, "modelOutputRequests"));
}
}
52 changes: 52 additions & 0 deletions python/engine/test/ReportingMeasureWithModelOutputs/measure.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
"""insert your copyright here.

# see the URL below for information on how to write OpenStudio measures
# http://nrel.github.io/OpenStudio-user-documentation/reference/measure_writing_guide/
"""

from pathlib import Path

import openstudio


class ReportingMeasureWithModelOutputs(openstudio.measure.ReportingMeasure):
"""An ReportingMeasure."""

def name(self):
return "ReportingMeasureWithModelOutputs"

def description(self):
return "DESCRIPTION_TEXT"

def modeler_description(self):
return "MODELER_DESCRIPTION_TEXT"

def arguments(self, model: openstudio.model.Model):
args = openstudio.measure.OSArgumentVector()
return args

def outputs(self):
outs = openstudio.measure.OSOutputVector()
return outs

def modelOutputRequests(
self,
model: openstudio.model.Model,
runner: openstudio.measure.OSRunner,
user_arguments: openstudio.measure.OSArgumentMap,
) -> bool:
return True

def energyPlusOutputRequests(
self, runner: openstudio.measure.OSRunner, user_arguments: openstudio.measure.OSArgumentMap
):
result = openstudio.IdfObjectVector()
return result

def run(
self,
runner: openstudio.measure.OSRunner,
user_arguments: openstudio.measure.OSArgumentMap,
):
super().run(runner, user_arguments)
return True
44 changes: 44 additions & 0 deletions python/engine/test/ReportingMeasureWithModelOutputs/measure.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?xml version="1.0"?>
<measure>
<schema_version>3.1</schema_version>
<name>reporting_measure_with_model_outputs</name>
<uid>9971eb7b-3225-4795-9690-9941a7471ce0</uid>
<version_id>be88754a-3d7f-433e-9912-94a2993c9463</version_id>
<version_modified>2025-03-14T13:15:30Z</version_modified>
<xml_checksum>1DA817AF</xml_checksum>
<class_name>ReportingMeasureWithModelOutputs</class_name>
<display_name>ReportingMeasureWithModelOutputs</display_name>
<description>DESCRIPTION_TEXT</description>
<modeler_description>MODELER_DESCRIPTION_TEXT</modeler_description>
<arguments />
<outputs />
<provenances />
<tags>
<tag>Reporting.QAQC</tag>
</tags>
<attributes>
<attribute>
<name>Measure Type</name>
<value>ReportingMeasure</value>
<datatype>string</datatype>
</attribute>
<attribute>
<name>Measure Language</name>
<value>Python</value>
<datatype>string</datatype>
</attribute>
</attributes>
<files>
<file>
<version>
<software_program>OpenStudio</software_program>
<identifier>3.9.0</identifier>
<min_compatible>3.9.0</min_compatible>
</version>
<filename>measure.py</filename>
<filetype>py</filetype>
<usage_type>script</usage_type>
<checksum>CFB17771</checksum>
</file>
</files>
</measure>
44 changes: 44 additions & 0 deletions python/engine/test/ReportingMeasureWithoutModelOutputs/measure.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"""insert your copyright here.

# see the URL below for information on how to write OpenStudio measures
# http://nrel.github.io/OpenStudio-user-documentation/reference/measure_writing_guide/
"""

from pathlib import Path

import openstudio


class ReportingMeasureWithoutModelOutputs(openstudio.measure.ReportingMeasure):
"""An ReportingMeasure."""

def name(self):
return "ReportingMeasureWithoutModelOutputs"

def description(self):
return "DESCRIPTION_TEXT"

def modeler_description(self):
return "MODELER_DESCRIPTION_TEXT"

def arguments(self, model: openstudio.model.Model):
args = openstudio.measure.OSArgumentVector()
return args

def outputs(self):
outs = openstudio.measure.OSOutputVector()
return outs

def energyPlusOutputRequests(
self, runner: openstudio.measure.OSRunner, user_arguments: openstudio.measure.OSArgumentMap
):
result = openstudio.IdfObjectVector()
return result

def run(
self,
runner: openstudio.measure.OSRunner,
user_arguments: openstudio.measure.OSArgumentMap,
):
super().run(runner, user_arguments)
return True
44 changes: 44 additions & 0 deletions python/engine/test/ReportingMeasureWithoutModelOutputs/measure.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?xml version="1.0"?>
<measure>
<schema_version>3.1</schema_version>
<name>reporting_measure_without_model_outputs</name>
<uid>a0876945-a15b-469d-bc22-90f133720e7c</uid>
<version_id>49740c2c-1804-4a4e-ace5-c30b6d350c5f</version_id>
<version_modified>2025-03-14T13:15:17Z</version_modified>
<xml_checksum>1DA817AF</xml_checksum>
<class_name>ReportingMeasureWithoutModelOutputs</class_name>
<display_name>ReportingMeasureWithoutModelOutputs</display_name>
<description>DESCRIPTION_TEXT</description>
<modeler_description>MODELER_DESCRIPTION_TEXT</modeler_description>
<arguments />
<outputs />
<provenances />
<tags>
<tag>Reporting.QAQC</tag>
</tags>
<attributes>
<attribute>
<name>Measure Type</name>
<value>ReportingMeasure</value>
<datatype>string</datatype>
</attribute>
<attribute>
<name>Measure Language</name>
<value>Python</value>
<datatype>string</datatype>
</attribute>
</attributes>
<files>
<file>
<version>
<software_program>OpenStudio</software_program>
<identifier>3.9.0</identifier>
<min_compatible>3.9.0</min_compatible>
</version>
<filename>measure.py</filename>
<filetype>py</filetype>
<usage_type>script</usage_type>
<checksum>816BF07D</checksum>
</file>
</files>
</measure>
Loading