Coverage for visailu/__init__.py: 100.00%
52 statements
« prev ^ index » next coverage.py v7.4.1, created at 2024-02-05 19:49:28 +00:00
« prev ^ index » next coverage.py v7.4.1, created at 2024-02-05 19:49:28 +00:00
1"""Quiz (Finnish: visailu) data operations."""
3import datetime as dti
4import logging
5import os
6import pathlib
7from typing import List, no_type_check
9# [[[fill git_describe()]]]
10__version__ = '2023.8.27+parent.gf8ac9e03'
11# [[[end]]] (checksum: 55c93108a923ec560e6be75c4304454e)
12__version_info__ = tuple(
13 e if '-' not in e else e.split('-')[0] for part in __version__.split('+') for e in part.split('.') if e != 'parent'
14)
16APP_ALIAS = str(pathlib.Path(__file__).parent.name)
17APP_ENV = APP_ALIAS.upper()
18APP_NAME = locals()['__doc__']
19DEBUG = bool(os.getenv(f'{APP_ENV}_DEBUG', ''))
20VERBOSE = bool(os.getenv(f'{APP_ENV}_VERBOSE', ''))
21QUIET = False
22STRICT = bool(os.getenv(f'{APP_ENV}_STRICT', ''))
23ENCODING = 'utf-8'
24ENCODING_ERRORS_POLICY = 'ignore'
25DEFAULT_CONFIG_NAME = f'.{APP_ALIAS}.json'
26log = logging.getLogger() # Module level logger is sufficient
27LOG_FOLDER = pathlib.Path('logs')
28LOG_FILE = f'{APP_ALIAS}.log'
29LOG_PATH = pathlib.Path(LOG_FOLDER, LOG_FILE) if LOG_FOLDER.is_dir() else pathlib.Path(LOG_FILE)
30LOG_LEVEL = logging.INFO
32TS_FORMAT_LOG = '%Y-%m-%dT%H:%M:%S'
33TS_FORMAT_PAYLOADS = '%Y-%m-%d %H:%M:%S.%f UTC'
35VERSION = __version__
36VERSION_INFO = __version_info__
38# simplistic initial output expectations:
39OUT_QUESTION_COUNT = 10
40OUT_ANSWERS_COUNT = 4
42# messages registry:
43INVALID_YAML_RESOURCE = 'is invalid yaml or the resource is inaccessible'
44MODEL_META_INVALID_DEFAULTS = 'contains invalid defaults for scale in meta'
45MODEL_META_INVALID_RANGE = 'contains an invalid range of scale in meta'
46MODEL_META_INVALID_RANGE_VALUE = 'contains an invalid default value for the scale'
47MODEL_QUESTION_ANSWER_MISSING = 'misses an answer'
48MODEL_QUESTION_ANSWER_MISSING_RATING = 'misses a rating for an answer'
49MODEL_QUESTION_INCOMPLETE = 'has incomplete questions'
50MODEL_QUESTION_INVALID_RANGE = 'contains an invalid range value for the scale'
51MODEL_QUESTION_INVALID_RANGE_VALUE = 'contains an invalid answer rating for the scale'
52MODEL_STRUCTURE_UNEXPECTED = 'has unexpected model structure'
53MODEL_VALUES_MISSING = 'misses model values'
56__all__: List[str] = [
57 'APP_ALIAS',
58 'APP_ENV',
59 'APP_NAME',
60 'DEBUG',
61 'DEFAULT_CONFIG_NAME',
62 'DEFAULT_STRUCTURE_NAME',
63 'ENCODING',
64 'INVALID_YAML_RESOURCE',
65 'MODEL_META_INVALID_DEFAULTS',
66 'MODEL_META_INVALID_RANGE',
67 'MODEL_META_INVALID_RANGE_VALUE',
68 'MODEL_QUESTION_ANSWER_MISSING',
69 'MODEL_QUESTION_ANSWER_MISSING_RATING',
70 'MODEL_QUESTION_INCOMPLETE',
71 'MODEL_QUESTION_INVALID_RANGE',
72 'MODEL_QUESTION_INVALID_RANGE_VALUE',
73 'MODEL_STRUCTURE_UNEXPECTED',
74 'MODEL_VALUES_MISSING',
75 'OUT_QUESTION_COUNT',
76 'OUT_ANSWERS_COUNT',
77 'log',
78]
81def slugify(text: str) -> str:
82 """Remove newlines and reduce multiple spaces to single spaces."""
83 return ' '.join(text.replace('\n', ' ').split())
86@no_type_check
87def formatTime_RFC3339(self, record, datefmt=None): # noqa
88 """HACK A DID ACK we could inject .astimezone() to localize ..."""
89 return dti.datetime.fromtimestamp(record.created, dti.timezone.utc).isoformat() # pragma: no cover
92@no_type_check
93def init_logger(name=None, level=None):
94 """Initialize module level logger"""
95 global log # pylint: disable=global-statement
97 log_format = {
98 'format': '%(asctime)s %(levelname)s [%(name)s]: %(message)s',
99 'datefmt': TS_FORMAT_LOG,
100 # 'filename': LOG_PATH,
101 'level': LOG_LEVEL if level is None else level,
102 }
103 logging.Formatter.formatTime = formatTime_RFC3339
104 logging.basicConfig(**log_format)
105 log = logging.getLogger(APP_ENV if name is None else name)
106 log.propagate = True
109init_logger(name=APP_ENV, level=logging.DEBUG if DEBUG else None)