5cbd3f29e3
Fuzz / Run fuzz harnesses (${{ github.event_name == 'schedule' && 'nightly' || 'smoke' }}) (push) Has been cancelled
Create Releases / call-mac (push) Has been cancelled
Create Releases / call-linux (push) Has been cancelled
Create Releases / call-sdist (push) Has been cancelled
Create Releases / call-win (push) Has been cancelled
Create Releases / call-pyodide (push) Has been cancelled
Windows_No_Exception_CI / build (x64, 3.10) (push) Has been cancelled
Check URLs / build (push) Has been cancelled
Create Releases / Attest CI build artifacts (push) Has been cancelled
Create Releases / Check for Publish release build to pypi (push) Has been cancelled
Create Releases / Check for Publish preview build to test.pypi-weekly (push) Has been cancelled
Create Releases / Publish preview build to test.pypi-weekly (push) Has been cancelled
Create Releases / Check for Publish release build to test.pypi (rc-candidates) (push) Has been cancelled
Create Releases / Publish release build to test.pypi (push) Has been cancelled
Create Releases / Check for Publish preview build to pypi-weekly (push) Has been cancelled
Create Releases / Publish preview build to pypi-weekly (push) Has been cancelled
Create Releases / Publish release build to pypi (push) Has been cancelled
Create Releases / test source distribution (push) Has been cancelled
clang-tidy / clang-tidy (push) Has been cancelled
Lint / Validate SBOM (push) Has been cancelled
Lint / Enforce style (push) Has been cancelled
CI / Test windows-2022, 3.14, External, debug=0, unity_build=0, onnx_ml=1, autogen=0 (push) Has been cancelled
CI / Test windows-latest, 3.10, Internal, debug=0, unity_build=0, onnx_ml=1, autogen=0 (push) Has been cancelled
CI / Test windows-latest, 3.14, Internal, debug=0, unity_build=0, onnx_ml=1, autogen=0 (push) Has been cancelled
CI / Test windows-latest, 3.14t, Internal, debug=0, unity_build=0, onnx_ml=1, autogen=0 (push) Has been cancelled
CI / Test ubuntu-24.04, 3.14, Internal, debug=1, unity_build=0, onnx_ml=1, autogen=0 (push) Has been cancelled
CI / Test ubuntu-24.04, 3.14, External, debug=0, unity_build=1, onnx_ml=1, autogen=1 (push) Has been cancelled
CI / Test ubuntu-24.04, 3.14, External, debug=0, unity_build=0, onnx_ml=0, autogen=0 (push) Has been cancelled
CI / Test macos-latest, 3.10, Internal, debug=0, unity_build=0, onnx_ml=1, autogen=0 (push) Has been cancelled
CI / Test macos-latest, 3.14, Internal, debug=0, unity_build=0, onnx_ml=1, autogen=0 (push) Has been cancelled
CI / Test macos-latest, 3.14t, Internal, debug=0, unity_build=0, onnx_ml=1, autogen=0 (push) Has been cancelled
CI / Test ubuntu-24.04, 3.14, External, debug=0, unity_build=0, onnx_ml=1, autogen=0 (push) Has been cancelled
CI / Test ubuntu-24.04, 3.10, Internal, debug=0, unity_build=0, onnx_ml=1, autogen=0 (push) Has been cancelled
CI / Test ubuntu-24.04, 3.14, Internal, debug=0, unity_build=0, onnx_ml=1, autogen=0 (push) Has been cancelled
CI / Test ubuntu-24.04, 3.14t, Internal, debug=0, unity_build=0, onnx_ml=1, autogen=0 (push) Has been cancelled
Pixi CI / Install and lint (ubuntu-24.04-arm) (push) Has been cancelled
Pixi CI / Install and lint (windows-2022) (push) Has been cancelled
Pixi CI / Xcode generator build (push) Has been cancelled
Pixi CI / Install and test (macos-latest, default) (push) Has been cancelled
Pixi CI / Install and test (ubuntu-24.04-arm, default) (push) Has been cancelled
Pixi CI / Install and test (ubuntu-latest, default) (push) Has been cancelled
Pixi CI / Install and test (windows-2022, default) (push) Has been cancelled
Pixi CI / Install and test (macos-latest, oldies) (push) Has been cancelled
Pixi CI / Install and test (ubuntu-24.04-arm, oldies) (push) Has been cancelled
Pixi CI / Install and test (ubuntu-latest, oldies) (push) Has been cancelled
Pixi CI / Install and test (windows-2022, oldies) (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (cpp) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Copilot Setup Steps / copilot-setup-steps (push) Has been cancelled
Generate and publish ONNX docs / build (push) Has been cancelled
Generate and publish ONNX docs / deploy (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
475 lines
18 KiB
Python
475 lines
18 KiB
Python
# Copyright (c) ONNX Project Contributors
|
|
#
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
from __future__ import annotations
|
|
|
|
import subprocess
|
|
import sys
|
|
from copy import deepcopy
|
|
from pathlib import Path
|
|
from typing import TYPE_CHECKING, Any
|
|
|
|
import onnx
|
|
from onnx.backend.test.case.test_case import TestCase
|
|
from onnx.backend.test.case.utils import import_recursive
|
|
from onnx.onnx_pb import (
|
|
AttributeProto,
|
|
FunctionProto,
|
|
GraphProto,
|
|
ModelProto,
|
|
NodeProto,
|
|
OperatorSetIdProto,
|
|
TensorProto,
|
|
TypeProto,
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
from collections.abc import Callable, Sequence
|
|
|
|
import numpy as np
|
|
|
|
_NodeTestCases = []
|
|
_TargetOpType = None
|
|
_DiffOpTypes = None
|
|
_existing_names: dict[str, onnx.NodeProto] = {}
|
|
|
|
|
|
def _rename_edges_helper(
|
|
internal_node: NodeProto,
|
|
rename_helper: Callable[[str], str],
|
|
attribute_map: dict[str, AttributeProto],
|
|
prefix: str,
|
|
) -> NodeProto:
|
|
new_node = NodeProto()
|
|
new_node.CopyFrom(internal_node)
|
|
new_node.ClearField("input")
|
|
new_node.ClearField("output")
|
|
new_node.ClearField("attribute")
|
|
for internal_name in internal_node.input:
|
|
new_node.input.append(rename_helper(internal_name))
|
|
for internal_name in internal_node.output:
|
|
new_node.output.append(rename_helper(internal_name))
|
|
for attr in internal_node.attribute:
|
|
if attr.HasField("ref_attr_name"):
|
|
if attr.ref_attr_name in attribute_map:
|
|
new_attr = AttributeProto()
|
|
new_attr.CopyFrom(attribute_map[attr.ref_attr_name])
|
|
new_attr.name = attr.name
|
|
new_node.attribute.extend([new_attr])
|
|
else:
|
|
new_attr = AttributeProto()
|
|
new_attr.CopyFrom(attr)
|
|
if attr.type == AttributeProto.GRAPH:
|
|
new_graph = new_attr.g
|
|
sg_rename = {}
|
|
for in_desc in new_graph.input:
|
|
sg_rename[in_desc.name] = in_desc.name = prefix + in_desc.name
|
|
for out_desc in new_graph.output:
|
|
sg_rename[out_desc.name] = out_desc.name = prefix + out_desc.name
|
|
for init_desc in new_graph.initializer:
|
|
sg_rename[init_desc.name] = init_desc.name = prefix + init_desc.name
|
|
for sparse_init_desc in new_graph.sparse_initializer:
|
|
sg_rename[sparse_init_desc.values.name] = (
|
|
sparse_init_desc.values.name
|
|
) = prefix + sparse_init_desc.values.name
|
|
for sparse_init_desc in new_graph.sparse_initializer:
|
|
sg_rename[sparse_init_desc.indices.name] = (
|
|
sparse_init_desc.indices.name
|
|
) = prefix + sparse_init_desc.indices.name
|
|
|
|
def subgraph_rename_helper(name: str) -> Any:
|
|
if name in sg_rename: # noqa: B023
|
|
return sg_rename[name] # noqa: B023
|
|
return rename_helper(name)
|
|
|
|
new_nodes = [
|
|
_rename_edges_helper(
|
|
node_desc, subgraph_rename_helper, attribute_map, prefix
|
|
)
|
|
for node_desc in new_graph.node
|
|
]
|
|
new_graph.ClearField("node")
|
|
new_graph.node.extend(new_nodes)
|
|
new_node.attribute.extend([new_attr])
|
|
return new_node
|
|
|
|
|
|
# FIXME(TMVector): Any reason we can't get rid of this and use the C++ helper directly?
|
|
def function_expand_helper(
|
|
node: NodeProto, function_proto: FunctionProto, op_prefix: str
|
|
) -> list[NodeProto]:
|
|
io_names_map = {}
|
|
attribute_map = {a.name: a for a in node.attribute}
|
|
|
|
for idx, input in enumerate(function_proto.input):
|
|
io_names_map[input] = node.input[idx] if idx in range(len(node.input)) else ""
|
|
|
|
for idx, output in enumerate(function_proto.output):
|
|
# Even if the node has been created with optional outputs missing, we
|
|
# can't assume that the function body handles this correctly, such as in
|
|
# the case that output is also an intermediate value.
|
|
# So we only add a name mapping if the output is present. An internal
|
|
# name will be generated if the missing output is used, the same as any
|
|
# other internal tensor.
|
|
if idx in range(len(node.output)) and node.output[idx] != "":
|
|
io_names_map[output] = node.output[idx]
|
|
|
|
def rename_helper(internal_name: str) -> Any:
|
|
if internal_name in io_names_map:
|
|
return io_names_map[internal_name]
|
|
if internal_name == "":
|
|
return ""
|
|
return op_prefix + internal_name
|
|
|
|
return [
|
|
_rename_edges_helper(internal_node, rename_helper, attribute_map, op_prefix)
|
|
for internal_node in function_proto.node
|
|
]
|
|
|
|
|
|
def function_testcase_helper(
|
|
node: NodeProto,
|
|
input_types: list[TypeProto],
|
|
name: str,
|
|
opset_imports: Sequence[OperatorSetIdProto] | None = None,
|
|
) -> tuple[list[tuple[list[NodeProto], Any]], int]:
|
|
test_op = node.op_type
|
|
op_prefix = test_op + "_" + name + "_expanded_function_"
|
|
if opset_imports is None:
|
|
# No opset in the model. We take the most recent definition.
|
|
schema = onnx.defs.get_schema(test_op, domain=node.domain)
|
|
else:
|
|
# We take the function defined in the specific version mentioned
|
|
# in the model. Find the opset_import matching the node's domain.
|
|
node_domain = node.domain or ""
|
|
matching_opset = None
|
|
for opset in opset_imports:
|
|
opset_domain = opset.domain or ""
|
|
if opset_domain == node_domain or (
|
|
node_domain in {"", "ai.onnx"} and opset_domain in {"", "ai.onnx"}
|
|
):
|
|
matching_opset = opset
|
|
break
|
|
if matching_opset is None:
|
|
raise ValueError(
|
|
f"No matching opset_import found for domain {node_domain!r} "
|
|
f"in {[o.domain for o in opset_imports]}."
|
|
)
|
|
version = matching_opset.version
|
|
schema = onnx.defs.get_schema(test_op, version, domain=node.domain)
|
|
|
|
# an op schema may have several functions, each for one opset version
|
|
# opset versions include the op's since_version and other opset versions
|
|
# if it is needed to define the op for a opset version other than the op's since_version.
|
|
function_protos = []
|
|
for opset_version in schema.function_opset_versions: # type: ignore[attr-defined]
|
|
function_proto_str = schema.get_function_with_opset_version(opset_version) # type: ignore[attr-defined]
|
|
function_proto = FunctionProto()
|
|
function_proto.ParseFromString(function_proto_str)
|
|
function_protos.append(function_proto)
|
|
for opset_version in schema.context_dependent_function_opset_versions: # type: ignore[attr-defined]
|
|
function_proto_str = schema.get_context_dependent_function_with_opset_version( # type: ignore[attr-defined]
|
|
opset_version,
|
|
node.SerializeToString(),
|
|
[t.SerializeToString() for t in input_types],
|
|
)
|
|
function_proto = FunctionProto()
|
|
function_proto.ParseFromString(function_proto_str)
|
|
function_protos.append(function_proto)
|
|
|
|
expanded_tests = []
|
|
for function_proto in function_protos:
|
|
for attr in schema.attributes:
|
|
if attr in [a.name for a in node.attribute]:
|
|
continue
|
|
if schema.attributes[attr].default_value:
|
|
node.attribute.extend([schema.attributes[attr].default_value])
|
|
|
|
# function_proto.attributes
|
|
node_list = function_expand_helper(node, function_proto, op_prefix)
|
|
expanded_tests.append((node_list, function_proto.opset_import))
|
|
|
|
return expanded_tests, schema.since_version
|
|
|
|
|
|
def _extract_value_info(
|
|
input: list[Any] | np.ndarray | None,
|
|
name: str,
|
|
type_proto: TypeProto | None = None,
|
|
) -> onnx.ValueInfoProto:
|
|
if type_proto is None:
|
|
if input is None:
|
|
raise NotImplementedError(
|
|
"_extract_value_info: both input and type_proto arguments cannot be None."
|
|
)
|
|
if isinstance(input, list):
|
|
elem_type = onnx.helper.np_dtype_to_tensor_dtype(input[0].dtype)
|
|
shape = None
|
|
tensor_type_proto = onnx.helper.make_tensor_type_proto(elem_type, shape)
|
|
type_proto = onnx.helper.make_sequence_type_proto(tensor_type_proto)
|
|
elif isinstance(input, TensorProto):
|
|
elem_type = input.data_type
|
|
shape = tuple(input.dims)
|
|
type_proto = onnx.helper.make_tensor_type_proto(elem_type, shape)
|
|
else:
|
|
elem_type = onnx.helper.np_dtype_to_tensor_dtype(input.dtype)
|
|
shape = input.shape
|
|
type_proto = onnx.helper.make_tensor_type_proto(elem_type, shape)
|
|
|
|
return onnx.helper.make_value_info(name, type_proto)
|
|
|
|
|
|
def _make_test_model_gen_version(graph: GraphProto, **kwargs: Any) -> ModelProto:
|
|
(
|
|
latest_onnx_version,
|
|
latest_ml_version,
|
|
latest_training_version,
|
|
) = onnx.helper.VERSION_TABLE[-1][2:5] # type: ignore[index]
|
|
if "opset_imports" in kwargs:
|
|
for opset in kwargs["opset_imports"]:
|
|
# If the test model uses an unreleased opset version (latest_version+1),
|
|
# directly use make_model to create a model with the latest ir version
|
|
if (
|
|
(
|
|
(opset.domain in {"", "ai.onnx"})
|
|
and opset.version == latest_onnx_version + 1
|
|
)
|
|
or (
|
|
opset.domain == "ai.onnx.ml"
|
|
and opset.version == latest_ml_version + 1
|
|
)
|
|
or (
|
|
(
|
|
opset.domain
|
|
in {"ai.onnx.training version", "ai.onnx.preview.training"}
|
|
)
|
|
and opset.version == latest_training_version + 1
|
|
)
|
|
):
|
|
return onnx.helper.make_model(graph, **kwargs)
|
|
# Otherwise, find and use the corresponding ir version according to given opset version
|
|
return onnx.helper.make_model_gen_version(graph, **kwargs)
|
|
|
|
|
|
# In the case of ops with optional inputs and outputs, node_op.input and node_op.output indicate
|
|
# which inputs/outputs are present and which are omitted. However, the parameter inputs
|
|
# and outputs of this function include values only for inputs/outputs that are present.
|
|
# E.g., for an op with 3 inputs, if the second parameter is optional and we wish to omit it,
|
|
# node_op.inputs would look like ["Param1", "", "Param3"], while inputs would look like
|
|
# [input-1-value, input-3-value]
|
|
# Instead of creating model with latest version, it now generates models for since_version by default.
|
|
# Thus it can make every model uses the same opset version after every opset change.
|
|
# Besides, user can specify "use_max_opset_version" to generate models for
|
|
# the latest opset version that supports before targeted opset version
|
|
def expect(
|
|
node_op: onnx.NodeProto,
|
|
inputs: Sequence[np.ndarray | TensorProto],
|
|
outputs: Sequence[np.ndarray | TensorProto],
|
|
name: str,
|
|
**kwargs: Any,
|
|
) -> None:
|
|
# skip if the node_op's op_type is not same as the given one
|
|
if _TargetOpType and node_op.op_type != _TargetOpType:
|
|
return
|
|
if _DiffOpTypes is not None and node_op.op_type.lower() not in _DiffOpTypes:
|
|
return
|
|
if name in _existing_names:
|
|
raise ValueError(
|
|
f"Name {name!r} is already using by one test case for node type {node_op.op_type!r}."
|
|
)
|
|
_existing_names[name] = node_op
|
|
|
|
# in case node_op is modified
|
|
node = deepcopy(node_op)
|
|
present_inputs = [x for x in node.input if (x != "")]
|
|
present_outputs = [x for x in node.output if (x != "")]
|
|
input_type_protos = [None] * len(inputs)
|
|
if "input_type_protos" in kwargs:
|
|
input_type_protos = kwargs["input_type_protos"]
|
|
del kwargs["input_type_protos"]
|
|
output_type_protos = [None] * len(outputs)
|
|
if "output_type_protos" in kwargs:
|
|
output_type_protos = kwargs["output_type_protos"]
|
|
del kwargs["output_type_protos"]
|
|
inputs_vi = [
|
|
_extract_value_info(arr, arr_name, input_type)
|
|
for arr, arr_name, input_type in zip(
|
|
inputs, present_inputs, input_type_protos, strict=False
|
|
)
|
|
]
|
|
outputs_vi = [
|
|
_extract_value_info(arr, arr_name, output_type)
|
|
for arr, arr_name, output_type in zip(
|
|
outputs, present_outputs, output_type_protos, strict=False
|
|
)
|
|
]
|
|
graph = onnx.helper.make_graph(
|
|
nodes=[node], name=name, inputs=inputs_vi, outputs=outputs_vi
|
|
)
|
|
kwargs["producer_name"] = "backend-test"
|
|
|
|
if "opset_imports" not in kwargs:
|
|
# To make sure the model will be produced with the same opset_version after opset changes
|
|
# By default, it uses since_version as opset_version for produced models
|
|
produce_opset_version = onnx.defs.get_schema(
|
|
node.op_type, domain=node.domain
|
|
).since_version
|
|
kwargs["opset_imports"] = [
|
|
onnx.helper.make_operatorsetid(node.domain, produce_opset_version)
|
|
]
|
|
|
|
model = _make_test_model_gen_version(graph, **kwargs)
|
|
|
|
_NodeTestCases.append(
|
|
TestCase(
|
|
name=name,
|
|
model_name=name,
|
|
url=None,
|
|
model_dir=None,
|
|
model=model,
|
|
data_sets=[(inputs, outputs)],
|
|
kind="node",
|
|
rtol=1e-3,
|
|
atol=1e-7,
|
|
)
|
|
)
|
|
|
|
# Create list of types for node.input, filling a default TypeProto for missing inputs:
|
|
# E.g. merge(["x", "", "y"], [x-value-info, y-value-info]) will return [x-type, default-type, y-type]
|
|
def merge(
|
|
node_inputs: list[str], present_value_info: list[onnx.ValueInfoProto]
|
|
) -> list[TypeProto]:
|
|
if node_inputs:
|
|
if node_inputs[0] != "":
|
|
return [
|
|
present_value_info[0].type,
|
|
*merge(node_inputs[1:], present_value_info[1:]),
|
|
]
|
|
return [TypeProto(), *merge(node_inputs[1:], present_value_info)]
|
|
return []
|
|
|
|
merged_types = merge(list(node.input), inputs_vi)
|
|
(
|
|
expanded_tests,
|
|
since_version,
|
|
) = function_testcase_helper(
|
|
node, merged_types, name, opset_imports=kwargs.get("opset_imports")
|
|
)
|
|
for expanded_function_nodes, func_opset_import in expanded_tests:
|
|
kwargs["producer_name"] = "backend-test"
|
|
|
|
# TODO: if kwargs["opset_imports"] already exists, only generate test case for the opset version.
|
|
# replace opset versions with what are specified in function proto
|
|
if "opset_imports" not in kwargs:
|
|
kwargs["opset_imports"] = func_opset_import
|
|
else:
|
|
for opset_import in func_opset_import:
|
|
matches = [
|
|
opset
|
|
for opset in kwargs["opset_imports"]
|
|
if opset.domain == opset_import.domain
|
|
]
|
|
if matches:
|
|
matches[0].version = opset_import.version
|
|
else:
|
|
kwargs["opset_imports"].append(opset_import)
|
|
|
|
onnx_ai_opset_version = ""
|
|
if "opset_imports" in kwargs:
|
|
onnx_ai_opset_imports = [
|
|
oi for oi in kwargs["opset_imports"] if oi.domain in ("", "ai.onnx")
|
|
]
|
|
if len(onnx_ai_opset_imports) == 1:
|
|
onnx_ai_opset_version = onnx_ai_opset_imports[0].version
|
|
|
|
function_test_name = name + "_expanded"
|
|
if onnx_ai_opset_version and onnx_ai_opset_version != since_version:
|
|
function_test_name += f"_ver{onnx_ai_opset_version}"
|
|
graph = onnx.helper.make_graph(
|
|
nodes=expanded_function_nodes,
|
|
name=function_test_name,
|
|
inputs=inputs_vi,
|
|
outputs=outputs_vi,
|
|
)
|
|
model = _make_test_model_gen_version(graph, **kwargs)
|
|
_NodeTestCases.append(
|
|
TestCase(
|
|
name=function_test_name,
|
|
model_name=function_test_name,
|
|
url=None,
|
|
model_dir=None,
|
|
model=model,
|
|
data_sets=[(inputs, outputs)],
|
|
kind="node",
|
|
rtol=1e-3,
|
|
atol=1e-7,
|
|
)
|
|
)
|
|
|
|
|
|
def collect_testcases(op_type: str) -> list[TestCase]:
|
|
"""Collect node test cases"""
|
|
# only keep those tests related to this operator
|
|
global _TargetOpType # noqa: PLW0603
|
|
_TargetOpType = op_type
|
|
|
|
import_recursive(sys.modules[__name__])
|
|
return _NodeTestCases
|
|
|
|
|
|
def collect_diff_testcases() -> list[TestCase]:
|
|
"""Collect node test cases which are different from the main branch"""
|
|
global _DiffOpTypes # noqa: PLW0603
|
|
_DiffOpTypes = get_diff_op_types()
|
|
|
|
import_recursive(sys.modules[__name__])
|
|
return _NodeTestCases
|
|
|
|
|
|
def get_diff_op_types():
|
|
cwd_path = Path.cwd()
|
|
# Resolve the upstream main branch from the canonical onnx/onnx repository
|
|
# to avoid depending on local branch or remote naming conventions.
|
|
upstream_url = "https://github.com/onnx/onnx.git"
|
|
ls_remote = subprocess.run(
|
|
["git", "ls-remote", upstream_url, "refs/heads/main"],
|
|
cwd=cwd_path,
|
|
capture_output=True,
|
|
check=True,
|
|
)
|
|
upstream_main_hash = ls_remote.stdout.split()[0].decode("utf-8")
|
|
# Fetch the upstream main commit so merge-base works even if the
|
|
# local repo hasn't fetched recently.
|
|
subprocess.run(
|
|
["git", "fetch", upstream_url, upstream_main_hash],
|
|
cwd=cwd_path,
|
|
capture_output=True,
|
|
check=True,
|
|
)
|
|
# Find the fork point from upstream main
|
|
merge_base = subprocess.run(
|
|
["git", "merge-base", "HEAD", upstream_main_hash],
|
|
cwd=cwd_path,
|
|
capture_output=True,
|
|
check=True,
|
|
)
|
|
base_commit = merge_base.stdout.strip().decode("utf-8")
|
|
# obtain list of added or modified files since the fork point
|
|
result = subprocess.run(
|
|
["git", "diff", "--name-only", "--diff-filter=AM", base_commit, "HEAD"],
|
|
cwd=cwd_path,
|
|
capture_output=True,
|
|
check=True,
|
|
)
|
|
diff_list = result.stdout.split()
|
|
changed_op_types = []
|
|
for file in diff_list:
|
|
file_name = file.decode("utf-8")
|
|
if file_name.startswith("onnx/backend/test/case/node/") and file_name.endswith(
|
|
".py"
|
|
):
|
|
changed_op_types.append(
|
|
file_name.split("/")[-1].replace(".py", "").rstrip("_")
|
|
)
|
|
return changed_op_types
|