def validate_entry(entry_name: str,
                   sorted_packages: List[str],
                   lenient_prefix=False):
    """
    Validate if this entry name can be used. It currently checks for:
      1) If the package name is defined. (This can be turn off by setting
        `lenient_prefix` to True.
      2) If the entry name have at least 3 segments.

    Args:
        entry_name: The name to be validated.
        sorted_packages: The package names that are allowed.
        lenient_prefix: Whether we enforce that the entry must follow the
          pre-defined package name.

    Returns:

    """
    if not lenient_prefix:
        for package_name in sorted_packages:
            if entry_name.startswith(package_name):
                break
        else:
            # None of the package name matches.
            raise InvalidIdentifierException(
                f"Entry name [{entry_name}] does not start with any predefined "
                f"packages, please define the packages by using "
                f"`additional_prefixes` in the ontology. Or you can use the "
                f"default prefix 'ft.onto'.")

    entry_splits = entry_name.split(".")

    for e in entry_splits:
        if not e.isidentifier():
            raise InvalidIdentifierException(
                f"The entry name segment {e} is not a valid python identifier."
            )

    if len(entry_splits) < 3:
        raise InvalidIdentifierException(
            f"We currently require each entry to contains at least 3 levels, "
            f"which corresponds to the directory name, the file (module) name,"
            f"the entry class name. There are only {len(entry_splits)}"
            f"levels in [{entry_name}].")
示例#2
0
def validate_entry(entry_name: str, sorted_packages: List[str]) -> str:
    for package_name in sorted_packages:
        if entry_name.startswith(package_name):
            matched_package = package_name
            break
    else:
        # None of the package name matches.
        raise InvalidIdentifierException(
            f"Entry name [{entry_name}] does not start with any predefined "
            f"packages, please define the packages by using "
            f"`additional_prefixes` in the ontology. Or you can use the "
            f"default prefix 'ft.onto'.")

    entry_splits = entry_name.split('.')

    if len(entry_splits) < 3:
        raise InvalidIdentifierException(
            f"We currently require each entry to contains at least 3 levels, "
            f"which corresponds to the directory name, the file (module) name,"
            f"the entry class name. There are only {len(entry_splits)}"
            f"levels in [{entry_name}].")
    return matched_package
    def parse_schema(
        self,
        schema: Dict,
        source_json_file: str,
        merged_schema: List[Dict],
        merged_prefixes: List[str],
        lenient_prefix=False,
        merged_entry_tree: Optional[EntryTree] = None,
    ):
        r"""Generates ontology code for a parsed schema extracted from a
        json config. Appends entry code to the corresponding module. Creates a
        new module file if module is generated for the first time.

        Args:
            schema: Ontology dictionary extracted from a json config.
            source_json_file: Path of the source json file.
            merged_schema: The merged schema is used to remember all
                definitions during parsing.
            merged_prefixes: To remember all prefixes encountered during
                parsing.
            lenient_prefix: Whether to remove the constraint on the prefix set.
            merged_entry_tree: an EntryTree type object and if it's not`None`
                then after running this function, all the entries from
                ontology specification file would be parsed into a tree
                structure with parent and children entries to represent
                the relationship.
        Returns:
            Modules to be imported by dependencies of the current ontology.
        """
        entry_definitions: List[Dict] = schema[SchemaKeywords.definitions]
        merged_schema.extend(entry_definitions)

        if SchemaKeywords.prefixes in schema:
            merged_prefixes.extend(schema[SchemaKeywords.prefixes])

        allowed_packages = set(
            schema.get(SchemaKeywords.prefixes, []) + [DEFAULT_PREFIX])
        sorted_prefixes = analyze_packages(allowed_packages)

        file_desc = file_header(
            schema.get(SchemaKeywords.description, ""),
            schema.get(SchemaKeywords.ontology_name, ""),
        )

        for definition in entry_definitions:
            raw_entry_name = definition[SchemaKeywords.entry_name]
            validate_entry(raw_entry_name, sorted_prefixes, lenient_prefix)

            if raw_entry_name in self.allowed_types_tree:
                warnings.warn(
                    f"Class {raw_entry_name} already present in the "
                    f"ontology, will be overridden.",
                    DuplicateEntriesWarning,
                )
            self.allowed_types_tree[raw_entry_name] = set()

            # Add the entry definition to the import managers.
            # This time adding to the root manager so everyone can access it
            # if needed, but they will only appear in the import list when
            # requested.
            # Entry class should be added to the imports before the attributes
            # to be able to used as the attribute type for the same entry.
            self.import_managers.root.add_object_to_import(raw_entry_name)

            # Get various parts of the entry name.
            en = EntryName(raw_entry_name)
            entry_item, properties = self.parse_entry(en, definition)

            # Add it as a defining object.
            self.import_managers.get(
                en.module_name).add_defining_objects(raw_entry_name)

            # Get or set module writer only if the ontology to be generated
            # is not already installed.
            if source_json_file not in self.exclude_from_writing:
                module_writer = self.module_writers.get(en.module_name)
                module_writer.set_description(file_desc)
                module_writer.source_file = source_json_file
                # Add entry item to the writer.
                module_writer.add_entry(en, entry_item)

            # Adding entry attributes to the allowed types for validation.
            for property_name in properties:
                # Check if the name is allowed.
                if not property_name.isidentifier():
                    raise InvalidIdentifierException(
                        f"The property name: {property_name} is not a valid "
                        f"python identifier.")

                if property_name in self.allowed_types_tree[en.class_name]:
                    warnings.warn(
                        f"Attribute type for the entry {en.class_name} "
                        f"and the attribute {property_name} already present in "
                        f"the ontology, will be overridden",
                        DuplicatedAttributesWarning,
                    )
                self.allowed_types_tree[en.class_name].add(property_name)
            # populate the entry tree based on information
            if merged_entry_tree is not None:
                curr_entry_name = en.class_name
                parent_entry_name = definition["parent_entry"]
                curr_entry_attributes = self.allowed_types_tree[en.class_name]
                merged_entry_tree.add_node(curr_entry_name, parent_entry_name,
                                           curr_entry_attributes)
