Compare commits
	
		
			4 Commits
		
	
	
		
			c35ee252d9
			...
			576b3a56a9
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 576b3a56a9 | |||
| 720493d0de | |||
| d1130baa10 | |||
| 8e06777e51 | 
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -234,4 +234,5 @@ fabric.properties | ||||
| # ---> Custom | ||||
| .idea/deployment.xml | ||||
| .idea/misc.xml | ||||
| .idea/remote-mappings.xml | ||||
| .idea/remote-mappings.xml | ||||
| .idea/mvw-dl.iml | ||||
							
								
								
									
										8
									
								
								.idea/mvw-dl.iml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										8
									
								
								.idea/mvw-dl.iml
									
									
									
										generated
									
									
									
								
							| @@ -1,8 +0,0 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <module type="PYTHON_MODULE" version="4"> | ||||
|   <component name="NewModuleRootManager"> | ||||
|     <content url="file://$MODULE_DIR$" /> | ||||
|     <orderEntry type="inheritedJdk" /> | ||||
|     <orderEntry type="sourceFolder" forTests="false" /> | ||||
|   </component> | ||||
| </module> | ||||
| @@ -11,6 +11,11 @@ state_file_name_suffix = .log | ||||
| min_duration = 1200 | ||||
| max_duration = 2700 | ||||
| query = @maus-query.json | ||||
| query = {"queries":[{"fields":["topic"],"query":"die sendung mit der maus"},{"fields":["channel"],"query":"ARD"}],"sortBy":"timestamp","sortOrder":"desc","future":false,"offset":0,"size":50} | ||||
| # query = {"queries":[{"fields":["topic"],"query":"die sendung mit der maus"},{"fields":["channel"],"query":"ARD"}],"sortBy":"timestamp","sortOrder":"desc","future":false,"offset":0,"size":50} | ||||
| # state_file_name = maus | ||||
| # tmp_base_dir = %(tmp_base_dir)s/maus | ||||
| # tmp_base_dir = %(tmp_base_dir)s/maus | ||||
|  | ||||
| [test] | ||||
| min_duration = 100 | ||||
| max_duration = 200 | ||||
| query = {"queries":[{"fields":["topic"],"query":"die sendung mit der maus"},{"fields":["channel"],"query":"ARD"}],"sortBy":"timestamp","sortOrder":"desc","future":false,"offset":0,"size":50} | ||||
							
								
								
									
										132
									
								
								main.py
									
									
									
									
									
								
							
							
						
						
									
										132
									
								
								main.py
									
									
									
									
									
								
							| @@ -1,3 +1,135 @@ | ||||
