Repository: pytest-dev/pytest
File: src/_pytest/main.py (Python)

Code before:
    def genitems(self, node: nodes.Item | nodes.Collector) -> Iterator[nodes.Item]:
        import inspect

        from _pytest.python import Class
        from _pytest.python import Function
        from _pytest.python import Module

        self.trace("genitems", node)
        if isinstance(node, nodes.Item):
            node.ihook.pytest_itemcollected(item=node)
            if not self.config.getini("collect_imported_tests"):
                if isinstance(node.parent, Module) and isinstance(node, Function):
                    if inspect.isfunction(node._getobj()):
                        fn_defined_at = node._getobj().__module__
                        in_module = node.parent._getobj().__name__
                        if fn_defined_at != in_module:
                            return
            yield node
        else:
            assert isinstance(node, nodes.Collector)
            keepduplicates = self.config.getoption("keepduplicates")
            # For backward compat, dedup only applies to files.
            handle_dupes = not (keepduplicates and isinstance(node, nodes.File))
            rep, duplicate = self._collect_one_node(node, handle_dupes)

            if not self.config.getini("collect_imported_tests"):
                for subnode in rep.result:
                    if isinstance(subnode, Class) and isinstance(
                        subnode.parent, Module
                    ):
                        if inspect.isclass(subnode._getobj()):
                            class_defined_at = subnode._getobj().__module__
                            in_module = subnode.parent._getobj().__name__
                            if class_defined_at != in_module:
                                rep.result.remove(subnode)

            if duplicate and not keepduplicates:
                return
            if rep.passed:
                for subnode in rep.result:
                    yield from self.genitems(subnode)
            if not duplicate:
                node.ihook.pytest_collectreport(report=rep)


def search_pypath(module_name: str) -> str | None:
    """Search sys.path for the given a dotted module name, and return its file
    system path if found."""
    try:
        spec = importlib.util.find_spec(module_name)
    # AttributeError: looks like package module, but actually filename

Review comment (suggestion):
Same comment here, this logic should be moved to `_pytest.python` instead of `main`.

Code after:
    def genitems(self, node: nodes.Item | nodes.Collector) -> Iterator[nodes.Item]:
        self.trace("genitems", node)
        if isinstance(node, nodes.Item):
            node.ihook.pytest_itemcollected(item=node)
            yield node
        else:
            assert isinstance(node, nodes.Collector)
            keepduplicates = self.config.getoption("keepduplicates")
            # For backward compat, dedup only applies to files.
            handle_dupes = not (keepduplicates and isinstance(node, nodes.File))
            rep, duplicate = self._collect_one_node(node, handle_dupes)
            if duplicate and not keepduplicates:
                return
            if rep.passed:
                for subnode in rep.result:
                    yield from self.genitems(subnode)
            if not duplicate:
                node.ihook.pytest_collectreport(report=rep)


def search_pypath(module_name: str) -> str | None:
    """Search sys.path for the given a dotted module name, and return its file
    system path if found."""
    try:
        spec = importlib.util.find_spec(module_name)
    # AttributeError: looks like package module, but actually filename
    # ImportError: module does not exist
    # ValueError: not a module name
    except (AttributeError, ImportError, ValueError):
        return None
    if spec is None or spec.origin is None or spec.origin == "namespace":
        return None
    elif spec.submodule_search_locations:
        return os.path.dirname(spec.origin)
    else:
        return spec.origin


@dataclasses.dataclass(frozen=True)
class CollectionArgument:
    """A resolved collection argument."""

    path: Path
    parts: Sequence[str]
    module_name: str | None


def resolve_collection_argument(
    invocation_path: Path, arg: str, *, as_pypath: bool = False
) -> CollectionArgument:
    """Parse path arguments optionally containing selection parts and return (fspath, names).