Модуль 1
50,00ч

Погружение в Python

Целью изучения модуля «Погружение в Python» является формирование компетенций слушателей в области решения задач в Python, применения шаблонов проектирования на Python, работы с Python библиотеками, применения объектно-ориентированного и функционального программирования.

Задачи Модуля 1:
1) сформировать умение использовать базовые типы и конструкции языка программирования Python$
2) сформировать умение работать со стандартными структурами данных в Python, писать функции на Python, применять функциональные особенности языа, работать с файлами с помощью языка Python;
3) сформировать умение применять механизмы наследования, создавать классы и работать с ними, обрабатывать исключения;
4) сформировать умение искать и исправлять ошибки в программе на Python, тестировать программы на Python;
5) сформировать умение писать многопоточный код на Python, писать асинхронный код на Python, работать с сетью, создать своё серверное сетевое приложение.
Часов в программе
10,00 часов
лекции
18,00 часов
практика
10,00 часов
самостоятельная
12,00 часов
промежуточная аттестация
50,00 часов
всего
Материально-технические условия реализации программы:
Вид занятий: Лекция
Требуемое ПО:
Zoom
Браузер Chrome
Microsoft Visual Studio 2010 или выше
Вид занятий: Практическая работа
Требуемое ПО:
Zoom
Браузер Chrome
Microsoft Visual Studio 2010 или выше
Вид занятий: Промежуточная (итоговая) аттестация
Требуемое ПО:
Zoom
Браузер Chrome
Microsoft Visual Studio 2010 или выше
Информационные ресуры
Документация Postgres про сравнение строк - https://postgrespro.ru/docs/postgrespro/9.5/functions-matching
Документация Postgres про другие функции работы со строками - https://postgrespro.ru/docs/postgrespro/9.5/functions-string
Тестер регулярных выражений - https://www.regextester.com
Интерактивный учебник по SQL -http://www.sql-tutorial.ru/ru/content.html
Введение в анализ данных с помощью Pandas - https://habr.com/ru/post/196980/
Начало работы с Power BI - https://docs.microsoft.com/ru-ru/power-bi/fundamentals/desktop-getting-started
Образовательные ресуры
https://www.coursera.org/learn/diving-in-python/home/week/1

Учебно-методические материалы

Методы, формы и технологии

Методы обучения:
- лекция;
- практическая работа под руководством учителя;
- самостоятельная практическая работа;
- изучение литературы по теме.
Методы контроля:
- выполнение практических занятий по темам лекций;
- выполнение итогового задания.
Формы организации учебных занятий:
- лекция;
- вебинар с элементами практической работы и разбора теоретического материала.
Формы организации учебной деятельности:
- групповая работа;
- индивидуальная работа.
Дистанционные образовательные технологии:
- использование образовательных интернет-ресурсов;
- использование ресурсов, созданных преподавателем (ноутбуки для решения задач по программированию);
- WEB-консультации и другие.

Методические разработки

Задание для промежуточной (итоговой) аттестации
Проект «Сервер для приема метрик»
Проект предполагает очную защиту перед экзаменационной комиссией. Перед комиссией студент готовит материалы согласно заданию и направляет экзаменационной комиссии не менее чем за 2 дня до назначенной даты экзамена
Ранее вы разработали клиентское сетевое приложение — клиента для сервера метрик, который умеет отправлять и получать данные о различных численных показателях. Пришло время финального задания — в нем необходимо реализовать серверную часть.
Как обычно, вам необходимо разработать программу в одном файле-модуле, который вы загрузите на проверку обычным способом. Сервер должен соответствовать протоколу, который был описан в задании к предыдущей неделе. Он должен уметь принимать от клиентов команды put и get, разбирать их, и формировать ответ согласно протоколу. По запросу put требуется сохранять метрики в структурах данных в памяти процесса. По запросу get сервер обязан отдавать данные в правильной последовательности. При работе с клиентом сервер должен поддерживать сессии, соединение с клиентом между запросами не должно "разрываться".
На верхнем уровне вашего модуля должна быть объявлена функция run_server(host, port) — она принимает адрес и порт, на которых должен быть запущен сервер.
Для проверки правильности решения мы воспользуемся своей реализацией клиента и будем отправлять на ваш сервер put и get запросы, ожидая в ответ правильные данные от сервера (согласно объявленному протоколу). Все запросы будут выполняться с таймаутом — сервер должен отвечать за приемлемое время.
Сервер должен быть готов к неправильным командам со стороны клиента и отдавать клиенту ошибку в формате, оговоренном в протоколе. В этих случаях работа сервера не должна завершаться аварийно.
Пример tcp-сервера на asyncio:

