Skip to content

Commit d4fa3aa

Browse files
committed
UPDATE: major code refactoring
1 parent 634a8c2 commit d4fa3aa

File tree

19 files changed

+877
-267
lines changed

19 files changed

+877
-267
lines changed

.idea/dbnavigator.xml

Lines changed: 458 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/misc.xml

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/moneyTrackerBot.iml

Lines changed: 3 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
# Description
2+
23
This is the first version of my telegram bot which allows a simple expenses tracker.
34

45
The bot can now take income and outcome and display the current month resume; here is a list of commands:
6+
57
* `/listoutcome` print the list of current month outcome
68
* `/listincome` print the list of current month income
79
* `/delete +/-<income/outcome amount> <comment>` delete the specified entry
@@ -10,8 +12,9 @@ The bot can now take income and outcome and display the current month resume; he
1012
* `/outcome <outcome amount> <comment>` add to the outcome table the amount and the comment associated
1113

1214
## Installation
13-
* First create the `master.txt` and `token.txt` files, filled respectively with your master chat id
14-
and telegram bot token
15+
16+
* First create the `master.txt` and `token.txt` files, filled respectively with your master chat id
17+
and telegram bot token
1518
* `Install docker`
1619
* Go into the clone folder and run `docker build moneytrakerBot/`
1720
* Copy the container id

__main__.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import argparse
2+
import logging.config
3+
import os
4+
5+
import yaml
6+
7+
from configurator import Config
8+
9+
LOGGER = logging.getLogger(__name__)
10+
11+
if __name__ == '__main__':
12+
parser = argparse.ArgumentParser(prog='vlc-backend', description='vlc-backend application')
13+
14+
parser.add_argument('-c', '--config_dir', metavar='configuration_directory',
15+
required=False, default="config", type=str,
16+
help='Configuration directory to use; '
17+
'if not specified, default config directory will be used')
18+
19+
# Configuration file
20+
parser.add_argument('-f', '--file_app', metavar='configuration_file',
21+
required=False, default="application.ini", type=str,
22+
help='Configuration file to use; '
23+
'if not specified, default configs will be used')
24+
25+
cmd_args = parser.parse_args()
26+
27+
with open(os.path.join(cmd_args.config_dir, "log.yaml"), 'rt') as f:
28+
log_file = yaml.safe_load(f.read())
29+
logging.config.dictConfig(log_file)
30+
LOGGER.info(f"Loaded logging configuration from log.yaml")
31+
32+
Config.init(cmd_args.config_dir, cmd_args.file_app)
33+
34+
from telegram.bot import MoneyTrackerBot
35+
bot = MoneyTrackerBot()
36+
bot.start()

bot.py

Lines changed: 0 additions & 123 deletions
This file was deleted.

config/application.ini

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[TELEGRAM]
2+
#token_position=config/token.txt

config/log.yaml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
version: 1
2+
disable_existing_loggers: False
3+
formatters:
4+
simple:
5+
format: "%(levelname) -10s %(asctime)s %(name) -30s %(funcName) -20s %(lineno) -5d: %(message)s"
6+
7+
handlers:
8+
console:
9+
class: logging.StreamHandler
10+
formatter: simple
11+
stream: ext://sys.stdout
12+
13+
loggers:
14+
__main__:
15+
level: DEBUG
16+
handlers: [ console ]
17+
propagate: no
18+
telegram:
19+
level: DEBUG
20+
handlers: [ console ]
21+
propagate: no
22+
db_manager:
23+
level: DEBUG
24+
handlers: [console]
25+
propagate: no

configurator/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .config import Config

