class TestConfigBase(ConfigBase): BOOL = BoolConfigEntry(key_path=["test", "bool"], default=False) STRING = StringConfigEntry(key_path=["test", "string"], default="default value") REGEX = RegexConfigEntry(key_path=["test", "regex"], default="^[a-zA-Z0-9]$") INT = IntConfigEntry(key_path=["test", "int"], default=100) FLOAT = FloatConfigEntry(key_path=["test", "float"], default=1.23) DATE = DateConfigEntry( key_path=["test", "this", "date", "is", "nested", "deep"], default=datetime.now()) TIMEDELTA = TimeDeltaConfigEntry( key_path=["test", "this", "timediff", "is", "in", "this", "branch"], default=timedelta(seconds=10)) FILE = FileConfigEntry(key_path=["test", "file"], ) DIRECTORY = DirectoryConfigEntry(key_path=["test", "directory"], ) RANGE = RangeConfigEntry(key_path=["test", "this", "is", "a", "range"], default=Range(0, 100)) DICT = DictConfigEntry(key_path=["dict"], schema=Schema({str: str})) DICT_LIST = ListConfigEntry( item_type=DictConfigEntry, item_args={"schema": Schema({str: str})}, key_path=[ "dict_list", ], ) STRING_LIST = ListConfigEntry(item_type=StringConfigEntry, key_path=["test", "this", "is", "a", "list"], example=["these", "are", "test", "values"], secret=False) NONE_INT = IntConfigEntry( key_path=["none", "int"], default=None, ) NONE_DATE = DateConfigEntry( key_path=["none", "date"], default=None, ) SECRET_BOOL = BoolConfigEntry(key_path=["secret", "bool"], default=False, secret=True) SECRET_INT = IntConfigEntry(key_path=["secret", "int"], default=None, secret=True) SECRET_REGEX = RegexConfigEntry(key_path=["secret", "regex"], default=None, secret=True) SECRET_LIST = ListConfigEntry(item_type=RegexConfigEntry, key_path=["secret", "list"], default=["[a-zA-Z]*"], secret=True)
class Config(ConfigBase): def __new__(cls, *args, **kwargs): data_sources = [ EnvSource(), YamlSource(FILE_NAME), TomlSource(FILE_NAME) ] kwargs["data_sources"] = data_sources return super(Config, cls).__new__(cls, *args, **kwargs) TELEGRAM_BOT_TOKEN = StringConfigEntry( key_path=[KEY_ROOT, KEY_TELEGRAM, "bot_token"], description="ID of the telegram chat to send messages to.", required=True, secret=True, example="123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11") TELEGRAM_CHAT_ID = StringConfigEntry( key_path=[KEY_ROOT, KEY_TELEGRAM, "chat_id"], description="ID of the telegram chat to send messages to.", required=True, secret=True, example="-123456789") RETRY_ENABLED = BoolConfigEntry( key_path=[KEY_ROOT, KEY_RETRY, "enabled"], description="Whether to retry sending messages or not.", default=True, ) RETRY_TIMEOUT = TimeDeltaConfigEntry( key_path=[KEY_ROOT, KEY_RETRY, "timeout"], description="Timeout between tries.", default="10s", ) RETRY_GIVE_UP_AFTER = TimeDeltaConfigEntry( key_path=[KEY_ROOT, KEY_RETRY, "give_up_after"], description="Time interval after which the retry should be cancelled.", default="1h", )
def test_timedelta_entry(self): from datetime import timedelta config_entry = TimeDeltaConfigEntry(key_path=["timedelta"]) input_output = [ ("20:56:35", timedelta(hours=20, minutes=56, seconds=35)), ("32m", timedelta(minutes=32)), ("4h0m3s", timedelta(hours=4, minutes=0, seconds=3)), ("4h3s", timedelta(hours=4, minutes=0, seconds=3)), ("4:13", timedelta(hours=0, minutes=4, seconds=13)), ] self.assert_input_output(config_entry, input_output)
class Config(ConfigBase): def __new__(cls, *args, **kwargs): yaml_source = YamlSource(NODE_MAIN) toml_source = TomlSource(NODE_MAIN) data_sources = [ EnvSource(), yaml_source, toml_source ] return super(Config, cls).__new__(cls, data_sources=data_sources) LOG_LEVEL = StringConfigEntry( description="Log level", key_path=[ NODE_MAIN, "log_level" ], regex=re.compile(f"{'|'.join(logging._nameToLevel.keys())}", flags=re.IGNORECASE), default="WARNING", ) TELEGRAM_BOT_TOKEN = StringConfigEntry( description="The telegram bot token to use", key_path=[ NODE_MAIN, NODE_TELEGRAM, "bot_token" ], example="123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11", secret=True, required=True ) TELEGRAM_ADMIN_USERNAMES = ListConfigEntry( item_type=StringConfigEntry, key_path=[ NODE_MAIN, NODE_TELEGRAM, "admin_usernames" ], required=True, example=[ "myadminuser", "myotheradminuser" ] ) TELEGRAM_CHAT_IDS = ListConfigEntry( item_type=StringConfigEntry, key_path=[ NODE_MAIN, NODE_TELEGRAM, "chat_ids" ], required=True, example=[ 12345678, 87654321 ] ) KEEL_HOST = StringConfigEntry( description="Hostname of the keel HTTP endpoint", key_path=[ NODE_MAIN, NODE_KEEL, NODE_HOST ], default="localhost", required=True ) KEEL_PORT = IntConfigEntry( description="Port of the keel HTTP endpoint", key_path=[ NODE_MAIN, NODE_KEEL, NODE_PORT ], default=9300, required=True ) KEEL_SSL = BoolConfigEntry( description="Whether to use HTTPS or not", key_path=[ NODE_MAIN, NODE_KEEL, "ssl" ], default=True ) KEEL_USER = StringConfigEntry( description="Keel basic auth username", key_path=[ NODE_MAIN, NODE_KEEL, "username" ], required=True ) KEEL_PASSWORD = StringConfigEntry( description="Keel basic auth password", key_path=[ NODE_MAIN, NODE_KEEL, "password" ], required=True, secret=True ) MONITOR_INTERVAL = TimeDeltaConfigEntry( description="Interval to check for new pending approvals", key_path=[ NODE_MAIN, "monitor" "interval" ], default="1m", required=True, ) STATS_ENABLED = BoolConfigEntry( description="Whether to enable prometheus statistics or not.", key_path=[ NODE_MAIN, NODE_STATS, NODE_ENABLED ], default=True ) STATS_PORT = IntConfigEntry( description="The port to expose statistics on.", key_path=[ NODE_MAIN, NODE_STATS, NODE_PORT ], default=8000 )
class Config(ConfigBase): def __new__(cls, *args, **kwargs): yaml_source = YamlSource(NODE_MAIN) toml_source = TomlSource(NODE_MAIN) data_sources = [EnvSource(), yaml_source, toml_source] return super(Config, cls).__new__(cls, data_sources=data_sources) LOG_LEVEL = StringConfigEntry( description="Log level", key_path=[NODE_MAIN, "log_level"], regex=re.compile(f"{'|'.join(logging._nameToLevel.keys())}", flags=re.IGNORECASE), default="WARNING", ) LOCALE = StringConfigEntry( description="Bot Locale", key_path=[NODE_MAIN, "locale"], default="en", ) TELEGRAM_BOT_TOKEN = StringConfigEntry( description="The telegram bot token to use", key_path=[NODE_MAIN, NODE_TELEGRAM, "bot_token"], example="123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11", secret=True) TELEGRAM_ADMIN_USERNAMES = ListConfigEntry( item_type=StringConfigEntry, key_path=[NODE_MAIN, NODE_TELEGRAM, "admin_usernames"], required=True, example=["myadminuser", "myotheradminuser"]) GROCY_CACHE_DURATION = TimeDeltaConfigEntry( description="Duration to cache Grocy REST api call responses", key_path=[NODE_MAIN, NODE_GROCY, "cache_duration"], required=True, default="60s", ) NOTIFICATION_CHAT_IDS = ListConfigEntry( item_type=StringConfigEntry, key_path=[NODE_MAIN, NODE_NOTIFICATION, "chat_ids"], default=[]) GROCY_HOST = StringConfigEntry( description="Hostname of the Grocy instance", key_path=[NODE_MAIN, NODE_GROCY, NODE_HOST], required=True, default="127.0.0.1") GROCY_PORT = IntConfigEntry(description="Port of the Grocy REST api", key_path=[NODE_MAIN, NODE_GROCY, NODE_PORT], range=Range(1, 65535), default=80) GROCY_API_KEY = StringConfigEntry( description="Grocy API Key used for REST authentication", key_path=[NODE_MAIN, NODE_GROCY, NODE_API_KEY], required=True, example="abcdefgh12345678", secret=True) STATS_ENABLED = BoolConfigEntry( description="Whether to enable prometheus statistics or not.", key_path=[NODE_MAIN, NODE_STATS, NODE_ENABLED], default=True) STATS_PORT = IntConfigEntry( description="The port to expose statistics on.", key_path=[NODE_MAIN, NODE_STATS, NODE_PORT], default=8000)
class DeduplicatorConfig(ConfigBase): def __new__(cls, *args, **kwargs): yaml_source = YamlSource("py_image_dedup") data_sources = [EnvSource(), yaml_source] return super(DeduplicatorConfig, cls).__new__(cls, data_sources=data_sources) DRY_RUN = BoolConfigEntry( description="If enabled no source file will be touched", key_path=[NODE_MAIN, NODE_DRY_RUN], default=True) ELASTICSEARCH_HOST = StringConfigEntry( description="Hostname of the elasticsearch backend instance to use", key_path=[NODE_MAIN, NODE_ELASTICSEARCH, NODE_HOST], default="127.0.0.1") ELASTICSEARCH_PORT = IntConfigEntry( description="Hostname of the elasticsearch backend instance to use", key_path=[NODE_MAIN, NODE_ELASTICSEARCH, NODE_PORT], range=Range(1, 65535), default=9200) ELASTICSEARCH_MAX_DISTANCE = FloatConfigEntry( description= "Maximum signature distance [0..1] to query from elasticsearch backend.", key_path=[NODE_MAIN, NODE_ELASTICSEARCH, NODE_MAX_DISTANCE], default=0.10) ELASTICSEARCH_AUTO_CREATE_INDEX = BoolConfigEntry( description= "Whether to automatically create an index in the target database.", key_path=[NODE_MAIN, NODE_ELASTICSEARCH, NODE_AUTO_CREATE_INDEX], default=True) ELASTICSEARCH_INDEX = StringConfigEntry( description= "The index name to use for storing and querying image analysis data.", key_path=[NODE_MAIN, NODE_ELASTICSEARCH, NODE_INDEX], default="images") ANALYSIS_USE_EXIF_DATA = BoolConfigEntry( description="Whether to scan for EXIF data or not.", key_path=[NODE_MAIN, NODE_ANALYSIS, NODE_USE_EXIF_DATA], default=True) SOURCE_DIRECTORIES = ListConfigEntry( description= "Comma separated list of source paths to analyse and deduplicate.", item_type=DirectoryConfigEntry, item_args={"check_existence": True}, key_path=[NODE_MAIN, NODE_ANALYSIS, NODE_SOURCE_DIRECTORIES], required=True, example=["/home/myuser/pictures/"]) RECURSIVE = BoolConfigEntry( description="When set all directories will be recursively analyzed.", key_path=[NODE_MAIN, NODE_ANALYSIS, NODE_RECURSIVE], default=True) SEARCH_ACROSS_ROOT_DIRS = BoolConfigEntry( description= "When set duplicates will be found even if they are located in different root directories.", key_path=[NODE_MAIN, NODE_ANALYSIS, NODE_SEARCH_ACROSS_ROOT_DIRS], default=False) FILE_EXTENSION_FILTER = ListConfigEntry( description="Comma separated list of file extensions.", item_type=StringConfigEntry, key_path=[NODE_MAIN, NODE_ANALYSIS, NODE_FILE_EXTENSIONS], required=True, default=[".png", ".jpg", ".jpeg"]) ANALYSIS_THREADS = IntConfigEntry( description="Number of threads to use for image analysis phase.", key_path=[NODE_MAIN, NODE_ANALYSIS, NODE_THREADS], default=1) MAX_FILE_MODIFICATION_TIME_DELTA = TimeDeltaConfigEntry( description="Maximum file modification date difference between multiple " "duplicates to be considered the same image", key_path=[ NODE_MAIN, NODE_DEDUPLICATION, NODE_MAX_FILE_MODIFICATION_TIME_DIFF ], default=None, example=timedelta(minutes=5)) REMOVE_EMPTY_FOLDERS = BoolConfigEntry( description="Whether to remove empty folders or not.", key_path=[NODE_MAIN, NODE_REMOVE_EMPTY_FOLDERS], default=False) DEDUPLICATOR_DUPLICATES_TARGET_DIRECTORY = DirectoryConfigEntry( description= "Directory path to move duplicates to instead of deleting them.", key_path=[ NODE_MAIN, NODE_DEDUPLICATION, NODE_DUPLICATES_TARGET_DIRECTORY ], check_existence=True, default=None, example="/home/myuser/pictures/duplicates/") DAEMON_PROCESSING_TIMEOUT = TimeDeltaConfigEntry( description= "Time to wait for filesystems changes to settle before analysing.", key_path=[NODE_MAIN, NODE_DAEMON, NODE_PROCESSING_TIMEOUT], default="30s") DAEMON_FILE_OBSERVER_TYPE = StringConfigEntry( description="Type of file observer to use.", key_path=[NODE_MAIN, NODE_DAEMON, NODE_FILE_OBSERVER_TYPE], regex="|".join( [FILE_OBSERVER_TYPE_POLLING, FILE_OBSERVER_TYPE_INOTIFY]), default=FILE_OBSERVER_TYPE_POLLING, required=True) STATS_ENABLED = BoolConfigEntry( description="Whether to enable prometheus statistics or not.", key_path=[NODE_MAIN, NODE_STATS, NODE_ENABLED], default=True) STATS_PORT = IntConfigEntry( description="The port to expose statistics on.", key_path=[NODE_MAIN, NODE_STATS, NODE_PORT], default=8000)
class AppConfig(ConfigBase): def __new__(cls, *args, **kwargs): yaml_source = YamlSource(CONFIG_NODE_ROOT) toml_source = TomlSource(CONFIG_NODE_ROOT) data_sources = [ EnvSource(), yaml_source, toml_source, ] return super(AppConfig, cls).__new__(cls, data_sources=data_sources) LOG_LEVEL = StringConfigEntry( description="Log level", key_path=[CONFIG_NODE_ROOT, "log_level"], regex=re.compile(f" {'|'.join(logging._nameToLevel.keys())}", flags=re.IGNORECASE), default="INFO", ) SERVER_HOST = StringConfigEntry( key_path=[CONFIG_NODE_ROOT, CONFIG_NODE_SERVER, "host"], default=DEFAULT_SERVER_HOST, secret=True) SERVER_PORT = IntConfigEntry( key_path=[CONFIG_NODE_ROOT, CONFIG_NODE_SERVER, CONFIG_NODE_PORT], range=Range(1, 65534), default=9465) SERVER_API_TOKEN = StringConfigEntry( key_path=[CONFIG_NODE_ROOT, CONFIG_NODE_SERVER, "api_token"], default=None, secret=True) DROP_EVENT_QUEUE_AFTER = TimeDeltaConfigEntry( key_path=[CONFIG_NODE_ROOT, "drop_event_queue_after"], default="2h", ) RETRY_INTERVAL = TimeDeltaConfigEntry( key_path=[CONFIG_NODE_ROOT, "retry_interval"], default="2s", ) HTTP_METHOD = StringConfigEntry( key_path=[CONFIG_NODE_ROOT, CONFIG_NODE_HTTP, "method"], required=True, default="POST", regex="GET|POST|PUT|PATCH") HTTP_URL = StringConfigEntry( key_path=[CONFIG_NODE_ROOT, CONFIG_NODE_HTTP, "url"], required=False) HTTP_HEADERS = ListConfigEntry( item_type=StringConfigEntry, key_path=[CONFIG_NODE_ROOT, CONFIG_NODE_HTTP, "headers"], default=[]) MQTT_HOST = StringConfigEntry( key_path=[CONFIG_NODE_ROOT, CONFIG_NODE_MQTT, "host"], required=False) MQTT_PORT = IntConfigEntry( key_path=[CONFIG_NODE_ROOT, CONFIG_NODE_MQTT, "port"], required=True, default=1883, range=Range(1, 65534), ) MQTT_CLIENT_ID = StringConfigEntry( key_path=[CONFIG_NODE_ROOT, CONFIG_NODE_MQTT, "client_id"], default="barcode-server") MQTT_USER = StringConfigEntry( key_path=[CONFIG_NODE_ROOT, CONFIG_NODE_MQTT, "user"]) MQTT_PASSWORD = StringConfigEntry( key_path=[CONFIG_NODE_ROOT, CONFIG_NODE_MQTT, "password"], secret=True) MQTT_TOPIC = StringConfigEntry( key_path=[CONFIG_NODE_ROOT, CONFIG_NODE_MQTT, "topic"], default="barcode-server/barcode", required=True) MQTT_QOS = IntConfigEntry( key_path=[CONFIG_NODE_ROOT, CONFIG_NODE_MQTT, "qos"], default=2, required=True) MQTT_RETAIN = BoolConfigEntry( key_path=[CONFIG_NODE_ROOT, CONFIG_NODE_MQTT, "retain"], default=False, required=True) DEVICE_PATTERNS = ListConfigEntry(item_type=RegexConfigEntry, item_args={"flags": re.IGNORECASE}, key_path=[CONFIG_NODE_ROOT, "devices"], default=[]) DEVICE_PATHS = ListConfigEntry(item_type=FileConfigEntry, key_path=[CONFIG_NODE_ROOT, "device_paths"], default=[]) STATS_PORT = IntConfigEntry( key_path=[CONFIG_NODE_ROOT, CONFIG_NODE_STATS, CONFIG_NODE_PORT], default=8000, required=False) def validate(self): super(AppConfig, self).validate() if len(self.DEVICE_PATHS.value) == len( self.DEVICE_PATTERNS.value) == 0: raise AssertionError( "You must provide at least one device pattern or device_path!")