Skip to content

Commit

Permalink
Simplify config module
Browse files Browse the repository at this point in the history
Access to configuration values is now done through an instance of lazy
interface to typed environment variables, which allows not to update
this module every time a new variable is added (unless it must have a
default value). From now on, addition of new variables will be done
primarily just by appending them to .env.example.

Signed-off-by: alfred richardsn <rchrdsn@protonmail.ch>
  • Loading branch information
r4rdsn committed Mar 13, 2020
1 parent 0ffe91d commit 0b8dad1
Show file tree
Hide file tree
Showing 14 changed files with 95 additions and 104 deletions.
3 changes: 0 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
# Project files
config.ini

# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
Expand Down
10 changes: 5 additions & 5 deletions src/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
from src import notifications
from src.bot import dp
from src.bot import tg
from src.config import Config
from src.config import config
from src.escrow import connect_to_blockchains


Expand All @@ -34,7 +34,7 @@ async def on_startup(webhook_path, *args):
Set webhook and run background tasks.
"""
await tg.delete_webhook()
await tg.set_webhook("https://" + Config.SERVER_HOST + webhook_path)
await tg.set_webhook("https://" + config.SERVER_HOST + webhook_path)
asyncio.create_task(notifications.run_loop())
asyncio.create_task(connect_to_blockchains())

Expand All @@ -45,15 +45,15 @@ def main():
Bot's main entry point.
"""
url_token = secrets.token_urlsafe()
webhook_path = Config.WEBHOOK_PATH + "/" + url_token
webhook_path = config.WEBHOOK_PATH + "/" + url_token

bot.setup()
executor.start_webhook(
dispatcher=dp,
webhook_path=webhook_path,
on_startup=lambda *args: on_startup(webhook_path, *args),
host=Config.INTERNAL_HOST,
port=Config.SERVER_PORT,
host=config.INTERNAL_HOST,
port=config.SERVER_PORT,
)
print() # noqa: T001 Executor stopped with ^C

Expand Down
8 changes: 4 additions & 4 deletions src/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
from aiogram.contrib.middlewares.logging import LoggingMiddleware
from aiogram.dispatcher import Dispatcher

from src.config import Config
from src.config import config
from src.database import MongoStorage
from src.i18n import i18n

Expand All @@ -33,16 +33,16 @@


def setup():
"""Set API token from Config to bot and setup dispatcher."""
with open(Config.TOKEN_FILENAME, "r") as token_file:
"""Set API token from config to bot and setup dispatcher."""
with open(config.TOKEN_FILENAME, "r") as token_file:
tg._ctx_token.set(token_file.read().strip())

dp.storage = MongoStorage()

i18n.reload()
dp.middleware.setup(i18n)

logging.basicConfig(level=Config.LOGGER_LEVEL)
logging.basicConfig(level=config.LOGGER_LEVEL)
dp.middleware.setup(LoggingMiddleware())


Expand Down
82 changes: 38 additions & 44 deletions src/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,58 +17,52 @@
from os import getenv


def getenv_int(key, default=None):
"""Convert the value of the environment variable key to an integer."""
env = getenv(key)
try:
return int(env)
except (TypeError, ValueError):
return default
DEFAULT_VALUES = {
"INTERNAL_HOST": "127.0.0.1",
"DATABASE_HOST": "127.0.0.1",
"DATABASE_PORT": 27017,
"DATABASE_NAME": "tellerbot",
"ESCROW_ENABLED": True,
}


def getenv_bool(key, default=None):
"""Convert the value of the environment variable key to a boolean."""
def get_typed_env(key):
"""Get an environment variable with inferred type."""
env = getenv(key)
return env == "true" if env in ("true", "false") else default
if env is None:
return None
elif env == "true":
return True
elif env == "false":
return False
try:
return int(env)
except ValueError:
return env


class Config:
"""Data holder for configuration values."""

TOKEN_FILENAME: str
INTERNAL_HOST: str = "127.0.0.1"
SERVER_HOST: str
SERVER_PORT: int
WEBHOOK_PATH: str
DATABASE_HOST: str = "127.0.0.1"
DATABASE_PORT: int = 27017
DATABASE_USERNAME: str
DATABASE_PASSWORD_FILENAME: str
DATABASE_NAME: str = "tellerbot"

LOGGER_LEVEL: str
LOG_FILENAME: str
"""Lazy interface to configuration values."""

SUPPORT_CHAT_ID: int
EXCEPTIONS_CHAT_ID: int
def __setattr__(self, name, value):
"""Set configuration value."""
super().__setattr__(name, value)