configurator/config.py

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import collections
2+
import configparser
3+
import distutils.util
4+
import logging
5+
import os
6+
7+
import expandvars
8+
9+
from . import exceptions
10+
11+
LOGGER = logging.getLogger(__name__)
12+
13+
_UNSET = object()
14+
15+
16+
class Config:
17+
class ConfigInterpolation(configparser.BasicInterpolation):
18+
19+
def before_get(self, parser, section, option, value, defaults):
20+
return expandvars.expandvars(value)
21+
22+
instances = list()
23+
parser = configparser.ConfigParser(interpolation=ConfigInterpolation())
24+
directory = None
25+
file_names = None
26+
27+
def __init__(self, data_type, section, option=None, fallback=_UNSET):
28+
self.__class__.instances.append(self)
29+
self._data_type = data_type
30+
self._is_none = False
31+
self._section = section
32+
self._option = option
33+
self._fallback = fallback
34+
self._gen_value = _UNSET
35+
36+
@staticmethod
37+
def init(directory, *file_names, ignore_error=False):
38+
# Set the directory of the configuration files to use
39+
Config.directory = directory
40+
# Load the configuration files
41+
Config.load(*file_names, ignore_error=ignore_error)
42+
43+
@staticmethod
44+
def path(target=""):
45+
yield os.path.join(Config.directory, target)
46+
47+
@staticmethod
48+
def dir(target=""):
49+
return os.path.join(Config.directory, target)
50+
51+
@staticmethod
52+
def load(*file_names, encoding=None, ignore_error=False):
53+
# If configuration should only be reloaded
54+
if not file_names: file_names = Config.file_names
55+
# Set file names
56+
Config.file_names = file_names
57+
# Build the real path of each configuration file
58+
config_file_paths = [os.path.join(Config.directory, file_name) for file_name in file_names]
59+
# Check if the file exists
60+
valid_file_paths = []
61+
for config_file_path in config_file_paths:
62+
if not os.path.isfile(config_file_path):
63+
if not ignore_error:
64+
raise exceptions.ConfigFileDoesNotExists(config_file_path)
65+
else:
66+
valid_file_paths.append(config_file_path)
67+
# Parse and read the configurations
68+
Config.parser.read(valid_file_paths, encoding)
69+
70+
def get(self, option=None, **kwargs):
71+
if self._is_none:
72+
return None
73+
option = self._option if option is None else option
74+
if self._fallback is _UNSET:
75+
value = Config.parser.get(self._section, option)
76+
else:
77+
value = Config.parser.get(self._section, option, fallback=self._fallback)
78+
if value == '':
79+
value = self._fallback
80+
# Check the value
81+
if value is None:
82+
return None
83+
if isinstance(value, collections.Generator):
84+
if self._gen_value is _UNSET:
85+
self._gen_value = next(value)
86+
return self._gen_value
87+
# Check data type
88+
if isinstance(self._data_type, type):
89+
if self._data_type == bool:
90+
if isinstance(value, bool):
91+
return value
92+
elif isinstance(value, str):
93+
return bool(distutils.util.strtobool(value))
94+
elif self._data_type == str and kwargs:
95+
value = str(value).format(**kwargs)
96+
elif isinstance(self._data_type, tuple) and isinstance(value, str):
97+
collection, data_type = self._data_type
98+
if collection == list:
99+
return [data_type(v) for v in value.split(",")]
100+
# Return the casted value
101+
return self._data_type(value)
102+
103+
def set(self, value):
104+
if value is not None:
105+
if not isinstance(value, self._data_type):
106+
raise exceptions.ValueTypeNotAllowed(self._option, type(self._data_type).__name__)
107+
self._is_none = False
108+
value = self._data_type(value)
109+
else:
110+
self._is_none = True
111+
return
112+
if self._section not in Config.parser.sections():
113+
Config.parser.add_section(self._section)
114+
try:
115+
Config.parser.set(self._section, self._option, str(value))
116+
except Exception as e:
117+
raise exceptions.SetValueError(value, str(e))
118+
119+
@staticmethod
120+
def update(data):
121+
for section, data in data.items():
122+
if section not in Config.parser.sections():
123+
Config.parser.add_section(section)
124+
for option, value in data:
125+
Config.parser.set(section, option, value)
126+
127+
@staticmethod
128+
def save(filename):
129+
with open(Config.dir(filename), 'w') as f:
130+
Config.parser.write(f)
131+
132+
@staticmethod
133+
def data():
134+
return {s: Config.parser.items(s) for s in Config.parser.sections()}

0 commit comments

Comments
 (0)