Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: meld LineInterface into BaseModel #20

Merged
merged 1 commit into from
Sep 20, 2024
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
43 changes: 3 additions & 40 deletions slurmutils/editors/cgroupconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,7 @@
from typing import Union

from ..models import CgroupConfig
from ..models.option import CgroupConfigOptionSet
from .editor import (
clean,
dumper,
loader,
marshall_content,
parse_line,
)
from .editor import dumper, loader

_logger = logging.getLogger("slurmutils")

Expand All @@ -43,7 +36,7 @@ def load(file: Union[str, os.PathLike]) -> CgroupConfig:

def loads(content: str) -> CgroupConfig:
"""Load `cgroup.conf` data model from string."""
return _parse(content)
return CgroupConfig.from_str(content)


@dumper
Expand All @@ -54,7 +47,7 @@ def dump(config: CgroupConfig, file: Union[str, os.PathLike]) -> None:

def dumps(config: CgroupConfig) -> str:
"""Dump `cgroup.conf` data model into a string."""
return _marshall(config)
return str(config)


@contextmanager
Expand All @@ -73,33 +66,3 @@ def edit(file: Union[str, os.PathLike]) -> CgroupConfig:

yield config
dump(config, file)


def _parse(content: str) -> CgroupConfig:
"""Parse contents of `cgroup.conf`.

Args:
content: Contents of `cgroup.conf`.
"""
data = {}
lines = content.splitlines()
for index, line in enumerate(lines):
config = clean(line)
if config is None:
_logger.debug("ignoring line %s at index %s in cgroup.conf", line, index)
continue

data.update(parse_line(CgroupConfigOptionSet, config))

return CgroupConfig.from_dict(data)


def _marshall(config: CgroupConfig) -> str:
"""Marshall `cgroup.conf` data model back into cgroup.conf format.

Args:
config: `cgroup.conf` data model to marshall.
"""
result = []
result.extend(marshall_content(CgroupConfigOptionSet, config.dict()))
return "\n".join(result)
61 changes: 0 additions & 61 deletions slurmutils/editors/editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,73 +15,12 @@
"""Base methods for Slurm workload manager configuration file editors."""

import logging
import shlex
from functools import wraps
from os import path
from typing import Any, Dict, List, Optional

from ..exceptions import EditorError

_logger = logging.getLogger("slurmutils")


def clean(line: str) -> Optional[str]:
"""Clean line before further processing.

Returns:
Line with inline comments removed. `None` if line is a comment.
"""
return cleaned if (cleaned := line.split("#", maxsplit=1)[0]) != "" else None


def parse_line(options, line: str) -> Dict[str, Any]:
"""Parse configuration line.

Args:
options: Available options for line.
line: Configuration line to parse.
"""
data = {}
opts = shlex.split(line) # Use `shlex.split(...)` to preserve quotation strings.
for opt in opts:
k, v = opt.split("=", maxsplit=1)
if not hasattr(options, k):
raise EditorError(
(
f"unable to parse configuration option {k}. "
+ f"valid configuration options are {list(options.keys())}"
)
)

parse = getattr(options, k).parser
data[k] = parse(v) if parse else v

return data


def marshall_content(options, line: Dict[str, Any]) -> List[str]:
"""Marshall data model content back into configuration line.

Args:
options: Available options for line.
line: Data model to marshall into line.
"""
result = []
for k, v in line.items():
if not hasattr(options, k):
raise EditorError(
(
f"unable to marshall configuration option {k}. "
+ f"valid configuration options are {[option.name for option in options]}"
)
)

marshall = getattr(options, k).marshaller
result.append(f"{k}={marshall(v) if marshall else v}")

return result


def loader(func):
"""Wrap function that loads configuration data from file."""

Expand Down
106 changes: 4 additions & 102 deletions slurmutils/editors/slurmconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,8 @@
from pathlib import Path
from typing import Union

from ..models import DownNodes, FrontendNode, Node, NodeSet, Partition, SlurmConfig
from ..models.option import SlurmConfigOptionSet
from .editor import (
clean,
dumper,
loader,
marshall_content,
parse_line,
)
from ..models import SlurmConfig
from .editor import dumper, loader

_logger = logging.getLogger("slurmutils")

Expand All @@ -43,7 +36,7 @@ def load(file: Union[str, os.PathLike]) -> SlurmConfig:

def loads(content: str) -> SlurmConfig:
"""Load `slurm.conf` data model from string."""
return _parse(content)
return SlurmConfig.from_str(content)


@dumper
Expand All @@ -54,7 +47,7 @@ def dump(config: SlurmConfig, file: Union[str, os.PathLike]) -> None:

def dumps(config: SlurmConfig) -> str:
"""Dump `slurm.conf` data model into a string."""
return _marshall(config)
return str(config)


@contextmanager
Expand All @@ -73,94 +66,3 @@ def edit(file: Union[str, os.PathLike]) -> SlurmConfig:

yield config
dump(config, file)


def _parse(content: str) -> SlurmConfig:
"""Parse contents of `slurm.conf`.

Args:
content: Contents of `slurm.conf`.
"""
data = {}
lines = content.splitlines()
for index, line in enumerate(lines):
config = clean(line)
if config is None:
_logger.debug("ignoring line %s at index %s in slurm.conf", line, index)
continue

