This commit is contained in:
Evgeny Zinoviev 2023-06-08 02:26:28 +03:00
parent 3ae1c3b5a7
commit eb825f62ee
42 changed files with 176 additions and 136 deletions

View File

@ -65,7 +65,7 @@ class ESP32CameraNodeServer(MediaNodeServer):
if __name__ == '__main__':
config.load('camera_node')
config.load_app('camera_node')
recorder_kwargs = {}
camera_type = CameraType(config['camera']['type'])

View File

@ -76,7 +76,7 @@ class ESP32CamCaptureDiffNode:
if __name__ == '__main__':
config.load('esp32cam_capture_diff_node')
config.load_app('esp32cam_capture_diff_node')
loop = asyncio.get_event_loop()
ESP32CamCaptureDiffNode()

View File

@ -13,7 +13,7 @@ if __name__ == '__main__':
if not os.getegid() == 0:
sys.exit('Must be run as root.')
config.load()
config.load_app()
try:
s = RelayServer(pinname=config.get('relayd.pin'),

View File

@ -1 +1,11 @@
from .config import ConfigStore, config, is_development_mode, setup_logging
from .config import (
Config,
config,
is_development_mode,
setup_logging,
app_config
)
from .validators import validate
from ._configs import (
LinuxBoardsConfig
)

View File

@ -0,0 +1,5 @@
from .config import ConfigUnit
class LinuxBoardsConfig(ConfigUnit):
NAME = 'linux_boards'

View File

@ -3,56 +3,116 @@ import yaml
import logging
import os
from . import validators
from os.path import join, isdir, isfile
from typing import Optional, Any, MutableMapping
from argparse import ArgumentParser
from ..util import parse_addr
import _validators as validators
_validators = {}
my_validators = {}
def _get_validator(name: str) -> Optional[callable]:
if hasattr(validators, f'{name}_validator'):
return getattr(validators, f'{name}_validator')
if name in _validators:
return _validators[name]
if name in my_validators:
return my_validators[name]
return None
def add_validator(name: str, f: callable):
_validators[name] = f
my_validators[name] = f
def _get_config_path(name: str) -> str:
formats = ['toml', 'yaml']
class ConfigUnit:
NAME = 'dumb'
dirnames = [
join(os.environ['HOME'], '.config', 'homekit'),
'/etc/homekit'
]
for dirname in dirnames:
if isdir(dirname):
for fmt in formats:
filename = join(dirname, f'{name}.{fmt}')
if isfile(filename):
return filename
raise IOError(f'config \'{name}\' not found')
class SingleConfig:
data: MutableMapping[str, Any]
def __init__(self):
@classmethod
def get_config_path(cls, name=None) -> str:
if name is None:
name = cls.NAME
if name is None:
raise ValueError('get_config_path: name is none')
dirnames = (
join(os.environ['HOME'], '.config', 'homekit'),
'/etc/homekit'
)
for dirname in dirnames:
if isdir(dirname):
for fmt in ('toml', 'yaml'):
filename = join(dirname, f'{name}.{fmt}')
if isfile(filename):
return filename
raise IOError(f'config \'{name}\' not found')
def __init__(self, name=None):
self.data = {}
def load(self,
name: Optional[str] = None,
use_cli=True,
parser: ArgumentParser = None):
if self.NAME != 'dumb':
self.load_from(self.get_config_path())
self.validate()
elif name is not None:
self.NAME = name
def load_from(self, path: str):
if path.endswith('.toml'):
self.data = toml.load(path)
elif path.endswith('.yaml'):
with open(path, 'r') as fd:
self.data = yaml.safe_load(fd)
def validate(self):
v = _get_validator(self.NAME)
v(self.data)
def __getitem__(self, key):
return self.data[key]
def __setitem__(self, key, value):
raise NotImplementedError('overwriting config values is prohibited')
def __contains__(self, key):
return key in self.data
def get(self, key: str, default=None):
cur = self.data
pts = key.split('.')
for i in range(len(pts)):
k = pts[i]
if i < len(pts)-1:
if k not in cur:
raise KeyError(f'key {k} not found')
else:
return cur[k] if k in cur else default
cur = self.data[k]
raise KeyError(f'option {key} not found')
def get_addr(self, key: str):
return parse_addr(self.get(key))
def items(self):
return self.data.items()
class Config:
app_name: Optional[str]
app_config: ConfigUnit
def __init__(self):
self.app_name = None
self.app_config = ConfigUnit()
def load_app(self,
name: Optional[str] = None,
use_cli=True,
parser: ArgumentParser = None):
self.app_name = name
if (name is None) and (not use_cli):
@ -86,75 +146,32 @@ class SingleConfig:
log_default_fmt = args.log_default_fmt
if not no_config and path is None:
path = _get_config_path(name)
path = ConfigUnit.get_config_path(name=name)
if no_config:
self.data = {}
else:
if path.endswith('.toml'):
self.data = toml.load(path)
elif path.endswith('.yaml'):
with open(path, 'r') as fd:
self.data = yaml.safe_load(fd)
if not no_config:
self.app_config.load_from(path)
if 'logging' in self:
if not log_file and 'file' in self['logging']:
log_file = self['logging']['file']
if log_default_fmt and 'default_fmt' in self['logging']:
log_default_fmt = self['logging']['default_fmt']
if 'logging' in self.app_config:
if not log_file and 'file' in self.app_config['logging']:
log_file = self.app_config['logging']['file']
if log_default_fmt and 'default_fmt' in self.app_config['logging']:
log_default_fmt = self.app_config['logging']['default_fmt']
setup_logging(log_verbose, log_file, log_default_fmt)
if use_cli:
return args
def __getitem__(self, key):
return self.data[key]
def __setitem__(self, key, value):
raise NotImplementedError('overwriting config values is prohibited')
def __contains__(self, key):
return key in self.data
def get(self, key: str, default=None):
cur = self.data
pts = key.split('.')
for i in range(len(pts)):
k = pts[i]
if i < len(pts)-1:
if k not in cur:
raise KeyError(f'key {k} not found')
else:
return cur[k] if k in cur else default
cur = self.data[k]
raise KeyError(f'option {key} not found')
def get_addr(self, key: str):
return parse_addr(self.get(key))
def items(self):
return self.data.items()
class Config:
app_name: Optional[str]
def __init__(self):
self.app_name = None
config = ConfigStore()
config = Config()
app_config = config.app_config
def is_development_mode() -> bool:
if 'HK_MODE' in os.environ and os.environ['HK_MODE'] == 'dev':
return True
return ('logging' in config) and ('verbose' in config['logging']) and (config['logging']['verbose'] is True)
return ('logging' in config.app_config) and ('verbose' in config.app_config['logging']) and (config.app_config['logging']['verbose'] is True)
def setup_logging(verbose=False, log_file=None, default_fmt=False):

View File

@ -0,0 +1,2 @@
from ._validators import *
from ._util import validate

View File

@ -0,0 +1,11 @@
import inspect
from cerberus import Validator, DocumentError
def validate(schema, data):
v = Validator(schema)
if not v.validate(data):
frame = inspect.currentframe().f_back
caller_name = frame.f_code.co_name
raise DocumentError(f'{caller_name}: failed to validate data: ' + v.errors)

View File

@ -1,23 +1,9 @@
import logging
import inspect
from cerberus import Validator, DocumentError
from ._util import validate
__all__ = [
'linux_boards_validator'
]
_logger = logging.getLogger(__name__)
def validate(schema, data):
v = Validator(schema)
if not v.validate(data):
frame = inspect.currentframe().f_back
caller_name = frame.f_code.co_name
raise DocumentError(f'{caller_name}: failed to validate data: ' + v.errors)
def linux_boards_validator(data) -> None:
validate({

View File

@ -44,7 +44,7 @@ flags_map = {
}
logger = logging.getLogger(__name__)
config.load('inverter_bot')
config.load_app('inverter_bot')
bot.initialize()
bot.lang.ru(

View File

@ -8,7 +8,7 @@ if __name__ == '__main__':
parser = ArgumentParser()
parser.add_argument('mode', type=str, choices=('sender', 'receiver'), nargs=1)
config.load('inverter_mqtt_util', parser=parser)
config.load_app('inverter_mqtt_util', parser=parser)
arg = parser.parse_args()
mqtt = MqttWrapper(clean_session=arg.mode[0] != 'receiver')

View File

@ -556,7 +556,7 @@ logger = logging.getLogger(__name__)
# --------------------
if __name__ == '__main__':
config.load('ipcam_server')
config.load_app('ipcam_server')
open_database()

View File

@ -23,7 +23,7 @@ if __name__ == '__main__':
parser.add_argument('--node-secret', type=str,
help='node admin password')
config.load('mqtt_util', parser=parser)
config.load_app('mqtt_util', parser=parser)
arg = parser.parse_args()
if (arg.switch_relay is not None or arg.node_secret is not None) and 'relay' not in arg.modules:

View File

@ -54,7 +54,7 @@ def main(mac: str,
if __name__ == '__main__':
config.load('openwrt_log_analyzer')
config.load_app('openwrt_log_analyzer')
for ap in config['openwrt_log_analyzer']['aps']:
state_file = config['simple_state']['file']
state_file = state_file.replace('.txt', f'-{ap}.txt')

View File

@ -46,7 +46,7 @@ if __name__ == '__main__':
parser.add_argument('--access-point', type=int, required=True,
help='access point number')
arg = config.load('openwrt_logger', parser=parser)
arg = config.load_app('openwrt_logger', parser=parser)
state = SimpleState(file=config['simple_state']['file'].replace('{ap}', str(arg.access_point)),
default={'seek': 0, 'size': 0})

View File

@ -41,7 +41,7 @@ from telegram.ext import (
)
logger = logging.getLogger(__name__)
config.load('polaris_kettle_bot')
config.load_app('polaris_kettle_bot')
primary_choices = (70, 80, 90, 100)
all_choices = range(

View File

@ -75,7 +75,7 @@ def main():
parser.add_argument('-t', '--temperature', dest='temp', type=int, default=tempmax,
choices=range(tempmin, tempmax+tempstep, tempstep))
arg = config.load('polaris_kettle_util', use_cli=True, parser=parser)
arg = config.load_app('polaris_kettle_util', use_cli=True, parser=parser)
if arg.mode == 'mqtt':
server = MqttServer()

View File

@ -16,7 +16,7 @@ from home.mqtt.module.temphum import MqttTemphumDataPayload
from home.mqtt.module.diagnostics import InitialDiagnosticsPayload, DiagnosticsPayload
config.load('pump_bot')
config.load_app('pump_bot')
mqtt: Optional[MqttWrapper] = None
mqtt_node: Optional[MqttNode] = None

View File

@ -13,7 +13,7 @@ from home.mqtt.module.relay import MqttRelayState
from home.mqtt.module.diagnostics import InitialDiagnosticsPayload, DiagnosticsPayload
config.load('pump_mqtt_bot')
config.load_app('pump_mqtt_bot')
bot.initialize()
bot.lang.ru(

View File

@ -11,7 +11,7 @@ from home.mqtt.module.relay import MqttRelayModule, MqttRelayState
from home.mqtt.module.diagnostics import InitialDiagnosticsPayload, DiagnosticsPayload
config.load('relay_mqtt_bot')
config.load_app('relay_mqtt_bot')
bot.initialize()
bot.lang.ru(

View File

@ -1,14 +1,14 @@
#!/usr/bin/env python3
from home import http
from home.config import config
from home.mqtt import MqttPayload, MqttWrapper, MqttNode
from home.mqtt import MqttPayload, MqttWrapper, MqttNode, MqttModule
from home.mqtt.module.relay import MqttRelayState, MqttRelayModule
from home.mqtt.module.diagnostics import InitialDiagnosticsPayload, DiagnosticsPayload
from typing import Optional
from typing import Optional, Union
mqtt: Optional[MqttWrapper] = None
mqtt_nodes: dict[str, MqttNode] = {}
relay_modules: dict[str, MqttRelayModule] = {}
relay_modules: dict[str, Union[MqttRelayModule, MqttModule]] = {}
relay_states: dict[str, MqttRelayState] = {}
@ -60,7 +60,7 @@ class RelayMqttHttpProxy(http.HTTPServer):
if __name__ == '__main__':
config.load('relay_mqtt_http_proxy')
config.load_app('relay_mqtt_http_proxy')
mqtt = MqttWrapper()
for device_id, data in config['relays'].items():

View File

@ -23,7 +23,7 @@ from home.api.types import (
TemperatureSensorLocation
)
config.load('sensors_bot')
config.load_app('sensors_bot')
bot.initialize()
bot.lang.ru(

View File

@ -23,7 +23,7 @@ from telegram import ReplyKeyboardMarkup, InlineKeyboardMarkup, InlineKeyboardBu
from PIL import Image
config.load('sound_bot')
config.load_app('sound_bot')
nodes = {}
for nodename, nodecfg in config['nodes'].items():

View File

@ -77,7 +77,7 @@ if __name__ == '__main__':
if not os.getegid() == 0:
raise RuntimeError("Must be run as root.")
config.load('sound_node')
config.load_app('sound_node')
storage = SoundRecordStorage(config['node']['storage'])

View File

@ -14,7 +14,7 @@ if __name__ == '__main__':
if not os.getegid() == 0:
sys.exit('Must be run as root.')
config.load('sound_sensor_node')
config.load_app('sound_sensor_node')
kwargs = {}
if 'delay' in config['node']:

View File

@ -159,7 +159,7 @@ def api_error_handler(exc, name, req: RequestParams):
if __name__ == '__main__':
config.load('sound_sensor_server')
config.load_app('sound_sensor_server')
hc = HitCounter()
api = WebAPIClient(timeout=(10, 60))

View File

@ -3,7 +3,7 @@
from home.config import config
if __name__ == '__main__':
config.load('ssh_tunnels_config_util')
config.load_app('ssh_tunnels_config_util')
network_prefix = config['network']
hostnames = []

View File

@ -63,7 +63,7 @@ async def run_server(host, port):
if __name__ == '__main__':
config.load()
config.load_app()
if 'measure_delay' in config['sensor']:
delay = float(config['sensor']['measure_delay'])

View File

@ -37,7 +37,7 @@ class MqttServer(Mqtt):
if __name__ == '__main__':
config.load('temphum_mqtt_receiver')
config.load_app('temphum_mqtt_receiver')
mqtt = MqttWrapper(clean_session=False)
node = MqttNode(node_id='+')

View File

@ -63,7 +63,7 @@ async def run_server(host, port):
if __name__ == '__main__':
config.load()
config.load_app()
if 'measure_delay' in config['sensor']:
delay = float(config['sensor']['measure_delay'])

9
src/test_new_config.py Normal file
View File

@ -0,0 +1,9 @@
from home.config import config, app_config, LinuxBoardsConfig
from pprint import pprint
if __name__ == '__main__':
config.load_app(name=False)
lbc = LinuxBoardsConfig()
pprint(lbc.data)

View File

@ -231,7 +231,7 @@ if __name__ == '__main__':
_app_name = 'web_api'
if is_development_mode():
_app_name += '_dev'
config.load(_app_name)
config.load_app(_app_name)
loop = asyncio.get_event_loop()

View File

@ -12,7 +12,7 @@ from src.home.mqtt.relay import MQTTRelayClient
if __name__ == '__main__':
config.load('test_mqtt_relay_server')
config.load_app('test_mqtt_relay_server')
relay = MQTTRelayClient('test')
relay.configure_tls()
relay.connect_and_loop()

View File

@ -18,7 +18,7 @@ if __name__ == '__main__':
parser.add_argument('--off', action='store_true')
parser.add_argument('--stat', action='store_true')
config.load('test_mqtt_relay', parser=parser)
config.load_app('test_mqtt_relay', parser=parser)
arg = parser.parse_args()
relay = MQTTRelayController('test')

View File

@ -28,7 +28,7 @@ if __name__ == '__main__':
parser.add_argument('--decr', type=str)
# parser.add_argument('--dump-config', action='store_true')
args = config.load('test_amixer', parser=parser)
args = config.load_app('test_amixer', parser=parser)
# if args.dump_config:
# print(config.data)

View File

@ -13,7 +13,7 @@ from src.home.config import config
if __name__ == '__main__':
config.load('test_api')
config.load_app('test_api')
api = WebAPIClient()
print(api.log_bot_request(BotType.ADMIN, 1, "test_api.py"))

View File

@ -21,7 +21,7 @@ if __name__ == '__main__':
parser.add_argument('--status', action='store_true',
help='print status and exit')
arg = config.load(False, parser=parser)
arg = config.load_app(False, parser=parser)
cam = esp32.WebClient(addr=parse_addr(arg.addr))
if arg.status:

View File

@ -372,5 +372,5 @@ def main():
if __name__ == '__main__':
config.load('test_inverter_monitor')
config.load_app('test_inverter_monitor')
main()

View File

@ -77,5 +77,5 @@ def cleanup_job():
if __name__ == '__main__':
config.load('ipcam_server')
config.load_app('ipcam_server')
cleanup_job()

View File

@ -64,7 +64,7 @@ def api_success_handler(response, name, req: RequestParams):
if __name__ == '__main__':
config.load('test_record_upload')
config.load_app('test_record_upload')
nodes = {}
for name, addr in config['nodes'].items():

View File

@ -56,7 +56,7 @@ def hits_sender():
if __name__ == '__main__':
config.load('test_api')
config.load_app('test_api')
hc = HitCounter()
api = WebAPIClient()

View File

@ -20,7 +20,7 @@ async def main():
if __name__ == '__main__':
config.load('test_telegram_aio_send_photo')
config.load_app('test_telegram_aio_send_photo')
loop = asyncio.get_event_loop()
asyncio.ensure_future(main())