def test_contains_int_range_inverted(self):
     int_range = Range(2, 0)
     self.assertNotIn(-1, int_range)
     self.assertIn(0, int_range)
     self.assertIn(1, int_range)
     self.assertIn(2, int_range)
     self.assertNotIn(3, int_range)
 def test_contains_float_range_inverted(self):
     float_range = Range(2, 0)
     self.assertNotIn(-1, float_range)
     self.assertIn(0, float_range)
     self.assertIn(1, float_range)
     self.assertIn(2, float_range)
     self.assertNotIn(3, float_range)
 def test_contains_int_range_over_zero(self):
     int_range = Range(-5, 5)
     self.assertNotIn(-6, int_range)
     self.assertIn(-5, int_range)
     self.assertIn(0, int_range)
     self.assertIn(5, int_range)
     self.assertNotIn(6, int_range)
 def test_contains_float_range_over_zero(self):
     float_range = Range(-5, 5)
     self.assertNotIn(-6, float_range)
     self.assertIn(-5, float_range)
     self.assertIn(0, float_range)
     self.assertIn(5, float_range)
     self.assertNotIn(6, float_range)
 def test_lt_int_range(self):
     int_range = Range(0, 5)
     self.assertTrue(-1 < int_range)
     self.assertFalse(0 < int_range)
     self.assertFalse(2 < int_range)
     self.assertFalse(5 < int_range)
     self.assertFalse(6 < int_range)
 def test_gt_float_range(self):
     float_range = Range(0, 5.2)
     self.assertFalse(-1 > float_range)
     self.assertFalse(0 > float_range)
     self.assertFalse(2 > float_range)
     self.assertFalse(5.2 > float_range)
     self.assertTrue(6 > float_range)
 def test_gt_int_range(self):
     int_range = Range(0, 5)
     self.assertFalse(-1 > int_range)
     self.assertFalse(0 > int_range)
     self.assertFalse(2 > int_range)
     self.assertFalse(5 > int_range)
     self.assertTrue(6 > int_range)
 def test_lt_float_range(self):
     float_range = Range(0, 5.2)
     self.assertTrue(-1 < float_range)
     self.assertFalse(0 < float_range)
     self.assertFalse(2 < float_range)
     self.assertFalse(5.2 < float_range)
     self.assertFalse(6 < float_range)
    def test_range_entry(self):
        config_entry = RangeConfigEntry(key_path=["range"])
        input_output = [
            ("[-5..5]", Range(-5, 5)),
        ]

        self.assert_input_output(config_entry, input_output)
    def test_float_entry(self):
        config_entry = FloatConfigEntry(key_path=["float"],
                                        range=Range(-10.0, 10))
        input_output = [("5", 5.0), (5, 5.0), ("-3.0", -3.0), (-3.0, -3.0),
                        (1.2, 1.2), ("1.2", 1.2), ("3%", 0.03),
                        (-20.0, ValueError)]

        self.assert_input_output(config_entry, input_output)
 def test_contains_int_range(self):
     int_range = Range(0, 5)
     self.assertNotIn(-1, int_range)
     self.assertIn(0, int_range)
     self.assertIn(3, int_range)
     self.assertNotIn(3.3, int_range)
     self.assertIn(3.0, int_range)
     self.assertIn(5, int_range)
     self.assertNotIn(6, int_range)
 def test_contains_float_infinity(self):
     float_range = Range(-math.inf, 5)
     self.assertIn(-math.inf, float_range)
     self.assertIn(-10000, float_range)
     self.assertIn(-6, float_range)
     self.assertIn(-5, float_range)
     self.assertIn(0, float_range)
     self.assertIn(5, float_range)
     self.assertNotIn(6, float_range)
示例#13
0
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)
 def test_contains_float_range(self):
     float_range = Range(0.0, 5.2)
     self.assertNotIn(-1.0, float_range)
     self.assertIn(-0.0, float_range)
     self.assertIn(0.0, float_range)
     self.assertIn(0, float_range)
     self.assertIn(3, float_range)
     self.assertIn(3.0, float_range)
     self.assertIn(3.3, float_range)
     self.assertIn(5, float_range)
     self.assertIn(5.2, float_range)
     self.assertNotIn(5.3, float_range)
 def test_contains_int_range_exclusive(self):
     int_range = Range(0, 2, False, False)
     self.assertNotIn(0, int_range)
     self.assertIn(1, int_range)
     self.assertNotIn(2, int_range)
示例#16
0
from py_range_parse import parse_range, Range

# parse a string
range = parse_range("[0..5]")
# or create one directly
range = Range(0, 5)
示例#17
0
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)
示例#18
0
 def test_str_float(self):
     int_range = Range(-4.2123, 123.4324)
     self.assertEqual(str(int_range), "[-4.2123..123.4324]")
 def test_contains_float_range_negative(self):
     float_range = Range(-5, -2)
     self.assertNotIn(-6, float_range)
     self.assertIn(-3, float_range)
     self.assertIn(-2, float_range)
     self.assertNotIn(-1, float_range)
示例#20
0
 def test_str_int(self):
     int_range = Range(-4, 123)
     self.assertEqual(str(int_range), "[-4..123]")
 def test_contains_float_range_exclusive(self):
     float_range = Range(0.0, 2, False, False)
     self.assertNotIn(0, float_range)
     self.assertIn(0.1, float_range)
     self.assertIn(1, float_range)
     self.assertNotIn(2, float_range)
示例#22
0
 def test_str_exclusive(self):
     int_range = Range(123, -4, False, False)
     self.assertEqual(str(int_range), "]-4..123[")
 def test_contains_int_range_negative(self):
     int_range = Range(-5, -2)
     self.assertNotIn(-6, int_range)
     self.assertIn(-3, int_range)
     self.assertIn(-2, int_range)
     self.assertNotIn(-1, int_range)
示例#24
0
 def test_str_inf(self):
     int_range = Range(-math.inf, math.inf)
     self.assertEqual(str(int_range), "[-inf..inf]")
示例#25
0
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)
    def test_int_entry(self):
        config_entry = IntConfigEntry(key_path=["int"], range=Range(-5, 5))
        input_output = [("5", 5), (5, 5), ("-3", -3), (-3, -3),
                        (-6, ValueError)]

        self.assert_input_output(config_entry, input_output)
示例#27
0
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!")