ORDERS_COUNT: int
ORDERS_LIMIT_HOURS: int
ORDERS_LIMIT_COUNT: int
ORDER_DURATION_LIMIT: int
def __getattr__(self, name):
"""Get configuration value.
ESCROW_FEE_PERCENTS: int
ESCROW_ENABLED: bool = True
WIF_FILENAME: str
OP_CHECK_TIMEOUT_HOURS: int
Return value of environment variable ``name`` if it is set or
default value otherwise.
"""
env = get_typed_env(name)
if env is not None:
value = env
elif name not in DEFAULT_VALUES:
raise AttributeError(f"config has no option '{name}'")
else:
value = DEFAULT_VALUES[name]
setattr(self, name, value)
return value


for name, annotation in Config.__annotations__.items():
if annotation == int:
value = getenv_int(name)
elif annotation == bool:
value = getenv_bool(name)
else:
value = getenv(name)
if value is not None:
setattr(Config, name, value)
config = Config()
14 changes: 7 additions & 7 deletions src/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,20 @@
from aiogram.dispatcher.storage import BaseStorage
from motor.motor_asyncio import AsyncIOMotorClient

from src.config import Config
from src.config import config


try:
with open(Config.DATABASE_PASSWORD_FILENAME, "r") as password_file:
with open(config.DATABASE_PASSWORD_FILENAME, "r") as password_file:
client = AsyncIOMotorClient(
Config.DATABASE_HOST,
Config.DATABASE_PORT,
username=Config.DATABASE_USERNAME,
config.DATABASE_HOST,
config.DATABASE_PORT,
username=config.DATABASE_USERNAME,
password=password_file.read(),
)
except (AttributeError, FileNotFoundError):
client = AsyncIOMotorClient(Config.DATABASE_HOST)
database = client[Config.DATABASE_NAME]
client = AsyncIOMotorClient(config.DATABASE_HOST)
database = client[config.DATABASE_NAME]

STATE_KEY = "state"

Expand Down
4 changes: 2 additions & 2 deletions src/escrow/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@
#
# You should have received a copy of the GNU Affero General Public License
# along with TellerBot. If not, see <https://www.gnu.org/licenses/>.
from src.config import Config
from src.config import config


if Config.ESCROW_ENABLED:
if config.ESCROW_ENABLED:
from src.escrow.blockchain.golos_blockchain import GolosBlockchain

SUPPORTED_BLOCKCHAINS = [GolosBlockchain()]
Expand Down
4 changes: 2 additions & 2 deletions src/escrow/blockchain/golos_blockchain.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
from golos.exceptions import TransactionNotFound
from golos.ws_client import error_handler

from src.config import Config
from src.config import config
from src.database import database
from src.escrow.blockchain import BaseBlockchain
from src.escrow.blockchain import BlockchainConnectionError
Expand Down Expand Up @@ -115,7 +115,7 @@ async def get_limits(self, asset: str):
return limits.get(asset)

