diff --git a/.gitignore b/.gitignore index 1813f2f..32ec512 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,7 @@ .vscode __pycache__ +.mypy_cache + venv .env diff --git a/main.py b/main.py index df85bb2..e48d954 100644 --- a/main.py +++ b/main.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python3 + import json import hashlib @@ -8,11 +10,11 @@ import requests from pydantic import BaseSettings -from aiogram import Bot -from aiogram.dispatcher import Dispatcher -from aiogram.utils import executor +from aiogram import Bot # type: ignore +from aiogram.dispatcher import Dispatcher # type: ignore +from aiogram.utils import executor # type: ignore -from aiogram.types import InlineQuery +from aiogram.types import InlineQuery # type: ignore from aiogram.types import InlineQueryResultArticle from aiogram.types import InputTextMessageContent @@ -22,9 +24,11 @@ DDG_URL = 'https://duckduckgo.com/js/spice/currency' COINAPI_URL = 'https://rest.coinapi.io/v1/exchangerate' # --- + # Config from .env class Settings(BaseSettings): debug: bool + timeout: int = 2 coinapi_key: str telegram_token: str @@ -32,9 +36,11 @@ class Settings(BaseSettings): env_file = '.env' env_file_encoding = 'utf-8' -settings = Settings() + +settings = Settings() # type: ignore # --- + # Logging log = logging.getLogger('shirino') handler = logging.StreamHandler() @@ -48,28 +54,29 @@ if settings.debug: log.setLevel(logging.DEBUG) # --- + bot = Bot(token=settings.telegram_token) dp = Dispatcher(bot) class CurrencyConverter: - + def __init__(self) -> None: - + self.amount = 1.0 self.conv_amount = 0.0 self.from_currency = '' self.conv_currency = '' - + def convert(self) -> None: """Currency conversion""" if not self.ddgapi(): self.coinapi() - + def ddgapi(self) -> bool: """Get data from DuckDuckGo's currency API - + Returns: `False` if the currency does not exist, `True` on successful conversion @@ -77,16 +84,19 @@ class CurrencyConverter: # API request resp = requests.get( - f'{DDG_URL}/{self.amount}/{self.from_currency}' - f'/{self.conv_currency}' + ( + f'{DDG_URL}/{self.amount}/{self.from_currency}' + f'/{self.conv_currency}' + ), + timeout=settings.timeout, ) log.debug(resp.text) # Parsing JSON data data: Dict[str, Any] = json.loads( - resp.text \ - .replace('ddg_spice_currency(', '') \ + resp.text + .replace('ddg_spice_currency(', '') .replace(');', '') ) @@ -104,7 +114,7 @@ class CurrencyConverter: log.debug(conv) return True - + def coinapi(self) -> None: """Get data from CoinAPI (for cryptocurrencies)""" @@ -116,6 +126,7 @@ class CurrencyConverter: headers={ 'X-CoinAPI-Key': settings.coinapi_key, }, + timeout=settings.timeout, ) data: Dict[str, Any] = resp.json() diff --git a/mypy.ini b/mypy.ini new file mode 100644 index 0000000..192ff32 --- /dev/null +++ b/mypy.ini @@ -0,0 +1,5 @@ +[mypy] +check_untyped_defs = True +warn_return_any = True +warn_unreachable = True +show_error_codes = True diff --git a/pylintrc b/pylintrc new file mode 100644 index 0000000..d554eae --- /dev/null +++ b/pylintrc @@ -0,0 +1,193 @@ +[MAIN] +analyse-fallback-blocks=no +clear-cache-post-run=no +extension-pkg-allow-list= +extension-pkg-whitelist= +fail-on= +fail-under=10 +ignore=CVS +ignore-paths= +ignore-patterns=^\.# +ignored-modules= +jobs=4 +limit-inference-results=100 +load-plugins= +persistent=yes +py-version=3.10 +recursive=no +suggestion-mode=yes +unsafe-load-any-extension=no + +[BASIC] +argument-naming-style=snake_case +attr-naming-style=snake_case +bad-names=foo, + bar, + baz, + toto, + tutu, + tata +bad-names-rgxs= +class-attribute-naming-style=any +class-const-naming-style=UPPER_CASE +class-naming-style=PascalCase +const-naming-style=UPPER_CASE +docstring-min-length=-1 +function-naming-style=snake_case +good-names=i, + j, + k, + ex, + Run, + _ +good-names-rgxs= +include-naming-hint=no +inlinevar-naming-style=any +method-naming-style=snake_case +module-naming-style=snake_case +name-group= +no-docstring-rgx=^_ +property-classes=abc.abstractproperty +variable-naming-style=snake_case + +[CLASSES] +check-protected-access-in-special-methods=no +defining-attr-methods=__init__, + __new__, + setUp, + __post_init__ +exclude-protected=_asdict, + _fields, + _replace, + _source, + _make +valid-classmethod-first-arg=cls +valid-metaclass-classmethod-first-arg=mcs + +[DESIGN] +exclude-too-few-public-methods= +ignored-parents= +max-args=5 +max-attributes=7 +max-bool-expr=5 +max-branches=12 +max-locals=15 +max-parents=7 +max-public-methods=20 +max-returns=6 +max-statements=50 +min-public-methods=0 + +[EXCEPTIONS] +overgeneral-exceptions=builtins.BaseException,builtins.Exception + +[FORMAT] +expected-line-ending-format= +ignore-long-lines=^\s*(# )??$ +indent-after-paren=4 +indent-string=' ' +max-line-length=100 +max-module-lines=1000 +single-line-class-stmt=no +single-line-if-stmt=no + +[IMPORTS] +allow-any-import-level= +allow-reexport-from-package=no +allow-wildcard-with-all=no +deprecated-modules= +ext-import-graph= +import-graph= +int-import-graph= +known-standard-library= +known-third-party=enchant +preferred-modules= + +[LOGGING] +logging-format-style=old +logging-modules=logging + +[MESSAGES CONTROL] +confidence=HIGH, + CONTROL_FLOW, + INFERENCE, + INFERENCE_FAILURE, + UNDEFINED +disable=raw-checker-failed, + bad-inline-option, + locally-disabled, + file-ignored, + suppressed-message, + useless-suppression, + deprecated-pragma, + use-symbolic-message-instead, + missing-module-docstring, + missing-class-docstring, + missing-function-docstring, + broad-exception-caught +enable=c-extension-no-member + +[METHOD_ARGS] +timeout-methods=requests.api.delete,requests.api.get,requests.api.head,requests.api.options,requests.api.patch,requests.api.post,requests.api.put,requests.api.request + +[MISCELLANEOUS] +notes=FIXME, + XXX, + TODO +notes-rgx= + +[REFACTORING] +max-nested-blocks=5 +never-returning-functions=sys.exit,argparse.parse_error + +[REPORTS] +evaluation=max(0, 0 if fatal else 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)) +msg-template= +reports=no +score=yes + +[SIMILARITIES] +ignore-comments=yes +ignore-docstrings=yes +ignore-imports=yes +ignore-signatures=yes +min-similarity-lines=4 + +[SPELLING] +max-spelling-suggestions=4 +spelling-dict= +spelling-ignore-comment-directives=fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy: +spelling-ignore-words= +spelling-private-dict-file= +spelling-store-unknown-words=no + +[STRING] +check-quote-consistency=no +check-str-concat-over-line-jumps=no + +[TYPECHECK] +contextmanager-decorators=contextlib.contextmanager +generated-members= +ignore-none=yes +ignore-on-opaque-inference=yes +ignored-checks-for-mixins=no-member, + not-async-context-manager, + not-context-manager, + attribute-defined-outside-init +ignored-classes=optparse.Values,thread._local,_thread._local,argparse.Namespace +missing-member-hint=yes +missing-member-hint-distance=1 +missing-member-max-choices=1 +mixin-class-rgx=.*[Mm]ixin +signature-mutators= + +[VARIABLES] +additional-builtins= +allow-global-unused-variables=yes +allowed-redefined-builtins= +callbacks=cb_, + _cb +dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ +ignored-argument-names=_.*|^ignored_|^unused_ +init-import=no +redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io