import asyncio

class ClientServerProtocol(asyncio.Protocol):
def connection_made(self, transport):
self.transport = transport

def data_received(self, data):
resp = process_data(data.decode())
self.transport.write(resp.encode())

loop = asyncio.get_event_loop()
coro = loop.create_server(
ClientServerProtocol,
'127.0.0.1', 8181
)

server = loop.run_until_complete(coro)

try:
loop.run_forever()
except KeyboardInterrupt:
pass

server.close()
loop.run_until_complete(server.wait_closed())
loop.close()

Данный код создает tcp-соединение для адреса 127.0.0.1:8181 и слушает все входящие запросы. При подключении клиента будет создан новый экземпляр класса ClientServerProtocol, а при поступлении новых данных вызовется метод этого объекта - data_received. Внутри asyncio.Protocol спрятана вся магия обработки запросов через корутины, остается реализовать протокол взаимодействия между клиентом и сервером.
Вы можете использовать этот код, как основу при написании вашей реализации сервера. Это не обязательное требование. Для реализации задачи вы можете использовать любые вызовы из стандартной библиотеки Python 3 (обратим ваше внимание, что в грейдере установлена версия Python 3.6). Сервер должен уметь обрабатывать запросы от нескольких клиентов одновременно.
В процессе разработки сервера для тестирования работоспособности вы можете использовать клиент, написанный на предыдущей неделе.
Рассмотрим текстовый протокол в действии при использовании утилиты telnet:

$: telnet 127.0.0.1 8888
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
> get test_key
< ok
<
> got test_key
< error
< wrong command
<
> put test_key 12.0 1503319740
< ok
<
> put test_key 13.0 1503319739
< ok
<
> get test_key
< ok
< test_key 13.0 1503319739
< test_key 12.0 1503319740
<
> put another_key 10 1503319739
< ok
<
> get *
< ok
< test_key 13.0 1503319739
< test_key 12.0 1503319740
< another_key 10.0 1503319739
<

Также вы можете воспользоваться вспомогательным скриптом, который использует "'эталонную" реализацию клиента, открывающуюся после сдачи задания на пятой неделе, для локального тестирования написанного вами сервера:

"""
Это вспомогательный скрипт для тестирования сервера из задания на неделе 6.

Для запуска скрипта на локальном компьютере разместите рядом файл client.py,
где содержится код клиента, который открывается по прохождении задания
недели 5.

Сначала запускаете ваш сервер на адресе 127.0.0.1 и порту 8888, а затем
запускаете этот скрипт.
"""
import sys
from client import Client, ClientError

def run(host, port):
client1 = Client(host, port, timeout=5)
client2 = Client(host, port, timeout=5)
command = "wrong command test\n"

try:
data = client1.get(command)
except ClientError:
pass
except BaseException as err:
print(f"Ошибка соединения с сервером: {err.__class__}: {err}")
sys.exit(1)
else:
print("Неверная команда, отправленная серверу, должна возвращать ошибку протокола")
sys.exit(1)

command = 'some_key'
try:
data_1 = client1.get(command)
data_2 = client1.get(command)
except ClientError:
print('Сервер вернул ответ на валидный запрос, который клиент определил, '
'как не корректный.. ')
except BaseException as err:
print(f"Сервер должен поддерживать соединение с клиентом между запросами, "
f"повторный запрос к серверу завершился ошибкой: {err.__class__}: {err}")
sys.exit(1)

assert data_1 == data_2 == {}, \
"На запрос клиента на получения данных по не существующему ключу, сервер " \
"вдолжен озвращать ответ с пустым полем данных."

try:
data_1 = client1.get(command)
data_2 = client2.get(command)
except ClientError:
print('Сервер вернул ответ на валидный запрос, который клиент определил'
', как не корректный.. ')
except BaseException as err:
print(f"Сервер должен поддерживать соединение с несколькими клиентами: "
f"{err.__class__}: {err}")
sys.exit(1)