示例#4
0
    def parse_entry(self, entry_name: EntryName,
                    schema: Dict) -> Tuple[EntryDefinition, List[str]]:
        """
        Args:
            entry_name: Object holds various name form of the entry.
            schema: Dictionary containing specifications for an entry.

        Returns: extracted entry information: entry package string, entry
        filename, entry class entry_name, generated entry code and entry
        attribute names.
        """
        this_manager = self.import_managers.get(entry_name.module_name)

        # Determine the parent entry of this entry.
        parent_entry: str = schema[SchemaKeywords.parent_entry]

        if parent_entry.startswith(TOP_MOST_MODULE_NAME):
            raise ParentEntryNotSupportedException(
                f"The parent entry {parent_entry} cannot be directly inherited,"
                f" please inherit a type from {top.__name__} or your own"
                f" ontology.")

        if not this_manager.is_imported(parent_entry):
            raise ParentEntryNotDeclaredException(
                f"The parent entry {parent_entry} is not declared. It is "
                f"neither in the base entries nor in custom entries. "
                f"Please check them ontology specification, and make sure the "
                f"entry is defined before this.")

        base_entry: Optional[str] = self.find_base_entry(
            entry_name.class_name, parent_entry)

        if base_entry is None:
            raise OntologySpecError(
                f"Cannot find the base entry for entry "
                f"{entry_name.class_name} and {parent_entry}")

        if base_entry not in self.top_init_args:
            raise ParentEntryNotSupportedException(
                f"Cannot add {entry_name.class_name} to the ontology as "
                f"it's parent entry {parent_entry} is not supported. This is "
                f"likely that the entries are not inheriting the allowed types."
            )

        # Take the property definitions of this entry.
        properties: List[Dict] = schema.get(SchemaKeywords.attributes, [])

        this_manager = self.import_managers.get(entry_name.module_name)

        # Validate if the parent entry is present.
        if not this_manager.is_known_name(parent_entry):
            raise ParentEntryNotDeclaredException(
                f"Cannot add {entry_name.class_name} to the ontology as "
                f"it's parent entry {parent_entry} is not present "
                f"in the ontology.")

        parent_entry_use_name = this_manager.get_name_to_use(parent_entry)

        property_items, property_names = [], []
        for prop_schema in properties:
            # TODO: add test
            prop_name = prop_schema["name"]
            if prop_name in RESERVED_ATTRIBUTE_NAMES:
                raise InvalidIdentifierException(
                    f"The attribute name {prop_name} is reserved and cannot be "
                    f"used, please consider changed the name. The list of "
                    f"reserved name strings are "
                    f"{RESERVED_ATTRIBUTE_NAMES}")

            property_names.append(prop_schema["name"])
            property_items.append(self.parse_property(entry_name, prop_schema))

        # For special classes that requires a constraint.
        core_bases: Set[str] = self.top_to_core_entries[base_entry]
        entry_constraint_keys: Dict[str, str] = {}
        if any(item == "BaseLink" for item in core_bases):
            entry_constraint_keys = DEFAULT_CONSTRAINTS_KEYS["BaseLink"]
        elif any(item == "BaseGroup" for item in core_bases):
            entry_constraint_keys = DEFAULT_CONSTRAINTS_KEYS["BaseGroup"]

        class_att_items: List[ClassTypeDefinition] = []
        for schema_key, class_key in entry_constraint_keys.items():
            if schema_key in schema:
                constraint_type_ = schema[schema_key]
                constraint_type_name = this_manager.get_name_to_use(
                    constraint_type_)

                if constraint_type_name is None:
                    raise TypeNotDeclaredException(
                        f"The type {constraint_type_} is not defined but it is "
                        f"specified in {schema_key} of the definition of "
                        f"{schema['entry_name']}. Please define them before "
                        f"this entry type.")

                # TODO: cannot handle constraints that contain self-references.
                # self_ref = entry_name.class_name == constraint_type_

                class_att_items.append(
                    ClassTypeDefinition(class_key, constraint_type_name))

        # TODO: Can assign better object type to Link and Group objects
        custom_init_arg_str: str = self.construct_init(entry_name, base_entry)

        entry_item = EntryDefinition(
            name=entry_name.name,
            class_type=parent_entry_use_name,
            init_args=custom_init_arg_str,
            properties=property_items,
            class_attributes=class_att_items,
            description=schema.get(SchemaKeywords.description, None),
        )

        return entry_item, property_names