You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by fg...@apache.org on 2024/08/02 15:44:52 UTC
(nifi-minifi-cpp) 02/05: MINIFICPP-2411 Extend NiFi Python API to support source processors
This is an automated email from the ASF dual-hosted git repository.
fgerlits pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi-minifi-cpp.git
commit 5777a3b8f88bdf3fbdd7f18b927b423fc350ecb5
Author: Gabor Gyimesi <ga...@gmail.com>
AuthorDate: Fri Jun 21 14:12:16 2024 +0200
MINIFICPP-2411 Extend NiFi Python API to support source processors
Signed-off-by: Ferenc Gerlits <fg...@gmail.com>
This closes #1830
---
docker/test/integration/cluster/ImageStore.py | 4 +-
docker/test/integration/features/python.feature | 13 ++++
.../minifi/processors/CreateFlowFile.py | 25 +++++++
.../integration/resources/python/CreateFlowFile.py | 58 ++++++++++++++++
extensions/python/PYTHON.md | 2 +-
.../pythonprocessors/nifiapi/flowfilesource.py | 77 ++++++++++++++++++++++
.../pythonprocessors/nifiapi/flowfiletransform.py | 45 ++-----------
.../{flowfiletransform.py => processorbase.py} | 62 +----------------
extensions/python/types/PyProcessSession.cpp | 51 +++++++++++++-
extensions/python/types/PyProcessSession.h | 2 +
msi/WixWin.wsi.in | 2 +
11 files changed, 237 insertions(+), 104 deletions(-)
diff --git a/docker/test/integration/cluster/ImageStore.py b/docker/test/integration/cluster/ImageStore.py
index 03a5b0b94..43e2174bb 100644
--- a/docker/test/integration/cluster/ImageStore.py
+++ b/docker/test/integration/cluster/ImageStore.py
@@ -163,6 +163,7 @@ class ImageStore:
COPY RotatingForwarder.py /opt/minifi/minifi-current/minifi-python/nifi_python_processors/RotatingForwarder.py
COPY SpecialPropertyTypeChecker.py /opt/minifi/minifi-current/minifi-python/nifi_python_processors/SpecialPropertyTypeChecker.py
COPY ProcessContextInterfaceChecker.py /opt/minifi/minifi-current/minifi-python/nifi_python_processors/ProcessContextInterfaceChecker.py
+ COPY CreateFlowFile.py /opt/minifi/minifi-current/minifi-python/nifi_python_processors/CreateFlowFile.py
RUN wget {parse_document_url} --directory-prefix=/opt/minifi/minifi-current/minifi-python/nifi_python_processors && \\
wget {chunk_document_url} --directory-prefix=/opt/minifi/minifi-current/minifi-python/nifi_python_processors && \\
echo 'langchain<=0.17.0' > /opt/minifi/minifi-current/minifi-python/nifi_python_processors/requirements.txt && \\
@@ -184,7 +185,8 @@ class ImageStore:
return self.__build_image(dockerfile, [os.path.join(self.test_dir, "resources", "python", "RotatingForwarder.py"),
os.path.join(self.test_dir, "resources", "python", "SpecialPropertyTypeChecker.py"),
- os.path.join(self.test_dir, "resources", "python", "ProcessContextInterfaceChecker.py")])
+ os.path.join(self.test_dir, "resources", "python", "ProcessContextInterfaceChecker.py"),
+ os.path.join(self.test_dir, "resources", "python", "CreateFlowFile.py")])
def __build_http_proxy_image(self):
dockerfile = dedent("""\
diff --git a/docker/test/integration/features/python.feature b/docker/test/integration/features/python.feature
index c0761b517..7c862a233 100644
--- a/docker/test/integration/features/python.feature
+++ b/docker/test/integration/features/python.feature
@@ -94,6 +94,19 @@ Feature: MiNiFi can use python processors in its flows
| with a pre-created virtualenv containing the required python packages |
| using inline defined Python dependencies to install packages |
+ @USE_NIFI_PYTHON_PROCESSORS
+ Scenario: MiNiFi C++ can use native NiFi source python processors
+ Given a CreateFlowFile processor
+ And a PutFile processor with the "Directory" property set to "/tmp/output"
+ And a LogAttribute processor with the "FlowFiles To Log" property set to "0"
+ And the "space" relationship of the CreateFlowFile processor is connected to the PutFile
+ And the "success" relationship of the PutFile processor is connected to the LogAttribute
+ And python is installed on the MiNiFi agent with a pre-created virtualenv
+ When the MiNiFi instance starts up
+ Then a flowfile with the content "Hello World!" is placed in the monitored directory in less than 10 seconds
+ And the Minifi logs contain the following message: "key:filename value:" in less than 60 seconds
+ And the Minifi logs contain the following message: "key:type value:space" in less than 60 seconds
+
@USE_NIFI_PYTHON_PROCESSORS
Scenario: MiNiFi C++ can use custom relationships in NiFi native python processors
Given a GetFile processor with the "Input Directory" property set to "/tmp/input"
diff --git a/docker/test/integration/minifi/processors/CreateFlowFile.py b/docker/test/integration/minifi/processors/CreateFlowFile.py
new file mode 100644
index 000000000..050532981
--- /dev/null
+++ b/docker/test/integration/minifi/processors/CreateFlowFile.py
@@ -0,0 +1,25 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+from ..core.Processor import Processor
+
+
+class CreateFlowFile(Processor):
+ def __init__(self, context, schedule={'scheduling period': '30 sec'}):
+ super(CreateFlowFile, self).__init__(
+ context=context,
+ clazz='CreateFlowFile',
+ class_prefix='org.apache.nifi.minifi.processors.nifi_python_processors.',
+ schedule=schedule,
+ auto_terminate=[])
diff --git a/docker/test/integration/resources/python/CreateFlowFile.py b/docker/test/integration/resources/python/CreateFlowFile.py
new file mode 100644
index 000000000..948734982
--- /dev/null
+++ b/docker/test/integration/resources/python/CreateFlowFile.py
@@ -0,0 +1,58 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from nifiapi.flowfilesource import FlowFileSource, FlowFileSourceResult
+from nifiapi.properties import PropertyDescriptor
+from nifiapi.relationship import Relationship
+
+
+class CreateFlowFile(FlowFileSource):
+ class Java:
+ implements = ['org.apache.nifi.python.processor.FlowFileSource']
+
+ class ProcessorDetails:
+ version = '2.0.0-snapshot'
+ description = '''Test Python source processor.'''
+ tags = ['text', 'test', 'python', 'source']
+
+ FF_CONTENTS = PropertyDescriptor(
+ name='FlowFile Contents',
+ description='''The contents of the FlowFile.''',
+ required=True,
+ default_value='Hello World!'
+ )
+
+ property_descriptors = [FF_CONTENTS]
+
+ REL_MULTILINE = Relationship(name='space', description='FlowFiles that contain space characters.')
+
+ def __init__(self, **kwargs):
+ pass
+
+ def getPropertyDescriptors(self):
+ return self.property_descriptors
+
+ def getRelationships(self):
+ return [self.REL_MULTILINE]
+
+ def create(self, context):
+ contents = context.getProperty(self.FF_CONTENTS).getValue()
+
+ if contents is not None and isinstance(contents, str):
+ contents_str = str.encode(contents)
+ if b' ' in contents_str:
+ return FlowFileSourceResult(relationship='space', attributes={"type": "space"}, contents=contents_str)
+
+ return FlowFileSourceResult(relationship='success', attributes={"type": "non-space"}, contents=contents)
diff --git a/extensions/python/PYTHON.md b/extensions/python/PYTHON.md
index 7d8849504..22bcfa7cd 100644
--- a/extensions/python/PYTHON.md
+++ b/extensions/python/PYTHON.md
@@ -155,7 +155,7 @@ The SentimentAnalysis processor will perform a Vader Sentiment Analysis. This re
## Using NiFi Python Processors
-MiNiFi C++ supports the use of NiFi Python processors, that are inherited from the FlowFileTransform base class. To use these processors, copy the Python processor module to the nifi_python_processors subdirectory of the python directory. By default, the python directory is ${minifi_root}/minifi-python. To see how to write NiFi Python processors, please refer to the Python Developer Guide under the [Apache NiFi documentation](https://nifi.apache.org/documentation/v2/).
+MiNiFi C++ supports the use of NiFi Python processors, that are inherited from the FlowFileTransform or the FlowFileSource base class. To use these processors, copy the Python processor module to the nifi_python_processors subdirectory of the python directory. By default, the python directory is ${minifi_root}/minifi-python. To see how to write NiFi Python processors, please refer to the Python Developer Guide under the [Apache NiFi documentation](https://nifi.apache.org/documentation/v2/).
In the flow configuration these Python processors can be referenced by their fully qualified class name, which looks like this: org.apache.nifi.minifi.processors.nifi_python_processors.<package_name>.<processor_name>. For example, the fully qualified class name of the PromptChatGPT processor implemented in the file nifi_python_processors/PromptChatGPT.py is org.apache.nifi.minifi.processors.nifi_python_processors.PromptChatGPT. If a processor is copied under a subdirectory, because it is [...]
diff --git a/extensions/python/pythonprocessors/nifiapi/flowfilesource.py b/extensions/python/pythonprocessors/nifiapi/flowfilesource.py
new file mode 100644
index 000000000..46510a925
--- /dev/null
+++ b/extensions/python/pythonprocessors/nifiapi/flowfilesource.py
@@ -0,0 +1,77 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import traceback
+from .properties import ProcessContext as ProcessContextProxy
+from abc import abstractmethod
+from minifi_native import ProcessContext, ProcessSession, FlowFile
+from .processorbase import ProcessorBase, WriteCallback
+
+
+class FlowFileSourceResult:
+ def __init__(self, relationship: str, attributes=None, contents=None):
+ self.relationship = relationship
+ self.attributes = attributes
+ if contents is not None and isinstance(contents, str):
+ self.contents = str.encode(contents)
+ else:
+ self.contents = contents
+
+ def getRelationship(self):
+ return self.relationship
+
+ def getContents(self):
+ return self.contents
+
+ def getAttributes(self):
+ return self.attributes
+
+
+class FlowFileSource(ProcessorBase):
+ # These will be added through the python bindings using C API
+ logger = None
+ REL_SUCCESS = None
+ REL_FAILURE = None
+ REL_ORIGINAL = None
+
+ def onTrigger(self, context: ProcessContext, session: ProcessSession):
+ context_proxy = ProcessContextProxy(context, self)
+ try:
+ result = self.create(context_proxy)
+ except Exception:
+ self.logger.error("Failed to create flow file due to error:\n{}".format(traceback.format_exc()))
+ return
+
+ flow_file = self.createFlowFile(session, result.getAttributes(), result.getContents())
+
+ if result.getRelationship() == "success":
+ session.transfer(flow_file, self.REL_SUCCESS)
+ else:
+ session.transferToCustomRelationship(flow_file, result.getRelationship())
+
+ def createFlowFile(self, session: ProcessSession, attributes, contents) -> FlowFile:
+ flow_file = session.create()
+ if attributes:
+ for key in attributes:
+ flow_file.addAttribute(key, attributes[key])
+
+ if contents:
+ session.write(flow_file, WriteCallback(contents))
+
+ return flow_file
+
+ @abstractmethod
+ def create(self, context):
+ pass
diff --git a/extensions/python/pythonprocessors/nifiapi/flowfiletransform.py b/extensions/python/pythonprocessors/nifiapi/flowfiletransform.py
index d17facd05..94487c3bc 100644
--- a/extensions/python/pythonprocessors/nifiapi/flowfiletransform.py
+++ b/extensions/python/pythonprocessors/nifiapi/flowfiletransform.py
@@ -14,21 +14,11 @@
# limitations under the License.
import traceback
-from abc import ABC, abstractmethod
-from typing import List
-from .properties import ExpressionLanguageScope, PropertyDescriptor, translateStandardValidatorToMiNiFiPropertype
+from abc import abstractmethod
+from minifi_native import ProcessContext, ProcessSession
+from .processorbase import ProcessorBase, WriteCallback
from .properties import FlowFile as FlowFileProxy
from .properties import ProcessContext as ProcessContextProxy
-from minifi_native import OutputStream, Processor, ProcessContext, ProcessSession
-
-
-class WriteCallback:
- def __init__(self, content):
- self.content = content
-
- def process(self, output_stream: OutputStream):
- output_stream.write(self.content)
- return len(self.content)
class FlowFileTransformResult:
@@ -50,37 +40,13 @@ class FlowFileTransformResult:
return self.attributes
-class FlowFileTransform(ABC):
+class FlowFileTransform(ProcessorBase):
# These will be added through the python bindings using C API
logger = None
REL_SUCCESS = None
REL_FAILURE = None
REL_ORIGINAL = None
- def describe(self, processor: Processor):
- if hasattr(self, 'ProcessorDetails') and hasattr(self.ProcessorDetails, 'description'):
- processor.setDescription(self.ProcessorDetails.description)
- else:
- processor.setDescription(self.__class__.__name__)
-
- def onInitialize(self, processor: Processor):
- processor.setSupportsDynamicProperties()
- for property in self.getPropertyDescriptors():
- property_type_code = translateStandardValidatorToMiNiFiPropertype(property.validators)
- expression_language_supported = True if property.expressionLanguageScope != ExpressionLanguageScope.NONE else False
-
- # MiNiFi C++ does not support dependant properties, so if a property depends on another property, it should not be required
- is_required = True if property.required and not property.dependencies else False
- processor.addProperty(property.name, property.description, property.defaultValue, is_required, expression_language_supported,
- property.sensitive, property_type_code, property.controllerServiceDefinition)
-
- def onScheduled(self, context_proxy: ProcessContextProxy):
- pass
-
- def onSchedule(self, context: ProcessContext):
- context_proxy = ProcessContextProxy(context, self)
- self.onScheduled(context_proxy)
-
def onTrigger(self, context: ProcessContext, session: ProcessSession):
original_flow_file = session.get()
if not original_flow_file:
@@ -121,6 +87,3 @@ class FlowFileTransform(ABC):
@abstractmethod
def transform(self, context: ProcessContextProxy, flowFile: FlowFileProxy) -> FlowFileTransformResult:
pass
-
- def getPropertyDescriptors(self) -> List[PropertyDescriptor]:
- return []
diff --git a/extensions/python/pythonprocessors/nifiapi/flowfiletransform.py b/extensions/python/pythonprocessors/nifiapi/processorbase.py
similarity index 58%
copy from extensions/python/pythonprocessors/nifiapi/flowfiletransform.py
copy to extensions/python/pythonprocessors/nifiapi/processorbase.py
index d17facd05..a343a4cb9 100644
--- a/extensions/python/pythonprocessors/nifiapi/flowfiletransform.py
+++ b/extensions/python/pythonprocessors/nifiapi/processorbase.py
@@ -13,11 +13,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-import traceback
from abc import ABC, abstractmethod
from typing import List
from .properties import ExpressionLanguageScope, PropertyDescriptor, translateStandardValidatorToMiNiFiPropertype
-from .properties import FlowFile as FlowFileProxy
from .properties import ProcessContext as ProcessContextProxy
from minifi_native import OutputStream, Processor, ProcessContext, ProcessSession
@@ -31,26 +29,7 @@ class WriteCallback:
return len(self.content)
-class FlowFileTransformResult:
- def __init__(self, relationship: str, attributes=None, contents=None):
- self.relationship = relationship
- self.attributes = attributes
- if contents is not None and isinstance(contents, str):
- self.contents = str.encode(contents)
- else:
- self.contents = contents
-
- def getRelationship(self):
- return self.relationship
-
- def getContents(self):
- return self.contents
-
- def getAttributes(self):
- return self.attributes
-
-
-class FlowFileTransform(ABC):
+class ProcessorBase(ABC):
# These will be added through the python bindings using C API
logger = None
REL_SUCCESS = None
@@ -81,45 +60,8 @@ class FlowFileTransform(ABC):
context_proxy = ProcessContextProxy(context, self)
self.onScheduled(context_proxy)
- def onTrigger(self, context: ProcessContext, session: ProcessSession):
- original_flow_file = session.get()
- if not original_flow_file:
- return
-
- flow_file = session.clone(original_flow_file)
-
- flow_file_proxy = FlowFileProxy(session, flow_file)
- context_proxy = ProcessContextProxy(context, self)
- try:
- result = self.transform(context_proxy, flow_file_proxy)
- except Exception:
- self.logger.error("Failed to transform flow file due to error:\n{}".format(traceback.format_exc()))
- session.remove(flow_file)
- session.transfer(original_flow_file, self.REL_FAILURE)
- return
-
- if result.getRelationship() == "failure":
- session.remove(flow_file)
- session.transfer(original_flow_file, self.REL_FAILURE)
- return
-
- result_attributes = result.getAttributes()
- if result_attributes is not None:
- for attribute in result_attributes:
- flow_file.addAttribute(attribute, result_attributes[attribute])
-
- result_content = result.getContents()
- if result_content is not None:
- session.write(flow_file, WriteCallback(result_content))
-
- if result.getRelationship() == "success":
- session.transfer(flow_file, self.REL_SUCCESS)
- else:
- session.transferToCustomRelationship(flow_file, result.getRelationship())
- session.transfer(original_flow_file, self.REL_ORIGINAL)
-
@abstractmethod
- def transform(self, context: ProcessContextProxy, flowFile: FlowFileProxy) -> FlowFileTransformResult:
+ def onTrigger(self, context: ProcessContext, session: ProcessSession):
pass
def getPropertyDescriptors(self) -> List[PropertyDescriptor]:
diff --git a/extensions/python/types/PyProcessSession.cpp b/extensions/python/types/PyProcessSession.cpp
index 1b20b4e5a..876c68bf7 100644
--- a/extensions/python/types/PyProcessSession.cpp
+++ b/extensions/python/types/PyProcessSession.cpp
@@ -132,7 +132,6 @@ void PyProcessSession::remove(const std::shared_ptr<core::FlowFile>& flow_file)
if (!session_) {
throw std::runtime_error("Access of ProcessSession after it has been released");
}
- std::shared_ptr<core::FlowFile> result;
session_->remove(flow_file);
flow_files_.erase(ranges::remove_if(flow_files_, [&flow_file](const auto& ff)-> bool { return ff == flow_file; }), flow_files_.end());
@@ -155,6 +154,14 @@ std::string PyProcessSession::getContentsAsString(const std::shared_ptr<core::Fl
return content;
}
+void PyProcessSession::putAttribute(const std::shared_ptr<core::FlowFile>& flow_file, std::string_view key, const std::string& value) {
+ if (!session_) {
+ throw std::runtime_error("Access of ProcessSession after it has been released");
+ }
+
+ session_->putAttribute(*flow_file, key, value);
+}
+
extern "C" {
static PyMethodDef PyProcessSessionObject_methods[] = { // NOLINT(cppcoreguidelines-avoid-c-arrays)
@@ -167,6 +174,7 @@ static PyMethodDef PyProcessSessionObject_methods[] = { // NOLINT(cppcoreguidel
{"transferToCustomRelationship", (PyCFunction) PyProcessSessionObject::transferToCustomRelationship, METH_VARARGS, nullptr},
{"remove", (PyCFunction) PyProcessSessionObject::remove, METH_VARARGS, nullptr},
{"getContentsAsBytes", (PyCFunction) PyProcessSessionObject::getContentsAsBytes, METH_VARARGS, nullptr},
+ {"putAttribute", (PyCFunction) PyProcessSessionObject::putAttribute, METH_VARARGS, nullptr},
{} /* Sentinel */
};
@@ -371,6 +379,47 @@ PyObject* PyProcessSessionObject::getContentsAsBytes(PyProcessSessionObject* sel
return PyBytes_FromStringAndSize(content.c_str(), gsl::narrow<Py_ssize_t>(content.size()));
}
+PyObject* PyProcessSessionObject::putAttribute(PyProcessSessionObject* self, PyObject* args) {
+ auto session = self->process_session_.lock();
+ if (!session) {
+ PyErr_SetString(PyExc_AttributeError, "tried reading process session outside 'on_trigger'");
+ return nullptr;
+ }
+ PyObject* script_flow_file = nullptr;
+ const char* attribute_key = nullptr;
+ const char* attribute_value = nullptr;
+ if (!PyArg_ParseTuple(args, "O!ss", PyScriptFlowFile::typeObject(), &script_flow_file, &attribute_key, &attribute_value)) {
+ return nullptr;
+ }
+
+ const auto flow_file = reinterpret_cast<PyScriptFlowFile*>(script_flow_file)->script_flow_file_.lock();
+ if (!flow_file) {
+ PyErr_SetString(PyExc_AttributeError, "tried reading FlowFile outside 'on_trigger'");
+ return nullptr;
+ }
+
+ if (!attribute_key) {
+ PyErr_SetString(PyExc_AttributeError, "Attribute key is invalid!");
+ return nullptr;
+ }
+
+ std::string attribute_key_str(attribute_key);
+ if (attribute_key_str.empty()) {
+ PyErr_SetString(PyExc_AttributeError, "Attribute key is empty!");
+ return nullptr;
+ }
+
+ if (!attribute_value) {
+ PyErr_SetString(PyExc_AttributeError, "Attribute value is invalid!");
+ return nullptr;
+ }
+
+ std::string attribute_value_str(attribute_value);
+
+ session->putAttribute(flow_file, attribute_key_str, attribute_value_str);
+ Py_RETURN_NONE;
+}
+
PyTypeObject* PyProcessSessionObject::typeObject() {
static OwnedObject PyProcessSessionObjectType{PyType_FromSpec(&PyProcessSessionObjectTypeSpec)};
return reinterpret_cast<PyTypeObject*>(PyProcessSessionObjectType.get());
diff --git a/extensions/python/types/PyProcessSession.h b/extensions/python/types/PyProcessSession.h
index 14866a820..a78bb9107 100644
--- a/extensions/python/types/PyProcessSession.h
+++ b/extensions/python/types/PyProcessSession.h
@@ -38,6 +38,7 @@ class PyProcessSession {
void write(const std::shared_ptr<core::FlowFile>& flow_file, BorrowedObject output_stream_callback);
void remove(const std::shared_ptr<core::FlowFile>& flow_file);
std::string getContentsAsString(const std::shared_ptr<core::FlowFile>& flow_file);
+ void putAttribute(const std::shared_ptr<core::FlowFile>& flow_file, std::string_view key, const std::string& value);
private:
std::vector<std::shared_ptr<core::FlowFile>> flow_files_;
@@ -63,6 +64,7 @@ struct PyProcessSessionObject {
static PyObject* transferToCustomRelationship(PyProcessSessionObject* self, PyObject* args);
static PyObject* remove(PyProcessSessionObject* self, PyObject* args);
static PyObject* getContentsAsBytes(PyProcessSessionObject* self, PyObject* args);
+ static PyObject* putAttribute(PyProcessSessionObject* self, PyObject* args);
static PyTypeObject* typeObject();
};
diff --git a/msi/WixWin.wsi.in b/msi/WixWin.wsi.in
index 85733f1e5..b12afc76b 100644
--- a/msi/WixWin.wsi.in
+++ b/msi/WixWin.wsi.in
@@ -395,7 +395,9 @@ ${WIX_EXTRA_COMPONENTS}
<Directory Id="INSTALLPYTHONDIR" Name="minifi-python">
<Directory Id="INSTALLNIFIAPIDIR" Name="nifiapi">
<Component Id="PythonProcessorNifiApiFiles" Guid="a9cb7b7b-e66d-4e32-9115-eab4aa980124">
+ <File Id="NifiApi_processorbase" Name="processorbase.py" KeyPath="no" Source="pythonprocessors\nifiapi\processorbase.py"/>
<File Id="NifiApi_flowfiletransform" Name="flowfiletransform.py" KeyPath="no" Source="pythonprocessors\nifiapi\flowfiletransform.py"/>
+ <File Id="NifiApi_flowfilesource" Name="flowfilesource.py" KeyPath="no" Source="pythonprocessors\nifiapi\flowfilesource.py"/>
<File Id="NifiApi_properties" Name="properties.py" KeyPath="no" Source="pythonprocessors\nifiapi\properties.py"/>
<File Id="NifiApi_relationship" Name="relationship.py" KeyPath="no" Source="pythonprocessors\nifiapi\relationship.py"/>
<File Id="NifiApi_documentation" Name="documentation.py" KeyPath="no" Source="pythonprocessors\nifiapi\documentation.py"/>