Browse Source

Рефактор, разнести логику по модулям

master
Дмитрий 3 years ago
parent
commit
ef2500a4fa
  1. 64
      app_config.py
  2. 33
      config_object.py
  3. 47
      request_builder.py
  4. 169
      solution.py

64
app_config.py

@ -0,0 +1,64 @@
"""Загрузка настроек приложения. Используется библиотека `configparser`."""
import configparser
from os import path
class AppConfig:
"""Класс для хранения настроек сервиса.
TODO: Дописать документацию!
"""
def __init__(self, path_to_conf_file: str, section: str = "Main"):
"""
Parameters
----------
path_to_conf_file: str
Путь к файлу конфига. Можно указывать относительный.
section: str
Секция в конфиге, которую нужно считывать. По-умолчанию секция `[Main]`.
Raises
------
KeyError
Если в конфиге не найдено указанной секции.
"""
cfg: configparser = configparser.ConfigParser()
try:
cfg.read(path.abspath(path_to_conf_file))
except FileNotFoundError:
print(f"File {path.abspath(path_to_conf_file)} not found!")
if section not in cfg.sections():
raise KeyError(f"Section {section} not found in config file!")
conf = dict(cfg.items(section))
self.template: str = conf["template"]
self.path_for_config: str = conf["path_for_config"]
self.frequency_sec = int(conf["frequency_sec"])
self.central_host_url: str = conf["central_host_url"]
self.requests_count: int = int(conf["requests_count"])
self.request_portion: int = int(conf["request_portion"])
@property
def configs_path(self) -> str:
"""Возвращает абсолютный путь до папки с конфигами."""
_path = path.abspath(self.path_for_config)
return _path
def _create_path(self) -> None:
pass
def __repr__(self):
return (
f"template = {self.template}\n"
f"path_for_config = {self.path_for_config}\n"
f"{self.frequency_sec}\n"
f"{self.central_host_url}\n"
f"{self.requests_count}\n"
f"{self.request_portion}\n"
)

33
config_object.py

@ -0,0 +1,33 @@
from dataclasses import dataclass
import asyncio
import os
@dataclass
class ConfigObject:
host: str
conf_body: str
path: str
@property
def existst(self) -> bool:
"""Возвращает True, если файл конфига уже существует."""
return path.isfile(path)
async def write(self) -> bool:
"""Записывает конфиг файл."""
_config_file_name: str = path.join(path, f"{host}.conf")
if not self.existst:
pass
return True
def read_config_template_file(path_to_file: str) -> str:
"""Прочесть шаблон конфига для сервера из файла."""
template: str = ""
_full_path = os.path.abspath(path_to_file)
with open(_full_path, mode="r", encoding="utf8") as file:
template = file.read()
return template

47
request_builder.py

@ -0,0 +1,47 @@
"""Модлуль для конструирования и обработки запросов."""
from app_config import AppConfig
from aiohttp import ClientResponse
import asyncio
import aiohttp
class RequestBulder:
def __init__(self, cfg: AppConfig):
self.cfg = cfg
_conn = aiohttp.TCPConnector(limit=cfg.requests_count)
self.session = aiohttp.ClientSession(connector=_conn)
async def __check_resp(self, resp: ClientResponse) -> bool:
if not resp.status == 200:
return False
response = await resp.json()
if not response["done"]:
return False
return True
async def send_request(self, url, json_body) -> dict | bool:
"""Выполняет запрос, при успехе возвращает json с ответом, при
неудаче возвращает False."""
_url = f"{self.cfg.central_host_url}/{url}"
raw_resp = await self.session.get(_url, json=json_body)
if await self.__check_resp(raw_resp):
json_resp = await raw_resp.json()
json_resp.pop("done")
return json_resp
return False
async def wait(self) -> None:
"""Ждет frequency_sec время."""
await asyncio.sleep(self.cfg.frequency_sec)
# def __del__(self):
# if self.session is not None:
# self.session.close()

169
solution.py

