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

Split Parsing and Config Containers #1

Merged
merged 9 commits into from
Mar 1, 2023
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
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,4 +156,16 @@ sensors: # Various sensors
parent_link: front_gps_mount
xyz: [0, 0, 0]
rpy: [0, 0, 0]
```
```

# Unit Tests
All unit tests are written using **PyTest** following the [Good Integration Practices](https://docs.pytest.org/en/6.2.x/goodpractices.html#goodpractices).

Therefore, `clearpath_config_test` is a package that mirrors the `clearpath_config` package structure. Each file from `clearpath_config` that is to be tested should have a corresponding file with the same name and the suffix `_test.py`.

To run the tests:
```bash
cd .../clearpath_config
python3 -m pytest
```
> **PyTest** will automatically search for the suffix `_test` throughout the current directory and run the tests.
57 changes: 0 additions & 57 deletions clearpath_config/base/config.py

This file was deleted.

77 changes: 0 additions & 77 deletions clearpath_config/base/keys.py

This file was deleted.

115 changes: 11 additions & 104 deletions clearpath_config/clearpath_config.py
Original file line number Diff line number Diff line change
@@ -1,116 +1,23 @@
from clearpath_config.base.config import BaseConfig
from clearpath_config.base.keys import Keys
from clearpath_config.parser import ConfigParser
from clearpath_config.system import SystemConfig, Host
from clearpath_config.utils import read_yaml, write_yaml

# HostsConfig
# - these are the hosts that are involved in this system
class HostsConfig(BaseConfig):

def __init__(self, config) -> None:
super().__init__(name=Keys.HOSTS, config=config)
self.required_keys = [Keys.PLATFORM]
self.optional_keys = [Keys.ONBOARD, Keys.REMOTE]

# Self:
# - the hostname of the computer this config was made/is running on.
def get_self(self) -> str:
_self = self.get_key(Keys.SELF)
assert Keys.is_valid(Keys.SELF, _self)
return _self

def set_self(self, _self) -> bool:
return self.set_key(Keys.PLATFORM, _self)

# Platform:
# - the main computer for tis system (i.e. the robot's computer)
def get_platform(self) -> tuple:
platform = self.get_key(Keys.PLATFORM)
assert Keys.is_valid(Keys.PLATFORM, platform)
hostname, ip = list(platform.items())[0]
return hostname, ip

def get_platform_hostname(self) -> str:
hostname, _ = self.get_platform()
return hostname

def get_platform_ip(self) -> str:
_, ip = self.get_platform()
return ip

def set_platform(self, platform: tuple) -> bool:
entry = {platform[0]: platform[2]}
return self.set_key(Keys.PLATFORM, entry)

def set_platform_hostname(self, hostname: str) -> bool:
ip = self.get_platform_ip()
entry = {hostname: ip}
return self.set_key(Keys.PLATFORM, entry)

def set_platform_ip(self, ip: str) -> bool:
hostname = self.get_platform_hostname()
entry = {hostname: ip}
return self.set_key(Keys.PLATFORM, entry)

# Onboard:
# - these are additional on-board computer
def get_onboard(self) -> list:
onboard = self.get_key(Keys.ONBOARD)
if onboard == None:
return []
else:
return onboard

class SystemConfig(BaseConfig):

def __init__(self, config) -> None:
super().__init__(name=Keys.SYSTEM, config=config)
self.required_keys = [Keys.SELF, Keys.HOSTS]
self.hosts = HostsConfig(self.get_key(Keys.HOSTS))

def update_config(self):
self.set_key(Keys.HOSTS, self.hosts)


class PlatformConfig():
pass

class MountsConfig():
pass

class SensorsConfig():
pass


# ClearpathConfig:
# - top level configurator
# - contains
class ClearpathConfig(BaseConfig):
class ClearpathConfig():

def __init__(self, config) -> None:
super().__init__(name=Keys.SYSTEM, config=config)
def __init__(self, config: dict = None) -> None:
self.config = config
self.version = 0
# Sub-Level Configs
self.system = SystemConfig(self.get_key(Keys.SYSTEM))
self.system = ConfigParser.load_system_config(config)
#self.platform = PlatformConfig()
#self.mounts = MountsConfig()
#self.sensors = SensorsConfig()

def main():
test_config = "/home/lcamero/Scripts/clearpath_config/test_config.yaml"
clearpath_config = ClearpathConfig(config=read_yaml(test_config))
print("Platform:", clearpath_config.system.hosts.get_platform())
print(" hostname: ", clearpath_config.system.hosts.get_platform_hostname())
print(" ip: ", clearpath_config.system.hosts.get_platform_ip())
print()
clearpath_config.system.hosts.set_platform_hostname("TEST_HOST")
print("Platform:", clearpath_config.system.hosts.get_platform())
print(" hostname: ", clearpath_config.system.hosts.get_platform_hostname())
print(" ip: ", clearpath_config.system.hosts.get_platform_ip())
print()
clearpath_config.system.hosts.set_platform_ip("TEST_IP")
print("Platform:", clearpath_config.system.hosts.get_platform())
print(" hostname: ", clearpath_config.system.hosts.get_platform_hostname())
print(" ip: ", clearpath_config.system.hosts.get_platform_ip())
print()
def load_config(self, config: dict):
self.config = config

main()
def export_config(self) -> dict:
config = {}
return config
97 changes: 97 additions & 0 deletions clearpath_config/common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import re

# Hostname
# - hostname class
class Hostname():

def __init__(self, hostname: str = "hostname") -> None:
self.assert_valid(hostname)
self.hostname = hostname

def __eq__(self, other: object) -> bool:
if isinstance(other, str):
return self.hostname == other
elif isinstance(other, Hostname):
return self.hostname == other.hostname
return False

def __str__(self) -> str:
return self.hostname


@staticmethod
def is_valid(hostname: str):
# Max 253 ASCII Characters
if len(hostname) > 253:
return False
# No Trailing Dots
# - not excatly a standard but generally undefined behaviour and should be avoided
if hostname[-1] == ".":
return False
# Only [A-Z][0-9] and '-' Allowed
# - cannot end or start with a hyphen ('-')
allowed = re.compile(r"(?!-)[A-Z\d-]{1,63}(?<!-)$", re.IGNORECASE)
return all(allowed.match(x) for x in hostname.split("."))

@staticmethod
def assert_valid(hostname: str):
assert isinstance(hostname, str), "Hostname '%s' must be of type 'str'" % hostname
# Max 253 ASCII Characters
assert len(hostname) < 254, "Hostname '%s' exceeds 253 ASCII character limit." % hostname
# No Trailing Dots
assert hostname[-1] != ".", "Hostname '%s' should not end with a ('.') period." % hostname
# Only [A-Z][0-9] and '-' Allowed
allowed = re.compile("(?!-)[A-Z\d-]{1,63}(?<!-)$", re.IGNORECASE)
assert all(allowed.match(x) for x in hostname.split(".")), "Hostname '%s' cannot contain characters other than [A-Z][0-9] and hypens ('-')." % hostname


# IP
# - ip class
class IP():

def __init__(self, ip: str = "0.0.0.0") -> None:
self.assert_valid(ip)
self.ip_str = ip

def __eq__(self, other: object) -> bool:
if isinstance(other, str):
return self.ip_str == other
elif isinstance(other, IP):
return self.ip_str == other.ip_str
else:
return False

def __str__(self) -> str:
return self.ip_str

@staticmethod
def is_valid(ip: str) -> bool:
# Must be String
if not isinstance(ip, str):
return False
# Must have Four Fields Delimited by '.'
fields = ip.split(".")
if not len(fields) == 4:
return False
# All Fields must be Integers and 8 Bit Wide
for field in fields:
if not field.isdecimal():
return False
field_int = int(field)
if not (0 <= field_int < 256):
return False
return True

@staticmethod
def assert_valid(ip: str) -> None:
# Must be String
assert isinstance(ip, str), "IP '%s' must be string" % ip
# Must have Four Fields Delimited by '.'
fields = ip.split(".")
assert len(fields) == 4, "IP '%s' must have four entries" % ip
for field in fields:
# Fields Must be Integer
assert field.isdecimal(), "IP '%s' entries must be integers" % ip
# Fields Must be 8-Bits Wide
field_int = int(field)
assert 0 <= field_int < 256, "IP '%s' entries must in range 0 to 255" % ip
Loading