dam4rus commented on code in PR #7003: URL: https://github.com/apache/nifi/pull/7003#discussion_r1142937771
########## nifi-nar-bundles/nifi-py4j-bundle/nifi-python-extension-api/src/main/python/src/nifiapi/properties.py: ########## @@ -0,0 +1,411 @@ +# 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 enum import Enum +from nifiapi.__jvm__ import JvmHolder +import re + +class ExpressionLanguageScope(Enum): + NONE = 1 + LIMITED = 2 + FLOWFILE_ATTRIBUTES = 3 + + +class StandardValidators: + __standard_validators__ = JvmHolder.jvm.org.apache.nifi.processor.util.StandardValidators + + ALWAYS_VALID = JvmHolder.jvm.org.apache.nifi.components.Validator.VALID + NON_EMPTY_VALIDATOR = __standard_validators__.NON_EMPTY_VALIDATOR + INTEGER_VALIDATOR = __standard_validators__.INTEGER_VALIDATOR + POSITIVE_INTEGER_VALIDATOR = __standard_validators__.POSITIVE_INTEGER_VALIDATOR + POSITIVE_LONG_VALIDATOR = __standard_validators__.POSITIVE_LONG_VALIDATOR + NON_NEGATIVE_INTEGER_VALIDATOR = __standard_validators__.NON_NEGATIVE_INTEGER_VALIDATOR + NUMBER_VALIDATOR = __standard_validators__.NUMBER_VALIDATOR + LONG_VALIDATOR = __standard_validators__.LONG_VALIDATOR + PORT_VALIDATOR = __standard_validators__.PORT_VALIDATOR + NON_EMPTY_EL_VALIDATOR = __standard_validators__.NON_EMPTY_EL_VALIDATOR + HOSTNAME_PORT_LIST_VALIDATOR = __standard_validators__.HOSTNAME_PORT_LIST_VALIDATOR + BOOLEAN_VALIDATOR = __standard_validators__.BOOLEAN_VALIDATOR + URL_VALIDATOR = __standard_validators__.URL_VALIDATOR + URI_VALIDATOR = __standard_validators__.URI_VALIDATOR + REGULAR_EXPRESSION_VALIDATOR = __standard_validators__.REGULAR_EXPRESSION_VALIDATOR + REGULAR_EXPRESSION_WITH_EL_VALIDATOR = __standard_validators__.REGULAR_EXPRESSION_WITH_EL_VALIDATOR + TIME_PERIOD_VALIDATOR = __standard_validators__.TIME_PERIOD_VALIDATOR + DATA_SIZE_VALIDATOR = __standard_validators__.DATA_SIZE_VALIDATOR + FILE_EXISTS_VALIDATOR = __standard_validators__.FILE_EXISTS_VALIDATOR + + + +class PropertyDependency: + def __init__(self, property_descriptor, dependent_values=None): + if dependent_values is None: + dependent_values = [] + + if isinstance(property_descriptor, str): + self.property_name = property_descriptor + else: + self.property_name = property_descriptor.getName() + self.dependentValues = dependent_values + + @staticmethod + def from_java_dependency(java_dependencies): + if java_dependencies is None or len(java_dependencies) == 0: + return None + + dependencies = [] + for dependency in java_dependencies: + dependencies.append(PropertyDependency(dependency.getPropertyName(), dependency.getDependentValues())) + + return dependencies + + +class ResourceDefinition: + def __init__(self, allow_multiple=False, allow_file=True, allow_url=False, allow_directory=False, allow_text=False): + self.allow_multiple = allow_multiple + self.allow_file = allow_file + self.allow_url = allow_url + self.allow_directory = allow_directory + self.allow_text = allow_text + + @staticmethod + def from_java_definition(java_definition): + if java_definition is None: + return None + + allow_multiple = java_definition.getCardinality().name() == "MULTIPLE" + resource_types = java_definition.getResourceTypes() + allow_file = False + allow_url = False + allow_directory = False + allow_text = False + for type in resource_types: + name = type.name() + if name == "FILE": + allow_file = True + elif name == "DIRECTORY": + allow_directory = True + elif name == "TEXT": + allow_text = True + elif name == "URL": + allow_url = True + + return ResourceDefinition(allow_multiple, allow_file, allow_url, allow_directory, allow_text) + + +class PropertyDescriptor: + def __init__(self, name, description, required=False, sensitive=False, + display_name=None, default_value=None, allowable_values=None, + dependencies=None, expression_language_scope=ExpressionLanguageScope.NONE, + dynamic=False, validators=None, + resource_definition=None, controller_service_definition=None): + """ + :param name: the name of the property + :param description: a description of the property + :param required: a boolean indicating whether or not the property is required. Defaults to False. + :param sensitive: a boolean indicating whether or not the property is sensitive. Defaults to False. + :param display_name: Once a Processor has been released, its properties' configuration are stored as key/value pairs where the key is the name of the property. + Because of that, subsequent versions of the Processor should not change the name of a property. However, there are times when renaming a property + would be advantageous. For example, one might find a typo in the name of a property, or users may find a property name confusing. While the name of + the property should not be changed, a display_name may be added. Doing this results in the Display Name being used in the NiFi UI but still maintains + the original name as the key. Generally, this value should be left unspecified. If unspecified (or a value of `None`), the display name will default + to whatever the name is. However, the display_name may be changed at any time between versions without adverse effects. + :param default_value: a default value for the property. If not specified, the initial value will be unset. If specified, any time the value is removed, it is reset to this + default_value. That is to say, if a default value is specified, the property cannot be unset. + :param allowable_values: a list of string values that are allowed. If specified, the UI will present the options as a drop-down instead of a freeform text. If any other value + is specified for the property, the Processor will be invalid. + :param dependencies: a list of dependencies for this property. By default, all properties are always configurable. However, sometimes we want to expose a property that only makes + sense in certain circumstances. In this situation, we can say that Property A depends on Property B. Now, Property A will only be shown if a value is selected + for Property B. Additionally, we may say that Property A depends on Property B being set to some explicit value, say "foo." Now, Property A will only be shown + in the UI if Property B is set to a value of "foo." If a Property is not shown in the UI, its value will also not be validated. For example, if we indicate that + Property A is required and depends on Property B, then Property A is only required if Property B is set. + :param expression_language_scope: documents the scope in which Expression Language is valid. This value must be specified as one of the enum values + in `nifiapi.properties.ExpressionLanguageScope`. A value of `NONE` indicates that Expression Language will not be evaluated for this property. + This is the default. A value of `FLOWFILE_ATTRIBUTES` indicates that FlowFile attributes may be referenced when configuring the property value. + A value of `LIMITED` indicates that Expression Language may be used but may not reference FlowFile attributes. For example, a value of `${now()}` might + be used to reference the current date and time, or `${hostname(true)}` might be used to specify the hostname. + :param dynamic: whether or not this Property Descriptor represents a dynamic (aka user-defined) property. This is not necessary to specify, as the framework can determine this. + However, it is available if there is a desire to explicitly set it for completeness' sake. + :param validators: A list of property validators that can be used to ensure that the user-supplied value is valid. The standard validators can be referenced using the + members of the `nifiapi.properties.StandardValidators` class. + :param resource_definition: an instance of `nifiapi.properties.ResourceDefinition`. This may be used to convey that the property references a file, directory, or URL, or a set of them. + :param controller_service_definition: if this Processor is to make use of a Controller Service, this indicates the type of Controller Service. This will always be a fully-qualified + classname of a Java interface that extends from `ControllerService`. + """ + if validators is None: + validators = [StandardValidators.ALWAYS_VALID] + + self.name = name + self.description = description + self.required = required + self.sensitive = sensitive + self.displayName = display_name + self.defaultValue = None if default_value is None else str(default_value) + self.allowableValues = allowable_values + self.dependencies = dependencies + self.expressionLanguageScope = expression_language_scope + self.dynamic = dynamic + self.validators = validators + self.resourceDefinition = resource_definition + self.controllerServiceDefinition = controller_service_definition + + @staticmethod + def from_java_descriptor(java_descriptor): + # Build the dependencies + dependencies = PropertyDependency.from_java_dependency(java_descriptor.getDependencies()) + + # Build the allowable values + allowable = java_descriptor.getAllowableValues() + if allowable is None or len(allowable) == 0: + allowable_values = None + else: + allowable_values = [] + for value in allowable: + allowable_values.append(value.getValue()) + + # Build the resource definition + resource_definition = ResourceDefinition.from_java_definition(java_descriptor.getResourceDefinition()) + + el_scope = java_descriptor.getExpressionLanguageScope() + el_scope_name = None if el_scope is None else el_scope.name() + + return PropertyDescriptor(java_descriptor.getName(), java_descriptor.getDescription(), + required = java_descriptor.isRequired(), + sensitive = java_descriptor.isSensitive(), + display_name = java_descriptor.getDisplayName(), + default_value = java_descriptor.getDefaultValue(), + allowable_values = allowable_values, + expression_language_scope = el_scope_name, + dynamic = java_descriptor.isDynamic(), + validators = java_descriptor.getValidators(), + controller_service_definition = java_descriptor.getControllerServiceDefinition(), + dependencies = dependencies, + resource_definition = resource_definition) + + + def to_java_descriptor(self, gateway, cs_type_lookup): + # TODO: Consider dependencies + el_scope = gateway.jvm.org.apache.nifi.expression.ExpressionLanguageScope.valueOf(self.expressionLanguageScope.name) + + builder = gateway.jvm.org.apache.nifi.components.PropertyDescriptor.Builder() \ + .name(self.name) \ + .displayName(self.displayName) \ + .description(self.description) \ + .required(self.required) \ + .sensitive(self.sensitive) \ + .defaultValue(self.defaultValue) \ + .expressionLanguageSupported(el_scope) \ + .dynamic(self.dynamic) + + if self.resourceDefinition is not None: + self.__add_resource_definition__(gateway, self.resourceDefinition, builder) + + if self.controllerServiceDefinition is not None: + cs_type = cs_type_lookup.lookup(self.controllerServiceDefinition) + builder.identifiesControllerService(cs_type) + + if self.allowableValues is not None: + builder.allowableValues(self.__get_allowable_values__(gateway)) + + for validator in self.validators: + builder.addValidator(validator) + + return builder.build() + + + def __get_allowable_values__(self, gateway): + if self.allowableValues is None: + return None + values = gateway.jvm.java.util.ArrayList() + for value in self.allowableValues: + values.add(value) + + return values + + def __add_resource_definition__(self, gateway, resouce_definition, builder): + allowed_types = 0 + if resouce_definition.allow_file: + allowed_types += 1 + if resouce_definition.allow_directory: + allowed_types += 1 + if resouce_definition.allow_url: + allowed_types += 1 + if resouce_definition.allow_text: + allowed_types += 1 + + array_type = gateway.jvm.org.apache.nifi.components.resource.ResourceType + types = gateway.new_array(array_type, allowed_types) + index = 0 + if resouce_definition.allow_file: + types[index] = gateway.jvm.org.apache.nifi.components.resource.ResourceType.FILE + index += 1 + if resouce_definition.allow_directory: + types[index] = gateway.jvm.org.apache.nifi.components.resource.ResourceType.DIRECTORY + index += 1 + if resouce_definition.allow_url: + types[index] = gateway.jvm.org.apache.nifi.components.resource.ResourceType.URL + index += 1 + if resouce_definition.allow_text: + types[index] = gateway.jvm.org.apache.nifi.components.resource.ResourceType.TEXT + index += 1 + + cardinality = gateway.jvm.org.apache.nifi.components.resource.ResourceCardinality.MULTIPLE if resouce_definition.allow_multiple else \ + gateway.jvm.org.apache.nifi.components.resource.ResourceCardinality.SINGLE + + builder.identifiesExternalResource(cardinality, types[0], types[1:]) + + +class ProcessContext: + __trivial_attribute_reference__ = re.compile("\$\{([^${}\[\],:;/*\' \t\r\n\d][^${}\[\],:;/*\' \t\r\n]*)}") + __escaped_attribute_reference__ = re.compile("\$\{'([^${}\[\],:;/*\' \t\r\n\d][^${}\[\],:;/*\' \t\r\n]*)'}") + + def __init__(self, java_context): + self.java_context = java_context + + descriptors = java_context.getProperties().keySet() + self.name = java_context.getName() + self.property_values = {} + self.descriptor_value_map = {} + + for descriptor in descriptors: + property_value = java_context.getProperty(descriptor.getName()) + string_value = property_value.getValue() + + property_value = self.__create_python_property_value__(descriptor.isExpressionLanguageSupported(), property_value, string_value) + self.property_values[descriptor.getName()] = property_value + + python_descriptor = PropertyDescriptor.from_java_descriptor(descriptor) + self.descriptor_value_map[python_descriptor] = string_value + + + def __create_python_property_value__(self, el_supported, java_property_value, string_value): + el_present = java_property_value.isExpressionLanguagePresent() + referenced_attribute = None + if el_present: + trivial_match = self.__trivial_attribute_reference__.match(string_value) + if trivial_match is not None: Review Comment: Kinda subjective. With that operator the code can be flattened, which improves readability IMHO. This can be reduced to this code: ```python if trivial_match := self.__trivial_attribute_reference__.match(string_value): referenced_attribute = trivial_match.group(1) elif escaped_match := self.__escaped_attribute_reference__.match(string_value): referenced_attribute = escaped_match.group(1) ``` -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: issues-unsubscr...@nifi.apache.org For queries about this service, please contact Infrastructure at: us...@infra.apache.org