@ -1,104 +1,139 @@
import asyncio
import requests
import json
from dataclasses import dataclass
from asyncio import Task
from typing import List
from aiohttp.client_exceptions import ClientConnectorError
import asyncio
import os
import configparser
import aiohttp
import aiofiles
from config_object import ConfigObject, read_config_template_file
from app_config import AppConfig
from request_builder import RequestBulder
async def write_config_file(
server_name: str, path_to_template_file: str, path_for_config: str
) -> None:
template: str = ""
with open(path_to_template_file, "r") as file:
template = file.read()
config_body: str = template.replace(
"server_name _;", f"server_name {server_name}.server.com;"
)
condifg_full_path: str = os.path.abspath(path_for_config)
config_filename: str = f"{server_name}.conf"
async def write_config_files(hosts: list, cfg: AppConfig, template: str) -> None:
"""Записываем конфиги для списка hosts."""
if not os.path.isdir(condifg_full_path):
os.mkdir(condifg_full_path)
full_path_to_config_dir: str = os.path.abspath(cfg.path_for_config)
with open(os.path.join(condifg_full_path, config_filename), "w") as file:
file.write(config_body)
if not os.path.isdir(full_path_to_config_dir):
os.mkdir(full_path_to_config_dir)
for host in hosts:
config_filename: str = f"{host}.conf"
config_file = os.path.join(full_path_to_config_dir, config_filename)
async def send_request(server: str, columns: list, limit: int = 1) -> dict:
response = requests.get(server, json={"columns": columns, "limit": limit})
if not os.path.isfile(config_file):
config_body: str = template.replace(
"server_name _;", f"server_name {host}.server.com;"
)
async with aiofiles.open(config_file, mode="w") as file:
await file.write(config_body)
def _get_template(templ_file: str) -> str:
"""Возвращает шаблон конфига для хоста из файла `templ_file`."""
template: str = ""
with open(templ_file, mode="r", encoding="utf8") as file:
template = file.read()
return response.json()
return template
async def get_hosts(server_response: dict) -> List[str]:
"""Получить хосты из ответа сервера.
async def get_records_count(session, server) -> int:
"""Возвращает количество записей в базе."""
async with session.get(f"{server}/count") as resp:
resp = await resp.json()
count: int = int(resp["result"])
return count
async def get_tasks(
url: str, portion: int, count: int, session: aiohttp.ClientSession, body: dict
) -> List[Task]:
"""Вернет список задач с запросами к API.
Функция не ограничивает кол-во запросов, это нужно сделать до
вызова, чтобы передать корректный `session`.
Parameters
----------
server_response : dict
url : str
Куда слать запрос. Ожидается "server/get"
portion : int
Сколько записей запрашивать за раз
count : int
Общее количество записей
session : aiohttp.ClientSession
Объект сессии, создается в уровне выше, с одним объектом
меньше накладных расходов на каждый запрос.
body : json
Json для запроса.
"""
tasks: List[Task] = []
hosts: list = []
for offset in range(0, count, portion):
tasks.append(asyncio.create_task(session.get(url, json=body)))
body["offset"] = offset
for host in server_response.get("result"):
hosts.append(host.get("hostname"))
return tasks
return hosts
async def send_async_request(cfg: AppConfig, json_body: dict) -> None:
"""Начать серию запросов."""
template: str = _get_template(cfg.template)
async def read_config(path_to_conf_file: str, section: str = "Main") -> dict:
"""
Считать конфиг с помощью `configparser`.
# ограничим одновременное число запросов
conn = aiohttp.TCPConnector(limit=cfg.requests_count)
Parameters
----------
path_to_conf_file: str
Путь к файлу конфига. Можно указывать относительный.
url = cfg.central_host_url
portion = cfg.request_portion
section: str
Секция в конфиге, которую нужно считывать. По-умолчанию секция [Main].
try:
async with aiohttp.ClientSession(connector=conn) as session:
# получаем количесвто записей
count = await get_records_count(session, url)
Raises
------
KeyError
Если в конфиге не найдено указанной секции.
tasks = await get_tasks(f"{url}/get", portion, count, session, json_body)
Returns
-------
dict
Словарь, из значений указанной секции.
"""
config = configparser.ConfigParser()
config.read(os.path.abspath(path_to_conf_file))
responses = await asyncio.gather(*tasks)
if section not in config.sections():
raise KeyError(f"Section {section} not found in config file!")
# Пройдемся по ответам на запросы, запишем файлы конфига для
# каждого respone. Каждый response содержит portion или меньше хостов
for response in responses:
resp = await response.json()
hosts = [i["hostname"] for i in resp.get("result")]
await write_config_files(hosts, cfg, template)
except ClientConnectorError:
print(f"Невозможно подключиться к серверу {url}")
return dict(config.items(section))
async def main() -> None:
"""Точка входа."""
cfg: AppConfig = AppConfig("service.conf")
print(cfg.configs_path)
async def main():
rb = RequestBulder(cfg)
cnf = await read_config("service.conf")
resp = await rb.send_request("count", json_body={})
wait_sec: int = int(cnf["frequency_sec"])
if resp:
records_count = resp.get("result")
while True:
resp = await send_request(
cnf["central_host_url"], columns=["hostname"], limit=9
)
print(records_count)
hosts = await get_hosts(resp)
body_json = {"columns": "['hostname']", "limit": cfg.request_portion, "offset": 0}
for host in hosts:
await write_config_file(
server_name=host,
path_to_template_file=cnf["template"],
path_for_config=cnf["path_for_config"],
)
await asyncio.sleep(wait_sec)
template = read_config_template_file(cfg.template)
print(template)
# while True:
# await send_async_request(cfg, json_body=body)
# await asyncio.sleep(cfg.frequency_sec)
if __name__ == "__main__":

Loading…
Cancel
Save