Score: add a condition class to allow painless future additions

This commit is contained in:
vitiko98 2021-08-25 01:10:40 -04:00
parent eb8f482eed
commit 9ced18d0d3

View file

@ -1,5 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import annotations
import logging import logging
import re import re
@ -10,6 +12,72 @@ from database import TableCustomScoreProfiles as profiles_table
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class Condition:
"""Base class for score conditions. Every condition can take the amount
of attributes needed from a subtitle object in order to find a match."""
type = None
against = ()
# {type: provider, value: subdivx, required: False, negate: False}
def __init__(self, value: str, required=False, negate=False, **kwargs):
self._value = str(value)
self._negate = negate
self.required = required
@classmethod
def from_dict(cls, item: dict) -> Condition:
"""A factory method to create a condition object from a database
dictionary."""
try:
new = _registered_conditions[item["type"]]
except IndexError:
raise NotImplementedError(f"{item} condition doesn't have a class.")
return new(**item)
def check(self, subtitle) -> bool:
"""Check if the condition is met against a Subtitle object. **May be implemented
in a subclass**."""
to_match = [str(getattr(subtitle, name, None)) for name in self.against]
met = any(item == self._value for item in to_match)
if met and not self._negate:
return True
return not met and self._negate
def __repr__(self) -> str:
return f"<Condition {self.type}={self._value} (r:{self.required} n:{self._negate})>"
class ProviderCondition(Condition):
type = "provider"
against = ("provider_name",)
class UploaderCondition(Condition):
type = "uploader"
against = ("uploader",)
class LanguageCondition(Condition):
type = "language"
against = ("language",)
class RegexCondition(Condition):
type = "regex"
against = ("release_info", "filename")
def check(self, subtitle):
to_match = [str(getattr(subtitle, name, None)) for name in self.against]
met = re.search(rf"{self._value}", "".join(to_match)) is not None
if met and not self._negate:
return True
return not met and self._negate
class CustomScoreProfile: class CustomScoreProfile:
table = profiles_table table = profiles_table
conditions_table = conditions_table conditions_table = conditions_table
@ -24,11 +92,12 @@ class CustomScoreProfile:
def load_conditions(self): def load_conditions(self):
try: try:
self._conditions = list( self._conditions = [
self.conditions_table.select() Condition.from_dict(item)
for item in self.conditions_table.select()
.where(self.conditions_table.profile_id == self.id) .where(self.conditions_table.profile_id == self.id)
.dicts() .dicts()
) ]
except self.conditions_table.DoesNotExist: except self.conditions_table.DoesNotExist:
logger.debug("Conditions not found for %s", self) logger.debug("Conditions not found for %s", self)
self._conditions = [] self._conditions = []
@ -42,50 +111,28 @@ class CustomScoreProfile:
# Always return False if no conditions are set # Always return False if no conditions are set
if not self._conditions: if not self._conditions:
logger.debug("No conditions found in %s profile", self) logger.debug("No conditions found in db for %s", self)
return False return False
logger.debug("Checking conditions for %s profile", self) return self._check_conditions(subtitle)
met = self._check_conditions(subtitle)
logger.debug("Profile conditions met? %s", met)
return met
def _check_conditions(self, subtitle): def _check_conditions(self, subtitle):
checkers = { logger.debug("Checking conditions for %s profile", self)
"provider": subtitle.provider_name,
"uploader": subtitle.uploader,
"language": subtitle.language,
"regex": subtitle.release_info,
}
matches = [] matches = []
for condition in self._conditions: for condition in self._conditions:
# Condition dict example: matched = condition.check(subtitle)
# {type: provider, value: subdivx, required: False, negate: False}
key = condition.get("type")
sub_value = checkers.get(key)
if sub_value is None:
continue
cond_value = condition.get("value", "") if matched is True:
negate = condition.get("negate", False) logger.debug("%s Condition met", condition)
matches.append(True)
logger.debug("Checking %s: %s (condition: %s)", key, sub_value, condition) elif condition.required and not matched:
logger.debug("%s not met, discarding profile", condition)
if key == "regex" and re.findall(rf"{cond_value}", sub_value):
logger.debug("Regex matched: %s -> %s", cond_value, sub_value)
matches.append(not negate and True)
elif cond_value == sub_value:
logger.debug("%s condition met: %s -> %s", key, cond_value, sub_value)
matches.append(not negate and True)
# Return False if any required condition is not met
elif condition.get("required"):
logger.debug("%s required condition not met, discarding profile", key)
return False return False
return True in matches met = True in matches
logger.debug("Profile conditions met? %s", met)
return met
def __repr__(self): def __repr__(self):
return f"<ScoreProfile {self.name} (score: {self.score})>" return f"<ScoreProfile {self.name} (score: {self.score})>"
@ -217,5 +264,12 @@ class MovieScore(Score):
self.data.update(kwargs["movie_scores"]) self.data.update(kwargs["movie_scores"])
_registered_conditions = {
"provider": ProviderCondition,
"uploader": UploaderCondition,
"language": LanguageCondition,
"regex": RegexCondition,
}
series_score = SeriesScore.from_config(**get_settings()) series_score = SeriesScore.from_config(**get_settings())
movie_score = MovieScore.from_config(**get_settings()) movie_score = MovieScore.from_config(**get_settings())