assert data_1 == data_2 == {}, \
"На запрос клиента на получения данных по не существующему ключу, сервер " \
"должен возвращать ответ с пустым полем данных."

try:
client1.put("k1", 0.25, timestamp=1)
client2.put("k1", 2.156, timestamp=2)
client1.put("k1", 0.35, timestamp=3)
client2.put("k2", 30, timestamp=4)
client1.put("k2", 40, timestamp=5)
client1.put("k2", 41, timestamp=5)
except Exception as err:
print(f"Ошибка вызова client.put(...) {err.__class__}: {err}")
sys.exit(1)

expected_metrics = {
"k1": [(1, 0.25), (2, 2.156), (3, 0.35)],
"k2": [(4, 30.0), (5, 41.0)],
}

try:
metrics = client1.get("*")
if metrics != expected_metrics:
print(f"client.get('*') вернул неверный результат. Ожидается: "
f"{expected_metrics}. Получено: {metrics}")
sys.exit(1)
except Exception as err:
print(f"Ошибка вызова client.get('*') {err.__class__}: {err}")
sys.exit(1)

expected_metrics = {"k2": [(4, 30.0), (5, 41.0)]}

try:
metrics = client2.get("k2")
if metrics != expected_metrics:
print(f"client.get('k2') вернул неверный результат. Ожидается: "
f"{expected_metrics}. Получено: {metrics}")
sys.exit(1)
except Exception as err:
print(f"Ошибка вызова client.get('k2') {err.__class__}: {err}")
sys.exit(1)

try:
result = client1.get("k3")
if result != {}:
print(
f"Ошибка вызова метода get с ключом, который еще не был добавлен. "
f"Ожидается: пустой словарь. Получено: {result}")
sys.exit(1)
except Exception as err:
print(f"Ошибка вызова метода get с ключом, который еще не был добавлен: "
f"{err.__class__} {err}")
sys.exit(1)

print("Похоже, что все верно! Попробуйте отправить решение на проверку.")

if __name__ == "__main__":
run("127.0.0.1", 8888)

Материалы курса

Примеры заданий по модулю 1
Практические задания

Тема 1. Введение в Python
Написать программу (скрипт), которая будет запускаться из командной строки. Программа принимает в качестве аргумента строку, состоящую из цифр. Гарантируется, что других символов в переданном параметре нет и на вход всегда подается не пустая строка. Программа должна вычислить сумму цифр из которых состоит строка и вывести полученный результат на печать в стандартный вывод.
Примеры работы программы:

$ python solution.py 12345
15
$ python solution.py 160438521039
42

Считать переданный параметр можно с помощью модуля стандартной библиотеки sys:

import sys

digit_string = sys.argv[1]

Выполнение этого кода, создаст переменную digit_string, значением которой будет строка, переданная в параметре при запуске вашей программы. Более подробно о модуле sys и передаче параметров в скрипт вы можете прочитать в документации к модулю sys.
Файл с программой должен называться solution.py. После написания и отладки вашего решения, вам необходимо загрузить файл solution.py на платформу для проверки.


Тема 2. Структуры данных и функции
Реализовать собственное key-values хранилище. Данные будут сохраняться в файле storage.data. Добавление новых данных в хранилище и получение текущих значений осуществляется с помощью утилиты командной строки storage.py. Пример работы утилиты:
Сохранение значения value по ключу key_name:

$ storage.py --key key_name --val value

Получение значения по ключу key_name:

$ storage.py --key key_name

Вашей задачей будет написать реализацию утилитыstorage.py.
Утилита может вызваться со следующими параметрами:
--key <имя ключа> , где <имя ключа> - ключ по которому сохраняются/получаются значения
--val <значение>, где <значение> - сохраняемое значение.
Если при запуске утилиты переданы оба ключа, происходит добавление переданного значения по ключу и сохранение данных в файле. Если передано только имя ключа, происходит чтение файла хранилища и вывод на печать значений, которые были сохранены по данному ключу. Обратите внимание, что значения по одному ключу не перезаписываются, а добавляются к уже сохраненным. Другими словами - по одному ключу могут храниться несколько значений. При выводе на печать, значения выводятся в порядке их добавления в хранилище. Формат вывода на печать для нескольких значений:

