2016-04-30 21:17:08 +00:00
|
|
|
import hashlib
|
2020-06-05 22:03:37 +00:00
|
|
|
import os
|
2016-04-02 12:13:26 +00:00
|
|
|
import re
|
2016-05-20 19:34:02 +00:00
|
|
|
import tempfile
|
|
|
|
from contextlib import contextmanager
|
2020-06-05 22:03:37 +00:00
|
|
|
from datetime import datetime, timedelta
|
|
|
|
from typing import Any, Dict, Generator, List, Optional, Tuple, TypeVar, Union
|
2016-03-28 20:53:56 +00:00
|
|
|
|
2020-06-05 22:03:37 +00:00
|
|
|
from szurubooru import errors
|
2016-08-14 12:22:53 +00:00
|
|
|
|
2020-06-05 22:03:37 +00:00
|
|
|
T = TypeVar("T")
|
2017-02-04 00:08:12 +00:00
|
|
|
|
|
|
|
|
|
|
|
def snake_case_to_lower_camel_case(text: str) -> str:
|
2020-06-05 22:03:37 +00:00
|
|
|
components = text.split("_")
|
|
|
|
return components[0].lower() + "".join(
|
|
|
|
word[0].upper() + word[1:].lower() for word in components[1:]
|
|
|
|
)
|
2016-06-18 08:55:44 +00:00
|
|
|
|
2016-08-14 12:22:53 +00:00
|
|
|
|
2017-02-04 00:08:12 +00:00
|
|
|
def snake_case_to_upper_train_case(text: str) -> str:
|
2020-06-05 22:03:37 +00:00
|
|
|
return "-".join(
|
|
|
|
word[0].upper() + word[1:].lower() for word in text.split("_")
|
|
|
|
)
|
2016-08-14 12:22:53 +00:00
|
|
|
|
2016-08-14 10:35:14 +00:00
|
|
|
|
2017-02-04 00:08:12 +00:00
|
|
|
def snake_case_to_lower_camel_case_keys(
|
2020-06-05 22:03:37 +00:00
|
|
|
source: Dict[str, Any]
|
|
|
|
) -> Dict[str, Any]:
|
2016-06-18 08:55:44 +00:00
|
|
|
target = {}
|
|
|
|
for key, value in source.items():
|
|
|
|
target[snake_case_to_lower_camel_case(key)] = value
|
|
|
|
return target
|
|
|
|
|
2016-08-14 12:22:53 +00:00
|
|
|
|
2016-05-20 19:34:02 +00:00
|
|
|
@contextmanager
|
2017-02-04 00:08:12 +00:00
|
|
|
def create_temp_file(**kwargs: Any) -> Generator:
|
|
|
|
(descriptor, path) = tempfile.mkstemp(**kwargs)
|
|
|
|
os.close(descriptor)
|
2016-05-20 19:34:02 +00:00
|
|
|
try:
|
2020-06-05 22:03:37 +00:00
|
|
|
with open(path, "r+b") as handle:
|
2016-05-20 19:34:02 +00:00
|
|
|
yield handle
|
|
|
|
finally:
|
|
|
|
os.remove(path)
|
|
|
|
|
2016-08-14 12:22:53 +00:00
|
|
|
|
2018-03-08 06:47:58 +00:00
|
|
|
@contextmanager
|
|
|
|
def create_temp_file_path(**kwargs: Any) -> Generator:
|
|
|
|
(descriptor, path) = tempfile.mkstemp(**kwargs)
|
|
|
|
os.close(descriptor)
|
|
|
|
try:
|
|
|
|
yield path
|
|
|
|
finally:
|
|
|
|
os.remove(path)
|
|
|
|
|
|
|
|
|
2017-02-04 00:08:12 +00:00
|
|
|
def unalias_dict(source: List[Tuple[List[str], T]]) -> Dict[str, T]:
|
|
|
|
output_dict = {} # type: Dict[str, T]
|
|
|
|
for aliases, value in source:
|
|
|
|
for alias in aliases:
|
|
|
|
output_dict[alias] = value
|
2016-05-07 19:42:03 +00:00
|
|
|
return output_dict
|
|
|
|
|
2016-08-14 12:22:53 +00:00
|
|
|
|
2017-02-04 00:08:12 +00:00
|
|
|
def get_md5(source: Union[str, bytes]) -> str:
|
2016-04-30 21:17:08 +00:00
|
|
|
if not isinstance(source, bytes):
|
2020-06-05 22:03:37 +00:00
|
|
|
source = source.encode("utf-8")
|
2016-04-30 21:17:08 +00:00
|
|
|
md5 = hashlib.md5()
|
|
|
|
md5.update(source)
|
|
|
|
return md5.hexdigest()
|
|
|
|
|
2016-08-14 12:22:53 +00:00
|
|
|
|
2017-02-04 00:08:12 +00:00
|
|
|
def get_sha1(source: Union[str, bytes]) -> str:
|
2016-08-20 11:04:49 +00:00
|
|
|
if not isinstance(source, bytes):
|
2020-06-05 22:03:37 +00:00
|
|
|
source = source.encode("utf-8")
|
2016-08-20 11:04:49 +00:00
|
|
|
sha1 = hashlib.sha1()
|
|
|
|
sha1.update(source)
|
|
|
|
return sha1.hexdigest()
|
|
|
|
|
|
|
|
|
2017-02-04 00:08:12 +00:00
|
|
|
def flip(source: Dict[Any, Any]) -> Dict[Any, Any]:
|
2016-04-30 21:17:08 +00:00
|
|
|
return {v: k for k, v in source.items()}
|
|
|
|
|
2016-08-14 12:22:53 +00:00
|
|
|
|
2017-02-04 00:08:12 +00:00
|
|
|
def is_valid_email(email: Optional[str]) -> bool:
|
2021-11-29 23:39:34 +00:00
|
|
|
"""Return whether given email address is valid or empty."""
|
2020-06-05 22:03:37 +00:00
|
|
|
return not email or re.match(r"^[^@]*@[^@]*\.[^@]*$", email) is not None
|
2016-04-03 16:00:38 +00:00
|
|
|
|
2016-08-14 12:22:53 +00:00
|
|
|
|
2020-06-05 14:02:18 +00:00
|
|
|
class dotdict(dict):
|
2021-11-29 23:39:34 +00:00
|
|
|
"""dot.notation access to dictionary attributes."""
|
2020-06-05 22:03:37 +00:00
|
|
|
|
2017-02-04 00:08:12 +00:00
|
|
|
def __getattr__(self, attr: str) -> Any:
|
2016-03-28 20:53:56 +00:00
|
|
|
return self.get(attr)
|
2017-02-04 00:08:12 +00:00
|
|
|
|
2016-03-28 20:53:56 +00:00
|
|
|
__setattr__ = dict.__setitem__
|
|
|
|
__delattr__ = dict.__delitem__
|
2016-04-02 12:13:26 +00:00
|
|
|
|
2016-08-14 12:22:53 +00:00
|
|
|
|
2017-02-04 00:08:12 +00:00
|
|
|
def parse_time_range(value: str) -> Tuple[datetime, datetime]:
|
2021-11-29 23:39:34 +00:00
|
|
|
"""Return tuple containing min/max time for given text representation."""
|
2016-08-14 12:22:53 +00:00
|
|
|
one_day = timedelta(days=1)
|
|
|
|
one_second = timedelta(seconds=1)
|
2017-02-03 20:42:15 +00:00
|
|
|
almost_one_day = one_day - one_second
|
2016-04-02 12:13:26 +00:00
|
|
|
|
|
|
|
value = value.lower()
|
|
|
|
if not value:
|
2020-06-05 22:03:37 +00:00
|
|
|
raise errors.ValidationError("Empty date format.")
|
2016-04-02 12:13:26 +00:00
|
|
|
|
2020-06-05 22:03:37 +00:00
|
|
|
if value == "today":
|
2016-08-14 12:22:53 +00:00
|
|
|
now = datetime.utcnow()
|
2016-04-02 12:13:26 +00:00
|
|
|
return (
|
2016-08-14 12:22:53 +00:00
|
|
|
datetime(now.year, now.month, now.day, 0, 0, 0),
|
2020-06-05 22:03:37 +00:00
|
|
|
datetime(now.year, now.month, now.day, 0, 0, 0) + almost_one_day,
|
2017-02-03 20:42:15 +00:00
|
|
|
)
|
2016-04-02 12:13:26 +00:00
|
|
|
|
2020-06-05 22:03:37 +00:00
|
|
|
if value == "yesterday":
|
2016-08-14 12:22:53 +00:00
|
|
|
now = datetime.utcnow()
|
2016-04-02 12:13:26 +00:00
|
|
|
return (
|
2016-08-14 12:22:53 +00:00
|
|
|
datetime(now.year, now.month, now.day, 0, 0, 0) - one_day,
|
2020-06-05 22:03:37 +00:00
|
|
|
datetime(now.year, now.month, now.day, 0, 0, 0) - one_second,
|
|
|
|
)
|
2016-04-02 12:13:26 +00:00
|
|
|
|
2020-06-05 22:03:37 +00:00
|
|
|
match = re.match(r"^(\d{4})$", value)
|
2016-04-02 12:13:26 +00:00
|
|
|
if match:
|
|
|
|
year = int(match.group(1))
|
2016-08-14 12:22:53 +00:00
|
|
|
return (datetime(year, 1, 1), datetime(year + 1, 1, 1) - one_second)
|
2016-04-02 12:13:26 +00:00
|
|
|
|
2020-06-05 22:03:37 +00:00
|
|
|
match = re.match(r"^(\d{4})-(\d{1,2})$", value)
|
2016-04-02 12:13:26 +00:00
|
|
|
if match:
|
|
|
|
year = int(match.group(1))
|
|
|
|
month = int(match.group(2))
|
|
|
|
return (
|
2016-08-14 12:22:53 +00:00
|
|
|
datetime(year, month, 1),
|
2020-06-05 22:03:37 +00:00
|
|
|
datetime(year, month + 1, 1) - one_second,
|
|
|
|
)
|
2016-04-02 12:13:26 +00:00
|
|
|
|
2020-06-05 22:03:37 +00:00
|
|
|
match = re.match(r"^(\d{4})-(\d{1,2})-(\d{1,2})$", value)
|
2016-04-02 12:13:26 +00:00
|
|
|
if match:
|
|
|
|
year = int(match.group(1))
|
|
|
|
month = int(match.group(2))
|
|
|
|
day = int(match.group(3))
|
|
|
|
return (
|
2016-08-14 12:22:53 +00:00
|
|
|
datetime(year, month, day),
|
2020-06-05 22:03:37 +00:00
|
|
|
datetime(year, month, day + 1) - one_second,
|
|
|
|
)
|
2016-04-02 12:13:26 +00:00
|
|
|
|
2020-06-05 22:03:37 +00:00
|
|
|
raise errors.ValidationError("Invalid date format: %r." % value)
|
2016-04-15 20:49:55 +00:00
|
|
|
|
2016-08-14 12:22:53 +00:00
|
|
|
|
2017-02-04 00:08:12 +00:00
|
|
|
def icase_unique(source: List[str]) -> List[str]:
|
|
|
|
target = [] # type: List[str]
|
|
|
|
target_low = [] # type: List[str]
|
2016-04-15 20:49:55 +00:00
|
|
|
for source_item in source:
|
|
|
|
if source_item.lower() not in target_low:
|
|
|
|
target.append(source_item)
|
|
|
|
target_low.append(source_item.lower())
|
|
|
|
return target
|
2016-04-16 13:07:33 +00:00
|
|
|
|
2016-08-14 12:22:53 +00:00
|
|
|
|
2017-02-04 00:08:12 +00:00
|
|
|
def value_exceeds_column_size(value: Optional[str], column: Any) -> bool:
|
2016-04-30 21:17:08 +00:00
|
|
|
if not value:
|
|
|
|
return False
|
|
|
|
max_length = column.property.columns[0].type.length
|
|
|
|
if max_length is None:
|
|
|
|
return False
|
|
|
|
return len(value) > max_length
|
2016-11-27 17:42:14 +00:00
|
|
|
|
|
|
|
|
2018-02-25 10:44:02 +00:00
|
|
|
def get_column_size(column: Any) -> Optional[int]:
|
|
|
|
if not column:
|
|
|
|
return None
|
|
|
|
return column.property.columns[0].type.length
|
|
|
|
|
|
|
|
|
2017-02-04 00:08:12 +00:00
|
|
|
def chunks(source_list: List[Any], part_size: int) -> Generator:
|
2016-11-27 17:42:14 +00:00
|
|
|
for i in range(0, len(source_list), part_size):
|
2020-06-05 22:03:37 +00:00
|
|
|
yield source_list[i : i + part_size]
|