Skip to content

Commit

Permalink
NIFI-13396 Added Python constant Allowable Values to Manifests
Browse files Browse the repository at this point in the history
This closes #8963

Signed-off-by: David Handermann <[email protected]>
  • Loading branch information
briansolo1985 authored and exceptionfactory committed Jun 25, 2024
1 parent 42c0aa6 commit a5501ba
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,8 @@ def __init__(self,
sensitive: bool = True,
default_value: str = None,
expression_language_scope: str = None,
controller_service_definition: str = None):
controller_service_definition: str = None,
allowable_values: list[str] = None):
self.name = name
self.description = description
self.display_name = display_name
Expand All @@ -116,6 +117,7 @@ def __init__(self,
self.default_value = default_value
self.expression_language_scope = expression_language_scope
self.controller_service_definition = controller_service_definition
self.allowable_values = allowable_values if allowable_values is not None else []

def getName(self):
return self.name
Expand All @@ -140,3 +142,6 @@ def getExpressionLanguageScope(self):

def getControllerServiceDefinition(self):
return self.controller_service_definition

def getAllowableValues(self):
return ArrayList(self.allowable_values)
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

package org.apache.nifi.python.processor.documentation;

import java.util.List;
import org.apache.nifi.python.PythonObjectProxy;

public interface PropertyDescription extends PythonObjectProxy {
Expand All @@ -35,4 +36,6 @@ public interface PropertyDescription extends PythonObjectProxy {
String getExpressionLanguageScope();

String getControllerServiceDefinition();

List<String> getAllowableValues();
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,33 @@
logger = logging.getLogger("python.ProcessorInspection")


class StringConstantVisitor(ast.NodeVisitor):
def __init__(self):
self.string_assignments = {}

def visit_Assign(self, node):
if isinstance(node.value, ast.Constant) and isinstance(node.value.value, str):
string_value = node.value.value
for target in node.targets:
if isinstance(target, ast.Name):
variable_name = target.id
self.string_assignments[variable_name] = string_value
elif isinstance(target, ast.Tuple):
for element in target.elts:
if isinstance(element, ast.Name):
variable_name = element.id
self.string_assignments.append[variable_name] = string_value
self.generic_visit(node)


def get_module_string_constants(module_file: str) -> dict:
with open(module_file) as file:
root_node = ast.parse(file.read())
visitor = StringConstantVisitor()
visitor.visit(root_node)
return visitor.string_assignments


def get_processor_class_nodes(module_file: str) -> list:
with open(module_file) as file:
root_node = ast.parse(file.read())
Expand All @@ -41,6 +68,7 @@ def get_processor_class_nodes(module_file: str) -> list:
def get_processor_details(class_node, module_file, extension_home, dependencies_bundled):
# Look for a 'ProcessorDetails' class
child_class_nodes = get_class_nodes(class_node)
module_string_constants = get_module_string_constants(module_file)

# Get the Java interfaces that it implements
interfaces = get_java_interfaces(class_node)
Expand All @@ -54,7 +82,7 @@ def get_processor_details(class_node, module_file, extension_home, dependencies_
tags = __get_processor_tags(child_class_node)
use_cases = get_use_cases(class_node)
multi_processor_use_cases = get_multi_processor_use_cases(class_node)
property_descriptions = get_property_descriptions(class_node)
property_descriptions = get_property_descriptions(class_node, module_string_constants)

return ExtensionDetails.ExtensionDetails(interfaces=interfaces,
type=class_node.name,
Expand Down Expand Up @@ -184,7 +212,7 @@ def get_processor_configurations(constructor_calls: ast.List) -> list:
return configurations


def get_property_descriptions(class_node):
def get_property_descriptions(class_node, module_string_constants):
descriptions = []

for element in class_node.body:
Expand All @@ -200,7 +228,7 @@ def get_property_descriptions(class_node):
descriptor_info = {}
for keyword in element.value.keywords:
key = keyword.arg
value = get_constant_values(keyword.value)
value = get_constant_values(keyword.value, module_string_constants)
descriptor_info[key] = value

description = PropertyDescription(name=descriptor_info.get('name'),
Expand All @@ -210,7 +238,8 @@ def get_property_descriptions(class_node):
sensitive=replace_null(descriptor_info.get('sensitive'), False),
default_value=descriptor_info.get('default_value'),
expression_language_scope=replace_null(descriptor_info.get('expression_language_scope'), 'NONE'),
controller_service_definition=descriptor_info.get('controller_service_definition'))
controller_service_definition=descriptor_info.get('controller_service_definition'),
allowable_values = descriptor_info.get('allowable_values'))
descriptions.append(description)

return descriptions
Expand All @@ -230,22 +259,24 @@ def get_assigned_value(class_node, assignment_id, default_value=None):

return default_value

def get_constant_values(val):
def get_constant_values(val, string_constants: dict = {}):
if isinstance(val, ast.Constant):
return val.value
if isinstance(val, ast.Name):
return string_constants.get(val.id)
if isinstance(val, ast.List):
return [get_constant_values(v) for v in val.elts]
return [get_constant_values(v, string_constants) for v in val.elts]
if isinstance(val, ast.Dict):
keys = val.keys
values = val.values
key_values = [get_constant_values(v).strip() for v in keys]
value_values = [get_constant_values(v).strip() for v in values]
key_values = [get_constant_values(v, string_constants).strip() for v in keys]
value_values = [get_constant_values(v, string_constants).strip() for v in values]
return dict(zip(key_values, value_values))
if isinstance(val, ast.Attribute):
return val.attr
if isinstance(val, ast.BinOp) and isinstance(val.op, ast.Add):
left = get_constant_values(val.left)
right = get_constant_values(val.right)
left = get_constant_values(val.left, string_constants)
right = get_constant_values(val.right, string_constants)
if left and right:
return left + right
if left and not right:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
import org.apache.nifi.bundle.BundleDetails;
import org.apache.nifi.c2.protocol.component.api.BuildInfo;
import org.apache.nifi.c2.protocol.component.api.RuntimeManifest;
import org.apache.nifi.components.PropertyDescriptor;
import org.apache.nifi.extension.manifest.AllowableValue;
import org.apache.nifi.extension.manifest.ControllerServiceDefinition;
import org.apache.nifi.extension.manifest.ExpressionLanguageScope;
Expand Down Expand Up @@ -212,8 +211,8 @@ private static Property createManifestProperty(final PropertyDescription propert
property.setRequired(propertyDescription.isRequired());
property.setSensitive(propertyDescription.isSensitive());

// TODO: Handle Allowable Values
property.setControllerServiceDefinition(getManifestControllerServiceDefinition(propertyDescription.getControllerServiceDefinition()));
property.setAllowableValues(getAllowableValues(propertyDescription));

return property;
}
Expand All @@ -228,23 +227,18 @@ private static ControllerServiceDefinition getManifestControllerServiceDefinitio
return definition;
}

private static List<AllowableValue> getManifestAllowableValues(final PropertyDescriptor propertyDescriptor) {
if (propertyDescriptor.getAllowableValues() == null) {
return List.of();
}

final List<AllowableValue> allowableValues = new ArrayList<>();
for (final org.apache.nifi.components.AllowableValue allowableValue : propertyDescriptor.getAllowableValues()) {
final AllowableValue manifestAllowableValue = new AllowableValue();
manifestAllowableValue.setDescription(allowableValue.getDescription());
manifestAllowableValue.setValue(allowableValue.getValue());
manifestAllowableValue.setDisplayName(allowableValue.getDisplayName());
allowableValues.add(manifestAllowableValue);
}
return allowableValues;
private static List<AllowableValue> getAllowableValues(final PropertyDescription propertyDescription) {
return Optional.ofNullable(propertyDescription.getAllowableValues()).orElse(List.of())
.stream()
.map(value -> {
AllowableValue allowableValue = new AllowableValue();
allowableValue.setValue(value);
allowableValue.setDisplayName(value);
return allowableValue;
})
.toList();
}


private static List<UseCase> getUseCases(final PythonProcessorDetails pythonProcessorDetails) {
final List<UseCase> useCases = new ArrayList<>();
for (final UseCaseDetails useCaseDetails : pythonProcessorDetails.getUseCases()) {
Expand Down

0 comments on commit a5501ba

Please sign in to comment.