Skip to content

Commit

Permalink
feat: support setting the pythonpath in configuration YAMLs
Browse files Browse the repository at this point in the history
  • Loading branch information
rpoisel committed Jul 30, 2024
1 parent 8599a03 commit a1101cf
Show file tree
Hide file tree
Showing 7 changed files with 95 additions and 5 deletions.
3 changes: 3 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ New Features in 24.0
- The pyproject.toml gained a config for `ruff <https://github.com/astral-sh/ruff>`_.
- ``setuptools_scm`` is now used to generate a version file.
- labgrid-client console will fallback to telnet if microcom is not available.
- The ``pythonpath`` key allows specifying additional directories for the
Python search path (sys.path). This helps include modules/packages not
in the standard library or current directory.


Bug fixes in 24.0
Expand Down
2 changes: 2 additions & 0 deletions doc/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3555,6 +3555,8 @@ The skeleton for an environment consists of:
imports:
- <import.py>
- <python module>
pythonpath:
- <absolute or relative path>
If you have a single target in your environment, name it "main", as the
``get_target`` function defaults to "main".
Expand Down
8 changes: 8 additions & 0 deletions labgrid/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@
This class encapsulates access functions to the environment configuration
"""
from typing import List
import os
from yaml import YAMLError
import attr

from .exceptions import NoConfigFoundError, InvalidConfigError
from .util.yaml import load, resolve_templates


@attr.s(eq=False)
class Config:
filename = attr.ib(validator=attr.validators.instance_of(str))
Expand Down Expand Up @@ -258,6 +260,12 @@ def get_imports(self):

return imports

def get_pythonpath(self) -> List[str]:
configured_pythonpath = self.data.get('pythonpath', [])
if not isinstance(configured_pythonpath, list):
raise KeyError("pythonpath needs to be a list")
return [self.resolve_path(p) for p in configured_pythonpath]

def get_paths(self):
"""Helper function that returns the subdict of all paths
Expand Down
14 changes: 9 additions & 5 deletions labgrid/environment.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import os
from typing import Optional
from importlib.machinery import SourceFileLoader
import importlib.util
import os
import sys
import attr

from .target import Target
Expand All @@ -19,11 +22,12 @@ def __attrs_post_init__(self):

self.config = Config(self.config_file)

for user_import in self.config.get_imports():
import importlib.util
from importlib.machinery import SourceFileLoader
import sys
# we want configured paths to appear at the beginning of the
# PYTHONPATH, so they need to be inserted in reverse order
for user_pythonpath in reversed(self.config.get_pythonpath()):
sys.path.insert(0, user_pythonpath)

for user_import in self.config.get_imports():
if user_import.endswith('.py'):
module_name = os.path.basename(user_import)[:-3]
loader = SourceFileLoader(module_name, user_import)
Expand Down
23 changes: 23 additions & 0 deletions man/labgrid-device-config.5
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,29 @@ imports:
.fi
.UNINDENT
.UNINDENT
.SH PYTHONPATH
.sp
The \fBpythonpath\fP key allows specifying additional directories to be added
to the Python search path (sys.path). This is particularly useful for
including directories that contain Python modules and packages which are
not installed in the standard library or not in the current directory.
Relative paths are resolved relative to the configuration file\(aqs location.
Specifying this option enables the environment to locate and import modules
that are not in the default Python path.
.SS PYTHONPATH EXAMPLE
.sp
Extend the Python search path to include the \fIlibs\fP directory:
.INDENT 0.0
.INDENT 3.5
.sp
.nf
.ft C
pythonpath:
\- libs
.ft P
.fi
.UNINDENT
.UNINDENT
.SH EXAMPLES
.sp
A sample configuration with one \fImain\fP target, accessible via SerialPort
Expand Down
19 changes: 19 additions & 0 deletions man/labgrid-device-config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,25 @@ Import a local `myfunctions.py` file:
imports:
- myfunctions.py

PYTHONPATH
----------
The ``pythonpath`` key allows specifying additional directories to be added
to the Python search path (sys.path). This is particularly useful for
including directories that contain Python modules and packages which are
not installed in the standard library or not in the current directory.
Relative paths are resolved relative to the configuration file's location.
Specifying this option enables the environment to locate and import modules
that are not in the default Python path.

PYTHONPATH EXAMPLE
~~~~~~~~~~~~~~~~~~
Extend the Python search path to include the `libs` directory:

::

pythonpath:
- libs

EXAMPLES
--------
A sample configuration with one `main` target, accessible via SerialPort
Expand Down
31 changes: 31 additions & 0 deletions tests/test_environment.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from pathlib import Path
import pytest
import warnings

Expand Down Expand Up @@ -81,6 +82,36 @@ class Test:
t = myimport.Test()
assert (isinstance(t, myimport.Test))

def test_env_pythonpath_yaml(self, tmp_path: Path):
config = tmp_path / "configs" / "config.yaml"
config.parent.mkdir(parents=True, exist_ok=True)
config.write_text(f"""---
target:
main:
drivers: {{}}
pythonpath:
- ../modules
imports:
- ../sample.py
""")
sample_py = tmp_path / "sample.py"
sample_py.write_text("""
from some_module import Foo
""")
some_module = tmp_path / "modules" / "some_module.py"
some_module.parent.mkdir(parents=True, exist_ok=True)
some_module.write_text("""
class Foo:
pass
""")
e = Environment(str(config))
assert isinstance(e, Environment)
import sys
assert 'some_module' in sys.modules
import some_module
f = some_module.Foo()
assert isinstance(f, some_module.Foo)

def test_create_named_resources(self, tmpdir):
p = tmpdir.join("config.yaml")
p.write(
Expand Down

0 comments on commit a1101cf

Please sign in to comment.