if config.startswith("Include"):
_, v = config.split(maxsplit=1)
data["Include"] = data.get("Include", []) + [v]
elif config.startswith("SlurmctldHost"):
_, v = config.split("=", maxsplit=1)
data["SlurmctldHost"] = data.get("SlurmctldHost", []) + [v]
elif config.startswith("NodeName"):
nodes = data.get("Nodes", {})
nodes.update(Node.from_str(config).dict())
data["Nodes"] = nodes
elif config.startswith("DownNodes"):
data["DownNodes"] = data.get("DownNodes", []) + [DownNodes.from_str(config).dict()]
elif config.startswith("FrontendNode"):
frontend_nodes = data.get("FrontendNodes", {})
frontend_nodes.update(FrontendNode.from_str(config).dict())
data["FrontendNodes"] = frontend_nodes
elif config.startswith("NodeSet"):
node_sets = data.get("NodeSets", {})
node_sets.update(NodeSet.from_str(config).dict())
data["NodeSets"] = node_sets
elif config.startswith("PartitionName"):
partitions = data.get("Partitions", {})
partitions.update(Partition.from_str(config).dict())
data["Partitions"] = partitions
else:
data.update(parse_line(SlurmConfigOptionSet, config))

return SlurmConfig.from_dict(data)


def _marshall(config: SlurmConfig) -> str:
"""Marshall `slurm.conf` data model back into slurm.conf format.

Args:
config: `slurm.conf` data model to marshall.
"""
result = []
data = config.dict()
include = data.pop("Include", None)
slurmctld_host = data.pop("SlurmctldHost", None)
nodes = data.pop("Nodes", {})
down_nodes = data.pop("DownNodes", [])
frontend_nodes = data.pop("FrontendNodes", {})
node_sets = data.pop("NodeSets", {})
partitions = data.pop("Partitions", {})

if include:
result.extend([f"Include {i}" for i in include])

if slurmctld_host:
result.extend([f"SlurmctldHost={host}" for host in slurmctld_host])

result.extend(marshall_content(SlurmConfigOptionSet, data))

if nodes:
for k, v in nodes.items():
result.append(str(Node(NodeName=k, **v)))

if down_nodes:
for entry in down_nodes:
result.append(str(DownNodes(**entry)))

if frontend_nodes:
for k, v in frontend_nodes.items():
result.append(str(FrontendNode(FrontendName=k, **v)))

if node_sets:
for k, v in node_sets.items():
result.append(str(NodeSet(NodeSet=k, **v)))

if partitions:
for k, v in partitions.items():
result.append(str(Partition(PartitionName=k, **v)))

return "\n".join(result)
43 changes: 3 additions & 40 deletions slurmutils/editors/slurmdbdconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,7 @@

from slurmutils.models import SlurmdbdConfig

from ..models.option import SlurmdbdConfigOptionSet
from .editor import (
clean,
dumper,
loader,
marshall_content,
parse_line,
)
from .editor import dumper, loader

_logger = logging.getLogger("slurmutils")

Expand All @@ -44,7 +37,7 @@ def load(file: Union[str, os.PathLike]) -> SlurmdbdConfig:

def loads(content: str) -> SlurmdbdConfig:
"""Load `slurmdbd.conf` data model from string."""
return _parse(content)
return SlurmdbdConfig.from_str(content)


@dumper
Expand All @@ -55,7 +48,7 @@ def dump(config: SlurmdbdConfig, file: Union[str, os.PathLike]) -> None:

def dumps(config: SlurmdbdConfig) -> str:
"""Dump `slurmdbd.conf` data model into a string."""
return _marshall(config)
return str(config)


@contextmanager
Expand All @@ -74,33 +67,3 @@ def edit(file: Union[str, os.PathLike]) -> SlurmdbdConfig:

yield config
dump(config, file)


def _parse(content: str) -> SlurmdbdConfig:
"""Parse contents of `slurmdbd.conf`.

Args:
content: Contents of `slurmdbd.conf`.
"""
data = {}
lines = content.splitlines()
for index, line in enumerate(lines):
config = clean(line)
if config is None:
_logger.debug("ignoring line %s at index %s in slurmdbd.conf", line, index)
continue

data.update(parse_line(SlurmdbdConfigOptionSet, config))

return SlurmdbdConfig.from_dict(data)


def _marshall(config: SlurmdbdConfig) -> str:
"""Marshall `slurmdbd.conf` data model back into slurmdbd.conf format.

Args:
config: `slurmdbd.conf` data model to marshall.
"""
result = []
result.extend(marshall_content(SlurmdbdConfigOptionSet, config.dict()))
return "\n".join(result)
22 changes: 21 additions & 1 deletion slurmutils/models/cgroup.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

"""Data models for `cgroup.conf` configuration file."""

from .model import BaseModel, format_key, generate_descriptors
from .model import BaseModel, clean, format_key, generate_descriptors, marshall_content, parse_line
from .option import CgroupConfigOptionSet


Expand All @@ -24,6 +24,26 @@ class CgroupConfig(BaseModel):
def __init__(self, **kwargs) -> None:
super().__init__(CgroupConfigOptionSet, **kwargs)

@classmethod
def from_str(cls, content: str) -> "CgroupConfig":
"""Construct SlurmdbdConfig data model from slurmdbd.conf format."""
data = {}
lines = content.splitlines()
for index, line in enumerate(lines):
config = clean(line)
if config is None:
continue

data.update(parse_line(CgroupConfigOptionSet, config))

return CgroupConfig.from_dict(data)

def __str__(self) -> str:
"""Return CgroupConfig data model in cgroup.conf format."""
result = []
result.extend(marshall_content(CgroupConfigOptionSet, self.dict()))
return "\n".join(result)


for opt in CgroupConfigOptionSet.keys():
setattr(CgroupConfig, format_key(opt), property(*generate_descriptors(opt)))
Loading