diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..853aa9c --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.coverage +__pycache__ +.pytest_cache diff --git a/client/__main__.py b/client/__main__.py new file mode 100644 index 0000000..bc7c8bc --- /dev/null +++ b/client/__main__.py @@ -0,0 +1,92 @@ +#!/usr/bin/python3 +__author__ = "Андрей Петров" + +""" +клиент отправляет запрос серверу; +сервер отвечает соответствующим кодом результата. Клиент и сервер должны быть реализованы в виде отдельных скриптов, +содержащих соответствующие функции. + +Функции клиента: +сформировать presence-сообщение; +отправить сообщение серверу; +получить ответ сервера; +разобрать сообщение сервера; +параметры командной строки скрипта client.py []: addr — ip-адрес сервера; +port — tcp-порт на сервере, +по умолчанию 7777. + +""" +import re +import sys +import argparse +import socket +import json +from datetime import datetime +from client_log_config import logger + + +def createParser(): + parser = argparse.ArgumentParser() + parser.add_argument('host', nargs='?', default='localhost') + parser.add_argument('port', nargs='?', default='7777') + return parser + + + +parser = createParser() +args = parser.parse_args(sys.argv[1:]) + +port = int(re.search('[0-9]{2,}', args.port).group(0)) + + +sock = socket.socket() +try: + sock.connect((args.host, port)) + + # сформировать presence-сообщение; + # В формате JIM + + msg_presence = json.dumps( + { + "action": "presence", + "time": datetime.now().timestamp(), + "type": "status", + "user": { + "account_name": input("Enter user Name: "), + "status": input("Status message: ") + } + } + ) + """ + msg_action = json.dumps( + { + "action": input("Enter action (lower_text): "), + "data": input("Enter data: ") + } + ) + #sock.send(msg_action.encode()) + """ + + # отправить сообщение серверу; + sock.send(msg_presence.encode()) + + + # получить ответ сервера; + data = sock.recv(1024) + response = json.loads( + data.decode('utf-8') + ) + # разобрать сообщение сервера; + + if response.get('response') == 200: + if response.get('alert'): + logger.debug(f"Response Message: {response.get('alert')}") + print( + f"Response Message: {response.get('alert')}" + ) + else: + logger.critical(f"Error request: {response.get('error')}") + + sock.close() +except Exception: + logger.critical(f'client cant conntect to host:{args.host} port{port}') diff --git a/client/client_log_config.py b/client/client_log_config.py new file mode 100644 index 0000000..9c31693 --- /dev/null +++ b/client/client_log_config.py @@ -0,0 +1,18 @@ +""" +Создание именованного логгера; +Сообщения лога должны иметь следующий формат: "<дата-время> <уровеньважности> <имямодуля> <сообщение>"; +Журналирование должно производиться в лог-файл; +На стороне сервера необходимо настроить ежедневную ротацию лог-файлов. +""" + +import logging.handlers + +logger = logging.getLogger('client') +formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(name)s - %(module)s - %(message)s') +handler = logging.handlers.TimedRotatingFileHandler('log/client.log', when='d', interval=1, backupCount=4) + +handler.setFormatter(formatter) +handler.setLevel(logging.DEBUG) + +logger.addHandler(handler) +logger.setLevel(logging.DEBUG) \ No newline at end of file diff --git a/lesson01/hw01.py b/lesson01/hw01.py deleted file mode 100644 index ad092a1..0000000 --- a/lesson01/hw01.py +++ /dev/null @@ -1,187 +0,0 @@ -#!/usr/bin/python3 -__author__ = "Андрей Петров" - - -""" -1. Каждое из слов «разработка», «сокет», «декоратор» представить в строковом формате и проверить тип и содержание -соответствующих переменных. Затем с помощью онлайн-конвертера преобразовать строковые представление в формат Unicode -и также проверить тип и содержимое переменных. - ->>> a = 'разработка' ->>> print(f'development: {a}, type {type(a)}') -development: разработка, type - ->>> b = 'сокет' ->>> print(f'socket: {b}, type {type(b)}') -socket: сокет, type - ->>> c = 'декоратор' ->>> print(f'decorator: {c}, type {type(c)}') -decorator: декоратор, type - ->>> a = a.encode('utf-8') ->>> print(f'development: {a}, type {type(a)}') -development: b'\xd1\x80\xd0\xb0\xd0\xb7\xd1\x80\xd0\xb0\xd0\xb1\xd0\xbe\xd1\x82\xd0\xba\xd0\xb0', type - ->>> b = b.encode('utf-8') ->>> print(f'socket: {b}, type {type(b)}') -socket: b'\xd1\x81\xd0\xbe\xd0\xba\xd0\xb5\xd1\x82', type - ->>> c = c.encode('utf-8') ->>> print(f'decorator: {c}, type {type(c)}') -decorator: b'\xd0\xb4\xd0\xb5\xd0\xba\xd0\xbe\xd1\x80\xd0\xb0\xd1\x82\xd0\xbe\xd1\x80', type - -""" - - -""" -2. Каждое из слов «class», «function», «method» записать в байтовом типе без преобразования в последовательность кодов -(не используя методы encode и decode) и определить тип, содержимое и длину соответствующих переменных. - ->>> a = b'class' ->>> print(f'class: {a}, type: {type(a)}, len:{len(a)}') -class: b'class', type: , len:5 - ->>> b = b'function' ->>> print(f'function: {b}, type: {type(b)}, len:{len(b)}') -function: b'function', type: , len:8 - ->>> c = b'method' ->>> print(f'method: {c}, type: {type(c)}, len:{len(c)}') -method: b'method', type: , len:6 -""" - - -""" -3. Определить, какие из слов «attribute», «класс», «функция», «type» невозможно записать в байтовом типе. - ->>> b'attribute' -b'attribute' - ->>> b'класс' - File "", line 1 -SyntaxError: bytes can only contain ASCII literal characters. - ->>> b'функция' - File "", line 1 -SyntaxError: bytes can only contain ASCII literal characters. - ->>> b'type' -b'type' - -Нельзя записать в байтовый тип без преобразования кириллические символы, т.к. на хранение одного -кириллическоно символа требуется больше 1 байта - -""" - - -""" -4. Преобразовать слова «разработка», «администрирование», «protocol», «standard» из строкового представления в байтовое - и выполнить обратное преобразование (используя методы encode и decode). - ->>> a = 'разработка' ->>> a -'разработка' ->>> a = a.encode() ->>> a -b'\xd1\x80\xd0\xb0\xd0\xb7\xd1\x80\xd0\xb0\xd0\xb1\xd0\xbe\xd1\x82\xd0\xba\xd0\xb0' ->>> a = a.decode() ->>> a -'разработка' - ->>> b = 'администрирование' ->>> b -'администрирование' ->>> b = b.encode() ->>> b -b'\xd0\xb0\xd0\xb4\xd0\xbc\xd0\xb8\xd0\xbd\xd0\xb8\xd1\x81\xd1\x82\xd1\x80\xd0\xb8\xd1\x80\xd0\xbe\xd0\xb2\xd0\xb0\xd0\xbd\xd0\xb8\xd0\xb5' ->>> b = b.decode() ->>> b -'администрирование' - ->>> c = 'protocol' ->>> c -'protocol' ->>> c = c.encode() ->>> c -b'protocol' ->>> c = c.decode() ->>> c -'protocol' - ->>> d = 'standard' ->>> d -'standard' ->>> d = d.encode() ->>> d -b'standard' ->>> d = d.decode() ->>> d -'standard' - -""" - -""" -5. Выполнить пинг веб-ресурсов yandex.ru, youtube.com и преобразовать результаты из байтовового -в строковый тип на кириллице. - ->>> import subprocess ->>> args = ['ping', 'yandex.ru'] ->>> subproc_ping = subprocess.Popen(args, stdout=subprocess.PIPE) ->>> for line in subproc_ping.stdout: -... line = line.decode('cp866').encode('utf-8') -... print(line.decode('utf-8')) -... -Ответ от 5.255.255.70: число байт=32 время=24мс TTL=57 -Ответ от 5.255.255.70: число байт=32 время=26мс TTL=57 -Ответ от 5.255.255.70: число байт=32 время=27мс TTL=57 - -Статистика Ping для 5.255.255.70: - Пакетов: отправлено = 4, получено = 4, потеряно = 0 - (0% потерь) -Приблизительное время приема-передачи в мс: - Минимальное = 24мсек, Максимальное = 27 мсек, Среднее = 25 мсек - - ->>> args = ['ping', 'youtube.com'] ->>> subproc_ping = subprocess.Popen(args, stdout=subprocess.PIPE) ->>> for line in subproc_ping.stdout: -... line = line.decode('cp866').encode('utf-8') -... print(line.decode('utf-8')) -... -Обмен пакетами с youtube.com [173.194.44.39] с 32 байтами данных: -Ответ от 173.194.44.39: число байт=32 время=23мс TTL=120 -Ответ от 173.194.44.39: число байт=32 время=22мс TTL=120 -Ответ от 173.194.44.39: число байт=32 время=21мс TTL=120 -Ответ от 173.194.44.39: число байт=32 время=23мс TTL=120 - -Статистика Ping для 173.194.44.39: - Пакетов: отправлено = 4, получено = 4, потеряно = 0 - (0% потерь) -Приблизительное время приема-передачи в мс: - Минимальное = 21мсек, Максимальное = 23 мсек, Среднее = 22 мсек - -""" - -""" -6. Создать текстовый файл test_file.txt, заполнить его тремя строками: «сетевое программирование», «сокет», -«декоратор». Проверить кодировку файла по умолчанию. Принудительно открыть файл в формате Unicode и -вывести его содержимое. - ->>> with open('E:/PythonScripts/Repo/python_advanced/lesson01/test_file.txt') as file: -... for f_str in file: -... print(f_str) -... -сетевое программирование -сокет -декоратор - ->>> with open('E:/PythonScripts/Repo/python_advanced/lesson01/test_file.txt', encoding='utf-8') as file: -... for f_str in file: -... print(f_str) -... -сетевое программирование -сокет -декоратор - -""" diff --git a/lesson01/test_file.txt b/lesson01/test_file.txt deleted file mode 100644 index d9c8af1..0000000 --- a/lesson01/test_file.txt +++ /dev/null @@ -1,3 +0,0 @@ -сетевое программирование -сокет -декоратор \ No newline at end of file diff --git a/lesson02/file.yaml b/lesson02/file.yaml deleted file mode 100644 index 4f0efa3..0000000 --- a/lesson02/file.yaml +++ /dev/null @@ -1,13 +0,0 @@ -100: -- 1 -- 2 -- 3 -- 4 -200: 8000 -300: - first: - - 1 - - 2 - - 3 - - 4 - second: 800 diff --git a/lesson02/info_1.txt b/lesson02/info_1.txt deleted file mode 100644 index 845390d..0000000 --- a/lesson02/info_1.txt +++ /dev/null @@ -1,42 +0,0 @@ - : Comp1 - : Microsoft Windows 7 - : 6.1.7601 Service Pack 1 7601 - : Microsoft Corporation - : - : Multiprocessor Free - : User - : - : 00971-OEM-1982661-00231 - : 11.10.2013, 10:18:05 - : 0 ., 4 ., 41 , 0 . - : LENOVO - : 3538F2G - : x64-based PC -(): - 1. - [01]: Intel64 Family 6 Model 42 Stepping 7 GenuineIntel ~3400 - BIOS: LENOVO - 1390 - Windows: C:\Windows - : C:\Windows\system32 - : \Device\HarddiskVolume1 - : ru; - : en-us; () - : (UTC+04:00) , , - - : 3 914 - : 2 299 - : . : 7 826 - : : 6 139 - : : 1 687 - : / -: net - : / -(): - 74. - [01]: KB982861 - [02]: 982861 -. . . - [74]: KB982018 - : - 1. - [01]: Realtek PCIe GBE Family Controller - : - DHCP : - IP- - [01]: 192.168.0.4 \ No newline at end of file diff --git a/lesson02/info_2.txt b/lesson02/info_2.txt deleted file mode 100644 index 0f96fb2..0000000 --- a/lesson02/info_2.txt +++ /dev/null @@ -1,42 +0,0 @@ - : Comp1 - : Microsoft Windows 10 Professional - : 16299 - : Microsoft Corporation - : - : Multiprocessor Free - : User - : - : 00971-OEM-1982661-00231 - : 11.10.2013, 10:18:05 - : 0 ., 4 ., 41 , 0 . - : ACER - : 3538F2G - : x64-based PC -(): - 1. - [01]: Intel64 Family 6 Model 42 Stepping 7 GenuineIntel ~3400 - BIOS: LENOVO - 1390 - Windows: C:\Windows - : C:\Windows\system32 - : \Device\HarddiskVolume1 - : ru; - : en-us; () - : (UTC+04:00) , , - - : 3 914 - : 2 299 - : . : 7 826 - : : 6 139 - : : 1 687 - : / -: net - : / -(): - 74. - [01]: KB982861 - [02]: 982861 -. . . - [74]: KB982018 - : - 1. - [01]: Realtek PCIe GBE Family Controller - : - DHCP : - IP- - [01]: 192.168.0.4 \ No newline at end of file diff --git a/lesson02/info_3.txt b/lesson02/info_3.txt deleted file mode 100644 index c2a964a..0000000 --- a/lesson02/info_3.txt +++ /dev/null @@ -1,42 +0,0 @@ - : Comp1 - : Microsoft Windows 8.1 Professional - : 10001 - : Microsoft Corporation - : - : Multiprocessor Free - : User - : - : 00971-OEM-1982661-00231 - : 11.10.2013, 10:18:05 - : 0 ., 4 ., 41 , 0 . - : DELL - : 3538F2G - : x86-based PC -(): - 1. - [01]: Intel64 Family 6 Model 42 Stepping 7 GenuineIntel ~3400 - BIOS: LENOVO - 1390 - Windows: C:\Windows - : C:\Windows\system32 - : \Device\HarddiskVolume1 - : ru; - : en-us; () - : (UTC+04:00) , , - - : 3 914 - : 2 299 - : . : 7 826 - : : 6 139 - : : 1 687 - : / -: net - : / -(): - 74. - [01]: KB982861 - [02]: 982861 -. . . - [74]: KB982018 - : - 1. - [01]: Realtek PCIe GBE Family Controller - : - DHCP : - IP- - [01]: 192.168.0.4 \ No newline at end of file diff --git a/lesson02/new_file.csv b/lesson02/new_file.csv deleted file mode 100644 index 0f481d5..0000000 --- a/lesson02/new_file.csv +++ /dev/null @@ -1,4 +0,0 @@ - , , , -LENOVO,Microsoft Windows 7 ,00971-OEM-1982661-00231,x64-based PC -ACER,Microsoft Windows 10 Professional,00971-OEM-1982661-00231,x64-based PC -DELL,Microsoft Windows 8.1 Professional,00971-OEM-1982661-00231,x86-based PC diff --git a/lesson02/orders.json b/lesson02/orders.json deleted file mode 100644 index 4a60f00..0000000 --- a/lesson02/orders.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "orders": [ - { - "item": "\u0411\u0430\u043d\u0430\u043d", - "quantity": 3, - "price": 300, - "buyer": "Pupkin", - "date": "08.03.2019" - }, - { - "item": "\u042f\u0431\u043b\u043e\u043a\u043e", - "quantity": 2, - "price": 700, - "buyer": "Pupkin", - "date": "08.03.2019" - }, - { - "item": "\u0410\u043f\u0435\u043b\u044c\u0441\u0438\u043d", - "quantity": 3, - "price": 1200, - "buyer": "Pupkin", - "date": "08.03.2019" - }, - { - "item": "\u0413\u0440\u0443\u0448\u0430", - "quantity": 4, - "price": 500, - "buyer": "Pupkin", - "date": "08.03.2019" - } - ] -} \ No newline at end of file diff --git a/lesson02/task01.py b/lesson02/task01.py deleted file mode 100644 index fa4e888..0000000 --- a/lesson02/task01.py +++ /dev/null @@ -1,65 +0,0 @@ -#!/usr/bin/python3 -__author__ = "Андрей Петров" - -import csv -import re - - -""" -1. Задание на закрепление знаний по модулю CSV. Написать скрипт, осуществляющий выборку определенных данных из файлов -info_1.txt, info_2.txt, info_3.txt и формирующий новый «отчетный» файл в формате CSV. Для этого: -Создать функцию get_data(), в которой в цикле осуществляется перебор файлов с данными, их открытие и считывание данных. -В этой функции из считанных данных необходимо с помощью регулярных выражений извлечь значения параметров «Изготовитель -системы», «Название ОС», «Код продукта», «Тип системы». Значения каждого параметра поместить в соответствующий список. -Должно получиться четыре списка — например, os_prod_list, os_name_list, os_code_list, os_type_list. В этой же функции -создать главный список для хранения данных отчета — например, main_data — и поместить в него названия столбцов отчета -в виде списка: «Изготовитель системы», «Название ОС», «Код продукта», «Тип системы». Значения для этих столбцов также -оформить в виде списка и поместить в файл main_data (также для каждого файла); -Создать функцию write_to_csv(), в которую передавать ссылку на CSV-файл. В этой функции реализовать получение данных -через вызов функции get_data(), а также сохранение подготовленных данных в соответствующий CSV-файл; -Проверить работу программы через вызов функции write_to_csv(). ### -""" - - -def write_to_csv(file, data): - with open(file, 'w') as f_n: - f_n_writer = csv.writer(f_n) - for nrow in data: - f_n_writer.writerow(nrow) - - -def get_data(lst): - os_prod_list = [] - os_name_list = [] - os_code_list = [] - os_type_list = [] - main_data = [['Изготовитель системы', 'Название ОС', 'Код продукта', 'Тип системы']] - for file in lst: - datafile = open(file) - for row in datafile: - row = row.rstrip() - if re.match('Изготовитель системы', row): - os_prod_list.append(re.search(r'(Изготовитель системы).\s*(.*)', row).group(2)) - elif re.match('Название ОС', row): - os_name_list.append(re.search(r'(Название ОС).\s*(.*)', row).group(2)) - elif re.match('Код продукта', row): - os_code_list.append(re.search(r'(Код продукта).\s*(.*)', row).group(2)) - elif re.match('Тип системы', row): - os_type_list.append(re.search(r'(Тип системы).\s*(.*)', row).group(2)) - - for k in range(len(lst)): - main_data.append([ - os_prod_list[k], - os_name_list[k], - os_code_list[k], - os_type_list[k] - ]) - return main_data - - -if __name__ == "__main__": - res = get_data(['info_1.txt', 'info_2.txt', 'info_3.txt']) - write_to_csv('new_file.csv', res) - - with open('new_file.csv') as f_n: - print(f_n.read()) diff --git a/lesson02/task02.py b/lesson02/task02.py deleted file mode 100644 index 65b1628..0000000 --- a/lesson02/task02.py +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/python3 -__author__ = "Андрей Петров" - -import json - -""" -2. Задание на закрепление знаний по модулю json. Есть файл orders в формате JSON с информацией о заказах. Написать -скрипт, автоматизирующий его заполнение данными. Для этого: -Создать функцию write_order_to_json(), в которую передается 5 параметров — товар (item), количество (quantity), -цена (price), покупатель (buyer), дата (date). Функция должна предусматривать запись данных в виде словаря -в файл orders.json. При записи данных указать величину отступа в 4 пробельных символа; -Проверить работу программы через вызов функции write_order_to_json() с передачей в нее значений каждого параметра. -""" - - -def write_order_to_json(item, quantity, price, buyer, date): - with open('orders.json') as f_n: - dict_to_json = json.load(f_n) - print(dict_to_json) - dict_to_json['orders'].append({ - 'item': item, - 'quantity': quantity, - 'price': price, - 'buyer': buyer, - 'date': date, - }) - with open('orders.json', 'w') as f_w: - json.dump(dict_to_json, f_w, indent=4) - - -if __name__ == "__main__": - write_order_to_json('Банан', 3, 300, 'Pupkin', '08.03.2019') - write_order_to_json('Яблоко', 2, 700, 'Pupkin', '08.03.2019') - write_order_to_json('Апельсин', 3, 1200, 'Pupkin', '08.03.2019') - write_order_to_json('Груша', 4, 500, 'Pupkin', '08.03.2019') diff --git a/lesson02/task03.py b/lesson02/task03.py deleted file mode 100644 index 59d2de8..0000000 --- a/lesson02/task03.py +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/python3 -__author__ = "Андрей Петров" - -import yaml - -""" -### 3. Задание на закрепление знаний по модулю yaml. Написать скрипт, автоматизирующий сохранение данных -в файле YAML-формата. Для этого: -Подготовить данные для записи в виде словаря, в котором первому ключу соответствует список, второму — целое число, -третьему — вложенный словарь, где значение каждого ключа — это целое число с юникод-символом, отсутствующим -в кодировке ASCII (например, €); -Реализовать сохранение данных в файл формата YAML — например, в файл file.yaml. При этом обеспечить стилизацию файла -с помощью параметра default_flow_style, а также установить возможность работы с юникодом: allow_unicode = True; -Реализовать считывание данных из созданного файла и проверить, совпадают ли они с исходными. -""" - -def write_dict_to_yaml(dict, file): - with open(file, 'w') as f_n: - yaml.dump(dict, f_n, default_flow_style=False, allow_unicode = True) - - with open(file) as f_n: - f_n_content = yaml.load(f_n) - - print(f_n_content == dict) - -if __name__ == "__main__": - my_dict = { - '100€': [1, 2, 3, 4], - '200€': 8000, - '300€': { - 'first': [1,2,3,4], - 'second': 800, - } - } - - write_dict_to_yaml(my_dict, 'file.yaml') diff --git a/lesson03/client/__main__.py b/lesson03/client/__main__.py deleted file mode 100644 index 0e9c034..0000000 --- a/lesson03/client/__main__.py +++ /dev/null @@ -1,73 +0,0 @@ -#!/usr/bin/python3 -__author__ = "Андрей Петров" - -""" -клиент отправляет запрос серверу; -сервер отвечает соответствующим кодом результата. Клиент и сервер должны быть реализованы в виде отдельных скриптов, -содержащих соответствующие функции. - -Функции клиента: -сформировать presence-сообщение; -отправить сообщение серверу; -получить ответ сервера; -разобрать сообщение сервера; -параметры командной строки скрипта client.py []: addr — ip-адрес сервера; -port — tcp-порт на сервере, -по умолчанию 7777. - -""" -import re -import sys -import argparse -import socket -import json -from datetime import datetime - - -def createParser(): - parser = argparse.ArgumentParser() - parser.add_argument('host', nargs='?', default='localhost') - parser.add_argument('port', nargs='?', default='7777') - return parser - - -parser = createParser() -args = parser.parse_args(sys.argv[1:]) - -port = int(re.search('[0-9]{2,}', args.port).group(0)) - -sock = socket.socket() -sock.connect((args.host, port)) - -# сформировать presence-сообщение; -# В формате JIM -msg_presence = json.dumps( - { - "action": "presence", - "time": datetime.now().timestamp(), - "type": "status", - "user": { - "account_name": "User Name", - "status": "Yep, I am here!" - } - } -) - -# отправить сообщение серверу; -sock.send(msg_presence.encode()) - -# получить ответ сервера; -data = sock.recv(1024) -response = json.loads( - data.decode('utf-8') -) -# разобрать сообщение сервера; -if response.get('response') == 200: - print( - f"Response Message: {response.get('msg')}" - ) -else: - print( - f"Error: {response.get('error')}" - ) -sock.close() diff --git a/lesson03/server/__main__.py b/lesson03/server/__main__.py deleted file mode 100644 index 6a146c4..0000000 --- a/lesson03/server/__main__.py +++ /dev/null @@ -1,61 +0,0 @@ -#!/usr/bin/python3 -__author__ = "Андрей Петров" - -""" -клиент отправляет запрос серверу; -сервер отвечает соответствующим кодом результата. Клиент и сервер должны быть реализованы в виде отдельных скриптов, -содержащих соответствующие функции. - -Функции сервера: -принимает сообщение клиента; -формирует ответ клиенту; -отправляет ответ клиенту; -имеет параметры командной строки: -p — TCP-порт для работы (по умолчанию использует 7777); --a — IP-адрес для прослушивания (по умолчанию слушает все доступные адреса). -""" - -import sys -import json -import socket -import argparse - - -def createParser(): - parser = argparse.ArgumentParser() - parser.add_argument('-a', '--addr', nargs='?', default='') - parser.add_argument('-p', '--port', nargs='?', default='7777') - return parser - - -parser = createParser() -args = parser.parse_args(sys.argv[1:]) - - -sock = socket.socket() -sock.bind((args.addr, int(args.port))) -sock.listen(5) - -while True: - # принимает сообщение клиента; - client, address = sock.accept() - - data = client.recv(1024) - request = json.loads( - data.decode('utf-8') - ) - - # формирует ответ клиенту; - if request.get('action') == 'presence': - response = { - "response": 200, - "msg": f"Hi {request.get('user')['account_name']}" - } - else: - response = { - "response": 400, - "error": "Wrong action, try again" - } - - # отправляет ответ клиенту; - client.send(json.dumps(response).encode('utf-8')) - client.close() \ No newline at end of file diff --git a/log/client.log b/log/client.log new file mode 100644 index 0000000..4f3b8ea --- /dev/null +++ b/log/client.log @@ -0,0 +1,7 @@ + +2019-03-19 03:05:05,291 - CRITICAL - client - __main__ - client cant conntect to host:localhost port7777 +2019-03-19 03:10:20,565 - DEBUG - client - __main__ - Response Message: Hi vASYA +2019-03-19 03:10:29,685 - CRITICAL - client - __main__ - client cant conntect to host:localhost port7777 +2019-03-19 03:11:09,124 - CRITICAL - client - __main__ - client cant conntect to host:localhost port7777 +2019-03-19 03:11:14,589 - CRITICAL - client - __main__ - client cant conntect to host:localhost port7777 +2019-03-19 03:11:57,166 - DEBUG - client - __main__ - Response Message: Hi gfd diff --git a/log/server.log b/log/server.log new file mode 100644 index 0000000..934ad27 --- /dev/null +++ b/log/server.log @@ -0,0 +1,5 @@ +2019-03-19 03:10:13,134 - DEBUG - __main__ - Client detected ('127.0.0.1', 64775) +2019-03-19 03:10:20,565 - DEBUG - __main__ - client ('127.0.0.1', 64775) closed +2019-03-19 03:10:56,456 - DEBUG - __main__ - Client detected ('127.0.0.1', 64819) +2019-03-19 03:11:50,998 - DEBUG - __main__ - Client detected ('127.0.0.1', 64851) +2019-03-19 03:11:57,166 - DEBUG - __main__ - client ('127.0.0.1', 64851) closed diff --git a/server/__main__.py b/server/__main__.py new file mode 100644 index 0000000..c63036f --- /dev/null +++ b/server/__main__.py @@ -0,0 +1,73 @@ +#!/usr/bin/python3 +__author__ = "Андрей Петров" + +import sys + + + +import json +import socket +import argparse + + + + + +from protocol import ( + validate_request, make_response, + make_400, make_404 +) +from routes import resolve +from server_log_config import logger + +def createParser(): + parser = argparse.ArgumentParser() + parser.add_argument('-a', '--addr', nargs='?', default='') + parser.add_argument('-p', '--port', nargs='?', default='7777') + return parser + + +parser = createParser() +args = parser.parse_args(sys.argv[1:]) + + +sock = socket.socket() +sock.bind((args.addr, int(args.port))) +sock.listen(5) + + +try: + while True: + # принимает сообщение клиента; + client, address = sock.accept() + logger.debug(f'Client detected {address}') + + data = client.recv(1024) + request = json.loads( + data.decode('utf-8') + ) + + if validate_request(request): + controller = resolve(request.get('action')) + if controller: + try: + response = controller(request) + except Exception: + logger.critical(f'error 500 controller: {controller}') + response = make_response( + request, 500, + error='Internal server error.' + ) + else: + logger.critical(f"error 404 controller: {request.get('action')} not found") + response = make_404(request) + else: + response = make_400(request) + logger.critical(f"error 400 bad request: {request}") + + response_string = json.dumps(response) + client.send(response_string.encode('utf-8')) + client.close() + logger.debug(f"client {address} closed") +except KeyboardInterrupt: + sock.close() diff --git a/server/presence/controllers.py b/server/presence/controllers.py new file mode 100644 index 0000000..bf56412 --- /dev/null +++ b/server/presence/controllers.py @@ -0,0 +1,12 @@ +from protocol import make_response, make_400 + + +def send_presence(request): + user = request.get('user') + if not user: + return make_400(request) + return make_response( + request, + 200, + alert=f"Hi {user['account_name']}" + ) diff --git a/server/presence/routes.py b/server/presence/routes.py new file mode 100644 index 0000000..6679a46 --- /dev/null +++ b/server/presence/routes.py @@ -0,0 +1,6 @@ +from .controllers import send_presence + + +routes = [ + {'action': 'presence', 'controller': send_presence} +] diff --git a/server/protocol.py b/server/protocol.py new file mode 100644 index 0000000..0a269fe --- /dev/null +++ b/server/protocol.py @@ -0,0 +1,29 @@ +from datetime import datetime + + +def validate_request(raw): + request_time = raw.get('time') + request_action = raw.get('action') + if request_time and request_action: + return True + return False + + +def make_response(request, code, data=None, error=None, alert=None): + return { + 'action': request.get('action'), + 'user': request.get('user'), + 'time': datetime.now().timestamp(), + 'data': data, + 'response': code, + 'error': error, + 'alert': alert, + } + + +def make_400(request): + return make_response(request, 400, error='Wrong request format') + + +def make_404(request): + return make_response(request, 404, error='Action is not supported.') diff --git a/server/routes.py b/server/routes.py new file mode 100644 index 0000000..6458acb --- /dev/null +++ b/server/routes.py @@ -0,0 +1,29 @@ +import os +from functools import reduce +from importlib import __import__ + +from settings import INSTALLED_MODULES + + +def get_server_routes(): + return reduce( + lambda routes, module: routes + getattr(module, 'routes', []), + reduce( + lambda submodules, module: submodules + [getattr(module, 'routes', [])], + reduce ( + lambda modules, module: modules + [__import__(f'{module}.routes')], + INSTALLED_MODULES, + [] + ), + [] + ), + [] + ) + + +def resolve(action, routes=None): + routes_mapping = { + route['action']:route['controller'] + for route in routes or get_server_routes() + } + return routes_mapping.get(action, None) diff --git a/server/server_log_config.py b/server/server_log_config.py new file mode 100644 index 0000000..7410b14 --- /dev/null +++ b/server/server_log_config.py @@ -0,0 +1,18 @@ +""" +Создание именованного логгера; +Сообщения лога должны иметь следующий формат: "<дата-время> <уровеньважности> <имямодуля> <сообщение>"; +Журналирование должно производиться в лог-файл; +На стороне сервера необходимо настроить ежедневную ротацию лог-файлов. +""" + +import logging.handlers + +logger = logging.getLogger('server') +formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(module)s - %(message)s') +handler = logging.handlers.TimedRotatingFileHandler('log/server.log', when='d', interval=1, backupCount=4) + +handler.setFormatter(formatter) +handler.setLevel(logging.DEBUG) + +logger.addHandler(handler) +logger.setLevel(logging.DEBUG) \ No newline at end of file diff --git a/server/settings.py b/server/settings.py new file mode 100644 index 0000000..fe7eb4c --- /dev/null +++ b/server/settings.py @@ -0,0 +1,9 @@ +import os + +BASE_DIR = os.path.dirname( + os.path.abspath(__file__) +) + +INSTALLED_MODULES = [ + 'text', 'presence' +] \ No newline at end of file diff --git a/server/tests/protocol/test_make_response.py b/server/tests/protocol/test_make_response.py new file mode 100644 index 0000000..d9d34eb --- /dev/null +++ b/server/tests/protocol/test_make_response.py @@ -0,0 +1,43 @@ +import pytest +from datetime import datetime +from protocol import make_response + + +@pytest.fixture +def success_status(): + return 200 + + +@pytest.fixture +def valid_request(): + return { + 'action': 'upper_text', + 'time': datetime.now().timestamp() + } + + +@pytest.fixture +def assert_response(success_status): + return { + 'action': 'upper_text', + 'user': None, + 'time': datetime.now().timestamp(), + 'data': None, + 'response': success_status, + } + + +def test_make_response( + valid_request, + assert_response, + success_status +): + response = make_response( + valid_request, + success_status, + ) + + assert response.get('action') == assert_response.get('action') + assert response.get('user') == assert_response.get('user') + assert response.get('data') == assert_response.get('data') + assert response.get('code') == assert_response.get('code') diff --git a/server/tests/protocol/test_validate_request.py b/server/tests/protocol/test_validate_request.py new file mode 100644 index 0000000..33f13a4 --- /dev/null +++ b/server/tests/protocol/test_validate_request.py @@ -0,0 +1,28 @@ +import pytest +from datetime import datetime +from protocol import validate_request + + +@pytest.fixture +def valid_request(): + return { + 'action': 'upper_text', + 'time': datetime.now().timestamp() + } + + +@pytest.fixture +def invalid_request(): + return {} + + +def test_validate_request_success( + valid_request +): + assert validate_request(valid_request) == True + + +def test_validate_request_fail( + invalid_request +): + assert validate_request(invalid_request) == False diff --git a/server/tests/routes/test_resolve.py b/server/tests/routes/test_resolve.py new file mode 100644 index 0000000..7efe1d5 --- /dev/null +++ b/server/tests/routes/test_resolve.py @@ -0,0 +1,19 @@ +import pytest +from routes import resolve + + +@pytest.fixture +def controller(): + return lambda arg: arg + + +@pytest.fixture +def routes(controller): + return [ + {'action': 'upper_text', 'controller': controller}, + ] + + +def test_resolve(routes, controller): + resolved = resolve('upper_text', routes) + assert resolved == controller diff --git a/server/text/controllers.py b/server/text/controllers.py new file mode 100644 index 0000000..f2ae160 --- /dev/null +++ b/server/text/controllers.py @@ -0,0 +1,23 @@ +from protocol import make_response, make_400 + + +def get_upper_text(request): + data = request.get('data') + if not data: + return make_400(request) + return make_response( + request, + 200, + data.upper() + ) + + +def get_lower_text(request): + data = request.get('data') + if not data: + return make_400(request) + return make_response( + request, + 200, + data.lower() + ) diff --git a/server/text/routes.py b/server/text/routes.py new file mode 100644 index 0000000..57b929d --- /dev/null +++ b/server/text/routes.py @@ -0,0 +1,9 @@ +from .controllers import ( + get_upper_text, get_lower_text +) + + +routes = [ + {'action': 'upper_text', 'controller': get_upper_text}, + {'action': 'lower_text', 'controller': get_lower_text}, +] \ No newline at end of file diff --git a/server/text/tests/controllers/__init__.py b/server/text/tests/controllers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/server/text/tests/controllers/test_lower.py b/server/text/tests/controllers/test_lower.py new file mode 100644 index 0000000..1650704 --- /dev/null +++ b/server/text/tests/controllers/test_lower.py @@ -0,0 +1,38 @@ +import pytest +from datetime import datetime +from text.controllers import get_lower_text + + +@pytest.fixture +def lower_text_request(): + return { + 'action': 'lower_text', + 'time': datetime.now().timestamp(), + 'data': 'LOWER TEXT', + } + + +@pytest.fixture +def lower_text_response_data(): + return 'lower text' + + +@pytest.fixture +def empty_request(): + return { + 'action': 'upper_text', + 'time': datetime.now().timestamp() + } + + +def test_get_lower_text_is_lower( + lower_text_request, + lower_text_response_data +): + response = get_lower_text(lower_text_request) + assert response.get('data') == lower_text_response_data + + +def test_get_lower_text_empty(empty_request): + response = get_lower_text(empty_request) + assert response.get('response') == 400 diff --git a/server/text/tests/controllers/test_upper.py b/server/text/tests/controllers/test_upper.py new file mode 100644 index 0000000..1dd16cd --- /dev/null +++ b/server/text/tests/controllers/test_upper.py @@ -0,0 +1,38 @@ +import pytest +from datetime import datetime +from text.controllers import get_upper_text + + +@pytest.fixture +def valid_request(): + return { + 'action': 'upper_text', + 'time': datetime.now().timestamp(), + 'data': 'upper text', + } + + +@pytest.fixture +def empty_request(): + return { + 'action': 'upper_text', + 'time': datetime.now().timestamp() + } + + +@pytest.fixture +def upper_text_response_data(): + return 'UPPER TEXT' + + +def test_get_upper_text_is_upper( + valid_request, + upper_text_response_data +): + response = get_upper_text(valid_request) + assert response.get('data') == upper_text_response_data + + +def test_get_upper_text_empty(empty_request): + response = get_upper_text(empty_request) + assert response.get('response') == 400