From ef2500a4fa398a35ebc5c76b0e2ade81e3da089e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9?= Date: Fri, 10 Jun 2022 17:30:49 +0300 Subject: [PATCH 1/3] =?UTF-8?q?=D0=A0=D0=B5=D1=84=D0=B0=D0=BA=D1=82=D0=BE?= =?UTF-8?q?=D1=80,=20=D1=80=D0=B0=D0=B7=D0=BD=D0=B5=D1=81=D1=82=D0=B8=20?= =?UTF-8?q?=D0=BB=D0=BE=D0=B3=D0=B8=D0=BA=D1=83=20=D0=BF=D0=BE=20=D0=BC?= =?UTF-8?q?=D0=BE=D0=B4=D1=83=D0=BB=D1=8F=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app_config.py | 64 +++++++++++++++++ config_object.py | 33 +++++++++ request_builder.py | 47 +++++++++++++ solution.py | 169 +++++++++++++++++++++++++++------------------ 4 files changed, 246 insertions(+), 67 deletions(-) create mode 100644 app_config.py create mode 100644 config_object.py create mode 100644 request_builder.py diff --git a/app_config.py b/app_config.py new file mode 100644 index 0000000..5fa1715 --- /dev/null +++ b/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" + ) diff --git a/config_object.py b/config_object.py new file mode 100644 index 0000000..345abb2 --- /dev/null +++ b/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 diff --git a/request_builder.py b/request_builder.py new file mode 100644 index 0000000..2ab25d8 --- /dev/null +++ b/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() diff --git a/solution.py b/solution.py index 1aa91cb..bff9960 100644 --- a/solution.py +++ b/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__": From 484b2af66958258404869ccf47e3413df25a57d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9?= Date: Wed, 22 Jun 2022 15:06:02 +0300 Subject: [PATCH 2/3] =?UTF-8?q?=D0=A0=D0=B0=D0=B1=D0=BE=D1=87=D0=B0=D1=8F?= =?UTF-8?q?=20=D0=B2=D0=B5=D1=80=D1=81=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config_object.py | 48 +++++++++---- server.py | 10 +++ solution.py | 172 +++++++++++++++-------------------------------- 3 files changed, 102 insertions(+), 128 deletions(-) diff --git a/config_object.py b/config_object.py index 345abb2..0002339 100644 --- a/config_object.py +++ b/config_object.py @@ -1,33 +1,57 @@ from dataclasses import dataclass import asyncio import os +import aiofiles -@dataclass class ConfigObject: host: str conf_body: str path: str + def __init__(self, host: str, conf_body: str, path: str): + self.host = host + self.conf_body = conf_body.replace( + "server_name _;", f"server_name {host}.server.com;" + ) + + self.path = os.path.abspath(path) + + if not os.path.isdir(self.path): + os.mkdir(self.path) + + self.full_path_to_file: str = os.path.join(self.path, f"{host}.conf") + @property def existst(self) -> bool: """Возвращает True, если файл конфига уже существует.""" - return path.isfile(path) + return os.path.isfile(self.full_path_to_file) async def write(self) -> bool: """Записывает конфиг файл.""" - _config_file_name: str = path.join(path, f"{host}.conf") - if not self.existst: - pass + async with aiofiles.open(self.full_path_to_file, mode="w") as file: + await file.write(self.conf_body) return True + def __repr__(self): + return f"Hi, config for {self.host}" + + +class ConfigFactory: + def __init__(self, path_to_template: str, path_to_configs_dir: str): + self.templ = self.__read_config_template_file(path_to_template) + self.path = path_to_configs_dir + + def create(self, host: str) -> ConfigObject: + return ConfigObject(host, self.templ, self.path) + + def __read_config_template_file(self, path_to_file: str) -> str: + """Прочесть шаблон конфига для сервера из файла.""" + template: str = "" -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() + _full_path = os.path.abspath(path_to_file) + with open(_full_path, mode="r", encoding="utf8") as file: + template = file.read() - return template + return template diff --git a/server.py b/server.py index 472c09c..727705b 100644 --- a/server.py +++ b/server.py @@ -15,6 +15,8 @@ curl --request GET \ from flask import Flask, jsonify, request import json import secrets +from random import randint +import time app = Flask(__name__) @@ -43,7 +45,15 @@ def index(): for host in range(0, limit): hosts.append({"hostname": f"content-creator-{secrets.token_hex(4)}"}) + time.sleep(randint(1, 6)) + return jsonify({"result": hosts, "done": True}) +@app.route("/api/portal/count", methods=["GET"]) +def count(): + time.sleep(randint(1, 2)) + return jsonify({"result": randint(20, 300), "done": True}) + + app.run() diff --git a/solution.py b/solution.py index bff9960..a1c3d29 100644 --- a/solution.py +++ b/solution.py @@ -1,140 +1,80 @@ -from dataclasses import dataclass +import argparse +import asyncio +import os from asyncio import Task +from dataclasses import dataclass from typing import List -from aiohttp.client_exceptions import ClientConnectorError -import asyncio -import os import aiohttp -import aiofiles +from aiohttp.client_exceptions import ClientConnectorError +from loguru import logger -from config_object import ConfigObject, read_config_template_file from app_config import AppConfig -from request_builder import RequestBulder - - -async def write_config_files(hosts: list, cfg: AppConfig, template: str) -> None: - """Записываем конфиги для списка hosts.""" - - full_path_to_config_dir: str = os.path.abspath(cfg.path_for_config) - - 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) - - 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 template +from config_object import ConfigFactory, ConfigObject +from request_builder import Json, RequestBulder -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 - ---------- - url : str - Куда слать запрос. Ожидается "server/get" - portion : int - Сколько записей запрашивать за раз - count : int - Общее количество записей - session : aiohttp.ClientSession - Объект сессии, создается в уровне выше, с одним объектом - меньше накладных расходов на каждый запрос. - body : json - Json для запроса. - """ - tasks: List[Task] = [] - - for offset in range(0, count, portion): - tasks.append(asyncio.create_task(session.get(url, json=body))) - body["offset"] = offset - - return tasks - - -async def send_async_request(cfg: AppConfig, json_body: dict) -> None: - """Начать серию запросов.""" - template: str = _get_template(cfg.template) - - # ограничим одновременное число запросов - conn = aiohttp.TCPConnector(limit=cfg.requests_count) - - url = cfg.central_host_url - portion = cfg.request_portion +async def get_records_count(rb: RequestBulder) -> int: + resp: Json | bool = await rb.send_request("count", json_body={}) + records_count = 0 + if resp: + records_count = resp.get("result") - try: - async with aiohttp.ClientSession(connector=conn) as session: - # получаем количесвто записей - count = await get_records_count(session, url) + return records_count - tasks = await get_tasks(f"{url}/get", portion, count, session, json_body) - responses = await asyncio.gather(*tasks) +async def custom_wrapper( + config_factory: ConfigFactory, + rb: RequestBulder, + usr: str, + json: Json, +) -> None: + resp: Json | bool = await rb.send_request("get", json) - # Пройдемся по ответам на запросы, запишем файлы конфига для - # каждого 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}") + # Если мы получили валидный ответ, то разбираем пачку хостов из + # resp['result'] с созданием для каждой строки кофнига через + # фабрику + if resp: + conf_list = [config_factory.create(host["hostname"]) for host in resp["result"]] + await asyncio.gather(*[c.write() for c in conf_list]) + else: + logger.error(f"Сервер вернул ошибку") -async def main() -> None: +async def main(config_path: str) -> None: """Точка входа.""" - cfg: AppConfig = AppConfig("service.conf") - print(cfg.configs_path) + cfg: AppConfig = AppConfig(config_path) rb = RequestBulder(cfg) - resp = await rb.send_request("count", json_body={}) - - if resp: - records_count = resp.get("result") - - print(records_count) - - body_json = {"columns": "['hostname']", "limit": cfg.request_portion, "offset": 0} + while True: + records_count = await get_records_count(rb) - template = read_config_template_file(cfg.template) + json_boby_list = [] + for offset in range(0, records_count, cfg.request_portion): + body_json = { + "columns": "['hostname']", + "limit": cfg.request_portion, + "offset": offset, + } + json_boby_list.append(body_json) - print(template) - # while True: + conf_factory = ConfigFactory(cfg.template, cfg.path_for_config) + await asyncio.gather( + *[custom_wrapper(conf_factory, rb, "get", jb) for jb in json_boby_list] + ) - # await send_async_request(cfg, json_body=body) - # await asyncio.sleep(cfg.frequency_sec) + await rb.wait() + # закрываем сессию + await rb.close() if __name__ == "__main__": - asyncio.run(main()) + parser = argparse.ArgumentParser(description="Service for nginx config creation.") + parser.add_argument( + "--config_path", required=True, type=str, help="path for conifg file" + ) + args = parser.parse_args() + config_path = os.path.abspath(args.config_path) + + asyncio.run(main(config_path)) From 455e1f41550a81427d6f7d51c446b2f5cf91c2d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9?= Date: Wed, 22 Jun 2022 16:09:56 +0300 Subject: [PATCH 3/3] =?UTF-8?q?=D0=A0=D0=B5=D1=84=D0=B0=D0=BA=D1=82=D0=BE?= =?UTF-8?q?=D1=80.=20=D0=92=D0=B5=D1=80=D1=81=D0=B8=D1=8F=20=D0=BF=D0=BE?= =?UTF-8?q?=D1=81=D0=BB=D0=B5=20=D1=82=D0=B5=D1=81=D1=82=D0=B8=D1=80=D0=BE?= =?UTF-8?q?=D0=B2=D0=B0=D0=BD=D0=B8=D1=8F.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app_config.py | 6 +--- config_object.py | 6 +--- request_builder.py | 75 +++++++++++++++++++++++++++++++++++----------- solution.py | 20 +++++++------ 4 files changed, 70 insertions(+), 37 deletions(-) diff --git a/app_config.py b/app_config.py index 5fa1715..dd37b97 100644 --- a/app_config.py +++ b/app_config.py @@ -5,7 +5,6 @@ from os import path class AppConfig: """Класс для хранения настроек сервиса. - TODO: Дописать документацию! """ def __init__(self, path_to_conf_file: str, section: str = "Main"): @@ -25,7 +24,7 @@ class AppConfig: """ - cfg: configparser = configparser.ConfigParser() + cfg = configparser.ConfigParser() try: cfg.read(path.abspath(path_to_conf_file)) @@ -50,9 +49,6 @@ class AppConfig: _path = path.abspath(self.path_for_config) return _path - def _create_path(self) -> None: - pass - def __repr__(self): return ( f"template = {self.template}\n" diff --git a/config_object.py b/config_object.py index 0002339..d7ca8db 100644 --- a/config_object.py +++ b/config_object.py @@ -1,14 +1,9 @@ -from dataclasses import dataclass import asyncio import os import aiofiles class ConfigObject: - host: str - conf_body: str - path: str - def __init__(self, host: str, conf_body: str, path: str): self.host = host self.conf_body = conf_body.replace( @@ -44,6 +39,7 @@ class ConfigFactory: self.path = path_to_configs_dir def create(self, host: str) -> ConfigObject: + """Конструирует `ConfigObject` используя только хост.""" return ConfigObject(host, self.templ, self.path) def __read_config_template_file(self, path_to_file: str) -> str: diff --git a/request_builder.py b/request_builder.py index 2ab25d8..6651ad0 100644 --- a/request_builder.py +++ b/request_builder.py @@ -1,15 +1,31 @@ -"""Модлуль для конструирования и обработки запросов.""" - -from app_config import AppConfig -from aiohttp import ClientResponse +"""Модуль для конструирования и обработки запросов.""" import asyncio +from typing import Dict, List, NewType + import aiohttp +from aiohttp import ClientResponse +from aiohttp.client_exceptions import ClientConnectorError, ServerDisconnectedError +from loguru import logger +from app_config import AppConfig class RequestBulder: - def __init__(self, cfg: AppConfig): - self.cfg = cfg + """Конструктор запросов. + Attributes + ---------- + cfg : AppConfig + Объект с конфигом для приложения. + + Methods + ------- + send_request(url: str, json_body: dict) + Асинхронный метод для отправки запроса. Может принимать пустой json_body. + """ + def __init__(self, cfg: AppConfig) -> None: + """Инициализация. Объект сесси `aiohttp.ClientSession` + создается здесь. """ + self.cfg = cfg _conn = aiohttp.TCPConnector(limit=cfg.requests_count) self.session = aiohttp.ClientSession(connector=_conn) @@ -24,24 +40,47 @@ class RequestBulder: return True - async def send_request(self, url, json_body) -> dict | bool: + async def send_request(self, url: str, json_body: dict) -> dict: """Выполняет запрос, при успехе возвращает json с ответом, при - неудаче возвращает False.""" - _url = f"{self.cfg.central_host_url}/{url}" + неудаче возвращает пустой dict. - raw_resp = await self.session.get(_url, json=json_body) + Parameters + ---------- + url : str + Адрес куда слать запрос. Нужна часть не включающая хост, только get/post. - if await self.__check_resp(raw_resp): - json_resp = await raw_resp.json() - json_resp.pop("done") - return json_resp + json_body: Json + Тело хапроса в Json формате. - return False + Returns + ------- + dict + Возвращает либо пустой dict, либо json ответа сервера (заполненный dict). + + Examples + -------- + >>> await send_request('count', json_body={} ) + + """ + _url = f"{self.cfg.central_host_url}/{url}" + try: + async with self.session.get(_url, json=json_body) as raw_resp: + if await self.__check_resp(raw_resp): + json_resp: dict = await raw_resp.json() + json_resp.pop("done") + return json_resp + except ClientConnectorError: + logger.error(f"Ошибка подключения к серверу {_url}") + # не придумал ничего умнее чем подождать frequency_sec из конфига + await asyncio.sleep(self.cfg.frequency_sec) + except ServerDisconnectedError: + logger.error(f"Сервер отклонил подключение {_url}") + return dict() 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() + async def close(self) -> None: + """Gracefull shutdown connection.""" + await self.session.close() diff --git a/solution.py b/solution.py index a1c3d29..f10c35d 100644 --- a/solution.py +++ b/solution.py @@ -1,9 +1,10 @@ +"""Создание конфиг. файлов для хостов.""" import argparse import asyncio import os from asyncio import Task from dataclasses import dataclass -from typing import List +from typing import List, Union import aiohttp from aiohttp.client_exceptions import ClientConnectorError @@ -11,25 +12,26 @@ from loguru import logger from app_config import AppConfig from config_object import ConfigFactory, ConfigObject -from request_builder import Json, RequestBulder +from request_builder import RequestBulder async def get_records_count(rb: RequestBulder) -> int: - resp: Json | bool = await rb.send_request("count", json_body={}) - records_count = 0 - if resp: - records_count = resp.get("result") + """Обертка для получения количества записей с сервера.""" + resp: dict = await rb.send_request("count", json_body={}) + + records_count: int = int(resp["result"]) if resp else 0 - return records_count + return int(records_count) async def custom_wrapper( config_factory: ConfigFactory, rb: RequestBulder, usr: str, - json: Json, + json: dict, ) -> None: - resp: Json | bool = await rb.send_request("get", json) + """Обертка для создания конфигов и их записи.""" + resp: dict = await rb.send_request("get", json) # Если мы получили валидный ответ, то разбираем пачку хостов из # resp['result'] с созданием для каждой строки кофнига через