value_1, value_2

Обратите внимание на пробел после запятой. Если значений по ключу не было найдено, выведите пустую строку или None.
Для работы с аргументами командной строки используйте модуль argparse. Хранить данные в файле мы рекомендуем в формате JSON с помощью использования модуля стандартной библиотеки json. Прежде чем отправлять ваше решение на проверку, протестируйте работу вашей утилиты на добавление нескольких ключей и разных значений.
Файл следует создавать с помощью модуля tempfile.

import os
import tempfile

storage_path = os.path.join(tempfile.gettempdir(), 'storage.data')
with open(storage_path, 'w') as f:
...

Пример работы:

$ python storage.py --key key_name --val value
$ python storage.py --key key_name
value

$ python storage.py --key multi_key --val value1
$ python storage.py --key multi_key --val value2
$ python storage.py --key multi_key
value1, value2

Тема 3. Объектно-ориентированное программирование
Написать python-модуль solution.py, внутрь которого необходимо поместить код класса FileReader. Конструктор этого класса принимает один параметр: путь до файла на диске. В классе FileReader должен быть реализован метод read, возвращающий строку - содержимое файла, путь к которому был указан при создании экземпляра класса. Python модуль должен быть написан таким образом, чтобы импорт класса FileReader из него не вызвал ошибок.
При написании реализации метода read, вам нужно учитывать случай, когда при инициализации был передан путь к несуществующему файлу. Требуется обработать возникающее при этом исключение FileNotFoundError и вернуть из метода read пустую строку.
Пример работы:

>>> from solution import FileReader
>>> reader = FileReader('not_exist_file.txt')
>>> text = reader.read()
>>> text
''
>>> with open('some_file.txt', 'w') as file:
... file.write('some text')
...
9
>>> reader = FileReader('some_file.txt')
>>> text = reader.read()
>>> text
'some text'
>>> type(reader)
<class 'solution.FileReader'>
>>>

Тема 4. Углубленный Python
Создать интерфейс для работы с файлами. Интерфейс должен предоставлять следующие возможности по работе с файлами:
- чтение из файла, метод read возвращает строку с текущим содержанием файла
- запись в файл, метод write принимает в качестве аргумента строку с новым содержанием файла
- сложение объектов типа File, результатом сложения является объект класса File, при этом создается новый файл и файловый объект, в котором содержимое второго файла добавляется к содержимому первого файла. Новый файл должен создаваться в директории, полученной с помощью функции tempfile.gettempdir. Для получения нового пути можно использовать os.path.join.
- возвращать в качестве строкового представления объекта класса File полный путь до файла
- поддерживать протокол итерации, причем итерация проходит по строкам файла
При создании экземпляра класса File в конструктор передается полный путь до файла на файловой системе. Если файла с таким путем не существует, он должен быть создан при инициализации.
Пример работы:

>>> import os.path
>>> from solution import File
>>> path_to_file = 'some_filename'
>>> os.path.exists(path_to_file)
False
>>> file_obj = File(path_to_file)
>>> os.path.exists(path_to_file)
True
>>> file_obj.read()
''
>>> file_obj.write('some text')
9
>>> file_obj.read()
'some text'
>>> file_obj.write('other text')
10
>>> file_obj.read()
'other text'
>>> file_obj_1 = File(path_to_file + '_1')
>>> file_obj_2 = File(path_to_file + '_2')
>>> file_obj_1.write('line 1\n')
7
>>> file_obj_2.write('line 2\n')
7
>>> new_file_obj = file_obj_1 + file_obj_2
>>> isinstance(new_file_obj, File)
True
>>> print(new_file_obj)
C:\Users\Media\AppData\Local\Temp\71b9e7b695f64d85a7488f07f2bc051c
>>> for line in new_file_obj:
.... print(ascii(line))
'line 1\n'
'line 2\n'

Тема 5. Многопоточное и асинхронное программирование
В крупных проектах, с большим количеством пользователей, необходимо тщательно наблюдать за всеми процессами, происходящими в нем. Информация о процессах может быть представлена различными численными показателями, например: количество запросов к вашему приложению, время ответа вашего сервиса на каждый запрос, количество пользователей в сутки и другие. Эти различные численные показатели мы будем называть метриками.
Для сбора, хранения и отображения подобных метрик существуют готовые решения, например Graphite, InfluxDB. Мы в рамках курса разработаем свою систему для сбора и хранения метрик, основанную на клиент-серверной архитектуре.
На этой неделе мы начнем с разработки клиента для отправки и получения метрик. На следующей неделе, в качестве финального задания, вам будет предложено реализовать сервер для хранения метрик.

