Мой первый реальный проект на Python - написать простой каркас (или повторно использовать / адаптировать существующий), который может обернуть небольшие скрипты Python (которые используются для сбора пользовательских данных для инструмента мониторинга) с «контейнером» «для решения стандартных задач, таких как:
- извлечение конфигурации скрипта из файла (и поддержание этой информации в актуальном состоянии, если файл изменяется и обрабатывает дешифрование конфиденциальных данных конфигурации)
- запуск нескольких экземпляров одного и того же скрипта в разных потоках вместо запуска нового процесса для каждого
- предоставляет API для кэширования дорогих данных и сохранения постоянного состояния от одного вызова скрипта до следующего
Сегодня авторы сценариев должны решать вышеуказанные проблемы, что обычно означает, что большинство авторов сценариев обрабатывают их некорректно, вызывая ошибки и проблемы с производительностью. Помимо того, что мы избегаем ошибок, нам нужно решение, которое снижает планку для создания и поддержки сценариев, особенно с учетом того, что многие авторы сценариев могут не быть обученными программистами.
Ниже приведены примеры API, о котором я думал, и о котором я хочу узнать ваши отзывы.
Сценарист должен будет создать один метод, который принимает (в качестве входных данных) конфигурацию, необходимую сценарию для выполнения своей работы, и либо возвращает объект python, либо вызывает метод для потоковой передачи данных в виде фрагментов. При желании сценарий может предоставить методы для обработки задач запуска и / или завершения работы.
Пример сценария HTTP-выборки (в псевдокоде, пропуская фактические данные выборки данных, чтобы сосредоточиться на API контейнера):
def run (config, context, cache) :
results = http_library_call (config.url, config.http_method, config.username, config.password, ...)
return { html : results.html, status_code : results.status, headers : results.response_headers }
def init(config, context, cache) :
config.max_threads = 20 # up to 20 URLs at one time (per process)
config.max_processes = 3 # launch up to 3 concurrent processes
config.keepalive = 1200 # keep process alive for 10 mins without another call
config.process_recycle.requests = 1000 # restart the process every 1000 requests (to avoid leaks)
config.kill_timeout = 600 # kill the process if any call lasts longer than 10 minutes
Пример сценария извлечения данных из базы данных может выглядеть так (в псевдокоде):
def run (config, context, cache) :
expensive = context.cache["something_expensive"]
for record in db_library_call (expensive, context.checkpoint, config.connection_string) :
context.log (record, "logDate") # log all properties, optionally specify name of timestamp property
last_date = record["logDate"]
context.checkpoint = last_date # persistent checkpoint, used next time through
def init(config, context, cache) :
cache["something_expensive"] = get_expensive_thing()
def shutdown(config, context, cache) :
expensive = cache["something_expensive"]
expensive.release_me()
Является ли этот API соответствующим образом "питоническим", или я должен что-то сделать, чтобы сделать это более естественным для сценария Python? (Я более знаком со сборкой C ++ / C # / Java API, поэтому я подозреваю, что мне не хватает полезных идиом Python.)
Конкретные вопросы:
- Естественно ли передать объект "config" в метод и попросить вызываемого пользователя установить различные параметры конфигурации? Или есть другой предпочтительный способ сделать это?
- когда вызываемый объект должен передавать данные обратно своему вызывающему, подходит ли такой метод, как
context.log()
(см. Выше), или вместо этого я должен использовать yield? (да, это кажется естественным, но я волнуюсь, это было бы у большинства сценаристов)
- Мой подход требует использования сценариев для определения функций с предопределенными именами (например, «run», «init», «shutdown»). Это хороший способ сделать это? Если нет, то какой другой механизм будет более естественным?
- Я передаю одинаковые
config, context, cache
параметры в каждый метод. Было бы лучше использовать один параметр «контекста» вместо этого? Было бы лучше вместо этого использовать глобальные переменные?
- Наконец, существуют ли библиотеки, которые вы бы порекомендовали сделать таким простым "контейнером для выполнения скриптов", который легче писать?