| import configparser | ||||
| import logging | ||||
| import os | ||||
| import sys | ||||
| from rich.logging import RichHandler | ||||
|  | ||||
|  | ||||
| # Exit codes | ||||
| # 1: Config file invalid, it has no sections | ||||
| # 2: Config file invalid, sections must define at least CONST.CFG_MANDATORY | ||||
|  | ||||
|  | ||||
| class CONST(object): | ||||
|     __slots__ = () | ||||
|     LOG_FORMAT = "%(message)s" | ||||
|     CFG_DEFAULT_FILENAME = "config.ini" | ||||
|     CFG_DEFAULT_ABS_PATH = os.path.join(os.getcwd(), CFG_DEFAULT_FILENAME) | ||||
|     CFG_MANDATORY = "query" | ||||
|  | ||||
|  | ||||
| CONST = CONST() | ||||
| logging.basicConfig( | ||||
|     level="NOTSET", | ||||
|     format=CONST.LOG_FORMAT, | ||||
|     datefmt="[%X]", | ||||
|     handlers=[RichHandler( | ||||
|         show_time=False if "SYSTEMD_EXEC_PID" in os.environ else True, | ||||
|         rich_tracebacks=True | ||||
|     )] | ||||
| ) | ||||
| log = logging.getLogger("rich") | ||||
| log.setLevel(logging.DEBUG) | ||||
|  | ||||
|  | ||||
| class ConfigParser(configparser.ConfigParser): | ||||
|     """Can get options() without defaults | ||||
|  | ||||
|     Taken from https://stackoverflow.com/a/12600066. | ||||
|     """ | ||||
|  | ||||
|     def options(self, section, no_defaults=False, **kwargs): | ||||
|         if no_defaults: | ||||
|             try: | ||||
|                 return list(self._sections[section].keys()) | ||||
|             except KeyError: | ||||
|                 raise configparser.NoSectionError(section) | ||||
|         else: | ||||
|             return super().options(section) | ||||
|  | ||||
|  | ||||
| ini_defaults = [] | ||||
| internal_defaults = { | ||||
|     "self_name": "mvw-dl", | ||||
|     "tmp_base_dir": "/tmp/%(self_name)s", | ||||
|     "state_base_dir": "/var/lib/%(self_name)s", | ||||
|     "state_files_dir": "%(state_base_dir)s/state", | ||||
|     "state_file_retention": "50", | ||||
|     "state_file_name_prefix": "state-", | ||||
|     "state_file_name_suffix": ".log" | ||||
| } | ||||
| config = ConfigParser(defaults=internal_defaults) | ||||
| config.read(CONST.CFG_DEFAULT_FILENAME) | ||||
|  | ||||
|  | ||||
| def print_section_header(header: str) -> str: | ||||
|     return f"Loading config section '[{header}]' ..." | ||||
|  | ||||
|  | ||||
| def validate_default_section(config_obj: configparser.ConfigParser()) -> None: | ||||
|     log.debug(f"Loading config from file '{CONST.CFG_DEFAULT_ABS_PATH}' ...") | ||||
|     if not config_obj.sections(): | ||||
|         log.error(f"No config sections found in '{CONST.CFG_DEFAULT_ABS_PATH}'. Exiting 1 ...") | ||||
|         sys.exit(1) | ||||
|     if config.defaults(): | ||||
|         log.debug(f"Symbol legend:\n" | ||||
|                   f"* Global default from section '[{config_obj.default_section}]'\n" | ||||
|                   f"~ Local option, doesn't exist in '[{config_obj.default_section}]'\n" | ||||
|                   f"+ Local override of a value from '[{config_obj.default_section}]'\n" | ||||
|                   f"= Local override, same value as in '[{config_obj.default_section}]'") | ||||
|         log.debug(print_section_header(config_obj.default_section)) | ||||
|         for default in config_obj.defaults(): | ||||
|             ini_defaults.append({default: config_obj[config_obj.default_section][default]}) | ||||
|             log.debug(f"* {default} = {config_obj[config_obj.default_section][default]}") | ||||
|     else: | ||||
|         log.debug(f"No defaults defined") | ||||
|  | ||||
|  | ||||
| def config_has_valid_section(config_obj: configparser.ConfigParser()) -> bool: | ||||
|     has_valid_section = False | ||||
|     for config_obj_section in config_obj.sections(): | ||||
|         if CONST.CFG_MANDATORY in config_obj.options(config_obj_section): | ||||
|             has_valid_section = True | ||||
|             break | ||||
|     return has_valid_section | ||||
|  | ||||
|  | ||||
| def is_default(config_key: str) -> bool: | ||||
|     return any(config_key in ini_default for ini_default in ini_defaults) | ||||
|  | ||||
|  | ||||
| def is_same_as_default(config_kv_pair: dict) -> bool: | ||||
|     return config_kv_pair in ini_defaults | ||||
|  | ||||
|  | ||||
| def validate_config_sections(config_obj: configparser.ConfigParser()) -> None: | ||||
|     for section in config_obj.sections(): | ||||
|         log.debug(print_section_header(section)) | ||||
|         if CONST.CFG_MANDATORY not in config_obj.options(section, no_defaults=True): | ||||
|             log.warning(f"Config section '[{section}]' does not have mandatory option '{CONST.CFG_MANDATORY}' set, " | ||||
|                         f"skipping section ...") | ||||
|             config_obj.remove_section(section) | ||||
|         else: | ||||
|             for key in config_obj.options(section, no_defaults=True): | ||||
|                 kv_prefix = "~" | ||||
|                 if is_default(key): | ||||
|                     kv_prefix = "+" | ||||
|                     if is_same_as_default({key: config_obj[section][key]}): | ||||
|                         kv_prefix = "=" | ||||
|                 log.debug(f"{kv_prefix} {key} = {config_obj[section][key]}") | ||||
|  | ||||
|  | ||||
| validate_default_section(config) | ||||
| if config_has_valid_section(config): | ||||
|     validate_config_sections(config) | ||||
| else: | ||||
|     log.error(f"No valid config section found. A valid config section has at least the '{CONST.CFG_MANDATORY}' " | ||||
|               f"option set. Exiting 2 ...") | ||||
|     sys.exit(2) | ||||
|  | ||||
| quit() | ||||
|  | ||||
|  | ||||
| # This is a sample Python script. | ||||
|  | ||||
| # Press Umschalt+F10 to execute it or replace it with your code. | ||||
|   | ||||
							
								
								
									
										1
									
								
								requirements.in
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								requirements.in
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| rich | ||||
							
								
								
									
										12
									
								
								requirements.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								requirements.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| # | ||||
| # This file is autogenerated by pip-compile with python 3.10 | ||||
| # To update, run: | ||||
| # | ||||
| #    pip-compile | ||||
| # | ||||
| commonmark==0.9.1 | ||||
|     # via rich | ||||
| pygments==2.11.2 | ||||
|     # via rich | ||||
| rich==12.0.0 | ||||
|     # via -r requirements.in | ||||
		Reference in New Issue
	
	Block a user