Протокол взаимодействия
Прежде, чем приступить к описанию задания, рассмотрим протокол взаимодействия, по которому будет происходить обмен данными между клиентом и сервером.
Клиент и сервер взаимодействуют между собой по простому текстовому протоколу через TCP сокеты. Текстовый протокол имеет главное преимущество – наглядность, можно просматривать диалог взаимодействия клиентской и серверной стороны без использования дополнительных инструментов.
Общий формат запросов и ответов.
Протокол поддерживает два вида запросов к серверу со стороны клиента:
- отправка данных для сохранения их на сервере
- получения сохраненных данных
Общий формат запроса клиента:

<команда> <данные запроса><\n>

где:
- <команда> - команда сервера (команда может принимать одно из двух значений: put — сохранить данные на сервере, get — вернуть сохраненные данные с сервера),
- <данные запроса> - данные запроса (их формат мы подробно разберем ниже в примере),
- <\n> - символ переноса строки.
Обратим ваше внимание на пробел между командой и данными запроса и его отсутствием между данными и символом перевода на новую строку.
Общий формат ответов сервера:

<статус ответа><\n><данные ответа><\n\n>

где:
- <статус ответа> - статус выполнения команды, допустимы два варианта: «ok» - команда успешно выполнена на сервере и «error» - выполнение команды завершилось ошибкой
- <данные ответа> - не обязательное поле (формат ответа и случаи его отсутствия будут рассмотрены в примере ниже)
- <\n\n> - два символа переноса строки.
Обратите внимание, что статус ответа и данные ответа разделены символом перевода строки <\n>.
Пример взаимодействия сервера и клиента.
Для наглядности рассмотрим протокол взаимодействия между клиентом и сервером на конкретном примере. В примере мы будем, собирать данные о работе операционной системы: cpu (загрузка процессора), usage (потребление памяти), disk_usage (потребление места на жестком диске), network_usage (статистика сетевых интерфейсов). Такие данные могут понадобится для контроля загрузки серверов и прогноза по расширению парка железа компании - проще говоря для мониторинга.
Какие данные мы будем сохранять?
Для каждой метрики (<key>) мы будем хранить данные о ее значениях (<value>) и времени, когда производилось измерение (<timestamp>) . Поскольку, в реальной жизни серверов может быть несколько, необходимо различать данные полученные от разных серверов (в нашем примере имеются в наличии два сервера palm и eardrum). Договоримся об именовании метрик, название метрики <key> в примере мы будем определять их по правилу:

<название сервера>.<название данных>

Примеры названий метрик: "palm.cpu", "eardrum.memory".
Таким образом на сервере по каждому ключу будет сохранятся список данных конкретных измерений (пара: значение, время измерения).
Обратим ваше внимание, на то что названия метрик могут быть произвольными и отличаться от тех, что используются в данном примере (название метрики не обязательно должно быть составным и иметь точку в названии).
Запросы клиента.
Рассмотрим пример отправки на сервер данных для сохранения. Пусть у нас имеются данные измерений - загрузка процессора «cpu» на сервере "palm" во время 1150864247 была равна 23.7 процента. Строка запроса в этом случае будет иметь вид:

put palm.cpu 23.7 1150864247\n

В запросе на сохранение мы можем передать данные только об одном измерении.
Чтобы получить с сервера данные, сохраненные по ключу «palm.cpu», необходимо в данных запроса просто передать имя ключа:

get palm.cpu\n

Для случая, когда необходимо получить все хранимые на сервере данные, в качестве ключа используется символ звездочки «*». Пример строки запроса:

get *\n

Ответы сервера
Допустим, что на сервере хранятся данные:

key | value | timestamp
-----------------------------------

"palm.cpu" | 2.0 | 1150864247

"palm.cpu" | 0.5 | 1150864248

"eardrum.cpu" | 3.0 | 1150864250

Тогда в ответ на запрос о получении данных по ключу "palm.cpu" сервер отправит строку:

