Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions py/src/clippy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@
logger.setLevel(cfg.get("loglevel"))


def _register_classes(generated_classes: AnyDict) -> None:
"""Expose backend-generated classes in the clippy namespace."""
globals().update(generated_classes)


def load_classes():
"""For each listed backend, import the module of the same name. The
backend should expose two functions: a classes() function that returns
Expand All @@ -40,12 +45,7 @@ def load_classes():
for backend in cfg.get("backends"):
b = importlib.import_module(f".backends.{backend}", package=__name__)
setattr(cfg, backend, b.get_cfg())
for name, c in b.classes().items():
# backend_config = importlib.import_module(f".backends.{name}.config.{name}_config")
# for k, v in backend_config.items():
# cfg._set(k, v)

globals()[name] = c
_register_classes(b.classes())


load_classes()
29 changes: 19 additions & 10 deletions py/src/clippy/backends/fs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,28 +60,37 @@ def get_cfg() -> CLIPPY_CONFIG:
return cfg


def classes() -> dict[str, Any]:
def classes(paths: list[str] | None = None) -> dict[str, Any]:
"""This is a mandatory function for all backends. It returns a dictionary of class name
to the actual Class for all classes supported by the backend."""
from ... import cfg as topcfg # pylint: disable=import-outside-toplevel

paths = cfg.get("fs_backend_paths")
if paths is None:
paths = cfg.get("fs_backend_paths")

_classes = {}
# iterate over all (filesystem) paths and for those that aren't explicitly excluded,
# create the class based on the directory name and add methods based on the executables
# within the directory.
for path in paths:
files = os.scandir(path)
for f in files:
if f.name in cfg.get("fs_exclude_paths"):
continue
p = pathlib.Path(path, f)
if os.path.isdir(p):
_cls = _create_class(f.name, path, topcfg)
_classes[f.name] = _cls
with os.scandir(path) as files:
for f in files:
if f.name in cfg.get("fs_exclude_paths"):
continue
p = pathlib.Path(path, f)
if os.path.isdir(p):
_cls = _create_class(f.name, path, topcfg)
_classes[f.name] = _cls
return _classes


def add_backend_path(path: str) -> None:
"""Generate and expose classes found in one filesystem backend path."""
from ... import _register_classes # pylint: disable=import-outside-toplevel

_register_classes(classes(paths=[path]))


def _create_class(name: str, path: str, topcfg: CLIPPY_CONFIG):
"""Given a name, a path, and a master configuration, create
a class with the given name, and add methods based on the
Expand Down
2 changes: 1 addition & 1 deletion py/src/clippy/backends/fs/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
# backend path for executables, in addition to the CLIPPY_BACKEND_PATH environment variable. Add to it here.
"fs_backend_paths": (
None,
[x.strip() for x in os.environ.get("CLIPPY_BACKEND_PATH", "").split(":")],
[path for entry in os.environ.get("CLIPPY_BACKEND_PATH", "").split(":") if (path := entry.strip())],
),
# exclude these directories from being made as classes.
"fs_exclude_paths": ("CLIPPY_FS_EXCLUDE_PATHS", ["CMakeFiles"]),
Expand Down
38 changes: 38 additions & 0 deletions test/test_clippy.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import pytest
import sys
import os
import subprocess

sys.path.append("src")

Expand All @@ -8,6 +10,7 @@
from clippy.error import ClippyValidationError, ClippyInvalidSelectorError
from clippy.backends.fs.execution import NonZeroReturnCodeError
import logging
from clippy.backends import fs

clippy.logger.setLevel(logging.WARN)
logging.getLogger().setLevel(logging.WARN)
Expand Down Expand Up @@ -45,6 +48,41 @@ def test_imports():
assert "ExampleBag" in clippy.__dict__


def test_import_without_backend_path():
env = os.environ.copy()
env.pop("CLIPPY_BACKEND_PATH", None)

result = subprocess.run(
[sys.executable, "-c", "import clippy"],
check=False,
capture_output=True,
env=env,
text=True,
)

assert result.returncode == 0, result.stderr


def test_add_backend_path_scans_only_explicit_path(monkeypatch):
class AddedClass:
pass

calls = []

def classes(*, paths=None):
calls.append(paths)
return {"AddedClass": AddedClass}

monkeypatch.setattr(fs, "classes", classes)
monkeypatch.delattr(clippy, "AddedClass", raising=False)

fs.add_backend_path("/foo/bar")
fs.add_backend_path("/foo/bar")

assert calls == [["/foo/bar"], ["/foo/bar"]]
assert clippy.AddedClass is AddedClass


def test_bag(examplebag):
examplebag.insert(41)
assert examplebag.size() == 1
Expand Down
Loading