Шаблон адаптера предоставляет минимальный набор функций для создания программы/скрипта для сбора, анализа и загрузки данных в MongoDB.
Для более управляемого поведения требуется использовать виртуальную среду. Можно использовать любой доступный вариант, но рекомендуется venv. Виртуальная среда избавит от проблем, связанных с изменениями в глобальном интерпретаторе.
Для работы программы требуется установить все зависимости.
Обновить pip и setuptools:
python -m pip install -U setuptools wheel pip
Установить необходимые зависимости:
pip install mongoengine requests
или
pip install -r requirements.txt
Затем, при необходимости, можно установить пользовательские пакеты. После завершения установки рекомендуется сохранить зависимости в специальном файле.
pip freeze > requirements.txt
adapter-template \
scripts \
config.py
database.py
utils.py
main.py
README.md
config.ini
log.log
requirements.txt
Адаптер сожержит в себе директорию со скриптами программы и основный скрипт main.py. Запуск main.py в первый раз создаст config.ini и log.log файлы. База данных не будет затронута, так как для этого требуется вызвать специальные функции.
Скрипт main.py связывает поведение функций адаптера. Рекомендуется использовать проверку __name__ == '__main__'.
Пример функции сбора данных. Операции внутри нее могут быть вынесены в отдельный файл. Декоратор utils.log будет записывать информацию в лог-файл. Декоратор utils.retry_if_none попытается перезапустить функцию, если на ее выходе было получено None.
@utils.log
@utils.retry_if_none
def get_joke():
response = requests.get("https://api.jokes.one/jod?category=knock-knock")
return None if response.status_code != 200 else response.json()
Пример подключения к базе данных с помощью monogoengine и скрипта scripts/database.py. Очевидно, что для работы требуется указать учетные данные в программе или в конфигурационном файле config.ini.
database.connect(config.get("Mongo", "name"),
host=config.get("Mongo", "host"),
port=int(config.get("Mongo", "port")),
username=config.get("Mongo", "username"),
password=config.get("Mongo", "password")
)
Пример вставки в базу данных. Перед любыми изменениями базы данных требуется подготовить модели данных внутри scripts/database.py.
data = get_joke()
for joke in data["contents"]["jokes"]:
joke_data = datetime.datetime.strptime(joke["joke"]["date"], "%Y-%m-%d")
joke_obj = database.Joke(text=joke["joke"]["text"], date=joke_data, category=joke["category"])
joke_obj.save()
config.ini - конфигурационный файл с ini-форматом. Прежде чем начать работу с базой данных и учетными данными, рекомендуется указать их данные в нем, после генерации файла.
Внутри scripts/config.py можно указать структуру конфигурационного файла. После формирования структуры при работе можно прописать всю нужную информацию уже внутри config.ini
Любая информация об аутентификации должна быть защищена и не должна быть записана в открытом доступе, например, внутри кода программы, также git-репозиторий не должен ее содержать.
import configparser
config = configparser.ConfigParser()
config["Credentials"] = {
"username": "-><-",
"password": "-><-",
}
with open('./config.ini', 'w') as configfile:
config.write(configfile)
Приведенный выше сценарий создаст новый ini-файл с этими данными внутри:
[Credentials]
username = -><-
password = -><-
Для удобного взаимодействия в адаптере используется mongoengine, специальный пакет позволяющий создать модели данных в формате классов, указав в них типы хранимых данных и другую информацию, в том числе указав ограничения (например, на уникальность).
from mongoengine import connect, Document
from mongoengine import IntField, StringField, DateTimeField
class Joke(Document):
text = StringField(required=True)
category = StringField()
date = DateTimeField()
def __repr__(self):
return f"<Joke {self.text}>"
В скрипте scripts/utils.py создается логгер и обработчик файлов для него. Здесь можно изменить формат логов.
import logging
from scripts.config import config
logger = logging.getLogger(config.get("Other", "log_name"))
logger.setLevel(logging.INFO)
fh = logging.FileHandler(config.get("Other", "log_name") + ".log")
formatter = logging.Formatter('[%(levelname)s] %(asctime)s %(name)s: %(message)s')
fh.setFormatter(formatter)
logger.addHandler(fh)
Для удобства в скрипте scripts/utils.py созданы декораторы, которые позволяют определить общий функционал для функций и использовать его без изменения кода функций.
from functools import wraps
import time
def log(f):
@wraps(f)
def wrap(*args, **kw):
ts = time.perf_counter()
logger.info(f"{f.__name__} started")
result = f(*args, **kw)
te = time.perf_counter()
logger.info(f'{f.__name__} took {te - ts:2.4f} sec')
return result
return wrap
def retry_if_none(f):
@wraps(f)
def wrap(*args, **kw):
result = None
while result is None:
result = f(*args, **kw)
if result is None:
logger.error(f"request in {f.__name__} failed")
return result
return wrap