ok\npalm.cpu 2.0 1150864247\npalm.cpu 0.5 1150864248\n\n

Данные ответа содержат данные о каждой сохраненной записи с ключом "palm.cpu" (метрика, значение, временная метка разделенные пробелом), которые разделены символом перевода строки «\n».
Строка ответа сервера на запрос о получении всех хранящихся на сервере данных (в качестве ключа передано «*») в нашем случае будет таким:
ok\npalm.cpu 2.0 1150864247\npalm.cpu 0.5 1150864248\neardrum.cpu 3.0 1150864250\n\n

В случаях:
- когда в запросе на получение данных передан не существующий ключ
- успешного выполнения команды сохранения данных put
сервер отправляет клиенту строку со статусом «оk» и пустым полем с данными ответа:

ok\n\n

Если в параметре запроса переданы не валидные данные (например: нарушен формат запроса, ошибочная команда или значения value и timestamp не могут быть приведены к необходимому типу данных) сервер отправляет строку со статусом ответа «error» и данными ответа «wrong command»:

error\nwrong command\n\n

Реализация клиента
Необходимо реализовать класс Client, в котором будет инкапсулировано соединение с сервером, клиентский сокет и методы для получения (get) и отправки (put) метрик на сервер. Отправка и получение данных в методах get и put должна быть реализована в соответствии с протоколом, описанным выше. В конструктор класса Client должна передаваться адресная пара хост и порт, а также необязательный аргумент timeout (имеющий значение по умолчанию - None). Соединение с сервером устанавливается при создании экземпляра класса Client и не должно разрываться между запросами.
Пример создания объекта клиента и отправки запросов на сервер:

>>> from solution import Client

>>> client = Client("127.0.0.1", 8888, timeout=15)

>>> client.put("palm.cpu", 0.5, timestamp=1150864247)

>>> client.put("palm.cpu", 2.0, timestamp=1150864248)

>>> client.put("palm.cpu", 0.5, timestamp=1150864248)

>>> client.put("eardrum.cpu", 3, timestamp=1150864250)

>>> client.put("eardrum.cpu", 4, timestamp=1150864251)

>>> client.put("eardrum.memory", 4200000)

>>> print(client.get("*"))



Метод put
Метод put принимает в качестве параметров: название метрики, численное значение и необязательный именованный параметр timestamp. Если пользователь вызвал метод put без аргумента timestamp, то клиент автоматически должен подставить значение временной отметки, полученное с помощью вызова int(time.time()).
Метод put не возвращает ничего в случае успешной отправки и выбрасывает пользовательское исключение ClientError в случае не успешной.

Метод get
Метод get принимает в качестве параметра имя метрики, значения которой мы хотим получить. В качестве имени метрики можно использовать символ «*», о котором мы упоминали в описании протокола.
Метод get возвращает словарь с метриками (смотрите пример ниже) в случае успешного получения ответа от сервера и выбрасывает исключение ClientError в случае не успешного.
Клиент получает данные от сервера в текстовом виде, метод get должен обработать строку ответа и вернуть словарь с полученными ключами с сервера. Значением ключей в словаре является список кортежей:

[(timestamp1, metric_value1), (timestamp2, metric_value2), …]

Значение timestamp и metric_value должны быть преобразованы соответственно к типам int и float. Список должен быть отсортирован по значению timestamp (по возрастанию).
Пример возвращаемого значения при успешном вызове client.get("palm.cpu"):
{
'palm.cpu': [
(1150864247, 0.5),
(1150864248, 0.5)
]
}

Обратите внимание, что сервер хранит данные с максимальным разрешением в одну секунду. Это означает, что если в одну и ту же секунду отправить две одинаковые метрики, то будет сохранено только одно значение, которое было обработано последним. Все остальные значения будут перезаписаны. По этой причине запрос по ключу "palm.cpu" вернул данные двух измерений.
Пример возвращаемого значения при успешном вызове client.get("*"):