async def transfer(self, to: str, amount: Decimal, asset: str, memo: str = ""):
with open(Config.WIF_FILENAME) as wif_file:
with open(config.WIF_FILENAME) as wif_file:
transaction = await get_running_loop().run_in_executor(
None,
self._golos.transfer,
Expand Down
4 changes: 2 additions & 2 deletions src/handlers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

from src.bot import dp
from src.bot import tg
from src.config import Config
from src.config import config
from src.handlers import escrow # noqa: F401, noreorder
from src.handlers import start_menu # noqa: F401, noreorder
from src.handlers import creation # noqa: F401
Expand Down Expand Up @@ -80,7 +80,7 @@ async def errors_handler(update: types.Update, exception: Exception):

if chat_id is not None:
try:
exceptions_chat_id = Config.EXCEPTIONS_CHAT_ID
exceptions_chat_id = config.EXCEPTIONS_CHAT_ID
except AttributeError:
pass
else:
Expand Down
14 changes: 7 additions & 7 deletions src/handlers/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
from aiogram.utils.emoji import emojize
from pymongo.cursor import Cursor

from src.config import Config
from src.config import config
from src.escrow import get_escrow_instance

from src.bot import ( # noqa: F401, noreorder
Expand Down Expand Up @@ -110,19 +110,19 @@ async def orders_list(
{"_id": user["_id"]}, {"$set": {"invert_book": invert}}
)

keyboard = types.InlineKeyboardMarkup(row_width=min(Config.ORDERS_COUNT // 2, 8))
keyboard = types.InlineKeyboardMarkup(row_width=min(config.ORDERS_COUNT // 2, 8))

inline_orders_buttons = (
types.InlineKeyboardButton(
emojize(":arrow_left:"),
callback_data="{} {} {}".format(
buttons_data, start - Config.ORDERS_COUNT, int(invert)
buttons_data, start - config.ORDERS_COUNT, int(invert)
),
),
types.InlineKeyboardButton(
emojize(":arrow_right:"),
callback_data="{} {} {}".format(
buttons_data, start + Config.ORDERS_COUNT, int(invert)
buttons_data, start + config.ORDERS_COUNT, int(invert)
),
),
)
Expand All @@ -136,7 +136,7 @@ async def orders_list(
await tg.edit_message_text(text, chat_id, message_id, reply_markup=keyboard)
return

all_orders = await cursor.to_list(length=start + Config.ORDERS_COUNT)
all_orders = await cursor.to_list(length=start + config.ORDERS_COUNT)
orders = all_orders[start:]

lines = []
Expand Down Expand Up @@ -197,8 +197,8 @@ async def orders_list(
text = (
"\\["
+ i18n("page {number} {total}").format(
number=math.ceil(start / Config.ORDERS_COUNT) + 1,
total=math.ceil(quantity / Config.ORDERS_COUNT),
number=math.ceil(start / config.ORDERS_COUNT) + 1,
total=math.ceil(quantity / config.ORDERS_COUNT),
)
+ "]\n"
+ "\n".join(lines)
Expand Down
18 changes: 9 additions & 9 deletions src/handlers/creation.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
from src import whitelist
from src.bot import dp
from src.bot import tg
from src.config import Config
from src.config import config
from src.database import database
from src.handlers.base import inline_control_buttons
from src.handlers.base import private_handler
Expand Down Expand Up @@ -244,14 +244,14 @@ async def whitelisting_request(call: types.CallbackQuery):
currency, len(request["users"]) + 1
)
if len(request["users"]) == 1:
message = await tg.send_message(Config.SUPPORT_CHAT_ID, support_text)
message = await tg.send_message(config.SUPPORT_CHAT_ID, support_text)
await database.whitelisting_requests.update_one(
{"_id": request["_id"]},
{"$set": {"message_id": message.message_id}},
)
else:
await tg.edit_message_text(
support_text, Config.SUPPORT_CHAT_ID, request["message_id"],
support_text, config.SUPPORT_CHAT_ID, request["message_id"],
)

await call.answer()
Expand Down Expand Up @@ -668,7 +668,7 @@ async def text_location(message: types.Message, state: FSMContext):
await OrderCreation.duration.set()
await tg.send_message(
message.chat.id,
i18n("ask_duration {limit}").format(limit=Config.ORDER_DURATION_LIMIT),
i18n("ask_duration {limit}").format(limit=config.ORDER_DURATION_LIMIT),
reply_markup=InlineKeyboardMarkup(
inline_keyboard=await inline_control_buttons()
),
Expand Down Expand Up @@ -718,7 +718,7 @@ async def choose_location(message: types.Message, state: FSMContext):
await OrderCreation.duration.set()
await tg.send_message(
message.chat.id,
i18n("ask_duration {limit}").format(limit=Config.ORDER_DURATION_LIMIT),
i18n("ask_duration {limit}").format(limit=config.ORDER_DURATION_LIMIT),
reply_markup=InlineKeyboardMarkup(
inline_keyboard=await inline_control_buttons()
),
Expand All @@ -729,7 +729,7 @@ async def choose_location(message: types.Message, state: FSMContext):
async def duration_handler(call: types.CallbackQuery):
"""Ask for duration."""
await tg.edit_message_text(
i18n("ask_duration {limit}").format(limit=Config.ORDER_DURATION_LIMIT),
i18n("ask_duration {limit}").format(limit=config.ORDER_DURATION_LIMIT),
call.message.chat.id,
call.message.message_id,
reply_markup=InlineKeyboardMarkup(
Expand All @@ -749,11 +749,11 @@ async def choose_duration(message: types.Message, state: FSMContext):
await tg.send_message(message.chat.id, i18n("send_natural_number"))
return

if duration > Config.ORDER_DURATION_LIMIT:
if duration > config.ORDER_DURATION_LIMIT:
await tg.send_message(
message.chat.id,
i18n("exceeded_duration_limit {limit}").format(
limit=Config.ORDER_DURATION_LIMIT
limit=config.ORDER_DURATION_LIMIT
),
)
return
Expand All @@ -776,7 +776,7 @@ async def set_order(order: MutableMapping[str, Any], chat_id: int):
"""Set missing values and finish order creation."""
order["start_time"] = time()
if "duration" not in order:
order["duration"] = Config.ORDER_DURATION_LIMIT
order["duration"] = config.ORDER_DURATION_LIMIT
order["expiration_time"] = time() + order["duration"] * 24 * 60 * 60
order["notify"] = True
if "price_sell" not in order and "sum_buy" in order and "sum_sell" in order:
Expand Down

0 comments on commit 0b8dad1

Please sign in to comment.