{
'palm.cpu': [
(1150864247, 0.5),
(1150864248, 0.5)
],
'eardrum.cpu': [
(1150864250, 3.0),
(1150864251, 4.0)
],
'eardrum.memory': [
(1503320872, 4200000.0)
]
}
Если в ответ на get-запрос сервер вернул положительный ответ "ok\n\n", но без данных (то есть данных по запрашиваемому ключу нет), то метод get клиента должен вернуть пустой словарь.
Итак, в качестве решения вам необходимо предоставить модуль с реализованным в нем классом Client, пользовательским исключением ClientError. В классе Client должны быть доступны методы get и put с описанной выше сигнатурой. При вызове методов get и put клиент должен посылать сообщения в TCP-соединение с сервером (в соответствии с описанным текстовым протоколом), получать ответ от сервера и возвращать словарь с данными, в формате описанном выше.
Примечание.
Не смотря на то, что на этой неделе вы изучали асинхронность, клиент должен быть синхронным. Не расстраивайтесь, если вы хотели попробовать свои силы в написании асинхронного кода, на следующей неделе вам представится такая возможность.

Учебная литература

Основная литература:
Think Python [Электронный ресурс] – Режим доступа - https://greenteapress.com/wp/think-python-2e/
Automate the Boring Stuff with Python [Электронный ресурс] – Режим доступа - https://automatetheboringstuff.com/
Dive Into Python 3 [Электронный ресурс] – Режим доступа -http://diveintopython3.problemsolving.io/
Problem Solving with Algorithms and Data Structures using Python [Электронный ресурс] – Режим доступа -https://runestone.academy/runestone/static/pythonds/index.html
Swaroop Chitlur. A Byte of Python [Электронный ресурс] – Режим доступа - https://wombat.org.ua/AByteOfPython/AByteofPythonRussian-2.02.pdf – 2020.
Федоров Д. Основы программирования на примере языка Python [Текст] : учебное пособие / Д. Федоров. - 2018.
Свейгарт Э. Автоматизация рутинных задач с помощью Python [Текст] : практическое руководство для начинающих / Эл Свейгарт. – 2017.

Дополнительная литература:
Маккинни У. Python и анализ данных [Текст] / У. Маккинни. – 2015.

Перечень учебно-методического обеспечения для самостоятельной работы обучающихся по дисциплине (модулю)
Бэрри П. Изучаем программирование на Python [Текст] / П. Бэрри. – 2017.
Савельев В. Статистика и котики [Текст] / Владимир Савельев. – 2018.
Бослав С. Статистика для всех [Текст] / Сара Бослав. – 2015.
Хамидуллин Р. Я. Теория вероятностей и математическая статистика [Текст] / Р.Я. Хамидуллин. – 2020.
Справочник по функциям DAX [Электронный ресурс] – Режим доступа - https://docs.microsoft.com/ru-ru/dax/dax-function-reference
Талер Р. Nudge. Архитектура выбора [Текст] / Ричард Талер. – 2017.
Желязны Дж. Говори на языке диаграмм [Текст] : пособие по визуальным коммуникациям / Джин Желязны. – 2020.

Перечень ресурсов информационно-телекоммуникационной сети "Интернет", необходимых для освоения дисциплины (модуля)
Документация Postgres про сравнение строк - https://postgrespro.ru/docs/postgrespro/9.5/functions-matching
Документация Postgres про другие функции работы со строками - https://postgrespro.ru/docs/postgrespro/9.5/functions-string
Тестер регулярных выражений - https://www.regextester.com
Интерактивный учебник по SQL -http://www.sql-tutorial.ru/ru/content.html
Введение в анализ данных с помощью Pandas - https://habr.com/ru/post/196980/
Начало работы с Power BI - https://docs.microsoft.com/ru-ru/power-bi/fundamentals/desktop-getting-started

Темы

Введение в Python Структуры данных и функции Объектно-ориентированное программирование Углубленный Python Многопоточное и асинхронное программирование
Лекции
2,00ч
Практические занятия
2,00ч
Всего
4,00ч
Лекции
2,00ч
Практические занятия
4,00ч
Самостоятельная работа
2,00ч
Всего
8,00ч
Лекции
2,00ч
Практические занятия
4,00ч
Самостоятельная работа
2,00ч
Всего
8,00ч
Лекции
2,00ч
Практические занятия
4,00ч
Самостоятельная работа
2,00ч
Всего
8,00ч
Лекции
2,00ч
Практические занятия
4,00ч
Самостоятельная работа
4,00ч
Всего
10,00ч
Промежуточная аттестация 12,00 часов
Зачет