diff --git a/(K+U+B)^3=KUB v2.py b/(K+U+B)^3=KUB v2.py index 385488445..5bfa792ba 100644 --- a/(K+U+B)^3=KUB v2.py +++ b/(K+U+B)^3=KUB v2.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/gil9red/SimplePyScripts/issues/13#issuecomment-943412289 @@ -15,5 +15,5 @@ for num in range(100, 1000): kub = set(str(num)) - if len(kub) == 3 and sum(map(int, kub))**3 == num: + if len(kub) == 3 and sum(map(int, kub)) ** 3 == num: print(f'({"+".join(str(num))})^3={num}') diff --git a/(K+U+B)^3=KUB.py b/(K+U+B)^3=KUB.py index e49f99cc9..20618a4b5 100644 --- a/(K+U+B)^3=KUB.py +++ b/(K+U+B)^3=KUB.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://stepik.org/lesson/360560/step/13?unit=345000 @@ -23,5 +23,5 @@ kub = k * 100 + u * 10 + b # Проверка, что число трехзначное или не совпадает условие - if kub <= 999 and kub == (k+u+b) ** 3: - print(f'({k}+{u}+{b})^3={kub}') + if kub <= 999 and kub == (k + u + b) ** 3: + print(f"({k}+{u}+{b})^3={kub}") diff --git a/.gitignore b/.gitignore index 6a1c0e53b..ac96cbc34 100644 --- a/.gitignore +++ b/.gitignore @@ -108,5 +108,13 @@ docs/_build/ target/ # Current project +.gigaide/ +backup/ +database/ TOKEN.txt LOGIN_PASSWORD.txt +geckodriver.log +*.sqlite* +*.db +log.txt +settings.* diff --git a/AnalysisOfChatLog/AnalysisOfChatLog.py b/AnalysisOfChatLog/AnalysisOfChatLog.py index da11354b3..02cb13777 100644 --- a/AnalysisOfChatLog/AnalysisOfChatLog.py +++ b/AnalysisOfChatLog/AnalysisOfChatLog.py @@ -1,25 +1,27 @@ # coding=utf-8 -__author__ = 'ipetrash' +__author__ = "ipetrash" # Есть лог-файл какого-то чата. Посчитать «разговорчивость» пользователей в нем в виде ник — количество фраз. # Посчитать среднее число букв на участника чата. -if __name__ == '__main__': - #file_name = raw_input("Chat log file: ") - file_name = "C:\Users\ipetrash\Desktop\chat log.txt" - user_count = {} - for line in file(file_name): - line = line.decode("windows-1251").replace("\n", "") - user, count = line.split(":")[0], len(line.split(":")[1].split(' ')) - 1 - if user in user_count: - user_count[user] += count - else: - user_count[user] = count - - sum = 0.0 - for user, count in user_count.items(): - sum += count - print(user + " = %d" % count) - - print("Average = %s" % (sum / len(user_count)) ) \ No newline at end of file + +file_name = r"chat log.txt" + +user_count: dict[str, int] = dict() +for line in open(file_name, encoding="utf-8"): + line = line.strip() + + user, text = line.split(":", maxsplit=1) + count = len(text.strip().split(" ")) - 1 + if user in user_count: + user_count[user] += count + else: + user_count[user] = count + +total = 0 +for user, count in user_count.items(): + total += count + print(f"{user} = {count}") + +print(f"Average = {total // len(user_count)}") diff --git a/AnalysisOfChatLog/chat log.txt b/AnalysisOfChatLog/chat log.txt new file mode 100644 index 000000000..800b904d7 --- /dev/null +++ b/AnalysisOfChatLog/chat log.txt @@ -0,0 +1,14 @@ +xxx: Многомного зарядный револьвер конечно жесть. Зачем оно надо? Ведь мы прекресно знаем из американских фильмов, что в обычном шестизарядном барабане пули не кончяются никогда, машина взрывается от одного выстрела девятым калибром, а стоит тебе спасти девушку от смерти, как она тут же с радостью даёт. +yyy: Чувак, в 1855 году не было автомобилей. От одного выстрела взрывалась лошадь. +zzz: Да-да, у ковбоев были специальные отравленные пули - с ДВУМЯ каплями никотина. +xxx: интересно, сколько интересно щас емкость всего инета? +xxx: нашел: Объем общемировых данных вырастет с 33 зеттабайт в 2018 году до 175 в 2025. +xxx: я таких приставок даже не знаю +yyy: да бля +yyy: 99 процентов это порно +yyy: остальные 99 процентов это видео с котиками +yyy: еще 99 процентов это порно с котиками. +yyy: и того 297 процентов говна +xxx: Пойду заниматься похоронным бизнесом. Кто со мной жмите класс. +yyy: Супер мега идея, я щас умру от восторга! +ххх: Будешь первым клиентом) \ No newline at end of file diff --git a/BFS__breadth_first_search.py b/BFS__breadth_first_search.py index eaa7f04fa..ff69f3612 100644 --- a/BFS__breadth_first_search.py +++ b/BFS__breadth_first_search.py @@ -1,17 +1,19 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://stackoverflow.com/a/47902476/5909792 from collections import deque -from typing import List, Tuple, Any +from typing import Any # Breadth-first search -def bfs(grid: List[List[Any]], start: Tuple[Any, Any], goal: Any, wall: Any) -> List[Tuple[Any, Any]]: +def bfs( + grid: list[list[Any]], start: tuple[Any, Any], goal: Any, wall: Any +) -> list[tuple[Any, Any]]: width, height = len(grid[0]), len(grid) queue = deque([[start]]) seen = {start} @@ -23,12 +25,17 @@ def bfs(grid: List[List[Any]], start: Tuple[Any, Any], goal: Any, wall: Any) -> return path for x2, y2 in ((x + 1, y), (x - 1, y), (x, y + 1), (x, y - 1)): - if 0 <= x2 < width and 0 <= y2 < height and grid[y2][x2] != wall and (x2, y2) not in seen: + if ( + 0 <= x2 < width + and 0 <= y2 < height + and grid[y2][x2] != wall + and (x2, y2) not in seen + ): queue.append(path + [(x2, y2)]) seen.add((x2, y2)) -if __name__ == '__main__': +if __name__ == "__main__": start = 5, 2 wall, goal = "#", "*" grid = [ @@ -39,8 +46,8 @@ def bfs(grid: List[List[Any]], start: Tuple[Any, Any], goal: Any, wall: Any) -> list("......*..."), ] x, y = start - grid[y][x] = '@' # Start - print('\n'.join(''.join(row) for row in grid)) + grid[y][x] = "@" # Start + print("\n".join("".join(row) for row in grid)) # .......... # ..*#...##. # ..##.@.#*. @@ -54,8 +61,8 @@ def bfs(grid: List[List[Any]], start: Tuple[Any, Any], goal: Any, wall: Any) -> # [(5, 2), (4, 2), (4, 3), (4, 4), (5, 4), (6, 4)] for x, y in path[1:]: - grid[y][x] = 'x' - print('\n'.join(''.join(row) for row in grid)) + grid[y][x] = "x" + print("\n".join("".join(row) for row in grid)) # .......... # ..*#...##. # ..##x@.#*. diff --git a/Base64_examples/decode_to_file.py b/Base64_examples/decode_to_file.py index cfbdff5ce..a2ce75c7e 100644 --- a/Base64_examples/decode_to_file.py +++ b/Base64_examples/decode_to_file.py @@ -1,20 +1,20 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from base64 import b64decode -def decode_base64_to_file(file_name: str, text_base64: str): - with open(file_name, 'wb') as f: +def decode_base64_to_file(file_name: str, text_base64: str) -> None: + with open(file_name, "wb") as f: data = b64decode(text_base64) f.write(data) -if __name__ == '__main__': +if __name__ == "__main__": # Hello World! - text = 'SGVsbG8gV29ybGQh' + text = "SGVsbG8gV29ybGQh" - decode_base64_to_file('result.txt', text) + decode_base64_to_file("result.txt", text) diff --git a/Base64_examples/decode_to_file_certificate__and_open.py b/Base64_examples/decode_to_file_certificate__and_open.py index 58ab98547..b2375126c 100644 --- a/Base64_examples/decode_to_file_certificate__and_open.py +++ b/Base64_examples/decode_to_file_certificate__and_open.py @@ -1,10 +1,10 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -FILE_NAME = 'cert.cer' +FILE_NAME = "cert.cer" TEXT = """ MIIBeTCCASigAwIBAgIEBoLBizAIBgYqhQMCAgMwMTELMAkGA1UEBhMCUlUxEjAQBgNVBAoMCUNy eXB0b1BybzEOMAwGA1UEAwwFQWxpYXMwHhcNMTcwNjA3MDk1MzM5WhcNMTgwNjA3MDk1MzM5WjAx @@ -16,8 +16,10 @@ """ from decode_to_file import decode_base64_to_file + decode_base64_to_file(FILE_NAME, TEXT) # Open file import os + os.startfile(FILE_NAME) diff --git a/Base64_examples/example_encode_and_decode.py b/Base64_examples/example_encode_and_decode.py index cae57b246..ba4960a1c 100644 --- a/Base64_examples/example_encode_and_decode.py +++ b/Base64_examples/example_encode_and_decode.py @@ -1,13 +1,13 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import base64 text = "Hello py! Привет py!" -print("Text: {}".format(text)) +print(f"Text: {text}") b16 = base64.b16encode(text.encode()) b32 = base64.b32encode(text.encode()) diff --git a/Base64_examples/gui_base64.py b/Base64_examples/gui_base64.py index 946d5aeb6..ba49bbf97 100644 --- a/Base64_examples/gui_base64.py +++ b/Base64_examples/gui_base64.py @@ -1,13 +1,15 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import base64 import traceback import sys +from os.path import split as path_split + try: from PyQt5.QtWidgets import * from PyQt5.QtCore import * @@ -22,12 +24,12 @@ from PySide.QtCore import * -def log_uncaught_exceptions(ex_cls, ex, tb): - text = '{}: {}:\n'.format(ex_cls.__name__, ex) - text += ''.join(traceback.format_tb(tb)) +def log_uncaught_exceptions(ex_cls, ex, tb) -> None: + text = f"{ex_cls.__name__}: {ex}:\n" + text += "".join(traceback.format_tb(tb)) print(text) - QMessageBox.critical(None, 'Error', text) + QMessageBox.critical(None, "Error", text) sys.exit(1) @@ -129,15 +131,14 @@ def log_uncaught_exceptions(ex_cls, ex, tb): "utf_16_le", "utf_7", "utf_8", - "utf_8_sig" + "utf_8_sig", ] class MainWindow(QWidget): - def __init__(self): + def __init__(self) -> None: super().__init__() - from os.path import split as path_split self.setWindowTitle(path_split(__file__)[1]) self.button_direct = QPushButton() @@ -147,10 +148,10 @@ def __init__(self): self.cb_encoding.addItems(STANDART_ENCODINGS) self.cb_encoding.setFixedWidth(100) - index = self.cb_encoding.findText('utf_8') + index = self.cb_encoding.findText("utf_8") self.cb_encoding.setCurrentIndex(index) - self.cb_raw = QCheckBox('raw') + self.cb_raw = QCheckBox("raw") button_layout = QHBoxLayout() button_layout.addWidget(self.button_direct) @@ -170,9 +171,9 @@ def __init__(self): self.label_error.setTextInteractionFlags(Qt.TextSelectableByMouse) self.label_error.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) - self.button_detail_error = QPushButton('...') + self.button_detail_error = QPushButton("...") self.button_detail_error.setFixedSize(20, 20) - self.button_detail_error.setToolTip('Detail error') + self.button_detail_error.setToolTip("Detail error") self.button_detail_error.clicked.connect(self.show_detail_error_massage) self.last_error_message = None @@ -204,11 +205,11 @@ def __init__(self): self.setLayout(layout) - def show_detail_error_massage(self): - message = self.last_error_message + '\n\n' + self.last_detail_error_message + def show_detail_error_massage(self) -> None: + message = self.last_error_message + "\n\n" + self.last_detail_error_message mb = QErrorMessage() - mb.setWindowTitle('Error') + mb.setWindowTitle("Error") # Сообщение ошибки содержит отступы, символы-переходы на следующую строку, # которые поломаются при вставке через QErrorMessage.showMessage, и нет возможности # выбрать тип текста, то делаем такой хак. @@ -216,7 +217,7 @@ def show_detail_error_massage(self): mb.exec_() - def input_text_changed(self): + def input_text_changed(self) -> None: self.label_error.clear() self.button_detail_error.hide() @@ -238,7 +239,7 @@ def input_text_changed(self): else: # Параметр errors='replace' нужен для того, чтобы при декодировании в строку проблемные символы # заменялись символами-заменителями (�) - text = text.decode(encoding=codec_name, errors='replace') + text = text.decode(encoding=codec_name, errors="replace") self.text_edit_output.setPlainText(text) @@ -253,16 +254,18 @@ def input_text_changed(self): self.last_detail_error_message = str(tb) self.button_detail_error.show() - self.label_error.setText('Error: ' + self.last_error_message) + self.label_error.setText("Error: " + self.last_error_message) - def change_convert_direct(self): + def change_convert_direct(self) -> None: self.direct_encode_text = not self.direct_encode_text - self.button_direct.setText('text -> base64' if self.direct_encode_text else 'base64 -> text') + self.button_direct.setText( + "text -> base64" if self.direct_encode_text else "base64 -> text" + ) self.input_text_changed() -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) mw = MainWindow() diff --git a/Builder__example.py b/Builder__example.py deleted file mode 100644 index 85b352bd9..000000000 --- a/Builder__example.py +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -__author__ = 'ipetrash' - - -class Foo: - def __init__(self): - self.items = [] - self.key_by_value = dict() - - def add_item(self, value): - self.items.append(value) - return self - - def add_items(self, values): - self.items += values - return self - - def set_value(self, key, value): - self.key_by_value[key] = value - return self - - def get_value(self, key): - return self.key_by_value[key] - - def __repr__(self): - return 'Foo'.format(len(self.items), len(self.key_by_value)) - - -foo = Foo()\ - .add_item(1)\ - .add_items('abc')\ - .set_value('x', 1)\ - .set_value('x[]', [1, 2, 3]) - -print(foo) -print(foo.items) -print(foo.key_by_value) -print(foo.get_value('x')) -print(foo.get_value('x[]')) diff --git a/CONTACT__examples/config.py b/CONTACT__examples/config.py index 6b2320374..d7d312554 100644 --- a/CONTACT__examples/config.py +++ b/CONTACT__examples/config.py @@ -1,13 +1,13 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -URL_NG_SERVER = '' +URL_NG_SERVER = "" # Идентификатор программного обеспечения интегратора -INT_SOFT_ID = '' +INT_SOFT_ID = "" # Код точки обслуживания -POINT_CODE = '' +POINT_CODE = "" diff --git a/CONTACT__examples/connect_to_ng_server_via_socket_.py b/CONTACT__examples/connect_to_ng_server_via_socket_.py index b7392a73f..eb4407d9d 100644 --- a/CONTACT__examples/connect_to_ng_server_via_socket_.py +++ b/CONTACT__examples/connect_to_ng_server_via_socket_.py @@ -1,20 +1,22 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" +import socket from config import * + # URL_NG_SERVER = 'http://10.7.8.31:12000' -HOST = URL_NG_SERVER.replace('https://', '').replace('http://', '') +HOST = URL_NG_SERVER.replace("https://", "").replace("http://", "") # HOST = 10.7.8.31, PORT = 12000 -HOST, PORT = HOST.split(':') +HOST, PORT = HOST.split(":") PORT = int(PORT) -post_data = """ +post_data = f""" -""".format(INT_SOFT_ID=INT_SOFT_ID, POINT_CODE=POINT_CODE) +""" http_request = ( - 'POST / HTTP/1.1\r\n', - 'Host: {host}:{port}\r\n', - 'Accept-Encoding: gzip, deflate\r\n', - 'User-Agent: {user_agent}\r\n', - 'Connection: close\r\n', - 'Accept: */*\r\n', - 'Content-Length: {content_length}\r\n', - '\r\n', - '{body}' + "POST / HTTP/1.1\r\n", + "Host: {host}:{port}\r\n", + "Accept-Encoding: gzip, deflate\r\n", + "User-Agent: {user_agent}\r\n", + "Connection: close\r\n", + "Accept: */*\r\n", + "Content-Length: {content_length}\r\n", + "\r\n", + "{body}", ) -http_request = ''.join(http_request) +http_request = "".join(http_request) http_request = http_request.format( host=HOST, port=PORT, - user_agent='iHuman', + user_agent="iHuman", content_length=len(post_data), body=post_data, ) @@ -48,13 +50,12 @@ print(repr(http_request)) -import socket sock = socket.socket() sock.connect((HOST, PORT)) sock.send(http_request.encode()) -print('Socket name: {}'.format(sock.getsockname())) -print('\nResponse:') +print(f"Socket name: {sock.getsockname()}") +print("\nResponse:") while True: data = sock.recv(1024) diff --git a/CONTACT__examples/create_and_fill_database_from_dictionary.py b/CONTACT__examples/create_and_fill_database_from_dictionary.py index 2936bc3c3..4537d3050 100644 --- a/CONTACT__examples/create_and_fill_database_from_dictionary.py +++ b/CONTACT__examples/create_and_fill_database_from_dictionary.py @@ -1,7 +1,14 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" + + +import glob +import sqlite3 +import os + +from bs4 import BeautifulSoup def build_sql_table(table_name: str, format_fields_of_table: [dict]) -> str: @@ -51,25 +58,29 @@ def build_sql_table(table_name: str, format_fields_of_table: [dict]) -> str: """ - def build_field_table(format_field: dict, indent=' ' * 4) -> str: - field_name = format_field['attrname'] + def build_field_table(format_field: dict, indent=" " * 4) -> str: + field_name = format_field["attrname"] - field_type = 'TEXT' - if format_field['fieldtype'] == 'i4': - field_type = 'INTEGER' + field_type = "TEXT" + if format_field["fieldtype"] == "i4": + field_type = "INTEGER" - if field_name.upper() == 'ID': - field_type += ' PRIMARY KEY' + if field_name.upper() == "ID": + field_type += " PRIMARY KEY" - return indent + '{} {}'.format(field_name, field_type) + return f"{indent}{field_name} {field_type}" - fields_list = [build_field_table(format_field) for format_field in format_fields_of_table] + fields_list = [ + build_field_table(format_field) for format_field in format_fields_of_table + ] table = """\ CREATE TABLE IF NOT EXISTS {table_name} ( {fields_list} ); -""".format(table_name=table_name.upper(), fields_list=',\n'.join(fields_list)) +""".format( + table_name=table_name.upper(), fields_list=",\n".join(fields_list) + ) return table @@ -89,26 +100,32 @@ def build_insert(row_of_table: dict) -> str: keys = sorted(key for key in row_of_table.keys()) # return "INSERT OR REPLACE INTO {table_name} ({fields}) VALUES ({values});".format( - return "INSERT OR IGNORE INTO {table_name} ({fields}) VALUES ({values});".format( - table_name=table_name.upper(), - fields=','.join(key.upper() for key in keys), - values=','.join(repr(row_of_table[key]).replace('\\"', '').replace("\\'", '') for key in keys), + return ( + "INSERT OR IGNORE INTO {table_name} ({fields}) VALUES ({values});".format( + table_name=table_name.upper(), + fields=",".join(key.upper() for key in keys), + values=",".join( + repr(row_of_table[key]).replace('\\"', "").replace("\\'", "") + for key in keys + ), + ) ) - return '\n'.join(build_insert(row_data.attrs) for row_data in rows_of_table) + return "\n".join(build_insert(row_data.attrs) for row_data in rows_of_table) def create_connect(): - import sqlite3 - return sqlite3.connect('database.sqlite') + return sqlite3.connect("database.sqlite") -def create_table(table_name: str, sql_table: str, sql_table_data_rows: str, drop_table=False): +def create_table( + table_name: str, sql_table: str, sql_table_data_rows: str, drop_table=False +) -> None: # Создание таблицы connect = create_connect() try: if drop_table: - connect.execute('DROP TABLE IF EXISTS ?;', (table_name,)) + connect.execute("DROP TABLE IF EXISTS ?;", (table_name,)) connect.execute(sql_table) connect.executescript(sql_table_data_rows) @@ -119,25 +136,24 @@ def create_table(table_name: str, sql_table: str, sql_table_data_rows: str, drop connect.close() -if __name__ == '__main__': - import glob - for file_name in glob.glob('contact_dicts/*.xml'): - import os +if __name__ == "__main__": + for file_name in glob.glob("contact_dicts/*.xml"): table_name = os.path.splitext(os.path.basename(file_name))[0] - print('Append {} from {}'.format(table_name, file_name)) + print(f"Append {table_name} from {file_name}") - from bs4 import BeautifulSoup - root = BeautifulSoup(open(file_name, 'rb'), 'lxml') + root = BeautifulSoup(open(file_name, "rb"), "lxml") - format_fields_of_dict = [row for row in root.select('metadata > fields > field')] - rows_of_dict = [row for row in root.select('rowdata > row')] + format_fields_of_dict = [ + row for row in root.select("metadata > fields > field") + ] + rows_of_dict = [row for row in root.select("rowdata > row")] sql_table = build_sql_table(table_name, format_fields_of_dict) sql_table_data_rows = build_sql_rows_data_table(table_name, rows_of_dict) # print(sql_table + "\n\n" + sql_table_data_rows) - print(' Append {} rows\n'.format(len(rows_of_dict))) + print(f" Append {len(rows_of_dict)} rows\n") create_table(table_name, sql_table, sql_table_data_rows) # print() diff --git a/CONTACT__examples/create_full_dict__GET_CHANGES_from_exported_dicts.py b/CONTACT__examples/create_full_dict__GET_CHANGES_from_exported_dicts.py index d70931aa5..97cab3430 100644 --- a/CONTACT__examples/create_full_dict__GET_CHANGES_from_exported_dicts.py +++ b/CONTACT__examples/create_full_dict__GET_CHANGES_from_exported_dicts.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """ @@ -10,6 +10,11 @@ """ +import base64 +import glob +import os +import zlib + def bytes_to_compress_to_base64(bytes_text: bytes) -> str: """ @@ -17,17 +22,14 @@ def bytes_to_compress_to_base64(bytes_text: bytes) -> str: """ - import zlib compress_bytes_xml = zlib.compress(bytes_text) - - import base64 base64_compress = base64.b64encode(compress_bytes_xml) - return base64_compress.decode('utf-8') + return base64_compress.decode("utf-8") -if __name__ == '__main__': - DIR_DICT_CONTACT = 'mini_full_dict__CONTACT' - FILE_NAME_FULL_DICT = 'new_mini_full_dict__CONTACT.xml' +if __name__ == "__main__": + DIR_DICT_CONTACT = "mini_full_dict__CONTACT" + FILE_NAME_FULL_DICT = "new_mini_full_dict__CONTACT.xml" HTML_PATTERN_RESPONSE__GET_CHANGES = """\ str: child_list = list() - import glob - for file_name in glob.glob(DIR_DICT_CONTACT + '/*.xml'): + for file_name in glob.glob(DIR_DICT_CONTACT + "/*.xml"): print(file_name) - import os dict_name = os.path.splitext(os.path.basename(file_name))[0].upper() - bytes_xml = open(file_name, 'rb').read() + bytes_xml = open(file_name, "rb").read() base64_str = bytes_to_compress_to_base64(bytes_xml) - child_list.append("<{0}>{1}".format(dict_name, base64_str)) + child_list.append(f"<{dict_name}>{base64_str}") - with open(FILE_NAME_FULL_DICT, 'w', encoding='utf-8') as f: - f.write(HTML_PATTERN_RESPONSE__GET_CHANGES.format(''.join(child_list))) + with open(FILE_NAME_FULL_DICT, "w", encoding="utf-8") as f: + f.write(HTML_PATTERN_RESPONSE__GET_CHANGES.format("".join(child_list))) print() - print('Write to {}'.format(FILE_NAME_FULL_DICT)) + print(f"Write to {FILE_NAME_FULL_DICT}") diff --git a/CONTACT__examples/export_dicts_from__full_dict__GET_CHANGES.py b/CONTACT__examples/export_dicts_from__full_dict__GET_CHANGES.py index a49cf7acb..7167ee2f7 100644 --- a/CONTACT__examples/export_dicts_from__full_dict__GET_CHANGES.py +++ b/CONTACT__examples/export_dicts_from__full_dict__GET_CHANGES.py @@ -1,12 +1,13 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import base64 import zlib import sys +import os from typing import Union @@ -20,7 +21,9 @@ def get_bytes_from_base64_zlib(text_or_tag_or_bytes: Union[str, bytes, Tag]) -> """ - if not isinstance(text_or_tag_or_bytes, str) and not isinstance(text_or_tag_or_bytes, bytes): + if not isinstance(text_or_tag_or_bytes, str) and not isinstance( + text_or_tag_or_bytes, bytes + ): text = text_or_tag_or_bytes.text else: text = text_or_tag_or_bytes @@ -29,24 +32,23 @@ def get_bytes_from_base64_zlib(text_or_tag_or_bytes: Union[str, bytes, Tag]) -> return zlib.decompress(compress_data) -if __name__ == '__main__': - file_name_full_dict = 'mini_full_dict__CONTACT.xml' - export_dir = 'mini_full_dict__CONTACT' +if __name__ == "__main__": + file_name_full_dict = "mini_full_dict__CONTACT.xml" + export_dir = "mini_full_dict__CONTACT" - import os os.makedirs(export_dir, exist_ok=True) # Parsing - root = BeautifulSoup(open(file_name_full_dict, 'rb'), 'lxml') - response = root.select_one('response') + root = BeautifulSoup(open(file_name_full_dict, "rb"), "lxml") + response = root.select_one("response") # Если ошибка - if response['re'] != '0': - print('Error text: "{}"'.format(response['err_text'])) + if response["re"] != "0": + print(f'Error text: "{response["err_text"]}"') sys.exit() - print('Справочник полный?:', response['full'] == '1') - print('Версия справочника:', response['version']) + print("Справочник полный?:", response["full"] == "1") + print("Версия справочника:", response["version"]) print() for child in response.children: @@ -54,9 +56,9 @@ def get_bytes_from_base64_zlib(text_or_tag_or_bytes: Union[str, bytes, Tag]) -> if child.name is None: continue - file_name = os.path.join(export_dir, child.name) + '.xml' + file_name = os.path.join(export_dir, child.name) + ".xml" print(file_name) - with open(file_name, 'w', encoding='cp1251') as f: + with open(file_name, "w", encoding="cp1251") as f: data = get_bytes_from_base64_zlib(child) - f.write(data.decode(encoding='cp1251')) + f.write(data.decode(encoding="cp1251")) diff --git a/CONTACT__examples/export_logo_from_dict.py b/CONTACT__examples/export_logo_from_dict.py index e1b4112fc..99830a0ff 100644 --- a/CONTACT__examples/export_logo_from_dict.py +++ b/CONTACT__examples/export_logo_from_dict.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """ @@ -9,26 +9,27 @@ """ +import base64 +import os -if __name__ == '__main__': - FILE_NAME_DICT_LOGO = 'mini_full_dict__CONTACT/logo.xml' - DIR_LOGO_IMAGES = 'logo_images' +from bs4 import BeautifulSoup - import os - os.makedirs(DIR_LOGO_IMAGES, exist_ok=True) - from bs4 import BeautifulSoup - root = BeautifulSoup(open(FILE_NAME_DICT_LOGO, 'rb'), 'lxml') +FILE_NAME_DICT_LOGO = "mini_full_dict__CONTACT/logo.xml" - for row in root.select('rowdata > row'): - logo_name = row['logo_name'] - print(logo_name) +DIR_LOGO_IMAGES = "logo_images" +os.makedirs(DIR_LOGO_IMAGES, exist_ok=True) - logo_data = row['logo_data'] - import base64 - img_data = base64.b64decode(logo_data) +root = BeautifulSoup(open(FILE_NAME_DICT_LOGO, "rb"), "lxml") - file_name = os.path.join(DIR_LOGO_IMAGES, logo_name) +for row in root.select("rowdata > row"): + logo_name = row["logo_name"] + print(logo_name) - with open(file_name, 'wb') as f: - f.write(img_data) + logo_data = row["logo_data"] + img_data = base64.b64decode(logo_data) + + file_name = os.path.join(DIR_LOGO_IMAGES, logo_name) + + with open(file_name, "wb") as f: + f.write(img_data) diff --git a/CONTACT__examples/get_full_dictionary__method_GET_CHANGES.py b/CONTACT__examples/get_full_dictionary__method_GET_CHANGES.py index 66ebc276b..eb41f8024 100644 --- a/CONTACT__examples/get_full_dictionary__method_GET_CHANGES.py +++ b/CONTACT__examples/get_full_dictionary__method_GET_CHANGES.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """ @@ -12,12 +12,14 @@ from config import * +import requests -FILE_NAME_FULL_DICT = 'full_dict__CONTACT.xml' +FILE_NAME_FULL_DICT = "full_dict__CONTACT.xml" -if __name__ == '__main__': - post_data = """ + +if __name__ == "__main__": + post_data = f""" - """.format(INT_SOFT_ID=INT_SOFT_ID, POINT_CODE=POINT_CODE) + """ - import requests rs = requests.post(URL_NG_SERVER, data=post_data) print(rs) print(len(rs.text)) - with open(FILE_NAME_FULL_DICT, 'wb') as f: + with open(FILE_NAME_FULL_DICT, "wb") as f: f.write(rs.content) - print('Write to: {}'.format(FILE_NAME_FULL_DICT)) + print(f"Write to: {FILE_NAME_FULL_DICT}") diff --git a/CONTACT__examples/reducing number row in rows dicts.py b/CONTACT__examples/reducing number row in rows dicts.py index 29234d602..8b83ff852 100644 --- a/CONTACT__examples/reducing number row in rows dicts.py +++ b/CONTACT__examples/reducing number row in rows dicts.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """ @@ -12,30 +12,31 @@ """ -if __name__ == '__main__': - # NOTE: справочники CONTACT хранятся в кодировке cp1251 (windows-1251) +import glob +import os - import glob - for file_name in glob.glob('mini_contact_dicts/*.xml'): - print(file_name) +from bs4 import BeautifulSoup - from bs4 import BeautifulSoup - root = BeautifulSoup(open(file_name, 'rb'), 'lxml', from_encoding='cp1251') - rows = root.select('row') - old_len = len(rows) +# NOTE: справочники CONTACT хранятся в кодировке cp1251 (windows-1251) - for row in rows[20:]: - row.decompose() +for file_name in glob.glob("mini_contact_dicts/*.xml"): + print(file_name) - rows = root.select('row') - print(' len {} -> {}'.format(old_len, len(rows))) + root = BeautifulSoup(open(file_name, "rb"), "lxml", from_encoding="cp1251") - import os - file_name = 'mini_contact_dicts/' + os.path.basename(file_name) + rows = root.select("row") + old_len = len(rows) - with open(file_name, 'w', encoding='cp1251') as f: - f.write(str(root)) + for row in rows[20:]: + row.decompose() - print(' Write to {}'.format(file_name)) - print() + rows = root.select("row") + print(f" len {old_len} -> {len(rows)}") + + file_name = "mini_contact_dicts/" + os.path.basename(file_name) + with open(file_name, "w", encoding="cp1251") as f: + f.write(str(root)) + + print(f" Write to {file_name}") + print() diff --git a/Callable modules/main.py b/Callable modules/main.py index 4f5582299..107b5729d 100644 --- a/Callable modules/main.py +++ b/Callable modules/main.py @@ -1,9 +1,11 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import print_this -print_this('1232') + + +print_this("1232") print_this(32) diff --git a/Callable modules/print_this.py b/Callable modules/print_this.py index 239db0df5..6f8f95e67 100644 --- a/Callable modules/print_this.py +++ b/Callable modules/print_this.py @@ -1,14 +1,16 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" + + +import sys # SOURCE: https://stackoverflow.com/a/1060872/5909792 class mod_call(object): - def __call__(self, text): + def __call__(self, text) -> None: print(text) -import sys sys.modules[__name__] = mod_call() diff --git a/CamelCase_to_snake_case.py b/CamelCase_to_snake_case.py index cdae0607c..2ac5445ea 100644 --- a/CamelCase_to_snake_case.py +++ b/CamelCase_to_snake_case.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import re @@ -9,15 +9,15 @@ # SOURCE: https://stackoverflow.com/a/1176023/5909792 def convert(text: str) -> str: - s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', text) - return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower() + s1 = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", text) + return re.sub("([a-z0-9])([A-Z])", r"\1_\2", s1).lower() -if __name__ == '__main__': - assert convert('CamelCase') == 'camel_case' - assert convert('CamelCamelCase') == 'camel_camel_case' - assert convert('Camel2Camel2Case') == 'camel2_camel2_case' - assert convert('getHTTPResponseCode') == 'get_http_response_code' - assert convert('get2HTTPResponseCode') == 'get2_http_response_code' - assert convert('HTTPResponseCode') == 'http_response_code' - assert convert('HTTPResponseCodeXYZ') == 'http_response_code_xyz' +if __name__ == "__main__": + assert convert("CamelCase") == "camel_case" + assert convert("CamelCamelCase") == "camel_camel_case" + assert convert("Camel2Camel2Case") == "camel2_camel2_case" + assert convert("getHTTPResponseCode") == "get_http_response_code" + assert convert("get2HTTPResponseCode") == "get2_http_response_code" + assert convert("HTTPResponseCode") == "http_response_code" + assert convert("HTTPResponseCodeXYZ") == "http_response_code_xyz" diff --git a/CreateNoteXmlNoteManagers/CreateNoteXmlNoteManagers.py b/CreateNoteXmlNoteManagers/CreateNoteXmlNoteManagers.py index f2931b079..cb60a4485 100644 --- a/CreateNoteXmlNoteManagers/CreateNoteXmlNoteManagers.py +++ b/CreateNoteXmlNoteManagers/CreateNoteXmlNoteManagers.py @@ -1,37 +1,48 @@ # coding=utf-8 -__author__ = 'ipetrash' +__author__ = "ipetrash" + import argparse import sys import os -def main(namespace): +def main(namespace) -> None: # @param namespace argparse.Namespace Содержит переданные в аргументах объекты. - indent = ' ' * 8 + indent = " " * 8 - dirs = filter(lambda x: os.path.isdir(os.path.join(namespace.dir, x)), os.listdir(namespace.dir)) + dirs = filter( + lambda x: os.path.isdir(os.path.join(namespace.dir, x)), + os.listdir(namespace.dir), + ) for dir in dirs: - print(indent + """""" % dir) - - -def create_parser(): - parser = argparse.ArgumentParser(prog="CreateNoteXml", - description=u"Cкрипт генерирует xml-файл программы NotesManager.", - epilog=u"(с) Petrash Ilya 2014. Автор: Илья Петраш.") - parser.add_argument('-dir', - help=u'Путь к директории notes.', - type=str) + print( + indent + + """""" % dir + ) + + +def create_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser( + prog="CreateNoteXml", + description="Cкрипт генерирует xml-файл программы NotesManager.", + epilog="(с) Petrash Ilya 2014. Автор: Илья Петраш.", + ) + parser.add_argument("-dir", help="Путь к директории notes.", type=str) return parser -if __name__ == '__main__': +if __name__ == "__main__": parser = create_parser() - sys.argv = [sys.argv[0], r'-dir=C:\Users\ipetrash\Desktop\NotesManager.v0.0.3.Windows\notes', ] + # TODO: Remove + # sys.argv = [ + # sys.argv[0], + # r"-dir=C:\Users\ipetrash\Desktop\NotesManager.v0.0.3.Windows\notes", + # ] if len(sys.argv) == 1: parser.print_help() else: namespace = parser.parse_args() - main(namespace) \ No newline at end of file + main(namespace) diff --git a/Crypto Git Repository/api.py b/Crypto Git Repository/api.py index 404062aaf..bbd03226b 100644 --- a/Crypto Git Repository/api.py +++ b/Crypto Git Repository/api.py @@ -1,17 +1,19 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" + + +# pip install GitPython +import git + +from config import REPO_PATH, URL_GIT repo = None def __get_repo(): - # pip install GitPython - import git - from config import REPO_PATH, URL_GIT - try: return git.Repo(REPO_PATH) @@ -31,9 +33,9 @@ def get_repo(): repo = get_repo() -def print_log(reverse=False): - logs = repo.git.log('--pretty=format:%H%x09%an%x09%ad%x09%s').splitlines() - print('Logs[{}]:'.format(len(logs))) +def print_log(reverse=False) -> None: + logs = repo.git.log("--pretty=format:%H%x09%an%x09%ad%x09%s").splitlines() + print(f"Logs[{len(logs)}]:") if reverse: logs.reverse() @@ -42,28 +44,27 @@ def print_log(reverse=False): print(log) -def append(file_or_list): +def append(file_or_list) -> None: if type(file_or_list) == str: file_or_list = [file_or_list] repo.index.add(file_or_list) -def remove(file_or_list): +def remove(file_or_list) -> None: if type(file_or_list) == str: file_or_list = [file_or_list] repo.index.remove(file_or_list) -def commit(message): +def commit(message) -> None: repo.index.commit(message) -def pull(): +def pull() -> None: repo.remotes.origin.pull() -def push(): +def push() -> None: repo.remotes.origin.push() - diff --git a/Crypto Git Repository/append_to_remote_repo.py b/Crypto Git Repository/append_to_remote_repo.py index afff168a0..6cfcc0d41 100644 --- a/Crypto Git Repository/append_to_remote_repo.py +++ b/Crypto Git Repository/append_to_remote_repo.py @@ -1,27 +1,32 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" + + +import random +import string +import os +import uuid def create_random_file(repo): - import uuid file_name = str(uuid.uuid4()) - - import os full_file_name = os.path.join(repo.working_tree_dir, file_name) - with open(full_file_name, 'w') as f: - import random - import string - text = "".join(random.choice(string.ascii_letters + string.digits) for _ in range(64)) + with open(full_file_name, "w") as f: + text = "".join( + random.choice(string.ascii_letters + string.digits) + for _ in range(64) + ) f.write(text) return file_name, file_name -if __name__ == '__main__': +if __name__ == "__main__": import api + repo = api.repo print(repo) print() @@ -30,7 +35,7 @@ def create_random_file(repo): print() file_name = create_random_file(repo)[1] - message = 'Create: ' + file_name + message = "Create: " + file_name print(message) api.append(file_name) diff --git a/Crypto Git Repository/config.py b/Crypto Git Repository/config.py index a48ee6d4f..392ec7dac 100644 --- a/Crypto Git Repository/config.py +++ b/Crypto Git Repository/config.py @@ -1,7 +1,10 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" + + +import os LOGIN = None @@ -10,16 +13,13 @@ # http://user:password@proxy_host:proxy_port PROXY = None -NEW_REPO = 'Test-Repo' - -import os +NEW_REPO = "Test-Repo" REPO_PATH = os.path.abspath(NEW_REPO) # How use without input login and password: # git clone https://username:password@github.com/username/repository.git -URL_GIT = 'https://{0}:{1}@github.com/{0}/{2}.git'.format(LOGIN, PASSWORD, NEW_REPO) +URL_GIT = f"https://{LOGIN}:{PASSWORD}@github.com/{LOGIN}/{NEW_REPO}.git" if PROXY: - import os - os.environ['http_proxy'] = PROXY + os.environ["http_proxy"] = PROXY diff --git a/Crypto Git Repository/create_local_repo.py b/Crypto Git Repository/create_local_repo.py index c280d66f4..d936ae89c 100644 --- a/Crypto Git Repository/create_local_repo.py +++ b/Crypto Git Repository/create_local_repo.py @@ -1,14 +1,15 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -if __name__ == '__main__': - import api - repo = api.repo +import api - print('Repo:', repo) - print() - api.print_log() +repo = api.repo + +print("Repo:", repo) +print() + +api.print_log() diff --git a/Crypto Git Repository/delete_random_file__from__remote_repo.py b/Crypto Git Repository/delete_random_file__from__remote_repo.py index 1f5d731f9..a9edbd6f4 100644 --- a/Crypto Git Repository/delete_random_file__from__remote_repo.py +++ b/Crypto Git Repository/delete_random_file__from__remote_repo.py @@ -1,31 +1,33 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" + + +import os +import random def get_random_file(repo): - file_names = list() + file_names = [] - import os for root, dirs, files in os.walk(repo.working_tree_dir): - if '.git' in root: + if ".git" in root: continue for file in files: file_names.append(os.path.join(root, file)) if file_names: - import random return random.choice(file_names) - else: - return None + return # NOTE: first execute append_to_remote_repo.py -if __name__ == '__main__': +if __name__ == "__main__": import api + repo = api.repo print(repo) print() @@ -35,9 +37,8 @@ def get_random_file(repo): abs_file_name = get_random_file(repo) if abs_file_name: - import os file_name = os.path.basename(abs_file_name) - message = 'Remove: ' + file_name + message = "Remove: " + file_name print(message) api.remove(file_name) diff --git a/Crypto Git Repository/print_repo_log.py b/Crypto Git Repository/print_repo_log.py index db556a910..ca967ec24 100644 --- a/Crypto Git Repository/print_repo_log.py +++ b/Crypto Git Repository/print_repo_log.py @@ -1,9 +1,10 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -if __name__ == '__main__': - import api - api.print_log(reverse=True) +import api + + +api.print_log(reverse=True) diff --git a/Crypto Git Repository/update_from_remote_repository__pull.py b/Crypto Git Repository/update_from_remote_repository__pull.py index cf97a3538..fb33fb143 100644 --- a/Crypto Git Repository/update_from_remote_repository__pull.py +++ b/Crypto Git Repository/update_from_remote_repository__pull.py @@ -1,9 +1,10 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -if __name__ == '__main__': - import api - api.pull() +import api + + +api.pull() diff --git a/DNS price tracking/common.py b/DNS price tracking/common.py index d42424e29..0bbf9589c 100644 --- a/DNS price tracking/common.py +++ b/DNS price tracking/common.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import json @@ -9,4 +9,4 @@ def get_tracked_products() -> List[dict]: - return json.load(open('tracked_products.json', encoding='utf-8')) + return json.load(open("tracked_products.json", encoding="utf-8")) diff --git a/DNS price tracking/db.py b/DNS price tracking/db.py index c322c9177..91a0635fe 100644 --- a/DNS price tracking/db.py +++ b/DNS price tracking/db.py @@ -1,33 +1,32 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -import datetime as DT +import datetime as dt +import pathlib +import os +import shutil from peewee import * -# Absolute file name -import pathlib -DB_FILE_NAME = str(pathlib.Path(__file__).resolve().parent / 'tracked_products.sqlite') +# Absolute file name +DB_FILE_NAME = str(pathlib.Path(__file__).resolve().parent / "tracked_products.sqlite") -def db_create_backup(backup_dir='backup'): - import datetime as DT - import os - import shutil +def db_create_backup(backup_dir="backup") -> None: os.makedirs(backup_dir, exist_ok=True) - file_name = str(DT.datetime.today().date()) + '.sqlite' + file_name = str(dt.datetime.today().date()) + ".sqlite" file_name = os.path.join(backup_dir, file_name) shutil.copy(DB_FILE_NAME, file_name) # Ensure foreign-key constraints are enforced. -db = SqliteDatabase(DB_FILE_NAME, pragmas={'foreign_keys': 1}) +db = SqliteDatabase(DB_FILE_NAME, pragmas={"foreign_keys": 1}) class BaseModel(Model): @@ -40,7 +39,7 @@ class Product(BaseModel): url = TextField(unique=True) def get_technopoint_url(self) -> str: - return self.url.replace('www.dns-shop.ru', 'technopoint.ru') + return self.url.replace("www.dns-shop.ru", "technopoint.ru") def get_last_price_dns(self, actual_price=True): prices = self.prices @@ -68,43 +67,47 @@ def get_last_price_technopoint(self, actual_price=True): return last_price.value_technopoint - def append_price(self, value_dns, value_technopoint): - Price.create(product=self, value_dns=value_dns, value_technopoint=value_technopoint) + def append_price(self, value_dns, value_technopoint) -> None: + Price.create( + product=self, value_dns=value_dns, value_technopoint=value_technopoint + ) - def __str__(self): + def __str__(self) -> str: # TODO: выводить последнюю цену и актуальную - return f'Product(title={self.title!r}, ' \ - f'last_price_dns={self.get_last_price_dns()}, ' \ - f'last_price_technopoint={self.get_last_price_technopoint()}, ' \ - f'url={self.url!r})' + return ( + f"Product(title={self.title!r}, " + f"last_price_dns={self.get_last_price_dns()}, " + f"last_price_technopoint={self.get_last_price_technopoint()}, " + f"url={self.url!r})" + ) class Price(BaseModel): value_dns = DecimalField(null=True) value_technopoint = DecimalField(null=True) - date = DateTimeField(default=DT.datetime.now) - product = ForeignKeyField(Product, backref='prices') + date = DateTimeField(default=dt.datetime.now) + product = ForeignKeyField(Product, backref="prices") class Meta: - indexes = ( - (("product_id", "date", "value_dns", "value_technopoint"), True), - ) + indexes = ((("product_id", "date", "value_dns", "value_technopoint"), True),) - def __str__(self): - return f'Price(value_dns={self.value_dns}, ' \ - f'value_technopoint={self.value_technopoint}, ' \ - f'date={self.date}, product_id={self.product.id})' + def __str__(self) -> str: + return ( + f"Price(value_dns={self.value_dns}, " + f"value_technopoint={self.value_technopoint}, " + f"date={self.date}, product_id={self.product.id})" + ) db.connect() db.create_tables([Product, Price]) -if __name__ == '__main__': +if __name__ == "__main__": for product in Product.select(): print(product) for p in product.prices.select(): - print(' ', p) + print(" ", p) print() diff --git a/DNS price tracking/main.py b/DNS price tracking/main.py index 57ca6e296..c2eae3c79 100644 --- a/DNS price tracking/main.py +++ b/DNS price tracking/main.py @@ -1,19 +1,19 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -import datetime as DT import time import traceback - -# Import https://github.com/gil9red/SimplePyScripts/blob/8fa9b9c23d10b5ee7ff0161da997b463f7a861bf/wait/wait.py import sys -sys.path.append('../wait') -sys.path.append('../html_parsing/www_dns_shop_ru') -from wait import wait +from datetime import datetime + +# pip install simple-wait +from simple_wait import wait + +sys.path.append("../html_parsing/www_dns_shop_ru") from get_price import get_price from common import get_tracked_products @@ -24,7 +24,7 @@ while True: - print(f'Started at {DT.datetime.now():%d/%m/%Y %H:%M:%S}\n') + print(f"Started at {datetime.now():%d/%m/%Y %H:%M:%S}\n") db_create_backup() @@ -33,19 +33,25 @@ try: for product_data in get_tracked_products(): if product_data in checked_products: - print(f"Duplicate: {repr(product_data['title'])}, url: {product_data['url']}\n") + print( + f"Duplicate: {repr(product_data['title'])}, url: {product_data['url']}\n" + ) continue - product, _ = Product.get_or_create(title=product_data['title'], url=product_data['url']) + product, _ = Product.get_or_create( + title=product_data["title"], url=product_data["url"] + ) print(product) last_price_dns = product.get_last_price_dns() last_price_technopoint = product.get_last_price_technopoint() - print(f'Last DNS: {last_price_dns}, Technopoint: {last_price_technopoint}') + print(f"Last DNS: {last_price_dns}, Technopoint: {last_price_technopoint}") current_url_price_dns = get_price(product.url) current_url_price_tp = get_price(product.get_technopoint_url()) - print(f'Current url price: DNS={current_url_price_dns}, Technopoint={current_url_price_tp}') + print( + f"Current url price: DNS={current_url_price_dns}, Technopoint={current_url_price_tp}" + ) is_change_dns = current_url_price_dns and current_url_price_dns != last_price_dns is_change_technopoint = current_url_price_tp and current_url_price_tp != last_price_technopoint @@ -53,17 +59,19 @@ # Добавляем новую цену, если цена отличается или у продукта еще нет цен if is_change_dns or is_change_technopoint or is_first_price: - text = f'Append new price: DNS={current_url_price_dns}, Technopoint={current_url_price_tp}.' \ - f' Reason: ' + text = ( + f"Append new price: DNS={current_url_price_dns}, Technopoint={current_url_price_tp}." + f" Reason: " + ) if is_first_price: - text += 'First price' + text += "First price" else: if is_change_dns and is_change_technopoint: - text += 'DNS and Technopoint' + text += "DNS and Technopoint" elif is_change_dns: - text += 'DNS' + text += "DNS" else: - text += 'Technopoint' + text += "Technopoint" print(text) product.append_price(current_url_price_dns, current_url_price_tp) @@ -81,7 +89,7 @@ tb = traceback.format_exc() print(tb) - print('Wait 15 minutes') - time.sleep(15 * 60) # 15 minutes + print("Wait 15 minutes") + wait(minutes=15) print() diff --git a/DNS price tracking/migrations/001_auto.py b/DNS price tracking/migrations/001_auto.py index fc9ef4731..a7eddd304 100644 --- a/DNS price tracking/migrations/001_auto.py +++ b/DNS price tracking/migrations/001_auto.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: http://docs.peewee-orm.com/en/latest/peewee/playhouse.html#schema-migrations @@ -11,7 +11,7 @@ # TODO: имя в конфиг -DB_NAME = '../tracked_products.sqlite' +DB_NAME = "../tracked_products.sqlite" my_db = SqliteDatabase(DB_NAME) @@ -20,8 +20,12 @@ with my_db.atomic(): migrate( - migrator.rename_column('price', 'value', 'value_dns'), - migrator.add_column('price', 'value_technopoint', DecimalField(null=True)), - migrator.drop_index('price', 'price_product_id_date_value'), - migrator.add_index('price', ('product_id', 'date', 'value_dns', 'value_technopoint'), unique=True), + migrator.rename_column("price", "value", "value_dns"), + migrator.add_column("price", "value_technopoint", DecimalField(null=True)), + migrator.drop_index("price", "price_product_id_date_value"), + migrator.add_index( + "price", + ("product_id", "date", "value_dns", "value_technopoint"), + unique=True, + ), ) diff --git a/DNS price tracking/web.py b/DNS price tracking/web.py index ca24080d4..d38570718 100644 --- a/DNS price tracking/web.py +++ b/DNS price tracking/web.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import logging @@ -49,23 +49,21 @@ def index(): "price_techopoint": get_float(p.value_technopoint), }) - return render_template('index.html', products=products, prices=prices) + return render_template("index.html", products=products, prices=prices) -@app.route('/favicon.ico') +@app.route("/favicon.ico") def favicon(): return send_from_directory( - os.path.join(app.root_path, 'static/img'), - 'favicon.png' + os.path.join(app.root_path, "static/img"), + "favicon.png", ) -if __name__ == '__main__': +if __name__ == "__main__": app.debug = True - app.run( - port=10010 - ) + app.run(port=10010) # # Public IP # app.run(host='0.0.0.0') diff --git "a/Damerau\342\200\223Levenshtein_distance__misprints__\320\276\320\277\320\265\321\207\320\260\321\202\320\272\320\270/simple_distance_Levenshtein/fix_word.py" "b/Damerau\342\200\223Levenshtein_distance__misprints__\320\276\320\277\320\265\321\207\320\260\321\202\320\272\320\270/simple_distance_Levenshtein/fix_word.py" index 4c3dec194..a0bf9d9e9 100644 --- "a/Damerau\342\200\223Levenshtein_distance__misprints__\320\276\320\277\320\265\321\207\320\260\321\202\320\272\320\270/simple_distance_Levenshtein/fix_word.py" +++ "b/Damerau\342\200\223Levenshtein_distance__misprints__\320\276\320\277\320\265\321\207\320\260\321\202\320\272\320\270/simple_distance_Levenshtein/fix_word.py" @@ -1,11 +1,13 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -def fix_word(word, need_word): - from simple_distance import distance +from simple_distance import distance + + +def fix_word(word: str, need_word: str) -> str | None: number = distance(word, need_word) # Считаем что разница в 2 символа и меньше еще нормальная @@ -15,13 +17,13 @@ def fix_word(word, need_word): return need_word -if __name__ == '__main__': - need_word = 'Привет' +if __name__ == "__main__": + need_word = "Привет" - print(fix_word('Привет', need_word)) # Привет - print(fix_word('Првет', need_word)) # Привет - print(fix_word('Прывет', need_word)) # Привет - print(fix_word('Привед', need_word)) # Привет - print(fix_word('Превед', need_word)) # Привет + print(fix_word("Привет", need_word)) # Привет + print(fix_word("Првет", need_word)) # Привет + print(fix_word("Прывет", need_word)) # Привет + print(fix_word("Привед", need_word)) # Привет + print(fix_word("Превед", need_word)) # Привет - print(fix_word('Преед', need_word)) # None + print(fix_word("Преед", need_word)) # None diff --git "a/Damerau\342\200\223Levenshtein_distance__misprints__\320\276\320\277\320\265\321\207\320\260\321\202\320\272\320\270/simple_distance_Levenshtein/match_two_words.py" "b/Damerau\342\200\223Levenshtein_distance__misprints__\320\276\320\277\320\265\321\207\320\260\321\202\320\272\320\270/simple_distance_Levenshtein/match_two_words.py" index 8270a7cd4..505a3db5c 100644 --- "a/Damerau\342\200\223Levenshtein_distance__misprints__\320\276\320\277\320\265\321\207\320\260\321\202\320\272\320\270/simple_distance_Levenshtein/match_two_words.py" +++ "b/Damerau\342\200\223Levenshtein_distance__misprints__\320\276\320\277\320\265\321\207\320\260\321\202\320\272\320\270/simple_distance_Levenshtein/match_two_words.py" @@ -1,24 +1,26 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -def match_two_words(word_1, word_2): - from simple_distance import distance +from simple_distance import distance + + +def match_two_words(word_1: str, word_2: str) -> bool: number = distance(word_1, word_2) # Считаем что разница в 2 символа и меньше еще нормальная return number < 3 -if __name__ == '__main__': - need_word = 'Привет' +if __name__ == "__main__": + need_word = "Привет" - print(match_two_words('Привет', need_word)) # True - print(match_two_words('Првет', need_word)) # True - print(match_two_words('Прывет', need_word)) # True - print(match_two_words('Привед', need_word)) # True - print(match_two_words('Превед', need_word)) # True + print(match_two_words("Привет", need_word)) # True + print(match_two_words("Првет", need_word)) # True + print(match_two_words("Прывет", need_word)) # True + print(match_two_words("Привед", need_word)) # True + print(match_two_words("Превед", need_word)) # True - print(match_two_words('Преед', need_word)) # False + print(match_two_words("Преед", need_word)) # False diff --git "a/Damerau\342\200\223Levenshtein_distance__misprints__\320\276\320\277\320\265\321\207\320\260\321\202\320\272\320\270/simple_distance_Levenshtein/simple_distance.py" "b/Damerau\342\200\223Levenshtein_distance__misprints__\320\276\320\277\320\265\321\207\320\260\321\202\320\272\320\270/simple_distance_Levenshtein/simple_distance.py" index d083eeaa7..c91ea7c46 100644 --- "a/Damerau\342\200\223Levenshtein_distance__misprints__\320\276\320\277\320\265\321\207\320\260\321\202\320\272\320\270/simple_distance_Levenshtein/simple_distance.py" +++ "b/Damerau\342\200\223Levenshtein_distance__misprints__\320\276\320\277\320\265\321\207\320\260\321\202\320\272\320\270/simple_distance_Levenshtein/simple_distance.py" @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://ru.wikibooks.org/wiki/Реализации_алгоритмов/Расстояние_Левенштейна#Python @@ -18,7 +18,11 @@ def distance(a, b): for i in range(1, m + 1): previous_row, current_row = current_row, [i] + [0] * n for j in range(1, n + 1): - add, delete, change = previous_row[j] + 1, current_row[j - 1] + 1, previous_row[j - 1] + add, delete, change = ( + previous_row[j] + 1, + current_row[j - 1] + 1, + previous_row[j - 1], + ) if a[j - 1] != b[i - 1]: change += 1 @@ -27,7 +31,7 @@ def distance(a, b): return current_row[n] -if __name__ == '__main__': - print(distance('Привет', 'Превет')) # 1 - print(distance('Привет', 'Првет')) # 1 - print(distance('Привет', 'Првед')) # 2 +if __name__ == "__main__": + print(distance("Привет", "Превет")) # 1 + print(distance("Привет", "Првет")) # 1 + print(distance("Привет", "Првед")) # 2 diff --git "a/Damerau\342\200\223Levenshtein_distance__misprints__\320\276\320\277\320\265\321\207\320\260\321\202\320\272\320\270/use__difflib_SequenceMatcher.py" "b/Damerau\342\200\223Levenshtein_distance__misprints__\320\276\320\277\320\265\321\207\320\260\321\202\320\272\320\270/use__difflib_SequenceMatcher.py" index 4cd58881c..7ce5f16bd 100644 --- "a/Damerau\342\200\223Levenshtein_distance__misprints__\320\276\320\277\320\265\321\207\320\260\321\202\320\272\320\270/use__difflib_SequenceMatcher.py" +++ "b/Damerau\342\200\223Levenshtein_distance__misprints__\320\276\320\277\320\265\321\207\320\260\321\202\320\272\320\270/use__difflib_SequenceMatcher.py" @@ -1,10 +1,9 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -from typing import Optional, List from difflib import SequenceMatcher # pip install pymorphy2 @@ -14,17 +13,16 @@ morph = pymorphy2.MorphAnalyzer() -def get_tokens(text: str) -> List[pymorphy2.analyzer.Parse]: +def get_tokens(text: str) -> list[pymorphy2.analyzer.Parse]: return [morph.parse(word)[0] for word in simple_word_tokenize(text)] -ALL_WORDS = ['замена', 'заменить', 'касса'] +ALL_WORDS = ["замена", "заменить", "касса"] -def fix_command(word: str) -> Optional[str]: +def fix_command(word: str) -> str | None: rations = [ - (word2, SequenceMatcher(None, word, word2).ratio()) - for word2 in ALL_WORDS + (word2, SequenceMatcher(None, word, word2).ratio()) for word2 in ALL_WORDS ] rations = [(word, ratio) for word, ratio in rations if ratio >= 0.7] if not rations: @@ -34,9 +32,9 @@ def fix_command(word: str) -> Optional[str]: commands = [ - 'замена кассы', - 'заменить кассs', - 'замени кассу', + "замена кассы", + "заменить кассs", + "замени кассу", ] for command in commands: words = get_tokens(command) diff --git "a/Damerau\342\200\223Levenshtein_distance__misprints__\320\276\320\277\320\265\321\207\320\260\321\202\320\272\320\270/use__pyxdameraulevenshtein/example.py" "b/Damerau\342\200\223Levenshtein_distance__misprints__\320\276\320\277\320\265\321\207\320\260\321\202\320\272\320\270/use__pyxdameraulevenshtein/example.py" index 6fa86ca01..c375215fb 100644 --- "a/Damerau\342\200\223Levenshtein_distance__misprints__\320\276\320\277\320\265\321\207\320\260\321\202\320\272\320\270/use__pyxdameraulevenshtein/example.py" +++ "b/Damerau\342\200\223Levenshtein_distance__misprints__\320\276\320\277\320\265\321\207\320\260\321\202\320\272\320\270/use__pyxdameraulevenshtein/example.py" @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install pyxDamerauLevenshtein @@ -9,62 +9,180 @@ # SOURCE: https://github.com/gfairchild/pyxDamerauLevenshtein/blob/master/examples/examples.py -from pyxdameraulevenshtein import damerau_levenshtein_distance, normalized_damerau_levenshtein_distance, damerau_levenshtein_distance_ndarray, normalized_damerau_levenshtein_distance_ndarray import random import string import timeit + import numpy as np +from pyxdameraulevenshtein import ( + damerau_levenshtein_distance, + normalized_damerau_levenshtein_distance, + damerau_levenshtein_distance_ndarray, + normalized_damerau_levenshtein_distance_ndarray, +) -print('# edit distances (low edit distance means words are more similar):') -print("damerau_levenshtein_distance('{}', '{}') = {}".format('smtih', 'smith', damerau_levenshtein_distance('smtih', 'smith'))) -print("damerau_levenshtein_distance('{}', '{}') = {}".format('snapple', 'apple', damerau_levenshtein_distance('snapple', 'apple'))) -print("damerau_levenshtein_distance('{}', '{}') = {}".format('testing', 'testtn', damerau_levenshtein_distance('testing', 'testtn'))) -print("damerau_levenshtein_distance('{}', '{}') = {}".format('saturday', 'sunday', damerau_levenshtein_distance('saturday', 'sunday'))) -print("damerau_levenshtein_distance('{}', '{}') = {}".format('Saturday', 'saturday', damerau_levenshtein_distance('Saturday', 'saturday'))) -print("damerau_levenshtein_distance('{}', '{}') = {}".format('orange', 'pumpkin', damerau_levenshtein_distance('orange', 'pumpkin'))) -print("damerau_levenshtein_distance('{}', '{}') = {}".format('gifts', 'profit', damerau_levenshtein_distance('gifts', 'profit'))) -print("damerau_levenshtein_distance('{}', '{}') = {} # unicode example\n".format('Sjöstedt', 'Sjostedt', damerau_levenshtein_distance('Sjöstedt', 'Sjostedt'))) # unicode example +print("# edit distances (low edit distance means words are more similar):") +print( + "damerau_levenshtein_distance('{}', '{}') = {}".format( + "smtih", "smith", damerau_levenshtein_distance("smtih", "smith") + ) +) +print( + "damerau_levenshtein_distance('{}', '{}') = {}".format( + "snapple", "apple", damerau_levenshtein_distance("snapple", "apple") + ) +) +print( + "damerau_levenshtein_distance('{}', '{}') = {}".format( + "testing", "testtn", damerau_levenshtein_distance("testing", "testtn") + ) +) +print( + "damerau_levenshtein_distance('{}', '{}') = {}".format( + "saturday", "sunday", damerau_levenshtein_distance("saturday", "sunday") + ) +) +print( + "damerau_levenshtein_distance('{}', '{}') = {}".format( + "Saturday", "saturday", damerau_levenshtein_distance("Saturday", "saturday") + ) +) +print( + "damerau_levenshtein_distance('{}', '{}') = {}".format( + "orange", "pumpkin", damerau_levenshtein_distance("orange", "pumpkin") + ) +) +print( + "damerau_levenshtein_distance('{}', '{}') = {}".format( + "gifts", "profit", damerau_levenshtein_distance("gifts", "profit") + ) +) +print( + "damerau_levenshtein_distance('{}', '{}') = {} # unicode example\n".format( + "Sjöstedt", "Sjostedt", damerau_levenshtein_distance("Sjöstedt", "Sjostedt") + ) +) # unicode example -print('# normalized edit distances (low ratio means words are similar):') -print("normalized_damerau_levenshtein_distance('{}', '{}') = {}".format('smtih', 'smith', normalized_damerau_levenshtein_distance('smtih', 'smith'))) -print("normalized_damerau_levenshtein_distance('{}', '{}') = {}".format('snapple', 'apple', normalized_damerau_levenshtein_distance('snapple', 'apple'))) -print("normalized_damerau_levenshtein_distance('{}', '{}') = {}".format('testing', 'testtn', normalized_damerau_levenshtein_distance('testing', 'testtn'))) -print("normalized_damerau_levenshtein_distance('{}', '{}') = {}".format('saturday', 'sunday', normalized_damerau_levenshtein_distance('saturday', 'sunday'))) -print("normalized_damerau_levenshtein_distance('{}', '{}') = {}".format('Saturday', 'saturday', normalized_damerau_levenshtein_distance('Saturday', 'saturday'))) -print("normalized_damerau_levenshtein_distance('{}', '{}') = {}".format('orange', 'pumpkin', normalized_damerau_levenshtein_distance('orange', 'pumpkin'))) -print("normalized_damerau_levenshtein_distance('{}', '{}') = {}".format('gifts', 'profit', normalized_damerau_levenshtein_distance('gifts', 'profit'))) -print("normalized_damerau_levenshtein_distance('{}', '{}') = {} # unicode example\n".format('Sjöstedt', 'Sjostedt', normalized_damerau_levenshtein_distance('Sjöstedt', 'Sjostedt'))) # unicode example +print("# normalized edit distances (low ratio means words are similar):") +print( + "normalized_damerau_levenshtein_distance('{}', '{}') = {}".format( + "smtih", "smith", normalized_damerau_levenshtein_distance("smtih", "smith") + ) +) +print( + "normalized_damerau_levenshtein_distance('{}', '{}') = {}".format( + "snapple", "apple", normalized_damerau_levenshtein_distance("snapple", "apple") + ) +) +print( + "normalized_damerau_levenshtein_distance('{}', '{}') = {}".format( + "testing", + "testtn", + normalized_damerau_levenshtein_distance("testing", "testtn"), + ) +) +print( + "normalized_damerau_levenshtein_distance('{}', '{}') = {}".format( + "saturday", + "sunday", + normalized_damerau_levenshtein_distance("saturday", "sunday"), + ) +) +print( + "normalized_damerau_levenshtein_distance('{}', '{}') = {}".format( + "Saturday", + "saturday", + normalized_damerau_levenshtein_distance("Saturday", "saturday"), + ) +) +print( + "normalized_damerau_levenshtein_distance('{}', '{}') = {}".format( + "orange", + "pumpkin", + normalized_damerau_levenshtein_distance("orange", "pumpkin"), + ) +) +print( + "normalized_damerau_levenshtein_distance('{}', '{}') = {}".format( + "gifts", "profit", normalized_damerau_levenshtein_distance("gifts", "profit") + ) +) +print( + "normalized_damerau_levenshtein_distance('{}', '{}') = {} # unicode example\n".format( + "Sjöstedt", + "Sjostedt", + normalized_damerau_levenshtein_distance("Sjöstedt", "Sjostedt"), + ) +) # unicode example -print('# edit distances for a single sequence against an array of sequences') -array = np.array(['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']) -print("damerau_levenshtein_distance_ndarray('{}', np.array({})) = {}".format('Saturday', array, damerau_levenshtein_distance_ndarray('Saturday', array))) -print("normalized_damerau_levenshtein_distance_ndarray('{}', np.array({})) = {}\n".format('Saturday', array, normalized_damerau_levenshtein_distance_ndarray('Saturday', array))) +print("# edit distances for a single sequence against an array of sequences") +array = np.array( + ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"] +) +print( + "damerau_levenshtein_distance_ndarray('{}', np.array({})) = {}".format( + "Saturday", array, damerau_levenshtein_distance_ndarray("Saturday", array) + ) +) +print( + "normalized_damerau_levenshtein_distance_ndarray('{}', np.array({})) = {}\n".format( + "Saturday", + array, + normalized_damerau_levenshtein_distance_ndarray("Saturday", array), + ) +) -print('# normalized edit distances for a single sequence against an array of sequences - unicode') -array = np.array(['Sjöstedt', 'Sjostedt', 'Söstedt', 'Sjöedt']) -print("damerau_levenshtein_distance_ndarray('{}', np.array({})) = {}".format('Sjöstedt', array, damerau_levenshtein_distance_ndarray('Sjöstedt', array))) -print("normalized_damerau_levenshtein_distance_ndarray('{}', np.array({})) = {}\n".format('Sjöstedt', array, normalized_damerau_levenshtein_distance_ndarray('Sjöstedt', array))) +print( + "# normalized edit distances for a single sequence against an array of sequences - unicode" +) +array = np.array(["Sjöstedt", "Sjostedt", "Söstedt", "Sjöedt"]) +print( + "damerau_levenshtein_distance_ndarray('{}', np.array({})) = {}".format( + "Sjöstedt", array, damerau_levenshtein_distance_ndarray("Sjöstedt", array) + ) +) +print( + "normalized_damerau_levenshtein_distance_ndarray('{}', np.array({})) = {}\n".format( + "Sjöstedt", + array, + normalized_damerau_levenshtein_distance_ndarray("Sjöstedt", array), + ) +) # random words will be comprised of ascii letters, numbers, and spaces -print('# performance testing:') -chars = string.ascii_letters + string.digits + ' ' -word1 = ''.join([random.choice(chars) for i in range(30)]) # generate a random string of characters of length 30 -word2 = ''.join([random.choice(chars) for i in range(30)]) # and another -print("""\ +print("# performance testing:") +chars = string.ascii_letters + string.digits + " " +word1 = "".join( + [random.choice(chars) for i in range(30)] +) # generate a random string of characters of length 30 +word2 = "".join([random.choice(chars) for i in range(30)]) # and another +print( + """\ timeit.timeit("damerau_levenshtein_distance('{}', '{}')", 'from pyxdameraulevenshtein import damerau_levenshtein_distance', number=500000) = {} seconds""".format( - word1, word2, timeit.timeit("damerau_levenshtein_distance('{}', '{}')".format(word1, word2), - 'from pyxdameraulevenshtein import damerau_levenshtein_distance', - number=500000)) + word1, + word2, + timeit.timeit( + "damerau_levenshtein_distance('{}', '{}')".format(word1, word2), + "from pyxdameraulevenshtein import damerau_levenshtein_distance", + number=500000, + ), + ) ) -print("""\ +print( + """\ timeit.timeit("damerau_levenshtein_distance('{}', '{}')", 'from pyxdameraulevenshtein import damerau_levenshtein_distance', number=500000) = {} seconds # short-circuit makes this faster""".format( - word1, word1, timeit.timeit("damerau_levenshtein_distance('{}', '{}')".format(word1, word1), - 'from pyxdameraulevenshtein import damerau_levenshtein_distance', - number=500000)) + word1, + word1, + timeit.timeit( + "damerau_levenshtein_distance('{}', '{}')".format(word1, word1), + "from pyxdameraulevenshtein import damerau_levenshtein_distance", + number=500000, + ), + ) ) diff --git "a/Damerau\342\200\223Levenshtein_distance__misprints__\320\276\320\277\320\265\321\207\320\260\321\202\320\272\320\270/use__pyxdameraulevenshtein/fix_command.py" "b/Damerau\342\200\223Levenshtein_distance__misprints__\320\276\320\277\320\265\321\207\320\260\321\202\320\272\320\270/use__pyxdameraulevenshtein/fix_command.py" index c1ebcbe22..3bb0a61ad 100644 --- "a/Damerau\342\200\223Levenshtein_distance__misprints__\320\276\320\277\320\265\321\207\320\260\321\202\320\272\320\270/use__pyxdameraulevenshtein/fix_command.py" +++ "b/Damerau\342\200\223Levenshtein_distance__misprints__\320\276\320\277\320\265\321\207\320\260\321\202\320\272\320\270/use__pyxdameraulevenshtein/fix_command.py" @@ -1,43 +1,54 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install pyxDamerauLevenshtein # https://github.com/gfairchild/pyxDamerauLevenshtein # SOURCE: https://github.com/gfairchild/pyxDamerauLevenshtein/blob/master/examples/examples.py +import numpy as np +from pyxdameraulevenshtein import normalized_damerau_levenshtein_distance_ndarray + ALL_COMMANDS = [ - 'команды', - 'ругнись', - 'насмеши', - 'погода', - 'котики', - 'что посмотреть', - 'угадай', - 'hex2str', 'str2hex', - 'bin2str', 'str2bin', - 'qrcode', - 'добавь напоминание', 'удали напоминание', - 'короткая ссылка', - 'найди гифку', - 'курс валют', - 'курс криптовалют', - 'язык Йоды', - 'язык падонков', 'олбанский', 'олбанский язык', - 'православный язык', + "команды", + "ругнись", + "насмеши", + "погода", + "котики", + "что посмотреть", + "угадай", + "hex2str", + "str2hex", + "bin2str", + "str2bin", + "qrcode", + "добавь напоминание", + "удали напоминание", + "короткая ссылка", + "найди гифку", + "курс валют", + "курс криптовалют", + "язык Йоды", + "язык падонков", + "олбанский", + "олбанский язык", + "православный язык", ] def fix_command(text): - import numpy as np array = np.array(ALL_COMMANDS) - from pyxdameraulevenshtein import normalized_damerau_levenshtein_distance_ndarray # TODO: поиграться с damerau_levenshtein_distance_ndarray - result = list(zip(ALL_COMMANDS, list(normalized_damerau_levenshtein_distance_ndarray(text, array)))) + result = list( + zip( + ALL_COMMANDS, + list(normalized_damerau_levenshtein_distance_ndarray(text, array)), + ) + ) # print('\n' + text, sorted(result, key=lambda x: x[1])) command, rate = min(result, key=lambda x: x[1]) @@ -50,67 +61,67 @@ def fix_command(text): return command -if __name__ == '__main__': +if __name__ == "__main__": # SHOW RESULT - def check(text): - format_text = '{:<%s} -> {}' % (len(max(ALL_COMMANDS, key=len)) + 2) + def check(text) -> None: + format_text = "{:<%s} -> {}" % (len(max(ALL_COMMANDS, key=len)) + 2) command = fix_command(text) if command is None: - result = 'is None (не удалось распознать команду: "{}")'.format(text) + result = f'is None (не удалось распознать команду: "{text}")' print(format_text.format(text, result)) else: print(format_text.format(text, command)) - check('команды') - check('комнды') - check('команду') - check('кманды') - check('комманды') - check('короткую ссылку') - check('курс валют') - check('курсы валюты') - check('добавь напоминание') - check('добавь напаменание') - check('добавить напоминание') - check('добавить команду') - check('hex2str') - check('hex2dtr') - check('qrcode') - check('qtcode') - check('qrcodr') - check('курс криптовалют') - check('курс крептоволют') + check("команды") + check("комнды") + check("команду") + check("кманды") + check("комманды") + check("короткую ссылку") + check("курс валют") + check("курсы валюты") + check("добавь напоминание") + check("добавь напаменание") + check("добавить напоминание") + check("добавить команду") + check("hex2str") + check("hex2dtr") + check("qrcode") + check("qtcode") + check("qrcodr") + check("курс криптовалют") + check("курс крептоволют") # # Run test - def run_tests(): - def test(text, expected): + def run_tests() -> None: + def test(text, expected) -> None: command = fix_command(text) - assert expected == command, 'Expected: "{}", get: "{}"'.format(expected, command) + assert expected == command, f'Expected: "{expected}", get: "{command}"' - expected = 'команды' - test('команды', expected) - test('комнды', expected) - test('команду', expected) - test('команду', expected) - test('комманды', expected) + expected = "команды" + test("команды", expected) + test("комнды", expected) + test("команду", expected) + test("команду", expected) + test("комманды", expected) - expected = 'короткая ссылка' - test('короткую ссылку', expected) + expected = "короткая ссылка" + test("короткую ссылку", expected) - expected = 'курс валют' - test('курс валют', expected) - test('курсы валюты', expected) + expected = "курс валют" + test("курс валют", expected) + test("курсы валюты", expected) - expected = 'добавь напоминание' - test('добавь напоминание', expected) - test('добавь напаменание', expected) - test('добавить напоминание', expected) + expected = "добавь напоминание" + test("добавь напоминание", expected) + test("добавь напаменание", expected) + test("добавить напоминание", expected) expected = None - test('добавить команду', expected) - test('купить слона', expected) - test('кмнд', expected) + test("добавить команду", expected) + test("купить слона", expected) + test("кмнд", expected) run_tests() diff --git "a/Damerau\342\200\223Levenshtein_distance__misprints__\320\276\320\277\320\265\321\207\320\260\321\202\320\272\320\270/use__pyxdameraulevenshtein/match_two_words.py" "b/Damerau\342\200\223Levenshtein_distance__misprints__\320\276\320\277\320\265\321\207\320\260\321\202\320\272\320\270/use__pyxdameraulevenshtein/match_two_words.py" index e4940afe8..6f4b332fc 100644 --- "a/Damerau\342\200\223Levenshtein_distance__misprints__\320\276\320\277\320\265\321\207\320\260\321\202\320\272\320\270/use__pyxdameraulevenshtein/match_two_words.py" +++ "b/Damerau\342\200\223Levenshtein_distance__misprints__\320\276\320\277\320\265\321\207\320\260\321\202\320\272\320\270/use__pyxdameraulevenshtein/match_two_words.py" @@ -1,28 +1,28 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install pyxDamerauLevenshtein # SOURCE: https://github.com/gfairchild/pyxDamerauLevenshtein +from pyxdameraulevenshtein import damerau_levenshtein_distance -def match_two_words(word_1, word_2): - from pyxdameraulevenshtein import damerau_levenshtein_distance +def match_two_words(word_1: str, word_2: str) -> bool: number = damerau_levenshtein_distance(word_1, word_2) # Считаем что разница в 2 символа и меньше еще нормальная return number < 3 -if __name__ == '__main__': - need_word = 'Привет' +if __name__ == "__main__": + need_word = "Привет" - print(match_two_words('Привет', need_word)) # True - print(match_two_words('Првет', need_word)) # True - print(match_two_words('Прывет', need_word)) # True - print(match_two_words('Привед', need_word)) # True - print(match_two_words('Превед', need_word)) # True + print(match_two_words("Привет", need_word)) # True + print(match_two_words("Првет", need_word)) # True + print(match_two_words("Прывет", need_word)) # True + print(match_two_words("Привед", need_word)) # True + print(match_two_words("Превед", need_word)) # True - print(match_two_words('Преед', need_word)) # False + print(match_two_words("Преед", need_word)) # False diff --git a/Dark Souls/backup__save_files/by_autosave/common.py b/Dark Souls/backup__save_files/by_autosave/common.py deleted file mode 100644 index 9582fad13..000000000 --- a/Dark Souls/backup__save_files/by_autosave/common.py +++ /dev/null @@ -1,60 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -__author__ = 'ipetrash' - - -import os -import time -from glob import glob -import traceback - -# For import utils.py -import sys -sys.path.append('..') - -from utils import get_logger, backup - - -log = get_logger(__file__) - - -def backup_saves(path_ds_save: str, forced=False): - try: - for path_file_name in glob(path_ds_save): - log.debug(f"Check: {path_file_name}") - - # Get timestamps - save_timestamp = os.path.getmtime(path_file_name) - now_timestamp = time.time() - - # Save backup. If less than 600 secs have passed since the last modification of the file - is_modified = (now_timestamp - save_timestamp) < 600 - ok = forced or is_modified - log.debug(f"{'Need backup' if ok else 'Not need backup'}. " - f"Reason: Forced={forced}, Is modified file save={is_modified}") - if not ok: - continue - - file_name_backup = backup(path_file_name, now_timestamp) - log.debug(f"Saving backup: {file_name_backup}") - - except: - print("ERROR:\n" + traceback.format_exc()) - time.sleep(5 * 60) - - -def run(path_ds_save: str, timeout_minutes=5): - # Example: r'~\Documents\NBGI\DarkSouls\*\DRAKS0005.sl2' - path_ds_save = os.path.expanduser(path_ds_save) - - print(f'{path_ds_save}\n') - - backup_saves(path_ds_save, forced=True) - - while True: - print(f"\nSleeping for {timeout_minutes} minutes\n") - time.sleep(timeout_minutes * 60) - - backup_saves(path_ds_save, False) - diff --git a/Dark Souls/bonfires.py b/Dark Souls/bonfires.py deleted file mode 100644 index ed89807eb..000000000 --- a/Dark Souls/bonfires.py +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -__author__ = 'ipetrash' - - -import requests -from bs4 import BeautifulSoup - - -rs = requests.get('https://darksouls.fandom.com/ru/wiki/Костер') -root = BeautifulSoup(rs.content, 'html.parser') - -for h2 in root.select('h2'): - headline = h2.select_one('.mw-headline') - if not headline: - continue - - if headline.get_text(strip=True) != 'Список костров': - continue - - for li in h2.find_next_sibling('ol').select('li'): - print(li.get_text(strip=True, separator=' ')) diff --git a/Dark Souls/generate_HTML_location_graph__with_d3js/generate_graph_html.py b/Dark Souls/generate_HTML_location_graph__with_d3js/generate_graph_html.py deleted file mode 100644 index c41aa9ffa..000000000 --- a/Dark Souls/generate_HTML_location_graph__with_d3js/generate_graph_html.py +++ /dev/null @@ -1,83 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -__author__ = 'ipetrash' - - -from typing import List, Tuple - - -def get_dataset_text(links: List[Tuple[str, str]]) -> str: - # Список всех узлов графа - nodes = set() - for a, b in links: - nodes.add(a) - nodes.add(b) - - # Задаем отсортированный порядок узлов - nodes = sorted(nodes) - - # Список ребер - edges = list() - - for a, b in links: - source = nodes.index(a) - target = nodes.index(b) - - edges.append({'source': source, 'target': target}) - - nodes_dict = [{"name": title} for title in nodes] - - # Пример структуры графа. - # nodes -- список узлов - # edges -- список ребер, описываются как {source: <индекс узла>, target: <индекс узла>}, - # - # var dataset = { - # nodes: [ - # {name: "Adam"}, {name: "Bob"}, {name: "Carrie"}, {name: "Donovan"}, - # {name: "Edward"}, {name: "Felicity"}, {name: "George"}, - # {name: "Hannah"}, {name: "Iris"}, {name: "Jerry"} - # ], - # edges: [ - # {source: 0, target: 1}, {source: 0, target: 2}, {source: 0, target: 3}, - # {source: 0, target: 4}, {source: 1, target: 5}, {source: 2, target: 5}, - # {source: 2, target: 5}, {source: 3, target: 4}, {source: 5, target: 8}, - # {source: 5, target: 9}, {source: 6, target: 7}, {source: 7, target: 8}, - # {source: 8, target: 9} - # ] - # }; - text = """\ - var dataset = {{ - nodes: {}, - edges: {} - }}; - """.format(nodes_dict, edges) - - return text - - -def generate(file_name_to: str, links: List[Tuple[str, str]], title="Graph"): - template_title = '{{title}}' - template_dataset = '{{dataset}}' - - dataset_text = get_dataset_text(links) - - with open('template_graph.html', 'r', encoding='utf-8') as f: - text = f.read() - text = text.replace(template_title, title) - text = text.replace(template_dataset, dataset_text) - - with open(file_name_to, 'w', encoding='utf-8') as f: - f.write(text) - - -if __name__ == '__main__': - links = [('Арена Братства Крови', 'Чистилище Нежити'), ('Башня Луны', 'Забытая Крепость'), ('Башня Солнца', 'Железная Цитадель'), ('Безлюдная Пристань', 'Забытая Крепость'), ('Безлюдная Пристань', 'Огненная Башня Хейда'), ('Большой Собор', 'Ледяная Элеум Лойс'), ('Большой Собор', 'Предвечный Хаос'), ('Бухта Брайтстоун-Тселдора', 'Воспоминания Дракона'), ('Бухта Брайтстоун-Тселдора', 'Двери Фарроса'), ('Бухта Брайтстоун-Тселдора', 'Личные Палаты Лорда'), ('Гнездо Дракона', 'Храм Дракона'), ('Гнездо Дракона', 'Цитадель Алдии'), ('Двери Фарроса', 'Темнолесье'), ('Долина Жатвы', 'Земляной Пик'), ('Долина Жатвы', 'Роща Охотника'), ('Железная Цитадель', 'Земляной Пик'), ('Железная Цитадель', 'Мглистая Башня'), ('Железный Проход', 'Мглистая Башня'), ('Забытая Крепость', 'Лес Павших Гигантов'), ('Забытая Крепость', 'Холм Грешников'), ('Замок Дранглик', 'Королевский Проход'), ('Замок Дранглик', 'Трон Желания'), ('Замок Дранглик', 'Храм Зимы'), ('Карта Дранглика', 'Маджула'), ('Королевский Проход', 'Храм Аманы'), ('Ледяная Элеум Лойс', 'Холодные Окраины'), ('Ледяная Элеум Лойс', 'Храм Зимы'), ('Лес Павших Гигантов', 'Маджула'), ('Лес Павших Гигантов', 'Память Ваммара'), ('Лес Павших Гигантов', 'Память Джейта'), ('Лес Павших Гигантов', 'Память Орро'), ('Маджула', 'Междумирье'), ('Маджула', 'Могила Святых'), ('Маджула', 'Огненная Башня Хейда'), ('Маджула', 'Помойка'), ('Маджула', 'Роща Охотника'), ('Маджула', 'Темнолесье'), ('Мглистая Башня', 'Память Старого Железного Короля'), ('Могила Святых', 'Помойка'), ('Огненная Башня Хейда', 'Собор Лазурного Пути'), ('Память Короля', 'Склеп Нежити'), ('Пещера Мертвых', 'Шульва, Священный Город'), ('Помойка', 'Черная Расселина'), ('Роща Охотника', 'Чистилище Нежити'), ('Святилище Дракона', 'Убежище Дракона'), ('Святилище Дракона', 'Шульва, Священный Город'), ('Склеп Нежити', 'Храм Аманы'), ('Темная Бездна Былого', 'Замок Дранглик'), ('Темная Бездна Былого', 'Темные Руины'), ('Темная Бездна Былого', 'Черная Расселина'), ('Темнолесье', 'Храм Зимы'), ('Темнолесье', 'Цитадель Алдии'), ('Черная Расселина', 'Шульва, Священный Город')] - dataset = get_dataset_text(links) - print(dataset) - - links = [('Adam', 'Bob'), ('Adam', 'Ivan'), ('Bob', 'Iris'), ('Bob', 'Jerry'), ('Iris', 'George')] - generate('example.html', links) - - links = [('Сквайр', 'Рыцарь'), ('Сквайр', 'Охотник на ведьм'), ('Охотник на ведьм', 'Инквизитор'), ('Инквизитор', 'Великий инквизитор'), ('Рыцарь', 'Рыцарь Империи'), ('Рыцарь Империи', 'Ангел'), ('Рыцарь Империи', 'Паладин'), ('Паладин', 'Святой мститель'), ('Паладин', 'Защитник веры')] - generate('example_disciples.html', links, title='Graph Disciples') diff --git a/Dark Souls/generate_HTML_location_graph__with_d3js/generate_graph_html__Dark_Souls.py b/Dark Souls/generate_HTML_location_graph__with_d3js/generate_graph_html__Dark_Souls.py deleted file mode 100644 index fa298ea65..000000000 --- a/Dark Souls/generate_HTML_location_graph__with_d3js/generate_graph_html__Dark_Souls.py +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -__author__ = 'ipetrash' - - -from generate_graph_html import generate - - -# ../common.py/find_links_ds1 -# ../print_locations/print_locations__Dark_Souls.py -links = [('Алтарь Огня', 'Бездна'), ('Алтарь Огня', 'Горнило Первого Пламени'), ('Алтарь Огня', 'Храм Огня'), ('Алтарь Света', 'Город Нежити'), ('Алтарь Света', 'Уезд Нежити'), ('Анор Лондо', 'Архивы Герцога'), ('Анор Лондо', 'Крепость Сена'), ('Анор Лондо', 'Нарисованный Мир Ариамис'), ('Архивы Герцога', 'Кристальный Грот'), ('Бездна', 'Руины Нового Лондо'), ('Владение Квилег', 'Руины Демонов'), ('Владение Квилег', 'Чумной Город'), ('Глубины', 'Город Нежити'), ('Глубины', 'Чумной Город'), ('Город Нежити', 'Озеро Темных Корней'), ('Город Нежити', 'Уезд Нежити'), ('Город Нежити', 'Храм Огня'), ('Долина Драконов', 'Озеро Темных Корней'), ('Долина Драконов', 'Руины Нового Лондо'), ('Долина Драконов', 'Чумной Город'), ('Забытый Изалит', 'Руины Демонов'), ('Зал Стоицизма', 'Поселок Олачиль'), ('Катакомбы', 'Склеп Великанов'), ('Катакомбы', 'Храм Огня'), ('Королевский Лес', 'Поселок Олачиль'), ('Королевский Лес', 'Святилище Олачиля'), ('Королевский Лес', 'Ущелье Бездны'), ('Крепость Сена', 'Уезд Нежити'), ('Озеро Золы', 'Полость'), ('Озеро Темных Корней', 'Сад Темных Корней'), ('Озеро Темных Корней', 'Священный Сад'), ('Подземелье Поселка Олачиль', 'Поселок Олачиль'), ('Подземелье Поселка Олачиль', 'Ущелье Бездны'), ('Полость', 'Чумной Город'), ('Поселок Олачиль', 'Битва За Стоицизм'), ('Руины Нового Лондо', 'Храм Огня'), ('Сад Темных Корней', 'Уезд Нежити'), ('Святилище Олачиля', 'Священный Сад'), ('Северное Прибежище Нежити', 'Храм Огня'), ('Уезд Нежити', 'Глубины'), ('Уезд Нежити', 'Храм Огня')] -generate('locations__Dark_Souls.html', links, title='Locations Dark Souls') - -# ../common.py/find_links_ds2 -# ../print_locations/print_locations__Dark_Souls_II.py -links = [('Арена Братства Крови', 'Чистилище Нежити'), ('Башня Луны', 'Забытая Крепость'), ('Башня Солнца', 'Железная Цитадель'), ('Безлюдная Пристань', 'Забытая Крепость'), ('Безлюдная Пристань', 'Огненная Башня Хейда'), ('Большой Собор', 'Ледяная Элеум Лойс'), ('Большой Собор', 'Предвечный Хаос'), ('Бухта Брайтстоун-Тселдора', 'Воспоминания Дракона'), ('Бухта Брайтстоун-Тселдора', 'Двери Фарроса'), ('Бухта Брайтстоун-Тселдора', 'Личные Палаты Лорда'), ('Гнездо Дракона', 'Храм Дракона'), ('Гнездо Дракона', 'Цитадель Алдии'), ('Двери Фарроса', 'Темнолесье'), ('Долина Жатвы', 'Земляной Пик'), ('Долина Жатвы', 'Роща Охотника'), ('Железная Цитадель', 'Земляной Пик'), ('Железная Цитадель', 'Мглистая Башня'), ('Железный Проход', 'Мглистая Башня'), ('Забытая Крепость', 'Лес Павших Гигантов'), ('Забытая Крепость', 'Холм Грешников'), ('Замок Дранглик', 'Королевский Проход'), ('Замок Дранглик', 'Трон Желания'), ('Замок Дранглик', 'Храм Зимы'), ('Карта Дранглика', 'Маджула'), ('Королевский Проход', 'Храм Аманы'), ('Ледяная Элеум Лойс', 'Холодные Окраины'), ('Ледяная Элеум Лойс', 'Храм Зимы'), ('Лес Павших Гигантов', 'Маджула'), ('Лес Павших Гигантов', 'Память Ваммара'), ('Лес Павших Гигантов', 'Память Джейта'), ('Лес Павших Гигантов', 'Память Орро'), ('Маджула', 'Междумирье'), ('Маджула', 'Могила Святых'), ('Маджула', 'Огненная Башня Хейда'), ('Маджула', 'Помойка'), ('Маджула', 'Роща Охотника'), ('Маджула', 'Темнолесье'), ('Мглистая Башня', 'Память Старого Железного Короля'), ('Могила Святых', 'Помойка'), ('Огненная Башня Хейда', 'Собор Лазурного Пути'), ('Память Короля', 'Склеп Нежити'), ('Пещера Мертвых', 'Шульва, Священный Город'), ('Помойка', 'Черная Расселина'), ('Роща Охотника', 'Чистилище Нежити'), ('Святилище Дракона', 'Убежище Дракона'), ('Святилище Дракона', 'Шульва, Священный Город'), ('Склеп Нежити', 'Храм Аманы'), ('Темная Бездна Былого', 'Замок Дранглик'), ('Темная Бездна Былого', 'Темнолесье'), ('Темная Бездна Былого', 'Черная Расселина'), ('Темнолесье', 'Храм Зимы'), ('Темнолесье', 'Цитадель Алдии'), ('Черная Расселина', 'Шульва, Священный Город')] -generate('locations__Dark_Souls_2.html', links, title='Locations Dark Souls 2') - -# ../common.py/find_links_ds3 -# ../print_locations/print_locations__Dark_Souls_III.py -links = [('Анор Лондо', 'Иритилл Холодной Долины'), ('Великий Архив', 'Замок Лотрика'), ('Высокая Стена Лотрика', 'Замок Лотрика'), ('Высокая Стена Лотрика', 'Кладбище Пепла'), ('Высокая Стена Лотрика', 'Поселение Нежити'), ('Город За Стеной', 'Груда Отбросов'), ('Груда Отбросов', 'Нарисованный Мир Арианделя'), ('Груда Отбросов', 'Печь Первого Пламени'), ('Заброшенные Могилы', 'Сад Снедаемого Короля'), ('Замок Лотрика', 'Сад Снедаемого Короля'), ('Иритилл Холодной Долины', 'Катакомбы Картуса'), ('Иритилл Холодной Долины', 'Подземелье Иритилла'), ('Катакомбы Картуса', 'Тлеющее Озеро'), ('Катакомбы Картуса', 'Цитадель Фаррона'), ('Кладбище Пепла', 'Храм Огня'), ('Нарисованный Мир Арианделя', 'Храм Глубин'), ('Оскверненная Столица', 'Подземелье Иритилла'), ('Печь Первого Пламени', 'Храм Огня'), ('Пик Древних Драконов', 'Подземелье Иритилла'), ('Поединок Нежити', 'Храм Огня'), ('Поселение Нежити', 'Путь Жертв'), ('Путь Жертв', 'Храм Глубин'), ('Путь Жертв', 'Цитадель Фаррона'), ('Храм Огня', 'Высокая Стена Лотрика')] -generate('locations__Dark_Souls_3.html', links, title='Locations Dark Souls 3') diff --git a/Dark Souls/graph_ds2_locations__graphviz__DOT/ds2_locations.py b/Dark Souls/graph_ds2_locations__graphviz__DOT/ds2_locations.py deleted file mode 100644 index 7818ed087..000000000 --- a/Dark Souls/graph_ds2_locations__graphviz__DOT/ds2_locations.py +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -__author__ = 'ipetrash' - - -# pip install graphviz -from graphviz import Graph - - -g = Graph('g', filename='ds2_locations.gv', comment='Locations Dark Souls') - -# SOURCE: https://github.com/gil9red/SimplePyScripts/blob/45852f0541644dfb0478e98e2df530c1bb93eeae/Dark%20Souls/generate_HTML_location_graph__with_d3js/generate_graph_html__Dark_Souls.py -links = [('Алтарь Огня', 'Бездна'), ('Алтарь Огня', 'Горнило Первого Пламени'), ('Алтарь Огня', 'Храм Огня'), ('Алтарь Света', 'Город Нежити'), ('Алтарь Света', 'Уезд Нежити'), ('Анор Лондо', 'Архивы Герцога'), ('Анор Лондо', 'Крепость Сена'), ('Анор Лондо', 'Нарисованный Мир Ариамис'), ('Архивы Герцога', 'Кристальный Грот'), ('Бездна', 'Руины Нового Лондо'), ('Владение Квилег', 'Руины Демонов'), ('Владение Квилег', 'Чумной Город'), ('Глубины', 'Город Нежити'), ('Глубины', 'Чумной Город'), ('Город Нежити', 'Озеро Темных Корней'), ('Город Нежити', 'Уезд Нежити'), ('Город Нежити', 'Храм Огня'), ('Долина Драконов', 'Озеро Темных Корней'), ('Долина Драконов', 'Руины Нового Лондо'), ('Долина Драконов', 'Чумной Город'), ('Забытый Изалит', 'Руины Демонов'), ('Зал Стоицизма', 'Поселок Олачиль'), ('Катакомбы', 'Склеп Великанов'), ('Катакомбы', 'Храм Огня'), ('Королевский Лес', 'Поселок Олачиль'), ('Королевский Лес', 'Святилище Олачиля'), ('Королевский Лес', 'Ущелье Бездны'), ('Крепость Сена', 'Уезд Нежити'), ('Озеро Золы', 'Полость'), ('Озеро Темных Корней', 'Сад Темных Корней'), ('Озеро Темных Корней', 'Священный Сад'), ('Подземелье Поселка Олачиль', 'Поселок Олачиль'), ('Подземелье Поселка Олачиль', 'Ущелье Бездны'), ('Полость', 'Чумной Город'), ('Поселок Олачиль', 'Битва За Стоицизм'), ('Руины Нового Лондо', 'Храм Огня'), ('Сад Темных Корней', 'Уезд Нежити'), ('Святилище Олачиля', 'Священный Сад'), ('Северное Прибежище Нежити', 'Храм Огня'), ('Уезд Нежити', 'Глубины'), ('Уезд Нежити', 'Храм Огня')] - -for a, b in links: - g.edge(a, b) - -g.attr(label=r'\n\nLocations Dark Souls') -g.view() - -# g.render(format='jpg') diff --git a/Dark Souls/print__stats/about_statistics.py b/Dark Souls/print__stats/about_statistics.py deleted file mode 100644 index 71cd6d298..000000000 --- a/Dark Souls/print__stats/about_statistics.py +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -__author__ = 'ipetrash' - - -# Fresh values from: -# from print__stats_ds1 import get_stats_ds1 -# from print__stats_ds2 import get_stats_ds2 -# from print__stats_ds3 import get_stats_ds3 -# -# stats_ds1 = [x[0] for x in get_stats_ds1()] -# stats_ds2 = [x[0] for x in get_stats_ds2()] -# stats_ds3 = [x[0] for x in get_stats_ds3()] - -stats_ds1 = ['Вера', 'Выносливость', 'Живучесть', 'Интеллект', 'Ловкость', 'Поиск предметов', 'Сила', 'Сопротивление', 'Уровень', 'Ученость', 'Человечность'] -stats_ds2 = ['Адаптируемость', 'Вера', 'Жизненная сила', 'Интеллект', 'Ловкость', 'Сила', 'Стойкость', 'Уровень', 'Ученость', 'Физическая мощь'] -stats_ds3 = ['Вера', 'Жизненная сила', 'Интеллект', 'Ловкость', 'Сила', 'Стойкость', 'Удача', 'Уровень', 'Ученость', 'Физическая мощь'] - -stats_ds1 = set(stats_ds1) -stats_ds2 = set(stats_ds2) -stats_ds3 = set(stats_ds3) - -common_stats = sorted(stats_ds1 & stats_ds2 & stats_ds3) -print(common_stats) # ['Вера', 'Интеллект', 'Ловкость', 'Сила', 'Уровень', 'Ученость'] -print() - -unique_ds1 = sorted(stats_ds1 - stats_ds2 - stats_ds3) -print(unique_ds1) # ['Выносливость', 'Живучесть', 'Поиск предметов', 'Сопротивление', 'Человечность'] - -unique_ds2 = sorted(stats_ds2 - stats_ds1 - stats_ds3) -print(unique_ds2) # ['Адаптируемость'] - -unique_ds3 = sorted(stats_ds3 - stats_ds1 - stats_ds2) -print(unique_ds3) # ['Удача'] diff --git a/Dark Souls/ru_darksouls_wikia_com__fandom__bosses/config.py b/Dark Souls/ru_darksouls_wikia_com__fandom__bosses/config.py deleted file mode 100644 index d83a6af15..000000000 --- a/Dark Souls/ru_darksouls_wikia_com__fandom__bosses/config.py +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -__author__ = 'ipetrash' - - -URL_DS1 = 'https://darksouls.fandom.com/ru/wiki/Боссы' -URL_DS2 = 'https://darksouls.fandom.com/ru/wiki/Боссы_(Dark_Souls_II)' -URL_DS3 = 'https://darksouls.fandom.com/ru/wiki/Боссы_(Dark_Souls_III)' diff --git a/Dark Souls/ru_darksouls_wikia_com__fandom__bosses/main.py b/Dark Souls/ru_darksouls_wikia_com__fandom__bosses/main.py deleted file mode 100644 index 4dc2f6da9..000000000 --- a/Dark Souls/ru_darksouls_wikia_com__fandom__bosses/main.py +++ /dev/null @@ -1,61 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -__author__ = 'ipetrash' - - -import sqlite3 - -from config import URL_DS1, URL_DS2, URL_DS3 -from utils import ( - get_bosses, print_bosses, convert_bosses_to_only_name, - export_to_json, export_to_sqlite, -) - - -bosses_ds1 = get_bosses(URL_DS1) -print_bosses(URL_DS1, bosses_ds1) -export_to_json('dumps/ds1/bosses.json', bosses_ds1) -export_to_json('dumps/ds1/bosses__only_name.json', convert_bosses_to_only_name(bosses_ds1)) - -bosses_ds2 = get_bosses(URL_DS2) -print_bosses(URL_DS2, bosses_ds2) -export_to_json('dumps/ds2/bosses.json', bosses_ds2) -export_to_json('dumps/ds2/bosses__only_name.json', convert_bosses_to_only_name(bosses_ds2)) - -bosses_ds3 = get_bosses(URL_DS3) -print_bosses(URL_DS3, bosses_ds3) -export_to_json('dumps/ds3/bosses.json', bosses_ds3) -export_to_json('dumps/ds3/bosses__only_name.json', convert_bosses_to_only_name(bosses_ds3)) - -# All bosses -bosses_ds123 = { - 'Dark Souls': bosses_ds1, - 'Dark Souls II': bosses_ds2, - 'Dark Souls III': bosses_ds3, -} -export_to_json('dumps/bosses_ds123.json', bosses_ds123) - -bosses_ds123__only_name = { - 'Dark Souls': convert_bosses_to_only_name(bosses_ds1), - 'Dark Souls II': convert_bosses_to_only_name(bosses_ds2), - 'Dark Souls III': convert_bosses_to_only_name(bosses_ds3), -} -export_to_json('dumps/bosses_ds123__only_name.json', bosses_ds123__only_name) - -# -# SQLITE -# -sql_file_name = 'dumps/bosses_ds123.sqlite' -export_to_sqlite(sql_file_name, bosses_ds123) - -# TEST -connect = sqlite3.connect(sql_file_name) -print('Total boss:', connect.execute('SELECT count(*) FROM BOSS').fetchone()[0]) -print('Total boss DS1:', connect.execute('SELECT count(*) FROM BOSS WHERE game = "Dark Souls"').fetchone()[0]) -print('Total boss DS2:', connect.execute('SELECT count(*) FROM BOSS WHERE game = "Dark Souls II"').fetchone()[0]) -print('Total boss DS3:', connect.execute('SELECT count(*) FROM BOSS WHERE game = "Dark Souls III"').fetchone()[0]) -# Total boss: 92 -# Total boss DS1: 26 -# Total boss DS2: 41 -# Total boss DS3: 25 diff --git a/Dark Souls/ru_darksouls_wikia_com__fandom__bosses/print_stats_by_health_and_souls/_utils.py b/Dark Souls/ru_darksouls_wikia_com__fandom__bosses/print_stats_by_health_and_souls/_utils.py deleted file mode 100644 index effc7640f..000000000 --- a/Dark Souls/ru_darksouls_wikia_com__fandom__bosses/print_stats_by_health_and_souls/_utils.py +++ /dev/null @@ -1,241 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -__author__ = 'ipetrash' - - -import re -import sys -from typing import Union, Dict, List, Tuple -from pathlib import Path - -DIR = Path(__file__).resolve().parent - -sys.path.append(str(DIR.parent)) -sys.path.append(str(DIR.parent.parent)) -sys.path.append(str(DIR.parent.parent.parent)) - -from config import URL_DS1, URL_DS2, URL_DS3 -from utils import Boss, get_bosses - -from ascii_table__simple_pretty__format import print_pretty_table - - -def only_digits(text: Union[str, int], do_sum=None) -> int: - if not isinstance(text, str): - return text - - if isinstance(do_sum, str): - return sum(only_digits(x) for x in text.split(do_sum)) - - return int(re.sub(r'\D', '', text)) - - -def get_health(boss: Boss, items: Dict[str, List[str]]) -> int: - health_items = items['Здоровье'] - - boss_name = boss.name.lower() - - if boss_name == 'горгулья': - return only_digits(health_items[0], do_sum='+') - - if boss_name == 'орнштейн и смоуг': - return only_digits(health_items[1].split('&')[0], do_sum='/') - - # 3x 2,330 - if boss_name == 'стражи руин': - h = health_items[0].split(' ')[-1] - return 3 * only_digits(h) - - if boss_name in ['нагой сит', 'присцилла полукровка', 'черный дракон каламит']: - return only_digits(health_items[1], do_sum='/') - - if boss_name == 'страж святилища': - parts = health_items[1].split('/') - return only_digits(parts[0]) + only_digits(parts[2]) - - if boss_name == 'алчный демон': - return only_digits(health_items[1]) - - if boss_name in ['преследователь', 'знаток кристальных чар']: - return only_digits(health_items[2]) - - if boss_name == 'гибкий часовой': - return only_digits(health_items[3]) - - if 'заллен' in boss_name and 'луд' in boss_name: - return only_digits(health_items[0], do_sum='/') - - if boss_name == 'душа пепла': - first_idx = health_items.index('Первая фаза:') - second_idx = health_items.index('Вторая Фаза:') - return ( - only_digits(health_items[first_idx + 1]) - + only_digits(health_items[second_idx + 1]) - ) - - if boss_name == 'два драконьих всадника': - first_idx = health_items.index('Лучник:') - second_idx = health_items.index('Воин:') - return ( - only_digits(health_items[first_idx + 1]) - + only_digits(health_items[second_idx + 1]) - ) - - if boss_name == 'защитник трона и смотритель трона': - first_idx = health_items.index('Защитник:') - second_idx = health_items.index('Смотритель:') - return ( - only_digits(health_items[first_idx + 1]) - + only_digits(health_items[second_idx + 1]) - ) - - if boss_name == 'копье церкви': - return only_digits(health_items[2]) - - if boss_name == 'хранители бездны': - first_idx = health_items.index('Первая Фаза') - second_idx = health_items.index('Вторая Фаза') - return ( - only_digits(health_items[first_idx + 2]) - + only_digits(health_items[second_idx + 2]) - ) - - if boss_name == 'лотрик, младший принц и лориан, старший принц': - first_idx = health_items.index('Первая Фаза') - second_idx = health_items.index('Вторая Фаза') - lothric_idx = health_items.index('Лотрик') - return ( - only_digits(health_items[first_idx + 2]) - + only_digits(health_items[second_idx + 2]) - + only_digits(health_items[lothric_idx + 1]) - ) - - if boss_name == 'странствующий маг и прихожане': - first_idx = health_items.index('Странствующий маг:') - second_idx = health_items.index('Жрец:') - third_idx = health_items.index('Полый проситель:') - return ( - only_digits(health_items[first_idx + 1]) - + only_digits(health_items[second_idx + 1]) - + only_digits(health_items[third_idx + 1]) - ) - - if boss_name == 'повелители скелетов': - first_idx = health_items.index('Повелитель-воин:') - second_idx = health_items.index('Повелитель-маг:') - third_idx = health_items.index('Повелитель-жнец:') - return ( - only_digits(health_items[first_idx + 1]) - + only_digits(health_items[second_idx + 1]) - + only_digits(health_items[third_idx + 1]) - ) - - if boss_name == 'варг, церах и разорительница гробниц': - first_idx = health_items.index('Скорбящая разорительница гробниц:') - second_idx = health_items.index('Древний солдат Варг:') - third_idx = health_items.index('Старый исследователь Церах:') - return ( - only_digits(health_items[first_idx + 1]) - + only_digits(health_items[second_idx + 1]) - + only_digits(health_items[third_idx + 1]) - ) - - if boss_name == 'безымянный король': - first_idx = health_items.index('Повелитель Шторма') - second_idx = health_items.index('Безымянный Король') - return ( - only_digits(health_items[first_idx + 2]) - + only_digits(health_items[second_idx + 2]) - ) - - if boss_name == 'хранитель могилы чемпиона и великий волк': - first_idx = health_items.index('Хранитель') - second_idx = health_items.index('Великий волк') - return ( - only_digits(health_items[first_idx + 2]) - + only_digits(health_items[second_idx + 2]) - ) - - if boss_name == 'отец ариандель и сестра фриде': - first_idx = health_items.index('Сестра Фриде') - second_idx = health_items.index('Отец Ариандель и сестра Фриде') - third_idx = health_items.index('Черное пламя Фриде') - return ( - only_digits(health_items[first_idx + 2]) - + only_digits(health_items[second_idx + 2]) - + only_digits(health_items[third_idx + 2]) - ) - - if boss_name == 'демон-принц': - first_idx = health_items.index('Демон в агонии/Демон из глубин') - second_idx = health_items.index('Демон-принц') - return ( - only_digits(health_items[first_idx + 2], do_sum='/') - + only_digits(health_items[second_idx + 2]) - ) - - health_value = health_items[0] - return only_digits(health_value) - - -def get_souls(boss: Boss, items: Dict[str, List[str]]) -> int: - souls_items = items['Души'] - - boss_name = boss.name.lower() - - if boss_name == 'страж святилища': - return only_digits(souls_items[1].split('/')[1]) - - if boss_name in ['преследователь', 'знаток кристальных чар']: - return only_digits(souls_items[2]) - - if boss_name == 'гибкий часовой': - return only_digits(souls_items[3]) - - if boss_name == 'алдия, ученый первородного греха': - return 0 - - if 'заллен' in boss_name and 'луд' in boss_name: - return only_digits(souls_items[0].split('/')[0]) - - souls_value = souls_items[0] - return only_digits(souls_value) - - -def parse(url: str, debug_log=False) -> List[Tuple[str, int, int]]: - rows = [] - - for category, bosses in get_bosses(url).items(): - debug_log and print(category) - - for boss in bosses: - items = boss.get_boss_items() - try: - health = get_health(boss, items) - souls = get_souls(boss, items) - debug_log and print(f" {boss.name}. Здоровье: {health}, души: {souls}") - - rows.append((boss.name, health, souls)) - - except ValueError as e: - debug_log and print(e) - debug_log and print(boss.name) - debug_log and print(items['Здоровье']) - debug_log and print(items['Души']) - debug_log and sys.exit() - - debug_log and print() - - return rows - - -def print_stats( - rows: List[Tuple[str, int, int]], - headers=('NAME', 'HEALTH', 'SOULS'), - sort_column=1, -): - rows.sort(key=lambda x: x[sort_column], reverse=True) - - rows.insert(0, headers) - print_pretty_table(rows, align='<') diff --git a/DecodingPhoneNumberForCLI/DecodingPhoneNumberForCLI.py b/DecodingPhoneNumberForCLI/DecodingPhoneNumberForCLI.py index 509b1e6cd..624a712b0 100644 --- a/DecodingPhoneNumberForCLI/DecodingPhoneNumberForCLI.py +++ b/DecodingPhoneNumberForCLI/DecodingPhoneNumberForCLI.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """ EN: Decoding the phone number for CLI. @@ -19,7 +19,7 @@ # Например, входящая строка 4434###552222311333661 соответствует номеру 4452136 -def decoding_phone_number(string): +def decoding_phone_number(string: str) -> str: string += " " result = "" last = "" @@ -39,7 +39,9 @@ def decoding_phone_number(string): repeat += 1 else: # Если символ не такой же как предыдущий if repeat > 1: # Если символ имеет повторы - if last is "#" and symbol_before_grill: # Если последним символов является # и ... + if ( + last is "#" and symbol_before_grill + ): # Если последним символов является # и ... result += symbol_before_grill # Добавим число, перед #, но саму # добавлять не будем symbol_before_grill = "" else: @@ -51,7 +53,7 @@ def decoding_phone_number(string): return result -if __name__ == '__main__': - string = '4434###552222311333661' - print('CLI = ' + string) +if __name__ == "__main__": + string = "4434###552222311333661" + print("CLI = " + string) print("Phone number = " + decoding_phone_number(string)) diff --git a/Decorators__examples/append_attributes_to_class.py b/Decorators__examples/append_attributes_to_class.py index 61fe0060d..d562c1a37 100644 --- a/Decorators__examples/append_attributes_to_class.py +++ b/Decorators__examples/append_attributes_to_class.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" def attrs(**kwargs): @@ -22,4 +22,7 @@ class Foo: print(Foo.author) # Guido van Rossum print(Foo().author) # Guido van Rossum print(Foo().version) # 2.2 -print(list(filter(lambda x: not x.startswith('_'), dir(Foo())))) # ['author', 'version'] +print( + list(filter(lambda x: not x.startswith("_"), dir(Foo()))) +) +# ['author', 'version'] diff --git a/Decorators__examples/append_attributes_to_func.py b/Decorators__examples/append_attributes_to_func.py index bc2dc6428..dbd289174 100644 --- a/Decorators__examples/append_attributes_to_func.py +++ b/Decorators__examples/append_attributes_to_func.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" def attrs(**kwargs): @@ -14,10 +14,10 @@ def decorate(f): @attrs(versionadded="2.2", author="Guido van Rossum") -def mymethod(text='ok'): +def mymethod(text="ok"): return text print(mymethod.author) # Guido van Rossum print(mymethod()) # ok -print(mymethod('no')) # ok +print(mymethod("no")) # ok diff --git a/Decorators__examples/append_handlers.py b/Decorators__examples/append_handlers.py index 4bfadcb5b..c44b19bfd 100644 --- a/Decorators__examples/append_handlers.py +++ b/Decorators__examples/append_handlers.py @@ -1,17 +1,18 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" + + +from collections import defaultdict class Collector: - def __init__(self): + def __init__(self) -> None: self.handlers = [] - - from collections import defaultdict self.handlers_by_name = defaultdict(list) - def add(self, name='default'): + def add(self, name="default"): def decorator(func): self.handlers.append(func) self.handlers_by_name[name].append(func) @@ -24,27 +25,29 @@ def decorator(func): collector = Collector() -@collector.add(name='test') -def hello_world(end='!'): - print('hello world' + end) +@collector.add(name="test") +def hello_world(end="!") -> None: + print("hello world" + end) print(collector.handlers) # [] -print(collector.handlers_by_name) # defaultdict(, {'test': []}) +print(collector.handlers_by_name) +# defaultdict(, {'test': []}) -hello_world('!!!') # hello world!!! +hello_world("!!!") # hello world!!! hello_world() # hello world! print(collector.handlers) # [] collector.handlers[0]() # hello world! -@collector.add(name='this it say_hello!') -def say_hello(): - print('hello!') +@collector.add(name="this it say_hello!") +def say_hello() -> None: + print("hello!") print() -print(collector.handlers) # [, ] +print(collector.handlers) +# [, ] print(len(collector.handlers_by_name)) # 2 for func in collector.handlers: func() diff --git a/Decorators__examples/combine_decorators.py b/Decorators__examples/combine_decorators.py index a5d8eb9d3..86677b0d6 100644 --- a/Decorators__examples/combine_decorators.py +++ b/Decorators__examples/combine_decorators.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import functools @@ -36,6 +36,7 @@ def deco(f): for dec in reversed(decs): f = dec(f) return f + return deco @@ -59,8 +60,8 @@ def hello_2(text): return text -print(hello('Hello World!')) +print(hello("Hello World!")) # HELLO WORLD! -print(hello_2('Hello World!')) +print(hello_2("Hello World!")) # HELLO WORLD! diff --git a/Decorators__examples/combine_decorators__with_args.py b/Decorators__examples/combine_decorators__with_args.py index cbdf4a2db..6e71ff93a 100644 --- a/Decorators__examples/combine_decorators__with_args.py +++ b/Decorators__examples/combine_decorators__with_args.py @@ -1,16 +1,16 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import functools def get_attrs_str(kwargs: dict) -> str: - attrs = '' + attrs = "" if kwargs: - attrs = ' ' + ' '.join(f'{k}="{v}"' for k, v in kwargs.items()) + attrs = " " + " ".join(f'{k}="{v}"' for k, v in kwargs.items()) return attrs @@ -18,9 +18,9 @@ def get_attrs_str(kwargs: dict) -> str: def makebold(**decorator_kwargs): def actual_decorator(func): @functools.wraps(func) - def wrapped(*args, **kwargs): + def wrapped(*args, **kwargs) -> str: attrs = get_attrs_str(decorator_kwargs) - return f"" + func(*args, **kwargs) + "" + return f"{func(*args, **kwargs)}" return wrapped @@ -30,9 +30,9 @@ def wrapped(*args, **kwargs): def makeitalic(**decorator_kwargs): def actual_decorator(func): @functools.wraps(func) - def wrapped(*args, **kwargs): + def wrapped(*args, **kwargs) -> str: attrs = get_attrs_str(decorator_kwargs) - return f"" + func(*args, **kwargs) + "" + return f"{func(*args, **kwargs)}" return wrapped @@ -47,22 +47,48 @@ def wrapped(*args, **kwargs): return wrapped +def custom_tag( + name: str, + **arguments, +): + def actual_decorator(func): + @functools.wraps(func) + def wrapped(*args, **kwargs) -> str: + attrs = get_attrs_str(arguments) + return f"<{name}{attrs}>{func(*args, **kwargs)}" + + return wrapped + + return actual_decorator + + def composed(*decs): def deco(f): for dec in reversed(decs): f = dec(f) return f + return deco def multi(func): return composed( + custom_tag( + name="a", + href="https://example.com", + title="Hint!", + ), makebold(foo="1"), makeitalic(bar="2"), upper, )(func) +@custom_tag( + name="a", + href="https://example.com", + title="Hint!", +) @makebold(foo="1") @makeitalic(bar="2") @upper @@ -75,8 +101,9 @@ def hello_2(text): return text -print(hello('Hello World!')) -# HELLO WORLD! +if __name__ == '__main__': + print(hello("Hello World!")) + # HELLO WORLD! -print(hello_2('Hello World!')) -# HELLO WORLD! + print(hello_2("Hello World!")) + # HELLO WORLD! diff --git a/Decorators__examples/decorator__args_as_funcs.py b/Decorators__examples/decorator__args_as_funcs.py index 93655d075..b6b27499f 100644 --- a/Decorators__examples/decorator__args_as_funcs.py +++ b/Decorators__examples/decorator__args_as_funcs.py @@ -1,11 +1,11 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" class TextBuilder: - def __init__(self): + def __init__(self) -> None: self.result = [] # Функция, принимающая аргументы и возвращающая декоратор @@ -22,7 +22,7 @@ def wrapper(self, *args, **kwargs): # Декоратор возвращает обертку return wrapper - # Возаращаем сам декоратор + # Возвращаем сам декоратор return decorator # Функция, принимающая аргументы и возвращающая декоратор @@ -41,17 +41,20 @@ def wrapper(self, *args, **kwargs): # Декоратор возвращает обертку return wrapper - # Возаращаем сам декоратор + # Возвращаем сам декоратор return decorator - @_call_before(lambda self: self.result.append('+' + '-' * 10 + '+')) - @_call_after(lambda self: self.result.append('+' + '-' * 10 + '+'), lambda self: self.result.append('\n')) - def append(self, text: str) -> 'TextBuilder': + @_call_before(lambda self: self.result.append("+" + "-" * 10 + "+")) + @_call_after( + lambda self: self.result.append("+" + "-" * 10 + "+"), + lambda self: self.result.append("\n"), + ) + def append(self, text: str) -> "TextBuilder": self.result.append(text) return self def build(self): - return '\n'.join(self.result) + return "\n".join(self.result) builder = TextBuilder() diff --git a/Decorators__examples/decorator_method_class__with_shelve.py b/Decorators__examples/decorator_method_class__with_shelve.py new file mode 100644 index 000000000..5ff1f1429 --- /dev/null +++ b/Decorators__examples/decorator_method_class__with_shelve.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import shelve +import functools + +from pathlib import Path +from typing import Any + + +DIR: Path = Path(__file__).resolve().parent +DIR_DB: Path = DIR / "databases" +DB_FILE_NAME: Path = DIR_DB / "db.shelve" + + +DIR_DB.mkdir(parents=True, exist_ok=True) + + +class DB: + db_name: str = str(DB_FILE_NAME) + + def __init__(self) -> None: + self.db: shelve.Shelf | None = None + + def session(*decorator_args, **decorator_kwargs): + def actual_decorator(func): + @functools.wraps(func) + def wrapped(self, *args, **kwargs): + has_db: bool = self.db is not None + try: + if not has_db: + self.db = shelve.open(self.db_name, writeback=True) + return func(self, *args, **kwargs) + finally: + if not has_db and self.db is not None: + self.db.close() + self.db = None + + return wrapped + + return actual_decorator + + @session() + def get_value(self, name: str, default: Any = None) -> Any: + if not name: + return dict(self.db) + + if name not in self.db: + return default + return self.db.get(name) + + @session() + def set_value(self, name: str, value: Any) -> None: + self.db[name] = value + + def inc_value(self, name: str) -> int: + value = self.get_value(name, default=0) + value += 1 + self.set_value(name, value) + return value + + +if __name__ == "__main__": + db = DB() + print("name", db.get_value("name")) + + db.set_value("name", 123) + + users: dict[str, dict[str, Any]] = db.get_value("users", default=dict()) + print("users", users) + if not users: + users["Foo"] = dict(name="Foo", age=12) + users["Bar"] = dict(name="Bar", age=12) + db.set_value("users", users) + + counter: dict[str, int] = db.get_value("counter", default=dict()) + print("counter", counter) + if "value" not in counter: + counter["value"] = 0 + counter["value"] += 1 + db.set_value("counter", counter) + + print([db.inc_value("age") for _ in range(3)]) + + print(dict(db.get_value(""))) diff --git a/Decorators__examples/decorator_with_args_in_partial.py b/Decorators__examples/decorator_with_args_in_partial.py new file mode 100644 index 000000000..f7ab35865 --- /dev/null +++ b/Decorators__examples/decorator_with_args_in_partial.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from functools import partial +from combine_decorators__with_args import custom_tag + + +@custom_tag( + name="a", + href="https://example.com", + title="Hint!", +) +def hello(text): + return text + + +custom_tag_a = partial( + custom_tag, + name="a", + href="https://example.com", + title="Hint!", +) + + +@custom_tag_a() +def hello2(text): + return text + + +@custom_tag_a( + title="Other hint!", +) +def hello3(text): + return text + + +print(hello("Foo")) +# Foo + +print(hello2("Foo")) +# Foo + +print(hello3("Foo")) +# Foo diff --git a/Decorators__examples/example_1.py b/Decorators__examples/example_1.py index 511764c42..e7d123809 100644 --- a/Decorators__examples/example_1.py +++ b/Decorators__examples/example_1.py @@ -1,77 +1,90 @@ -__author__ = 'ipetrash' - - -if __name__ == '__main__': - def getprint(str="hello world!"): - print(str) - - def decor(func): - def wrapper(*args, **kwargs): - print("1 begin: " + func.__name__) - print("Args={} kwargs={}".format(args, kwargs)) - f = func(*args, **kwargs) - print("2 end: " + func.__name__ + "\n") - return f - return wrapper - - def predecor(w="W"): - print(w, end=': ') - - getprint() - getprint("Py!") - print() - f = decor(getprint) - f() - f("Py!") - - - def rgb2hex(get_rgb_func): - def wrapper(*args, **kwargs): - r, g, b = get_rgb_func(*args, **kwargs) - return '#{:02x}{:02x}{:02x}'.format(r, g, b) - return wrapper - - class RGB: - def __init__(self): - self._r = 0xff - self._g = 0xff - self._b = 0xff - - def getr(self): - return self._r - def setr(self, r): - self._r = r - r = property(getr, setr) - - def getg(self): - return self._g - def setg(self, g): - self._g = g - g = property(getg, setg) - - def getb(self): - return self._b - def setb(self, b): - self._b = b - b = property(getb, setb) - - def setrgb(self, r, g, b): - self.r, self.g, self.b = r, g, b - - @rgb2hex - def getrgb(self): - return (self.r, self.g, self.b) - - - rgb = RGB() - print('rgb.r={}'.format(rgb.r)) - rgb.setrgb(0xff, 0x1, 0xff) - print("rgb.getrgb(): %s" % rgb.getrgb()) - print() - - @decor - def foo(a, b): - print("{} ^ {} = {}".format(a, b, (a ** b))) - - foo(2, 3) - foo(b=3, a=2) \ No newline at end of file +__author__ = "ipetrash" + + +def getprint(str="hello world!") -> None: + print(str) + + +def decor(func): + def wrapper(*args, **kwargs): + print("1 begin: " + func.__name__) + print(f"Args={args} kwargs={kwargs}") + f = func(*args, **kwargs) + print("2 end: " + func.__name__ + "\n") + return f + + return wrapper + + +def predecor(w="W") -> None: + print(w, end=": ") + + +getprint() +getprint("Py!") +print() +f = decor(getprint) +f() +f("Py!") + + +def rgb2hex(get_rgb_func): + def wrapper(*args, **kwargs) -> str: + r, g, b = get_rgb_func(*args, **kwargs) + return f"#{r:02x}{g:02x}{b:02x}" + + return wrapper + + +class RGB: + def __init__(self) -> None: + self._r = 0xFF + self._g = 0xFF + self._b = 0xFF + + def getr(self): + return self._r + + def setr(self, r) -> None: + self._r = r + + r = property(getr, setr) + + def getg(self): + return self._g + + def setg(self, g) -> None: + self._g = g + + g = property(getg, setg) + + def getb(self): + return self._b + + def setb(self, b) -> None: + self._b = b + + b = property(getb, setb) + + def setrgb(self, r, g, b) -> None: + self.r, self.g, self.b = r, g, b + + @rgb2hex + def getrgb(self): + return (self.r, self.g, self.b) + + +rgb = RGB() +print(f"rgb.r={rgb.r}") +rgb.setrgb(0xFF, 0x1, 0xFF) +print(f"rgb.getrgb(): {rgb.getrgb()}") +print() + + +@decor +def foo(a, b) -> None: + print(f"{a} ^ {b} = {(a ** b)}") + + +foo(2, 3) +foo(b=3, a=2) diff --git a/Decorators__examples/hello_world.py b/Decorators__examples/hello_world.py index bcef31853..38ae2dfa6 100644 --- a/Decorators__examples/hello_world.py +++ b/Decorators__examples/hello_world.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import functools @@ -38,7 +38,7 @@ def hello(text): return text -print(hello('Hello World!')) +print(hello("Hello World!")) # HELLO WORLD! -assert hello('Hello World!') == 'HELLO WORLD!' +assert hello("Hello World!") == "HELLO WORLD!" diff --git a/Decorators__examples/hello_world__async.py b/Decorators__examples/hello_world__async.py new file mode 100644 index 000000000..69c05d92d --- /dev/null +++ b/Decorators__examples/hello_world__async.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import asyncio +import functools + + +def makebold(func): + @functools.wraps(func) + async def wrapped(*args, **kwargs): + return "" + await func(*args, **kwargs) + "" + + return wrapped + + +def makeitalic(func): + @functools.wraps(func) + async def wrapped(*args, **kwargs): + return "" + await func(*args, **kwargs) + "" + + return wrapped + + +def upper(func): + @functools.wraps(func) + async def wrapped(*args, **kwargs): + return (await func(*args, **kwargs)).upper() + + return wrapped + + +@makebold +@makeitalic +@upper +async def hello(text): + return text + + +loop = asyncio.new_event_loop() + +print(loop.run_until_complete(hello("Hello World!"))) +# HELLO WORLD! + +assert loop.run_until_complete(hello("Hello World!")) == "HELLO WORLD!" diff --git a/Decorators__examples/memoize_class.py b/Decorators__examples/memoize_class.py index 0122fb33c..3654b2225 100644 --- a/Decorators__examples/memoize_class.py +++ b/Decorators__examples/memoize_class.py @@ -1,12 +1,12 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # Using memoization as decorator (decorator-class) class MemoizeClass: - def __init__(self, func): + def __init__(self, func) -> None: self.func = func self.memo = dict() diff --git a/Decorators__examples/memoize_func.py b/Decorators__examples/memoize_func.py index e66a3095b..d2494ede1 100644 --- a/Decorators__examples/memoize_func.py +++ b/Decorators__examples/memoize_func.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # Using memoization as decorator (decorator-function) diff --git a/Decorators__examples/requests__append_attempts.py b/Decorators__examples/requests__append_attempts.py new file mode 100644 index 000000000..2919a37a8 --- /dev/null +++ b/Decorators__examples/requests__append_attempts.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import functools +import time + +import requests +from requests.exceptions import RequestException + + +def attempts( + max_number: int = 5, + sleep: int = 30, + ignored_exceptions: tuple[type(Exception)] = (Exception,), +): + def actual_decorator(func): + @functools.wraps(func) + def wrapped(*args, **kwargs): + number = 0 + while True: + try: + print("\nGO", args, kwargs) + return func(*args, **kwargs) + except Exception as e: + number += 1 + print(f"ERROR on {number}/{max_number}: {e}") + + if number >= max_number or not isinstance(e, ignored_exceptions): + raise e + + print(f"Sleep {sleep} seconds") + time.sleep(sleep) + + return wrapped + + return actual_decorator + + +@attempts( + max_number=3, + sleep=5, + ignored_exceptions=(RequestException,), +) +def do_get(url: str, *args, **kwargs) -> requests.Response: + rs = requests.get(url, *args, **kwargs) + rs.raise_for_status() + + return rs + + +print(do_get("https://google.com")) +""" +GO ('https://google.com',) {} + +""" + +print(do_get("http://sdfsdfs.dfsdf")) +""" +GO ('http://sdfsdfs.dfsdf',) {} +ERROR on 1/3: HTTPConnectionPool(host='sdfsdfs.dfsdf', port=80): Max retries exceeded with url: / (Caused by NewConnectionError(': Failed to establish a new connection: [Errno 11001] getaddrinfo failed')) +Sleep 5 seconds + +GO ('http://sdfsdfs.dfsdf',) {} +ERROR on 2/3: HTTPConnectionPool(host='sdfsdfs.dfsdf', port=80): Max retries exceeded with url: / (Caused by NewConnectionError(': Failed to establish a new connection: [Errno 11001] getaddrinfo failed')) +Sleep 5 seconds + +GO ('http://sdfsdfs.dfsdf',) {} +ERROR on 3/3: HTTPConnectionPool(host='sdfsdfs.dfsdf', port=80): Max retries exceeded with url: / (Caused by NewConnectionError(': Failed to establish a new connection: [Errno 11001] getaddrinfo failed')) +Traceback (most recent call last): + ... +socket.gaierror: [Errno 11001] getaddrinfo failed + +During handling of the above exception, another exception occurred: + +Traceback (most recent call last): + ... +urllib3.exceptions.NewConnectionError: : Failed to establish a new connection: [Errno 11001] getaddrinfo failed + +During handling of the above exception, another exception occurred: + +Traceback (most recent call last): + ... +urllib3.exceptions.MaxRetryError: HTTPConnectionPool(host='sdfsdfs.dfsdf', port=80): Max retries exceeded with url: / (Caused by NewConnectionError(': Failed to establish a new connection: [Errno 11001] getaddrinfo failed')) + +During handling of the above exception, another exception occurred: + +Traceback (most recent call last): + ... + raise ConnectionError(e, request=request) +requests.exceptions.ConnectionError: HTTPConnectionPool(host='sdfsdfs.dfsdf', port=80): Max retries exceeded with url: / (Caused by NewConnectionError(': Failed to establish a new connection: [Errno 11001] getaddrinfo failed')) +""" diff --git a/Decorators__examples/thread.py b/Decorators__examples/thread.py index 6002a9718..70426ff29 100644 --- a/Decorators__examples/thread.py +++ b/Decorators__examples/thread.py @@ -1,32 +1,33 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" +import time from threading import Thread def thread(my_func): - def wrapper(*args, **kwargs): + def wrapper(*args, **kwargs) -> None: my_thread = Thread(target=my_func, args=args, kwargs=kwargs) my_thread.start() + return wrapper -if __name__ == '__main__': +if __name__ == "__main__": @thread - def _print_and_sleep(timeout=2): - print('start. print_and_sleep') - import time + def _print_and_sleep(timeout=2) -> None: + print("start. print_and_sleep") + time.sleep(timeout) - print('finish. print_and_sleep') + print("finish. print_and_sleep") @thread - def _print_loop(name, max_num=10): - print('start. _print_loop') + def _print_loop(name, max_num=10) -> None: + print("start. _print_loop") - import time i = 0 while True: @@ -37,9 +38,9 @@ def _print_loop(name, max_num=10): if i == max_num: break - print('finish. _print_loop') + print("finish. _print_loop") _print_and_sleep() _print_and_sleep(4) _print_and_sleep() - _print_loop(' loop') + _print_loop(" loop") diff --git a/Decorators__examples/timer.py b/Decorators__examples/timer.py index 8e2642c7f..c441a32ad 100644 --- a/Decorators__examples/timer.py +++ b/Decorators__examples/timer.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import time @@ -13,12 +13,13 @@ def wrapper(*args, **kwargs): r = f(*args, **kwargs) print("Время выполнения функции: %f сек." % (time.time() - t)) return r + return wrapper -if __name__ == '__main__': +if __name__ == "__main__": @timer - def my_sleep(): + def my_sleep() -> None: print(123) time.sleep(0.3) print(456) @@ -29,6 +30,6 @@ def my_sleep(): @timer def my_foo(): - return [i for i in range(10 ** 7) if i % 2 == 0] + return [i for i in range(10**7) if i % 2 == 0] print(my_foo()[:20]) diff --git a/Decorators__examples/try_repeat.py b/Decorators__examples/try_repeat.py index ca2e834ca..a2bc6a507 100644 --- a/Decorators__examples/try_repeat.py +++ b/Decorators__examples/try_repeat.py @@ -1,11 +1,15 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" + + +import random # http://ru.stackoverflow.com/a/491681/201445 + def try_repeat(func): def wrapper(*args, **kwargs): count = 10 @@ -14,7 +18,7 @@ def wrapper(*args, **kwargs): try: return func(*args, **kwargs) except Exception as e: - print('Error:', e) + print("Error:", e) count -= 1 return wrapper @@ -22,9 +26,8 @@ def wrapper(*args, **kwargs): @try_repeat def exception_func(): - import random if random.randint(0, 1): - raise Exception('!!!') + raise Exception("!!!") exception_func() diff --git "a/Demons Souls/demonssouls_fandom_com__ru__wiki__\320\232\320\273\320\260\321\201\321\201\321\213.py" "b/Demons Souls/demonssouls_fandom_com__ru__wiki__\320\232\320\273\320\260\321\201\321\201\321\213.py" deleted file mode 100644 index af3cf673a..000000000 --- "a/Demons Souls/demonssouls_fandom_com__ru__wiki__\320\232\320\273\320\260\321\201\321\201\321\213.py" +++ /dev/null @@ -1,77 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -__author__ = 'ipetrash' - - -from typing import Dict - -from bs4 import BeautifulSoup -import requests - - -def get_class_by_stats() -> Dict[str, Dict[str, int]]: - url = 'https://demonssouls.fandom.com/ru/wiki/Классы' - - rs = requests.get(url) - root = BeautifulSoup(rs.content, 'html.parser') - - class_by_stats = dict() - - for tr in root.select('table > tr'): - td_list = [td.get_text(strip=True) for td in tr.select('td')] - if not td_list: - continue - - class_name = td_list[0] - stats = list(map(int, td_list[1:])) - - class_by_stats[class_name] = { - "Жизненная сила": stats[0], - "Интеллект": stats[1], - "Стойкость": stats[2], - "Сила": stats[3], - "Ловкость": stats[4], - "Магия": stats[5], - "Вера": stats[6], - "Удача": stats[7], - "Уровень души": stats[8], - } - - return class_by_stats - - -if __name__ == '__main__': - class_by_stats = get_class_by_stats() - print(class_by_stats) - - print() - - for class_name, stats in sorted(class_by_stats.items(), key=lambda x: x[1]["Уровень души"]): - total_stats = sum(list(stats.values())[:-1]) - print(f'{class_name} (level={stats["Уровень души"]}), total stats: {total_stats}') - # Дворянин (level=1), total stats: 81 - # Рыцарь (level=4), total stats: 84 - # Храмовник (level=4), total stats: 84 - # Солдат (level=6), total stats: 86 - # Охотник (level=6), total stats: 86 - # Жрец (level=6), total stats: 86 - # Маг (level=6), total stats: 86 - # Странник (level=6), total stats: 86 - # Варвар (level=9), total stats: 89 - # Вор (level=9), total stats: 89 - - print() - - for class_name, stats in class_by_stats.items(): - print(f'{class_name}: {stats}') - # Солдат: {'Жизненная сила': 14, 'Интеллект': 9, 'Стойкость': 12, 'Сила': 12, 'Ловкость': 11, 'Магия': 8, 'Вера': 10, 'Удача': 10, 'Уровень души': 6} - # Рыцарь: {'Жизненная сила': 10, 'Интеллект': 11, 'Стойкость': 11, 'Сила': 14, 'Ловкость': 10, 'Магия': 10, 'Вера': 11, 'Удача': 7, 'Уровень души': 4} - # Охотник: {'Жизненная сила': 12, 'Интеллект': 10, 'Стойкость': 13, 'Сила': 11, 'Ловкость': 12, 'Магия': 8, 'Вера': 8, 'Удача': 12, 'Уровень души': 6} - # Жрец: {'Жизненная сила': 13, 'Интеллект': 11, 'Стойкость': 12, 'Сила': 13, 'Ловкость': 8, 'Магия': 8, 'Вера': 13, 'Удача': 8, 'Уровень души': 6} - # Маг: {'Жизненная сила': 9, 'Интеллект': 15, 'Стойкость': 10, 'Сила': 9, 'Ловкость': 11, 'Магия': 15, 'Вера': 6, 'Удача': 11, 'Уровень души': 6} - # Странник: {'Жизненная сила': 10, 'Интеллект': 10, 'Стойкость': 11, 'Сила': 11, 'Ловкость': 15, 'Магия': 9, 'Вера': 7, 'Удача': 13, 'Уровень души': 6} - # Варвар: {'Жизненная сила': 15, 'Интеллект': 7, 'Стойкость': 13, 'Сила': 15, 'Ловкость': 9, 'Магия': 11, 'Вера': 8, 'Удача': 11, 'Уровень души': 9} - # Вор: {'Жизненная сила': 10, 'Интеллект': 13, 'Стойкость': 10, 'Сила': 9, 'Ловкость': 14, 'Магия': 10, 'Вера': 8, 'Удача': 15, 'Уровень души': 9} - # Храмовник: {'Жизненная сила': 11, 'Интеллект': 8, 'Стойкость': 13, 'Сила': 14, 'Ловкость': 12, 'Магия': 6, 'Вера': 13, 'Удача': 7, 'Уровень души': 4} - # Дворянин: {'Жизненная сила': 8, 'Интеллект': 12, 'Стойкость': 8, 'Сила': 9, 'Ловкость': 12, 'Магия': 13, 'Вера': 12, 'Удача': 7, 'Уровень души': 1} diff --git a/DownloadURL/DownloadURL.py b/DownloadURL_CLI/DownloadURL.py similarity index 56% rename from DownloadURL/DownloadURL.py rename to DownloadURL_CLI/DownloadURL.py index 8b55f0b98..df6fb3943 100644 --- a/DownloadURL/DownloadURL.py +++ b/DownloadURL_CLI/DownloadURL.py @@ -1,18 +1,20 @@ # coding=utf-8 -import argparse -import urllib2 +__author__ = "ipetrash" + -__author__ = 'ipetrash' +import argparse +from urllib.request import urlopen def create_parser(): parser = argparse.ArgumentParser(description="Download content URL.") return parser -if __name__ == '__main__': + +if __name__ == "__main__": create_parser().parse_args() - url = raw_input("Input url: ") - file = urllib2.urlopen(url) + url = input("Input url: ") + file = urlopen(url) content = file.read() - print(content) \ No newline at end of file + print(content) diff --git a/DownloadURL/README.md b/DownloadURL_CLI/README.md similarity index 100% rename from DownloadURL/README.md rename to DownloadURL_CLI/README.md diff --git a/EncodeStringExample/EncodeStringExample.py b/EncodeStringExample/EncodeStringExample.py index 502e11169..75f52c63b 100644 --- a/EncodeStringExample/EncodeStringExample.py +++ b/EncodeStringExample/EncodeStringExample.py @@ -1,20 +1,27 @@ # coding=utf-8 import argparse -__author__ = 'ipetrash' +__author__ = "ipetrash" def create_parser(): - parse = argparse.ArgumentParser(description="Output a user-defined string in different encodings.") + parse = argparse.ArgumentParser( + description="Output a user-defined string in different encodings." + ) return parse -if __name__ == '__main__': +if __name__ == "__main__": create_parser().parse_args() - string = raw_input("text = ") - uni = unicode(string) - - list_encoding = {"windows-1251", "UTF-16", "UTF-16LE", "UTF-16BE", "ASCII", "Latin-1"} + string = input("text = ") + list_encoding = { + "windows-1251", + "UTF-16", + "UTF-16LE", + "UTF-16BE", + "ASCII", + "Latin-1", + } for encoding in list_encoding: - print(encoding + ": " + uni.encode(encoding)) \ No newline at end of file + print(f"{encoding}: {string.encode(encoding)}") diff --git a/Environment variables/EnvironmentVariables.py b/Environment variables/EnvironmentVariables.py index 07d2922b9..f4537ae9c 100644 --- a/Environment variables/EnvironmentVariables.py +++ b/Environment variables/EnvironmentVariables.py @@ -1,33 +1,33 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import os # Вывести все переменные окружения (environment variables) -print("Environment variables:\n{}".format(os.environ)) +print(f"Environment variables:\n{os.environ}") print("\nEnvironment variables:") for var, value in os.environ.items(): - print("'{}': '{}'".format(var, value)) + print(f"'{var}': '{value}'") # # Получение значения переменной окружения Path env_path = os.environ["Path"] # или os.environ.get("Path") # Вывести значение переменной окружения Path -print("\nPath: {}".format(env_path)) +print(f"\nPath: {env_path}") # Разделение строки с путями на список values_env_path = env_path.split(";") -print("Path: {}".format(values_env_path)) # Вывод списка +print(f"Path: {values_env_path}") # Вывод списка # Вывод списка путей print("Path:") for i, val in enumerate(values_env_path, 1): # Если элемент не пустой if val: - print("{}. '{}'".format(i, val) + (": not exist" if not os.path.exists(val) else "")) + print(f"{i}. '{val}'" + (": not exist" if not os.path.exists(val) else "")) diff --git a/Environment variables/expand_env__APPDATA.py b/Environment variables/expand_env__APPDATA.py index 372a7ab65..d8a39639c 100644 --- a/Environment variables/expand_env__APPDATA.py +++ b/Environment variables/expand_env__APPDATA.py @@ -1,19 +1,22 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # https://ru.wikipedia.org/wiki/Переменная_среды_Windows import os -app_data = os.path.expandvars('%APPDATA%') -print(app_data) + +app_data = os.path.expandvars("%APPDATA%") +print(app_data) print() -print(os.path.expandvars('%OS%')) -print(os.path.expandvars('%COMPUTERNAME%')) -print(os.path.expandvars('%WINDIR%')) + +print(os.path.expandvars("%OS%")) +print(os.path.expandvars("%COMPUTERNAME%")) +print(os.path.expandvars("%WINDIR%")) print() -print(os.path.expandvars('%NUMBER_OF_PROCESSORS%')) -print(os.path.expandvars('%PROCESSOR_ARCHITECTURE%')) + +print(os.path.expandvars("%NUMBER_OF_PROCESSORS%")) +print(os.path.expandvars("%PROCESSOR_ARCHITECTURE%")) diff --git a/EscapePyPromptLineString/main.py b/EscapePyPromptLineString/main.py index 92e87e64d..4555e837e 100644 --- a/EscapePyPromptLineString/main.py +++ b/EscapePyPromptLineString/main.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import traceback @@ -23,12 +23,12 @@ from PySide.QtCore import * -def log_uncaught_exceptions(ex_cls, ex, tb): - text = '{}: {}:\n'.format(ex_cls.__name__, ex) - text += ''.join(traceback.format_tb(tb)) +def log_uncaught_exceptions(ex_cls, ex, tb) -> None: + text = f"{ex_cls.__name__}: {ex}:\n" + text += "".join(traceback.format_tb(tb)) print(text) - QMessageBox.critical(None, 'Error', text) + QMessageBox.critical(None, "Error", text) sys.exit(1) @@ -36,10 +36,10 @@ def log_uncaught_exceptions(ex_cls, ex, tb): class MainWindow(QWidget): - def __init__(self): + def __init__(self) -> None: super().__init__() - self.setWindowTitle('EscapePyPromptLineString') + self.setWindowTitle("EscapePyPromptLineString") self.text_edit_input = QPlainTextEdit() @@ -51,9 +51,9 @@ def __init__(self): self.label_error.setTextInteractionFlags(Qt.TextSelectableByMouse) self.label_error.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) - self.button_detail_error = QPushButton('...') + self.button_detail_error = QPushButton("...") self.button_detail_error.setFixedSize(20, 20) - self.button_detail_error.setToolTip('Detail error') + self.button_detail_error.setToolTip("Detail error") self.button_detail_error.clicked.connect(self.show_detail_error_massage) self.last_error_message = None @@ -77,11 +77,11 @@ def __init__(self): self.setLayout(layout) - def show_detail_error_massage(self): - message = self.last_error_message + '\n\n' + self.last_detail_error_message + def show_detail_error_massage(self) -> None: + message = self.last_error_message + "\n\n" + self.last_detail_error_message mb = QErrorMessage() - mb.setWindowTitle('Error') + mb.setWindowTitle("Error") # Сообщение ошибки содержит отступы, символы-переходы на следующую строку, # которые поломаются при вставке через QErrorMessage.showMessage, и нет возможности # выбрать тип текста, то делаем такой хак. @@ -89,7 +89,7 @@ def show_detail_error_massage(self): mb.exec_() - def input_text_changed(self): + def input_text_changed(self) -> None: self.label_error.clear() self.button_detail_error.hide() @@ -104,15 +104,15 @@ def input_text_changed(self): new_out_text = [] for line in out_text.splitlines(): - if line.startswith('>>> ') or line.startswith('... ') or line == '...': + if line.startswith(">>> ") or line.startswith("... ") or line == "...": line = line[4:] new_out_text.append(line) - out_text = '\n'.join(new_out_text) + out_text = "\n".join(new_out_text) self.text_edit_output.setPlainText(out_text) - print('Escape for {:.3f} secs'.format(time.perf_counter() - t)) + print(f"Escape for {time.perf_counter() - t:.3f} secs") except Exception as e: # Выводим ошибку в консоль @@ -125,15 +125,16 @@ def input_text_changed(self): self.last_detail_error_message = str(tb) self.button_detail_error.show() - self.label_error.setText('Error: ' + self.last_error_message) + self.label_error.setText("Error: " + self.last_error_message) -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) mw = MainWindow() mw.resize(650, 500) - mw.text_edit_input.setPlainText("""\ + mw.text_edit_input.setPlainText( + """\ >>> import bcrypt >>> password = b"super secret password" >>> # Hash a password for the first time, with a randomly-generated salt diff --git a/EscapeString/main.py b/EscapeString/main.py index 7390eaf30..c6b7903dd 100644 --- a/EscapeString/main.py +++ b/EscapeString/main.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import time @@ -23,12 +23,12 @@ from PySide.QtCore import * -def log_uncaught_exceptions(ex_cls, ex, tb): - text = f'{ex_cls.__name__}: {ex}:\n' - text += ''.join(traceback.format_tb(tb)) +def log_uncaught_exceptions(ex_cls, ex, tb) -> None: + text = f"{ex_cls.__name__}: {ex}:\n" + text += "".join(traceback.format_tb(tb)) print(text) - QMessageBox.critical(None, 'Error', text) + QMessageBox.critical(None, "Error", text) sys.exit(1) @@ -37,17 +37,17 @@ def log_uncaught_exceptions(ex_cls, ex, tb): class MainWindow(QWidget): ESCAPE_RULES = [ - ('"', '"', r'\"', True), + ('"', '"', r"\"", True), ("'", "'", r"\'", False), - (r'\n', "\n", r'\n', True), - (r'\t', "\t", r'\t', False), - (r'\b', "\b", r'\b', False), - (r'\f', "\f", r'\f', False), - ('\\', "\\", '\\\\', False), + (r"\n", "\n", r"\n", True), + (r"\t", "\t", r"\t", False), + (r"\b", "\b", r"\b", False), + (r"\f", "\f", r"\f", False), + ("\\", "\\", "\\\\", False), ] - TITLE = 'EscapeString' + TITLE = "EscapeString" - def __init__(self, parent=None): + def __init__(self, parent=None) -> None: super().__init__(parent) self.setWindowTitle(self.TITLE) @@ -72,16 +72,21 @@ def __init__(self, parent=None): button_layout.addStretch() - self.cb_save_multiline = QCheckBox('Save multiline') + self.cb_save_multiline = QCheckBox("Save multiline") self.cb_save_multiline.setChecked(False) self.cb_save_multiline.clicked.connect(self.input_text_changed) button_layout.addWidget(self.cb_save_multiline) - self.cb_string_literal = QCheckBox('String literal') + self.cb_string_literal = QCheckBox("String literal") self.cb_string_literal.setChecked(True) self.cb_string_literal.clicked.connect(self.input_text_changed) button_layout.addWidget(self.cb_string_literal) + self.cb_semicolon = QCheckBox(";") + self.cb_semicolon.setChecked(True) + self.cb_semicolon.clicked.connect(self.input_text_changed) + button_layout.addWidget(self.cb_semicolon) + layout = QVBoxLayout() layout.addLayout(button_layout) @@ -95,9 +100,9 @@ def __init__(self, parent=None): self.label_error.setTextInteractionFlags(Qt.TextSelectableByMouse) self.label_error.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) - self.button_detail_error = QPushButton('...') + self.button_detail_error = QPushButton("...") self.button_detail_error.setFixedSize(20, 20) - self.button_detail_error.setToolTip('Detail error') + self.button_detail_error.setToolTip("Detail error") self.button_detail_error.clicked.connect(self.show_detail_error_massage) self.button_detail_error.hide() @@ -124,14 +129,14 @@ def __init__(self, parent=None): self.setLayout(layout) - def show_detail_error_massage(self): + def show_detail_error_massage(self) -> None: if not self.last_error_message or not self.last_detail_error_message: return - message = self.last_error_message + '\n\n' + self.last_detail_error_message + message = self.last_error_message + "\n\n" + self.last_detail_error_message mb = QErrorMessage() - mb.setWindowTitle('Error') + mb.setWindowTitle("Error") # Сообщение ошибки содержит отступы, символы-переходы на следующую строку, # которые поломаются при вставке через QErrorMessage.showMessage, и нет возможности # выбрать тип текста, то делаем такой хак. @@ -139,22 +144,24 @@ def show_detail_error_massage(self): mb.exec_() - def _escape(self, in_text: str, ignored='') -> str: + def _escape(self, in_text: str, ignored="") -> str: out_text = [] for char in in_text: # Если char нет в словаре экранирования, или флаг экранирования для char выключен, # или char среди игнорируемых - if char not in self.escape_char_by_checkbox \ - or not self.escape_char_by_checkbox[char].isChecked()\ - or char in ignored: + if ( + char not in self.escape_char_by_checkbox + or not self.escape_char_by_checkbox[char].isChecked() + or char in ignored + ): out_text.append(char) continue out_text.append(self.char_by_escape[char]) - return ''.join(out_text) + return "".join(out_text) - def input_text_changed(self): + def input_text_changed(self) -> None: self.label_error.clear() self.button_detail_error.hide() @@ -172,7 +179,7 @@ def input_text_changed(self): line = self._escape(line) lines.append(f'"{line}"') - out_text = ' +\n'.join(lines) + out_text = " +\n".join(lines) else: out_text = self._escape(in_text) @@ -182,16 +189,18 @@ def input_text_changed(self): out_text = '"' + out_text if not out_text.endswith('"'): out_text += '"' - out_text += ';' + + if self.cb_semicolon.isChecked(): + out_text += ";" self.text_edit_output.setPlainText(out_text) self.setWindowTitle( - f'{self.TITLE} (number of characters: ' - f'{len(self.text_edit_input.toPlainText())} -> ' - f'{len(self.text_edit_output.toPlainText())})' + f"{self.TITLE} (number of characters: " + f"{len(self.text_edit_input.toPlainText())} -> " + f"{len(self.text_edit_output.toPlainText())})" ) - print('Escape for {:.6f} secs'.format(time.perf_counter() - t)) + print(f"Escape for {time.perf_counter() - t:.6f} secs") except Exception as e: # Выводим ошибку в консоль @@ -204,10 +213,10 @@ def input_text_changed(self): self.last_detail_error_message = str(tb) self.button_detail_error.show() - self.label_error.setText('Error: ' + self.last_error_message) + self.label_error.setText("Error: " + self.last_error_message) -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) mw = MainWindow() diff --git a/Fibonacci number/6 ways.py b/Fibonacci number/6 ways.py index 2fbe6dc50..25cd58937 100644 --- a/Fibonacci number/6 ways.py +++ b/Fibonacci number/6 ways.py @@ -1,13 +1,14 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://technobeans.com/2012/04/16/5-ways-of-fibonacci-in-python/ NUMBER = 15 + # Example 1: Using looping technique def fib_loop(n): a, b = 1, 1 @@ -16,7 +17,7 @@ def fib_loop(n): return a -print('Example 1') +print("Example 1") print(fib_loop(NUMBER)) print() @@ -28,7 +29,7 @@ def fib_recursion(n): return fib_recursion(n - 1) + fib_recursion(n - 2) -print('Example 2') +print("Example 2") print(fib_recursion(NUMBER)) print() @@ -40,7 +41,8 @@ def fib_generator(): a, b = b, a + b yield a -print('Example 3') + +print("Example 3") f = fib_generator() for _ in range(NUMBER): print(next(f)) @@ -56,7 +58,7 @@ def memoize(fn, arg): # fib() as written in example 1. -print('Example 4') +print("Example 4") fibm = memoize(fib_loop, NUMBER) print(fibm) print() @@ -64,7 +66,7 @@ def memoize(fn, arg): # Example 5: Using memoization as decorator (decorator-class) class MemoizeClass: - def __init__(self, func): + def __init__(self, func) -> None: self.func = func self.memo = dict() @@ -82,7 +84,8 @@ def fib(n): a, b = b, a + b return a -print('Example 5') + +print("Example 5") print(fib(NUMBER)) print() @@ -98,6 +101,7 @@ def func(*args): return func + @memoize_func def fib(n): a, b = 1, 1 @@ -105,6 +109,7 @@ def fib(n): a, b = b, a + b return a -print('Example 6') + +print("Example 6") print(fib(NUMBER)) print() diff --git a/Fibonacci number/FibonacciNumber.py b/Fibonacci number/FibonacciNumber.py index 30d2dad3d..a164d783e 100644 --- a/Fibonacci number/FibonacciNumber.py +++ b/Fibonacci number/FibonacciNumber.py @@ -1,21 +1,22 @@ -__author__ = 'ipetrash' +__author__ = "ipetrash" -def fibo_1(n): + +def fibo_1(n) -> None: f = [0, 1] for i in range(2, n + 1): - f.append(f[i-1] + f[i-2]) + f.append(f[i - 1] + f[i - 2]) print(f) -def fibo_2(n): +def fibo_2(n) -> None: a, b = 0, 1 - print(a, b, end=' ') + print(a, b, end=" ") for i in range(2, n + 1): a, b = b, a + b - print(b, end=' ') + print(b, end=" ") -if __name__ == '__main__': +if __name__ == "__main__": # Fibo / Фибоначчи n = 10 fibo_1(n) diff --git a/FindWordInString/FindWordInString.py b/FindWordInString/FindWordInString.py index 332ca14c8..21b9dbb7c 100644 --- a/FindWordInString/FindWordInString.py +++ b/FindWordInString/FindWordInString.py @@ -1,17 +1,22 @@ # coding=utf-8 -import argparse -__author__ = 'ipetrash' +__author__ = "ipetrash" + + +import argparse def create_parser(): - parser = argparse.ArgumentParser(description="Find the longest word in a string, separated by spaces.") + parser = argparse.ArgumentParser( + description="Find the longest word in a string, separated by spaces." + ) return parser -if __name__ == '__main__': + +if __name__ == "__main__": create_parser().parse_args() - string = str(raw_input("Input string: ")) + string = input("Input string: ") max_len = -1 max_len_word = "" for word in string.split(" "): @@ -20,4 +25,4 @@ def create_parser(): max_len = current_len max_len_word = word - print("Longest word: " + max_len_word) \ No newline at end of file + print("Longest word: " + max_len_word) diff --git a/FindWordInString/regexp.py b/FindWordInString/regexp.py index 5c2935268..ff9df6b75 100644 --- a/FindWordInString/regexp.py +++ b/FindWordInString/regexp.py @@ -1,11 +1,13 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -text = 'in comparison to dogs, cats have not undergone major changes during the domestication process.' - import re -words = re.findall(r'\b(\w+)\b', text) + + +text = "in comparison to dogs, cats have not undergone major changes during the domestication process." + +words = re.findall(r"\b(\w+)\b", text) print(words) diff --git a/Fingerprinting with Zero-Width Characters/main.py b/Fingerprinting with Zero-Width Characters/main.py index 49c4c19ae..898f98e8c 100644 --- a/Fingerprinting with Zero-Width Characters/main.py +++ b/Fingerprinting with Zero-Width Characters/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # Fingerprinting with Zero-Width Characters @@ -23,10 +23,10 @@ # const zeroWidthNoBreakSpace = '\uFEFF'; // 65279 -ZERO_WIDTH_SPACE = '\u200B' # 8203 -ZERO_WIDTH_NON_JOINER = '\u200C' # 8204 -ZERO_WIDTH_JOINER = '\u200D' # 8205 -ZERO_WIDTH_NO_BREAK_SPACE = '\uFEFF' # 65279 +ZERO_WIDTH_SPACE = "\u200B" # 8203 +ZERO_WIDTH_NON_JOINER = "\u200C" # 8204 +ZERO_WIDTH_JOINER = "\u200D" # 8205 +ZERO_WIDTH_NO_BREAK_SPACE = "\uFEFF" # 65279 def to_binary(c: str) -> str: @@ -34,16 +34,16 @@ def to_binary(c: str) -> str: def text_to_binary(username: str) -> str: - return ' '.join(map(to_binary, username)) + return " ".join(map(to_binary, username)) def binary_to_zero_width(binary_username: str) -> str: zero_width_items = [] for c in binary_username: - if c == '1': + if c == "1": zero_width = ZERO_WIDTH_SPACE - elif c == '0': + elif c == "0": zero_width = ZERO_WIDTH_NON_JOINER else: zero_width = ZERO_WIDTH_JOINER @@ -69,23 +69,29 @@ def zero_width_to_binary(text: str) -> str: for c in text.split(ZERO_WIDTH_NO_BREAK_SPACE): if c == ZERO_WIDTH_SPACE: - binary.append('1') + binary.append("1") elif c == ZERO_WIDTH_NON_JOINER: - binary.append('0') + binary.append("0") else: - binary.append(' ') + binary.append(" ") - return ''.join(binary) + return "".join(binary) def binary_to_text(text: str) -> str: - return ''.join(chr(int(num, 2)) for num in text.split(' ')) + return "".join(chr(int(num, 2)) for num in text.split(" ")) def get_zero_width_from_text(text: str) -> str: - return ''.join( - c for c in text - if c in (ZERO_WIDTH_SPACE, ZERO_WIDTH_NON_JOINER, ZERO_WIDTH_JOINER, ZERO_WIDTH_NO_BREAK_SPACE) + return "".join( + c + for c in text + if c in ( + ZERO_WIDTH_SPACE, + ZERO_WIDTH_NON_JOINER, + ZERO_WIDTH_JOINER, + ZERO_WIDTH_NO_BREAK_SPACE, + ) ) @@ -102,10 +108,12 @@ def get_username_from_text(text: str) -> str: return text_username -if __name__ == '__main__': - text = "This is some confidential text that you really shouldn't be sharing anywhere else. " \ - "Это конфиденциальный текст, которым вы действительно не должны делиться." - username = 'hello world/привет мир' +if __name__ == "__main__": + text = ( + "This is some confidential text that you really shouldn't be sharing anywhere else. " + "Это конфиденциальный текст, которым вы действительно не должны делиться." + ) + username = "hello world/привет мир" print(len(text), text) diff --git a/FizzBuzz/FizzBuzz.py b/FizzBuzz/FizzBuzz.py index a3520b6b4..17f69b275 100644 --- a/FizzBuzz/FizzBuzz.py +++ b/FizzBuzz/FizzBuzz.py @@ -1,22 +1,23 @@ -__author__ = 'ipetrash' +__author__ = "ipetrash" -if __name__ == '__main__': - # EN: - # Write a program that displays a number from 1 to 100. In this case, instead of numbers that are - # multiples of three, the program should display the word «Fizz», but instead of multiples of five - the - # word «Buzz». If the number is a multiple and 3 and 5, the program should display the word «FizzBuzz» +# EN: +# Write a program that displays a number from 1 to 100. In this case, instead of numbers that are +# multiples of three, the program should display the word «Fizz», but instead of multiples of five - the +# word «Buzz». If the number is a multiple and 3 and 5, the program should display the word «FizzBuzz» - # RU: - # Напишите программу, которая выводит на экран числа от 1 до 100. При этом вместо чисел, кратных трем, программа - # должна выводить слово «Fizz», а вместо чисел, кратных пяти — слово «Buzz». Если число кратно и 3, и 5, - # то программа должна выводить слово «FizzBuzz» - for num in range(1, 100 + 1): - if num % 15 is 0: - print("FizzBuzz") - elif num % 3 is 0: - print("Fizz") - elif num % 5 is 0: - print("Buzz") - else: - print(num) \ No newline at end of file +# RU: +# Напишите программу, которая выводит на экран числа от 1 до 100. При этом вместо чисел, кратных трем, программа +# должна выводить слово «Fizz», а вместо чисел, кратных пяти — слово «Buzz». Если число кратно и 3, и 5, +# то программа должна выводить слово «FizzBuzz» + + +for num in range(1, 100 + 1): + if num % 15 is 0: + print("FizzBuzz") + elif num % 3 is 0: + print("Fizz") + elif num % 5 is 0: + print("Buzz") + else: + print(num) diff --git a/Get MAC from Syslog.txt Router/main.py b/Get MAC from Syslog.txt Router/main.py index e635f5aee..0969c98d9 100644 --- a/Get MAC from Syslog.txt Router/main.py +++ b/Get MAC from Syslog.txt Router/main.py @@ -1,12 +1,20 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- +__author__ = "ipetrash" -__author__ = 'ipetrash' +import re -text = open('Syslog.txt').read() -import re -for mac in set(re.findall(r'[0-9a-fA-F]{2}(?::[0-9a-fA-F]{2}){5}', text)): +with open("Syslog.txt") as f: + text = f.read() + +for mac in set(re.findall(r"[0-9a-fA-F]{2}(?::[0-9a-fA-F]{2}){5}", text)): print(mac) +""" +51:FD:DE:2B:B6:DE +61:61:6D:3F:99:1F +03:B4:1D:40:9B:CD +AC:38:72:82:FF:C4 +""" diff --git a/Grab/empty_tags_stackoverflow.py b/Grab/empty_tags_stackoverflow.py index eb4bace36..43b5fa8e5 100644 --- a/Grab/empty_tags_stackoverflow.py +++ b/Grab/empty_tags_stackoverflow.py @@ -1,16 +1,18 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import time +import traceback + from urllib.parse import quote import grab -if __name__ == '__main__': +if __name__ == "__main__": g = grab.Grab() tag_list = set() @@ -19,8 +21,8 @@ while True: try: - url = 'http://ru.stackoverflow.com/tags?page={}&tab=name'.format(page) - print('Go page (found tags: {}): {}'.format(len(tag_list), url)) + url = f"http://ru.stackoverflow.com/tags?page={page}&tab=name" + print(f"Go page (found tags: {len(tag_list)}): {url}") g.go(url) next_page = g.doc.select('//span[@class="page-numbers next"]') @@ -30,14 +32,19 @@ for a in g.doc.select('//a[@class="post-tag"]'): tag = a.text() - url = 'http://ru.stackoverflow.com/tags/{}/info'.format(quote(tag)) - print(' Go tag: {}'.format(url)) + url = f"http://ru.stackoverflow.com/tags/{quote(tag)}/info" + print(f" Go tag: {url}") tag_g = grab.Grab() tag_g.go(url) - has_not_ref_guide = "Для этой метки до сих пор нет руководства по использованию." in tag_g.response.body - has_not_description = "Для этой метки до сих пор нет описания." in tag_g.response.body + has_not_ref_guide = ( + "Для этой метки до сих пор нет руководства по использованию." + in tag_g.response.body + ) + has_not_description = ( + "Для этой метки до сих пор нет описания." in tag_g.response.body + ) if has_not_ref_guide or has_not_description: tag_info = ( @@ -47,23 +54,21 @@ ) tag_list.add(tag_info) - time.sleep(.3) + time.sleep(0.3) page += 1 time.sleep(1) except Exception as e: - import traceback - # Сохраняем в переменную tb = traceback.format_exc() - print('Error:\n{}'.format(tb)) - print('Wait 60 sec.') + print(f"Error:\n{tb}") + print("Wait 60 sec.") time.sleep(60) - print('Tags: {}.'.format(len(tag_list))) + print(f"Tags: {len(tag_list)}.") for tag in tag_list: print(*tag) diff --git a/Grab/find_pictures_on_request.py b/Grab/find_pictures_on_request.py index badf6bd20..215f08fff 100644 --- a/Grab/find_pictures_on_request.py +++ b/Grab/find_pictures_on_request.py @@ -1,17 +1,18 @@ -__author__ = 'ipetrash' +__author__ = "ipetrash" # Find images on request search engine -from grab import Grab -import urllib.parse import re +import urllib.parse + +from grab import Grab -if __name__ == '__main__': - url = 'http://yandex.ru/images/search?text=' - rq_text = 'husky puppies' +if __name__ == "__main__": + url = "http://yandex.ru/images/search?text=" + rq_text = "husky puppies" url += urllib.parse.quote(rq_text) @@ -19,8 +20,8 @@ g.go(url) images = g.doc.select('//a[@class="serp-item__link"]/@onmousedown') - print('Total: %s' % images.count()) + print(f"Total: {images.count()}") for im in images: im_href = re.search(r'"href":"(.+)"', im.text()).group(1) - print(im_href) \ No newline at end of file + print(im_href) diff --git a/Grab/get_boormarks_readmanga.py b/Grab/get_boormarks_readmanga.py index f53ebd0a4..d366f6773 100644 --- a/Grab/get_boormarks_readmanga.py +++ b/Grab/get_boormarks_readmanga.py @@ -3,14 +3,14 @@ манга, оставленных пользователем в закладках. """ -__author__ = 'ipetrash' +__author__ = "ipetrash" from grab import Grab import re -if __name__ == '__main__': +if __name__ == "__main__": username = input("Логин: ") password = input("Пароль: ") @@ -18,12 +18,12 @@ # Переходим на страницу входа print("...Перехожу на страницу входа...") - g.go('http://grouple.ru/internal/auth/login') + g.go("http://grouple.ru/internal/auth/login") # Заполняем формы логина и пароля print("...Заполняем формы логина и пароля...") - g.set_input('j_username', username) - g.set_input('j_password', password) + g.set_input("j_username", username) + g.set_input("j_password", password) # Отсылаю данные print("...Отсылаю данные...") @@ -35,15 +35,17 @@ # Переход на страницу Закладки print('...Перехожу на страницу "Закладки"...') - g.go('http://grouple.ru/private/bookmarks') + g.go("http://grouple.ru/private/bookmarks") - print("\nПользователь: {}".format(user)) + print(f"\nПользователь: {user}") # Запрос на получение всех закладок - TEMPLATE_BOOKMARKS = ('//div[@class="bookmarks-lists"]/table[starts-with(@class, "cTable bookmarks_")]' - '//tr[@class="bookmark-row"]') + TEMPLATE_BOOKMARKS = ( + '//div[@class="bookmarks-lists"]/table[starts-with(@class, "cTable bookmarks_")]' + '//tr[@class="bookmark-row"]' + ) # Общее количество заметок - print("\nЗакладки({}):".format(g.doc.select(TEMPLATE_BOOKMARKS).count())) + print(f"\nЗакладки({g.doc.select(TEMPLATE_BOOKMARKS).count()}):") # Запрос на получение всех типов закладок TEMPLATE_TYPE_BOOKMARK = '//div[@class="bookmarks-lists"]/table[starts-with(@class, "cTable bookmarks_")]' @@ -51,7 +53,7 @@ # Регулярка для вытаскивания из имени закладки нужное # Например, из "Пока бросил (1)" будет вытащено: "Пока бросил" - regexp_bookmark = re.compile("(.+) \(.+\)") + regexp_bookmark = re.compile(r"(.+) \(.+\)") # Перебор всех типов закладок for tb in type_bookmarks: @@ -61,15 +63,15 @@ # вытаскивается первая группа: # Например, из "Пока бросил (1)" будет вытащено: "Пока бросил" bookmark = regexp_bookmark.search(bookmark).group(1) - print(' Закладка "{}":'.format(bookmark)) + print(f' Закладка "{bookmark}":') # Получение всех закладок данного типа group_bookmarks = tb.select('tr[@class="bookmark-row"]') # Перебор всех закладок данного типа for bm in group_bookmarks: - href = bm.select("td/a").attr('href') # Ссылка на мангу + href = bm.select("td/a").attr("href") # Ссылка на мангу name = bm.select("td/a/text()").text() # Название манги - print(' "{}": {}'.format(name, href)) + print(f' "{name}": {href}') - print() \ No newline at end of file + print() diff --git a/Grab/get_example_from_de_brauwer.py b/Grab/get_example_from_de_brauwer.py index fe09d34fe..119653d05 100644 --- a/Grab/get_example_from_de_brauwer.py +++ b/Grab/get_example_from_de_brauwer.py @@ -1,7 +1,11 @@ -from grab import Grab + +__author__ = "ipetrash" + + import re +import os -__author__ = 'ipetrash' +from grab import Grab """Пример получения исходного кода с сайта www.de-brauwer.be.""" @@ -9,39 +13,35 @@ def get_example_from_de_brauwer(wakka): g = Grab() - g.go('http://www.de-brauwer.be/wiki/wikka.php?wakka=' + wakka) + g.go("http://www.de-brauwer.be/wiki/wikka.php?wakka=" + wakka) code_html = g.doc.select('//div[@class="code"]').html() - pattern = (r'(
)' - '|' - '|' - '|' - '|
') - source_code = re.sub(pattern, '', code_html) - source_code = re.sub('\xa0', ' ', source_code) # remove   + pattern = r'(
)' "|" "|" "|" "|
" + source_code = re.sub(pattern, "", code_html) + source_code = re.sub("\xa0", " ", source_code) # remove   return source_code -DIR_NAME = 'PyOpenGLExample' -import os + +DIR_NAME = "PyOpenGLExample" os.makedirs(DIR_NAME, exist_ok=True) examples = [ - 'PyOpenGLHelloWorld', - 'PyOpenGLSierpinski', - 'PyOpenGLSquares', - 'PyOpenGLCheckerBoard', - 'PyOpenGLMouse', - 'PyOpenGLScatter', - 'PyOpenGLGingerbread', - 'PyOpenGLMaze', - 'PyOpenGLReshape', - 'PyOpenGLTurtle', - 'PyOpenGLRosette', - 'PyOpenGLWireframe', + "PyOpenGLHelloWorld", + "PyOpenGLSierpinski", + "PyOpenGLSquares", + "PyOpenGLCheckerBoard", + "PyOpenGLMouse", + "PyOpenGLScatter", + "PyOpenGLGingerbread", + "PyOpenGLMaze", + "PyOpenGLReshape", + "PyOpenGLTurtle", + "PyOpenGLRosette", + "PyOpenGLWireframe", ] for e in examples: source_code = get_example_from_de_brauwer(e) - with open(os.path.join(DIR_NAME, e + '.py'), mode='w', encoding='utf-8') as f: - f.write(source_code) \ No newline at end of file + with open(os.path.join(DIR_NAME, e + ".py"), mode="w", encoding="utf-8") as f: + f.write(source_code) diff --git a/Grab/get_repo_github_info.py b/Grab/get_repo_github_info.py index 7d6f95b4b..53bf1b03d 100644 --- a/Grab/get_repo_github_info.py +++ b/Grab/get_repo_github_info.py @@ -3,13 +3,13 @@ репозиториев и некоторую информацию о них. """ -__author__ = 'ipetrash' +__author__ = "ipetrash" from grab import Grab -if __name__ == '__main__': +if __name__ == "__main__": login = input("Логин: ") password = input("Пароль: ") @@ -17,12 +17,12 @@ # Переходим на страницу входа print("...Перехожу на страницу входа...") - g.go('https://github.com/login') + g.go("https://github.com/login") # Заполняем формы логина и пароля print("...Заполняем формы логина и пароля...") - g.set_input('login', login) - g.set_input('password', password) + g.set_input("login", login) + g.set_input("password", password) # Отсылаю данные формы print("...Отсылаю данные формы...") @@ -30,21 +30,21 @@ # Переход на страницу с репозиториями print("...Перехожу на страницу с репозиториями...") - g.go("https://github.com/{}?tab=repositories".format(login)) + g.go(f"https://github.com/{login}?tab=repositories") # Получение списка репозиториев print("...Получаю список репозиториев...") # list_repo = g.doc.select('//ul[@class="repo-list js-repo-list"]/li/h3/a') list_repo = g.doc.select('//h3[@class="repo-list-name"]/a') - print("\nРепозитории({}):".format(len(list_repo))) + print(f"\nРепозитории({len(list_repo)}):") # Перебор всех репозиториев for i, repo in enumerate(list_repo, 1): - url = "https://github.com" + repo.attr('href') + url = "https://github.com" + repo.attr("href") grab_repo = Grab() grab_repo.go(url) # переход на страницу репозитория # получение количества коммитов данного репозитория count = grab_repo.doc.select('//span[@class="num text-emphasized"]').text() - print('{}. "{}" ({} commits): {}'.format(i, repo.text(), count, url)) \ No newline at end of file + print(f'{i}. "{repo.text()}" ({count} commits): {url}') diff --git a/Grab/get_translation_completed_manga_from_readmanga.py b/Grab/get_translation_completed_manga_from_readmanga.py index 1bcb7d10c..b44dcd10d 100644 --- a/Grab/get_translation_completed_manga_from_readmanga.py +++ b/Grab/get_translation_completed_manga_from_readmanga.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """Вывод манг жанра 'game' с статусом 'Переведена'. @@ -22,52 +22,51 @@ from grab import Grab -LOGIN = '' -PASSWORD = '' +LOGIN = "" +PASSWORD = "" -if __name__ == '__main__': +if __name__ == "__main__": g = Grab() # Авторизация - g.go('http://grouple.ru/internal/auth/login') - g.set_input('j_username', LOGIN) - g.set_input('j_password', PASSWORD) + g.go("http://grouple.ru/internal/auth/login") + g.set_input("j_username", LOGIN) + g.set_input("j_password", PASSWORD) g.submit() - if g.response.url == 'http://grouple.ru/internal/auth/login?login_error=1': - print('Авторизация прошла неудачно :(') + if g.response.url == "http://grouple.ru/internal/auth/login?login_error=1": + print("Авторизация прошла неудачно :(") # Зайдем в закладки - g.go('http://grouple.ru/private/bookmarks') + g.go("http://grouple.ru/private/bookmarks") # Получим список всех url'ов манг в закладках get_href_manga_xpath = '//div[@class="bookmarks-lists"]/*/tr/td/a/@href' - all_bookmarks_href = [href.text() - for href in g.doc.select(get_href_manga_xpath)] + all_bookmarks_href = [href.text() for href in g.doc.select(get_href_manga_xpath)] # Заходим на страницу жанра манг "игра" - g.go('http://readmanga.me/list/genre/game') + g.go("http://readmanga.me/list/genre/game") # Ищем элементы div, у которых есть дети div/span у которого есть класс "mangaTranslationCompleted", # и если мы такой нашли, мы у него ищем div с классом "desc" xpath = '//div[div/span[@class="mangaTranslationCompleted"]]/div[@class="desc"]' - print('Список законченной и переведенной манги:') + print("Список законченной и переведенной манги:") # Переберем список найденных элементов for i, tile in enumerate(g.doc.select(xpath), 1): - title = tile.select('h3/a/@title').text() + title = tile.select("h3/a/@title").text() - alt_title = tile.select('h4/@title') + alt_title = tile.select("h4/@title") # Если альтернативный заголовок есть, добавим его if alt_title.count(): - title = '"{}" / "{}"'.format(title, alt_title.text()) + title = f'"{title}" / "{alt_title.text()}"' else: - title = '"{}"'.format(title) + title = f'"{title}"' - href = 'http://readmanga.me' + tile.select('h3/a/@href').text() + href = "http://readmanga.me" + tile.select("h3/a/@href").text() if href not in all_bookmarks_href: - print('{}. {}: {}'.format(i, title, href)) + print("{i}. {title}: {href}") else: - print('Уже есть в закладках {}: {}'.format(title, href)) \ No newline at end of file + print("Уже есть в закладках {title}: {href}") diff --git a/Grab/get_user_github_info.py b/Grab/get_user_github_info.py index f6637d14a..1bc3b6e7f 100644 --- a/Grab/get_user_github_info.py +++ b/Grab/get_user_github_info.py @@ -5,10 +5,10 @@ """ -__author__ = 'ipetrash' +__author__ = "ipetrash" -if __name__ == '__main__': +if __name__ == "__main__": login = input("Логин: ") password = input("Пароль: ") @@ -36,7 +36,9 @@ fullname = g.doc.select('//span[@itemprop="name"]').text() username = g.doc.select('//span[@itemprop="additionalName"]').text() - avatar = g.doc.select('//*[@class="vcard-avatar tooltipped tooltipped-s"]/*[@class="avatar"]').attr('src') + avatar = g.doc.select( + '//*[@class="vcard-avatar tooltipped tooltipped-s"]/*[@class="avatar"]' + ).attr("src") organization = g.doc.select('//li[@itemprop="worksFor"]').text() homeLocation = g.doc.select('//li[@itemprop="homeLocation"]').text() email = g.doc.select('//a[@class="email"]').text() @@ -53,31 +55,30 @@ print("home location:", homeLocation) print("email:", email) print("url:", url) - print('{} {} ({})'.format(join_label, join_title_time, join_datetime)) + print(f"{join_label} {join_title_time} ({join_datetime})") # Получение списка репозиториев print() - print('...Перехожу на вкладку репозиториев...') - g.go("https://github.com/" + login + '?tab=repositories') + print("...Перехожу на вкладку репозиториев...") + g.go("https://github.com/" + login + "?tab=repositories") print("...Получаю список репозиториев...") list_source_repo = g.doc.select('//li[@class="repo-list-item public source"]') print() print("Репозитории:") - print("Sources({}):".format(len(list_source_repo))) + print(f"Sources({len(list_source_repo)}):") for i, repo in enumerate(list_source_repo, 1): name = repo.select('*[@class="repo-list-name"]/a') - href = 'https://github.com' + name.attr('href') - print(' {}. {}: {}'.format(i, name.text(), href)) + href = "https://github.com" + name.attr("href") + print(f" {i}. {name.text()}: {href}") description = repo.select('*[@class="repo-list-description"]') if description.count(): - print(' "{}"'.format(description.text())) + print(f' "{description.text()}"') - stats = repo.select('*[@class="repo-list-stats"]').text().split(' ') + stats = repo.select('*[@class="repo-list-stats"]').text().split(" ") lang, stars, forks = stats - print(' lang: {}, stars: {}, forks: {}'.format(lang, stars, forks)) - print() \ No newline at end of file + print(f" lang: {lang}, stars: {stars}, forks: {forks}\n") diff --git a/Grab/parse_onlinemultfilmy.py b/Grab/parse_onlinemultfilmy.py index 71512d032..0c2eed163 100644 --- a/Grab/parse_onlinemultfilmy.py +++ b/Grab/parse_onlinemultfilmy.py @@ -1,20 +1,23 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # Поиск мультсериалов 16+ # Пример сериала: 'http://onlinemultfilmy.ru/bratya-ventura/' + import time from grab import Grab + g = Grab() +# TODO: магическое число нужно заменить # Перебор страниц с мультами for i in range(1, 82 + 1): - url_page = 'http://onlinemultfilmy.ru/multserialy/page/' + str(i) + url_page = f"http://onlinemultfilmy.ru/multserialy/page/{i}" print(url_page) # Загрузка страницы с мультами @@ -22,10 +25,10 @@ # Перебор и загрузка мультов на странице for url in g.doc.select('//div[@class="cat-post"]/a'): - g.go(url.attr('href')) + g.go(url.attr("href")) if g.doc.select('//*[@class="age_icon age_icon_16"]').count(): - print(' ', url.attr('title'), url.attr('href')) + print(" ", url.attr("title"), url.attr("href")) # Чтобы сервер не посчитал это дос атакой time.sleep(2) diff --git a/Guess the number/GuessTheNumber.py b/Guess the number/GuessTheNumber.py deleted file mode 100644 index 82bca49dd..000000000 --- a/Guess the number/GuessTheNumber.py +++ /dev/null @@ -1,32 +0,0 @@ -__author__ = 'ipetrash' - -# Guess the number / Угадай число -import random - -if __name__ == '__main__': - max = int(input("Input max: ")) - print("Random number (x): from %d to %d" % (1, max)) - number = random.randrange(1, max + 1) - user_choice = -1 - range_min = range_max = "?" - - while True: - print("\n%s < x < %s" % (range_min, range_max)) - user_choice = int(input("Input number: ")) - if number > user_choice: - if range_min == "?": - range_min = user_choice - if user_choice > range_min: - range_min = user_choice - print("x > %d" % user_choice) - - elif number < user_choice: - if range_max == "?": - range_max = user_choice - if user_choice < range_max: - range_max = user_choice - print("x < %d" % user_choice) - - elif number == user_choice: - print("Congratulations! You guessed it!") - break diff --git a/Hash/available.py b/Hash/available.py index e67d78ab3..6cf6ac4a9 100644 --- a/Hash/available.py +++ b/Hash/available.py @@ -1,29 +1,29 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import hashlib import sys -if __name__ == '__main__': - algorithms = list(hashlib.algorithms_available) # get list algorithms - algorithms.sort() - print("Algorithms available: %s" % ", ".join(algorithms)) - - text = input("Text: ") - if not text: - print("Empty text!") - sys.exit(1) - - alg_name = input("Name algorithm: ") - if alg_name not in algorithms: # search in list - print("Algorithm not found!") - sys.exit(1) - - alg = hashlib.new(alg_name) # create hash function from name - alg.update(text.encode()) # set data in hash-function - print("Result:") - print(" hex: %s" % alg.hexdigest()) - print(" HEX: %s" % alg.hexdigest().upper()) + +algorithms = list(hashlib.algorithms_available) # get list algorithms +algorithms.sort() +print(f"Algorithms available: {', '.join(algorithms)}") + +text = input("Text: ") +if not text: + print("Empty text!") + sys.exit(1) + +alg_name = input("Name algorithm: ") +if alg_name not in algorithms: # search in list + print("Algorithm not found!") + sys.exit(1) + +alg = hashlib.new(alg_name) # create hash function from name +alg.update(text.encode()) # set data in hash-function +print("Result:") +print(f" hex: {alg.hexdigest()}") +print(f" HEX: {alg.hexdigest().upper()}") diff --git a/Hash/custom.py b/Hash/custom.py index dcd6dceff..b24dd957f 100644 --- a/Hash/custom.py +++ b/Hash/custom.py @@ -1,23 +1,21 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" +import hashlib from datetime import datetime -import hashlib -if __name__ == '__main__': - ts = datetime.today().timestamp() - bts = str(ts).encode() +ts = datetime.today().timestamp() +bts = str(ts).encode() - # Long - md5 = hashlib.md5() - md5.update(bts) +# Long +md5 = hashlib.md5() +md5.update(bts) - print(md5.hexdigest()) - - # Short - print(hashlib.md5(bts).hexdigest()) +print(md5.hexdigest()) +# Short +print(hashlib.md5(bts).hexdigest()) diff --git a/Hash/encode_all_algo.py b/Hash/encode_all_algo.py index 221cb683c..516bc94ea 100644 --- a/Hash/encode_all_algo.py +++ b/Hash/encode_all_algo.py @@ -1,22 +1,22 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import hashlib -if __name__ == '__main__': - available = list(set(map(str.lower, hashlib.algorithms_available))) - available.sort() - text = 'hello world' +available = list(set(map(str.lower, hashlib.algorithms_available))) +available.sort() - for algo_name in available: - try: - alg = hashlib.new(algo_name) # create hash function from name - alg.update(text.encode()) # set data in hash-function - print("{}: {}".format(algo_name, alg.hexdigest().upper())) +text = "hello world" - except ValueError: - pass +for algo_name in available: + try: + alg = hashlib.new(algo_name) # create hash function from name + alg.update(text.encode()) # set data in hash-function + print(f"{algo_name}: {alg.hexdigest().upper()}") + + except ValueError: + pass diff --git a/Hash/to sha256.py b/Hash/to sha256.py index 1ad99dd18..62140261e 100644 --- a/Hash/to sha256.py +++ b/Hash/to sha256.py @@ -1,13 +1,15 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -def to_sha256(text): - import hashlib +import hashlib + + +def to_sha256(text: str) -> str: return hashlib.sha256(text.encode()).hexdigest() -if __name__ == '__main__': - print(to_sha256('Hello World!')) +if __name__ == "__main__": + print(to_sha256("Hello World!")) diff --git a/HelloPython/HelloPython.py b/HelloPython/HelloPython.py index 0f82a6c8d..388f750d8 100644 --- a/HelloPython/HelloPython.py +++ b/HelloPython/HelloPython.py @@ -1,15 +1,14 @@ -__author__ = 'ipetrash' +__author__ = "ipetrash" import argparse +from datetime import datetime, time -def main(namespace): +def main(namespace) -> None: args = namespace.parse_args() args.user = "Илья" if args.user: - from datetime import datetime, time - welcome = "Привет" current = datetime.now().time() @@ -25,16 +24,17 @@ def main(namespace): elif time(0) <= current < time(6): welcome = "Доброй ночи" - print("{}, {}!".format(welcome, args.user)) + print(f"{welcome}, {args.user}!") else: print("Привет, Python!") -def create_parser(): - parser = argparse.ArgumentParser(description='Hello World Example!') - parser.add_argument('--user', type=str, help=' user name.') +def create_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser(description="Hello World Example!") + parser.add_argument("--user", type=str, help=" user name.") return parser -if __name__ == '__main__': + +if __name__ == "__main__": parser = create_parser() main(parser) diff --git a/HypotenuseOfTriangle/HypotenuseOfTriangle.py b/HypotenuseOfTriangle/HypotenuseOfTriangle.py index 82f6ca44f..a59faafbe 100644 --- a/HypotenuseOfTriangle/HypotenuseOfTriangle.py +++ b/HypotenuseOfTriangle/HypotenuseOfTriangle.py @@ -1,34 +1,40 @@ # coding=utf-8 -import math + + +__author__ = "ipetrash" + + import argparse +import math -__author__ = 'ipetrash' def createParser(): - parse = argparse.ArgumentParser(description=u"Программа высчитывает гипотенузу прямоугольного треугольника") - parse.add_argument("-a", type=int, help=u"Первый катет") - parse.add_argument("-b", type=int, help=u"второй катет") + parse = argparse.ArgumentParser( + description="Программа высчитывает гипотенузу прямоугольного треугольника" + ) + parse.add_argument("-a", type=int, help="Первый катет") + parse.add_argument("-b", type=int, help="второй катет") return parse def hypotenuse(a, b): - u""" + """ Функция высчитывает гипотенузу прямоугольного треугольника. :param a: Первый катет :param b: Второй катет :return: Гипотенуза """ - return math.sqrt(a ** 2 + b ** 2) + return math.sqrt(a**2 + b**2) -if __name__ == '__main__': +if __name__ == "__main__": parse = createParser() args = parse.parse_args() if args.a is not None and args.b is not None: a = args.a b = args.b - print(u"Катет а=%s, катет b=%s, гипотенуза с=%s" % (a, b, hypotenuse(a, b))) + print(f"Катет а={a}, катет b={b}, гипотенуза с={hypotenuse(a, b)}") else: - a = int(raw_input(u"Введи катет a: ")) - b = int(raw_input(u"Введи катет b: ")) - print(u"Гипотенуза с=%s" % hypotenuse(a, b)) \ No newline at end of file + a = int(input("Введи катет a: ")) + b = int(input("Введи катет b: ")) + print(f"Гипотенуза с={hypotenuse(a, b)}") diff --git a/LCM/LCM.py b/LCM/LCM.py index b59cc9fcf..802cb9f97 100644 --- a/LCM/LCM.py +++ b/LCM/LCM.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # EN: Least Common Multiple. @@ -15,9 +15,11 @@ def gcd(a, b): return b -if __name__ == '__main__': +if __name__ == "__main__": a, b = 7006652, 112307574 print("LCM: %s" % ((a * b) // gcd(a, b))) + # LCM: 637682405172 import math print("LCM: %s" % ((a * b) // math.gcd(a, b))) + # LCM: 637682405172 diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 000000000..8e4fbe851 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Ilya Petrash (gil9red) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/LuhnAlgorithm/LuhnAlgorithm.py b/LuhnAlgorithm/LuhnAlgorithm.py index 0fa9d6048..ed6fcbc45 100644 --- a/LuhnAlgorithm/LuhnAlgorithm.py +++ b/LuhnAlgorithm/LuhnAlgorithm.py @@ -1,13 +1,13 @@ # coding=utf-8 -__author__ = 'ipetrash' +__author__ = "ipetrash" -if __name__ == '__main__': - number_card = raw_input("Number card: ") +if __name__ == "__main__": + number_card = input("Number card: ") sum = 0 digits = [int(x) for x in number_card if x.isdigit()] digits.reverse() - for i in xrange(len(digits)): + for i in range(len(digits)): p = digits[i] if i % 2 is not 0: p *= 2 @@ -15,9 +15,9 @@ p -= 9 sum += p - print("sum: %d" % sum) + print(f"sum: {sum}") sum = 10 - (sum % 10) sum = 0 if sum is 10 else sum - print("checksum: %s" % sum) + print(f"checksum: {sum}") print("card number is correct" if (sum % 10 is 0) else "card number is incorrect") diff --git a/MergeListOfLists/MergeListOfLists.py b/MergeListOfLists/MergeListOfLists.py index 965b53674..f198a5d5a 100644 --- a/MergeListOfLists/MergeListOfLists.py +++ b/MergeListOfLists/MergeListOfLists.py @@ -1,9 +1,16 @@ -__author__ = 'ipetrash' +__author__ = "ipetrash" -## EN: Ways to merge a list of lists -## RU: Cпособы слияния списка списков -# source: http://habrahabr.ru/post/63539/ -def listmerge1(lstlst): + +# EN: Ways to merge a list of lists +# RU: Cпособы слияния списка списков + + +import operator +from functools import reduce + + +# Source: http://habrahabr.ru/post/63539/ +def list_merge_1(lstlst): all = [] for lst in lstlst: for el in lst: @@ -11,43 +18,37 @@ def listmerge1(lstlst): return all -def listmerge2(lstlst): +def list_merge_2(lstlst): all = [] for lst in lstlst: all = all + lst return all -def listmerge3(lstlst): +def list_merge_3(lstlst): all = [] for lst in lstlst: all.extend(lst) return all -from functools import reduce - -listmerge4a = lambda ll: reduce(lambda a, b: a + b, ll, []) -listmerge4b = lambda ll: sum(ll, []) - +list_merge_4_a = lambda ll: reduce(lambda a, b: a + b, ll, []) +list_merge_4_b = lambda ll: sum(ll, []) -listmerge5 = lambda ll: [el for lst in ll for el in lst] +list_merge_5 = lambda ll: [el for lst in ll for el in lst] - -listmerge6a = lambda s: reduce(lambda d, el: d.extend(el) or d, s, []) - -import operator -listmerge6b = lambda s: reduce(operator.iadd, s, []) +list_merge_6_a = lambda s: reduce(lambda d, el: d.extend(el) or d, s, []) +list_merge_6_b = lambda s: reduce(operator.iadd, s, []) -lstlst = ([6, 6], [1, 2, 3], [4, 5], [6], [7, 8], [9]) -print("List: ", lstlst) +lst_lst = ([6, 6], [1, 2, 3], [4, 5], [6], [7, 8], [9]) +print("List: ", lst_lst) print("Result:") -print("1. ", listmerge1(lstlst)) -print("2. ", listmerge2(lstlst)) -print("3. ", listmerge3(lstlst)) -print("4a. ", listmerge4a(lstlst)) -print("4b. ", listmerge4b(lstlst)) -print("5. ", listmerge5(lstlst)) -print("6a. ", listmerge6a(lstlst)) -print("6b. ", listmerge6b(lstlst)) \ No newline at end of file +print("1. ", list_merge_1(lst_lst)) +print("2. ", list_merge_2(lst_lst)) +print("3. ", list_merge_3(lst_lst)) +print("4a. ", list_merge_4_a(lst_lst)) +print("4b. ", list_merge_4_b(lst_lst)) +print("5. ", list_merge_5(lst_lst)) +print("6a. ", list_merge_6_a(lst_lst)) +print("6b. ", list_merge_6_b(lst_lst)) diff --git a/Moodle_examples/get_token.py b/Moodle_examples/get_token.py index 19bde3153..69047a1cf 100644 --- a/Moodle_examples/get_token.py +++ b/Moodle_examples/get_token.py @@ -1,32 +1,35 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" + + +import requests def get_token(username, password): params = { - 'username': username, - 'password': password, - 'service': 'moodle_mobile_app', + "username": username, + "password": password, + "service": "moodle_mobile_app", } - import requests - rs = requests.get('http://newlms.magtu.ru/login/token.php', params=params) + + rs = requests.get("http://newlms.magtu.ru/login/token.php", params=params) print(rs) json_data = rs.json() - print('Response: {}'.format(json_data)) + print(f"Response: {json_data}") - if 'errorcode' in json_data: - print(json_data['errorcode']) + if "errorcode" in json_data: + print(json_data["errorcode"]) return - return json_data['token'] + return json_data["token"] -USERNAME = '' -PASSWORD = '' +USERNAME = "" +PASSWORD = "" -if __name__ == '__main__': +if __name__ == "__main__": token = get_token(USERNAME, PASSWORD) - print('token: {}'.format(token)) + print(f"token: {token}") diff --git a/Moodle_examples/search_current_user.py b/Moodle_examples/search_current_user.py index 3e3f9ae6e..aca5fced5 100644 --- a/Moodle_examples/search_current_user.py +++ b/Moodle_examples/search_current_user.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import sys @@ -9,36 +9,36 @@ from get_token import get_token -USERNAME = '' -PASSWORD = '' +USERNAME = "" +PASSWORD = "" token = get_token(USERNAME, PASSWORD) params = { - 'wstoken': token, - 'wsfunction': 'core_user_get_users_by_field', - 'moodlewsrestformat': 'json', - 'field': 'username', - 'values[0]': USERNAME, + "wstoken": token, + "wsfunction": "core_user_get_users_by_field", + "moodlewsrestformat": "json", + "field": "username", + "values[0]": USERNAME, } -rs = requests.get('http://newlms.magtu.ru/webservice/rest/server.php', params=params) +rs = requests.get("http://newlms.magtu.ru/webservice/rest/server.php", params=params) print(rs) json_data = rs.json() -print('Response: {}'.format(json_data)) +print(f"Response: {json_data}") -if 'errorcode' in json_data: - print(json_data['errorcode']) +if "errorcode" in json_data: + print(json_data["errorcode"]) sys.exit() if not json_data: - print('Not found user "{}"'.format(USERNAME)) + print(f'Not found user "{USERNAME}"') sys.exit() info = json_data[0] -print(info['username']) -print(info['fullname']) -print(info['email']) -print(info['idnumber']) +print(info["username"]) +print(info["fullname"]) +print(info["email"]) +print(info["idnumber"]) diff --git a/Morse code with Python unary + and - operators.py b/Morse code with Python unary + and - operators.py index b5d5dfb2d..e0ae258e3 100644 --- a/Morse code with Python unary + and - operators.py +++ b/Morse code with Python unary + and - operators.py @@ -58,7 +58,7 @@ "(": "-.--.-", ")": "-.--.-", "'": ".----.", - "\"": ".-..-.", + '"': ".-..-.", "-": "-....-", "/": "-..-.", "?": "..--..", @@ -122,5 +122,5 @@ def morsify(s): _, ___ = Morse(), MorseWithSpace() print(+--+_ + -+_ + +_ + --_ + _ - _ + -+-+-___ - -+_ + +_ - _ + +++_ + -_ - +++_ - -++--_) - print(morsify('ПРИВЕТ ХАБР!')) # +--+_+-+_++_+--_+_-___++++_+-_-+++_+-+_--++--_ - print(+--+_+-+_++_+--_+_-___++++_+-_-+++_+-+_--++--_) + print(morsify("ПРИВЕТ ХАБР!")) # +--+_+-+_++_+--_+_-___++++_+-_-+++_+-+_--++--_ + print(+--+_ + -+_ + +_ + --_ + _ - ___ + +++_ + -_ - +++_ + -+_ - -++--_) diff --git a/MultiplicationTableByX/MultiplicationTableByX.py b/MultiplicationTableByX/MultiplicationTableByX.py index bf732c808..368fffdf8 100644 --- a/MultiplicationTableByX/MultiplicationTableByX.py +++ b/MultiplicationTableByX/MultiplicationTableByX.py @@ -1,8 +1,12 @@ -# coding=utf-8 +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + # Реализовать функцию-генератор строки с таблицей умножения на число Х. -__author__ = 'ipetrash' -if __name__ == '__main__': - x = int(raw_input("Input x: ")) - print(' '.join(['%d' % (i * x) for i in xrange(1, 10 + 1)])) \ No newline at end of file +if __name__ == "__main__": + x = int(input("Input x: ")) + print(" ".join(f"{i * x}" for i in range(1, 10 + 1))) diff --git a/MultiplicationTablesByM/MultiplicationTablesByM.py b/MultiplicationTablesByM/MultiplicationTablesByM.py index 3050071e6..68874ceaf 100644 --- a/MultiplicationTablesByM/MultiplicationTablesByM.py +++ b/MultiplicationTablesByM/MultiplicationTablesByM.py @@ -1,20 +1,25 @@ -# coding=utf-8 -import argparse +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" -__author__ = 'ipetrash' + +import argparse def create_parser(): - parser = argparse.ArgumentParser(description="The table of multiplication by M. Table compiled from M * a, to M * b, " - "where M, a, b are requested from the user.") + parser = argparse.ArgumentParser( + description="The table of multiplication by M. Table compiled from M * a, to M * b, " + "where M, a, b are requested from the user." + ) return parser -if __name__ == '__main__': +if __name__ == "__main__": create_parser().parse_args() - M = int(raw_input("M=")) - a = int(raw_input("a=")) - b = int(raw_input("b=")) + M = int(input("M=")) + a = int(input("a=")) + b = int(input("b=")) for x in range(a, b): - print("%d * %d = %d" % (M, x, M * x)) + print(f"{M:d} * {x:d} = {M * x:d}") diff --git a/No choice line edit.py b/No choice line edit.py index 28d567f2f..c8db94aac 100644 --- a/No choice line edit.py +++ b/No choice line edit.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """ @@ -14,31 +14,31 @@ class Widget(QWidget): - def __init__(self): + def __init__(self) -> None: super().__init__() - self.setWindowTitle('No choice') + self.setWindowTitle("No choice") self.line_edit_no_choice = QLineEdit() self.line_edit_no_choice.textEdited.connect(self.on_text_edited_no_choice) - self.line_edit_source = QLineEdit('Я придурок') + self.line_edit_source = QLineEdit("Я придурок") self.line_edit_source.textEdited.connect(self.line_edit_no_choice.clear) layout = QFormLayout() - layout.addRow('Скажи:', self.line_edit_source) + layout.addRow("Скажи:", self.line_edit_source) layout.addWidget(self.line_edit_no_choice) self.setLayout(layout) self.line_edit_no_choice.setFocus() - def on_text_edited_no_choice(self, text): - text = self.line_edit_source.text()[:len(text)] + def on_text_edited_no_choice(self, text) -> None: + text = self.line_edit_source.text()[: len(text)] self.line_edit_no_choice.setText(text) -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) w = Widget() diff --git a/ObjectWithArrayAccess.py b/ObjectWithArrayAccess.py index 8de66be6b..1886745e0 100644 --- a/ObjectWithArrayAccess.py +++ b/ObjectWithArrayAccess.py @@ -1,29 +1,29 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" class ObjectWithArrayAccess: - def __init__(self): + def __init__(self) -> None: self._fields = dict() def __getitem__(self, item): return self._fields.get(item) - def __setitem__(self, key, value): + def __setitem__(self, key, value) -> None: self._fields[key] = value - def __len__(self): + def __len__(self) -> int: return len(self._fields) -if __name__ == '__main__': +if __name__ == "__main__": obj = ObjectWithArrayAccess() - print(bool(obj)) # False - obj[0] = 'Hello' - obj[1] = 'world' - print(obj._fields) # {0: 'Hello', 1: 'world'} - print(len(obj)) # 2 - print(bool(obj)) # True + print(bool(obj)) # False + obj[0] = "Hello" + obj[1] = "world" + print(obj._fields) # {0: 'Hello', 1: 'world'} + print(len(obj)) # 2 + print(bool(obj)) # True print(obj[0], obj[1]) # Hello world diff --git a/OpenSSL_example/generate_selfsigned_cert.py b/OpenSSL_example/generate_selfsigned_cert.py index f86eaff25..c71bd75f5 100644 --- a/OpenSSL_example/generate_selfsigned_cert.py +++ b/OpenSSL_example/generate_selfsigned_cert.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://stackoverflow.com/a/60804101/5909792 @@ -21,8 +21,8 @@ def cert_gen( serial_number=0, validity_end_in_seconds=10 * 365 * 24 * 60 * 60, key_file="key.pem", - cert_file="cert.pem" -): + cert_file="cert.pem", +) -> None: # Create a key pair k = crypto.PKey() k.generate_key(crypto.TYPE_RSA, 4096) @@ -41,7 +41,7 @@ def cert_gen( cert.gmtime_adj_notAfter(validity_end_in_seconds) cert.set_issuer(cert.get_subject()) cert.set_pubkey(k) - cert.sign(k, 'sha512') + cert.sign(k, "sha512") with open(cert_file, "wb") as f: f.write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert)) @@ -50,5 +50,5 @@ def cert_gen( f.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, k)) -if __name__ == '__main__': +if __name__ == "__main__": cert_gen() diff --git a/OpenSSL_example/p12_to_pem.py b/OpenSSL_example/p12_to_pem.py index b35bd1f90..8c419fd66 100644 --- a/OpenSSL_example/p12_to_pem.py +++ b/OpenSSL_example/p12_to_pem.py @@ -1,24 +1,24 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import os.path from OpenSSL import crypto -def save_pem(p12_file_name, p12_password, pem_file_name=None): +def save_pem(p12_file_name, p12_password, pem_file_name=None) -> None: """Функция из p12 вытаскивает pem. Если pem_file_name не указан, сохраняется в той же папке, что и p12_file_name.""" if pem_file_name is None: - pem_file_name = os.path.splitext(p12_file_name)[0] + '.pem' + pem_file_name = os.path.splitext(p12_file_name)[0] + ".pem" # May require "" for empty password depending on version - p12 = crypto.load_pkcs12(open(p12_file_name, 'rb').read(), p12_password) + p12 = crypto.load_pkcs12(open(p12_file_name, "rb").read(), p12_password) - with open(pem_file_name, mode='wb') as f: + with open(pem_file_name, mode="wb") as f: # PEM formatted private key f.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, p12.get_privatekey())) @@ -26,7 +26,7 @@ def save_pem(p12_file_name, p12_password, pem_file_name=None): f.write(crypto.dump_certificate(crypto.FILETYPE_PEM, p12.get_certificate())) -if __name__ == '__main__': - p12_file_name = '' - p12_password = '' +if __name__ == "__main__": + p12_file_name = "" + p12_password = "" save_pem(p12_file_name, p12_password) diff --git a/Pascal Triangle fact.py b/Pascal Triangle fact.py index 34e3206eb..b131d2a92 100644 --- a/Pascal Triangle fact.py +++ b/Pascal Triangle fact.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import math @@ -9,4 +9,10 @@ N = 8 for n in range(N): - print(n, [math.factorial(n) // (math.factorial(m) * math.factorial(n - m)) for m in range(n+1)]) + print( + n, + [ + math.factorial(n) // (math.factorial(m) * math.factorial(n - m)) + for m in range(n + 1) + ], + ) diff --git a/Pascal Triangle pow11.py b/Pascal Triangle pow11.py index 9f93a114b..964cfe19e 100644 --- a/Pascal Triangle pow11.py +++ b/Pascal Triangle pow11.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://www.delftstack.com/howto/python/python-pascal-triangle/#print-pascal-s-triangle-by-computing-the-power-of-11-in-python @@ -13,4 +13,4 @@ N = 5 # NOTE: MAXIMUM for n in range(N): - print(list(map(int, str(11 ** n)))) + print(list(map(int, str(11**n)))) diff --git a/Pipe/Pipe.py b/Pipe/Pipe.py index 96048b125..17cf75506 100644 --- a/Pipe/Pipe.py +++ b/Pipe/Pipe.py @@ -1,20 +1,22 @@ -__author__ = 'ipetrash' +__author__ = "ipetrash" ## Example of using pipe module + # https://github.com/JulienPalard/Pipe # ru: http://habrahabr.ru/post/117679/ import pipe -if __name__ == '__main__': + +if __name__ == "__main__": print((i for i in range(10)) | pipe.as_list) # tuple to list - print([i for i in range(10)] | pipe.as_tuple) # list to tuple - print(((1,1), ('a', 2), (3, 'd')) | pipe.as_dict) # tuple to dict + print([i for i in range(10)] | pipe.as_tuple) # list to tuple + print(((1, 1), ("a", 2), (3, "d")) | pipe.as_dict) # tuple to dict print() # list of even numbers l = (i for i in range(10)) | pipe.where(lambda x: x % 2 is 0) | pipe.as_list c = l | pipe.count # count elements - print("List: {}, count: {}".format(l, c)) + print(f"List: {l}, count: {c}") print() # custom pipe: @@ -22,4 +24,4 @@ def custom_add(x): return sum(x) - print([1,2,3,4] | custom_add) # = 10 \ No newline at end of file + print([1, 2, 3, 4] | custom_add) # = 10 diff --git a/Printing all instances of a class/use__garbage_collector__gc.py b/Printing all instances of a class/use__garbage_collector__gc.py index 7016ddd00..d407f84e8 100644 --- a/Printing all instances of a class/use__garbage_collector__gc.py +++ b/Printing all instances of a class/use__garbage_collector__gc.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://stackoverflow.com/a/328882/5909792 @@ -15,7 +15,7 @@ def get_instances(class_: type) -> [type]: class X: - def __init__(self, name): + def __init__(self, name) -> None: self.name = name diff --git a/Printing all instances of a class/use__mixin_and_weakrefs.py b/Printing all instances of a class/use__mixin_and_weakrefs.py index 92bced1c7..3cca80828 100644 --- a/Printing all instances of a class/use__mixin_and_weakrefs.py +++ b/Printing all instances of a class/use__mixin_and_weakrefs.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://stackoverflow.com/a/328882/5909792 @@ -14,7 +14,7 @@ class KeepRefs: __refs__ = defaultdict(list) - def __init__(self): + def __init__(self) -> None: self.__refs__[self.__class__].append(weakref.ref(self)) @classmethod @@ -26,7 +26,7 @@ def get_instances(cls): class X(KeepRefs): - def __init__(self, name): + def __init__(self, name) -> None: super().__init__() self.name = name diff --git a/PyGithub_examples/config.py b/PyGithub_examples/config.py index 940a77379..88e342a0e 100644 --- a/PyGithub_examples/config.py +++ b/PyGithub_examples/config.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import os @@ -9,13 +9,12 @@ DIR = Path(__file__).resolve().parent -TOKEN_FILE_NAME = DIR / 'TOKEN.txt' +TOKEN_FILE_NAME = DIR / "TOKEN.txt" -TOKEN = os.environ.get('TOKEN') or TOKEN_FILE_NAME.read_text('utf-8').strip() +TOKEN = os.environ.get("TOKEN") or TOKEN_FILE_NAME.read_text("utf-8").strip() # http://user:password@proxy_host:proxy_port PROXY = None if PROXY: - import os - os.environ['http_proxy'] = PROXY + os.environ["http_proxy"] = PROXY diff --git a/PyGithub_examples/create_repo.py b/PyGithub_examples/create_repo.py index 181d18239..205493b1c 100644 --- a/PyGithub_examples/create_repo.py +++ b/PyGithub_examples/create_repo.py @@ -1,16 +1,18 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install pygithub from github import Github + from config import LOGIN, PASSWORD + # NOTE: delete repo: Settings -> -> # or repo.delete() -NEW_REPO = 'Test-Repo' +NEW_REPO = "Test-Repo" gh = Github(LOGIN, PASSWORD) diff --git a/PyGithub_examples/download_gist_files.py b/PyGithub_examples/download_gist_files.py index 5537844d5..bff993a31 100644 --- a/PyGithub_examples/download_gist_files.py +++ b/PyGithub_examples/download_gist_files.py @@ -1,33 +1,38 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" +import os + # pip install pygithub from github import Github + from config import LOGIN, PASSWORD + gh = Github(LOGIN, PASSWORD) -gist = gh.get_gist('2f80a34fb601cd685353') +gist = gh.get_gist("2f80a34fb601cd685353") # Filter by commit version history_list = [ - x for x in gist.history if x.version in [ - 'd4572ecd5b66ac06a64973cd35ef71936ebf84cd', - '8040c03f7ee008b0d3ce315a34e4c364f164d939', - '798e7a89e02cac541e1866ac16278ce9a068cf68', - '758e49da06e0bb5b6b455e9035a47047ece714d0', + x + for x in gist.history + if x.version in [ + "d4572ecd5b66ac06a64973cd35ef71936ebf84cd", + "8040c03f7ee008b0d3ce315a34e4c364f164d939", + "798e7a89e02cac541e1866ac16278ce9a068cf68", + "758e49da06e0bb5b6b455e9035a47047ece714d0", ] ] -import os -if not os.path.exists('gist_file'): - os.mkdir('gist_file') +if not os.path.exists("gist_file"): + os.mkdir("gist_file") for history in history_list: for gist_file_name, gist_file in history.files.items(): - file_name = 'gist_file/{}_{}.txt'.format(history.version, gist_file_name) + file_name = f"gist_file/{history.version}_{gist_file_name}.txt" - with open(file_name, mode='w', encoding='utf-8') as f: + with open(file_name, mode="w", encoding="utf-8") as f: f.write(gist_file.content) diff --git a/PyGithub_examples/get_rate_limit_info.py b/PyGithub_examples/get_rate_limit_info.py index 5750b88e6..c90249a1c 100644 --- a/PyGithub_examples/get_rate_limit_info.py +++ b/PyGithub_examples/get_rate_limit_info.py @@ -1,22 +1,24 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install pygithub from github import Github + from config import LOGIN, PASSWORD + gh = Github(LOGIN, PASSWORD) -print('With auth:') -print(' rate_limiting:', gh.rate_limiting) -print(' rate_limiting_resettime:', gh.rate_limiting_resettime) -print(' gh.get_rate_limit():', gh.get_rate_limit()) +print("With auth:") +print(" rate_limiting:", gh.rate_limiting) +print(" rate_limiting_resettime:", gh.rate_limiting_resettime) +print(" gh.get_rate_limit():", gh.get_rate_limit()) print() gh = Github() -print('Without auth:') -print(' rate_limiting:', gh.rate_limiting) -print(' rate_limiting_resettime:', gh.rate_limiting_resettime) -print(' gh.get_rate_limit():', gh.get_rate_limit()) +print("Without auth:") +print(" rate_limiting:", gh.rate_limiting) +print(" rate_limiting_resettime:", gh.rate_limiting_resettime) +print(" gh.get_rate_limit():", gh.get_rate_limit()) diff --git a/PyGithub_examples/gist_history_to_sqlite_db.py b/PyGithub_examples/gist_history_to_sqlite_db.py index 4374ed063..8cd7ca839 100644 --- a/PyGithub_examples/gist_history_to_sqlite_db.py +++ b/PyGithub_examples/gist_history_to_sqlite_db.py @@ -1,21 +1,22 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" +import sqlite3 from config import TOKEN def create_connect(): - import sqlite3 - return sqlite3.connect('gist_commits.sqlite') + return sqlite3.connect("gist_commits.sqlite") -def init_db(): +def init_db() -> None: # Создание базы и таблицы with create_connect() as connect: - connect.execute(''' + connect.execute( + """ CREATE TABLE IF NOT EXISTS GistFile ( id INTEGER PRIMARY KEY, commit_hash TEXT NOT NULL, @@ -25,16 +26,20 @@ def init_db(): CONSTRAINT raw_url_unique UNIQUE (commit_hash, raw_url) ); - ''') + """ + ) connect.commit() -if __name__ == '__main__': - init_db() +if __name__ == "__main__": + import traceback # pip install pygithub from github import Github + + init_db() + gh = Github(TOKEN) # # # # OR: @@ -43,31 +48,30 @@ def init_db(): # # documentation for more details.)", 'documentation_url': 'https://developer.github.com/v3/#rate-limiting'}" # gh = Github() - gist = gh.get_gist('2f80a34fb601cd685353') + gist = gh.get_gist("2f80a34fb601cd685353") print(gist) - print('History ({}):'.format(len(gist.history))) + print(f"History ({len(gist.history)}):") with create_connect() as connect: try: for history in reversed(gist.history): - print(' committed_at: {}, version: {}, files: {}'.format( - history.committed_at, history.version, history.files) + print( + f" committed_at: {history.committed_at}, version: {history.version}, files: {history.files}" ) - if 'gistfile1.txt' not in history.files: + if "gistfile1.txt" not in history.files: print(' Not found file "gistfile1.txt"!') continue - file = history.files['gistfile1.txt'] + file = history.files["gistfile1.txt"] # print(' url: {}'.format(file.raw_url)) # print(' [{}]: {}'.format(len(file.content), repr(file.content)[:150])) # print() connect.execute( - 'INSERT OR IGNORE INTO GistFile (commit_hash, committed_at, raw_url, content) VALUES (?, ?, ?, ?)', - (history.version, history.committed_at, file.raw_url, file.content) + "INSERT OR IGNORE INTO GistFile (commit_hash, committed_at, raw_url, content) VALUES (?, ?, ?, ?)", + (history.version, history.committed_at, file.raw_url, file.content), ) except Exception as e: - import traceback - print('ERROR: {}:\n\n{}'.format(e, traceback.format_exc())) + print(f"ERROR: {e}:\n\n{traceback.format_exc()}") diff --git a/PyGithub_examples/print_gist_history.py b/PyGithub_examples/print_gist_history.py index e7bad839c..e9839fcec 100644 --- a/PyGithub_examples/print_gist_history.py +++ b/PyGithub_examples/print_gist_history.py @@ -1,13 +1,15 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install pygithub from github import Github + from config import LOGIN, PASSWORD + gh = Github(LOGIN, PASSWORD) # # # # OR: @@ -16,15 +18,15 @@ # # documentation for more details.)", 'documentation_url': 'https://developer.github.com/v3/#rate-limiting'}" # gh = Github() -gist = gh.get_gist('2f80a34fb601cd685353') +gist = gh.get_gist("2f80a34fb601cd685353") print(gist) -print('created_at: {}, updated_at: {}'.format(gist.created_at, gist.updated_at)) +print(f"created_at: {gist.created_at}, updated_at: {gist.updated_at}") print() -print('files ({}): {}'.format(len(gist.files), gist.files)) +print(f"files ({len(gist.files)}): {gist.files}") print() -print('history ({}):'.format(len(gist.history))) +print(f"history ({len(gist.history)}):") # From new to old: # for history in gist.history: @@ -34,11 +36,11 @@ # # First 10: for history in list(reversed(gist.history))[:10]: - print(' committed_at: {}, version: {}, files: {}'.format( - history.committed_at, history.version, history.files) + print( + f" committed_at: {history.committed_at}, version: {history.version}, files: {history.files}" ) - file = history.files['gistfile1.txt'] - print(' url: {}'.format(file.raw_url)) - print(' [{}]: {}'.format(len(file.content), repr(file.content)[:150])) + file = history.files["gistfile1.txt"] + print(f" url: {file.raw_url}") + print(f" [{len(file.content)}]: {repr(file.content)[:150]}") print() diff --git a/PyGithub_examples/print_user_gists.py b/PyGithub_examples/print_user_gists.py index 17e220213..456960f6e 100644 --- a/PyGithub_examples/print_user_gists.py +++ b/PyGithub_examples/print_user_gists.py @@ -1,13 +1,15 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install pygithub from github import Github + from config import LOGIN, PASSWORD + # TODO: сделать версию с gui gh = Github(LOGIN, PASSWORD) @@ -19,6 +21,6 @@ print(i, gist.description, gist.html_url) for file, gist_file in gist.files.items(): - print(' ', file, gist_file.raw_url) + print(" ", file, gist_file.raw_url) print() diff --git a/PyGithub_examples/print_user_repo.py b/PyGithub_examples/print_user_repo.py index ba777502c..c01d1add6 100644 --- a/PyGithub_examples/print_user_repo.py +++ b/PyGithub_examples/print_user_repo.py @@ -1,17 +1,21 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" +from collections import Counter, defaultdict + # pip install pygithub from github import Github + from config import LOGIN, PASSWORD + # TODO: сделать версию с gui # Пользователь, чьи репозитории собираемся вывести в консоль -user = 'gil9red' +user = "gil9red" gh = Github(LOGIN, PASSWORD) @@ -19,14 +23,10 @@ repo_by_name_dict = {repo.name: repo for repo in gh.get_user(user).get_repos()} # Подсчет количества репозиториев по языкам -from collections import Counter - counter_langs = Counter(repo.language for _, repo in repo_by_name_dict.items()) counter_langs = sorted(counter_langs.items(), key=lambda x: x[1], reverse=True) # Словарь, у которого ключом является язык репозитория, значением -- объект репозитория -from collections import defaultdict - repo_by_lang_dict = defaultdict(list) for _, repo in repo_by_name_dict.items(): @@ -38,10 +38,11 @@ # Вывод репозиториев в соответствии их языку и популярности for lang, number in counter_langs: - print("{} ({}):".format(lang, number)) + print(f"{lang} ({number}):") for i, repo in enumerate(repo_by_lang_dict[lang], 1): - print(" {}. {}: '{}' ({} star): {}".format(i, repo.name, repo.description, - repo.stargazers_count, repo.html_url)) + print( + f" {i}. {repo.name}: '{repo.description}' ({repo.stargazers_count} star): {repo.html_url}" + ) print() diff --git a/PyGithub_examples/search_by_code.py b/PyGithub_examples/search_by_code.py index 4b3ce1c1a..fc99b7b89 100644 --- a/PyGithub_examples/search_by_code.py +++ b/PyGithub_examples/search_by_code.py @@ -1,17 +1,21 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" +from base64 import b64decode as base64_to_text + # pip install pygithub from github import Github + from config import LOGIN, PASSWORD + gh = Github(LOGIN, PASSWORD) # print(list(gh.search_code('requests auth github filename:.py language:python')[:5])) -search_query = 'requests auth github filename:.py language:python' +search_query = "requests auth github filename:.py language:python" # print(gh.search_code(search_query).totalCount) # The Search API has a custom rate limit. For requests using Basic Authentication, OAuth, or client ID and @@ -35,12 +39,12 @@ print(dir(data[0])) print(data[0].url) print(data[0].content) -from base64 import b64decode as base64_to_text + print(base64_to_text(data[0].content.encode()).decode()) print(data[0].html_url) # get user from repo url -user = data[0].html_url.split('/')[3] +user = data[0].html_url.split("/")[3] print(user) # i = 1 diff --git a/PyMsgBox__examples/alert.py b/PyMsgBox__examples/alert.py index 55574341f..7fd767637 100644 --- a/PyMsgBox__examples/alert.py +++ b/PyMsgBox__examples/alert.py @@ -1,10 +1,12 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install PyMsgBox from pymsgbox import alert -button = alert(text='My Text', title='My Title', button='OK') + + +button = alert(text="My Text", title="My Title", button="OK") print(button) diff --git a/PyMsgBox__examples/confirm.py b/PyMsgBox__examples/confirm.py index 939955a0f..67bddc287 100644 --- a/PyMsgBox__examples/confirm.py +++ b/PyMsgBox__examples/confirm.py @@ -1,10 +1,12 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install PyMsgBox from pymsgbox import confirm -button = confirm(text='My Text', title='My Title', buttons=['OK', 'Cancel']) + + +button = confirm(text="My Text", title="My Title", buttons=["OK", "Cancel"]) print(button) diff --git a/PyMsgBox__examples/password.py b/PyMsgBox__examples/password.py index 27af73228..2b2d0bd8e 100644 --- a/PyMsgBox__examples/password.py +++ b/PyMsgBox__examples/password.py @@ -1,13 +1,15 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install PyMsgBox from pymsgbox import password -result = password(text='My Text', title='My Title', default='Default Text', mask='*') + + +result = password(text="My Text", title="My Title", default="Default Text", mask="*") print(result) -result = password(text='My Text', title='My Title', default='Default Text', mask='?') +result = password(text="My Text", title="My Title", default="Default Text", mask="?") print(result) diff --git a/PyMsgBox__examples/prompt.py b/PyMsgBox__examples/prompt.py index 15fe0f43b..a1d3a57f4 100644 --- a/PyMsgBox__examples/prompt.py +++ b/PyMsgBox__examples/prompt.py @@ -1,10 +1,12 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install PyMsgBox from pymsgbox import prompt -result = prompt(text='My Text', title='My Title', default='Default Text') + + +result = prompt(text="My Text", title="My Title", default="Default Text") print(result) diff --git a/PyOpenGLExample/checkerboard.py b/PyOpenGLExample/checkerboard.py index e37968c4b..c95783a60 100644 --- a/PyOpenGLExample/checkerboard.py +++ b/PyOpenGLExample/checkerboard.py @@ -10,7 +10,7 @@ """ -def initFun(): +def initFun() -> None: glClearColor(1.0, 1.0, 1.0, 0.0) glColor3f(0.0, 0.0, 0.0) glMatrixMode(GL_PROJECTION) @@ -18,7 +18,7 @@ def initFun(): gluOrtho2D(0.0, 640.0, 0.0, 480.0) -def displayFun(): +def displayFun() -> None: CELL_SIZE = 40 glClear(GL_COLOR_BUFFER_BIT) @@ -32,7 +32,7 @@ def displayFun(): y2 = y1 + CELL_SIZE if (i % 2 and not j % 2) or (not i % 2 and j % 2): - glColor3f(0xff, 0xff, 0xff) # white + glColor3f(0xFF, 0xFF, 0xFF) # white else: glColor3f(0x0, 0x0, 0x0) # black @@ -41,11 +41,11 @@ def displayFun(): glFlush() -if __name__ == '__main__': +if __name__ == "__main__": glutInit() glutInitWindowSize(640, 480) glutCreateWindow(b"DrawSquares") glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB) glutDisplayFunc(displayFun) initFun() - glutMainLoop() \ No newline at end of file + glutMainLoop() diff --git a/PyOpenGLExample/gingerbread.py b/PyOpenGLExample/gingerbread.py index 955e9d203..3a788504c 100644 --- a/PyOpenGLExample/gingerbread.py +++ b/PyOpenGLExample/gingerbread.py @@ -18,7 +18,7 @@ y = 121 -def initFun(): +def initFun() -> None: glClearColor(1.0, 1.0, 1.0, 0.0) glColor3f(0.0, 0.0, 0.0) glPointSize(4.0) @@ -27,7 +27,7 @@ def initFun(): gluOrtho2D(0.0, 640.0, 0.0, 480.0) -def mouseFun(button, state, xIn, yIn): +def mouseFun(button, state, xIn, yIn) -> None: global x global y if button == GLUT_LEFT_BUTTON and state == GLUT_DOWN: @@ -37,7 +37,7 @@ def mouseFun(button, state, xIn, yIn): glutPostRedisplay() -def displayFun(): +def displayFun() -> None: global x global y glClear(GL_COLOR_BUFFER_BIT) @@ -54,7 +54,7 @@ def displayFun(): glFlush() -if __name__ == '__main__': +if __name__ == "__main__": glutInit() glutInitWindowSize(640, 480) glutCreateWindow(b"Gingerbread") @@ -62,4 +62,4 @@ def displayFun(): glutDisplayFunc(displayFun) glutMouseFunc(mouseFun) initFun() - glutMainLoop() \ No newline at end of file + glutMainLoop() diff --git a/PyOpenGLExample/helloworld.py b/PyOpenGLExample/helloworld.py index 6a6fbb4cc..eaaa108d3 100644 --- a/PyOpenGLExample/helloworld.py +++ b/PyOpenGLExample/helloworld.py @@ -14,7 +14,7 @@ """ -def initFun(): +def initFun() -> None: glClearColor(1.0, 1.0, 1.0, 0.0) glColor3f(0.0, 0.0, 0.0) glPointSize(4.0) @@ -23,7 +23,7 @@ def initFun(): gluOrtho2D(0.0, 640.0, 0.0, 480.0) -def displayFun(): +def displayFun() -> None: glClear(GL_COLOR_BUFFER_BIT) glBegin(GL_POINTS) glVertex2i(100, 50) @@ -33,11 +33,11 @@ def displayFun(): glFlush() -if __name__ == '__main__': +if __name__ == "__main__": glutInit() glutInitWindowSize(640, 480) glutCreateWindow(b"Drawdots") glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB) glutDisplayFunc(displayFun) initFun() - glutMainLoop() \ No newline at end of file + glutMainLoop() diff --git a/PyOpenGLExample/maze.py b/PyOpenGLExample/maze.py index 6ecb74b0b..0ed046b6c 100644 --- a/PyOpenGLExample/maze.py +++ b/PyOpenGLExample/maze.py @@ -38,7 +38,7 @@ class MazeCell: - def __init__(self, x, y): + def __init__(self, x, y) -> None: self.x = x self.y = y self.wall_north = True @@ -49,7 +49,7 @@ def __init__(self, x, y): class Maze: - def __init__(self, dimx, dimy): + def __init__(self, dimx, dimy) -> None: self.numx = dimx self.numy = dimy @@ -71,32 +71,61 @@ def __init__(self, dimx, dimy): cell.visited = True # If a cell is not connected, see if we can connected it to the path - if cell.wall_north and cell.wall_east and cell.wall_south and cell.wall_north: - if cell.x > 0 and self.cells[(cell.x - 1) + (cell.y + 0) * dimx].visited: + if ( + cell.wall_north + and cell.wall_east + and cell.wall_south + and cell.wall_north + ): + if ( + cell.x > 0 + and self.cells[(cell.x - 1) + (cell.y + 0) * dimx].visited + ): cell.wall_west = False self.cells[(cell.x - 1) + (cell.y + 0) * dimx].wall_east = False - elif cell.x < (dimx - 2) and self.cells[(cell.x + 1) + (cell.y + 0) * dimx].visited: + elif ( + cell.x < (dimx - 2) + and self.cells[(cell.x + 1) + (cell.y + 0) * dimx].visited + ): cell.wall_east = False self.cells[(cell.x + 1) + (cell.y + 0) * dimx].wall_west = False - elif cell.y > 0 and self.cells[(cell.x + 0) + (cell.y - 1) * dimx].visited: + elif ( + cell.y > 0 + and self.cells[(cell.x + 0) + (cell.y - 1) * dimx].visited + ): cell.wall_south = False self.cells[(cell.x + 0) + (cell.y - 1) * dimx].wall_north = False - elif cell.y < (dimy - 2) and self.cells[(cell.x + 0) + (cell.y + 1) * dimx].visited: + elif ( + cell.y < (dimy - 2) + and self.cells[(cell.x + 0) + (cell.y + 1) * dimx].visited + ): cell.wall_north = False self.cells[(cell.x + 0) + (cell.y + 1) * dimx].wall_south = False # Append neighbors if they are not yet in the path. num = 0 - if cell.x > 0 and not self.cells[(cell.x - 1) + (cell.y + 0) * dimx].visited: + if ( + cell.x > 0 + and not self.cells[(cell.x - 1) + (cell.y + 0) * dimx].visited + ): cell_list.append(self.cells[(cell.x - 1) + (cell.y + 0) * dimx]) num += 1 - if cell.x < (dimx - 1) and not self.cells[(cell.x + 1) + (cell.y + 0) * dimx].visited: + if ( + cell.x < (dimx - 1) + and not self.cells[(cell.x + 1) + (cell.y + 0) * dimx].visited + ): cell_list.append(self.cells[(cell.x + 1) + (cell.y + 0) * dimx]) num += 1 - if cell.y < (dimy - 1) and not self.cells[(cell.x + 0) + (cell.y + 1) * dimx].visited: + if ( + cell.y < (dimy - 1) + and not self.cells[(cell.x + 0) + (cell.y + 1) * dimx].visited + ): cell_list.append(self.cells[(cell.x + 0) + (cell.y + 1) * dimx]) num += 1 - if cell.y > 0 and not self.cells[(cell.x + 0) + (cell.y - 1) * dimx].visited: + if ( + cell.y > 0 + and not self.cells[(cell.x + 0) + (cell.y - 1) * dimx].visited + ): cell_list.append(self.cells[(cell.x + 0) + (cell.y - 1) * dimx]) num += 1 @@ -123,11 +152,11 @@ def __init__(self, dimx, dimy): cell.wall_east = False conn.wall_west = False - # Push it back on the list since this is our next node + # Push it back on the list since this is our next node cell_list.append(conn) - def draw(self): - """ Draws the field """ + def draw(self) -> None: + """Draws the field""" glBegin(GL_LINES) for i in range(0, self.numx * self.numy): x = i % self.numx @@ -155,7 +184,7 @@ def draw(self): maze = Maze(MAZECOLS, MAZEROWS) -def init_fun(): +def init_fun() -> None: glClearColor(1.0, 1.0, 1.0, 0.0) glColor3f(0.0, 0.0, 0.0) glMatrixMode(GL_PROJECTION) @@ -164,17 +193,17 @@ def init_fun(): gluOrtho2D(-1.0, WIDTH, -1.0, HEIGHT) -def display_fun(): +def display_fun() -> None: glClear(GL_COLOR_BUFFER_BIT) maze.draw() glFlush() -if __name__ == '__main__': +if __name__ == "__main__": glutInit() glutInitWindowSize(WIDTH, HEIGHT) glutCreateWindow(b"Maze") glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB) glutDisplayFunc(display_fun) init_fun() - glutMainLoop() \ No newline at end of file + glutMainLoop() diff --git a/PyOpenGLExample/mouse.py b/PyOpenGLExample/mouse.py index e8b8ec2df..77a6f7bbb 100644 --- a/PyOpenGLExample/mouse.py +++ b/PyOpenGLExample/mouse.py @@ -18,7 +18,7 @@ class Point: - def __init__(self, x, y): + def __init__(self, x, y) -> None: self.x = x self.y = y @@ -26,7 +26,7 @@ def __init__(self, x, y): points = [] -def initFun(): +def initFun() -> None: glClearColor(1.0, 1.0, 1.0, 0.0) glColor3f(0.0, 0.0, 0.0) glMatrixMode(GL_PROJECTION) @@ -34,7 +34,7 @@ def initFun(): gluOrtho2D(0.0, 640.0, 0.0, 480.0) -def displayFun(): +def displayFun() -> None: global points glClear(GL_COLOR_BUFFER_BIT) glBegin(GL_LINE_STRIP) @@ -45,7 +45,7 @@ def displayFun(): glFlush() -def mouseFun(button, state, x, y): +def mouseFun(button, state, x, y) -> None: global points if button == GLUT_LEFT_BUTTON and state == GLUT_DOWN: p = Point(x, 480 - y) @@ -56,7 +56,7 @@ def mouseFun(button, state, x, y): glutPostRedisplay() -if __name__ == '__main__': +if __name__ == "__main__": glutInit() glutInitWindowSize(640, 480) glutCreateWindow(b"Polyline") @@ -64,4 +64,4 @@ def mouseFun(button, state, x, y): glutDisplayFunc(displayFun) glutMouseFunc(mouseFun) initFun() - glutMainLoop() \ No newline at end of file + glutMainLoop() diff --git a/PyOpenGLExample/print_text.py b/PyOpenGLExample/print_text.py index c943cc6a3..1a6c62e32 100644 --- a/PyOpenGLExample/print_text.py +++ b/PyOpenGLExample/print_text.py @@ -9,7 +9,7 @@ height = 0 -def glSetup(w, h): +def glSetup(w, h) -> None: global width, height width = w height = h @@ -31,7 +31,7 @@ def glSetup(w, h): glMatrixMode(GL_MODELVIEW) -def glResize(w, h): +def glResize(w, h) -> None: global width, height width = w height = h @@ -43,18 +43,18 @@ def glResize(w, h): glMatrixMode(GL_MODELVIEW) -def glSetupCam(): +def glSetupCam() -> None: gluPerspective(45.0, float(width) / float(height), 0.1, 100.0) gluLookAt(0, 0, 5, 0, 0, 0, 0, 1, 0) -def glDraw(): +def glDraw() -> None: glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) glLoadIdentity() # render objects here - # print text after rendering world so it is on top + # print text after rendering world so it is on top glut_print(10, 15, GLUT_BITMAP_9_BY_15, "Hello World", 1.0, 1.0, 1.0, 1.0) @@ -62,7 +62,7 @@ def glDraw(): # glut_print adapted from http://stackoverflow.com/questions/12837747/print-text-with-glut-and-python -def glut_print(x, y, font, text, r, g, b, a): +def glut_print(x, y, font, text, r, g, b, a) -> None: glMatrixMode(GL_PROJECTION) glLoadIdentity() gluOrtho2D(0.0, width, height, 0.0) @@ -80,13 +80,13 @@ def glut_print(x, y, font, text, r, g, b, a): glDisable(GL_BLEND) -def keyPressed(*args): - ESCAPE = b'\x1b' +def keyPressed(*args) -> None: + ESCAPE = b"\x1b" if args[0] == ESCAPE: sys.exit() -if __name__ == '__main__': +if __name__ == "__main__": glutInit() glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH) @@ -100,8 +100,8 @@ def keyPressed(*args): glutReshapeFunc(glResize) glutKeyboardFunc(keyPressed) - #setup things up + # setup things up glSetup(640, 480) - #start rendering - glutMainLoop() \ No newline at end of file + # start rendering + glutMainLoop() diff --git a/PyOpenGLExample/reshape.py b/PyOpenGLExample/reshape.py index b781e2899..d6db419d7 100644 --- a/PyOpenGLExample/reshape.py +++ b/PyOpenGLExample/reshape.py @@ -19,7 +19,7 @@ """ -def init_fun(): +def init_fun() -> None: glClearColor(1.0, 1.0, 1.0, 0.0) glColor3f(0.0, 0.0, 0.0) glMatrixMode(GL_PROJECTION) @@ -27,7 +27,7 @@ def init_fun(): gluOrtho2D(-100, 100, -100, 100) -def reshape_fun(w, h): +def reshape_fun(w, h) -> None: glViewport(0, 0, w, h) # if w > h: # glViewport((w - h) / 2, 0, h, h) @@ -35,7 +35,7 @@ def reshape_fun(w, h): # glViewport(0, (h - w) / 2, w, w) -def display_fun(): +def display_fun() -> None: glClear(GL_COLOR_BUFFER_BIT) glColor3f(0.0, 0.0, 1.0) glutWireTeapot(40) @@ -50,7 +50,7 @@ def display_fun(): glFlush() -if __name__ == '__main__': +if __name__ == "__main__": glutInit() glutInitWindowSize(640, 480) glutCreateWindow(b"Viewport") @@ -58,4 +58,4 @@ def display_fun(): glutDisplayFunc(display_fun) glutReshapeFunc(reshape_fun) init_fun() - glutMainLoop() \ No newline at end of file + glutMainLoop() diff --git a/PyOpenGLExample/rosette.py b/PyOpenGLExample/rosette.py index a3125f16b..1668d4bc2 100644 --- a/PyOpenGLExample/rosette.py +++ b/PyOpenGLExample/rosette.py @@ -23,7 +23,7 @@ RADIUS = 95 -def init_fun(): +def init_fun() -> None: glClearColor(1.0, 1.0, 1.0, 0.0) glColor3f(0.0, 0.0, 0.0) glMatrixMode(GL_PROJECTION) @@ -31,7 +31,7 @@ def init_fun(): gluOrtho2D(-100, 100, -100, 100) -def reshape_fun(w, h): +def reshape_fun(w, h) -> None: glViewport(0, 0, w, h) # if w > h: @@ -40,7 +40,7 @@ def reshape_fun(w, h): # glViewport(0, (h - w) / 2, w, w) -def display_fun(): +def display_fun() -> None: glClear(GL_COLOR_BUFFER_BIT) glColor3f(0.0, 0.0, 1.0) xpts = [] @@ -58,7 +58,7 @@ def display_fun(): glFlush() -if __name__ == '__main__': +if __name__ == "__main__": glutInit() glutInitWindowSize(400, 400) glutCreateWindow(b"Rosette") @@ -66,4 +66,4 @@ def display_fun(): glutDisplayFunc(display_fun) glutReshapeFunc(reshape_fun) init_fun() - glutMainLoop() \ No newline at end of file + glutMainLoop() diff --git a/PyOpenGLExample/scatter.py b/PyOpenGLExample/scatter.py index 10d81e11b..85ec9bbf4 100644 --- a/PyOpenGLExample/scatter.py +++ b/PyOpenGLExample/scatter.py @@ -19,7 +19,7 @@ """ -def initFun(): +def initFun() -> None: glClearColor(1.0, 1.0, 1.0, 0.0) glColor3f(0.0, 0.0, 0.0) glPointSize(1.0) @@ -29,7 +29,7 @@ def initFun(): def getRandom(mode): - """ + """ Wrapper around several RNG's. If mode is 0 random.randint() is used, if mode is one a guassian distribution is used. """ @@ -39,7 +39,7 @@ def getRandom(mode): return random.gauss(200, 40) % 400 -def displayFun(): +def displayFun() -> None: glClear(GL_COLOR_BUFFER_BIT) glBegin(GL_POINTS) @@ -54,11 +54,11 @@ def displayFun(): glFlush() -if __name__ == '__main__': +if __name__ == "__main__": glutInit() glutInitWindowSize(400, 400) glutCreateWindow(b"Scatter") glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB) glutDisplayFunc(displayFun) initFun() - glutMainLoop() \ No newline at end of file + glutMainLoop() diff --git a/PyOpenGLExample/sierpinski.py b/PyOpenGLExample/sierpinski.py index bb1ca0d67..3e10e1897 100644 --- a/PyOpenGLExample/sierpinski.py +++ b/PyOpenGLExample/sierpinski.py @@ -21,7 +21,7 @@ """ -def initFun(): +def initFun() -> None: glClearColor(1.0, 1.0, 1.0, 0.0) glColor3f(0.0, 0.0, 0.0) glPointSize(1.0) @@ -30,7 +30,7 @@ def initFun(): gluOrtho2D(0.0, 640.0, 0.0, 480.0) -def displayFun(): +def displayFun() -> None: glClear(GL_COLOR_BUFFER_BIT) glBegin(GL_POINTS) @@ -49,7 +49,7 @@ def displayFun(): glFlush() -if __name__ == '__main__': +if __name__ == "__main__": # This is the current implementation, note that all points are recalculated on # a window update, for any other purpose than showing off with how simply it is # to create this eyecode you'd probably want to precalculate the dots and put them @@ -61,4 +61,4 @@ def displayFun(): glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB) glutDisplayFunc(displayFun) initFun() - glutMainLoop() \ No newline at end of file + glutMainLoop() diff --git a/PyOpenGLExample/squares.py b/PyOpenGLExample/squares.py index 364ba43c4..b75d7d0a2 100644 --- a/PyOpenGLExample/squares.py +++ b/PyOpenGLExample/squares.py @@ -16,7 +16,7 @@ """ -def initFun(): +def initFun() -> None: glClearColor(1.0, 1.0, 1.0, 0.0) glColor3f(0.0, 0.0, 0.0) glMatrixMode(GL_PROJECTION) @@ -24,21 +24,25 @@ def initFun(): gluOrtho2D(0.0, 640.0, 0.0, 480.0) -def displayFun(): +def displayFun() -> None: glClear(GL_COLOR_BUFFER_BIT) for i in range(0, 25): - gray = idx = random.randint(0, 25) / 25.0 + gray = random.randint(0, 25) / 25.0 glColor3f(gray, gray, gray) - glRecti(random.randint(0, 640), random.randint(0, 480), - random.randint(0, 640), random.randint(0, 480)) + glRecti( + random.randint(0, 640), + random.randint(0, 480), + random.randint(0, 640), + random.randint(0, 480), + ) glFlush() -if __name__ == '__main__': +if __name__ == "__main__": glutInit() glutInitWindowSize(640, 480) glutCreateWindow(b"DrawSquares") glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB) glutDisplayFunc(displayFun) initFun() - glutMainLoop() \ No newline at end of file + glutMainLoop() diff --git a/PyOpenGLExample/turtle.py b/PyOpenGLExample/turtle.py index 486b7ac49..4b8e94969 100644 --- a/PyOpenGLExample/turtle.py +++ b/PyOpenGLExample/turtle.py @@ -23,8 +23,8 @@ angle = 0.0 -def reset(): - """ Reset the position to the origin """ +def reset() -> None: + """Reset the position to the origin""" global curX global curY global angle @@ -34,20 +34,20 @@ def reset(): angle = 0.0 -def turnTo(deg): - """ Turn to a certain angle """ +def turnTo(deg) -> None: + """Turn to a certain angle""" global angle angle = deg -def turn(deg): - """ Turn a certain number of degrees """ +def turn(deg) -> None: + """Turn a certain number of degrees""" global angle angle += deg -def forw(len, visible): - """ Move forward over a certain distance """ +def forw(len, visible) -> None: + """Move forward over a certain distance""" global curX global curY tmpX = curX @@ -61,7 +61,7 @@ def forw(len, visible): glEnd() -def initFun(): +def initFun() -> None: glClearColor(1.0, 1.0, 1.0, 0.0) glColor3f(0.0, 0.0, 0.0) glMatrixMode(GL_PROJECTION) @@ -69,7 +69,7 @@ def initFun(): gluOrtho2D(-100, 100, -100, 100) -def reshapeFun(w, h): +def reshapeFun(w, h) -> None: glViewport(0, 0, w, h) # if w > h: # glViewport((w-h)/2,0,h,h) @@ -77,7 +77,7 @@ def reshapeFun(w, h): # glViewport(0,(h-w)/2,w,w) -def turtle_1(): +def turtle_1() -> None: glClear(GL_COLOR_BUFFER_BIT) reset() glColor3f(0.0, 0.0, 1.0) @@ -93,7 +93,7 @@ def turtle_1(): glFlush() -def turtle_2(): +def turtle_2() -> None: glClear(GL_COLOR_BUFFER_BIT) glColor3f(0.0, 0.0, 1.0) reset() @@ -106,7 +106,7 @@ def turtle_2(): glFlush() -def turtle_3(): +def turtle_3() -> None: glClear(GL_COLOR_BUFFER_BIT) glColor3f(0.0, 0.0, 1.0) reset() @@ -119,7 +119,7 @@ def turtle_3(): glFlush() -def turtle_4(): +def turtle_4() -> None: glClear(GL_COLOR_BUFFER_BIT) glColor3f(0.0, 0.0, 1.0) reset() @@ -132,7 +132,7 @@ def turtle_4(): glFlush() -def turtle_5(): +def turtle_5() -> None: glClear(GL_COLOR_BUFFER_BIT) glColor3f(0.0, 0.0, 1.0) reset() @@ -145,7 +145,7 @@ def turtle_5(): glFlush() -def turtle_6(): +def turtle_6() -> None: glClear(GL_COLOR_BUFFER_BIT) glColor3f(0.0, 0.0, 1.0) reset() @@ -159,7 +159,7 @@ def turtle_6(): glFlush() -def turtle_7(): +def turtle_7() -> None: glClear(GL_COLOR_BUFFER_BIT) glColor3f(0.0, 0.0, 1.0) reset() @@ -172,7 +172,7 @@ def turtle_7(): glFlush() -def turtle_8(): +def turtle_8() -> None: glClear(GL_COLOR_BUFFER_BIT) glColor3f(0.0, 0.0, 1.0) reset() @@ -192,7 +192,7 @@ def turtle_8(): glFlush() -def turtle_9(): +def turtle_9() -> None: glClear(GL_COLOR_BUFFER_BIT) glColor3f(0.0, 0.0, 1.0) reset() @@ -208,7 +208,7 @@ def turtle_9(): glFlush() -def turtle_10(): +def turtle_10() -> None: glClear(GL_COLOR_BUFFER_BIT) glColor3f(0.0, 0.0, 1.0) reset() @@ -226,7 +226,7 @@ def turtle_10(): glFlush() -if __name__ == '__main__': +if __name__ == "__main__": glutInit() glutInitWindowSize(400, 400) glutCreateWindow(b"Turtle") @@ -245,4 +245,4 @@ def turtle_10(): glutReshapeFunc(reshapeFun) initFun() - glutMainLoop() \ No newline at end of file + glutMainLoop() diff --git a/PyOpenGLExample/wireframe.py b/PyOpenGLExample/wireframe.py index e07ef2658..1d16c92d7 100644 --- a/PyOpenGLExample/wireframe.py +++ b/PyOpenGLExample/wireframe.py @@ -12,8 +12,8 @@ """ -def axis(length): - """ Draws an axis (basicly a line with a cone on top) """ +def axis(length) -> None: + """Draws an axis (basicly a line with a cone on top)""" glPushMatrix() glBegin(GL_LINES) glVertex3d(0, 0, 0) @@ -24,8 +24,8 @@ def axis(length): glPopMatrix() -def three_axis(length): - """ Draws an X, Y and Z-axis """ +def three_axis(length) -> None: + """Draws an X, Y and Z-axis""" glPushMatrix() # Z-axis @@ -42,7 +42,7 @@ def three_axis(length): glPopMatrix() -def display_fun(): +def display_fun() -> None: glMatrixMode(GL_PROJECTION) glLoadIdentity() glOrtho(-2.0 * 64 / 48.0, 2.0 * 64 / 48.0, -1.5, 1.5, 0.1, 100) @@ -56,7 +56,7 @@ def display_fun(): glColor3f(0.0, 0.0, 0.0) glPushMatrix() - glTranslated(0.5, 0.5, 0.5); + glTranslated(0.5, 0.5, 0.5) glutWireCube(1.0) glPopMatrix() @@ -106,11 +106,11 @@ def display_fun(): glFlush() -if __name__ == '__main__': +if __name__ == "__main__": glutInit() glutInitWindowSize(640, 480) glutCreateWindow(b"3D") glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB) glClearColor(1.0, 1.0, 1.0, 0.0) glutDisplayFunc(display_fun) - glutMainLoop() \ No newline at end of file + glutMainLoop() diff --git a/PySvn_examples/analys_commits.py b/PySvn_examples/analys_commits.py index 246f23353..1e32fbc42 100644 --- a/PySvn_examples/analys_commits.py +++ b/PySvn_examples/analys_commits.py @@ -1,18 +1,21 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" +import config from common import get_log_list, get_log_list_by_author -import config + url = config.SVN_FILE_NAME log_list = get_log_list(url) -print('Total commits ({}):'.format(len(log_list))) +print(f"Total commits ({len(log_list)}):") author_by_log = get_log_list_by_author(url, log_list) -for author, logs in sorted(author_by_log.items(), key=lambda item: len(item[1]), reverse=True): - print(' {}: {}'.format(author, len(logs))) +for author, logs in sorted( + author_by_log.items(), key=lambda item: len(item[1]), reverse=True +): + print(f" {author}: {len(logs)}") diff --git a/PySvn_examples/commit_watcher.py b/PySvn_examples/commit_watcher.py index 07881a8ed..c5319e731 100644 --- a/PySvn_examples/commit_watcher.py +++ b/PySvn_examples/commit_watcher.py @@ -1,21 +1,22 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" +import time + import config +from common import get_log_list + + url = config.URL_SVN -from common import get_log_list last_log = get_log_list(url, limit=1)[0] last_revision = last_log.revision -print('Start from commit rev{} by {}: "{}" in {}'.format( - last_revision, - last_log.author, - repr(last_log.msg), - last_log.date -)) +print( + f'Start from commit rev{last_revision} by {last_log.author}: "{repr(last_log.msg)}" in {last_log.date}' +) while True: log_list = get_log_list(url, revision_from=last_revision) @@ -26,17 +27,12 @@ last_log = log_list[-1] if last_revision != last_log.revision: - print('commits +{} from: rev{} by {}: "{}" in {}....{}\n'.format( - len(log_list), - last_revision, - last_log.author, - repr(last_log.msg), - last_log.date, - log_list - )) + print( + f'commits +{len(log_list)} from: rev{last_revision} by {last_log.author}: ' + f'"{repr(last_log.msg)}" in {last_log.date}....{log_list}\n' + ) last_revision = last_log.revision # Every 10 minutes - import time time.sleep(60 * 10) diff --git a/PySvn_examples/commits__draw_plot__by_all_authors.py b/PySvn_examples/commits__draw_plot__by_all_authors.py index 326f028c1..29a61d77e 100644 --- a/PySvn_examples/commits__draw_plot__by_all_authors.py +++ b/PySvn_examples/commits__draw_plot__by_all_authors.py @@ -1,41 +1,50 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -import pandas as pd +import os from datetime import datetime +import pandas as pd +import matplotlib.pyplot as plt + +import config +from common import get_log_list_by_author + + # Название текущего файла без '.py' -import os plot_title = os.path.basename(__file__)[:-3] -import matplotlib.pyplot as plt fig = plt.figure(1) -fig.suptitle(plot_title, fontsize=14, fontweight='bold') +fig.suptitle(plot_title, fontsize=14, fontweight="bold") ax = fig.add_subplot(111) -import config -from common import get_log_list_by_author author_by_log = get_log_list_by_author(config.SVN_FILE_NAME) -list_top_author_by_log = sorted(author_by_log.items(), key=lambda item: len(item[1]), reverse=True) +list_top_author_by_log = sorted( + author_by_log.items(), key=lambda item: len(item[1]), reverse=True +) # Добавление на график информации о авторах for author, logs in list_top_author_by_log: # Сбор коммитов за месяц/год records = [datetime(log.date.year, log.date.month, 1) for log in logs] - df = pd.DataFrame(data=records, columns=['year_month']) - df_month = pd.DataFrame({'count': df.groupby("year_month").size()}).reset_index() - ax.plot(df_month['year_month'], df_month['count'], label='{} ({})'.format(author, len(logs))) + df = pd.DataFrame(data=records, columns=["year_month"]) + df_month = pd.DataFrame({"count": df.groupby("year_month").size()}).reset_index() + ax.plot( + df_month["year_month"], + df_month["count"], + label=f"{author} ({len(logs)})", + ) ax.legend() ax.grid() -ax.set_title('Commits') -ax.set_xlabel('Date') -ax.set_ylabel('Count') +ax.set_title("Commits") +ax.set_xlabel("Date") +ax.set_ylabel("Count") plt.gcf().autofmt_xdate() diff --git a/PySvn_examples/commits__draw_plot__by_single_author.py b/PySvn_examples/commits__draw_plot__by_single_author.py index a42ee0e2d..ca0edb58a 100644 --- a/PySvn_examples/commits__draw_plot__by_single_author.py +++ b/PySvn_examples/commits__draw_plot__by_single_author.py @@ -1,43 +1,51 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -AUTHOR = 'ipetrash' +import os +from datetime import datetime + +import pandas as pd +import matplotlib.pyplot as plt import config from common import get_log_list_by_author + + +AUTHOR = 'ipetrash' + author_by_log = get_log_list_by_author(config.SVN_FILE_NAME) # Сбор коммитов за месяц/год -from datetime import datetime records = [datetime(log.date.year, log.date.month, 1) for log in author_by_log[AUTHOR]] -import pandas as pd -df = pd.DataFrame(data=records, columns=['year_month']) +df = pd.DataFrame(data=records, columns=["year_month"]) print(df) -print('Total rows:', len(df)) +print("Total rows:", len(df)) -df_month = pd.DataFrame({'count': df.groupby("year_month").size()}).reset_index() +df_month = pd.DataFrame({"count": df.groupby("year_month").size()}).reset_index() print(df_month) print() # Название текущего файла без '.py' -import os plot_title = os.path.basename(__file__)[:-3] -import matplotlib.pyplot as plt fig = plt.figure(1) -fig.suptitle(plot_title, fontsize=14, fontweight='bold') +fig.suptitle(plot_title, fontsize=14, fontweight="bold") ax = fig.add_subplot(111) -ax.plot(df_month['year_month'], df_month['count'], label='{} ({})'.format(AUTHOR, len(author_by_log[AUTHOR]))) +ax.plot( + df_month["year_month"], + df_month["count"], + label=f"{AUTHOR} ({len(author_by_log[AUTHOR])})", +) ax.legend() ax.grid() -ax.set_title('Commits') -ax.set_xlabel('Date') -ax.set_ylabel('Count') +ax.set_title("Commits") +ax.set_xlabel("Date") +ax.set_ylabel("Count") plt.gcf().autofmt_xdate() diff --git a/PySvn_examples/commits__draw_plot__by_top5_authors.py b/PySvn_examples/commits__draw_plot__by_top5_authors.py index bb44fb9bb..31e034348 100644 --- a/PySvn_examples/commits__draw_plot__by_top5_authors.py +++ b/PySvn_examples/commits__draw_plot__by_top5_authors.py @@ -1,41 +1,50 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -import pandas as pd +import os from datetime import datetime +import pandas as pd +import matplotlib.pyplot as plt + +import config +from common import get_log_list_by_author + + # Название текущего файла без '.py' -import os plot_title = os.path.basename(__file__)[:-3] -import matplotlib.pyplot as plt fig = plt.figure(1) -fig.suptitle(plot_title, fontsize=14, fontweight='bold') +fig.suptitle(plot_title, fontsize=14, fontweight="bold") ax = fig.add_subplot(111) -import config -from common import get_log_list_by_author author_by_log = get_log_list_by_author(config.SVN_FILE_NAME) -list_top5_author_by_log = sorted(author_by_log.items(), key=lambda item: len(item[1]), reverse=True)[:5] +list_top5_author_by_log = sorted( + author_by_log.items(), key=lambda item: len(item[1]), reverse=True +)[:5] # Добавление на график информации о авторах for author, logs in list_top5_author_by_log: # Сбор коммитов за месяц/год records = [datetime(log.date.year, log.date.month, 1) for log in logs] - df = pd.DataFrame(data=records, columns=['year_month']) - df_month = pd.DataFrame({'count': df.groupby("year_month").size()}).reset_index() - ax.plot(df_month['year_month'], df_month['count'], label='{} ({})'.format(author, len(logs))) + df = pd.DataFrame(data=records, columns=["year_month"]) + df_month = pd.DataFrame({"count": df.groupby("year_month").size()}).reset_index() + ax.plot( + df_month["year_month"], + df_month["count"], + label=f"{author} ({len(logs)})", + ) ax.legend() ax.grid() -ax.set_title('Commits') -ax.set_xlabel('Date') -ax.set_ylabel('Count') +ax.set_title("Commits") +ax.set_xlabel("Date") +ax.set_ylabel("Count") plt.gcf().autofmt_xdate() diff --git a/PySvn_examples/common.py b/PySvn_examples/common.py index 2d6bf871d..dc845bc8e 100644 --- a/PySvn_examples/common.py +++ b/PySvn_examples/common.py @@ -1,18 +1,23 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/dsoprea/PySvn + +import os +from collections import defaultdict + +import svn.local +import svn.remote + + def get_log_list(url__or__file_name: str, revision_from=None, limit=None) -> list: - import os if os.path.exists(url__or__file_name): - import svn.local repo = svn.local.LocalClient(url__or__file_name) else: - import svn.remote repo = svn.remote.RemoteClient(url__or__file_name) return list(repo.log_default(revision_from=revision_from, limit=limit)) @@ -22,9 +27,7 @@ def get_log_list_by_author(url__or__file_name: str, log_list: list = None) -> di if not log_list: log_list = get_log_list(url__or__file_name) - from collections import defaultdict author_by_log = defaultdict(list) - for log in log_list: author_by_log[log.author].append(log) diff --git a/PySvn_examples/config.py b/PySvn_examples/config.py index 0f3e72989..f714e9e1d 100644 --- a/PySvn_examples/config.py +++ b/PySvn_examples/config.py @@ -1,8 +1,8 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -URL_SVN = 'svn+cplus://svn2.compassplus.ru/twrbs/csm/optt/dev/trunk' -SVN_FILE_NAME = 'E:/OPTT/optt_trunk' +URL_SVN = "svn+cplus://svn2.compassplus.ru/twrbs/csm/optt/dev/trunk" +SVN_FILE_NAME = "E:/OPTT/optt_trunk" diff --git a/PySvn_examples/filter_log_by_message__print_release_version.py b/PySvn_examples/filter_log_by_message__print_release_version.py index 22f0eef65..b1a1aada3 100644 --- a/PySvn_examples/filter_log_by_message__print_release_version.py +++ b/PySvn_examples/filter_log_by_message__print_release_version.py @@ -1,12 +1,13 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" +import svn.local import config -import svn.local + repo = svn.local.LocalClient(config.SVN_FILE_NAME) # OR: @@ -15,11 +16,15 @@ log_list = [log for log in repo.log_default()] -print('Total commits:', len(log_list)) +print("Total commits:", len(log_list)) print() -release_version_log_list = [log for log in log_list if log.msg and 'Release version' in log.msg] -print('Release version log ({}):'.format(len(release_version_log_list))) +release_version_log_list = [ + log for log in log_list if log.msg and "Release version" in log.msg +] +print(f"Release version log ({len(release_version_log_list)}):") for i, log in enumerate(release_version_log_list, 1): - print(' {:6}. [rev {}] {} {:15} "{}"'.format(i, log.revision, log.date, log.author, log.msg)) + print( + f' {i:6}. [rev {log.revision}] {log.date} {log.author:15} "{log.msg}"' + ) diff --git a/PySvn_examples/get_commit_by_revision.py b/PySvn_examples/get_commit_by_revision.py index 2505d13a6..40cac5fc1 100644 --- a/PySvn_examples/get_commit_by_revision.py +++ b/PySvn_examples/get_commit_by_revision.py @@ -1,12 +1,13 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" +import svn.remote import config -import svn.remote + repo = svn.remote.RemoteClient(config.URL_SVN) print(list(repo.log_default(revision_from=159807, limit=1))) diff --git a/PySvn_examples/print_jira_by_date.py b/PySvn_examples/print_jira_by_date.py index 29ff024af..29f21d50d 100644 --- a/PySvn_examples/print_jira_by_date.py +++ b/PySvn_examples/print_jira_by_date.py @@ -1,11 +1,16 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import re -PATTERN_GET_JIRA = re.compile(r'\[.*?\] \((\w+?-\d+?)\)') + +import config +import common + + +PATTERN_GET_JIRA = re.compile(r"\[.*?\] \((\w+?-\d+?)\)") def get_jira(msg): @@ -22,26 +27,21 @@ def get_jira(msg): return match.group(1) -import config -import common - log_list = common.get_log_list(config.SVN_FILE_NAME) # OR: # log_list = common.get_log_list(config.URL_SVN) -print('Total commits:', len(log_list)) +print("Total commits:", len(log_list)) -from collections import OrderedDict -month_by_jira_info = OrderedDict() - +month_by_jira_info = dict() for log in reversed(log_list): jira = get_jira(log.msg) if not jira: continue - key = log.date.strftime('%Y/%m') + key = log.date.strftime("%Y/%m") if key not in month_by_jira_info: month_by_jira_info[key] = set() @@ -50,7 +50,8 @@ def get_jira(msg): for month, jira_items in month_by_jira_info.items(): # Хитрая сортировка по двум параметрами: имя проекта и номер джиры - jira_items = sorted(jira_items, key=lambda x: (x.split('-')[0], int(x.split('-')[1]))) - - print('{}: ({})\t{}'.format(month, len(jira_items), jira_items)) + jira_items = sorted( + jira_items, key=lambda x: (x.split("-")[0], int(x.split("-")[1])) + ) + print(f"{month}: ({len(jira_items)})\t{jira_items}") diff --git a/QuadraticEquation/QuadraticEquation.py b/QuadraticEquation/QuadraticEquation.py index 10fd8a2a8..3a49e4bb0 100644 --- a/QuadraticEquation/QuadraticEquation.py +++ b/QuadraticEquation/QuadraticEquation.py @@ -1,33 +1,37 @@ # coding=utf-8 + +__author__ = "ipetrash" + + import argparse import math -__author__ = 'ipetrash' - def create_parser(): - return argparse.ArgumentParser(description="Finding the roots of a quadratic equation.") + return argparse.ArgumentParser( + description="Finding the roots of a quadratic equation." + ) def calculate_D(a, b, c): - return (b ** 2) - 4 * a * c + return (b**2) - 4 * a * c + - def calculate_Roots(a, b, D): sqrt_D = math.sqrt(D) x1 = (-b - sqrt_D) / (2 * a) x2 = (-b + sqrt_D) / (2 * a) return x1, x2 - -if __name__ == '__main__': + +if __name__ == "__main__": parse = create_parser() parse.parse_args() - a = int(raw_input("a=")) - b = int(raw_input("b=")) - c = int(raw_input("c=")) + a = int(input("a=")) + b = int(input("b=")) + c = int(input("c=")) D = calculate_D(a, b, c) print("D=%s" % D) if D > 0: @@ -35,4 +39,4 @@ def calculate_Roots(a, b, D): elif D is 0: print("Root x=%s" % (-b / (2 * a))) elif D < 0: - print("No roots.") \ No newline at end of file + print("No roots.") diff --git a/QuickChart__examples/common.py b/QuickChart__examples/common.py new file mode 100644 index 000000000..98ecabbd6 --- /dev/null +++ b/QuickChart__examples/common.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +# pip install quickchart.io +from quickchart import QuickChart + + +def get_chart() -> QuickChart: + qc = QuickChart() + qc.width = 500 + qc.height = 300 + qc.version = "2" + qc.background_color = "" + qc.config = { + "type": "doughnut", + "data": { + "datasets": [ + { + "data": [43, 21], + "backgroundColor": ["rgb(173 225 232)", "rgb(33 170 184)"], + "borderWidth": 0, + }, + ], + }, + "options": { + "cutoutPercentage": 80, + "legend": { + "display": "false", + }, + "plugins": { + "datalabels": { + "display": "false", + }, + }, + }, + } + return qc diff --git a/QuickChart__examples/draw_chart_on_other_image/input.jpg b/QuickChart__examples/draw_chart_on_other_image/input.jpg new file mode 100644 index 000000000..da7126dfb Binary files /dev/null and b/QuickChart__examples/draw_chart_on_other_image/input.jpg differ diff --git a/QuickChart__examples/draw_chart_on_other_image/main.py b/QuickChart__examples/draw_chart_on_other_image/main.py new file mode 100644 index 000000000..12c012ee5 --- /dev/null +++ b/QuickChart__examples/draw_chart_on_other_image/main.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import sys + +from io import BytesIO +from pathlib import Path + +# pip install requests +import requests + +# pip install pillow +from PIL import Image + +DIR = Path(__file__).resolve().absolute().parent +sys.path.append(str(DIR.parent)) +from common import get_chart + + +qc = get_chart() + +# Variant 1 +f = BytesIO(qc.get_bytes()) +img_chart = Image.open(f) + +img = Image.open("input.jpg") +Image.Image.paste(img, img_chart, (10, 10), mask=img_chart) +img.save("input_with_chart_v1.png") + +# Variant 2 +url = qc.get_url() +raw = requests.get(url, stream=True).raw +img_chart = Image.open(raw) + +img = Image.open("input.jpg") +Image.Image.paste(img, img_chart, (10, 10), mask=img_chart) +img.save("input_with_chart_v2.png") diff --git a/QuickChart__examples/get_bytes.py b/QuickChart__examples/get_bytes.py new file mode 100644 index 000000000..5ce63436e --- /dev/null +++ b/QuickChart__examples/get_bytes.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from common import get_chart + + +qc = get_chart() +data = qc.get_bytes() +print(data[:6]) +# b'\x89PNG\r\n' + +print(len(data)) +# 13061 diff --git a/QuickChart__examples/get_url__get_short_url.py b/QuickChart__examples/get_url__get_short_url.py new file mode 100644 index 000000000..be6fa2777 --- /dev/null +++ b/QuickChart__examples/get_url__get_short_url.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from common import get_chart + + +qc = get_chart() +print(qc.get_url()) +# https://quickchart.io/chart?c=%7B%22type%22%3A%22doughnut%22%2C%22data%22%3A%7B%22datasets%22%3A%5B%7B%22data%22%3A%5B43%2C21%5D%2C%22backgroundColor%22%3A%5B%22rgb%28173+225+232%29%22%2C%22rgb%2833+170+184%29%22%5D%2C%22borderWidth%22%3A0%7D%5D%7D%2C%22options%22%3A%7B%22cutoutPercentage%22%3A80%2C%22legend%22%3A%7B%22display%22%3A%22false%22%7D%2C%22plugins%22%3A%7B%22datalabels%22%3A%7B%22display%22%3A%22false%22%7D%7D%7D%7D&w=500&h=300&bkg=&devicePixelRatio=1.0&f=png&v=2 + +print(qc.get_short_url()) +# https://quickchart.io/chart/render/sf-9237a1bc-f7ce-4156-ac58-2452a5729e9e diff --git a/QuickChart__examples/to_file.py b/QuickChart__examples/to_file.py new file mode 100644 index 000000000..e2add1ba9 --- /dev/null +++ b/QuickChart__examples/to_file.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from pathlib import Path +from common import get_chart + + +PATH = Path(__file__).resolve().absolute() +FILE_NAME = Path(f"{PATH}.png") + + +qc = get_chart() +qc.to_file(FILE_NAME) diff --git a/RLE_encode.py b/RLE_encode.py index 272e5bd40..feb2978b4 100644 --- a/RLE_encode.py +++ b/RLE_encode.py @@ -1,13 +1,16 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://ru.wikipedia.org/wiki/Кодирование_длин_серий # SOURCE: https://en.wikipedia.org/wiki/Run-length_encoding +import itertools + + def compress(text): """ Функция для сжатия текста, с повторяющимися последовательностями символов @@ -20,15 +23,14 @@ def compress(text): chars = list() - import itertools for char, same in itertools.groupby(text): count = len(tuple(same)) # number of repetitions chars.append(char if count == 1 else str(count) + char) - return ''.join(chars) + return "".join(chars) -if __name__ == '__main__': +if __name__ == "__main__": print(compress("aaaa")) print(compress("aaaabbbbb")) print(compress("aabcDDfaaaa")) diff --git a/RSSbashorg/RSSbashorg.py b/RSSbashorg/RSSbashorg.py index 11473978d..c1591337b 100644 --- a/RSSbashorg/RSSbashorg.py +++ b/RSSbashorg/RSSbashorg.py @@ -1,16 +1,18 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" +import html import feedparser -rss = feedparser.parse('http://bash.im/rss/') + + +rss = feedparser.parse("http://bash.im/rss/") print(rss.feed.subtitle) for entry in rss.entries: - import html quote = html.unescape(entry.summary) - quote = quote.replace('
', '\n') + quote = quote.replace("
", "\n") - print('{0.title}: {0.link}\n{1}\n\n'.format(entry, quote)) + print(f"{entry.title}: {entry.link}\n{quote}\n\n") diff --git a/Random_example/main.py b/Random_example/main.py index 3a26e4f69..495d3dc2b 100644 --- a/Random_example/main.py +++ b/Random_example/main.py @@ -1,19 +1,22 @@ -__author__ = 'ipetrash' +__author__ = "ipetrash" + import random -if __name__ == '__main__': - direction = ["up", 'down', 'left', 'right'] - for i in range(5): - print("Directions: %s, random direction = %s" % (direction, random.choice(direction))) - print("random: %s" % random.random()) # rand float number - print("randrange(5): %s" % random.randrange(5)) # rand int number - print("randrange(0, 10): %s" % random.randrange(0, 10)) # rand int number - print("uniform(1, 10): %s" % random.uniform(1, 10)) # rand float number +direction = ["up", "down", "left", "right"] +for i in range(5): + print( + f"Directions: {direction}, random direction = {random.choice(direction)}" + ) + +print(f"random: {random.random()}") # rand float number +print(f"randrange(5): {random.randrange(5)}") # rand int number +print(f"randrange(0, 10): {random.randrange(0, 10)}") # rand int number +print(f"uniform(1, 10): {random.uniform(1, 10)}") # rand float number - print("Directions: %s" % direction) - random.shuffle(direction) # shuffle list - print("Shuffle directions: %s" % direction) +print(f"Directions: {direction}") +random.shuffle(direction) # shuffle list +print(f"Shuffle directions: {direction}") - print(random.sample(direction, 2)) # select two element \ No newline at end of file +print(random.sample(direction, 2)) # select two element diff --git a/ReadURLsFromFile/ReadURLsFromFile.py b/ReadURLsFromFile/ReadURLsFromFile.py index 8863c831d..4dbc56e31 100644 --- a/ReadURLsFromFile/ReadURLsFromFile.py +++ b/ReadURLsFromFile/ReadURLsFromFile.py @@ -1,18 +1,19 @@ # coding=utf-8 -import argparse +__author__ = "ipetrash" + -__author__ = 'ipetrash' +import argparse def create_parser(): parser = argparse.ArgumentParser(description="Read URLs from file with URL.") return parser -if __name__ == '__main__': + +if __name__ == "__main__": create_parser().parse_args() - file_path = raw_input("Input file path: ") - file = open(file_path, "r") - for line in file.readlines(): - print(line) - file.close() \ No newline at end of file + file_path = input("Input file path: ") + with open(file_path) as f: + for line in f.readlines(): + print(line) diff --git a/Reading_the_target_of_a_lnk_file/using__winshell.py b/Reading_the_target_of_a_lnk_file/using__winshell.py index 51f77a85e..2fa0a7ae2 100644 --- a/Reading_the_target_of_a_lnk_file/using__winshell.py +++ b/Reading_the_target_of_a_lnk_file/using__winshell.py @@ -1,16 +1,17 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -from glob import glob import os +from glob import glob # pip install winshell import winshell -path_desktop_lnk = os.path.expanduser(r'~\Desktop\**\*.lnk') + +path_desktop_lnk = os.path.expanduser(r"~\Desktop\**\*.lnk") file_name = glob(path_desktop_lnk, recursive=True)[0] shortcut = winshell.shortcut(file_name) diff --git a/Recursive search exe/via glob.py b/Recursive search exe/via glob.py index 90abef127..bc49042d4 100644 --- a/Recursive search exe/via glob.py +++ b/Recursive search exe/via glob.py @@ -1,9 +1,11 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import glob -for file_name in glob.iglob('C://**/*.exe', recursive=True): + + +for file_name in glob.iglob("C://**/*.exe", recursive=True): print(file_name) diff --git a/Recursive search exe/via os.walk.py b/Recursive search exe/via os.walk.py index 1eadda79a..baca5f3cd 100644 --- a/Recursive search exe/via os.walk.py +++ b/Recursive search exe/via os.walk.py @@ -1,14 +1,16 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import os -for root, dirs, files in os.walk('C://'): + + +for root, dirs, files in os.walk("C://"): for file in files: base_name, ext = os.path.splitext(file) - if ext.lower() == '.exe': + if ext.lower() == ".exe": file_name = os.path.join(root, file) file_name = os.path.normpath(file_name) print(file_name) diff --git a/Recursive search exe/via pathlib.py b/Recursive search exe/via pathlib.py new file mode 100644 index 000000000..e1f5196e5 --- /dev/null +++ b/Recursive search exe/via pathlib.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from pathlib import Path + + +for file_name in Path("C://").rglob("*.exe"): + print(file_name) diff --git a/RemovingDuplicatesFromList/RemovingDuplicatesFromList.py b/RemovingDuplicatesFromList/RemovingDuplicatesFromList.py index 1538fec61..af828a316 100644 --- a/RemovingDuplicatesFromList/RemovingDuplicatesFromList.py +++ b/RemovingDuplicatesFromList/RemovingDuplicatesFromList.py @@ -1,8 +1,9 @@ # coding=utf-8 -import argparse +__author__ = "ipetrash" + -__author__ = 'ipetrash' +import argparse def create_parser(): @@ -10,10 +11,10 @@ def create_parser(): return parser -if __name__ == '__main__': +if __name__ == "__main__": create_parser().parse_args() - list = raw_input("Enter a list of elements separated by a space: ").split(" ") - list = filter(lambda x: x is not "", list) + list = input("Enter a list of elements separated by a space: ").split(" ") + list = list(filter(None, list)) print("Source list: %s" % list) list.sort() print("Sorted list: %s" % list) @@ -21,4 +22,4 @@ def create_parser(): if list.count(x) > 1: list.remove(x) print("Result: %s" % list) - print("List: " + ", ".join(list)) \ No newline at end of file + print("List: " + ", ".join(list)) diff --git a/SNMP__pysnmp__examples/get_table__get_CPU.py b/SNMP__pysnmp__examples/get_table__get_CPU.py index 00b52408c..942ca665a 100644 --- a/SNMP__pysnmp__examples/get_table__get_CPU.py +++ b/SNMP__pysnmp__examples/get_table__get_CPU.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: http://www.oidview.com/mibs/0/SNMPv2-MIB.html @@ -10,11 +10,19 @@ from typing import Iterator # pip install pysnmp -from pysnmp.hlapi import nextCmd, SnmpEngine, CommunityData, UdpTransportTarget, ContextData, ObjectType, ObjectIdentity +from pysnmp.hlapi import ( + nextCmd, + SnmpEngine, + CommunityData, + UdpTransportTarget, + ContextData, + ObjectType, + ObjectIdentity, +) -def get_iterator(host: str, port: int = 161, community: str = '') -> Iterator: - hrProcessorLoad = '1.3.6.1.2.1.25.3.3.1.2' +def get_iterator(host: str, port: int = 161, community: str = "") -> Iterator: + hrProcessorLoad = "1.3.6.1.2.1.25.3.3.1.2" return nextCmd( SnmpEngine(), @@ -26,23 +34,23 @@ def get_iterator(host: str, port: int = 161, community: str = '') -> Iterator: ) -iterator = get_iterator('localhost', 161, 'public') +iterator = get_iterator("localhost", 161, "public") items = list(iterator) print(f"Number CPU cores: {len(items)}\n") for i, (error_indication, error_status, error_index, var_binds) in enumerate(items, 1): if error_indication: - print(f'Error indication: {error_indication}') + print(f"Error indication: {error_indication}") elif error_status: - at = '?' + at = "?" if error_index: at = var_binds[int(error_index) - 1][0] - print(f'Error status {error_status.prettyPrint()!r} at {at}') + print(f"Error status {error_status.prettyPrint()!r} at {at}") else: for var, value in var_binds: - print(f'{var} = {value}') - print(f'{var.getOid()} = {value}') - print(f'{var.prettyPrint()} = {value.prettyPrint()}') + print(f"{var} = {value}") + print(f"{var.getOid()} = {value}") + print(f"{var.prettyPrint()} = {value.prettyPrint()}") print() diff --git a/SNMP__pysnmp__examples/hello_world.py b/SNMP__pysnmp__examples/hello_world.py index 97345d8ff..202531c7e 100644 --- a/SNMP__pysnmp__examples/hello_world.py +++ b/SNMP__pysnmp__examples/hello_world.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: http://www.oidview.com/mibs/0/SNMPv2-MIB.html @@ -13,28 +13,28 @@ iterator = getCmd( SnmpEngine(), - CommunityData('public', mpModel=0), - UdpTransportTarget(('localhost', 161)), + CommunityData("public", mpModel=0), + UdpTransportTarget(("localhost", 161)), ContextData(), - ObjectType(ObjectIdentity('SNMPv2-MIB', 'sysDescr', 0)), - ObjectType(ObjectIdentity('1.3.6.1.2.1.1.1.0')), # OID, same as above - ObjectType(ObjectIdentity('.1.3.6.1.2.1.1.1.0')), # OID, same as above + ObjectType(ObjectIdentity("SNMPv2-MIB", "sysDescr", 0)), + ObjectType(ObjectIdentity("1.3.6.1.2.1.1.1.0")), # OID, same as above + ObjectType(ObjectIdentity(".1.3.6.1.2.1.1.1.0")), # OID, same as above ) error_indication, error_status, error_index, var_binds = next(iterator) if error_indication: - print(f'Error indication: {error_indication}') + print(f"Error indication: {error_indication}") elif error_status: - at = '?' + at = "?" if error_index: at = var_binds[int(error_index) - 1][0] - print(f'Error status {error_status.prettyPrint()!r} at {at}') + print(f"Error status {error_status.prettyPrint()!r} at {at}") else: for var, value in var_binds: - print(f'{var} = {value}') - print(f'{var.getOid()} = {value}') - print(f'{var.prettyPrint()} = {value.prettyPrint()}') + print(f"{var} = {value}") + print(f"{var.getOid()} = {value}") + print(f"{var.prettyPrint()} = {value.prettyPrint()}") print() diff --git a/SNMP__pysnmp__examples/hello_world__fix_imports.py b/SNMP__pysnmp__examples/hello_world__fix_imports.py index fc8230727..61507386d 100644 --- a/SNMP__pysnmp__examples/hello_world__fix_imports.py +++ b/SNMP__pysnmp__examples/hello_world__fix_imports.py @@ -1,40 +1,48 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: http://www.oidview.com/mibs/0/SNMPv2-MIB.html # pip install pysnmp -from pysnmp.hlapi import getCmd, SnmpEngine, CommunityData, UdpTransportTarget, ContextData, ObjectType, ObjectIdentity +from pysnmp.hlapi import ( + getCmd, + SnmpEngine, + CommunityData, + UdpTransportTarget, + ContextData, + ObjectType, + ObjectIdentity, +) iterator = getCmd( SnmpEngine(), - CommunityData('public', mpModel=0), - UdpTransportTarget(('localhost', 161)), + CommunityData("public", mpModel=0), + UdpTransportTarget(("localhost", 161)), ContextData(), - ObjectType(ObjectIdentity('SNMPv2-MIB', 'sysDescr', 0)), - ObjectType(ObjectIdentity('1.3.6.1.2.1.1.1.0')), # OID, same as above - ObjectType(ObjectIdentity('.1.3.6.1.2.1.1.1.0')), # OID, same as above + ObjectType(ObjectIdentity("SNMPv2-MIB", "sysDescr", 0)), + ObjectType(ObjectIdentity("1.3.6.1.2.1.1.1.0")), # OID, same as above + ObjectType(ObjectIdentity(".1.3.6.1.2.1.1.1.0")), # OID, same as above ) error_indication, error_status, error_index, var_binds = next(iterator) if error_indication: - print(f'Error indication: {error_indication}') + print(f"Error indication: {error_indication}") elif error_status: - at = '?' + at = "?" if error_index: at = var_binds[int(error_index) - 1][0] - print(f'Error status {error_status.prettyPrint()!r} at {at}') + print(f"Error status {error_status.prettyPrint()!r} at {at}") else: for var, value in var_binds: - print(f'{var} = {value}') - print(f'{var.getOid()} = {value}') - print(f'{var.prettyPrint()} = {value.prettyPrint()}') + print(f"{var} = {value}") + print(f"{var.getOid()} = {value}") + print(f"{var.prettyPrint()} = {value.prettyPrint()}") print() diff --git a/Serialize with pickle/SerializeWithPickle.py b/Serialize with pickle/SerializeWithPickle.py index 7b45c23c4..2ad97101d 100644 --- a/Serialize with pickle/SerializeWithPickle.py +++ b/Serialize with pickle/SerializeWithPickle.py @@ -1,106 +1,106 @@ -__author__ = 'ipetrash' +__author__ = "ipetrash" # Serialization / Сериализация + # Example of using pickle module # https://docs.python.org/3.4/library/pickle.html import pickle -if __name__ == '__main__': - data = { - 'foo': [1, 2, 3], - 'bar': ('Hello', 'world!'), - 'baz': True, - "fiz": { - 1: "first", - 6: "six", - "seven": 7, - }, - } - - ## Simple example - # write the serialized data in the file - with open('data.pkl', 'wb') as f: - pickle.dump(data, f) - - # open and read from a file - with open('data.pkl', 'rb') as f: - data = pickle.load(f) # stored in the variable - print(data) - - - ## Maybe add a few data - # write the serialized data in the file - with open('data_1.pkl', 'wb') as f: - pickle.dump([1, 1, 2, 3], f) - pickle.dump("Man", f) - pickle.dump((666, ), f) - - with open('data_1.pkl', 'rb') as f: - # Need to preserve the order of reading - fi = pickle.load(f) - tw = pickle.load(f) - th = pickle.load(f) - print(fi) # [1, 1, 2, 3] - print(tw) # Man - print(th) # (666,) - - - ## Serialization of custom classes - class Monster: - """Monster class!""" - def __init__(self, health, power, name, level): - self.health = health - self.power = power - self.name = name - self.level = level - self.abilities = ["Eater"] - - def __str__(self): - return ("Name: '{}' lv {}, health: {}, power: {}, abilities: {}: {}" - .format(self.name, self.level, self.health, self.power, self.abilities, hex(id(self)))) - - def say(self): - print("I'm %s" % self.name) - - - zombi = Monster(name="Zombi", health=100, power=10, level=2) - zombi.abilities.append("Undead") - zombi.abilities.append("Insensitivity to pain") - - goblin = Monster(name="Goblin", health=50, power=8, level=1) - ork = Monster(name="Ork", health=250, power=25, level=4) +data = { + "foo": [1, 2, 3], + "bar": ("Hello", "world!"), + "baz": True, + "fiz": { + 1: "first", + 6: "six", + "seven": 7, + }, +} + +## Simple example +# write the serialized data in the file +with open("data.pkl", "wb") as f: + pickle.dump(data, f) + +# open and read from a file +with open("data.pkl", "rb") as f: + data = pickle.load(f) # stored in the variable + print(data) + +## Maybe add a few data +# write the serialized data in the file +with open("data_1.pkl", "wb") as f: + pickle.dump([1, 1, 2, 3], f) + pickle.dump("Man", f) + pickle.dump((666,), f) + +with open("data_1.pkl", "rb") as f: + # Need to preserve the order of reading + fi = pickle.load(f) + tw = pickle.load(f) + th = pickle.load(f) + print(fi) # [1, 1, 2, 3] + print(tw) # Man + print(th) # (666,) + +## Serialization of custom classes +class Monster: + """Monster class!""" + + def __init__(self, health, power, name, level) -> None: + self.health = health + self.power = power + self.name = name + self.level = level + self.abilities = ["Eater"] + + def __str__(self) -> str: + return ( + f"Name: '{self.name}' lv {self.level}, health: {self.health}, " + f"power: {self.power}, abilities: {self.abilities}: {hex(id(self))}" + ) + + def say(self) -> None: + print("I'm %s" % self.name) + +zombi = Monster(name="Zombi", health=100, power=10, level=2) +zombi.abilities.append("Undead") +zombi.abilities.append("Insensitivity to pain") + +goblin = Monster(name="Goblin", health=50, power=8, level=1) +ork = Monster(name="Ork", health=250, power=25, level=4) + +print() +print(Monster) +print(zombi) +print(goblin) +print(ork) + +# write the serialized data in the file +with open("monster.pkl", "wb") as f: + pickle.dump(Monster, f) + pickle.dump(zombi, f) + pickle.dump(goblin, f) + pickle.dump(ork, f) + +with open("monster.pkl", "rb") as f: + m = pickle.load(f) # get Monster + z = pickle.load(f) # get Zombi + g = pickle.load(f) # get Goblin + o = pickle.load(f) # get Ork print() - print(Monster) - print(zombi) - print(goblin) - print(ork) - - # write the serialized data in the file - with open('monster.pkl', 'wb') as f: - pickle.dump(Monster, f) - pickle.dump(zombi, f) - pickle.dump(goblin, f) - pickle.dump(ork, f) - - with open('monster.pkl', 'rb') as f: - m = pickle.load(f) # get Monster - z = pickle.load(f) # get Zombi - g = pickle.load(f) # get Goblin - o = pickle.load(f) # get Ork - print() - print(m) - print(z) - print(g) - print(o) - - - # Serialize: object -> string - temp_1 = pickle.dumps(zombi) - print() - print(temp_1) - - # Serialize: string ->object - temp_2 = pickle.loads(temp_1) # create new object - print(temp_2) - temp_2.say() \ No newline at end of file + print(m) + print(z) + print(g) + print(o) + +# Serialize: object -> string +temp_1 = pickle.dumps(zombi) +print() +print(temp_1) + +# Serialize: string ->object +temp_2 = pickle.loads(temp_1) # create new object +print(temp_2) +temp_2.say() diff --git a/Sorting algorithm list/comparison_of_sorting.py b/Sorting algorithm list/comparison_of_sorting.py index ec8c021f0..afb189f32 100644 --- a/Sorting algorithm list/comparison_of_sorting.py +++ b/Sorting algorithm list/comparison_of_sorting.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/gil9red/SimplePyScripts/blob/master/timeout_block/main.py @@ -15,17 +15,17 @@ class Kill(Exception): class KThread(Thread): - def __init__(self, *args, **keywords): + def __init__(self, *args, **keywords) -> None: Thread.__init__(self, *args, **keywords) self.killed = False - def start(self): + def start(self) -> None: """Start the thread.""" self.__run_backup = self.run self.run = self.__run # Force the Thread to install our trace. Thread.start(self) - def __run(self): + def __run(self) -> None: """Hacked run function, which installs the trace.""" sys.settrace(self.globaltrace) @@ -37,18 +37,18 @@ def __run(self): self.run = self.__run_backup def globaltrace(self, frame, why, arg): - if why == 'call': + if why == "call": return self.localtrace else: return None def localtrace(self, frame, why, arg): if self.killed: - if why == 'line': + if why == "line": raise Kill() return self.localtrace - def kill(self): + def kill(self) -> None: self.killed = True @@ -69,42 +69,44 @@ def the_wrapper_around_the_original_function(*args, **kwargs): return wrapper + ############################################################################################################ -if __name__ == '__main__': - items = list(range(10 ** 3)) +if __name__ == "__main__": import random - random.shuffle(items) + import sorts - import time + from timeit import default_timer + + items = list(range(10**3)) + random.shuffle(items) time_by_algo_name = dict() - import sorts for name, algo in sorted(sorts.ALGO_LIST.items(), key=lambda x: x[0]): print(name) @timeout(seconds=10, raise_timeout=True) - def run(): + def run() -> None: new_items = list(items) algo(new_items) - t = time.clock() + t = default_timer() try: run() except TimeoutError: - print(' timeout!') + print(" timeout!") except Exception as e: - print(' Error: {}: sort: {}'.format(e, name)) + print(f" Error: {e}: sort: {name}") - t = time.clock() - t + t = default_timer() - t time_by_algo_name[t] = name # print(' duration: {:.3f} secs'.format(t)) print() - print('Sorted by time:') + print("Sorted by time:") for t, name in sorted(time_by_algo_name.items(), key=lambda x: x[0]): - print('{}: {:.3f} secs'.format(name, t)) + print(f"{name}: {t:.3f} secs") diff --git a/Sorting algorithm list/sorts/__init__.py b/Sorting algorithm list/sorts/__init__.py index 2e44ba1f7..a60c6f788 100644 --- a/Sorting algorithm list/sorts/__init__.py +++ b/Sorting algorithm list/sorts/__init__.py @@ -1,16 +1,14 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from sorts import ( bogosort, - # TODO: ImportError: No module named 'P26_InsertionSort' # bubble_sort, # bucket_sort, - cocktail_shaker_sort, gnome_sort, heap_sort, @@ -18,10 +16,8 @@ merge_sort, quick_sort, radix_sort, - # TODO: remove this # random_normaldistribution_quicksort, - selection_sort, shell_sort, topological_sort, @@ -32,16 +28,15 @@ # 'bogosort': bogosort.bogosort, # 'bubble_sort': bubble_sort.bubble_sort, # 'bucket_sort': bucket_sort.bucketSort, - - 'cocktail_shaker_sort': cocktail_shaker_sort.cocktail_shaker_sort, - 'gnome_sort': gnome_sort.gnome_sort, - 'heap_sort': heap_sort.heap_sort, - 'insertion_sort': insertion_sort.insertion_sort, - 'merge_sort': merge_sort.merge_sort, - 'quick_sort': quick_sort.quick_sort, - 'radix_sort': radix_sort.radixsort, + "cocktail_shaker_sort": cocktail_shaker_sort.cocktail_shaker_sort, + "gnome_sort": gnome_sort.gnome_sort, + "heap_sort": heap_sort.heap_sort, + "insertion_sort": insertion_sort.insertion_sort, + "merge_sort": merge_sort.merge_sort, + "quick_sort": quick_sort.quick_sort, + "radix_sort": radix_sort.radixsort, # 'random_normaldistribution_quicksort': random_normaldistribution_quicksort.random_normaldistribution_quicksort, - 'selection_sort': selection_sort.selection_sort, - 'shell_sort': shell_sort.shell_sort, + "selection_sort": selection_sort.selection_sort, + "shell_sort": shell_sort.shell_sort, # 'topological_sort': topological_sort.topological_sort, } diff --git a/Sorting algorithm list/sorts/bogosort.py b/Sorting algorithm list/sorts/bogosort.py index 2512dab51..84b3c9d9a 100644 --- a/Sorting algorithm list/sorts/bogosort.py +++ b/Sorting algorithm list/sorts/bogosort.py @@ -26,7 +26,7 @@ def bogosort(collection): [-45, -5, -2] """ - def isSorted(collection): + def isSorted(collection) -> bool: if len(collection) < 2: return True for i in range(len(collection) - 1): @@ -38,7 +38,8 @@ def isSorted(collection): random.shuffle(collection) return collection -if __name__ == '__main__': + +if __name__ == "__main__": import sys # For python 2.x and 3.x compatibility: 3.x has not raw_input builtin @@ -48,6 +49,6 @@ def isSorted(collection): else: input_function = input - user_input = input_function('Enter numbers separated by a comma:\n') - unsorted = [int(item) for item in user_input.split(',')] + user_input = input_function("Enter numbers separated by a comma:\n") + unsorted = [int(item) for item in user_input.split(",")] print(bogosort(unsorted)) diff --git a/Sorting algorithm list/sorts/bubble_sort.py b/Sorting algorithm list/sorts/bubble_sort.py index 54d69e5ba..0e69f38d7 100644 --- a/Sorting algorithm list/sorts/bubble_sort.py +++ b/Sorting algorithm list/sorts/bubble_sort.py @@ -31,16 +31,17 @@ def bubble_sort(collection): [-45, -5, -2] """ length = len(collection) - for i in range(length-1, -1, -1):#range(length-1, -1, -1) - for j in range(i):#range(1, i) - if collection[j] > collection[j+1]: - collection[j], collection[j+1] = collection[j+1], collection[j] + for i in range(length - 1, -1, -1): # range(length-1, -1, -1) + for j in range(i): # range(1, i) + if collection[j] > collection[j + 1]: + collection[j], collection[j + 1] = collection[j + 1], collection[j] return collection -if __name__ == '__main__': +if __name__ == "__main__": import sys + # For python 2.x and 3.x compatibility: 3.x has not raw_input builtin # otherwise 2.x's input builtin function is too "smart" if sys.version_info.major < 3: @@ -48,6 +49,6 @@ def bubble_sort(collection): else: input_function = input - user_input = input_function('Enter numbers separated by a comma:\n') - unsorted = [int(item) for item in user_input.split(',')] + user_input = input_function("Enter numbers separated by a comma:\n") + unsorted = [int(item) for item in user_input.split(",")] print(bubble_sort(unsorted)) diff --git a/Sorting algorithm list/sorts/bucket_sort.py b/Sorting algorithm list/sorts/bucket_sort.py index e378d65f4..aa500b6c8 100644 --- a/Sorting algorithm list/sorts/bucket_sort.py +++ b/Sorting algorithm list/sorts/bucket_sort.py @@ -3,7 +3,7 @@ # This program will illustrate how to implement bucket sort algorithm # Wikipedia says: Bucket sort, or bin sort, is a sorting algorithm that works by distributing the -# elements of an array into a number of buckets. Each bucket is then sorted individually, either using +# elements of an array into a number of buckets. Each bucket is then sorted individually, either using # a different sorting algorithm, or by recursively applying the bucket sorting algorithm. It is a # distribution sort, and is a cousin of radix sort in the most to least significant digit flavour. # Bucket sort is a generalization of pigeonhole sort. Bucket sort can be implemented with comparisons @@ -18,9 +18,10 @@ DEFAULT_BUCKET_SIZE = 5 + def bucketSort(myList, bucketSize=DEFAULT_BUCKET_SIZE): - if(len(myList) == 0): - print('You don\'t have any elements in array!') + if len(myList) == 0: + print("You don't have any elements in array!") minValue = myList[0] maxValue = myList[0] @@ -51,6 +52,7 @@ def bucketSort(myList, bucketSize=DEFAULT_BUCKET_SIZE): return sortedArray -if __name__ == '__main__': + +if __name__ == "__main__": sortedArray = bucketSort([12, 23, 4, 5, 3, 2, 12, 81, 56, 95]) print(sortedArray) diff --git a/Sorting algorithm list/sorts/cocktail_shaker_sort.py b/Sorting algorithm list/sorts/cocktail_shaker_sort.py index a21224632..b54235166 100644 --- a/Sorting algorithm list/sorts/cocktail_shaker_sort.py +++ b/Sorting algorithm list/sorts/cocktail_shaker_sort.py @@ -1,26 +1,28 @@ from __future__ import print_function + def cocktail_shaker_sort(unsorted): """ Pure implementation of the cocktail shaker sort algorithm in Python. """ - for i in range(len(unsorted)-1, 0, -1): + for i in range(len(unsorted) - 1, 0, -1): swapped = False - + for j in range(i, 0, -1): - if unsorted[j] < unsorted[j-1]: - unsorted[j], unsorted[j-1] = unsorted[j-1], unsorted[j] + if unsorted[j] < unsorted[j - 1]: + unsorted[j], unsorted[j - 1] = unsorted[j - 1], unsorted[j] swapped = True for j in range(i): - if unsorted[j] > unsorted[j+1]: - unsorted[j], unsorted[j+1] = unsorted[j+1], unsorted[j] + if unsorted[j] > unsorted[j + 1]: + unsorted[j], unsorted[j + 1] = unsorted[j + 1], unsorted[j] swapped = True - + if not swapped: return unsorted - -if __name__ == '__main__': + + +if __name__ == "__main__": import sys # For python 2.x and 3.x compatibility: 3.x has not raw_input builtin @@ -29,8 +31,8 @@ def cocktail_shaker_sort(unsorted): input_function = raw_input else: input_function = input - - user_input = input_function('Enter numbers separated by a comma:\n') - unsorted = [int(item) for item in user_input.split(',')] + + user_input = input_function("Enter numbers separated by a comma:\n") + unsorted = [int(item) for item in user_input.split(",")] cocktail_shaker_sort(unsorted) - print(unsorted) \ No newline at end of file + print(unsorted) diff --git a/Sorting algorithm list/sorts/gnome_sort.py b/Sorting algorithm list/sorts/gnome_sort.py index 6126f576d..6bbc41a31 100644 --- a/Sorting algorithm list/sorts/gnome_sort.py +++ b/Sorting algorithm list/sorts/gnome_sort.py @@ -1,27 +1,28 @@ from __future__ import print_function + def gnome_sort(unsorted): """ Pure implementation of the gnome sort algorithm in Python. """ if len(unsorted) <= 1: return unsorted - + i = 1 - + while i < len(unsorted): - if unsorted[i-1] <= unsorted[i]: + if unsorted[i - 1] <= unsorted[i]: i += 1 else: - unsorted[i-1], unsorted[i] = unsorted[i], unsorted[i-1] + unsorted[i - 1], unsorted[i] = unsorted[i], unsorted[i - 1] i -= 1 - if (i == 0): + if i == 0: i = 1 return unsorted - -if __name__ == '__main__': + +if __name__ == "__main__": import sys # For python 2.x and 3.x compatibility: 3.x has not raw_input builtin @@ -30,8 +31,8 @@ def gnome_sort(unsorted): input_function = raw_input else: input_function = input - - user_input = input_function('Enter numbers separated by a comma:\n') - unsorted = [int(item) for item in user_input.split(',')] + + user_input = input_function("Enter numbers separated by a comma:\n") + unsorted = [int(item) for item in user_input.split(",")] gnome_sort(unsorted) - print(unsorted) \ No newline at end of file + print(unsorted) diff --git a/Sorting algorithm list/sorts/heap_sort.py b/Sorting algorithm list/sorts/heap_sort.py index 2d9dd844d..869ed18fb 100644 --- a/Sorting algorithm list/sorts/heap_sort.py +++ b/Sorting algorithm list/sorts/heap_sort.py @@ -1,4 +1,4 @@ -''' +""" This is a pure python implementation of the heap sort algorithm. For doctests run following command: @@ -8,12 +8,12 @@ For manual testing run: python heap_sort.py -''' +""" from __future__ import print_function -def heapify(unsorted, index, heap_size): +def heapify(unsorted, index, heap_size) -> None: largest = index left_index = 2 * index + 1 right_index = 2 * index + 2 @@ -29,7 +29,7 @@ def heapify(unsorted, index, heap_size): def heap_sort(unsorted): - ''' + """ Pure implementation of the heap sort algorithm in Python :param collection: some mutable ordered collection with heterogeneous comparable items inside @@ -44,7 +44,7 @@ def heap_sort(unsorted): >>> heap_sort([-2, -5, -45]) [-45, -5, -2] - ''' + """ n = len(unsorted) for i in range(n // 2 - 1, -1, -1): heapify(unsorted, i, n) @@ -53,13 +53,15 @@ def heap_sort(unsorted): heapify(unsorted, 0, i) return unsorted -if __name__ == '__main__': + +if __name__ == "__main__": import sys + if sys.version_info.major < 3: input_function = raw_input else: input_function = input - user_input = input_function('Enter numbers separated by a comma:\n') - unsorted = [int(item) for item in user_input.split(',')] + user_input = input_function("Enter numbers separated by a comma:\n") + unsorted = [int(item) for item in user_input.split(",")] print(heap_sort(unsorted)) diff --git a/Sorting algorithm list/sorts/insertion_sort.py b/Sorting algorithm list/sorts/insertion_sort.py index caaa9305c..14bffb736 100644 --- a/Sorting algorithm list/sorts/insertion_sort.py +++ b/Sorting algorithm list/sorts/insertion_sort.py @@ -31,14 +31,16 @@ def insertion_sort(collection): """ for index in range(1, len(collection)): while 0 < index and collection[index] < collection[index - 1]: - collection[index], collection[ - index - 1] = collection[index - 1], collection[index] + collection[index], collection[index - 1] = ( + collection[index - 1], + collection[index], + ) index -= 1 return collection -if __name__ == '__main__': +if __name__ == "__main__": import sys # For python 2.x and 3.x compatibility: 3.x has not raw_input builtin @@ -48,6 +50,6 @@ def insertion_sort(collection): else: input_function = input - user_input = input_function('Enter numbers separated by a comma:\n') - unsorted = [int(item) for item in user_input.split(',')] + user_input = input_function("Enter numbers separated by a comma:\n") + unsorted = [int(item) for item in user_input.split(",")] print(insertion_sort(unsorted)) diff --git a/Sorting algorithm list/sorts/merge_sort.py b/Sorting algorithm list/sorts/merge_sort.py index 92a678016..7047ac3fa 100644 --- a/Sorting algorithm list/sorts/merge_sort.py +++ b/Sorting algorithm list/sorts/merge_sort.py @@ -61,7 +61,7 @@ def merge_sort(collection): return collection -if __name__ == '__main__': +if __name__ == "__main__": import sys # For python 2.x and 3.x compatibility: 3.x has not raw_input builtin @@ -71,6 +71,6 @@ def merge_sort(collection): else: input_function = input - user_input = input_function('Enter numbers separated by a comma:\n') - unsorted = [int(item) for item in user_input.split(',')] + user_input = input_function("Enter numbers separated by a comma:\n") + unsorted = [int(item) for item in user_input.split(",")] print(merge_sort(unsorted)) diff --git a/Sorting algorithm list/sorts/quick_sort.py b/Sorting algorithm list/sorts/quick_sort.py index 8974e1bd8..0bd836fec 100644 --- a/Sorting algorithm list/sorts/quick_sort.py +++ b/Sorting algorithm list/sorts/quick_sort.py @@ -30,16 +30,16 @@ def quick_sort(ARRAY): [-45, -5, -2] """ ARRAY_LENGTH = len(ARRAY) - if( ARRAY_LENGTH <= 1): + if ARRAY_LENGTH <= 1: return ARRAY else: PIVOT = ARRAY[0] - GREATER = [ element for element in ARRAY[1:] if element > PIVOT ] - LESSER = [ element for element in ARRAY[1:] if element <= PIVOT ] + GREATER = [element for element in ARRAY[1:] if element > PIVOT] + LESSER = [element for element in ARRAY[1:] if element <= PIVOT] return quick_sort(LESSER) + [PIVOT] + quick_sort(GREATER) -if __name__ == '__main__': +if __name__ == "__main__": import sys # For python 2.x and 3.x compatibility: 3.x has not raw_input builtin @@ -49,6 +49,6 @@ def quick_sort(ARRAY): else: input_function = input - user_input = input_function('Enter numbers separated by a comma:\n') - unsorted = [ int(item) for item in user_input.split(',') ] - print( quick_sort(unsorted) ) + user_input = input_function("Enter numbers separated by a comma:\n") + unsorted = [int(item) for item in user_input.split(",")] + print(quick_sort(unsorted)) diff --git a/Sorting algorithm list/sorts/random_normaldistribution_quicksort.py b/Sorting algorithm list/sorts/random_normaldistribution_quicksort.py index 19b180578..0c0276158 100644 --- a/Sorting algorithm list/sorts/random_normaldistribution_quicksort.py +++ b/Sorting algorithm list/sorts/random_normaldistribution_quicksort.py @@ -1,66 +1,60 @@ from random import randint from tempfile import TemporaryFile import numpy as np -import math - -def _inPlaceQuickSort(A,start,end): +def _inPlaceQuickSort(A, start, end): count = 0 - if start None: self.name = name self.age = age - def __repr__(self): + def __repr__(self) -> str: return "%s (%d)" % (self.name, self.age) @@ -48,7 +54,7 @@ def __repr__(self): print() words = ["he", "He", "Ab", "ab", "Cc", "cC"] -print("Words: {0}".format(words)) +print(f"Words: {words}") print("Sorting:") print(sorted(words)) print(sorted(words, reverse=True)) diff --git a/Split a string into two elements.py b/Split a string into two elements.py index 1d5f9bcb5..7e846a686 100644 --- a/Split a string into two elements.py +++ b/Split a string into two elements.py @@ -1,11 +1,14 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" + + +import re def split_by_pair(text): - items = list() + items = [] for i in range(0, len(text), 2): pair = text[i] + text[i + 1] @@ -20,12 +23,11 @@ def split_by_pair_1(text): def split_by_pair_2(text): - import re - return re.findall('..', text) + return re.findall("..", text) -if __name__ == '__main__': - text = 'a1b2c3d4f5' +if __name__ == "__main__": + text = "a1b2c3d4f5" items = split_by_pair(text) print(items) # ['a1', 'b2', 'c3', 'd4', 'f5'] diff --git a/Start a file with its associated application/main.py b/Start a file with its associated application/main.py index cf211dff6..e8dabfef5 100644 --- a/Start a file with its associated application/main.py +++ b/Start a file with its associated application/main.py @@ -1,10 +1,12 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import os -os.startfile('result.txt') -os.startfile('img.png') -os.startfile('cert.cer') + + +os.startfile("result.txt") +os.startfile("img.png") +os.startfile("cert.cer") diff --git a/Timer/pyside_qtimer.py b/Timer/pyside_qtimer.py index d23ecf4b0..4d6640a2f 100644 --- a/Timer/pyside_qtimer.py +++ b/Timer/pyside_qtimer.py @@ -1,14 +1,14 @@ -__author__ = 'ipetrash' +__author__ = "ipetrash" """Пример таймера.""" -from PySide.QtCore import * import sys +from PySide.QtCore import * -def say(): +def say() -> None: print("say!") @@ -19,4 +19,4 @@ def say(): t.timeout.connect(say) t.start() -sys.exit(app.exec_()) \ No newline at end of file +sys.exit(app.exec_()) diff --git a/WrapperMap__work_with_dict_through_atts.py b/WrapperMap__work_with_dict_through_atts.py index dfa1f2914..316813bf1 100644 --- a/WrapperMap__work_with_dict_through_atts.py +++ b/WrapperMap__work_with_dict_through_atts.py @@ -1,11 +1,11 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" class WrapperMap: - def __init__(self, d: dict): + def __init__(self, d: dict) -> None: self.d = d def get_value(self): @@ -18,16 +18,16 @@ def __getattr__(self, item: str): return value - def __repr__(self): + def __repr__(self) -> str: return repr(self.d) genMessage = { - 'from_user': { - 'id': 123, - 'username': "username", - 'full_name': "fullName" - } + "from_user": { + "id": 123, + "username": "username", + "full_name": "fullName", + }, } x = WrapperMap(genMessage) diff --git a/XML/XML_to_dict__xmltodict__examples/dict_to_xml__unparse.py b/XML/XML_to_dict__xmltodict__examples/dict_to_xml__unparse.py index 7073f16f0..9f52d5b65 100644 --- a/XML/XML_to_dict__xmltodict__examples/dict_to_xml__unparse.py +++ b/XML/XML_to_dict__xmltodict__examples/dict_to_xml__unparse.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/martinblech/xmltodict#roundtripping @@ -12,9 +12,9 @@ my_dict = { - 'response': { - 'status': 'good', - 'last_updated': '2014-02-16T23:10:12Z', + "response": { + "status": "good", + "last_updated": "2014-02-16T23:10:12Z", } } print(xmltodict.unparse(my_dict)) @@ -30,16 +30,16 @@ # 2014-02-16T23:10:12Z # -print('\n') +print("\n") # Text values for nodes can be specified with the cdata_key key in the python dict, while node properties can # be specified with the attr_prefix prefixed to the key name in the python dict. The default value for attr_ # prefix is @ and the default value for cdata_key is #text. my_dict = { - 'text': { - '@color': 'red', - '@stroke': '2', - '#text': 'This is a test', + "text": { + "@color": "red", + "@stroke": "2", + "#text": "This is a test", } } print(xmltodict.unparse(my_dict, pretty=True)) diff --git a/XML/XML_to_dict__xmltodict__examples/dict_to_xml__unparse__real_example.py b/XML/XML_to_dict__xmltodict__examples/dict_to_xml__unparse__real_example.py index 88aabe2d7..59a327073 100644 --- a/XML/XML_to_dict__xmltodict__examples/dict_to_xml__unparse__real_example.py +++ b/XML/XML_to_dict__xmltodict__examples/dict_to_xml__unparse__real_example.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://ru.stackoverflow.com/q/861914/201445 @@ -18,11 +18,9 @@ root = dict() for i, item in enumerate(items, 1): - root['person' + str(i)] = item + root["person" + str(i)] = item -my_dict = { - 'root': root -} +my_dict = {"root": root} # my_dict = { # 'root': { # 'person1': {"first_name": "Ivan", "last_name": "Ivanov", "city": "Moscow"}, diff --git a/XML/XML_to_dict__xmltodict__examples/hello_world.py b/XML/XML_to_dict__xmltodict__examples/hello_world.py index 2c6bbf829..e135e3d2f 100644 --- a/XML/XML_to_dict__xmltodict__examples/hello_world.py +++ b/XML/XML_to_dict__xmltodict__examples/hello_world.py @@ -1,17 +1,20 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/martinblech/xmltodict +import json + # pip install xmltodict import xmltodict -doc = xmltodict.parse(""" +doc = xmltodict.parse( + """ elements @@ -19,16 +22,18 @@ element as well -""") - -print(doc['mydocument']['@has']) # an attribute -print(doc['mydocument']['and']["many"]) # ['elements', 'more elements'] -print(doc['mydocument']['plus']) # OrderedDict([('@a', 'complex'), ('#text', 'element as well')]) -print(doc['mydocument']['plus']['@a']) # complex -print(doc['mydocument']['plus']['#text']) # element as well +""" +) + +print(doc["mydocument"]["@has"]) # an attribute +print(doc["mydocument"]["and"]["many"]) # ['elements', 'more elements'] +print( + doc["mydocument"]["plus"] +) # OrderedDict([('@a', 'complex'), ('#text', 'element as well')]) +print(doc["mydocument"]["plus"]["@a"]) # complex +print(doc["mydocument"]["plus"]["#text"]) # element as well print() -import json print(json.dumps(doc, indent=4)) # { # "mydocument": { diff --git a/XML/XML_to_dict__xmltodict__examples/namespace_support.py b/XML/XML_to_dict__xmltodict__examples/namespace_support.py index af6cd206d..6aab421ff 100644 --- a/XML/XML_to_dict__xmltodict__examples/namespace_support.py +++ b/XML/XML_to_dict__xmltodict__examples/namespace_support.py @@ -1,12 +1,14 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/martinblech/xmltodict#namespace-support +import json + # pip install xmltodict import xmltodict @@ -24,7 +26,6 @@ """ doc = xmltodict.parse(xml, process_namespaces=True) -import json print(json.dumps(doc, indent=4)) # { # "http://defaultns.com/:root": { @@ -34,13 +35,13 @@ # } # } -print('\n') +print("\n") # It also lets you collapse certain namespaces to shorthand prefixes, or skip them altogether: namespaces = { - 'http://defaultns.com/': None, # skip this namespace - 'http://a.com/': 'ns_a', # collapse "http://a.com/" -> "ns_a" + "http://defaultns.com/": None, # skip this namespace + "http://a.com/": "ns_a", # collapse "http://a.com/" -> "ns_a" } doc = xmltodict.parse(xml, process_namespaces=True, namespaces=namespaces) print(json.dumps(doc, indent=4)) diff --git a/XML/XML_to_dict__xmltodict__examples/postprocessor.py b/XML/XML_to_dict__xmltodict__examples/postprocessor.py index f88e6cd85..cbe2c4844 100644 --- a/XML/XML_to_dict__xmltodict__examples/postprocessor.py +++ b/XML/XML_to_dict__xmltodict__examples/postprocessor.py @@ -1,29 +1,31 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/martinblech/xmltodict +import json + # pip install xmltodict import xmltodict -# The optional argument `postprocessor` is a function that takes `path`, `key` and `value` as positional arguments +# The optional argument `postprocessor` is a function that takes `path`, `key` and `value` as positional arguments # and returns a new `(key, value)` pair where both `key` and `value` may have changed. Usage example: + def postprocessor(path, key, value): try: - return key + ':int', int(value) + return key + ":int", int(value) except (ValueError, TypeError): return key, value -doc = xmltodict.parse('12x', postprocessor=postprocessor) +doc = xmltodict.parse("12x", postprocessor=postprocessor) -import json print(json.dumps(doc, indent=4)) # { # "a": { diff --git a/XML/XML_to_dict__xmltodict__examples/simple_python_object_to_xml__unparse.py b/XML/XML_to_dict__xmltodict__examples/simple_python_object_to_xml__unparse.py index 5700c9901..1297bfeb7 100644 --- a/XML/XML_to_dict__xmltodict__examples/simple_python_object_to_xml__unparse.py +++ b/XML/XML_to_dict__xmltodict__examples/simple_python_object_to_xml__unparse.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/martinblech/xmltodict#roundtripping @@ -12,21 +12,21 @@ class Dog: - def __init__(self, name): + def __init__(self, name) -> None: self.name = name - self.type = 'Animal' + self.type = "Animal" self.paws = 4 self.has_tail = True - def __repr__(self): + def __repr__(self) -> str: return f'' -dog = Dog('Ray') +dog = Dog("Ray") print(dog) # print() -print(xmltodict.unparse({'Dog': dog.__dict__}, pretty=True)) +print(xmltodict.unparse({"Dog": dog.__dict__}, pretty=True)) # # # Ray diff --git a/XML/XML_to_dict__xmltodict__examples/streaming_mode.py b/XML/XML_to_dict__xmltodict__examples/streaming_mode.py index cd78e6e77..ead6df188 100644 --- a/XML/XML_to_dict__xmltodict__examples/streaming_mode.py +++ b/XML/XML_to_dict__xmltodict__examples/streaming_mode.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/martinblech/xmltodict#streaming-mode @@ -11,8 +11,8 @@ import xmltodict -def handle(path, item): - print('path: {} item: {}'.format(path, repr(item))) +def handle(path, item) -> bool: + print(f"path: {path} item: {item!r}") return True diff --git a/XML/XML_to_dict__xmltodict__examples/streaming_mode__from_url_gzip.py b/XML/XML_to_dict__xmltodict__examples/streaming_mode__from_url_gzip.py index 69218047b..49daf60e6 100644 --- a/XML/XML_to_dict__xmltodict__examples/streaming_mode__from_url_gzip.py +++ b/XML/XML_to_dict__xmltodict__examples/streaming_mode__from_url_gzip.py @@ -1,32 +1,32 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/martinblech/xmltodict#streaming-mode -# pip install xmltodict -import xmltodict - import gzip from urllib.request import urlopen +# pip install xmltodict +import xmltodict + # 241.8 MB -url = 'http://discogs-data.s3-us-west-2.amazonaws.com/data/2018/discogs_20180201_artists.xml.gz' +url = "http://discogs-data.s3-us-west-2.amazonaws.com/data/2018/discogs_20180201_artists.xml.gz" -def handle_artist(_, artist): - print(artist['name']) +def handle_artist(_, artist) -> bool: + print(artist["name"]) return True xmltodict.parse( gzip.open(urlopen(url)), item_depth=2, - item_callback=handle_artist + item_callback=handle_artist, ) # The Persuader # Mr. James Barth & A.D. diff --git a/XML/XML_to_dict__xmltodict__examples/use_other__expat.py b/XML/XML_to_dict__xmltodict__examples/use_other__expat.py index 6ed590ad7..76700eca7 100644 --- a/XML/XML_to_dict__xmltodict__examples/use_other__expat.py +++ b/XML/XML_to_dict__xmltodict__examples/use_other__expat.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/martinblech/xmltodict @@ -10,8 +10,9 @@ # pip install xmltodict import xmltodict - # You can pass an alternate version of `expat` (such as `defusedexpat`) by using the `expat` parameter. E.g: import defusedexpat -doc = xmltodict.parse('hello', expat=defusedexpat.pyexpat) + + +doc = xmltodict.parse("hello", expat=defusedexpat.pyexpat) print(doc) # OrderedDict([('a', 'hello')]) diff --git a/XML/XML_to_python_objects__lxml.objectify__examples/from_text.py b/XML/XML_to_python_objects__lxml.objectify__examples/from_text.py index 6b1cf58b0..2c6cf5588 100644 --- a/XML/XML_to_python_objects__lxml.objectify__examples/from_text.py +++ b/XML/XML_to_python_objects__lxml.objectify__examples/from_text.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://lxml.de/objectify.html @@ -12,6 +12,7 @@ # pip install lxml from lxml import objectify + text = """\ @@ -30,13 +31,13 @@ def to_date(date_str): - return datetime.strptime(date_str, '%Y-%m-%d') + return datetime.strptime(date_str, "%Y-%m-%d") root = objectify.fromstring(text) items = root.Data.Report.LeaderList.Leader -leader = max(items, key=lambda x: to_date(x.attrib['ActualDate'])) -print(leader.attrib['FIO']) # Шxxxxxxx Аxxxxx Шxxxxxx -print(leader.attrib['ActualDate']) # 2009-12-01 -print(leader.attrib['Position']) # генеральный директор +leader = max(items, key=lambda x: to_date(x.attrib["ActualDate"])) +print(leader.attrib["FIO"]) # Шxxxxxxx Аxxxxx Шxxxxxx +print(leader.attrib["ActualDate"]) # 2009-12-01 +print(leader.attrib["Position"]) # генеральный директор diff --git a/XML/XML_to_python_objects__untagle__examples/from_file.py b/XML/XML_to_python_objects__untagle__examples/from_file.py index a174e71da..19f8fab93 100644 --- a/XML/XML_to_python_objects__untagle__examples/from_file.py +++ b/XML/XML_to_python_objects__untagle__examples/from_file.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/stchris/untangle @@ -12,8 +12,11 @@ # pip install git+https://github.com/stchris/untangle.git import untangle -obj = untangle.parse('data.xml') -print(obj.root.child) # [Element(name = child, attributes = {'name': 'child1'}, cdata = ), ... -print(obj.root.child[0]['name']) # child1 -print(obj.root.child[1].cdata) # Text + +obj = untangle.parse("data.xml") +print( + obj.root.child +) # [Element(name = child, attributes = {'name': 'child1'}, cdata = ), ... +print(obj.root.child[0]["name"]) # child1 +print(obj.root.child[1].cdata) # Text print(repr(obj.root.child[2].cdata)) # '\n This text!\n ' diff --git a/XML/XML_to_python_objects__untagle__examples/from_text.py b/XML/XML_to_python_objects__untagle__examples/from_text.py index 893e6cc91..bf2fe79b7 100644 --- a/XML/XML_to_python_objects__untagle__examples/from_text.py +++ b/XML/XML_to_python_objects__untagle__examples/from_text.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/stchris/untangle @@ -13,7 +13,8 @@ import untangle -obj = untangle.parse('''\ +obj = untangle.parse( + """\ Text @@ -21,20 +22,27 @@ This text! -''') -print(obj.root.child) # [Element(name = child, attributes = {'name': 'child1'}, cdata = ), ... -print(obj.root.child[0]['name']) # child1 -print(obj.root.child[1].cdata) # Text +""" +) +print( + obj.root.child +) # [Element(name = child, attributes = {'name': 'child1'}, cdata = ), ... +print(obj.root.child[0]["name"]) # child1 +print(obj.root.child[1].cdata) # Text print(repr(obj.root.child[2].cdata)) # '\n This text!\n ' # print(','.join([child['name'] for child in obj.root.child])) print() print() -obj = untangle.parse('''\ +obj = untangle.parse( + """\ Text -''') -print(obj.root.child) # Element with attributes {'name': 'child2'}, children [] and cdata Text -print(obj.root.child['name']) # child2 -print(obj.root.child.cdata) # Text +""" +) +print( + obj.root.child +) # Element with attributes {'name': 'child2'}, children [] and cdata Text +print(obj.root.child["name"]) # child2 +print(obj.root.child.cdata) # Text diff --git a/XML/XML_to_python_objects__untagle__examples/from_url.py b/XML/XML_to_python_objects__untagle__examples/from_url.py index e71381f10..accb05dc9 100644 --- a/XML/XML_to_python_objects__untagle__examples/from_url.py +++ b/XML/XML_to_python_objects__untagle__examples/from_url.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/stchris/untangle @@ -12,10 +12,11 @@ # pip install git+https://github.com/stchris/untangle.git import untangle -obj = untangle.parse('https://news.yandex.ru/games.rss') + +obj = untangle.parse("https://news.yandex.ru/games.rss") channel = obj.rss.channel -print(channel.title.cdata) # Яндекс.Новости: Игры -print(channel.link.cdata) # https://news.yandex.ru/games.html?from=rss +print(channel.title.cdata) # Яндекс.Новости: Игры +print(channel.link.cdata) # https://news.yandex.ru/games.html?from=rss print(channel.image.url.cdata) # https://company.yandex.ru/i/50x23.gif print() diff --git a/XML/lxml__xml.etree__examples/css_selector.py b/XML/lxml__xml.etree__examples/css_selector.py index 2ffd15910..eb696446f 100644 --- a/XML/lxml__xml.etree__examples/css_selector.py +++ b/XML/lxml__xml.etree__examples/css_selector.py @@ -1,11 +1,13 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from lxml import html -root = html.fromstring('

Hello
world!


') -word = root.cssselect('b')[0] -print(html.tostring(word, encoding='unicode', with_tail=False)) # world + +root = html.fromstring("

Hello
world!


") + +word = root.cssselect("b")[0] +print(html.tostring(word, encoding="unicode", with_tail=False)) # world diff --git a/XML/lxml__xml.etree__examples/get_inner_html.py b/XML/lxml__xml.etree__examples/get_inner_html.py index 390eb55f7..1e72e74a0 100644 --- a/XML/lxml__xml.etree__examples/get_inner_html.py +++ b/XML/lxml__xml.etree__examples/get_inner_html.py @@ -1,31 +1,31 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -from html import escape # Доступно с Python 3.2 import lxml.html +from html import escape # SOURCE: https://ru.stackoverflow.com/a/862559/201445 def inner_html(elem): # Текст в самом начале внутри тега # (не забываем про экранирование!) - result = [escape(elem.text or '')] + result = [escape(elem.text or "")] # Все элементы-потомки for child in elem.iterchildren(): - result.append(lxml.html.tostring(child, encoding='unicode')) + result.append(lxml.html.tostring(child, encoding="unicode")) # Текст в конце тега принадлежит последнему элементу-потомку (tail) # и добавится автоматически # Собираем результат в одну строку - return ''.join(result) + return "".join(result) -if __name__ == '__main__': +if __name__ == "__main__": text = """\
Самая популярная игра в Steam diff --git a/XML/lxml__xml.etree__examples/lxml_remove_blank_text.py b/XML/lxml__xml.etree__examples/lxml_remove_blank_text.py new file mode 100644 index 000000000..10634f5f9 --- /dev/null +++ b/XML/lxml__xml.etree__examples/lxml_remove_blank_text.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from lxml import etree + + +data: bytes = b"\r\nHello\r\n\r\nworld\r\n\r\n!\r\n" + +root = etree.fromstring(data) +result: bytes = etree.tostring(root) +print(result) +# b'\nHello\n\nworld\n\n!\n' + +assert result == b'\nHello\n\nworld\n\n!\n' + +print() + +root = etree.fromstring( + text=data, + parser=etree.XMLParser(remove_blank_text=True), +) +result: bytes = etree.tostring(root) +print(result) +# b'Hello\nworld\n\n!' + +assert result == b'Hello\nworld\n\n!' diff --git a/XML/lxml__xml.etree__examples/parse_WSDL/print__operation.py b/XML/lxml__xml.etree__examples/parse_WSDL/print__operation.py index e067985d8..fa6d215a8 100644 --- a/XML/lxml__xml.etree__examples/parse_WSDL/print__operation.py +++ b/XML/lxml__xml.etree__examples/parse_WSDL/print__operation.py @@ -1,16 +1,20 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from lxml import etree -root = etree.parse(open('smev-message-exchange-service-1.3.wsdl', 'rb')) -for operation_node in root.xpath('//*[local-name()="portType"]/*[local-name()="operation"]'): - print(operation_node.attrib['name']) + +root = etree.parse(open("smev-message-exchange-service-1.3.wsdl", "rb")) + +for operation_node in root.xpath( + '//*[local-name()="portType"]/*[local-name()="operation"]' +): + print(operation_node.attrib["name"]) text = operation_node.xpath('./*[local-name()="documentation"]/text()')[0] - text = ' '.join(filter(None, map(str.strip, text.split('\n')))) + text = " ".join(filter(None, map(str.strip, text.split("\n")))) print(f' "{text}"') print() diff --git a/XML/lxml__xml.etree__examples/parse_html.py b/XML/lxml__xml.etree__examples/parse_html.py index ae871d2f6..f9ab60411 100644 --- a/XML/lxml__xml.etree__examples/parse_html.py +++ b/XML/lxml__xml.etree__examples/parse_html.py @@ -1,16 +1,20 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from lxml import html -root = html.fromstring('

Hello
world!


') -print(html.tostring(root)) # b'

Hello
world!


' -print(html.tostring(root, encoding='unicode')) #

Hello
world!


-print(html.tostring(root, pretty_print=True)) # b'
\n

Hello
world!

\n
\n
\n' -print(html.tostring(root, encoding='unicode', pretty_print=True)) + +root = html.fromstring("

Hello
world!


") + +print(html.tostring(root)) # b'

Hello
world!


' +print(html.tostring(root, encoding="unicode")) #

Hello
world!


+print( + html.tostring(root, pretty_print=True) +) # b'
\n

Hello
world!

\n
\n
\n' +print(html.tostring(root, encoding="unicode", pretty_print=True)) #
#

Hello
world!

#
diff --git a/XML/lxml__xml.etree__examples/parse_xml.py b/XML/lxml__xml.etree__examples/parse_xml.py index 596e0ea0a..e3be61ec4 100644 --- a/XML/lxml__xml.etree__examples/parse_xml.py +++ b/XML/lxml__xml.etree__examples/parse_xml.py @@ -1,16 +1,20 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from lxml import etree -root = etree.fromstring('Helloworld!') -print(etree.tostring(root)) # b'Helloworld!' -print(etree.tostring(root, encoding='unicode')) # Helloworld! -print(etree.tostring(root, pretty_print=True)) # b'\n Hello\n world!\n\n' -print(etree.tostring(root, encoding='unicode', pretty_print=True)) + +root = etree.fromstring("Helloworld!") + +print(etree.tostring(root)) # b'Helloworld!' +print(etree.tostring(root, encoding="unicode")) # Helloworld! +print( + etree.tostring(root, pretty_print=True) +) # b'\n Hello\n world!\n\n' +print(etree.tostring(root, encoding="unicode", pretty_print=True)) # # Hello # world! diff --git a/XML/lxml__xml.etree__examples/small_parsing_of_different_sites/https___aquapolis_ru_jelektronagrevatel-elecro-titan-optima-plus-380v_html.py b/XML/lxml__xml.etree__examples/small_parsing_of_different_sites/https___aquapolis_ru_jelektronagrevatel-elecro-titan-optima-plus-380v_html.py index 639f2fbbc..b6f73d799 100644 --- a/XML/lxml__xml.etree__examples/small_parsing_of_different_sites/https___aquapolis_ru_jelektronagrevatel-elecro-titan-optima-plus-380v_html.py +++ b/XML/lxml__xml.etree__examples/small_parsing_of_different_sites/https___aquapolis_ru_jelektronagrevatel-elecro-titan-optima-plus-380v_html.py @@ -1,32 +1,35 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -from lxml import html import requests +from lxml import html + -rs = requests.get('https://aquapolis.ru/jelektronagrevatel-elecro-titan-optima-plus-380v.html') +rs = requests.get( + "https://aquapolis.ru/jelektronagrevatel-elecro-titan-optima-plus-380v.html" +) root = html.fromstring(rs.content) def get_text(node): - return html.tostring(node, method='text', encoding='unicode').strip() + return html.tostring(node, method="text", encoding="unicode").strip() -for tr in root.cssselect('#super-product-table tr'): - tds = tr.cssselect('td') +for tr in root.cssselect("#super-product-table tr"): + tds = tr.cssselect("td") if not tds: continue name = get_text(tds[0]) - price = get_text(tds[1].cssselect('.price-box > .regular-price > .price')[0]) + price = get_text(tds[1].cssselect(".price-box > .regular-price > .price")[0]) - value = tds[2].cssselect('input')[0].get('value') + value = tds[2].cssselect("input")[0].get("value") stock_status = "Нет в наличии" if value == "0" else "Есть" - print('{:65} | {:16} | {}'.format(name, price, stock_status)) + print(f"{name:65} | {price:16} | {stock_status}") # Электронагреватель Elecro Titan Optima Plus СP-18 18 кВт (380В) | 211 572,00 руб. | Есть diff --git a/XML/sax_FIAS_with_progress__HOUSEGUID.py b/XML/sax_FIAS_with_progress__HOUSEGUID.py index c657f5372..39f211183 100644 --- a/XML/sax_FIAS_with_progress__HOUSEGUID.py +++ b/XML/sax_FIAS_with_progress__HOUSEGUID.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import xml.sax @@ -13,32 +13,32 @@ class AttrHandler(xml.sax.handler.ContentHandler): - def startDocument(self): + def startDocument(self) -> None: self.it = iter(tqdm(iter(lambda: 0, 1))) - def startElement(self, name, attrs): - if 'HOUSEGUID' in attrs: - all_house_guid.add(attrs['HOUSEGUID']) + def startElement(self, name, attrs) -> None: + if "HOUSEGUID" in attrs: + all_house_guid.add(attrs["HOUSEGUID"]) next(self.it) -print('Сбор HOUSEGUID') +print("Сбор HOUSEGUID") parser = xml.sax.make_parser() parser.setContentHandler(AttrHandler()) -parser.parse('AS_HOUSE_20210318_88f2df80-430a-400f-9373-da5b2c80e051.XML') +parser.parse("AS_HOUSE_20210318_88f2df80-430a-400f-9373-da5b2c80e051.XML") -print(f'Найдено {len(all_house_guid)}') +print(f"Найдено {len(all_house_guid)}") -print('Сохранение в JSON...') +print("Сохранение в JSON...") -with open('all_house_guid.json', 'w') as f: - f.write('[') +with open("all_house_guid.json", "w") as f: + f.write("[") for i, guid in tqdm(enumerate(all_house_guid), total=len(all_house_guid)): if i > 0: - f.write(',') + f.write(",") f.write(f'"{guid}"') - f.write(']') + f.write("]") diff --git a/XML/sax_FIAS_with_progress__HOUSEGUID__no_collect.py b/XML/sax_FIAS_with_progress__HOUSEGUID__no_collect.py index 62609dc5d..71e03517a 100644 --- a/XML/sax_FIAS_with_progress__HOUSEGUID__no_collect.py +++ b/XML/sax_FIAS_with_progress__HOUSEGUID__no_collect.py @@ -1,27 +1,27 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import xml.sax from tqdm import tqdm -f = open('all_house_guid.json', 'w') -f.write('[') +f = open("all_house_guid.json", "w") +f.write("[") class AttrHandler(xml.sax.handler.ContentHandler): - def startDocument(self): + def startDocument(self) -> None: self.it = iter(tqdm(iter(lambda: 0, 1))) self.number = 0 - def startElement(self, name, attrs): - if 'HOUSEGUID' in attrs: - guid = attrs['HOUSEGUID'] + def startElement(self, name, attrs) -> None: + if "HOUSEGUID" in attrs: + guid = attrs["HOUSEGUID"] if self.number > 0: - f.write(',') + f.write(",") f.write(f'"{guid}"') self.number += 1 @@ -29,11 +29,11 @@ def startElement(self, name, attrs): next(self.it) -print('Сбор HOUSEGUID и сохранение в JSON') +print("Сбор HOUSEGUID и сохранение в JSON") parser = xml.sax.make_parser() parser.setContentHandler(AttrHandler()) -parser.parse('AS_HOUSE_20210318_88f2df80-430a-400f-9373-da5b2c80e051.XML') +parser.parse("AS_HOUSE_20210318_88f2df80-430a-400f-9373-da5b2c80e051.XML") -f.write(']') +f.write("]") f.close() diff --git a/XML/sax_FIAS_with_progress__HOUSEGUID__no_collect__with_write_handler.py b/XML/sax_FIAS_with_progress__HOUSEGUID__no_collect__with_write_handler.py index 916bb10e1..644ab2882 100644 --- a/XML/sax_FIAS_with_progress__HOUSEGUID__no_collect__with_write_handler.py +++ b/XML/sax_FIAS_with_progress__HOUSEGUID__no_collect__with_write_handler.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import xml.sax @@ -9,37 +9,37 @@ class WriteHouseGuidHandler(xml.sax.handler.ContentHandler): - def __init__(self, file_name: str): + def __init__(self, file_name: str) -> None: super().__init__() - self.f = open(file_name, 'w') + self.f = open(file_name, "w") self.it = None self.number = 0 - def startDocument(self): + def startDocument(self) -> None: self.it = iter(tqdm(iter(lambda: 0, 1))) self.number = 0 - self.f.write('[') + self.f.write("[") - def startElement(self, name, attrs): - if 'HOUSEGUID' in attrs: - guid = attrs['HOUSEGUID'] + def startElement(self, name, attrs) -> None: + if "HOUSEGUID" in attrs: + guid = attrs["HOUSEGUID"] if self.number > 0: - self.f.write(',') + self.f.write(",") self.f.write(f'"{guid}"') self.number += 1 next(self.it) - def endDocument(self): - self.f.write(']') + def endDocument(self) -> None: + self.f.write("]") self.f.close() -print('Сбор HOUSEGUID и сохранение в JSON') +print("Сбор HOUSEGUID и сохранение в JSON") parser = xml.sax.make_parser() -parser.setContentHandler(WriteHouseGuidHandler('all_house_guid.json')) -parser.parse('AS_HOUSE_20210318_88f2df80-430a-400f-9373-da5b2c80e051.XML') +parser.setContentHandler(WriteHouseGuidHandler("all_house_guid.json")) +parser.parse("AS_HOUSE_20210318_88f2df80-430a-400f-9373-da5b2c80e051.XML") diff --git a/XML/xml.dom.minidom__examples/print_element_with_max_date.py b/XML/xml.dom.minidom__examples/print_element_with_max_date.py index 6d6a87507..294384d00 100644 --- a/XML/xml.dom.minidom__examples/print_element_with_max_date.py +++ b/XML/xml.dom.minidom__examples/print_element_with_max_date.py @@ -1,11 +1,13 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" +import xml.dom.minidom from datetime import datetime + # SOURCE: https://ru.stackoverflow.com/questions/905565/ text = """\ @@ -25,16 +27,13 @@ def to_date(date_str): - return datetime.strptime(date_str, '%Y-%m-%d') + return datetime.strptime(date_str, "%Y-%m-%d") -# Из стандартной библиотеки -import xml.dom.minidom - dom = xml.dom.minidom.parseString(text) -items = dom.getElementsByTagName('Leader') -leader = max(items, key=lambda x: to_date(x.attributes['ActualDate'].value)) +items = dom.getElementsByTagName("Leader") +leader = max(items, key=lambda x: to_date(x.attributes["ActualDate"].value)) -print(leader.attributes['FIO'].value) # Шxxxxxxx Аxxxxx Шxxxxxx -print(leader.attributes['ActualDate'].value) # 2009-12-01 -print(leader.attributes['Position'].value) # генеральный директор +print(leader.attributes["FIO"].value) # Шxxxxxxx Аxxxxx Шxxxxxx +print(leader.attributes["ActualDate"].value) # 2009-12-01 +print(leader.attributes["Position"].value) # генеральный директор diff --git a/XML/xml.etree.ElementTree__examples/create_xml.py b/XML/xml.etree.ElementTree__examples/create_xml.py index 5dfad2993..6f63fdb9e 100644 --- a/XML/xml.etree.ElementTree__examples/create_xml.py +++ b/XML/xml.etree.ElementTree__examples/create_xml.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://docs.python.org/3/library/xml.etree.elementtree.html @@ -12,28 +12,28 @@ from pretty_print import indent -root = ET.Element('data') +root = ET.Element("data") -country = ET.Element('country', name="Liechtenstein") +country = ET.Element("country", name="Liechtenstein") -rank = ET.Element('rank', updated="yes") -rank.text = '2' +rank = ET.Element("rank", updated="yes") +rank.text = "2" country.append(rank) -year = ET.Element('year') -year.text = '2008' +year = ET.Element("year") +year.text = "2008" country.append(year) -gdppc = ET.Element('gdppc') -gdppc.text = '141100' +gdppc = ET.Element("gdppc") +gdppc.text = "141100" country.append(gdppc) -country.append(ET.Element('neighbor', name="Austria", direction="E")) -country.append(ET.Element('neighbor', name="Switzerland", direction="W")) +country.append(ET.Element("neighbor", name="Austria", direction="E")) +country.append(ET.Element("neighbor", name="Switzerland", direction="W")) root.append(country) -country = ET.Element('country', name="Singapore") +country = ET.Element("country", name="Singapore") root.append(country) ... @@ -55,7 +55,7 @@ etree = ET.ElementTree(root) f = io.BytesIO() -etree.write(f, encoding='utf-8', xml_declaration=True) +etree.write(f, encoding="utf-8", xml_declaration=True) print(f.getvalue().decode(encoding="utf-8")) # # diff --git a/XML/xml.etree.ElementTree__examples/dict_to_xml.etree.ElementTree.py b/XML/xml.etree.ElementTree__examples/dict_to_xml.etree.ElementTree.py index cbb7bd860..f59a0e96e 100644 --- a/XML/xml.etree.ElementTree__examples/dict_to_xml.etree.ElementTree.py +++ b/XML/xml.etree.ElementTree__examples/dict_to_xml.etree.ElementTree.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import xml.etree.ElementTree as ET @@ -12,16 +12,16 @@ {"first_name": "Sergey", "last_name": "Sidorov", "city": "Sochi"}, ] -root = ET.Element('root') +root = ET.Element("root") for i, item in enumerate(items, 1): - person = ET.SubElement(root, f'person{i}') + person = ET.SubElement(root, f"person{i}") for k, v in item.items(): ET.SubElement(person, k).text = v tree = ET.ElementTree(root) -tree.write('person.xml') +tree.write("person.xml") # # # Ivan diff --git a/XML/xml.etree.ElementTree__examples/pretty_print.py b/XML/xml.etree.ElementTree__examples/pretty_print.py index 4561ca3c7..93836b547 100644 --- a/XML/xml.etree.ElementTree__examples/pretty_print.py +++ b/XML/xml.etree.ElementTree__examples/pretty_print.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://docs.python.org/3/library/xml.etree.elementtree.html @@ -10,15 +10,15 @@ # SOURCE: http://effbot.org/zone/element-lib.htm#prettyprint -def indent(elem, level=0): - i = "\n" + level*" " +def indent(elem, level=0) -> None: + i = "\n" + level * " " if len(elem): if not elem.text or not elem.text.strip(): elem.text = i + " " if not elem.tail or not elem.tail.strip(): elem.tail = i for elem in elem: - indent(elem, level+1) + indent(elem, level + 1) if not elem.tail or not elem.tail.strip(): elem.tail = i else: @@ -26,12 +26,12 @@ def indent(elem, level=0): elem.tail = i -if __name__ == '__main__': - root = ET.Element('data') +if __name__ == "__main__": + root = ET.Element("data") - country = ET.Element('country', name="Liechtenstein") - rank = ET.Element('rank', updated="yes") - rank.text = '2' + country = ET.Element("country", name="Liechtenstein") + rank = ET.Element("rank", updated="yes") + rank.text = "2" country.append(rank) root.append(country) diff --git a/XML/xml.etree.ElementTree__examples/print_element_with_max_date.py b/XML/xml.etree.ElementTree__examples/print_element_with_max_date.py index 2cf12595d..6f1bc5971 100644 --- a/XML/xml.etree.ElementTree__examples/print_element_with_max_date.py +++ b/XML/xml.etree.ElementTree__examples/print_element_with_max_date.py @@ -1,11 +1,13 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" +import xml.etree.ElementTree as ET from datetime import datetime + # SOURCE: https://ru.stackoverflow.com/questions/905565/ text = """\ @@ -25,18 +27,16 @@ def to_date(date_str): - return datetime.strptime(date_str, '%Y-%m-%d') + return datetime.strptime(date_str, "%Y-%m-%d") -# Из стандартной библиотеки -import xml.etree.ElementTree as ET root = ET.fromstring(text) -items = root.iter('Leader') +items = root.iter("Leader") # OR: # items = root.findall('.//Leader') -leader = max(items, key=lambda x: to_date(x.attrib['ActualDate'])) +leader = max(items, key=lambda x: to_date(x.attrib["ActualDate"])) -print(leader.attrib['FIO']) # Шxxxxxxx Аxxxxx Шxxxxxx -print(leader.attrib['ActualDate']) # 2009-12-01 -print(leader.attrib['Position']) # генеральный директор +print(leader.attrib["FIO"]) # Шxxxxxxx Аxxxxx Шxxxxxx +print(leader.attrib["ActualDate"]) # 2009-12-01 +print(leader.attrib["Position"]) # генеральный директор diff --git a/XML/xml.etree.ElementTree__examples/xml_declaration.py b/XML/xml.etree.ElementTree__examples/xml_declaration.py index 11ce349bb..a1cab22d8 100644 --- a/XML/xml.etree.ElementTree__examples/xml_declaration.py +++ b/XML/xml.etree.ElementTree__examples/xml_declaration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://docs.python.org/3/library/xml.etree.elementtree.html @@ -12,12 +12,12 @@ from pretty_print import indent -root = ET.Element('data') +root = ET.Element("data") -country = ET.Element('country', name="Liechtenstein") +country = ET.Element("country", name="Liechtenstein") -rank = ET.Element('rank', updated="yes") -rank.text = '2' +rank = ET.Element("rank", updated="yes") +rank.text = "2" country.append(rank) root.append(country) @@ -34,7 +34,7 @@ etree = ET.ElementTree(root) f = io.BytesIO() -etree.write(f, encoding='utf-8', xml_declaration=True) +etree.write(f, encoding="utf-8", xml_declaration=True) print(f.getvalue().decode(encoding="utf-8")) # # diff --git a/XML/xml.parsers.expat__examples__like_sax/hello_world.py b/XML/xml.parsers.expat__examples__like_sax/hello_world.py index 18dbd6df6..0fa8d4d0d 100644 --- a/XML/xml.parsers.expat__examples__like_sax/hello_world.py +++ b/XML/xml.parsers.expat__examples__like_sax/hello_world.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://docs.python.org/3.6/library/pyexpat.html @@ -11,16 +11,16 @@ # 3 handler functions -def on_start_element(name, attrs): - print('Start element:', name, attrs) +def on_start_element(name, attrs) -> None: + print("Start element:", name, attrs) -def on_end_element(name): - print('End element:', name) +def on_end_element(name) -> None: + print("End element:", name) -def on_char_data(data): - print('Character data:', repr(data)) +def on_char_data(data) -> None: + print("Character data:", repr(data)) p = xml.parsers.expat.ParserCreate() @@ -28,9 +28,12 @@ def on_char_data(data): p.EndElementHandler = on_end_element p.CharacterDataHandler = on_char_data -p.Parse("""\ +p.Parse( + """\ Text goes here More text -""", 1) +""", + 1, +) diff --git a/XML/xml_replace_comments/xml_replace_comments.py b/XML/xml_replace_comments/xml_replace_comments.py index 88454cb4e..868ee74e4 100644 --- a/XML/xml_replace_comments/xml_replace_comments.py +++ b/XML/xml_replace_comments/xml_replace_comments.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import re @@ -9,39 +9,39 @@ from lxml import etree -def replace(file_name, save_file_name): - with open(file_name, encoding='utf8') as f: +def replace(file_name, save_file_name) -> None: + with open(file_name, encoding="utf8") as f: text = f.read() # Ищем закрывающиеся скобки комментариев и удаляем из них многочисленные дефисы, из-за которых # xml парсер ругается - text = re.sub('-{3,}>', '-->', text) + text = re.sub("-{3,}>", "-->", text) - for match in re.findall('', text): + for match in re.findall("", text): # Вырезаем текст из тега комментария comment_text = match[4:][:-3] # Заменяем дефисы одинарным, если они подряд идут от 2 и больше - comment_text = re.sub('-{2,}', '-', comment_text) - comment_tag = ''.format(comment_text) + comment_text = re.sub("-{2,}", "-", comment_text) + comment_tag = f"" if match != comment_tag: print(match) - print('\t', comment_tag, '\n') + print("\t", comment_tag, "\n") # Делаем замему в тексте text = text.replace(match, comment_tag) - with open(save_file_name, mode='w', encoding='utf8') as f_in: + with open(save_file_name, mode="w", encoding="utf8") as f_in: f_in.write(text) # TODO: уверен есть какой то метож для проверки валидности xml # Пытаемся открыть парсером, если не получится, значит xml еще невалидная - with open(save_file_name, encoding='utf8') as fb2: + with open(save_file_name, encoding="utf8") as fb2: tree = etree.XML(fb2.read().encode()) -if __name__ == '__main__': +if __name__ == "__main__": # file_name = "Mahouka_Koukou_no_Rettousei_13.fb2" # replace(file_name, '_' + file_name) @@ -59,7 +59,7 @@ def replace(file_name, save_file_name): # tree = etree.XML(fb2.read().encode()) file_name = "Mahouka_Koukou_no_Rettousei_16.fb2" - replace(file_name, '_' + file_name) + replace(file_name, "_" + file_name) # for i in range(13, 17): # file_name = "Mahouka_Koukou_no_Rettousei_{}.fb2".format(i) diff --git a/_FOO_TEST_TEST/1.html b/_FOO_TEST_TEST/1.html new file mode 100644 index 000000000..16931446f --- /dev/null +++ b/_FOO_TEST_TEST/1.html @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
38
39
40
010203040506070809101112
2024
+ +

+ + + + + + + + + + + + +
091011120102030405060708
20232024
+ +

+ + + + + + + + + + + + + +
120102030405060708091011
20222023
diff --git a/_FOO_TEST_TEST/FOO_TEST_TEST.py b/_FOO_TEST_TEST/FOO_TEST_TEST.py index 962fa3083..86c64dacc 100644 --- a/_FOO_TEST_TEST/FOO_TEST_TEST.py +++ b/_FOO_TEST_TEST/FOO_TEST_TEST.py @@ -1,7 +1,888 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" +# # pip install ollama==0.6.1 +# import ollama +# from ollama import chat +# from ollama import ChatResponse +# +# +# response = ollama.generate( +# model='qwen2.5:7b', +# prompt="Выдели из фразы: 'Напомни купить молоко завтра в 9 утра' событие и время.", +# format='json', # КЛЮЧЕВОЙ МОМЕНТ +# system="Отвечай строго в формате JSON с полями 'task' и 'time'.", +# ) +# print(response['response']) +# # +# # rs = requests.get("http://localhost:11434") +# # print(rs.text) + +from ollama import chat +from pydantic import BaseModel + +# 1. Описываем структуру данных +class CityInfo(BaseModel): + city: str + population: int + is_capital: bool + about: str + region: str + country: str + +class PersonInfo(BaseModel): + full_name: str + url_github: str + city: str + about: str + country: str + +# # 2. Делаем запрос +# response = chat( +# # model='qwen2.5', +# model='qwen3:4b', +# messages=[ +# {'role': 'system', 'content': 'Отвечай на вопросы на русском языке'}, +# {'role': 'user', 'content': 'Расскажи про gil9red'}, +# ], +# format=PersonInfo.model_json_schema(), # Передаем схему JSON +# options={'temperature': 0}, +# ) +# +# # 3. Валидируем и превращаем в объект Python +# print(response) +# print(response.message.content) +# person_data = PersonInfo.model_validate_json(response.message.content) +# print(person_data) + +from datetime import datetime +dt = datetime.now() + +# 2. Делаем запрос +response = chat( + # model='qwen2.5', + model='qwen3:4b', + messages=[ + {'role': 'system', 'content': 'Отвечай на вопросы на русском языке'}, + {'role': 'user', 'content': 'Расскажи про Магнитогорск'}, + ], + format=CityInfo.model_json_schema(), # Передаем схему JSON + options={'temperature': 0}, +) + +# 3. Валидируем и превращаем в объект Python +print(response) +print(response.message.content) +city_data = CityInfo.model_validate_json(response.message.content) +print(city_data) +print(city_data.city, city_data.population) +print(datetime.now() - dt) + +quit() + +import sys +import traceback +from random import randint +from timeit import default_timer + +from PyQt6.QtWidgets import ( + QApplication, + QGraphicsScene, + QGraphicsView, + QGraphicsItem, + QGraphicsRectItem, + QGraphicsEllipseItem, + QMessageBox, + QMainWindow, +) +from PyQt6.QtCore import QRectF, QLineF, Qt, QTimer + + +def log_uncaught_exceptions(ex_cls, ex, tb) -> None: + text = f"{ex_cls.__name__}: {ex}\n" + text += "".join(traceback.format_tb(tb)) + print(text) + + if QApplication.instance(): + msg_box = QMessageBox( + QMessageBox.Critical, + "Error", + f"Error: {ex}", + parent=None, + ) + msg_box.setDetailedText(text) + msg_box.setStandardButtons(QMessageBox.Ok) + msg_box.exec() + + +sys.excepthook = log_uncaught_exceptions + + +# TODO: Пусть будет 13 x 3 +board = """\ + x x x x x +xxx xxx xxx +xxx x x xxx +""".rstrip() +# TODO: +brick_width: int = 40 +brick_height: int = 20 + +app = QApplication([]) + +scene_width, scene_height = 600, 300 +scene_rect = QRectF(0, 0, scene_width, scene_height) + +scene = QGraphicsScene() +scene.setSceneRect(scene_rect) + +# TODO: У линий есть проблема с координатами как у QRectF? +scene_top_line_item = scene.addLine(QLineF(scene_rect.topLeft(), scene_rect.topRight())) +scene_left_line_item = scene.addLine( + QLineF(scene_rect.topLeft(), scene_rect.bottomLeft()) +) +scene_bottom_line_item = scene.addLine( + QLineF(scene_rect.bottomRight(), scene_rect.bottomLeft()) +) +scene_right_line_item = scene.addLine( + QLineF(scene_rect.topRight(), scene_rect.bottomRight()) +) + +print( + "scene_top_line_item", + scene_top_line_item.sceneBoundingRect(), + scene_top_line_item.sceneBoundingRect().bottom(), +) +print( + "scene_left_line_item", + scene_left_line_item.sceneBoundingRect(), + scene_left_line_item.sceneBoundingRect().right(), +) +print( + "scene_bottom_line_item", + scene_bottom_line_item.sceneBoundingRect(), + scene_bottom_line_item.sceneBoundingRect().top(), +) +print( + "scene_right_line_item", + scene_right_line_item.sceneBoundingRect(), + scene_right_line_item.sceneBoundingRect().left(), +) + +bricks: list[QGraphicsRectItem] = [] +top: int = 0 +for line in board.splitlines(): + print(repr(line)) + left: int = 0 + for x in line: + if x == "x": + ball_item = scene.addRect( + QRectF(0, 0, brick_width, brick_height), + brush=Qt.GlobalColor.red, + ) + ball_item.setPos(left, top) + bricks.append(ball_item) + left += brick_width + + top += brick_height + +ball_radius: int = 40 + +platform_width: int = 100 +platform_height: int = 20 + +# TODO: +platform_item = scene.addRect( + QRectF( + 0, + 0, + platform_width, + platform_height, + ), + brush=Qt.GlobalColor.red, +) +platform_item.setPos( + (scene.width() / 2) - (platform_width / 2), scene.height() - platform_height +) +platform_item.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsMovable) + +ball_item = scene.addEllipse( + QRectF( + 0, + 0, + ball_radius, + ball_radius, + ), + brush=Qt.GlobalColor.green, +) +ball_item.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsMovable) + +ball_item.setPos( + platform_item.sceneBoundingRect().center().x() + - (ball_item.sceneBoundingRect().width() / 2), + platform_item.sceneBoundingRect().top() - ball_item.sceneBoundingRect().height(), +) + + +class Ball: + def __init__(self, ball_item: QGraphicsEllipseItem, v_x, v_y) -> None: + # def __init__(self, x, y, r, v_x, v_y, color): + # TODO: + self.x = ball_item.x() + self.y = ball_item.y() + self.r = ball_item.sceneBoundingRect().width() + + self.ball_item = ball_item + self.v_x = v_x + self.v_y = v_y + + self.is_collision: bool = False + # self.color = color + + def update(self) -> None: + self.x += self.v_x + self.y += self.v_y + + self.ball_item.setPos(self.x, self.y) + + # def draw(self): + # # def draw(self, screen): + # self.ball_item.setPos(self.center) + # # pygame.draw.circle(screen, self.color, self.center, self.r) + # # + # # # Нарисуем поверх первого, прозрачный второй с границей (параметр width) + # # pygame.draw.circle(screen, (0, 0, 0), self.center, self.r, 1) + + # @property + # def center(self): + # return self.x, self.y + # + # @property + # def top(self): + # return self.y - self.r + # + # @property + # def bottom(self): + # return self.y + self.r + # + # @property + # def left(self): + # return self.x - self.r + # + # @property + # def right(self): + # return self.x + self.r + + +# class Ball: +# r = 50 # Радиус шарика +# x = 0 # Координата по х центра шарика +# y = 0 # Координата по y центра шарика +# speed = 0 # Скорость движения +# dir_x = 0 # Компонент x вектора движения шарика +# dir_y = 0 # Компонент y вектора движения шарика +# # TODO: Не нужно +# damp = 10 # Скорость уменьшения скорости движения (сопротивление) +# collision = False # Признак коллизии с внешним кругом +# # TODO: Не нужно +# speed_after_collision = 300 # Скорость движения шарика после столкновения +# +# # # Функция, которая проверяет наличие коллизии шарика с внешним кругом +# # def hit_outer_circle_check(self, outer_circle: int): +# # dr = outer_circle - self.r # Разница радиусов +# # +# # # По теореме пифагора проверяем выход за пределы круга (коллизию) +# # if self.x * self.x + self.y * self.y > dr * dr: +# # # Если коллизия уже была обсчитана, но шарик еще не вернулся в круг, +# # # чтобы он не застревал больше не надо обсчитывать коллизии, поэтому выходим +# # if self.collision: +# # return +# # +# # # Устанавливаем для шарика признак коллизии +# # self.collision = True +# # +# # # Далее идет код расчета нового вектора движения +# # +# # # Найдем вектор нормали. тут он берется приближенно, +# # # в точке центра шарика в момент обсчета коллизии, +# # # при том что шарик уже проскочил границу. по идее тут +# # # необходимо посчитать точку соударения геометрически. +# # max_value = max(abs(self.x), abs(self.y)) +# # nx = -self.x / max_value +# # ny = -self.y / max_value +# # +# # # Найдем новый вектор движения по формуле +# # # r = i−2(i⋅n)n , где +# # # i - исходный вектор +# # # n - нормаль +# # # ⋅ знак скалярного произведения +# # +# # dot2 = self.dir_x * nx * 2 + self.dir_y * ny * 2 +# # self.dir_x = self.dir_x - dot2 * nx +# # self.dir_y = self.dir_y - dot2 * ny +# # +# # # Нормализуем вектор движения +# # max_value = max(abs(self.dir_x), abs(self.dir_y)) +# # self.dir_x /= max_value +# # self.dir_y /= max_value +# # +# # else: +# # # Сбрасываем признак коллизии когда шарик вернулся в круг. +# # self.collision = False +# # +# # # Функция проверки коллизии шарика и мышки +# # def hit_mouse_check(self, x, y): +# # # Если есть коллизия с внешним кругом игнорируем мышку +# # if self.collision: +# # return +# # +# # # Разница координат мышки и шарика +# # dx = self.x - x +# # dy = self.y - y +# # +# # # Проверяем по теореме Пифагора столкновение с мышкой +# # if dx * dx + dy * dy < self.r * self.r: +# # # Задаем вектор движения и нормализуем его +# # max_value = max(abs(dx), abs(dy)) +# # if not max_value: +# # return +# # +# # self.dir_x = dx / max_value +# # self.dir_y = dy / max_value +# # +# # # Задаем скорость +# # self.speed = self.speed_after_collision +# +# # Тут осуществляется передвижение +# # dt - кол-во секунд с прошлого обсчета +# def do_move(self, dt): +# # К текущей координате прибавляем вектор скорости помноженный +# # на значение скорости помноженные на прошедшее время +# self.x += self.dir_x * self.speed * dt +# self.y += self.dir_y * self.speed * dt +# +# # Тормозим объект, так же на значение зависящее от времени +# self.speed = max(0, self.speed - self.damp * dt) + + +class MainWindow(QMainWindow): + def __init__(self) -> None: + super().__init__() + + self.setWindowTitle("TODO") + + self.view = QGraphicsView() + self.view.setScene(scene) + + scene.changed.connect(self.on_scene_changed) + + timeout = 1000 // 60 + + # Используется, чтобы в независимости от количества вызовов + # tick скорость шарика была одинаковая + self.t = 0 + + # Таймер обновления движения и обработки столкновения шариков + self.timer = QTimer() + self.timer.timeout.connect(self.tick) + # TODO: + # self.timer.start(timeout) + + # TODO: Вектор вниз не нужно генерировать + def get_random_vector() -> tuple[int, int]: + pos = 0, 0 + # Если pos равен (0, 0), пересчитываем значения, т.к. шарик должен двигаться + while pos == (0, 0): + pos = randint(-3, 3), randint(-3, 3) + + return pos + + v_x, v_y = get_random_vector() + self.ball = Ball(ball_item=ball_item, v_x=v_x, v_y=v_y) + # self.ball.dir_x + # self.ball.r = ball_item.sceneBoundingRect().width() # TODO: + # self.ball.x = ball_item.sceneBoundingRect().center().x() # TODO: просто ball_item.x()? + # self.ball.y = ball_item.sceneBoundingRect().center().y() + + self.setCentralWidget(self.view) + + # TODO: + def tick(self) -> None: + # TODO: Использовать + # Считаем сколько времени прошло с прошлого обсчета + dt = default_timer() - self.t + + # self.ball.hit_mouse_check(self.mouse_center_x, self.mouse_center_y) + # self.ball.do_move(dt) + # self.ball.hit_outer_circle_check(self.outer_circle) + + ball = self.ball # TODO: + # ball.draw() + ball.update() + + # TODO: определять глубину проникновения шарика за границы и выталкивать его перед сменой вектора движения + + # # Условия отскакивания шарика от левого и правого края + # if ball.left <= 0 or ball.right >= self.width: + # ball.v_x = -ball.v_x + # + # # Условия отскакивания шарика верхнего и нижнего края + # if ball.top <= 0 or ball.bottom >= self.height: + # ball.v_y = -ball.v_y + + self.t = default_timer() + # + # self.update() + + def on_scene_changed(self, region: list[QRectF]) -> None: + print("on_scene_changed", region) + + # if self.ball.is_collision: + # return + + # TODO: технически, ball_item может быть много + + # TODO: Проверка выхода за сцену ball_item + colliding_items = ball_item.collidingItems() + print(colliding_items) + + # for item in colliding_items: + # color = Qt.GlobalColor.darkMagenta if item.collidesWithItem(ball_item) else Qt.GlobalColor.red + # + # if isinstance(item, QGraphicsRectItem): + # item.setBrush(color) + # elif isinstance(item, QGraphicsLineItem): + # item.setPen(color) + + collisions: list[str] = [] + for brick in bricks: + brick.setBrush( + Qt.GlobalColor.darkMagenta + if brick.collidesWithItem(ball_item) + else Qt.GlobalColor.red + ) + if brick in colliding_items: + # if brick.collidesWithItem(ball_item): # TODO: + collisions.append("brick") + + # NOTE: Фиксация по Y + platform_item.setY(scene.sceneRect().bottom() - platform_height) + + if ( + platform_item.sceneBoundingRect().left() + <= scene_left_line_item.sceneBoundingRect().right() + ): + platform_item.setX(scene_left_line_item.sceneBoundingRect().right()) + elif ( + platform_item.sceneBoundingRect().right() + >= scene_right_line_item.sceneBoundingRect().left() + ): + # TODO: Немного не доходит до границ + platform_item.setX( + scene_right_line_item.sceneBoundingRect().left() + - platform_item.sceneBoundingRect().width() + ) + + # if platform_item.collidesWithItem(ball_item): # TODO: + if platform_item in colliding_items: + platform_item.setBrush(Qt.GlobalColor.darkMagenta) + collisions.append("platform") + else: + platform_item.setBrush(Qt.GlobalColor.red) + + # if scene_top_line_item.collidesWithItem(ball_item): # TODO: + if scene_top_line_item in colliding_items: + collisions.append("top") + scene_top_line_item.setPen(Qt.GlobalColor.red) + else: + scene_top_line_item.setPen(Qt.GlobalColor.black) + + # if scene_right_line_item.collidesWithItem(ball_item): # TODO: + if scene_right_line_item in colliding_items: + collisions.append("right") + scene_right_line_item.setPen(Qt.GlobalColor.red) + else: + scene_right_line_item.setPen(Qt.GlobalColor.black) + + # if scene_bottom_line_item.collidesWithItem(ball_item): # TODO: + if scene_bottom_line_item in colliding_items: + collisions.append("bottom") + scene_bottom_line_item.setPen(Qt.GlobalColor.red) + else: + scene_bottom_line_item.setPen(Qt.GlobalColor.black) + + # if scene_left_line_item.collidesWithItem(ball_item): # TODO: + if scene_left_line_item in colliding_items: + collisions.append("left") + scene_left_line_item.setPen(Qt.GlobalColor.red) + else: + scene_left_line_item.setPen(Qt.GlobalColor.black) + + self.setWindowTitle( + f"collidingItems: {len(colliding_items)}. Collisions: {', '.join(collisions)}" + ) + + # Условия отскакивания шарика от левого и правого края + ball = self.ball # TODO: + # ball.is_collision = bool(collisions) + + if "left" in collisions or "right" in collisions: + ball.v_x = -ball.v_x + ball.is_collision = True + else: + ball.is_collision = False + + # Условия отскакивания шарика верхнего и нижнего края + if "top" in collisions or "bottom" in collisions: + ball.v_y = -ball.v_y + ball.is_collision = True + else: + ball.is_collision = False + + +mw = MainWindow() + +# TODO: +# n = 3 +# view.scale(1.0 / n, 1.0 / n) + +mw.resize(scene_width + 20, scene_height + 20) +mw.show() + +app.exec() + + +quit() + +from datetime import date, timedelta, datetime + +start = date(year=1992, month=8, day=18) +year = 1 +while True: + print(year, start) + start += timedelta(days=365) + year += 1 + if start.year > 2025: + break + +# print((date.today() - ).days / 366) + +quit() + +from pathlib import Path + +from typing import Any, Generator, Sized + + +def chunks(l: Sized, n: int) -> Generator[Any, None, None]: + """Yield successive n-sized chunks from l.""" + for i in range(0, len(l), n): + yield l[i : i + n] + + +p = Path("C:/Users/ipetrash/Downloads/0000_0039_Trnv_P_20250201_01_CIB0983543.ebc") +data = p.read_bytes() +for line in chunks(data, 170): + print(line.hex().upper()) + +quit() + +import copy2clipboard__via_pyperclip as copy2clipboard + +while n := input(): + value = n.title() + copy2clipboard.to(value) + print(value + "\n") + + +quit() + +from PyQt5.QtCore import Qt +from PyQt5.QtWidgets import ( + QApplication, + QDockWidget, + QMainWindow, + QTextEdit, + QPushButton, +) + +import sys +import traceback + +from PyQt5.QtWidgets import QApplication, QTextEdit, QMessageBox + + +def log_uncaught_exceptions(ex_cls, ex, tb) -> None: + text = f"{ex_cls.__name__}: {ex}:\n" + text += "".join(traceback.format_tb(tb)) + + print(text) + QMessageBox.critical(None, "Error", text) + sys.exit(1) + + +sys.excepthook = log_uncaught_exceptions + + +app = QApplication([]) + +dock_widget_2 = QDockWidget("Right2") +pb = QPushButton("!!!", clicked=dock_widget_2.setFloating) +pb.setCheckable(True) +dock_widget_2.setTitleBarWidget(pb) + +dock_widget_left = QDockWidget("Left") +# TODO: Добавить кнопку PIN, которая вытаскивает доквиджет, отвязывает от родителя, делает поверх всех окон +# Показывать кнопку возврата обратно +dock_widget_left.topLevelChanged.connect( + lambda flag: ( + dock_widget_left.setWindowFlag(Qt.WindowStaysOnTopHint, flag), + dock_widget_left.setParent(None) if flag else None, + dock_widget_left.show(), + ) +) +# dock_widget_left.setWindowFlags(Qt.WindowType.Window) +# dock_widget_left.show() + +mw = QMainWindow() +mw.setCentralWidget(QTextEdit()) +mw.addDockWidget(Qt.DockWidgetArea.RightDockWidgetArea, QDockWidget("Right")) +mw.addDockWidget(Qt.DockWidgetArea.RightDockWidgetArea, dock_widget_2) +mw.addDockWidget(Qt.DockWidgetArea.LeftDockWidgetArea, dock_widget_left) +mw.show() + +app.exec() + + +quit() + +from dataclasses import dataclass, field +from datetime import datetime, timedelta, date, timezone + + +def add_to_month(d: date, inc: bool = True, number: int = 1) -> date: + d = d.replace(day=1) + + year = d.year + month = d.month + + for _ in range(number): + month += 1 if inc else -1 + if month > 12: + month = 1 + year += 1 + elif month < 1: + month = 12 + year -= 1 + + d = date(year=year, month=month, day=1) + + return d + + +""" +Date: 06 мая 2024 г. 21:11:42 | Release version 3.2.40.10 (release based on revision 324264) +Date: 03 июля 2024 г. 19:11:35 | Release version 3.2.41.10 (release based on revision 327113) +Date: 04 сентября 2024 г. 15:08:49 | Release version 3.2.42.10 (release based on revision 330920) +Date: 05 ноября 2024 г. 19:40:28 | Release version 3.2.43.10 (release based on revision 335027) + +26, 11.01.2022, 14.03.2022, 15.11.2022 +8, + +27, 03.03.2022, 27.05.2022, 09.12.2022 +7, +1 +28, 05.05.2022, 15.07.2022, 10.02.2023 +7, +2 +29, 04.07.2022, 15.09.2022, 24.05.2023 +8, +3 + +30, 01.09.2022, 15.11.2022, 05.06.2023 +7, +1 +31, 01.11.2023, 19.01.2023, 01.08.2023 +7, +2 +32, 09.01.2023, 14.03.2023, 07.11.2023 +8, +3 + +33, 02.03.2023, 18.05.2023, 04.12.2023 +7, +1 +34, 04.05.2023, 21.07.2023, 01.02.2024 +7, +2 +35, 05.07.2023, 15.09.2023, 03.05.2024 +8, +3 + +36, 04.09.2023, 21.11.2023, 07.06.2024 +7, +1 +37, 02.11.2023, 23.01.2024, 05.08.2024 +7, +2 +38, 11.01.2024, 15.03.2024, 28.11.2024 +8, +3 +""" + + +INIT_RELEASE_VERSION: int = 31 +INIT_RELEASE_DATE: date = date(year=2022, month=11, day=1) + + +@dataclass +class Release: + version: int + date: date + free_commit_date: date = field(init=False) + testing_finish_date: date = field(init=False) + support_end_date: date = field(init=False) + + def __post_init__(self) -> None: + self.free_commit_date = add_to_month(self.date, number=1) - timedelta(days=1) + self.testing_finish_date = add_to_month(self.date, number=2) + self.support_end_date = add_to_month( + self.testing_finish_date, + # NOTE: Месяца 3 и 9, похоже, связаны с IPS mandates + number=8 if self.testing_finish_date.month in (3, 9) else 7, + ) + + @classmethod + def get_by(cls, d: date = None, version: int = None) -> "Release": + if d is None and version is None: + # TODO: Нормальное исключение + raise Exception() + + if d is not None: + _is_found = lambda r: r.date <= d < r.testing_finish_date + else: + _is_found = lambda r: r.version == version + + if d is not None: + _is_need_next = lambda r: d > r.date + else: + _is_need_next = lambda r: version > r.version + + release = Release( + version=INIT_RELEASE_VERSION, + date=INIT_RELEASE_DATE, + ) + + while True: + if _is_found(release): + return release + + release = ( + release.get_next_release() + if _is_need_next(release) + else release.get_prev_release() + ) + + @classmethod + def get_by_date(cls, d: date) -> "Release": + return cls.get_by(d=d) + + @classmethod + def get_by_version(cls, version: int) -> "Release": + return cls.get_by(version=version) + + @classmethod + def get_last_release(cls) -> "Release": + return cls.get_by_date(date.today()) + + def get_next_release(self) -> "Release": + return Release( + version=self.version + 1, + date=add_to_month(self.date, number=2), + ) + + def get_prev_release(self) -> "Release": + return Release( + version=self.version - 1, + date=add_to_month(self.date, inc=False, number=2), + ) + + def is_last_release(self) -> bool: + return self == self.get_last_release() + + +last_release: Release = Release.get_last_release() +print("last_release:", last_release) +print("trunk:", last_release.get_next_release()) +print() + +releases: list[Release] = [ + Release.get_by_version(version) + for version in range(last_release.version - 6, last_release.version + 6 + 1) +] +for release in releases: + print(release, release.is_last_release()) + + +# for _ in range(15): +# release = releases[-1] +# releases.append(release.get_next_release()) + + +# TODO: В тесты +# release = releases[-1] +# releases_v2 = [release] +# for _ in range(15): +# release = release.get_prev_release() +# releases_v2.append(release) +# print(releases_v2 == releases) +# +# for r1, r2 in zip(releases, releases_v2[::-1]): +# print(r1 == r2) +# print(f"{r1}\n{r2}") +# print() + +# for r in releases: +# print(r) + + +# d = date.today().replace(day=1) +# print(d) +# print() +# +# for _ in range(20): +# d = change_month(d) +# print(d) + + +print("\n" + "-" * 100 + "\n") + + +def get_items( + start_date: date, + end_date: date, + delta: timedelta, +) -> list[tuple[date, date]]: + items = [] + + dt = end_date + while True: + if dt <= start_date: + break + + dt1 = dt + dt -= delta + + if dt < start_date: + dt = start_date + + items.append((dt, dt1)) + + return items + + +def to_ms(d: date) -> int: + utc_timestamp = datetime.combine( + d, datetime.min.time(), tzinfo=timezone.utc + ).timestamp() + return int(utc_timestamp * 1000) + + +d = datetime.now(timezone.utc).date() +print(d) +# 2025-01-10 + +for d1, d2 in get_items( + start_date=d - timedelta(weeks=6), + end_date=d + timedelta(days=1), + delta=timedelta(weeks=1), +): + print(f"{d1} - {d2}. {to_ms(d1)}+{to_ms(d2)}") +""" +2025-01-04 - 2025-01-11. 1735948800000+1736553600000 +2024-12-28 - 2025-01-04. 1735344000000+1735948800000 +2024-12-21 - 2024-12-28. 1734739200000+1735344000000 +2024-12-14 - 2024-12-21. 1734134400000+1734739200000 +2024-12-07 - 2024-12-14. 1733529600000+1734134400000 +2024-11-30 - 2024-12-07. 1732924800000+1733529600000 +2024-11-29 - 2024-11-30. 1732838400000+1732924800000 +""" diff --git a/download_file_with_progressbar/utils.py b/_FOO_TEST_TEST/config.py similarity index 51% rename from download_file_with_progressbar/utils.py rename to _FOO_TEST_TEST/config.py index 6432bece4..07b3e6c3d 100644 --- a/download_file_with_progressbar/utils.py +++ b/_FOO_TEST_TEST/config.py @@ -1,13 +1,12 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -import sys from pathlib import Path + DIR = Path(__file__).resolve().parent -sys.path.append(str(DIR.parent)) -from human_byte_size import sizeof_fmt +PATH_DB = DIR / "db.sqlite" diff --git a/_FOO_TEST_TEST/db.py b/_FOO_TEST_TEST/db.py new file mode 100644 index 000000000..129c2fef2 --- /dev/null +++ b/_FOO_TEST_TEST/db.py @@ -0,0 +1,287 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from datetime import datetime +from dataclasses import dataclass, fields +from typing import Any, Generator, Optional, Type + +from PyQt5.QtSql import QSqlDatabase, QSqlQuery + +from config import PATH_DB + + +def get_fields(class_or_instance) -> list[str]: + return [f.name for f in fields(class_or_instance)] + + +db = QSqlDatabase.addDatabase("QSQLITE") +db.setDatabaseName(str(PATH_DB)) +if not db.open(): + raise Exception(db.lastError().text()) + + +class BaseModel: + @classmethod + def get_table_name(cls) -> str: + return cls.__name__ + + @classmethod + def create_table(cls): + raise NotImplemented() + + @classmethod + def select(cls, where: dict[str, Any] = None) -> Generator[Type["BaseModel"], None, None]: + this_fields: list[str] = get_fields(cls) + + if where: + where_filter = "AND".join(f"{k} = :{k}" for k, v in where.items()) + where_str = f"WHERE {where_filter}" + else: + where_str = "" + + query = QSqlQuery() + query.prepare( + f""" + SELECT {",".join(this_fields)} + FROM {cls.get_table_name()} + {where_str} + """ + ) + if where: + for k, v in where.items(): + query.bindValue(f":{k}", v) + query.exec() + + while query.next(): + data: dict[str, Any] = { + name: query.value(name) + for name in this_fields + } + yield cls(**data) + + @classmethod + def select_one(cls, where: dict[str, Any] = None) -> Optional[Type["BaseModel"]]: + return next( + cls.select(where=where), + None, + ) + + @classmethod + def get_inherited_models(cls) -> list[Type["BaseModel"]]: + return sorted(cls.__subclasses__(), key=lambda x: x.__name__) + + +@dataclass +class Logged(BaseModel): + id: int + date: str + total_seconds: int + total_seconds_human: str + + @classmethod + def create_table(cls) -> None: + QSqlQuery( + f""" + CREATE TABLE IF NOT EXISTS {cls.get_table_name()}( + id INTEGER PRIMARY KEY AUTOINCREMENT, + date VARCHAR(10) UNIQUE, + total_seconds INTEGER DEFAULT 0, + total_seconds_human TEXT + ) + """ + ).exec() + + @classmethod + def get_by_date(cls, date: str) -> Optional["Logged"]: + return cls.select_one(where=dict(date=date)) + + @classmethod + def add(cls, date: str) -> "Logged": + # Если уже есть + if obj := cls.get_by_date(date): + return obj + + query = QSqlQuery() + query.prepare(f"INSERT INTO {cls.get_table_name()} (date) VALUES (:date)") + query.bindValue(":date", date) + query.exec() + + return cls.get_by_date(date) + + @classmethod + def update( + cls, + id: int, + total_seconds: int, + total_seconds_human: str, + ) -> None: + query = QSqlQuery() + query.prepare( + f""" + UPDATE {cls.get_table_name()} + SET + total_seconds = :total_seconds, + total_seconds_human = :total_seconds_human + WHERE id = :id + """ + ) + query.bindValue(":id", id) + query.bindValue(":total_seconds", total_seconds) + query.bindValue(":total_seconds_human", total_seconds_human) + query.exec() + + +@dataclass +class LoggedItem(BaseModel): + uuid: str + logged_id: int + time: str + seconds: int + seconds_human: str + jira_id: str + jira_title: str + + @classmethod + def create_table(cls) -> None: + QSqlQuery( + f""" + CREATE TABLE IF NOT EXISTS {cls.get_table_name()}( + uuid TEXT PRIMARY KEY, + logged_id INTEGER, + time VARCHAR(8), + seconds INTEGER DEFAULT 0, + seconds_human TEXT, + jira_id TEXT, + jira_title TEXT, + + FOREIGN KEY (logged_id) REFERENCES Logged (id) ON DELETE CASCADE + ) + """ + ).exec() + + @classmethod + def get_by_uuid(cls, uuid: str) -> Optional["LoggedItem"]: + return cls.select_one(where=dict(uuid=uuid)) + + @classmethod + def add( + cls, + uuid: str, + logged_id: int, + time_str: str, + seconds: int, + seconds_human: str, + jira_id: str, + jira_title: str, + ) -> "LoggedItem": + # Если уже есть + if obj := cls.get_by_uuid(uuid): + return obj + + query = QSqlQuery() + query.prepare( + f""" + INSERT INTO {cls.get_table_name()} (uuid, logged_id, time, seconds, seconds_human, jira_id, jira_title) + VALUES (:uuid, :logged_id, :time, :seconds, :seconds_human, :jira_id, :jira_title) + """ + ) + query.bindValue(":uuid", uuid) + query.bindValue(":logged_id", logged_id) + query.bindValue(":time", time_str) + query.bindValue(":seconds", seconds) + query.bindValue(":seconds_human", seconds_human) + query.bindValue(":jira_id", jira_id) + query.bindValue(":jira_title", jira_title) + query.exec() + + return cls.get_by_uuid(uuid) + + +for model in BaseModel.get_inherited_models(): + model.create_table() + + +# date = "2024-09-23" +# print(Logged.get_by_date(date)) +# print(Logged.add(date)) +# print(Logged.add(date)) +# print(Logged.get_by_date(date)) +# print() +# +# for obj in Logged.select(): +# print(obj) + +items = [ + { + "uuid": "bf5540c1-2614-4521-898c-64fcd2222c1d", + "date_time": "24/08/2024 21:46:41", + "logged_human_time": "1 hour", + "logged_seconds": 3600, + "jira_id": "FOO-11202", + "jira_title": "Учет времени, не связанного с конкретной джирой" + }, + { + "uuid": "e8ea6140-daa2-46ca-981f-bee449fe4a34", + "date_time": "24/08/2024 20:56:56", + "logged_human_time": "4 hours", + "logged_seconds": 14400, + "jira_id": "FOO-10238", + "jira_title": "October 2024" + }, + { + "uuid": "64469ae4-325d-4fae-833d-7b3c61e4d8ce", + "date_time": "24/08/2024 20:28:38", + "logged_human_time": "1 hour", + "logged_seconds": 3600, + "jira_id": "FOO-10468", + "jira_title": "January 2025" + }, + { + "uuid": "1f5540c1-2614-4521-898c-64fcd2222c1d", + "date_time": "25/08/2024 11:23:11", + "logged_human_time": "1 hour", + "logged_seconds": 3600, + "jira_id": "FOO-11202", + "jira_title": "Учет времени, не связанного с конкретной джирой" + }, +] +from collections import defaultdict +date_by_items = defaultdict(list) +for item in items: + date_time = datetime.strptime(item["date_time"], "%d/%m/%Y %H:%M:%S") + date_str = date_time.date().isoformat() + date_by_items[date_str].append(item) + +for date_str, items in date_by_items.items(): + logged_id = Logged.add(date_str).id + + total_seconds: int = 0 + for item in items: + date_time = datetime.strptime(item["date_time"], "%d/%m/%Y %H:%M:%S") + time_str = date_time.time().isoformat() + + logged_seconds = item["logged_seconds"] + total_seconds += logged_seconds + + LoggedItem.add( + uuid=item["uuid"], + logged_id=logged_id, + time_str=time_str, + seconds=logged_seconds, + seconds_human=item["logged_human_time"], + jira_id=item["jira_id"], + jira_title=item["jira_title"], + ) + + # TODO: + from datetime import timedelta + total_seconds_human = str(timedelta(seconds=total_seconds)) + + Logged.update( + id=logged_id, + total_seconds=total_seconds, + total_seconds_human=total_seconds_human, + ) diff --git a/_FOO_TEST_TEST/parse_web_outlook_calendar/config.py b/_FOO_TEST_TEST/parse_web_outlook_calendar/config.py deleted file mode 100644 index 20e74f071..000000000 --- a/_FOO_TEST_TEST/parse_web_outlook_calendar/config.py +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -__author__ = 'ipetrash' - - -LOGIN = '' -PASSWORD = '' diff --git a/_FOO_TEST_TEST/parse_web_outlook_calendar/main.py b/_FOO_TEST_TEST/parse_web_outlook_calendar/main.py deleted file mode 100644 index 1c5e88c15..000000000 --- a/_FOO_TEST_TEST/parse_web_outlook_calendar/main.py +++ /dev/null @@ -1,73 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -__author__ = 'ipetrash' - - -import time - -# pip install selenium -from selenium import webdriver -from selenium.webdriver.support.ui import WebDriverWait -from selenium.webdriver.support import expected_conditions as EC -from selenium.webdriver.common.by import By - -from config import LOGIN, PASSWORD - - -URL = 'https://mail.compassplus.com' - -driver = webdriver.Firefox() - -try: - driver.implicitly_wait(10) # seconds - driver.get(URL) - print('Title: "{}"'.format(driver.title)) - - driver.find_element_by_id('username').send_keys(LOGIN) - driver.find_element_by_id('password').send_keys(PASSWORD) - - # Делаем скриншот результата - driver.save_screenshot('before_auth.png') - - driver.find_element_by_class_name("signinbutton").click() - - driver.save_screenshot('after_auth.png') - print('Title: "{}"'.format(driver.title)) - - driver.save_screenshot('before_click_on_calendar.png') - print('Title: "{}"'.format(driver.title)) - - html = driver.page_source - print('Length:', len(html)) - open('driver.before_click_on_calendar.html', 'w', encoding='utf-8').write(html) - - # Ждем и кликаем на кнопку - driver.find_element_by_xpath('//*[text()="Календарь"]').click() - - html = driver.page_source - print('Length:', len(html)) - open('driver.after_click_on_calendar.html', 'w', encoding='utf-8').write(html) - - driver.save_screenshot('after_click_on_calendar.png') - print('Title: "{}"'.format(driver.title)) - - # Ждем пока появится элемент - driver.find_element_by_css_selector('[aria-label="Представление календаря"]') - - # Даем еще время на прогрузку календаря - time.sleep(10) - - html = driver.page_source - print('Length:', len(html)) - open('driver.after_click_on_calendar_2.html', 'w', encoding='utf-8').write(html) - - driver.save_screenshot('after_click_on_calendar_2.png') - print('Title: "{}"'.format(driver.title)) - - # TODO: нужно - -finally: - # TODO: - # driver.quit() - pass diff --git a/_FOO_TEST_TEST/print_processes_in_directory.py b/_FOO_TEST_TEST/print_processes_in_directory.py new file mode 100644 index 000000000..e081ba42d --- /dev/null +++ b/_FOO_TEST_TEST/print_processes_in_directory.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from pathlib import Path +from psutil import process_iter, Process, Error + + +def is_found(p: Process, cwd: str | Path) -> bool: + if isinstance(cwd, str): + cwd = Path(cwd) + return cwd.exists() and Path(p.cwd()).is_relative_to(cwd) + + +def get_processes(cwd: str | Path = None) -> list[Process]: + items = [] + for p in process_iter(): + try: + if is_found(p, cwd): + items.append(p) + except Error: + pass + return items + + +# TODO: Вывести деревом список процессов +for p in get_processes(r"C:\DEV__TX\trunk"): + print(p, p.cwd(), p.name(), p.cmdline(), p.parent()) + +# TODO: Алгоритм преобразования список в дерево diff --git a/_FOO_TEST_TEST/tx_version_calendar.html b/_FOO_TEST_TEST/tx_version_calendar.html new file mode 100644 index 000000000..b932157f3 --- /dev/null +++ b/_FOO_TEST_TEST/tx_version_calendar.html @@ -0,0 +1,283 @@ + + + + TX Version Calendar + + + + + + +

TX Version Calendar

+ + + +
+ + + + + +
TaskMinor BugImportant BugEnd of Support
+
+ + + + \ No newline at end of file diff --git a/about clipboard changed/with_notification/main.py b/about clipboard changed/with_notification/main.py index 97cf1a3a9..1de9a1b8b 100644 --- a/about clipboard changed/with_notification/main.py +++ b/about clipboard changed/with_notification/main.py @@ -23,7 +23,7 @@ if data != last_data: # First changed without notification if last_data is not None: - title = 'Clipboard changed (len: {})'.format(len(data)) + title = f'Clipboard changed (len: {len(data)})' # Max text: 20 text = data[:100] diff --git a/about clipboard changed/with_notification/notifications.py b/about clipboard changed/with_notification/notifications.py index 983993f5c..09aa68cfa 100644 --- a/about clipboard changed/with_notification/notifications.py +++ b/about clipboard changed/with_notification/notifications.py @@ -19,7 +19,7 @@ class WindowsBalloonTip: @staticmethod - def balloon_tip(title, msg, duration=5, icon_path_name=None): + def balloon_tip(title, msg, duration=5, icon_path_name=None) -> None: message_map = { win32con.WM_DESTROY: WindowsBalloonTip.on_destroy, } @@ -62,7 +62,7 @@ def balloon_tip(title, msg, duration=5, icon_path_name=None): UnregisterClass(wc.lpszClassName, None) @staticmethod - def on_destroy(hwnd, msg, wparam, lparam): + def on_destroy(hwnd, msg, wparam, lparam) -> None: nid = (hwnd, 0) Shell_NotifyIcon(NIM_DELETE, nid) PostQuitMessage(0) # Terminate the app. diff --git a/about__user_agent/bots.py b/about__user_agent/bots.py index a996205ac..b89726929 100644 --- a/about__user_agent/bots.py +++ b/about__user_agent/bots.py @@ -70,24 +70,24 @@ ] RAMBLER = [ - 'StackRambler/2.0 (MSIE incompatible)', - 'StackRambler/2.0', + "StackRambler/2.0 (MSIE incompatible)", + "StackRambler/2.0", ] YAHOO = [ - 'Mozilla/5.0 (compatible; Yahoo! Slurp; http://help.yahoo.com/help/us/ysearch/slurp)', - 'Mozilla/5.0 (compatible; Yahoo! Slurp/3.0; http://help.yahoo.com/help/us/ysearch/slurp)', + "Mozilla/5.0 (compatible; Yahoo! Slurp; http://help.yahoo.com/help/us/ysearch/slurp)", + "Mozilla/5.0 (compatible; Yahoo! Slurp/3.0; http://help.yahoo.com/help/us/ysearch/slurp)", ] MSN = [ - 'msnbot/1.1 (+http://search.msn.com/msnbot.htm)', - 'msnbot-media/1.0 (+http://search.msn.com/msnbot.htm)', - 'msnbot-media/1.1 (+http://search.msn.com/msnbot.htm)', - 'msnbot-news (+http://search.msn.com/msnbot.htm)', + "msnbot/1.1 (+http://search.msn.com/msnbot.htm)", + "msnbot-media/1.0 (+http://search.msn.com/msnbot.htm)", + "msnbot-media/1.1 (+http://search.msn.com/msnbot.htm)", + "msnbot-news (+http://search.msn.com/msnbot.htm)", ] BING = [ - 'Mozilla/5.0 (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm)', + "Mozilla/5.0 (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm)", ] ALL = [] @@ -100,6 +100,6 @@ ALL.extend(BING) -if __name__ == '__main__': +if __name__ == "__main__": import random print(random.choice(ALL)) diff --git a/about__user_agent/fake_useragent__examples.py b/about__user_agent/fake_useragent__examples.py index 745b51d75..b54db910e 100644 --- a/about__user_agent/fake_useragent__examples.py +++ b/about__user_agent/fake_useragent__examples.py @@ -1,28 +1,16 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -# SOURCE: https://github.com/hellysmile/fake-useragent - +# SOURCE: https://github.com/fake-useragent/fake-useragent # pip install fake-useragent from fake_useragent import UserAgent -ua = UserAgent() - -print(ua.ie) -# Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.0; Trident/4.0; GTB7.4; InfoPath.3; SV1; .NET CLR 3.1.76908; WOW64; en-US) -print(ua.msie) -# Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; InfoPath.2; SLCC1; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET CLR 2.0.50727) - -print(ua['Internet Explorer']) -# Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; Zune 4.0; InfoPath.3; MS-RTC LM 8; .NET4.0C; .NET4.0E) - -print(ua.opera) -# Opera/9.80 (Windows NT 6.1; WOW64; U; pt) Presto/2.10.229 Version/11.62 +ua = UserAgent() print(ua.chrome) # Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.67 Safari/537.36 @@ -30,7 +18,7 @@ print(ua.google) # Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1866.237 Safari/537.36 -print(ua['google chrome']) +print(ua["google chrome"]) # Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.16 Safari/537.36 print(ua.firefox) diff --git a/about__user_agent/get_random_win_UserAgent.py b/about__user_agent/get_random_win_UserAgent.py index 0ad815505..7126c8da8 100644 --- a/about__user_agent/get_random_win_UserAgent.py +++ b/about__user_agent/get_random_win_UserAgent.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import random @@ -17,16 +17,16 @@ def get_chrome_version(cls) -> str: a = random.randint(40, 69) b = random.randint(2987, 3497) c = random.randint(80, 140) - return '{}.0.{}.{}'.format(a, b, c) + return f"{a}.0.{b}.{c}" @classmethod def get(cls) -> str: - a = 'Mozilla/5.0 (Windows NT {}; Win64; x64)'.format(cls.get_win_version()) - b = 'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/{} Safari/537.36'.format(cls.get_chrome_version()) - return '{} {}'.format(a, b) + a = f"Mozilla/5.0 (Windows NT {cls.get_win_version()}; Win64; x64)" + b = f"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/{cls.get_chrome_version()} Safari/537.36" + return f"{a} {b}" -if __name__ == '__main__': +if __name__ == "__main__": print(UserAgent.get()) # Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 # (KHTML, like Gecko) Chrome/41.0.2993.140 Safari/537.36 diff --git a/about__user_agent/ua_parser__examples.py b/about__user_agent/ua_parser__examples.py index 0ff45ca6e..282b28c4c 100644 --- a/about__user_agent/ua_parser__examples.py +++ b/about__user_agent/ua_parser__examples.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/ua-parser/uap-python @@ -13,8 +13,10 @@ from ua_parser import user_agent_parser -ua_string = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ' \ - '(KHTML, like Gecko) Chrome/61.0.3347.109 Safari/537.36' +ua_string = ( + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " + "(KHTML, like Gecko) Chrome/61.0.3347.109 Safari/537.36" +) pp = pprint.PrettyPrinter(indent=4) diff --git a/about__user_agent/user_agents_parse__examples.py b/about__user_agent/user_agents_parse__examples.py index 7060fe83d..e54f86282 100644 --- a/about__user_agent/user_agents_parse__examples.py +++ b/about__user_agent/user_agents_parse__examples.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/selwin/python-user-agents @@ -11,8 +11,10 @@ from user_agents import parse -ua_string = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ' \ - '(KHTML, like Gecko) Chrome/61.0.3347.109 Safari/537.36' +ua_string = ( + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " + "(KHTML, like Gecko) Chrome/61.0.3347.109 Safari/537.36" +) user_agent = parse(ua_string) print(user_agent) diff --git a/ago.py b/ago.py new file mode 100644 index 000000000..42bdca4b3 --- /dev/null +++ b/ago.py @@ -0,0 +1,166 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from datetime import timedelta +from enum import IntEnum + + +class UnitSeconds(IntEnum): + SECOND = 1 + MINUTE = 60 + HOUR = 60 * MINUTE + DAY = 24 * HOUR + WEEK = 7 * DAY + MONTH = 4 * WEEK + YEAR = 12 * MONTH + + +class L10n: + def get_template(self) -> str: + return "{value} {unit} ago" + + def get_unit(self, value: int, unit: UnitSeconds) -> str: + unit = unit.name.lower() + if value != 1: + unit = f"{unit}s" + return unit + + def get_value(self, value: int, unit: UnitSeconds) -> str: + unit = self.get_unit(value, unit) + return self.get_template().format(value=value, unit=unit) + + +class L10nRu(L10n): + def get_template(self) -> str: + return "{value} {unit} назад" + + # SOURCE: https://ru.stackoverflow.com/a/1380460/201445 + @staticmethod + def declension(n: int, form_0: str, form_1: str, form_2: str) -> str: + units = n % 10 + tens = (n // 10) % 10 + if tens == 1: + return form_0 + if units in [0, 5, 6, 7, 8, 9]: + return form_0 + if units == 1: + return form_1 + if units in [2, 3, 4]: + return form_2 + return "" + + def get_unit(self, value: int, unit: UnitSeconds) -> str: + match unit: + case UnitSeconds.SECOND: + return self.declension(value, "секунд", "секунда", "секунды") + + case UnitSeconds.MINUTE: + return self.declension(value, "минут", "минута", "минуты") + + case UnitSeconds.HOUR: + return self.declension(value, "часов", "час", "часа") + + case UnitSeconds.DAY: + return self.declension(value, "дней", "день", "дня") + + case UnitSeconds.WEEK: + return self.declension(value, "недель", "неделя", "недели") + + case UnitSeconds.MONTH: + return self.declension(value, "месяцев", "месяц", "месяца") + + case UnitSeconds.YEAR: + return self.declension(value, "лет", "год", "года") + + case _: + raise NotImplemented() + + +def ago(seconds: timedelta, l10n: L10n = L10n()) -> str: + seconds = int(seconds.total_seconds()) + if seconds < 0: + seconds = -seconds + + for unit in sorted(UnitSeconds, reverse=True): + value, seconds = divmod(seconds, unit) + if value: + return l10n.get_value(value, unit) + + return l10n.get_value(seconds, UnitSeconds.SECOND) + + +if __name__ == "__main__": + from datetime import datetime + + dt = datetime(year=2024, month=12, day=12) + + items = [ + (timedelta(seconds=0), "0 seconds ago"), + (timedelta(seconds=1), "1 second ago"), + (timedelta(seconds=59), "59 seconds ago"), + (timedelta(seconds=60), "1 minute ago"), + (timedelta(seconds=90), "1 minute ago"), + (timedelta(minutes=1), "1 minute ago"), + (timedelta(minutes=5), "5 minutes ago"), + (timedelta(minutes=45), "45 minutes ago"), + (timedelta(hours=1, minutes=45), "1 hour ago"), + (timedelta(hours=4, minutes=50), "4 hours ago"), + (timedelta(hours=23, minutes=50), "23 hours ago"), + (timedelta(hours=40, minutes=50), "1 day ago"), + (timedelta(days=1), "1 day ago"), + (timedelta(hours=48), "2 days ago"), + (timedelta(days=2), "2 days ago"), + (timedelta(days=7), "1 week ago"), + (timedelta(days=8), "1 week ago"), + (timedelta(weeks=1), "1 week ago"), + (timedelta(weeks=2), "2 weeks ago"), + (timedelta(weeks=4), "1 month ago"), + (timedelta(weeks=8), "2 months ago"), + (timedelta(weeks=12 * 4), "1 year ago"), + (timedelta(weeks=5 * 12 * 4), "5 years ago"), + ] + for value, expected in items: + actual = ago(dt - (dt - value)) + print(f"{value!r} -> {actual!r}") + assert expected == actual, f"{expected!r} != {actual!r}" + + dt2 = datetime(year=2024, month=12, day=10) + assert ago(dt2 - dt) == "2 days ago" + + print("\n" + "-" * 10 + "\n") + + items = [ + (timedelta(seconds=0), "0 секунд назад"), + (timedelta(seconds=1), "1 секунда назад"), + (timedelta(seconds=59), "59 секунд назад"), + (timedelta(seconds=60), "1 минута назад"), + (timedelta(seconds=90), "1 минута назад"), + (timedelta(minutes=1), "1 минута назад"), + (timedelta(minutes=5), "5 минут назад"), + (timedelta(minutes=45), "45 минут назад"), + (timedelta(hours=1, minutes=45), "1 час назад"), + (timedelta(hours=4, minutes=50), "4 часа назад"), + (timedelta(hours=23, minutes=50), "23 часа назад"), + (timedelta(hours=40, minutes=50), "1 день назад"), + (timedelta(days=1), "1 день назад"), + (timedelta(hours=48), "2 дня назад"), + (timedelta(days=2), "2 дня назад"), + (timedelta(days=7), "1 неделя назад"), + (timedelta(days=8), "1 неделя назад"), + (timedelta(weeks=1), "1 неделя назад"), + (timedelta(weeks=2), "2 недели назад"), + (timedelta(weeks=4), "1 месяц назад"), + (timedelta(weeks=8), "2 месяца назад"), + (timedelta(weeks=12 * 4), "1 год назад"), + (timedelta(weeks=5 * 12 * 4), "5 лет назад"), + ] + for value, expected in items: + actual = ago(dt - (dt - value), l10n=L10nRu()) + print(f"{value!r} -> {actual!r}") + assert expected == actual, f"{expected!r} != {actual!r}" + + dt2 = datetime(year=2024, month=12, day=10) + assert ago(dt2 - dt, l10n=L10nRu()) == "2 дня назад" diff --git a/aiohttp__asyncio__examples/aiohttp_check_user_agent.py b/aiohttp__asyncio__examples/aiohttp_check_user_agent.py index f5df227b4..b905dc183 100644 --- a/aiohttp__asyncio__examples/aiohttp_check_user_agent.py +++ b/aiohttp__asyncio__examples/aiohttp_check_user_agent.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import asyncio @@ -10,17 +10,17 @@ import aiohttp -async def main(): +async def main() -> None: async with aiohttp.ClientSession() as session: - async with session.get('https://httpbin.org/get') as rs: + async with session.get("https://httpbin.org/get") as rs: print("Status:", rs.status) - print("Content-type:", rs.headers['content-type']) + print("Content-type:", rs.headers["content-type"]) data = await rs.json() - print(data['headers']['User-Agent']) + print(data["headers"]["User-Agent"]) # Python/3.7 aiohttp/3.7.3 -if __name__ == '__main__': +if __name__ == "__main__": loop = asyncio.get_event_loop() loop.run_until_complete(main()) diff --git a/aiohttp__asyncio__examples/aiohttp_client.py b/aiohttp__asyncio__examples/aiohttp_client.py index 3fba5e51e..44ba6d470 100644 --- a/aiohttp__asyncio__examples/aiohttp_client.py +++ b/aiohttp__asyncio__examples/aiohttp_client.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import asyncio @@ -10,13 +10,13 @@ import aiohttp -async def main(): - url = 'https://python.org' +async def main() -> None: + url = "https://python.org" async with aiohttp.ClientSession() as session: async with session.get(url) as rs: print("Status:", rs.status) - print("Content-type:", rs.headers['Content-Type']) + print("Content-type:", rs.headers["Content-Type"]) html = await rs.text() print("Body:", html[:15], "...") @@ -25,12 +25,12 @@ async def main(): async with aiohttp.request("GET", url) as rs: print("Status:", rs.status) - print("Content-type:", rs.headers['Content-Type']) + print("Content-type:", rs.headers["Content-Type"]) html = await rs.text() print("Body:", html[:15], "...") -if __name__ == '__main__': +if __name__ == "__main__": loop = asyncio.get_event_loop() loop.run_until_complete(main()) diff --git a/aiohttp__asyncio__examples/aiohttp_server.py b/aiohttp__asyncio__examples/aiohttp_server.py index 8db089c7e..5c0f6bf08 100644 --- a/aiohttp__asyncio__examples/aiohttp_server.py +++ b/aiohttp__asyncio__examples/aiohttp_server.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install aiohttp @@ -9,7 +9,7 @@ async def handle(request): - name = request.match_info.get('name', "Anonymous") + name = request.match_info.get("name", "Anonymous") text = "Hello, " + name return web.Response(text=text) @@ -20,7 +20,7 @@ async def wshandle(request): async for msg in ws: if msg.type == web.WSMsgType.text: - await ws.send_str("Hello, {}".format(msg.data)) + await ws.send_str(f"Hello, {msg.data}") elif msg.type == web.WSMsgType.binary: await ws.send_bytes(msg.data) elif msg.type == web.WSMsgType.close: @@ -31,11 +31,11 @@ async def wshandle(request): app = web.Application() app.add_routes([ - web.get('/', handle), - web.get('/echo', wshandle), - web.get('/{name}', handle) + web.get("/", handle), + web.get("/echo", wshandle), + web.get("/{name}", handle) ]) -if __name__ == '__main__': +if __name__ == "__main__": web.run_app(app) diff --git a/aiohttp__asyncio__examples/asyncio__hello_world.py b/aiohttp__asyncio__examples/asyncio__hello_world.py index 64edc9eb4..432a7777e 100644 --- a/aiohttp__asyncio__examples/asyncio__hello_world.py +++ b/aiohttp__asyncio__examples/asyncio__hello_world.py @@ -1,16 +1,16 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import asyncio -async def main(): - print('Hello ', end='') +async def main() -> None: + print("Hello ", end="") await asyncio.sleep(1) - print('World!') + print("World!") # Python 3.7+ diff --git a/aiohttp__asyncio__examples/concurrent_requests.py b/aiohttp__asyncio__examples/concurrent_requests.py index 2980ca86b..647a97859 100644 --- a/aiohttp__asyncio__examples/concurrent_requests.py +++ b/aiohttp__asyncio__examples/concurrent_requests.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import asyncio @@ -12,7 +12,7 @@ from ignore_aiohttp_ssl_error import ignore_aiohttp_ssl_error -async def fetch_page(url: str, idx: int): +async def fetch_page(url: str, idx: int) -> None: async with aiohttp.request("GET", url) as rs: if rs.status == 200: print(f"[{idx}] Data fetched successfully") @@ -21,8 +21,8 @@ async def fetch_page(url: str, idx: int): print(rs.content) -async def main(): - url = 'https://python.org' +async def main() -> None: + url = "https://python.org" urls = [url] * 100 tasks = [ @@ -32,7 +32,7 @@ async def main(): await asyncio.gather(*tasks) -if __name__ == '__main__': +if __name__ == "__main__": import time t = time.perf_counter() @@ -40,4 +40,4 @@ async def main(): ignore_aiohttp_ssl_error(loop) loop.run_until_complete(main()) - print(f'Elapsed {time.perf_counter() - t:.2f} secs') + print(f"Elapsed {time.perf_counter() - t:.2f} secs") diff --git a/aiohttp__asyncio__examples/hello_world.py b/aiohttp__asyncio__examples/hello_world.py index 2007933e4..0a395a322 100644 --- a/aiohttp__asyncio__examples/hello_world.py +++ b/aiohttp__asyncio__examples/hello_world.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/aio-libs/aiohttp @@ -17,12 +17,12 @@ async def fetch(session, url): return await response.text() -async def main(): +async def main() -> None: async with aiohttp.ClientSession() as session: - html = await fetch(session, 'http://python.org') + html = await fetch(session, "http://python.org") print(html) -if __name__ == '__main__': +if __name__ == "__main__": loop = asyncio.get_event_loop() loop.run_until_complete(main()) diff --git a/aiohttp__asyncio__examples/hello_world__parse__lxml_etree.py b/aiohttp__asyncio__examples/hello_world__parse__lxml_etree.py index 47eb2b786..508eefbc5 100644 --- a/aiohttp__asyncio__examples/hello_world__parse__lxml_etree.py +++ b/aiohttp__asyncio__examples/hello_world__parse__lxml_etree.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/aio-libs/aiohttp @@ -20,15 +20,15 @@ async def fetch(session, url): return await response.content.read() -async def main(): +async def main() -> None: async with aiohttp.ClientSession() as session: - xml_str = await fetch(session, 'https://sdvk-oboi.ru/sitemap.xml') + xml_str = await fetch(session, "https://sdvk-oboi.ru/sitemap.xml") root = etree.fromstring(xml_str) for url in root.xpath('//*[local-name()="loc"]/text()'): print(url) -if __name__ == '__main__': +if __name__ == "__main__": loop = asyncio.get_event_loop() loop.run_until_complete(main()) diff --git a/aiohttp__asyncio__examples/ignore_aiohttp_ssl_error.py b/aiohttp__asyncio__examples/ignore_aiohttp_ssl_error.py index d3b0fe7ce..f6beffd36 100644 --- a/aiohttp__asyncio__examples/ignore_aiohttp_ssl_error.py +++ b/aiohttp__asyncio__examples/ignore_aiohttp_ssl_error.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import asyncio @@ -19,7 +19,7 @@ SSL_PROTOCOLS = (*SSL_PROTOCOLS, uvloop.loop.SSLProtocol) -def ignore_aiohttp_ssl_error(loop): +def ignore_aiohttp_ssl_error(loop) -> None: """Ignore aiohttp #3535 / cpython #13548 issue with SSL data after close There is an issue in Python 3.7 up to 3.7.3 that over-reports a @@ -41,21 +41,21 @@ def ignore_aiohttp_ssl_error(loop): orig_handler = loop.get_exception_handler() - def ignore_ssl_error(loop, context): + def ignore_ssl_error(loop, context) -> None: if context.get("message") in { "SSL error in data received", "Fatal error on transport", }: # validate we have the right exception, transport and protocol - exception = context.get('exception') - protocol = context.get('protocol') + exception = context.get("exception") + protocol = context.get("protocol") if ( isinstance(exception, ssl.SSLError) - and exception.reason == 'KRB5_S_INIT' + and exception.reason == "KRB5_S_INIT" and isinstance(protocol, SSL_PROTOCOLS) ): if loop.get_debug(): - asyncio.log.logger.debug('Ignoring asyncio SSL KRB5_S_INIT error') + asyncio.log.logger.debug("Ignoring asyncio SSL KRB5_S_INIT error") return if orig_handler is not None: orig_handler(loop, context) diff --git a/alice_and_bob.py b/alice_and_bob.py index 0b832e99e..0a31b91d7 100644 --- a/alice_and_bob.py +++ b/alice_and_bob.py @@ -1,12 +1,13 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # «Если Алиса будет кидать монетки до тех пор, пока не получит решку, следующую за орлом, а Боб – до тех пор, # пока не получит две решки подряд, то Алисе в среднем потребуется четыре броска монеты, в то время как Бобу – шесть.» + from random import randrange diff --git a/alpha2_to_country/main.py b/alpha2_to_country/main.py index 3b37f9705..c8ca59b42 100644 --- a/alpha2_to_country/main.py +++ b/alpha2_to_country/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/gil9red/SimplePyScripts/blob/f2c11477ae4410e5a9d47272b72cb69354c271b8/html_parsing/ru_wikipedia_org__wiki__ISO_3166_1.py @@ -14,26 +14,26 @@ import requests -FILE_NAME_COUNTRY = Path(__file__).resolve().parent / 'alpha2_to_country.json' +FILE_NAME_COUNTRY = Path(__file__).resolve().parent / "alpha2_to_country.json" ALPHA2_TO_COUNTRY = None -def init(): +def init() -> None: global ALPHA2_TO_COUNTRY if FILE_NAME_COUNTRY.exists(): ALPHA2_TO_COUNTRY = json.loads( - FILE_NAME_COUNTRY.read_text('utf-8') + FILE_NAME_COUNTRY.read_text("utf-8") ) return - rs = requests.get('https://ru.wikipedia.org/wiki/ISO_3166-1') - root = BeautifulSoup(rs.content, 'html.parser') + rs = requests.get("https://ru.wikipedia.org/wiki/ISO_3166-1") + root = BeautifulSoup(rs.content, "html.parser") ALPHA2_TO_COUNTRY = dict() - for tr in root.select_one('.wikitable').select('tr'): - td_list = tr.select('td') + for tr in root.select_one(".wikitable").select("tr"): + td_list = tr.select("td") if not td_list: continue @@ -45,9 +45,9 @@ def init(): json.dump( ALPHA2_TO_COUNTRY, - open(FILE_NAME_COUNTRY, 'w', encoding='utf-8'), + open(FILE_NAME_COUNTRY, "w", encoding="utf-8"), ensure_ascii=False, - indent=4 + indent=4, ) @@ -58,12 +58,12 @@ def get_country(alpha2: str) -> str: return ALPHA2_TO_COUNTRY[alpha2] -if __name__ == '__main__': - print(get_country('RU')) +if __name__ == "__main__": + print(get_country("RU")) # Россия - print(get_country('US')) + print(get_country("US")) # США - print(get_country('DE')) + print(get_country("DE")) # Германия diff --git a/analog_using_webkit.py b/analog_using_webkit.py index ceece96a3..0074f199d 100644 --- a/analog_using_webkit.py +++ b/analog_using_webkit.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """Использование selenium для загрузки и получения страницы с javascript.""" @@ -11,15 +11,20 @@ from operator import itemgetter from selenium import webdriver + url = "http://www.oddsportal.com/soccer/england/premier-league/everton-arsenal-tnWxil2o#over-under;2" browser = webdriver.Firefox() browser.get(url) soup = BeautifulSoup(browser.page_source) -data_table = soup.find('div', {'id': 'odds-data-table'}) +data_table = soup.find("div", {"id": "odds-data-table"}) -for div in data_table.find_all_next('div', attrs={'class=': 'table-container'}): - row = div.find_all(['span', 'strong']) +for div in data_table.find_all_next("div", attrs={"class=": "table-container"}): + row = div.find_all(["span", "strong"]) if len(row): - print(','.join(cell.get_text(strip=True) for cell in itemgetter(0, 4, 3, 2, 1)(row))) + print( + ",".join( + cell.get_text(strip=True) for cell in itemgetter(0, 4, 3, 2, 1)(row) + ) + ) diff --git a/analysis_append_game_date__sqlite_db__from__gist_commits/main.py b/analysis_append_game_date__sqlite_db__from__gist_commits/main.py index fbbcf7474..ae999c6bb 100644 --- a/analysis_append_game_date__sqlite_db__from__gist_commits/main.py +++ b/analysis_append_game_date__sqlite_db__from__gist_commits/main.py @@ -1,35 +1,35 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # Parser from https://github.com/gil9red/played_games/blob/f23777a1368f9124450bedac036791068d8ca099/mini_played_games_parser.py#L7 -def parse_played_games(text: str, silence: bool=False) -> dict: +def parse_played_games(text: str, silence: bool = False) -> dict: """ Функция для парсинга списка игр. """ - FINISHED_GAME = 'FINISHED_GAME' - NOT_FINISHED_GAME = 'NOT_FINISHED_GAME' - FINISHED_WATCHED = 'FINISHED_WATCHED' - NOT_FINISHED_WATCHED = 'NOT_FINISHED_WATCHED' + FINISHED_GAME = "FINISHED_GAME" + NOT_FINISHED_GAME = "NOT_FINISHED_GAME" + FINISHED_WATCHED = "FINISHED_WATCHED" + NOT_FINISHED_WATCHED = "NOT_FINISHED_WATCHED" FLAG_BY_CATEGORY = { - ' ': FINISHED_GAME, - '- ': NOT_FINISHED_GAME, - ' -': NOT_FINISHED_GAME, - ' @': FINISHED_WATCHED, - '@ ': FINISHED_WATCHED, - '-@': NOT_FINISHED_WATCHED, - '@-': NOT_FINISHED_WATCHED, + " ": FINISHED_GAME, + "- ": NOT_FINISHED_GAME, + " -": NOT_FINISHED_GAME, + " @": FINISHED_WATCHED, + "@ ": FINISHED_WATCHED, + "-@": NOT_FINISHED_WATCHED, + "@-": NOT_FINISHED_WATCHED, } # Регулярка вытаскивает выражения вида: 1, 2, 3 или 1-3, или римские цифры: III, IV import re PARSE_GAME_NAME_PATTERN = re.compile( - r'(\d+(, *?\d+)+)|(\d+ *?- *?\d+)|([MDCLXVI]+(, ?[MDCLXVI]+)+)', - flags=re.IGNORECASE + r"(\d+(, *?\d+)+)|(\d+ *?- *?\d+)|([MDCLXVI]+(, ?[MDCLXVI]+)+)", + flags=re.IGNORECASE, ) def parse_game_name(game_name: str) -> list: @@ -54,14 +54,14 @@ def parse_game_name(game_name: str) -> list: index = game_name.index(seq_str) base_name = game_name[:index].strip() - seq_str = seq_str.replace(' ', '') + seq_str = seq_str.replace(" ", "") - if ',' in seq_str: + if "," in seq_str: # '1,2,3' -> ['1', '2', '3'] - seq = seq_str.split(',') + seq = seq_str.split(",") - elif '-' in seq_str: - seq = seq_str.split('-') + elif "-" in seq_str: + seq = seq_str.split("-") # ['1', '7'] -> [1, 7] seq = list(map(int, seq)) @@ -73,7 +73,7 @@ def parse_game_name(game_name: str) -> list: return [game_name] # Сразу проверяем номер игры в серии и если она первая, то не добавляем в названии ее номер - return [base_name if num == '1' else base_name + " " + num for num in seq] + return [base_name if num == "1" else base_name + " " + num for num in seq] platforms = dict() platform = None @@ -84,7 +84,7 @@ def parse_game_name(game_name: str) -> list: continue flag = line[:2] - if flag not in FLAG_BY_CATEGORY and line.endswith(':'): + if flag not in FLAG_BY_CATEGORY and line.endswith(":"): platform_name = line[:-1] platform = { @@ -103,7 +103,7 @@ def parse_game_name(game_name: str) -> list: category_name = FLAG_BY_CATEGORY.get(flag) if not category_name: if not silence: - print('Странный формат строки: "{}"'.format(line)) + print(f'Странный формат строки: "{line}"') continue category = platform[category_name] @@ -112,7 +112,7 @@ def parse_game_name(game_name: str) -> list: for game in parse_game_name(game_name): if game in category: if not silence: - print('Предотвращено добавление дубликата игры "{}"'.format(game)) + print(f'Предотвращено добавление дубликата игры "{game}"') continue category.append(game) @@ -121,7 +121,7 @@ def parse_game_name(game_name: str) -> list: # Get FULL database from: https://github.com/gil9red/SimplePyScripts/blob/2f50908d5c70fafa885db009bfe9570f8fc111e8/PyGithub_examples/gist_history_to_sqlite_db.py -DB_FILE_NAME = '../PyGithub_examples/gist_commits.sqlite' +DB_FILE_NAME = "../PyGithub_examples/gist_commits.sqlite" def create_connect(): @@ -129,19 +129,19 @@ def create_connect(): return sqlite3.connect(DB_FILE_NAME) -FINISHED_GAME = 'FINISHED_GAME' -FINISHED_WATCHED = 'FINISHED_WATCHED' -NOT_FINISHED_GAME = 'NOT_FINISHED_GAME' -NOT_FINISHED_WATCHED = 'NOT_FINISHED_WATCHED' +FINISHED_GAME = "FINISHED_GAME" +FINISHED_WATCHED = "FINISHED_WATCHED" +NOT_FINISHED_GAME = "NOT_FINISHED_GAME" +NOT_FINISHED_WATCHED = "NOT_FINISHED_WATCHED" CATEGORIES = [FINISHED_GAME, FINISHED_WATCHED, NOT_FINISHED_GAME, NOT_FINISHED_WATCHED] -if __name__ == '__main__': +if __name__ == "__main__": from collections import defaultdict append_game_date = defaultdict(dict) with create_connect() as connect: - sql = 'SELECT committed_at, content FROM GistFile ORDER BY committed_at' + sql = "SELECT committed_at, content FROM GistFile ORDER BY committed_at" for committed_at, content in connect.execute(sql): platforms = parse_played_games(content, silence=True) @@ -155,10 +155,10 @@ def create_connect(): append_game_date[platform][category][game] = committed_at # Check - print('Ведьмак:', append_game_date['PC']['FINISHED_GAME']['Ведьмак']) - print('Dragon Age II:', append_game_date['PC']['FINISHED_GAME']['Dragon Age II']) + print("Ведьмак:", append_game_date["PC"]["FINISHED_GAME"]["Ведьмак"]) + print("Dragon Age II:", append_game_date["PC"]["FINISHED_GAME"]["Dragon Age II"]) # Dump this - with open('dumn.json', mode='w', encoding='utf-8') as f: + with open("dumn.json", mode="w", encoding="utf-8") as f: import json json.dump(append_game_date, f, ensure_ascii=False, indent=4, sort_keys=True) diff --git a/anonymization_quotes.py b/anonymization_quotes.py index dcdf738d2..0b31d6e83 100644 --- a/anonymization_quotes.py +++ b/anonymization_quotes.py @@ -1,7 +1,11 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" + + +import re +import string def anonymization_quotes(quote_text): @@ -13,21 +17,19 @@ def anonymization_quotes(quote_text): """ - import re - login_pattern = re.compile(r'^(.+?):') + login_pattern = re.compile(r"^(.+?):") # Словарь, в котором ключом является логин, а значением псевдоним - all_logins = {} + all_logins = dict() # Счетчик логинов count_logins = 0 # Сгенерируем список с псевдонимами. Список будет вида: ['aaa', 'bbb', ..., 'zzz', 'AAA', ... 'ZZZ'] - import string - login_aliases = [c*3 for c in string.ascii_letters] + login_aliases = [c * 3 for c in string.ascii_letters] # Разбиваем цитату по строчно - for line in quote_text.split('\n'): + for line in quote_text.split("\n"): # Ищем логин match = login_pattern.search(line) @@ -50,7 +52,7 @@ def anonymization_quotes(quote_text): return quote -if __name__ == '__main__': +if __name__ == "__main__": quote = """ Аня: Не хочу и комп занят Кирилл: вредный старший брат окупировал комп? diff --git a/api_clans.py b/api_clans.py index 2675cfa2e..54ee5fbb6 100644 --- a/api_clans.py +++ b/api_clans.py @@ -1,22 +1,25 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" +import base64 +import json + import requests class Api: # TODO: this - API_URL = '/api_clans/1/index.php?request=' + API_URL = "/api_clans/1/index.php?request=" - def __init__(self, login: str, password: str): + def __init__(self, login: str, password: str) -> None: self.login = login self.password = password self.session = requests.Session() - self.session.headers['Authorization'] = self.make_authorization(login, password) + self.session.headers["Authorization"] = self.make_authorization(login, password) # # Or: # self.session = requests.Session() # self.auth = (login, password) @@ -25,33 +28,31 @@ def method(self, method: str, data: dict = None) -> requests.Response: url = self.API_URL + method # Debug - print('POST: url: {}, data: {}'.format(url, data)) + print(f"POST: url: {url}, data: {data}") rs = self.session.post(url, data) # Debug print(rs) - print('rs.text: "{}"'.format(rs.text)) - import json - print('pretty rs:', json.dumps(rs.json(), indent=4, ensure_ascii=False)) - print('\n') + print(f'rs.text: "{rs.text}"') + print("pretty rs:", json.dumps(rs.json(), indent=4, ensure_ascii=False)) + print("\n") return rs def get_user_data(self, people_id=None) -> requests.Response: - return self.method('get_user_data', {'people_id': people_id}) + return self.method("get_user_data", {"people_id": people_id}) # Append more api methods @staticmethod def make_authorization(login: str, password: str) -> str: - credentials = login + ':' + password + credentials = login + ":" + password # As base64 - import base64 credentials = base64.b64encode(credentials.encode()).decode() - return 'Basic ' + credentials + return "Basic " + credentials # # NOTE: Requests debug @@ -75,41 +76,41 @@ def make_authorization(login: str, password: str) -> str: # requests_log.propagate = True -if __name__ == '__main__': +if __name__ == "__main__": # TODO: this - LOGIN = '' - PASSWORD = '' + LOGIN = "" + PASSWORD = "" api = Api(LOGIN, PASSWORD) # Получение информации о текущем пользователе rs = api.get_user_data() # Or: - rs = api.method('get_user_data') + rs = api.method("get_user_data") # Получение информации о пользователе с id = 1 rs = api.get_user_data(people_id=1) # Or: - rs = api.method('get_user_data', data={'people_id': 1}) + rs = api.method("get_user_data", data={"people_id": 1}) - print('\n') + print("\n") # Создание пользователя data = { - 'name': 'Вася', - 'lastname': 'Пупкин', - 'secondname': 'secondname', - 'sex': 'man', - 'phone': '79957777555', - 'email': 'guvuwer@p33.org', - 'pass': '123', - 'is_live': '1', + "name": "Вася", + "lastname": "Пупкин", + "secondname": "secondname", + "sex": "man", + "phone": "79957777555", + "email": "guvuwer@p33.org", + "pass": "123", + "is_live": "1", } - rs = api.method('add_user', data) + rs = api.method("add_user", data) new_user_id = rs.json() # Регистрация (Проверка кода подтверждения e-mail) - rs = api.method('check_email_code', data={'people_id': new_user_id}) + rs = api.method("check_email_code", data={"people_id": new_user_id}) email_code = rs.json() # Получение информации о новом пользователе с id = new_user_id diff --git a/append_attrs_in_class_by_condition__MetaClass.py b/append_attrs_in_class_by_condition__MetaClass.py index d52cb24d0..7c0f7f56f 100644 --- a/append_attrs_in_class_by_condition__MetaClass.py +++ b/append_attrs_in_class_by_condition__MetaClass.py @@ -1,18 +1,18 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" class MyMeta(type): def __new__(cls, name, bases, attrs): print(f"{name}, initialize={attrs['initialize']}") - if attrs['initialize']: - attrs['SUPER_VALUE'] = 42 - attrs['run'] = lambda self=None: print('run!') + if attrs["initialize"]: + attrs["SUPER_VALUE"] = 42 + attrs["run"] = lambda self=None: print("run!") - attrs['reverse_initialize'] = not attrs['initialize'] + attrs["reverse_initialize"] = not attrs["initialize"] return super().__new__(cls, name, bases, attrs) @@ -25,8 +25,8 @@ class B(A): initialize = True -print(hasattr(A, 'SUPER_VALUE')) # False -print(hasattr(B, 'SUPER_VALUE')) # True +print(hasattr(A, "SUPER_VALUE")) # False +print(hasattr(B, "SUPER_VALUE")) # True print() print(A.initialize, A.reverse_initialize) # False True @@ -34,8 +34,8 @@ class B(A): print() print(B.SUPER_VALUE) # 42 -B.run() # run! +B.run() # run! b = B() print(b.SUPER_VALUE) # 42 -b.run() # run! +b.run() # run! diff --git a/ascii_magic__examples/input.png b/ascii_magic__examples/input.png new file mode 100644 index 000000000..4d5126505 Binary files /dev/null and b/ascii_magic__examples/input.png differ diff --git a/ascii_magic__examples/print_ascii_string.py b/ascii_magic__examples/print_ascii_string.py new file mode 100644 index 000000000..1da291a89 --- /dev/null +++ b/ascii_magic__examples/print_ascii_string.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +# SOURCE: https://github.com/LeandroBarone/python-ascii_magic + + +# pip install ascii-magic +from ascii_magic import AsciiArt + + +file_name = "input.png" +my_art = AsciiArt.from_image(file_name) +print(my_art.to_ascii(columns=40, monochrome=True)) +""" + + ii + .]EE]. + ;JXEEXJ; + iqX4444Xqi + `tXg444444gXt` + +CFLdg4444gdLFC+ + xuv. >6gEEg6> .vnx + `au` :2442: `ua` + ^wXc+////[!^^![////+cXw^ + %SX4d44444GLiiLG44444d4XS% + `tEg44EEEEEXn^::^nXEEEEE44gEt` + =fPEEEEEEEEd* *dEEEEEEEEPf= + i6dhhhhhhhhh6a*%ii%*a6hhhhhhhhhd6i + ^=^^^^^^^^^^=////////=^^^^^^^^^^=^ +""" diff --git a/ascii_table.py b/ascii_table.py index 4c375c12f..9093a99f8 100644 --- a/ascii_table.py +++ b/ascii_table.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # source: http://stackoverflow.com/a/5910078/5909792 @@ -11,10 +11,7 @@ def ascii_table(rows): for i in range(len(rows[0])): lens.append( len( - max( - [str(x[i]) for x in rows] + [headers[i]], - key=lambda x: len(str(x)) - ) + max([str(x[i]) for x in rows] + [headers[i]], key=lambda x: len(str(x))) ) ) @@ -22,18 +19,356 @@ def ascii_table(rows): pattern = " | ".join(formats) hpattern = " | ".join(formats) - separator = "-+-".join(['-' * n for n in lens]) + separator = "-+-".join(["-" * n for n in lens]) text_lines = [hpattern % tuple(headers), separator] for line in rows[1:]: text_lines.append(pattern % tuple(t for t in line)) - return '\n'.join(text_lines) + return "\n".join(text_lines) -if __name__ == '__main__': - columns = ['id', 'url', 'name', 'short_name', 'birthday', 'job', 'department', 'photo', 'work_phone', 'mobile_phone', 'email'] - rows = [['#1', 'http://amiller.example.com', 'Andrew Miller', 'amiller', '11 December', 'Testing Engineer', 'CD, Product Support Service', 'amiller.jpg', '888888888888', '', 'amiller@example.com'], ['#2', 'http://ataylor.example.com', 'Anthony Taylor', 'ataylor', '17 July', 'Software Engineer', 'CD, Product Support Service', 'ataylor.jpg', '', '', 'ataylor@example.com'], ['#3', 'http://dmoore.example.com', 'Daniel Moore', 'dmoore', '2 March', 'Testing Engineer', 'CD, Product Support Service', 'dmoore.jpg', '', '', 'dmoore@example.com'], ['#4', 'http://dsmith.example.com', 'David Smith', 'dsmith', '5 January', 'Testing Engineer', 'Processing Services Division', 'dsmith.jpg', '', '', 'dsmith@example.com'], ['#5', 'http://awilson.example.com', 'Alexander Wilson', 'awilson', '11 April', 'Software Engineer', 'CD, Product Support Service', 'awilson.jpg', '', '', 'awilson@example.com'], ['#6', 'http://asmith.example.com', 'Alexander Smith', 'asmith', '3 November', 'Testing Engineer', 'CD, Product Support Service', 'asmith.jpg', '', '', 'asmith@example.com'], ['#7', 'http://jdavis.example.com', 'Jayden Davis', 'jdavis', '10 April', 'Shift Engineer', 'BSD, Presale Solution Bureau', 'jdavis.jpg', '', '', 'jdavis@example.com'], ['#8', 'http://jdavis.example.com', 'Jacob Davis', 'jdavis', '5 July', 'Shift Engineer', 'DD, Technical Translation Bureau', 'jdavis.jpg', '', '', 'jdavis@example.com'], ['#9', 'http://ejones.example.com', 'Ethan Jones', 'ejones', '14 September', 'Software Engineer', 'BSD, Presale Solution Bureau', 'ejones.jpg', '', '', 'ejones@example.com'], ['#10', 'http://abrown.example.com', 'Angel Brown', 'abrown', '15 April', 'Testing Engineer', 'Processing Services Division', 'abrown.jpg', '', '', 'abrown@example.com'], ['#11', 'http://dwilliams.example.com', 'David Williams', 'dwilliams', '23 November', 'Application Developer', 'DD, Technical Translation Bureau', 'dwilliams.jpg', '', '', 'dwilliams@example.com'], ['#12', 'http://ddavis.example.com', 'Daniel Davis', 'ddavis', '5 September', 'Shift Engineer', 'DD, Technical Translation Bureau', 'ddavis.jpg', '', '', 'ddavis@example.com'], ['#13', 'http://ntaylor.example.com', 'Nathan Taylor', 'ntaylor', '16 February', 'Shift Engineer', 'BSD, Presale Solution Bureau', 'ntaylor.jpg', '', '', 'ntaylor@example.com'], ['#14', 'http://dtaylor.example.com', 'Daniel Taylor', 'dtaylor', '10 March', 'Application Developer', 'DD, Technical Translation Bureau', 'dtaylor.jpg', '', '', 'dtaylor@example.com'], ['#15', 'http://jbrown.example.com', 'Jayden Brown', 'jbrown', '1 January', 'Software Engineer', 'DD, Technical Translation Bureau', 'jbrown.jpg', '', '', 'jbrown@example.com'], ['#16', 'http://asmith.example.com', 'Andrew Smith', 'asmith', '9 August', 'Software Engineer', 'CD, Product Support Service', 'asmith.jpg', '', '', 'asmith@example.com'], ['#17', 'http://jsmith.example.com', 'Jayden Smith', 'jsmith', '8 April', 'Testing Engineer', 'BSD, Presale Solution Bureau', 'jsmith.jpg', '', '', 'jsmith@example.com'], ['#18', 'http://asmith.example.com', 'Alexander Smith', 'asmith', '7 November', 'Shift Engineer', 'Processing Services Division', 'asmith.jpg', '', '', 'asmith@example.com'], ['#19', 'http://dbrown.example.com', 'David Brown', 'dbrown', '20 October', 'Shift Engineer', 'Processing Services Division', 'dbrown.jpg', '', '', 'dbrown@example.com'], ['#20', 'http://nwilliams.example.com', 'Nathan Williams', 'nwilliams', '9 March', 'Shift Engineer', 'CD, Product Support Service', 'nwilliams.jpg', '', '', 'nwilliams@example.com'], ['#21', 'http://dtaylor.example.com', 'Daniel Taylor', 'dtaylor', '5 April', 'Shift Engineer', 'CD, Product Support Service', 'dtaylor.jpg', '', '', 'dtaylor@example.com'], ['#22', 'http://nmoore.example.com', 'Nathan Moore', 'nmoore', '14 July', 'Testing Engineer', 'DD, Technical Translation Bureau', 'nmoore.jpg', '', '', 'nmoore@example.com'], ['#23', 'http://ewilson.example.com', 'Ethan Wilson', 'ewilson', '7 September', 'Software Engineer', 'CD, Product Support Service', 'ewilson.jpg', '', '', 'ewilson@example.com'], ['#24', 'http://awilson.example.com', 'Angel Wilson', 'awilson', '5 December', 'Application Developer', 'BSD, Presale Solution Bureau', 'awilson.jpg', '', '', 'awilson@example.com'], ['#25', 'http://abrown.example.com', 'Angel Brown', 'abrown', '21 October', 'Shift Engineer', 'BSD, Presale Solution Bureau', 'abrown.jpg', '', '', 'abrown@example.com']] +if __name__ == "__main__": + columns = [ + "id", + "url", + "name", + "short_name", + "birthday", + "job", + "department", + "photo", + "work_phone", + "mobile_phone", + "email", + ] + rows = [ + [ + "#1", + "http://amiller.example.com", + "Andrew Miller", + "amiller", + "11 December", + "Testing Engineer", + "CD, Product Support Service", + "amiller.jpg", + "888888888888", + "", + "amiller@example.com", + ], + [ + "#2", + "http://ataylor.example.com", + "Anthony Taylor", + "ataylor", + "17 July", + "Software Engineer", + "CD, Product Support Service", + "ataylor.jpg", + "", + "", + "ataylor@example.com", + ], + [ + "#3", + "http://dmoore.example.com", + "Daniel Moore", + "dmoore", + "2 March", + "Testing Engineer", + "CD, Product Support Service", + "dmoore.jpg", + "", + "", + "dmoore@example.com", + ], + [ + "#4", + "http://dsmith.example.com", + "David Smith", + "dsmith", + "5 January", + "Testing Engineer", + "Processing Services Division", + "dsmith.jpg", + "", + "", + "dsmith@example.com", + ], + [ + "#5", + "http://awilson.example.com", + "Alexander Wilson", + "awilson", + "11 April", + "Software Engineer", + "CD, Product Support Service", + "awilson.jpg", + "", + "", + "awilson@example.com", + ], + [ + "#6", + "http://asmith.example.com", + "Alexander Smith", + "asmith", + "3 November", + "Testing Engineer", + "CD, Product Support Service", + "asmith.jpg", + "", + "", + "asmith@example.com", + ], + [ + "#7", + "http://jdavis.example.com", + "Jayden Davis", + "jdavis", + "10 April", + "Shift Engineer", + "BSD, Presale Solution Bureau", + "jdavis.jpg", + "", + "", + "jdavis@example.com", + ], + [ + "#8", + "http://jdavis.example.com", + "Jacob Davis", + "jdavis", + "5 July", + "Shift Engineer", + "DD, Technical Translation Bureau", + "jdavis.jpg", + "", + "", + "jdavis@example.com", + ], + [ + "#9", + "http://ejones.example.com", + "Ethan Jones", + "ejones", + "14 September", + "Software Engineer", + "BSD, Presale Solution Bureau", + "ejones.jpg", + "", + "", + "ejones@example.com", + ], + [ + "#10", + "http://abrown.example.com", + "Angel Brown", + "abrown", + "15 April", + "Testing Engineer", + "Processing Services Division", + "abrown.jpg", + "", + "", + "abrown@example.com", + ], + [ + "#11", + "http://dwilliams.example.com", + "David Williams", + "dwilliams", + "23 November", + "Application Developer", + "DD, Technical Translation Bureau", + "dwilliams.jpg", + "", + "", + "dwilliams@example.com", + ], + [ + "#12", + "http://ddavis.example.com", + "Daniel Davis", + "ddavis", + "5 September", + "Shift Engineer", + "DD, Technical Translation Bureau", + "ddavis.jpg", + "", + "", + "ddavis@example.com", + ], + [ + "#13", + "http://ntaylor.example.com", + "Nathan Taylor", + "ntaylor", + "16 February", + "Shift Engineer", + "BSD, Presale Solution Bureau", + "ntaylor.jpg", + "", + "", + "ntaylor@example.com", + ], + [ + "#14", + "http://dtaylor.example.com", + "Daniel Taylor", + "dtaylor", + "10 March", + "Application Developer", + "DD, Technical Translation Bureau", + "dtaylor.jpg", + "", + "", + "dtaylor@example.com", + ], + [ + "#15", + "http://jbrown.example.com", + "Jayden Brown", + "jbrown", + "1 January", + "Software Engineer", + "DD, Technical Translation Bureau", + "jbrown.jpg", + "", + "", + "jbrown@example.com", + ], + [ + "#16", + "http://asmith.example.com", + "Andrew Smith", + "asmith", + "9 August", + "Software Engineer", + "CD, Product Support Service", + "asmith.jpg", + "", + "", + "asmith@example.com", + ], + [ + "#17", + "http://jsmith.example.com", + "Jayden Smith", + "jsmith", + "8 April", + "Testing Engineer", + "BSD, Presale Solution Bureau", + "jsmith.jpg", + "", + "", + "jsmith@example.com", + ], + [ + "#18", + "http://asmith.example.com", + "Alexander Smith", + "asmith", + "7 November", + "Shift Engineer", + "Processing Services Division", + "asmith.jpg", + "", + "", + "asmith@example.com", + ], + [ + "#19", + "http://dbrown.example.com", + "David Brown", + "dbrown", + "20 October", + "Shift Engineer", + "Processing Services Division", + "dbrown.jpg", + "", + "", + "dbrown@example.com", + ], + [ + "#20", + "http://nwilliams.example.com", + "Nathan Williams", + "nwilliams", + "9 March", + "Shift Engineer", + "CD, Product Support Service", + "nwilliams.jpg", + "", + "", + "nwilliams@example.com", + ], + [ + "#21", + "http://dtaylor.example.com", + "Daniel Taylor", + "dtaylor", + "5 April", + "Shift Engineer", + "CD, Product Support Service", + "dtaylor.jpg", + "", + "", + "dtaylor@example.com", + ], + [ + "#22", + "http://nmoore.example.com", + "Nathan Moore", + "nmoore", + "14 July", + "Testing Engineer", + "DD, Technical Translation Bureau", + "nmoore.jpg", + "", + "", + "nmoore@example.com", + ], + [ + "#23", + "http://ewilson.example.com", + "Ethan Wilson", + "ewilson", + "7 September", + "Software Engineer", + "CD, Product Support Service", + "ewilson.jpg", + "", + "", + "ewilson@example.com", + ], + [ + "#24", + "http://awilson.example.com", + "Angel Wilson", + "awilson", + "5 December", + "Application Developer", + "BSD, Presale Solution Bureau", + "awilson.jpg", + "", + "", + "awilson@example.com", + ], + [ + "#25", + "http://abrown.example.com", + "Angel Brown", + "abrown", + "21 October", + "Shift Engineer", + "BSD, Presale Solution Bureau", + "abrown.jpg", + "", + "", + "abrown@example.com", + ], + ] rows.insert(0, columns) print(ascii_table(rows)) diff --git a/ascii_table__simple_pretty__format.py b/ascii_table__simple_pretty__format.py index 38e9caf79..afe0340f7 100644 --- a/ascii_table__simple_pretty__format.py +++ b/ascii_table__simple_pretty__format.py @@ -1,10 +1,10 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -def pretty_table(data, cell_sep=' | ', header_separator=True, align='>') -> str: +def pretty_table(data, cell_sep=" | ", header_separator=True, align=">") -> str: rows = len(data) cols = len(data[0]) @@ -13,8 +13,8 @@ def pretty_table(data, cell_sep=' | ', header_separator=True, align='>') -> str: columns = [str(data[row][col]) for row in range(rows)] col_width.append(len(max(columns, key=len))) - templates = ['{:' + align + '%d}' % width for width in col_width] - separator = "-+-".join('-' * n for n in col_width) + templates = ["{:" + align + "%d}" % width for width in col_width] + separator = "-+-".join("-" * n for n in col_width) lines = [] for i, row in enumerate(range(rows)): @@ -29,20 +29,20 @@ def pretty_table(data, cell_sep=' | ', header_separator=True, align='>') -> str: if i == 0 and header_separator: lines.append(separator) - return '\n'.join(lines) + return "\n".join(lines) -def print_pretty_table(data, cell_sep=' | ', header_separator=True, align='>'): +def print_pretty_table(data, cell_sep=" | ", header_separator=True, align=">") -> None: print(pretty_table(data, cell_sep, header_separator, align)) -if __name__ == '__main__': +if __name__ == "__main__": table_data = [ - ['FRUIT', 'PERSON', 'ANIMAL'], - ['apples', 'Alice', 'dogs'], - ['oranges', 'Bob', 'cats'], - ['cherries', 'Carol', 'moose'], - ['banana', 'David', 'goose'], + ["FRUIT", "PERSON", "ANIMAL"], + ["apples", "Alice", "dogs"], + ["oranges", "Bob", "cats"], + ["cherries", "Carol", "moose"], + ["banana", "David", "goose"], ] print_pretty_table(table_data, header_separator=False) @@ -64,4 +64,4 @@ def print_pretty_table(data, cell_sep=' | ', header_separator=True, align='>'): print() - print_pretty_table([['FRUIT', 'PERSON', 'ANIMAL']]) + print_pretty_table([["FRUIT", "PERSON", "ANIMAL"]]) diff --git a/ascii_table__simple_pretty__ljust.py b/ascii_table__simple_pretty__ljust.py index 25fe9a90e..b762934b4 100644 --- a/ascii_table__simple_pretty__ljust.py +++ b/ascii_table__simple_pretty__ljust.py @@ -1,10 +1,10 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -def pretty_table(data, cell_sep=' | ', header_separator=True) -> str: +def pretty_table(data, cell_sep=" | ", header_separator=True) -> str: rows = len(data) cols = len(data[0]) @@ -13,7 +13,7 @@ def pretty_table(data, cell_sep=' | ', header_separator=True) -> str: columns = [str(data[row][col]) for row in range(rows)] col_width.append(len(max(columns, key=len))) - separator = "-+-".join('-' * n for n in col_width) + separator = "-+-".join("-" * n for n in col_width) lines = [] @@ -28,20 +28,20 @@ def pretty_table(data, cell_sep=' | ', header_separator=True) -> str: if i == 0 and header_separator: lines.append(separator) - return '\n'.join(lines) + return "\n".join(lines) -def print_pretty_table(data, cell_sep=' | ', header_separator=True): +def print_pretty_table(data, cell_sep=" | ", header_separator=True) -> None: print(pretty_table(data, cell_sep, header_separator)) -if __name__ == '__main__': +if __name__ == "__main__": table_data = [ - ['FRUIT', 'PERSON', 'ANIMAL'], - ['apples', 'Alice', 'dogs'], - ['oranges', 'Bob', 'cats'], - ['cherries', 'Carol', 'moose'], - ['banana', 'David', 'goose'], + ["FRUIT", "PERSON", "ANIMAL"], + ["apples", "Alice", "dogs"], + ["oranges", "Bob", "cats"], + ["cherries", "Carol", "moose"], + ["banana", "David", "goose"], ] print_pretty_table(table_data, header_separator=False) @@ -63,4 +63,4 @@ def print_pretty_table(data, cell_sep=' | ', header_separator=True): print() - print_pretty_table([['FRUIT', 'PERSON', 'ANIMAL']]) + print_pretty_table([["FRUIT", "PERSON", "ANIMAL"]]) diff --git a/ascii_table__simple_pretty__rjust.py b/ascii_table__simple_pretty__rjust.py index f12ab1fc3..8abdf420d 100644 --- a/ascii_table__simple_pretty__rjust.py +++ b/ascii_table__simple_pretty__rjust.py @@ -1,10 +1,10 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -def pretty_table(data, cell_sep=' | ', header_separator=True) -> str: +def pretty_table(data, cell_sep=" | ", header_separator=True) -> str: rows = len(data) cols = len(data[0]) @@ -13,7 +13,7 @@ def pretty_table(data, cell_sep=' | ', header_separator=True) -> str: columns = [str(data[row][col]) for row in range(rows)] col_width.append(len(max(columns, key=len))) - separator = "-+-".join('-' * n for n in col_width) + separator = "-+-".join("-" * n for n in col_width) lines = [] @@ -28,20 +28,20 @@ def pretty_table(data, cell_sep=' | ', header_separator=True) -> str: if i == 0 and header_separator: lines.append(separator) - return '\n'.join(lines) + return "\n".join(lines) -def print_pretty_table(data, cell_sep=' | ', header_separator=True): +def print_pretty_table(data, cell_sep=" | ", header_separator=True) -> None: print(pretty_table(data, cell_sep, header_separator)) -if __name__ == '__main__': +if __name__ == "__main__": table_data = [ - ['FRUIT', 'PERSON', 'ANIMAL'], - ['apples', 'Alice', 'dogs'], - ['oranges', 'Bob', 'cats'], - ['cherries', 'Carol', 'moose'], - ['banana', 'David', 'goose'], + ["FRUIT", "PERSON", "ANIMAL"], + ["apples", "Alice", "dogs"], + ["oranges", "Bob", "cats"], + ["cherries", "Carol", "moose"], + ["banana", "David", "goose"], ] print_pretty_table(table_data, header_separator=False) @@ -63,4 +63,4 @@ def print_pretty_table(data, cell_sep=' | ', header_separator=True): print() - print_pretty_table([['FRUIT', 'PERSON', 'ANIMAL']]) + print_pretty_table([["FRUIT", "PERSON", "ANIMAL"]]) diff --git a/ascii_table__using_tabulate.py b/ascii_table__using_tabulate.py index d635231b9..c76faa7cc 100644 --- a/ascii_table__using_tabulate.py +++ b/ascii_table__using_tabulate.py @@ -1,24 +1,364 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -headers = ['id', 'url', 'name', 'short_name', 'birthday', 'job', 'department', 'photo', 'work_phone', 'mobile_phone', 'email'] -rows = [['#1', 'http://amiller.example.com', 'Andrew Miller', 'amiller', '11 December', 'Testing Engineer', 'CD, Product Support Service', 'amiller.jpg', '888888888888', '', 'amiller@example.com'], ['#2', 'http://ataylor.example.com', 'Anthony Taylor', 'ataylor', '17 July', 'Software Engineer', 'CD, Product Support Service', 'ataylor.jpg', '', '', 'ataylor@example.com'], ['#3', 'http://dmoore.example.com', 'Daniel Moore', 'dmoore', '2 March', 'Testing Engineer', 'CD, Product Support Service', 'dmoore.jpg', '', '', 'dmoore@example.com'], ['#4', 'http://dsmith.example.com', 'David Smith', 'dsmith', '5 January', 'Testing Engineer', 'Processing Services Division', 'dsmith.jpg', '', '', 'dsmith@example.com'], ['#5', 'http://awilson.example.com', 'Alexander Wilson', 'awilson', '11 April', 'Software Engineer', 'CD, Product Support Service', 'awilson.jpg', '', '', 'awilson@example.com'], ['#6', 'http://asmith.example.com', 'Alexander Smith', 'asmith', '3 November', 'Testing Engineer', 'CD, Product Support Service', 'asmith.jpg', '', '', 'asmith@example.com'], ['#7', 'http://jdavis.example.com', 'Jayden Davis', 'jdavis', '10 April', 'Shift Engineer', 'BSD, Presale Solution Bureau', 'jdavis.jpg', '', '', 'jdavis@example.com'], ['#8', 'http://jdavis.example.com', 'Jacob Davis', 'jdavis', '5 July', 'Shift Engineer', 'DD, Technical Translation Bureau', 'jdavis.jpg', '', '', 'jdavis@example.com'], ['#9', 'http://ejones.example.com', 'Ethan Jones', 'ejones', '14 September', 'Software Engineer', 'BSD, Presale Solution Bureau', 'ejones.jpg', '', '', 'ejones@example.com'], ['#10', 'http://abrown.example.com', 'Angel Brown', 'abrown', '15 April', 'Testing Engineer', 'Processing Services Division', 'abrown.jpg', '', '', 'abrown@example.com'], ['#11', 'http://dwilliams.example.com', 'David Williams', 'dwilliams', '23 November', 'Application Developer', 'DD, Technical Translation Bureau', 'dwilliams.jpg', '', '', 'dwilliams@example.com'], ['#12', 'http://ddavis.example.com', 'Daniel Davis', 'ddavis', '5 September', 'Shift Engineer', 'DD, Technical Translation Bureau', 'ddavis.jpg', '', '', 'ddavis@example.com'], ['#13', 'http://ntaylor.example.com', 'Nathan Taylor', 'ntaylor', '16 February', 'Shift Engineer', 'BSD, Presale Solution Bureau', 'ntaylor.jpg', '', '', 'ntaylor@example.com'], ['#14', 'http://dtaylor.example.com', 'Daniel Taylor', 'dtaylor', '10 March', 'Application Developer', 'DD, Technical Translation Bureau', 'dtaylor.jpg', '', '', 'dtaylor@example.com'], ['#15', 'http://jbrown.example.com', 'Jayden Brown', 'jbrown', '1 January', 'Software Engineer', 'DD, Technical Translation Bureau', 'jbrown.jpg', '', '', 'jbrown@example.com'], ['#16', 'http://asmith.example.com', 'Andrew Smith', 'asmith', '9 August', 'Software Engineer', 'CD, Product Support Service', 'asmith.jpg', '', '', 'asmith@example.com'], ['#17', 'http://jsmith.example.com', 'Jayden Smith', 'jsmith', '8 April', 'Testing Engineer', 'BSD, Presale Solution Bureau', 'jsmith.jpg', '', '', 'jsmith@example.com'], ['#18', 'http://asmith.example.com', 'Alexander Smith', 'asmith', '7 November', 'Shift Engineer', 'Processing Services Division', 'asmith.jpg', '', '', 'asmith@example.com'], ['#19', 'http://dbrown.example.com', 'David Brown', 'dbrown', '20 October', 'Shift Engineer', 'Processing Services Division', 'dbrown.jpg', '', '', 'dbrown@example.com'], ['#20', 'http://nwilliams.example.com', 'Nathan Williams', 'nwilliams', '9 March', 'Shift Engineer', 'CD, Product Support Service', 'nwilliams.jpg', '', '', 'nwilliams@example.com'], ['#21', 'http://dtaylor.example.com', 'Daniel Taylor', 'dtaylor', '5 April', 'Shift Engineer', 'CD, Product Support Service', 'dtaylor.jpg', '', '', 'dtaylor@example.com'], ['#22', 'http://nmoore.example.com', 'Nathan Moore', 'nmoore', '14 July', 'Testing Engineer', 'DD, Technical Translation Bureau', 'nmoore.jpg', '', '', 'nmoore@example.com'], ['#23', 'http://ewilson.example.com', 'Ethan Wilson', 'ewilson', '7 September', 'Software Engineer', 'CD, Product Support Service', 'ewilson.jpg', '', '', 'ewilson@example.com'], ['#24', 'http://awilson.example.com', 'Angel Wilson', 'awilson', '5 December', 'Application Developer', 'BSD, Presale Solution Bureau', 'awilson.jpg', '', '', 'awilson@example.com'], ['#25', 'http://abrown.example.com', 'Angel Brown', 'abrown', '21 October', 'Shift Engineer', 'BSD, Presale Solution Bureau', 'abrown.jpg', '', '', 'abrown@example.com']] - # pip install tabulate from tabulate import tabulate + + +headers = [ + "id", + "url", + "name", + "short_name", + "birthday", + "job", + "department", + "photo", + "work_phone", + "mobile_phone", + "email", +] +rows = [ + [ + "#1", + "http://amiller.example.com", + "Andrew Miller", + "amiller", + "11 December", + "Testing Engineer", + "CD, Product Support Service", + "amiller.jpg", + "888888888888", + "", + "amiller@example.com", + ], + [ + "#2", + "http://ataylor.example.com", + "Anthony Taylor", + "ataylor", + "17 July", + "Software Engineer", + "CD, Product Support Service", + "ataylor.jpg", + "", + "", + "ataylor@example.com", + ], + [ + "#3", + "http://dmoore.example.com", + "Daniel Moore", + "dmoore", + "2 March", + "Testing Engineer", + "CD, Product Support Service", + "dmoore.jpg", + "", + "", + "dmoore@example.com", + ], + [ + "#4", + "http://dsmith.example.com", + "David Smith", + "dsmith", + "5 January", + "Testing Engineer", + "Processing Services Division", + "dsmith.jpg", + "", + "", + "dsmith@example.com", + ], + [ + "#5", + "http://awilson.example.com", + "Alexander Wilson", + "awilson", + "11 April", + "Software Engineer", + "CD, Product Support Service", + "awilson.jpg", + "", + "", + "awilson@example.com", + ], + [ + "#6", + "http://asmith.example.com", + "Alexander Smith", + "asmith", + "3 November", + "Testing Engineer", + "CD, Product Support Service", + "asmith.jpg", + "", + "", + "asmith@example.com", + ], + [ + "#7", + "http://jdavis.example.com", + "Jayden Davis", + "jdavis", + "10 April", + "Shift Engineer", + "BSD, Presale Solution Bureau", + "jdavis.jpg", + "", + "", + "jdavis@example.com", + ], + [ + "#8", + "http://jdavis.example.com", + "Jacob Davis", + "jdavis", + "5 July", + "Shift Engineer", + "DD, Technical Translation Bureau", + "jdavis.jpg", + "", + "", + "jdavis@example.com", + ], + [ + "#9", + "http://ejones.example.com", + "Ethan Jones", + "ejones", + "14 September", + "Software Engineer", + "BSD, Presale Solution Bureau", + "ejones.jpg", + "", + "", + "ejones@example.com", + ], + [ + "#10", + "http://abrown.example.com", + "Angel Brown", + "abrown", + "15 April", + "Testing Engineer", + "Processing Services Division", + "abrown.jpg", + "", + "", + "abrown@example.com", + ], + [ + "#11", + "http://dwilliams.example.com", + "David Williams", + "dwilliams", + "23 November", + "Application Developer", + "DD, Technical Translation Bureau", + "dwilliams.jpg", + "", + "", + "dwilliams@example.com", + ], + [ + "#12", + "http://ddavis.example.com", + "Daniel Davis", + "ddavis", + "5 September", + "Shift Engineer", + "DD, Technical Translation Bureau", + "ddavis.jpg", + "", + "", + "ddavis@example.com", + ], + [ + "#13", + "http://ntaylor.example.com", + "Nathan Taylor", + "ntaylor", + "16 February", + "Shift Engineer", + "BSD, Presale Solution Bureau", + "ntaylor.jpg", + "", + "", + "ntaylor@example.com", + ], + [ + "#14", + "http://dtaylor.example.com", + "Daniel Taylor", + "dtaylor", + "10 March", + "Application Developer", + "DD, Technical Translation Bureau", + "dtaylor.jpg", + "", + "", + "dtaylor@example.com", + ], + [ + "#15", + "http://jbrown.example.com", + "Jayden Brown", + "jbrown", + "1 January", + "Software Engineer", + "DD, Technical Translation Bureau", + "jbrown.jpg", + "", + "", + "jbrown@example.com", + ], + [ + "#16", + "http://asmith.example.com", + "Andrew Smith", + "asmith", + "9 August", + "Software Engineer", + "CD, Product Support Service", + "asmith.jpg", + "", + "", + "asmith@example.com", + ], + [ + "#17", + "http://jsmith.example.com", + "Jayden Smith", + "jsmith", + "8 April", + "Testing Engineer", + "BSD, Presale Solution Bureau", + "jsmith.jpg", + "", + "", + "jsmith@example.com", + ], + [ + "#18", + "http://asmith.example.com", + "Alexander Smith", + "asmith", + "7 November", + "Shift Engineer", + "Processing Services Division", + "asmith.jpg", + "", + "", + "asmith@example.com", + ], + [ + "#19", + "http://dbrown.example.com", + "David Brown", + "dbrown", + "20 October", + "Shift Engineer", + "Processing Services Division", + "dbrown.jpg", + "", + "", + "dbrown@example.com", + ], + [ + "#20", + "http://nwilliams.example.com", + "Nathan Williams", + "nwilliams", + "9 March", + "Shift Engineer", + "CD, Product Support Service", + "nwilliams.jpg", + "", + "", + "nwilliams@example.com", + ], + [ + "#21", + "http://dtaylor.example.com", + "Daniel Taylor", + "dtaylor", + "5 April", + "Shift Engineer", + "CD, Product Support Service", + "dtaylor.jpg", + "", + "", + "dtaylor@example.com", + ], + [ + "#22", + "http://nmoore.example.com", + "Nathan Moore", + "nmoore", + "14 July", + "Testing Engineer", + "DD, Technical Translation Bureau", + "nmoore.jpg", + "", + "", + "nmoore@example.com", + ], + [ + "#23", + "http://ewilson.example.com", + "Ethan Wilson", + "ewilson", + "7 September", + "Software Engineer", + "CD, Product Support Service", + "ewilson.jpg", + "", + "", + "ewilson@example.com", + ], + [ + "#24", + "http://awilson.example.com", + "Angel Wilson", + "awilson", + "5 December", + "Application Developer", + "BSD, Presale Solution Bureau", + "awilson.jpg", + "", + "", + "awilson@example.com", + ], + [ + "#25", + "http://abrown.example.com", + "Angel Brown", + "abrown", + "21 October", + "Shift Engineer", + "BSD, Presale Solution Bureau", + "abrown.jpg", + "", + "", + "abrown@example.com", + ], +] + print(tabulate(rows, headers=headers, tablefmt="grid")) -print('\n') +print("\n") # Without header rows = [ - ['randint(x)', 'Return a random int below x'], - ['rand()', 'Return a random float between 0 and 1'], - ['int(x)', 'Convert x to an int.'], - ['float(x)', 'Convert x to a float.'], - ['str(x)', 'Convert x to a str (unicode in py2)'], + ["randint(x)", "Return a random int below x"], + ["rand()", "Return a random float between 0 and 1"], + ["int(x)", "Convert x to an int."], + ["float(x)", "Convert x to a float."], + ["str(x)", "Convert x to a str (unicode in py2)"], ] print(tabulate(rows, headers=[], tablefmt="grid")) diff --git a/autoclick_RDP_task_icon/main.py b/autoclick_RDP_task_icon/main.py index f05f6262b..d8caa22fe 100644 --- a/autoclick_RDP_task_icon/main.py +++ b/autoclick_RDP_task_icon/main.py @@ -1,15 +1,15 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -import datetime as DT import logging import time import traceback import sys -from typing import Optional, Tuple + +from datetime import date # pip install pyautogui # pip install pillow @@ -26,27 +26,27 @@ logging.basicConfig( level=logging.INFO, - format='[%(asctime)s] %(filename)s:%(lineno)d %(levelname)-8s %(message)s', + format="[%(asctime)s] %(filename)s:%(lineno)d %(levelname)-8s %(message)s", stream=sys.stdout, ) def is_exists_rdp_process() -> bool: - return any(process.name() == 'mstsc.exe' for process in psutil.process_iter()) + return any(process.name() == "mstsc.exe" for process in psutil.process_iter()) -def get_pos_rdp_task_icon() -> Optional[Tuple[int, int]]: +def get_pos_rdp_task_icon() -> tuple[int, int] | None: try: return pyautogui.locateCenterOnScreen("RDP_task_icon.png", minSearchTime=5) except: pass -def run(): +def run() -> None: logging.info("") logging.info("Run") - if DT.date.today().weekday() in [5, 6]: + if date.today().weekday() in [5, 6]: logging.info("Today is a weekend, skip") return @@ -60,14 +60,14 @@ def run(): img = pyautogui.screenshot() for _ in range(5): # Свернуть все окна - pyautogui.hotkey('win', 'd') + pyautogui.hotkey("win", "d") time.sleep(2) if img != pyautogui.screenshot(): break pos = get_pos_rdp_task_icon() - logging.info(f'1. get_pos_rdp_task_icon: {pos}') + logging.info(f"1. get_pos_rdp_task_icon: {pos}") if not pos: # Нужно сдвинуть курсор туда, где точно не может находиться кнопка задачи RPD, # это поможет если курсор уже находится на кнопке задачи RDP @@ -76,7 +76,7 @@ def run(): time.sleep(2) pos = get_pos_rdp_task_icon() - logging.info(f'2. get_pos_rdp_task_icon: {pos}') + logging.info(f"2. get_pos_rdp_task_icon: {pos}") if pos: pyautogui.click(*pos) logging.info("Click") @@ -85,21 +85,18 @@ def run(): print(traceback.format_exc()) -if __name__ == '__main__': +if __name__ == "__main__": logging.info("Started...") # Каждый день в 08:30 - schedule \ - .every().day.at("08:30") \ - .do(run) + schedule.every().day.at("08:30").do(run) - logging.info('Jobs:') + logging.info("Jobs:") for job in schedule.jobs: - logging.info(f' {job!r}') + logging.info(f" {job!r}") - logging.info('') + logging.info("") while True: schedule.run_pending() time.sleep(1) - diff --git a/autoclick_in_game__pyautoit.py b/autoclick_in_game__pyautoit.py deleted file mode 100644 index d56d75b25..000000000 --- a/autoclick_in_game__pyautoit.py +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -__author__ = 'ipetrash' - - -import threading -import time - -import keyboard - -# pip install PyAutoIt -import autoit - - -DATA = { - 'START': False, -} - - -def change_start(): - DATA['START'] = not DATA['START'] - print(DATA['START']) - - -def process_auto_click(): - while True: - if not DATA['START']: - time.sleep(0.100) - continue - - # Симуляция атаки - if DATA['START']: - autoit.mouse_click() - - time.sleep(0.070) - - -keyboard.add_hotkey('Ctrl+Alt+Space', change_start) - -# Запуск потока для автоатаки -thread_auto_click = threading.Thread(target=process_auto_click) -thread_auto_click.start() - - -while True: - if not DATA['START']: - time.sleep(0.100) - continue diff --git a/bcrypt__hash_password__kdf_key_derivation_function/hash_password.py b/bcrypt__hash_password__kdf_key_derivation_function/hash_password.py index df15cc720..38aabead8 100644 --- a/bcrypt__hash_password__kdf_key_derivation_function/hash_password.py +++ b/bcrypt__hash_password__kdf_key_derivation_function/hash_password.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/pyca/bcrypt diff --git a/bcrypt__hash_password__kdf_key_derivation_function/kdf__key_derivation_function.py b/bcrypt__hash_password__kdf_key_derivation_function/kdf__key_derivation_function.py index 02122420b..053723207 100644 --- a/bcrypt__hash_password__kdf_key_derivation_function/kdf__key_derivation_function.py +++ b/bcrypt__hash_password__kdf_key_derivation_function/kdf__key_derivation_function.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/pyca/bcrypt @@ -18,9 +18,10 @@ key = bcrypt.kdf( - password=b'password', - salt=b'salt', + password=b"password", + salt=b"salt", desired_key_bytes=32, - rounds=100 + rounds=100, ) -print(key) # b'W\x1cq\xbd\xf25W\xf9\xe7\x99\x0fH\xfb\x1a-:n\xbd\x03\xd0\x1a>\x12\xa7\xf7\x0b\x85\x03\xc9\xf9\xbe8' +print(key) +# b'W\x1cq\xbd\xf25W\xf9\xe7\x99\x0fH\xfb\x1a-:n\xbd\x03\xd0\x1a>\x12\xa7\xf7\x0b\x85\x03\xc9\xf9\xbe8' diff --git a/bigfishgames_com__hidden_object/get_all_games.py b/bigfishgames_com__hidden_object/get_all_games.py deleted file mode 100644 index d754ca9cb..000000000 --- a/bigfishgames_com__hidden_object/get_all_games.py +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -__author__ = 'ipetrash' - - -from typing import List - -import requests -from bs4 import BeautifulSoup - - -DEFAULT_URL = 'https://www.bigfishgames.com/download-games/genres/15/hidden-object.html' - - -def get_all_games(url=DEFAULT_URL, prefix='', postfix='') -> List[str]: - def is_valid(game: str) -> bool: - game = game.upper() - return game.startswith(prefix.upper()) and game.endswith(postfix.upper()) - - rs = requests.get(url) - root = BeautifulSoup(rs.content, 'html.parser') - - return [a.text.strip() for a in root.select('#genre_bottom a') if is_valid(a.text.strip())] - - -if __name__ == '__main__': - games = get_all_games() - print(f'Games ({len(games)}): {games}') - - import json - json.dump(games, open('games.json', 'w', encoding='utf-8'), ensure_ascii=False) diff --git a/bin2str/bin2str.py b/bin2str/bin2str.py index 280e870f9..93e85d504 100644 --- a/bin2str/bin2str.py +++ b/bin2str/bin2str.py @@ -1,16 +1,22 @@ -def bin2str(bin_str: str, encoding: str = 'utf-8') -> str: +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +def bin2str(bin_str: str, encoding: str = "utf-8") -> str: # Удаление всех символов кроме 0 и 1 - bin_str = ''.join(c for c in bin_str if c in '01') + bin_str = "".join(c for c in bin_str if c in "01") h = hex(int(bin_str, 2))[2:] return bytes.fromhex(h).decode(encoding) -def str2bin(text: str, sep=' ', encoding: str = 'utf-8') -> str: +def str2bin(text: str, sep=" ", encoding: str = "utf-8") -> str: data: bytes = text.encode(encoding) # Example: 'You' -> '10110010110111101110101' - bin_str = f'{int(data.hex(), 16):08b}' + bin_str = f"{int(data.hex(), 16):08b}" # Example: '10110010110111101110101 -> '01011001 01101111 01110101' bin_str = bin_str[::-1] @@ -23,21 +29,23 @@ def str2bin(text: str, sep=' ', encoding: str = 'utf-8') -> str: return sep.join(items) -if __name__ == '__main__': - assert str2bin('You') == '01011001 01101111 01110101' - assert str2bin(bin2str('01011001 01101111 01110101')) == '01011001 01101111 01110101' - assert str2bin(bin2str('010110010110111101110101')) == '01011001 01101111 01110101' +if __name__ == "__main__": + assert str2bin("You") == "01011001 01101111 01110101" + assert ( + str2bin(bin2str("01011001 01101111 01110101")) == "01011001 01101111 01110101" + ) + assert str2bin(bin2str("010110010110111101110101")) == "01011001 01101111 01110101" - assert bin2str('01011001 01101111 01110101') == 'You' - assert bin2str('010110010110111101110101') == 'You' - assert bin2str('01011001-01101111-01110101') == 'You' - assert bin2str('BIN: 01011001-01101111-01110101') == 'You' + assert bin2str("01011001 01101111 01110101") == "You" + assert bin2str("010110010110111101110101") == "You" + assert bin2str("01011001-01101111-01110101") == "You" + assert bin2str("BIN: 01011001-01101111-01110101") == "You" - assert bin2str(str2bin('You')) == 'You' + assert bin2str(str2bin("You")) == "You" - assert bin2str(str2bin('Привет')) == 'Привет' + assert bin2str(str2bin("Привет")) == "Привет" - print('Bin to text:') + print("Bin to text:") text = ( "01011001 01101111 01110101 00100000 01100001 01110010 01100101 00100000 01101101 01101111 " "01110010 01100101 00100000 01110100 01101000 01100001 01101110 00100000 01101010 01110101 " @@ -47,6 +55,6 @@ def str2bin(text: str, sep=' ', encoding: str = 'utf-8') -> str: print(bin2str(text)) print() - print('Text to bin:') + print("Text to bin:") text = "You are more than just an animal." print(str2bin(text)) diff --git a/bin2str/gui.py b/bin2str/gui.py index f5fe7da6a..808c22891 100644 --- a/bin2str/gui.py +++ b/bin2str/gui.py @@ -1,9 +1,12 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" +import traceback +import sys + try: from PyQt5.QtWidgets import * from PyQt5.QtCore import * @@ -18,30 +21,31 @@ from PySide.QtCore import * -def log_uncaught_exceptions(ex_cls, ex, tb): - text = '{}: {}:\n'.format(ex_cls.__name__, ex) - import traceback - text += ''.join(traceback.format_tb(tb)) +from bin2str import bin2str, str2bin + + +def log_uncaught_exceptions(ex_cls, ex, tb) -> None: + text = f"{ex_cls.__name__}: {ex}:\n" + text += "".join(traceback.format_tb(tb)) print(text) - QMessageBox.critical(None, 'Error', text) + QMessageBox.critical(None, "Error", text) sys.exit(1) -import sys sys.excepthook = log_uncaught_exceptions class Widget(QWidget): - def __init__(self): + def __init__(self) -> None: super().__init__() - self.setWindowTitle('bin2str') + self.setWindowTitle("bin2str") - button_bin2str = QPushButton('bin -> str') + button_bin2str = QPushButton("bin -> str") button_bin2str.clicked.connect(self._bin2str) - button_str2bin = QPushButton('str -> bin') + button_str2bin = QPushButton("str -> bin") button_str2bin.clicked.connect(self._str2bin) self.plain_text_bin = QPlainTextEdit() @@ -53,37 +57,35 @@ def __init__(self): layout = QVBoxLayout() layout.addLayout(h_layout) - layout.addWidget(QLabel('Bin:')) + layout.addWidget(QLabel("Bin:")) layout.addWidget(self.plain_text_bin) layout.addSpacing(10) - layout.addWidget(QLabel('Text:')) + layout.addWidget(QLabel("Text:")) layout.addWidget(self.plain_text_str) self.setLayout(layout) - def _bin2str(self): + def _bin2str(self) -> None: text = self.plain_text_bin.toPlainText() - - from bin2str import bin2str text = bin2str(text) self.plain_text_str.setPlainText(text) - def _str2bin(self): + def _str2bin(self) -> None: text = self.plain_text_str.toPlainText() - - from bin2str import str2bin text = str2bin(text) self.plain_text_bin.setPlainText(text) -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) w = Widget() - w.plain_text_bin.setPlainText('01001000 01100101 01101100 01101100 01101111 00100000 ' - '01010111 01101111 01110010 01101100 01100100 00100001') + w.plain_text_bin.setPlainText( + "01001000 01100101 01101100 01101100 01101111 00100000 " + "01010111 01101111 01110010 01101100 01100100 00100001" + ) w.show() app.exec() diff --git a/bitstruct__examples__parse_binary_files/parse_ts__print_packet_transport_stream_header/main.py b/bitstruct__examples__parse_binary_files/parse_ts__print_packet_transport_stream_header/main.py index b91d10b87..852e46af1 100644 --- a/bitstruct__examples__parse_binary_files/parse_ts__print_packet_transport_stream_header/main.py +++ b/bitstruct__examples__parse_binary_files/parse_ts__print_packet_transport_stream_header/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://en.wikipedia.org/wiki/MPEG_transport_stream#Packet @@ -17,7 +17,7 @@ # SOURCE: https://nypromo2019.hb.bizmrg.com/video/man/ny2019__000.ts # from m3u8 https://github.com/gil9red/SimplePyScripts/blob/ad33a95c3f0487555e9d8a0a0c5aef00613c9bbf/html_parsing/https_newyear_mail_ru/download__m3u8.py -with open('ny2019__000.ts', 'rb') as file: +with open("ny2019__000.ts", "rb") as file: while True: data = file.read(188) if not data: @@ -26,27 +26,31 @@ f = io.BytesIO(data) # 4-BYTE TRANSPORT STREAM HEADER - print('4-BYTE TRANSPORT STREAM HEADER') + print("4-BYTE TRANSPORT STREAM HEADER") sync_byte = f.read(1) - print('sync_byte:', sync_byte) + print("sync_byte:", sync_byte) data = f.read(2) - transport_error_indicator, \ - payload_unit_start_indicator, \ - transport_priority, \ - pid = bitstruct.unpack('>b1b1b1u13', data) - print('transport_error_indicator:', transport_error_indicator) - print('payload_unit_start_indicator:', payload_unit_start_indicator) - print('transport_priority:', transport_priority) - print('pid:', pid) + ( + transport_error_indicator, + payload_unit_start_indicator, + transport_priority, + pid, + ) = bitstruct.unpack(">b1b1b1u13", data) + print("transport_error_indicator:", transport_error_indicator) + print("payload_unit_start_indicator:", payload_unit_start_indicator) + print("transport_priority:", transport_priority) + print("pid:", pid) data = f.read(1) - transport_scrambling_control, \ - adaptation_field_control, \ - continuity_counter = bitstruct.unpack('>u2u2u4', data) - print('transport_scrambling_control:', transport_scrambling_control) - print('adaptation_field_control:', adaptation_field_control) - print('continuity_counter:', continuity_counter) + ( + transport_scrambling_control, + adaptation_field_control, + continuity_counter, + ) = bitstruct.unpack(">u2u2u4", data) + print("transport_scrambling_control:", transport_scrambling_control) + print("adaptation_field_control:", adaptation_field_control) + print("continuity_counter:", continuity_counter) print() # 184-BYTE + diff --git a/bleach__examples__remove_tags__clear_html/class_Cleaner.py b/bleach__examples__remove_tags__clear_html/class_Cleaner.py index d67d44b54..5291f1e2c 100644 --- a/bleach__examples__remove_tags__clear_html/class_Cleaner.py +++ b/bleach__examples__remove_tags__clear_html/class_Cleaner.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/mozilla/bleach @@ -12,10 +12,10 @@ from bleach.sanitizer import Cleaner -html = ''' +html = """

is not allowed

Hello World!

-'''.strip() +""".strip() cleaner = Cleaner() @@ -26,7 +26,7 @@ print() -cleaner = Cleaner(tags=['b', 'span'], strip=True) +cleaner = Cleaner(tags=["b", "span"], strip=True) for text in html.splitlines(): print(cleaner.clean(text)) # is not allowed diff --git a/bleach__examples__remove_tags__clear_html/class_LinkifyFilter.py b/bleach__examples__remove_tags__clear_html/class_LinkifyFilter.py index 2f7a09cad..c867ed9b3 100644 --- a/bleach__examples__remove_tags__clear_html/class_LinkifyFilter.py +++ b/bleach__examples__remove_tags__clear_html/class_LinkifyFilter.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/mozilla/bleach @@ -15,24 +15,26 @@ from bleach.linkifier import LinkifyFilter -html = '
http://example.com
' +html = "
http://example.com
" -cleaner = Cleaner(tags=['pre']) +cleaner = Cleaner(tags=["pre"]) print(cleaner.clean(html)) #
http://example.com
-cleaner = Cleaner(tags=['pre'], filters=[LinkifyFilter]) +cleaner = Cleaner(tags=["pre"], filters=[LinkifyFilter]) print(cleaner.clean(html)) #
http://example.com
-print('\n' + '-' * 100 + '\n') +print("\n" + "-" * 100 + "\n") # skip_tags (list) – list of tags that you don’t want to linkify # the contents of; for example, you could set this to ['pre'] # to skip linkifying contents of pre tags cleaner = Cleaner( - tags=['pre'], - filters=[partial(LinkifyFilter, skip_tags=['pre'])] + tags=["pre"], + filters=[ + partial(LinkifyFilter, skip_tags=["pre"]), + ] ) print(cleaner.clean(html)) #
http://example.com
diff --git a/bleach__examples__remove_tags__clear_html/custom_filter.py b/bleach__examples__remove_tags__clear_html/custom_filter.py index 7f8df2b91..072c5501f 100644 --- a/bleach__examples__remove_tags__clear_html/custom_filter.py +++ b/bleach__examples__remove_tags__clear_html/custom_filter.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/mozilla/bleach @@ -16,15 +16,15 @@ class MooFilter(Filter): def __iter__(self): for token in super().__iter__(): - if token['type'] in ['StartTag', 'EmptyTag'] and token['data']: - for attr, value in token['data'].items(): - token['data'][attr] = 'moo' + if token["type"] in ["StartTag", "EmptyTag"] and token["data"]: + for attr, value in token["data"].items(): + token["data"][attr] = "moo" yield token -TAGS = ['img'] +TAGS = ["img"] ATTRS = { - 'img': ['rel', 'src'] + "img": ["rel", "src"], } html = 'this is cute! ' diff --git a/bleach__examples__remove_tags__clear_html/escape_attributes.py b/bleach__examples__remove_tags__clear_html/escape_attributes.py index 1ee454c7b..a2ac829e0 100644 --- a/bleach__examples__remove_tags__clear_html/escape_attributes.py +++ b/bleach__examples__remove_tags__clear_html/escape_attributes.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/mozilla/bleach @@ -15,67 +15,71 @@ # Map of allowed attributes by tag: -print('Map of allowed attributes by tag:', bleach.sanitizer.ALLOWED_ATTRIBUTES) +print("Map of allowed attributes by tag:", bleach.sanitizer.ALLOWED_ATTRIBUTES) # {'a': ['href', 'title'], 'abbr': ['title'], 'acronym': ['title']} # As a list print( bleach.clean( '

blah blah blah

', - tags=['p'], - attributes=['style'], - styles=['color'], + tags=["p"], + attributes=["style"], + styles=["color"], ) ) #

blah blah blah

# As a dict attrs = { - '*': ['class'], # Any tag with class - 'a': ['href', 'rel'], - 'img': ['alt'], + "*": ["class"], # Any tag with class + "a": ["href", "rel"], + "img": ["alt"], } print( bleach.clean( 'an example', - tags=['img'], - attributes=attrs + tags=["img"], + attributes=attrs, ) ) # an example print() + # # Using functions # def allow_h(tag, name, value): - return name[0] == 'h' + return name[0] == "h" + print( bleach.clean( 'link', - tags=['a'], + tags=["a"], attributes=allow_h, ) ) # link + def allow_src(tag, name, value): - if name in ('alt', 'height', 'width'): + if name in ("alt", "height", "width"): return True - if name == 'src': + if name == "src": p = urlparse(value) - return not p.netloc or p.netloc == 'mydomain.com' + return not p.netloc or p.netloc == "mydomain.com" return False + print( bleach.clean( 'an example', - tags=['img'], + tags=["img"], attributes={ - 'img': allow_src - } + "img": allow_src + }, ) ) # an example diff --git a/bleach__examples__remove_tags__clear_html/escape_protocols.py b/bleach__examples__remove_tags__clear_html/escape_protocols.py index 7f60d515f..5093945d5 100644 --- a/bleach__examples__remove_tags__clear_html/escape_protocols.py +++ b/bleach__examples__remove_tags__clear_html/escape_protocols.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/mozilla/bleach @@ -13,7 +13,7 @@ # List of allowed protocols -print('List of allowed protocols:', bleach.sanitizer.ALLOWED_PROTOCOLS) +print("List of allowed protocols:", bleach.sanitizer.ALLOWED_PROTOCOLS) # ['http', 'https', 'mailto'] print( @@ -26,7 +26,7 @@ print( bleach.clean( 'allowed protocol', - protocols=['http', 'https', 'smb'] + protocols=["http", "https", "smb"], ) ) # allowed protocol @@ -34,7 +34,7 @@ print( bleach.clean( 'allowed protocol', - protocols=bleach.ALLOWED_PROTOCOLS + ['smb'] + protocols=bleach.ALLOWED_PROTOCOLS + ["smb"], ) ) # allowed protocol diff --git a/bleach__examples__remove_tags__clear_html/escape_styles.py b/bleach__examples__remove_tags__clear_html/escape_styles.py index 3174df761..1a937a452 100644 --- a/bleach__examples__remove_tags__clear_html/escape_styles.py +++ b/bleach__examples__remove_tags__clear_html/escape_styles.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/mozilla/bleach @@ -13,15 +13,15 @@ # List of allowed styles -print('List of allowed styles:', bleach.sanitizer.ALLOWED_STYLES) +print("List of allowed styles:", bleach.sanitizer.ALLOWED_STYLES) # [] -tags = ['p', 'em', 'strong'] +tags = ["p", "em", "strong"] attrs = { # Any tag with style - '*': ['style'] + "*": ["style"] } -styles = ['color', 'font-weight'] +styles = ["color", "font-weight"] html = '

my html

' print( @@ -29,7 +29,7 @@ html, tags=tags, attributes=attrs, - styles=styles + styles=styles, ) ) #

my html

diff --git a/bleach__examples__remove_tags__clear_html/escape_tags.py b/bleach__examples__remove_tags__clear_html/escape_tags.py index dc287179f..8857dbf43 100644 --- a/bleach__examples__remove_tags__clear_html/escape_tags.py +++ b/bleach__examples__remove_tags__clear_html/escape_tags.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/mozilla/bleach @@ -13,17 +13,17 @@ # List of allowed tags -print('List of allowed tags:', bleach.sanitizer.ALLOWED_TAGS) +print("List of allowed tags:", bleach.sanitizer.ALLOWED_TAGS) # ['a', 'abbr', 'acronym', 'b', 'blockquote', 'code', 'em', 'i', 'li', 'ol', 'strong', 'ul'] -print(bleach.clean('an example')) +print(bleach.clean("an example")) # an <script>evil()</script> example # Allowed tags print( bleach.clean( - 'an example', - tags=['b'], + "an example", + tags=["b"], ) ) # <i>an example</i> diff --git a/bleach__examples__remove_tags__clear_html/linkify__callbacks_for_altering_attributes.py b/bleach__examples__remove_tags__clear_html/linkify__callbacks_for_altering_attributes.py index f95ba4921..f0cb80729 100644 --- a/bleach__examples__remove_tags__clear_html/linkify__callbacks_for_altering_attributes.py +++ b/bleach__examples__remove_tags__clear_html/linkify__callbacks_for_altering_attributes.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/mozilla/bleach @@ -16,39 +16,47 @@ # Altering Attributes + def shorten_url(attrs, new=False): """Shorten overly-long URLs in the text.""" # Only adjust newly-created links if not new: return attrs # _text will be the same as the URL for new links - text = attrs['_text'] + text = attrs["_text"] if len(text) > 25: - attrs['_text'] = text[:22] + '...' + attrs["_text"] = text[:22] + "..." return attrs + linker = Linker(callbacks=[shorten_url]) -print(linker.linkify('http://example.com/longlonglonglonglongurl')) +print(linker.linkify("http://example.com/longlonglonglonglongurl")) # http://example.com/lon... -print(linker.linkify('abc http://example.com/longlonglonglonglongurl def')) +print( + linker.linkify( + 'abc http://example.com/longlonglonglonglongurl def' + ) +) # abc http://example.com/longlonglonglonglongurl def print() + def outgoing_bouncer(attrs, new=False): """Send outgoing links through a bouncer.""" - href_key = (None, 'href') + href_key = (None, "href") p = urlparse(attrs.get(href_key)) - if p.netloc not in ['example.com', 'www.example.com', '']: + if p.netloc not in ["example.com", "www.example.com", ""]: url = attrs[href_key] - bouncer = 'http://bn.ce/?destination=%s' + bouncer = "http://bn.ce/?destination=%s" attrs[href_key] = bouncer % quote(url) return attrs + linker = Linker(callbacks=[outgoing_bouncer]) -print(linker.linkify('http://example.com')) +print(linker.linkify("http://example.com")) # http://example.com -print(linker.linkify('http://foo.com')) +print(linker.linkify("http://foo.com")) # http://foo.com diff --git a/bleach__examples__remove_tags__clear_html/linkify__callbacks_for_preventing_links.py b/bleach__examples__remove_tags__clear_html/linkify__callbacks_for_preventing_links.py index 6b4b8ca4d..8e70d8b18 100644 --- a/bleach__examples__remove_tags__clear_html/linkify__callbacks_for_preventing_links.py +++ b/bleach__examples__remove_tags__clear_html/linkify__callbacks_for_preventing_links.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/mozilla/bleach @@ -14,14 +14,15 @@ # Preventing Links + def dont_linkify_python(attrs, new=False): # This is an existing link, so leave it be if not new: return attrs # If the TLD is '.py', make sure it starts with http: or https:. # Use _text because that's the original text - link_text = attrs['_text'] - if link_text.endswith('.py') and not link_text.startswith(('http:', 'https:')): + link_text = attrs["_text"] + if link_text.endswith(".py") and not link_text.startswith(("http:", "https:")): # This looks like a Python file, not a URL. Don't make a link. return None # Everything checks out, keep going to the next callback. @@ -29,62 +30,62 @@ def dont_linkify_python(attrs, new=False): linker = Linker(callbacks=[dont_linkify_python]) -print(linker.linkify('abc http://example.com def')) +print(linker.linkify("abc http://example.com def")) # abc http://example.com def -print(linker.linkify('abc models.py def')) +print(linker.linkify("abc models.py def")) # abc models.py def -print('\n' + '-' * 100 + '\n') +print("\n" + "-" * 100 + "\n") -linker = Linker(skip_tags=['pre']) -print(linker.linkify('a b c http://example.com d e f')) +linker = Linker(skip_tags=["pre"]) +print(linker.linkify("a b c http://example.com d e f")) # a b c http://example.com d e f -print(linker.linkify('
http://example.com
')) +print(linker.linkify("
http://example.com
")) #
http://example.com
-print('\n' + '-' * 100 + '\n') +print("\n" + "-" * 100 + "\n") -only_fish_tld_url_re = build_url_re(tlds=['fish']) +only_fish_tld_url_re = build_url_re(tlds=["fish"]) linker = Linker(url_re=only_fish_tld_url_re) -print(linker.linkify('com TLD does not link https://example.com')) +print(linker.linkify("com TLD does not link https://example.com")) # com TLD does not link https://example.com -print(linker.linkify('fish TLD links https://example.fish')) +print(linker.linkify("fish TLD links https://example.fish")) # fish TLD links https://example.fish -print('\n' + '-' * 100 + '\n') +print("\n" + "-" * 100 + "\n") -only_https_url_re = build_url_re(protocols=['https']) +only_https_url_re = build_url_re(protocols=["https"]) linker = Linker(url_re=only_https_url_re) -print(linker.linkify('gopher does not link gopher://example.link')) +print(linker.linkify("gopher does not link gopher://example.link")) # gopher does not link gopher://example.link -print(linker.linkify('https links https://example.com/')) +print(linker.linkify("https links https://example.com/")) # https links https://example.com/ -print('\n' + '-' * 100 + '\n') +print("\n" + "-" * 100 + "\n") -html = 'https://xn--80aaksdi3bpu.xn--p1ai/ https://дайтрафик.рф/' +html = "https://xn--80aaksdi3bpu.xn--p1ai/ https://дайтрафик.рф/" -linker = Linker(url_re=build_url_re(tlds=['рф'])) +linker = Linker(url_re=build_url_re(tlds=["рф"])) print(linker.linkify(html)) # https://xn--80aaksdi3bpu.xn--p1ai/ https://дайтрафик.рф/ -puny_linker = Linker(url_re=build_url_re(tlds=['рф', 'xn--p1ai'])) +puny_linker = Linker(url_re=build_url_re(tlds=["рф", "xn--p1ai"])) print(puny_linker.linkify(html)) # https://xn--80aaksdi3bpu.xn--p1ai/ https://дайтрафик.рф/ -print('\n' + '-' * 100 + '\n') +print("\n" + "-" * 100 + "\n") -only_fish_tld_url_re = build_email_re(tlds=['fish']) +only_fish_tld_url_re = build_email_re(tlds=["fish"]) linker = Linker(email_re=only_fish_tld_url_re, parse_email=True) -print(linker.linkify('does not link email: foo@example.com')) +print(linker.linkify("does not link email: foo@example.com")) # does not link email: foo@example.com -print(linker.linkify('links email foo@example.fish')) +print(linker.linkify("links email foo@example.fish")) # links email foo@example.fish diff --git a/bleach__examples__remove_tags__clear_html/linkify__callbacks_for_removing_attributes.py b/bleach__examples__remove_tags__clear_html/linkify__callbacks_for_removing_attributes.py index fcc8cd143..2d21bb56a 100644 --- a/bleach__examples__remove_tags__clear_html/linkify__callbacks_for_removing_attributes.py +++ b/bleach__examples__remove_tags__clear_html/linkify__callbacks_for_removing_attributes.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/mozilla/bleach @@ -14,14 +14,15 @@ # Removing Attributes + def allowed_attrs(attrs, new=False): """Only allow href, target, rel and title.""" allowed = [ - (None, 'href'), - (None, 'target'), - (None, 'rel'), - (None, 'title'), - '_text', + (None, "href"), + (None, "target"), + (None, "rel"), + (None, "title"), + "_text", ] return dict((k, v) for k, v in attrs.items() if k in allowed) @@ -34,10 +35,12 @@ def allowed_attrs(attrs, new=False): print() + def remove_title(attrs, new=False): - attrs.pop((None, 'title'), None) + attrs.pop((None, "title"), None) return attrs + linker = Linker(callbacks=[remove_title]) print(linker.linkify('link')) # link diff --git a/bleach__examples__remove_tags__clear_html/linkify__callbacks_for_removing_links.py b/bleach__examples__remove_tags__clear_html/linkify__callbacks_for_removing_links.py index 5fa74a432..2fd82cd76 100644 --- a/bleach__examples__remove_tags__clear_html/linkify__callbacks_for_removing_links.py +++ b/bleach__examples__remove_tags__clear_html/linkify__callbacks_for_removing_links.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/mozilla/bleach @@ -14,8 +14,9 @@ # Removing Links + def remove_mailto(attrs, new=False): - if attrs[(None, 'href')].startswith('mailto:'): + if attrs[(None, "href")].startswith("mailto:"): return None return attrs @@ -23,10 +24,10 @@ def remove_mailto(attrs, new=False): linker = Linker(callbacks=[remove_mailto]) -html = ''' +html = """ mail janet! abc http://example.com def -'''.strip() +""".strip() print(linker.linkify(html)) # mail janet! # abc http://example.com def diff --git a/bleach__examples__remove_tags__clear_html/linkify__callbacks_for_setting_attributes.py b/bleach__examples__remove_tags__clear_html/linkify__callbacks_for_setting_attributes.py index 7381bafd6..10c8d7901 100644 --- a/bleach__examples__remove_tags__clear_html/linkify__callbacks_for_setting_attributes.py +++ b/bleach__examples__remove_tags__clear_html/linkify__callbacks_for_setting_attributes.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/mozilla/bleach @@ -16,30 +16,34 @@ # Setting Attributes + def set_title(attrs, new=False): - attrs[(None, 'title')] = 'link in user text' + attrs[(None, "title")] = "link in user text" return attrs + linker = Linker(callbacks=[set_title]) -print(linker.linkify('abc http://example.com def')) +print(linker.linkify("abc http://example.com def")) # abc http://example.com def print() + def set_target(attrs, new=False): - p = urlparse(attrs[(None, 'href')]) - if p.netloc not in ['my-domain.com', 'other-domain.com']: - attrs[(None, 'target')] = '_blank' - attrs[(None, 'class')] = 'external' + p = urlparse(attrs[(None, "href")]) + if p.netloc not in ["my-domain.com", "other-domain.com"]: + attrs[(None, "target")] = "_blank" + attrs[(None, "class")] = "external" else: - attrs.pop((None, 'target'), None) + attrs.pop((None, "target"), None) return attrs -html = ''' + +html = """ abc http://example.com def 123 https://my-domain.com 456 !!! https://my-domain.com ### -'''.strip() +""".strip() linker = Linker(callbacks=[set_target]) print(linker.linkify(html)) # abc http://example.com def diff --git a/bleach__examples__remove_tags__clear_html/linkify__url_email.py b/bleach__examples__remove_tags__clear_html/linkify__url_email.py index 1c62ef42d..00557d99b 100644 --- a/bleach__examples__remove_tags__clear_html/linkify__url_email.py +++ b/bleach__examples__remove_tags__clear_html/linkify__url_email.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/mozilla/bleach @@ -12,10 +12,10 @@ import bleach -html = ''' +html = """ http://example.com hello_world@example.com -'''.strip() +""".strip() # The default callback adds rel="nofollow" print(bleach.linkify(html, parse_email=True)) diff --git a/bleach__examples__remove_tags__clear_html/remove_comments.py b/bleach__examples__remove_tags__clear_html/remove_comments.py index 948e7a0ee..d1e739893 100644 --- a/bleach__examples__remove_tags__clear_html/remove_comments.py +++ b/bleach__examples__remove_tags__clear_html/remove_comments.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/mozilla/bleach @@ -12,7 +12,7 @@ import bleach -html = 'my html' +html = "my html" print(bleach.clean(html)) # my html diff --git a/bleach__examples__remove_tags__clear_html/remove_tags.py b/bleach__examples__remove_tags__clear_html/remove_tags.py index 4a59077b5..e746856dc 100644 --- a/bleach__examples__remove_tags__clear_html/remove_tags.py +++ b/bleach__examples__remove_tags__clear_html/remove_tags.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/mozilla/bleach @@ -12,7 +12,7 @@ import bleach -html = '

is not allowed

' +html = "

is not allowed

" print(bleach.clean(html)) # <p><span>is not <span>allowed</span></span></p> diff --git a/broke_cyrillic_text_and_recovery.py b/broke_cyrillic_text_and_recovery.py index 04112bb14..0551df92b 100644 --- a/broke_cyrillic_text_and_recovery.py +++ b/broke_cyrillic_text_and_recovery.py @@ -1,10 +1,10 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -if __name__ == '__main__': - text = 'Создание таблицы' +if __name__ == "__main__": + text = "Создание таблицы" print(text) - print(text.encode('cp1251').decode('utf-8')) + print(text.encode("cp1251").decode("utf-8")) diff --git a/brute_force__examples/multiprocess.py b/brute_force__examples/multiprocess.py index 736f08b30..9c69a83d5 100644 --- a/brute_force__examples/multiprocess.py +++ b/brute_force__examples/multiprocess.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://ru.stackoverflow.com/a/776977/201445 @@ -11,9 +11,11 @@ import hashlib import itertools import multiprocessing +import string + from functools import partial -import string + alphabet = (string.ascii_letters + string.digits).encode() @@ -35,12 +37,14 @@ def brute_force(mask, target_sha256, n_cutoff=4): target_sha256 = binascii.unhexlify(target_sha256) - bytes_format = mask.replace(b'%', b'%%').replace(b'*', b'%c') - mp_check = partial(check_sha256, - bytes_format=bytes_format, - n=min(n_cutoff, mask.count(b'*')), - target_sha256=target_sha256) - n = max(0, mask.count(b'*') - n_cutoff) + bytes_format = mask.replace(b"%", b"%%").replace(b"*", b"%c") + mp_check = partial( + check_sha256, + bytes_format=bytes_format, + n=min(n_cutoff, mask.count(b"*")), + target_sha256=target_sha256, + ) + n = max(0, mask.count(b"*") - n_cutoff) all_repls_parent = itertools.product(alphabet, repeat=n) with multiprocessing.Pool() as pool: for data in pool.imap_unordered(mp_check, all_repls_parent): @@ -48,9 +52,9 @@ def brute_force(mask, target_sha256, n_cutoff=4): return data -if __name__ == '__main__': +if __name__ == "__main__": # Password: qwe12y - sha256_hex = b'b8cc184b3f4aeb6adb6601ea3e39ef5ac098791acc2a505a66746f43d0c7ed85' + sha256_hex = b"b8cc184b3f4aeb6adb6601ea3e39ef5ac098791acc2a505a66746f43d0c7ed85" - passw_bytes = brute_force(b'qw****', sha256_hex) + passw_bytes = brute_force(b"qw****", sha256_hex) print(passw_bytes.decode()) diff --git a/brute_force__examples/single.py b/brute_force__examples/single.py index 5b2ab6907..54029d840 100644 --- a/brute_force__examples/single.py +++ b/brute_force__examples/single.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://ru.stackoverflow.com/a/776780/201445 @@ -13,28 +13,28 @@ def brute_force(mask, hsh, alphabet): # заменяем '*' на '{}' для последующей подстановки в 'str.format()' - pwd_pat = mask.replace('*', '{}') + pwd_pat = mask.replace("*", "{}") # число звездочек - будем использовать в качестве `product(.., repeat)` - N = mask.count('*') + N = mask.count("*") i = 0 for chars in product(alphabet, repeat=N): i += 1 if i % 10000 == 0: - print('Iterations: {}'.format(i)) + print(f"Iterations: {i}") if hsh == hashlib.sha256(pwd_pat.format(*chars).encode()).hexdigest(): text = pwd_pat.format(*chars) - print('Found: ' + text) + print(f"Found: {text}") return text return None -if __name__ == '__main__': +if __name__ == "__main__": from string import ascii_letters, digits alphabet = ascii_letters + digits # Password: qwe12y - hsh = 'b8cc184b3f4aeb6adb6601ea3e39ef5ac098791acc2a505a66746f43d0c7ed85' - print(brute_force('qw****', hsh, alphabet)) + hsh = "b8cc184b3f4aeb6adb6601ea3e39ef5ac098791acc2a505a66746f43d0c7ed85" + print(brute_force("qw****", hsh, alphabet)) diff --git a/bs4__BeautifulSoup__examples/about_builders.py b/bs4__BeautifulSoup__examples/about_builders.py index ebf817572..e3479b7b5 100644 --- a/bs4__BeautifulSoup__examples/about_builders.py +++ b/bs4__BeautifulSoup__examples/about_builders.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install lxml @@ -13,20 +13,20 @@ text = "" -root = BeautifulSoup(text, 'html.parser') +root = BeautifulSoup(text, "html.parser") print(root.builder) # bs4.builder._htmlparser.HTMLParserTreeBuilder -root = BeautifulSoup(text, 'html5lib') +root = BeautifulSoup(text, "html5lib") print(root.builder) # bs4.builder._html5lib.HTML5TreeBuilder -root = BeautifulSoup(text, 'xml') +root = BeautifulSoup(text, "xml") print(root.builder) # bs4.builder._lxml.LXMLTreeBuilderForXML -root = BeautifulSoup(text, 'lxml-xml') +root = BeautifulSoup(text, "lxml-xml") print(root.builder) # bs4.builder._lxml.LXMLTreeBuilderForXML -root = BeautifulSoup(text, 'lxml') +root = BeautifulSoup(text, "lxml") print(root.builder) # bs4.builder._lxml.LXMLTreeBuilder -root = BeautifulSoup(text, 'lxml-html') +root = BeautifulSoup(text, "lxml-html") print(root.builder) # bs4.builder._lxml.LXMLTreeBuilder diff --git a/bs4__book_authors.py b/bs4__book_authors.py index bfb0db8d1..9c31119c6 100644 --- a/bs4__book_authors.py +++ b/bs4__book_authors.py @@ -1,7 +1,10 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" + + +from bs4 import BeautifulSoup text = """ @@ -173,17 +176,17 @@ def get_title_book(item) -> str: def get_author_full_name(author_node) -> str: - return author_node.lastname.text + ' ' + author_node.initials.text + return author_node.lastname.text + " " + author_node.initials.text def get_authors(item) -> list: - authors = item.select('authors > author') + authors = item.select("authors > author") # Словарь для хранения номера автора и списка на разных языках num_author_by_authors = dict() for author in authors: - num = author['num'] + num = author["num"] if num not in num_author_by_authors: num_author_by_authors[num] = [] @@ -203,7 +206,7 @@ def get_authors(item) -> list: for author in authors: # Приоритетный язык - if author['lang'] == "RU": + if author["lang"] == "RU": full_name = get_author_full_name(author) break @@ -212,10 +215,9 @@ def get_authors(item) -> list: return authors_full_name -from bs4 import BeautifulSoup -root = BeautifulSoup(text, 'html.parser') +root = BeautifulSoup(text, "html.parser") -for item in root.select('item'): +for item in root.select("item"): title_book = get_title_book(item) print(title_book) diff --git a/build_exe/pyinstaller_example/adding_an_icon/main.py b/build_exe/pyinstaller_example/adding_an_icon/main.py index f7eb998d6..88708e355 100644 --- a/build_exe/pyinstaller_example/adding_an_icon/main.py +++ b/build_exe/pyinstaller_example/adding_an_icon/main.py @@ -1,9 +1,9 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -if __name__ == '__main__': +if __name__ == "__main__": import os os.system("echo Hello World!") diff --git a/build_exe/pyinstaller_example/compress_exe/main.py b/build_exe/pyinstaller_example/compress_exe/main.py index a3bf04cfc..f1e929469 100644 --- a/build_exe/pyinstaller_example/compress_exe/main.py +++ b/build_exe/pyinstaller_example/compress_exe/main.py @@ -1,10 +1,10 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -if __name__ == '__main__': +if __name__ == "__main__": import sys from PySide.QtGui import * @@ -13,11 +13,11 @@ app = QApplication(sys.argv) - line_edit = QLineEdit('New line') - button = QPushButton('Add') + line_edit = QLineEdit("New line") + button = QPushButton("Add") text_edit = QTextEdit() - def add_to_text(): + def add_to_text() -> None: text = line_edit.text() text_edit.append(text) @@ -29,7 +29,7 @@ def add_to_text(): layout.addWidget(text_edit) w = QWidget() - w.setWindowTitle('Example') + w.setWindowTitle("Example") w.setLayout(layout) w.show() diff --git a/build_exe/pyinstaller_example/console/hello_world/main.py b/build_exe/pyinstaller_example/console/hello_world/main.py index f7eb998d6..88708e355 100644 --- a/build_exe/pyinstaller_example/console/hello_world/main.py +++ b/build_exe/pyinstaller_example/console/hello_world/main.py @@ -1,9 +1,9 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -if __name__ == '__main__': +if __name__ == "__main__": import os os.system("echo Hello World!") diff --git a/build_exe/pyinstaller_example/console/print_current_script_dir/main.py b/build_exe/pyinstaller_example/console/print_current_script_dir/main.py index 8b5b0dfac..854782ed9 100644 --- a/build_exe/pyinstaller_example/console/print_current_script_dir/main.py +++ b/build_exe/pyinstaller_example/console/print_current_script_dir/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # FROM: https://github.com/gil9red/SimplePyScripts/blob/927af3d3f05a0129338b21d074ca241c8a055118/get_current_script_dir.py @@ -14,7 +14,7 @@ def get_current_script_dir(follow_symlinks=True, normcase=False) -> str: # py2exe, PyInstaller, cx_Freeze - if getattr(sys, 'frozen', False): + if getattr(sys, "frozen", False): path = os.path.abspath(sys.executable) else: # Analog inspect.getabsfile without os.path.normcase @@ -30,5 +30,5 @@ def get_current_script_dir(follow_symlinks=True, normcase=False) -> str: return os.path.dirname(path) -if __name__ == '__main__': +if __name__ == "__main__": print(get_current_script_dir()) diff --git a/build_exe/pyinstaller_example/gui/qt_pyqt5/main.py b/build_exe/pyinstaller_example/gui/qt_pyqt5/main.py index c855caf64..f504fb661 100644 --- a/build_exe/pyinstaller_example/gui/qt_pyqt5/main.py +++ b/build_exe/pyinstaller_example/gui/qt_pyqt5/main.py @@ -1,21 +1,28 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import sys -from PyQt5.QtWidgets import QApplication, QLineEdit, QPushButton, QTextEdit, QVBoxLayout, QWidget +from PyQt5.QtWidgets import ( + QApplication, + QLineEdit, + QPushButton, + QTextEdit, + QVBoxLayout, + QWidget, +) app = QApplication(sys.argv) -line_edit = QLineEdit('New line') -button = QPushButton('Add') +line_edit = QLineEdit("New line") +button = QPushButton("Add") text_edit = QTextEdit() -def add_to_text(): +def add_to_text() -> None: text = line_edit.text() text_edit.append(text) @@ -28,7 +35,7 @@ def add_to_text(): layout.addWidget(text_edit) w = QWidget() -w.setWindowTitle('Example') +w.setWindowTitle("Example") w.setLayout(layout) w.show() diff --git a/build_exe/pyinstaller_example/gui/qt_pyside/main.py b/build_exe/pyinstaller_example/gui/qt_pyside/main.py index a3bf04cfc..f1e929469 100644 --- a/build_exe/pyinstaller_example/gui/qt_pyside/main.py +++ b/build_exe/pyinstaller_example/gui/qt_pyside/main.py @@ -1,10 +1,10 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -if __name__ == '__main__': +if __name__ == "__main__": import sys from PySide.QtGui import * @@ -13,11 +13,11 @@ app = QApplication(sys.argv) - line_edit = QLineEdit('New line') - button = QPushButton('Add') + line_edit = QLineEdit("New line") + button = QPushButton("Add") text_edit = QTextEdit() - def add_to_text(): + def add_to_text() -> None: text = line_edit.text() text_edit.append(text) @@ -29,7 +29,7 @@ def add_to_text(): layout.addWidget(text_edit) w = QWidget() - w.setWindowTitle('Example') + w.setWindowTitle("Example") w.setLayout(layout) w.show() diff --git a/build_exe/pyinstaller_example/no_console__without_console/main.py b/build_exe/pyinstaller_example/no_console__without_console/main.py index ef6d21d52..400b0f33c 100644 --- a/build_exe/pyinstaller_example/no_console__without_console/main.py +++ b/build_exe/pyinstaller_example/no_console__without_console/main.py @@ -1,10 +1,10 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -print('Start!') +print("Start!") -with open('hello.txt', 'w', encoding='utf-8') as f: - f.write('Hello World!') +with open("hello.txt", "w", encoding="utf-8") as f: + f.write("Hello World!") diff --git a/build_fake_image__build_exe/__injected_code.py b/build_fake_image__build_exe/__injected_code.py index ccab49c37..a029e4a3b 100644 --- a/build_fake_image__build_exe/__injected_code.py +++ b/build_fake_image__build_exe/__injected_code.py @@ -1,5 +1,7 @@ import os.path from pathlib import Path -file_name = Path(os.path.expanduser("~/Desktop")).resolve() / "README_YOU_WERE_HACKED.txt" + +DIR = Path(os.path.expanduser("~/Desktop")).resolve() +file_name = DIR / "README_YOU_WERE_HACKED.txt" file_name.touch(exist_ok=True) diff --git a/build_fake_image__build_exe/build_exe.py b/build_fake_image__build_exe/build_exe.py index eb0736a55..389339342 100644 --- a/build_fake_image__build_exe/build_exe.py +++ b/build_fake_image__build_exe/build_exe.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://habr.com/post/126198/ @@ -19,19 +19,19 @@ from pathlib import Path -FILE_NAME = 'image.jpg' -FILE_NAME_ICO = 'icon.ico' +FILE_NAME = "image.jpg" +FILE_NAME_ICO = "icon.ico" ICON_SIZES = [(16, 16), (32, 32), (48, 48), (64, 64)] -OUT_FILE_NAME = 'main.exe' +OUT_FILE_NAME = "main.exe" # Юникодная последовательсть \u202E нужна чтобы превратить: picgpj.exe -> pic‮gpj.exe -NEW_FILE_NAME = 'pic\u202Egpj.exe' +NEW_FILE_NAME = "pic\u202Egpj.exe" -FILE_NAME_ARCHIVE = 'pic.zip' +FILE_NAME_ARCHIVE = "pic.zip" -INJECT_FILE_NAME = '__injected_code.py' -INJECT_CODE = Path(INJECT_FILE_NAME).read_text(encoding='utf-8') +INJECT_FILE_NAME = "__injected_code.py" +INJECT_CODE = Path(INJECT_FILE_NAME).read_text(encoding="utf-8") # Создадим ico файл для иконки приложения @@ -41,20 +41,27 @@ generator.generate(FILE_NAME, INJECT_CODE) # Analog build_exe.bat -subprocess.call([ - "pyinstaller", "--onefile", "--noconsole", - "--icon=" + FILE_NAME_ICO, "--name=" + OUT_FILE_NAME, - generator.FILE_NAME -]) - -shutil.copy('dist/' + OUT_FILE_NAME, 'dist/' + NEW_FILE_NAME) +subprocess.call( + [ + "pyinstaller", + "--onefile", + "--noconsole", + "--icon=" + FILE_NAME_ICO, + "--name=" + OUT_FILE_NAME, + generator.FILE_NAME, + ] +) + +shutil.copy("dist/" + OUT_FILE_NAME, "dist/" + NEW_FILE_NAME) # Добавляем сгенерированный файл в архив -with zipfile.ZipFile('dist/' + FILE_NAME_ARCHIVE, mode='w', compression=zipfile.ZIP_DEFLATED) as f: - f.write('dist/' + NEW_FILE_NAME, NEW_FILE_NAME) +with zipfile.ZipFile( + "dist/" + FILE_NAME_ARCHIVE, mode="w", compression=zipfile.ZIP_DEFLATED +) as f: + f.write("dist/" + NEW_FILE_NAME, NEW_FILE_NAME) # Подчистим за собой, удалив ненужные файлы os.remove(FILE_NAME_ICO) os.remove(generator.FILE_NAME) -os.remove('main.exe.spec') -shutil.rmtree('build') +os.remove("main.exe.spec") +shutil.rmtree("build") diff --git a/build_fake_image__build_exe/generator.py b/build_fake_image__build_exe/generator.py index 7ef7e4f3e..8b0eabda3 100644 --- a/build_fake_image__build_exe/generator.py +++ b/build_fake_image__build_exe/generator.py @@ -1,12 +1,15 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" + + +import base64 +from PIL import Image # SOURCE: https://github.com/gil9red/SimplePyScripts/blob/7cebedb16a5ac81333ebf62af410fdb53a690d29/convert_image_to_ico/main.py -def convert_image_to_ico(file_name, file_name_ico, icon_sizes=None): - from PIL import Image +def convert_image_to_ico(file_name, file_name_ico, icon_sizes=None) -> None: img = Image.open(file_name) if icon_sizes: @@ -15,19 +18,18 @@ def convert_image_to_ico(file_name, file_name_ico, icon_sizes=None): img.save(file_name_ico) -FILE_NAME = 'image_png.py' +FILE_NAME = "image_png.py" # SOURCE: https://github.com/gil9red/SimplePyScripts/blob/aec64c1749d4f6f3176e3222c7e7c554f40c693f/generator_py_with_inner_image_with_open/main.py -def generate(file_name, inject_code: str): - with open(file_name, 'rb') as f: +def generate(file_name, inject_code: str) -> None: + with open(file_name, "rb") as f: img_bytes = f.read() - import base64 - img_base64 = base64.b64encode(img_bytes).decode('utf-8') + img_base64 = base64.b64encode(img_bytes).decode("utf-8") - with open(FILE_NAME, 'w', encoding='utf-8') as f: - f.write('''\ + with open(FILE_NAME, "w", encoding="utf-8") as f: + f.write("""\ #!/usr/bin/env python3 # -*- coding: utf-8 -*- @@ -54,11 +56,11 @@ def save_and_run(): # INJECTED CODE {} -'''.format(file_name, img_base64, inject_code)) +""".format(file_name, img_base64, inject_code)) -if __name__ == '__main__': - file_name = 'image.jpg' +if __name__ == "__main__": + file_name = "image.jpg" inject_code = """ import os.path from pathlib import Path diff --git a/caesar_code/caesar_code.py b/caesar_code/caesar_code.py index a98f5176b..a2dec9261 100644 --- a/caesar_code/caesar_code.py +++ b/caesar_code/caesar_code.py @@ -1,13 +1,14 @@ -__author__ = 'ipetrash' +__author__ = "ipetrash" """Реализация алгоритма Код Цезаря.""" import typing - from string import ascii_lowercase, ascii_uppercase -ru_lowercase = 'абвгдеёжзийклмнопрстуфхцчшщъыьэюя' + + +ru_lowercase = "абвгдеёжзийклмнопрстуфхцчшщъыьэюя" ru_uppercase = ru_lowercase.upper() alphabet_list = [ @@ -17,7 +18,7 @@ ru_uppercase, # Грузинский язык - 'აბგდევზთიკლმნოპჟრსტჳუფქღყშჩცძწჭხჴჯჰ', + "აბგდევზთიკლმნოპჟრსტჳუფქღყშჩცძწჭხჴჯჰ", ] @@ -33,7 +34,7 @@ def caesar_code(text: str, shift: int) -> str: """Функция принимает текстовую строку и возвращает, новую строку символы которой сдвинуты по алфавиту.""" - shift_text = '' + shift_text = "" for c in text: alphabet = get_alp_by_char(c) @@ -47,7 +48,7 @@ def caesar_code(text: str, shift: int) -> str: return shift_text -if __name__ == '__main__': +if __name__ == "__main__": text = "Hello World!" print(caesar_code(text, shift=0)) print(caesar_code(text, shift=2)) @@ -57,10 +58,10 @@ def caesar_code(text: str, shift: int) -> str: print(caesar_code(text, shift=78)) assert caesar_code(text, shift=0) == text - assert caesar_code(text, shift=2) == 'Jgnnq Yqtnf!' - assert caesar_code(text, shift=-4) == 'Dahhk Sknhz!' + assert caesar_code(text, shift=2) == "Jgnnq Yqtnf!" + assert caesar_code(text, shift=-4) == "Dahhk Sknhz!" assert caesar_code(text, shift=26) == text - assert caesar_code(text, shift=50) == 'Fcjjm Umpjb!' + assert caesar_code(text, shift=50) == "Fcjjm Umpjb!" assert caesar_code(text, shift=78) == text print() @@ -74,10 +75,10 @@ def caesar_code(text: str, shift: int) -> str: print(caesar_code(text, shift=99)) assert caesar_code(text, shift=0) == text - assert caesar_code(text, shift=2) == 'Сткджф окт!' - assert caesar_code(text, shift=-4) == 'Лмеюбо ием!' + assert caesar_code(text, shift=2) == "Сткджф окт!" + assert caesar_code(text, shift=-4) == "Лмеюбо ием!" assert caesar_code(text, shift=33) == text - assert caesar_code(text, shift=50) == 'Абщтхг эщб!' + assert caesar_code(text, shift=50) == "Абщтхг эщб!" assert caesar_code(text, shift=99) == text print() @@ -87,22 +88,22 @@ def caesar_code(text: str, shift: int) -> str: print(caesar_code(text, shift=-4)) assert caesar_code(text, shift=0) == text - assert caesar_code(text, shift=2) == 'Jgnnq окт!' - assert caesar_code(text, shift=-4) == 'Dahhk ием!' + assert caesar_code(text, shift=2) == "Jgnnq окт!" + assert caesar_code(text, shift=-4) == "Dahhk ием!" print() - text = 'გამარჯობა მსოფლიოში!' + text = "გამარჯობა მსოფლიოში!" print(caesar_code(text, shift=0)) print(caesar_code(text, shift=2)) print(caesar_code(text, shift=-4)) assert caesar_code(text, shift=0) == text - assert caesar_code(text, shift=2) == 'ეგოგტაჟდგ ოჳჟღნლჟცლ!' - assert caesar_code(text, shift=-4) == 'ჯხთხნწკჴხ თოკსზეკფე!' + assert caesar_code(text, shift=2) == "ეგოგტაჟდგ ოჳჟღნლჟცლ!" + assert caesar_code(text, shift=-4) == "ჯხთხნწკჴხ თოკსზეკფე!" # Hint: see shift=23 print() print() - TEXT = 'VWDQ LV QRW ZKDW KH VHHPV' + TEXT = "VWDQ LV QRW ZKDW KH VHHPV" for i in range(len(ascii_uppercase)): print(i, caesar_code(TEXT, i)) diff --git a/caesar_code/rot13.py b/caesar_code/rot13.py index f729b3ca1..cccc270fd 100644 --- a/caesar_code/rot13.py +++ b/caesar_code/rot13.py @@ -1,13 +1,16 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" + + +import codecs def rot13(message): - import codecs - return codecs.encode(message, 'rot13') + return codecs.encode(message, "rot13") -assert rot13("test") == "grfg" -assert rot13("Test") == "Grfg" +if __name__ == '__main__': + assert rot13("test") == "grfg" + assert rot13("Test") == "Grfg" diff --git a/calc_statistic_anime_Naruto__by_fillers/main.py b/calc_statistic_anime_Naruto__by_fillers/main.py index b1e08c715..83b27bd89 100644 --- a/calc_statistic_anime_Naruto__by_fillers/main.py +++ b/calc_statistic_anime_Naruto__by_fillers/main.py @@ -1,26 +1,28 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -def get_html_by_url__from_cache(url, cache_dir='cache'): - import os +import os +import re + +import requests + + +def get_html_by_url__from_cache(url, cache_dir="cache"): os.makedirs(cache_dir, exist_ok=True) file_name = os.path.basename(url) + file_name = re.sub(r"\W", "_", file_name) - import re - file_name = re.sub(r'[^\w\d]', '_', file_name) - - file_name = cache_dir + '/' + file_name + '.html' + file_name = cache_dir + "/" + file_name + ".html" if os.path.exists(file_name): - with open(file_name, 'rb') as f: + with open(file_name, "rb") as f: return f.read() - with open(file_name, 'wb') as f: - import requests + with open(file_name, "wb") as f: rs = requests.get(url) f.write(rs.content) @@ -30,9 +32,9 @@ def get_html_by_url__from_cache(url, cache_dir='cache'): def get_ep_chapters_s1(): def get_urls_of_season(): return [ - 'https://ru.wikipedia.org/wiki/Список_серий_«Наруто»_(сезоны_1—3)', - 'https://ru.wikipedia.org/wiki/Список_серий_«Наруто»_(сезоны_4—6)', - 'https://ru.wikipedia.org/wiki/Список_серий_«Наруто»_(сезоны_7—9)', + "https://ru.wikipedia.org/wiki/Список_серий_«Наруто»_(сезоны_1—3)", + "https://ru.wikipedia.org/wiki/Список_серий_«Наруто»_(сезоны_4—6)", + "https://ru.wikipedia.org/wiki/Список_серий_«Наруто»_(сезоны_7—9)", ] from bs4 import BeautifulSoup @@ -42,10 +44,10 @@ def get_urls_of_season(): for url in get_urls_of_season(): html_content = get_html_by_url__from_cache(url) - root = BeautifulSoup(html_content, 'html.parser') + root = BeautifulSoup(html_content, "html.parser") - for td in root.select('td'): - if td.has_attr('id') and td['id'].startswith('ep'): + for td in root.select("td"): + if td.has_attr("id") and td["id"].startswith("ep"): episode = td.text.strip() manga_chapters = td.next_sibling.next_sibling.text.strip() @@ -57,13 +59,13 @@ def get_urls_of_season(): def get_ep_chapters_s2(): def get_urls_of_season(): return [ - 'https://ru.wikipedia.org/wiki/Список_серий_«Наруто:_Ураганные_хроники»_(сезоны_1—4)', - 'https://ru.wikipedia.org/wiki/Список_серий_«Наруто:_Ураганные_хроники»_(сезоны_5—8)', - 'https://ru.wikipedia.org/wiki/Список_серий_«Наруто:_Ураганные_хроники»_(сезоны_9—12)', - 'https://ru.wikipedia.org/wiki/Список_серий_«Наруто:_Ураганные_хроники»_(сезоны_13—16)', - 'https://ru.wikipedia.org/wiki/Список_серий_«Наруто:_Ураганные_хроники»_(сезоны_17—20)', - 'https://ru.wikipedia.org/wiki/Список_серий_«Наруто:_Ураганные_хроники»_(сезоны_21—24)', - 'https://ru.wikipedia.org/wiki/Список_серий_«Наруто:_Ураганные_хроники»_(сезоны_25—28)', + "https://ru.wikipedia.org/wiki/Список_серий_«Наруто:_Ураганные_хроники»_(сезоны_1—4)", + "https://ru.wikipedia.org/wiki/Список_серий_«Наруто:_Ураганные_хроники»_(сезоны_5—8)", + "https://ru.wikipedia.org/wiki/Список_серий_«Наруто:_Ураганные_хроники»_(сезоны_9—12)", + "https://ru.wikipedia.org/wiki/Список_серий_«Наруто:_Ураганные_хроники»_(сезоны_13—16)", + "https://ru.wikipedia.org/wiki/Список_серий_«Наруто:_Ураганные_хроники»_(сезоны_17—20)", + "https://ru.wikipedia.org/wiki/Список_серий_«Наруто:_Ураганные_хроники»_(сезоны_21—24)", + "https://ru.wikipedia.org/wiki/Список_серий_«Наруто:_Ураганные_хроники»_(сезоны_25—28)", ] from bs4 import BeautifulSoup @@ -73,10 +75,10 @@ def get_urls_of_season(): for url in get_urls_of_season(): html_content = get_html_by_url__from_cache(url) - root = BeautifulSoup(html_content, 'html.parser') + root = BeautifulSoup(html_content, "html.parser") - for td in root.select('td'): - if td.has_attr('id') and td['id'].startswith('ep'): + for td in root.select("td"): + if td.has_attr("id") and td["id"].startswith("ep"): episode = td.text.strip() manga_chapters = td.next_sibling.next_sibling.next_sibling.text.strip() @@ -87,28 +89,40 @@ def get_urls_of_season(): def calc_statistic(ep_chapters: list) -> (int, int, int, int): number_all_ep = len(ep_chapters) - number_filler_ep = len([x for x in ep_chapters if 'Нет (филлер)' in x[1]]) + number_filler_ep = len([x for x in ep_chapters if "Нет (филлер)" in x[1]]) number_manga_ep = number_all_ep - number_filler_ep precent_filler = number_filler_ep / number_all_ep * 100 return number_all_ep, number_manga_ep, number_filler_ep, precent_filler -if __name__ == '__main__': +if __name__ == "__main__": ep_chapters_s1 = get_ep_chapters_s1() - number_all_ep_s1, _, number_filler_ep_s1, precent_filler_s1 = calc_statistic(ep_chapters_s1) - print(f'Season 1.\n' - f' Total: {number_all_ep_s1}, fillers: {number_filler_ep_s1} ({precent_filler_s1:.1f}%)\n') + number_all_ep_s1, _, number_filler_ep_s1, precent_filler_s1 = calc_statistic( + ep_chapters_s1 + ) + print( + f"Season 1.\n" + f" Total: {number_all_ep_s1}, fillers: {number_filler_ep_s1} ({precent_filler_s1:.1f}%)\n" + ) ep_chapters_s2 = get_ep_chapters_s2() - number_all_ep_s2, _, number_filler_ep_s2, precent_filler_s2 = calc_statistic(ep_chapters_s2) - print(f'Season 2.\n' - f' Total: {number_all_ep_s2}, fillers: {number_filler_ep_s2} ({precent_filler_s2:.1f}%)\n') + number_all_ep_s2, _, number_filler_ep_s2, precent_filler_s2 = calc_statistic( + ep_chapters_s2 + ) + print( + f"Season 2.\n" + f" Total: {number_all_ep_s2}, fillers: {number_filler_ep_s2} ({precent_filler_s2:.1f}%)\n" + ) ep_chapters_s1_2 = ep_chapters_s1 + ep_chapters_s2 - number_all_ep_s1_2, _, number_filler_ep_s1_2, precent_filler_s1_2 = calc_statistic(ep_chapters_s1_2) - print(f'Season 1+2.\n' - f' Total: {number_all_ep_s1_2}, fillers: {number_filler_ep_s1_2} ({precent_filler_s1_2:.1f}%)\n') + number_all_ep_s1_2, _, number_filler_ep_s1_2, precent_filler_s1_2 = calc_statistic( + ep_chapters_s1_2 + ) + print( + f"Season 1+2.\n" + f" Total: {number_all_ep_s1_2}, fillers: {number_filler_ep_s1_2} ({precent_filler_s1_2:.1f}%)\n" + ) # Season 1. # Total: 220, fillers: 93 (42.3%) diff --git a/calculator/calculator.py b/calculator/calculator.py index a29b6a375..fb3da2435 100644 --- a/calculator/calculator.py +++ b/calculator/calculator.py @@ -1,38 +1,38 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" while True: oper = input("Enter 'add', 'substract', 'multiply', 'divide', 'quit', 'power': ") - if oper == 'quit': + if oper == "quit": break - + try: - if oper == 'add': + if oper == "add": x = int(input("Enter 1st num: ")) y = int(input("Enter 2nd num: ")) print(x + y) - elif oper == 'substract': + elif oper == "substract": x = int(input("Enter 1st num: ")) y = int(input("Enter 2nd num: ")) print(x - y) - elif oper == 'multiply': + elif oper == "multiply": x = int(input("Enter 1st num: ")) y = int(input("Enter 2nd num: ")) print(x * y) - elif oper == 'divide': + elif oper == "divide": x = int(input("Enter 1st num: ")) y = int(input("Enter 2nd num: ")) print(x / y) - elif oper == 'power': + elif oper == "power": x = int(input("Enter the base: ")) y = int(input("Enter the exponent: ")) - print(x ** y) + print(x**y) else: print("Unknown command!") - + except ValueError: print("Invalid value!") diff --git a/calculator/use__ast_parse_eval.py b/calculator/use__ast_parse_eval.py index c38e6e840..4ed6dd3d5 100644 --- a/calculator/use__ast_parse_eval.py +++ b/calculator/use__ast_parse_eval.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://stackoverflow.com/a/9558001/5909792 @@ -20,7 +20,7 @@ ast.FloorDiv: op.floordiv, ast.Pow: op.pow, ast.BitXor: op.xor, - ast.USub: op.neg + ast.USub: op.neg, } @@ -48,13 +48,13 @@ def eval_expr(expr): -5.0 """ - return eval_(ast.parse(expr, mode='eval').body) + return eval_(ast.parse(expr, mode="eval").body) -if __name__ == '__main__': - print(eval_expr('2 + 2 * 2')) # 6 - print(eval_expr('(2 + 2) * 2')) # 8 - print(eval_expr('10 * 10')) # 100 - print(eval_expr('100 / 10')) # 10.0 - print(eval_expr('100 // 10')) # 10 - print(eval_expr('2 ** 3')) # 8 +if __name__ == "__main__": + print(eval_expr("2 + 2 * 2")) # 6 + print(eval_expr("(2 + 2) * 2")) # 8 + print(eval_expr("10 * 10")) # 100 + print(eval_expr("100 / 10")) # 10.0 + print(eval_expr("100 // 10")) # 10 + print(eval_expr("2 ** 3")) # 8 diff --git a/calculator/use__numeric_string_parser__module/main.py b/calculator/use__numeric_string_parser__module/main.py index ab105b079..102f34f3e 100644 --- a/calculator/use__numeric_string_parser__module/main.py +++ b/calculator/use__numeric_string_parser__module/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # from math import * @@ -22,8 +22,8 @@ from numeric_string_parser import NumericStringParser -if __name__ == '__main__': +if __name__ == "__main__": nsp = NumericStringParser() - print(nsp.eval('2^4')) - print(nsp.eval('(2+2)*2^4')) - print(nsp.eval('sin(2+2)')) + print(nsp.eval("2^4")) + print(nsp.eval("(2+2)*2^4")) + print(nsp.eval("sin(2+2)")) diff --git a/calculator/use__numeric_string_parser__module/numeric_string_parser.py b/calculator/use__numeric_string_parser__module/numeric_string_parser.py index 8fa13ce0b..673a5e664 100644 --- a/calculator/use__numeric_string_parser__module/numeric_string_parser.py +++ b/calculator/use__numeric_string_parser__module/numeric_string_parser.py @@ -1,32 +1,47 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -from pyparsing import (Literal,CaselessLiteral,Word,Combine,Group,Optional, - ZeroOrMore,Forward,nums,alphas,oneOf) +from pyparsing import ( + Literal, + CaselessLiteral, + Word, + Combine, + Group, + Optional, + ZeroOrMore, + Forward, + nums, + alphas, + oneOf, +) import math import operator -__author__='Paul McGuire' -__version__ = '$Revision: 0.0 $' -__date__ = '$Date: 2009-03-20 $' -__source__='''http://pyparsing.wikispaces.com/file/view/fourFn.py +__author__ = "Paul McGuire" +__version__ = "$Revision: 0.0 $" +__date__ = "$Date: 2009-03-20 $" +__source__ = """http://pyparsing.wikispaces.com/file/view/fourFn.py http://pyparsing.wikispaces.com/message/view/home/15549426 -''' -__note__=''' +""" +__note__ = """ All I've done is rewrap Paul McGuire's fourFn.py as a class, so I can use it more easily in other places. -''' +""" + class NumericStringParser(object): - ''' + """ Most of this code comes from the fourFn.py pyparsing example - ''' - def pushFirst(self, strg, loc, toks ): - self.exprStack.append( toks[0] ) - def pushUMinus(self, strg, loc, toks ): - if toks and toks[0]=='-': - self.exprStack.append( 'unary -' ) + """ + + def pushFirst(self, strg, loc, toks): + self.exprStack.append(toks[0]) + + def pushUMinus(self, strg, loc, toks): + if toks and toks[0] == "-": + self.exprStack.append("unary -") + def __init__(self): """ expop :: '^' @@ -38,72 +53,84 @@ def __init__(self): term :: factor [ multop factor ]* expr :: term [ addop term ]* """ - point = Literal( "." ) - e = CaselessLiteral( "E" ) - fnumber = Combine( Word( "+-"+nums, nums ) + - Optional( point + Optional( Word( nums ) ) ) + - Optional( e + Word( "+-"+nums, nums ) ) ) - ident = Word(alphas, alphas+nums+"_$") - plus = Literal( "+" ) - minus = Literal( "-" ) - mult = Literal( "*" ) - div = Literal( "/" ) - lpar = Literal( "(" ).suppress() - rpar = Literal( ")" ).suppress() - addop = plus | minus + point = Literal(".") + e = CaselessLiteral("E") + fnumber = Combine( + Word("+-" + nums, nums) + + Optional(point + Optional(Word(nums))) + + Optional(e + Word("+-" + nums, nums)) + ) + ident = Word(alphas, alphas + nums + "_$") + plus = Literal("+") + minus = Literal("-") + mult = Literal("*") + div = Literal("/") + lpar = Literal("(").suppress() + rpar = Literal(")").suppress() + addop = plus | minus multop = mult | div - expop = Literal( "^" ) - pi = CaselessLiteral( "PI" ) + expop = Literal("^") + pi = CaselessLiteral("PI") expr = Forward() - atom = ((Optional(oneOf("- +")) + - (pi|e|fnumber|ident+lpar+expr+rpar).setParseAction(self.pushFirst)) - | Optional(oneOf("- +")) + Group(lpar+expr+rpar) - ).setParseAction(self.pushUMinus) + atom = ( + ( + Optional(oneOf("- +")) + + (pi | e | fnumber | ident + lpar + expr + rpar).setParseAction( + self.pushFirst + ) + ) + | Optional(oneOf("- +")) + Group(lpar + expr + rpar) + ).setParseAction(self.pushUMinus) # by defining exponentiation as "atom [ ^ factor ]..." instead of # "atom [ ^ atom ]...", we get right-to-left exponents, instead of left-to-right # that is, 2^3^2 = 2^(3^2), not (2^3)^2. factor = Forward() - factor << atom + ZeroOrMore( ( expop + factor ).setParseAction( self.pushFirst ) ) - term = factor + ZeroOrMore( ( multop + factor ).setParseAction( self.pushFirst ) ) - expr << term + ZeroOrMore( ( addop + term ).setParseAction( self.pushFirst ) ) + factor << atom + ZeroOrMore((expop + factor).setParseAction(self.pushFirst)) + term = factor + ZeroOrMore((multop + factor).setParseAction(self.pushFirst)) + expr << term + ZeroOrMore((addop + term).setParseAction(self.pushFirst)) # addop_term = ( addop + term ).setParseAction( self.pushFirst ) # general_term = term + ZeroOrMore( addop_term ) | OneOrMore( addop_term) # expr << general_term self.bnf = expr # map operator symbols to corresponding arithmetic operations epsilon = 1e-12 - self.opn = { "+" : operator.add, - "-" : operator.sub, - "*" : operator.mul, - "/" : operator.truediv, - "^" : operator.pow } - self.fn = { "sin" : math.sin, - "cos" : math.cos, - "tan" : math.tan, - "abs" : abs, - "trunc" : lambda a: int(a), - "round" : round, - "sgn" : lambda a: abs(a)>epsilon and cmp(a,0) or 0} - def evaluateStack(self, s ): + self.opn = { + "+": operator.add, + "-": operator.sub, + "*": operator.mul, + "/": operator.truediv, + "^": operator.pow, + } + self.fn = { + "sin": math.sin, + "cos": math.cos, + "tan": math.tan, + "abs": abs, + "trunc": lambda a: int(a), + "round": round, + "sgn": lambda a: abs(a) > epsilon and cmp(a, 0) or 0, + } + + def evaluateStack(self, s): op = s.pop() - if op == 'unary -': - return -self.evaluateStack( s ) + if op == "unary -": + return -self.evaluateStack(s) if op in "+-*/^": - op2 = self.evaluateStack( s ) - op1 = self.evaluateStack( s ) - return self.opn[op]( op1, op2 ) + op2 = self.evaluateStack(s) + op1 = self.evaluateStack(s) + return self.opn[op](op1, op2) elif op == "PI": - return math.pi # 3.1415926535 + return math.pi # 3.1415926535 elif op == "E": return math.e # 2.718281828 elif op in self.fn: - return self.fn[op]( self.evaluateStack( s ) ) + return self.fn[op](self.evaluateStack(s)) elif op[0].isalpha(): return 0 else: - return float( op ) - def eval(self,num_string,parseAll=True): - self.exprStack=[] - results=self.bnf.parseString(num_string,parseAll) - val=self.evaluateStack( self.exprStack[:] ) + return float(op) + + def eval(self, num_string, parseAll=True): + self.exprStack = self.bnf.parseString(num_string, parseAll) + val = self.evaluateStack(self.exprStack[:]) return val diff --git a/calculator/use__numexpr__module/main.py b/calculator/use__numexpr__module/main.py index 1470bfae3..c8def08db 100644 --- a/calculator/use__numexpr__module/main.py +++ b/calculator/use__numexpr__module/main.py @@ -1,13 +1,15 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import numexpr -print(numexpr.evaluate('2 + 2 * 2')) -print(numexpr.evaluate('10 ** 3')) -print(numexpr.evaluate('2 ** 10')) -print(numexpr.evaluate('sin(2 ** 10)')) -print(numexpr.evaluate('(0xFF + 255) / 0b1010')) -print(numexpr.evaluate('(0xFF + 255) // 0b1010')) + + +print(numexpr.evaluate("2 + 2 * 2")) +print(numexpr.evaluate("10 ** 3")) +print(numexpr.evaluate("2 ** 10")) +print(numexpr.evaluate("sin(2 ** 10)")) +print(numexpr.evaluate("(0xFF + 255) / 0b1010")) +print(numexpr.evaluate("(0xFF + 255) // 0b1010")) diff --git a/calculator/use__simpleeval__module.py b/calculator/use__simpleeval__module.py index 5572ecb37..96bcb731e 100644 --- a/calculator/use__simpleeval__module.py +++ b/calculator/use__simpleeval__module.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install simpleeval @@ -10,6 +10,6 @@ print(simple_eval("21 + 21")) # 42 print(simple_eval("2 + 2 * 2")) # 6 -print(simple_eval('10 ** 123')) # 1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +print(simple_eval("10 ** 123")) # 1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 print(simple_eval("21 + 19 / 7 + (8 % 3) ** 9")) # 535.7142857142857 print(simple_eval("square(11)", functions={"square": lambda x: x * x})) # 121 diff --git a/calculator/very_very_simple_calc.py b/calculator/very_very_simple_calc.py index cfcd0bbff..c8815a6b6 100644 --- a/calculator/very_very_simple_calc.py +++ b/calculator/very_very_simple_calc.py @@ -1,22 +1,22 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # Very-very simple calc def calc(expr): operator_function = { - '-': lambda x, y: x - y, - '+': lambda x, y: x + y, - '*': lambda x, y: x * y, - '/': lambda x, y: x / y, + "-": lambda x, y: x - y, + "+": lambda x, y: x + y, + "*": lambda x, y: x * y, + "/": lambda x, y: x / y, } nums = list() opers = list() - tokens = list('(' + expr + ')') + tokens = list("(" + expr + ")") while tokens: token = tokens.pop(0) @@ -24,9 +24,9 @@ def calc(expr): if token.isdecimal(): nums.append(float(token)) else: - if token == ')': + if token == ")": oper = opers.pop() - while opers and oper != '(': + while opers and oper != "(": b, a = nums.pop(), nums.pop() f = operator_function[oper] nums.append(f(a, b)) @@ -39,7 +39,7 @@ def calc(expr): return nums[0] -if __name__ == '__main__': +if __name__ == "__main__": print(calc("((1+(2*3))*2)+4")) # 18.0 print(calc("(1*(3/2))+4")) # 5.5 print(calc("(2*(3/2))+4")) # 7.0 diff --git a/cbr_ru/XML_bic/main.py b/cbr_ru/XML_bic/main.py index b41f9166d..77ab64f54 100644 --- a/cbr_ru/XML_bic/main.py +++ b/cbr_ru/XML_bic/main.py @@ -1,21 +1,22 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -if __name__ == '__main__': - from urllib.request import urlopen - from urllib.parse import quote +from urllib.request import urlopen +from urllib.parse import quote - query = 'сбер'.encode('windows-1251') - url = 'http://www.cbr.ru/scripts/XML_bic.asp?name=' + quote(query) - with urlopen(url) as f: - from bs4 import BeautifulSoup - root = BeautifulSoup(f.read(), "xml") +from bs4 import BeautifulSoup - for i, record in enumerate(root.find_all('Record'), 1): - name = record.find('ShortName').text - bic = record.find('Bic').text - print('{}. "{}", {}, {}'.format(i, name, bic, record['DU'])) +query = "сбер".encode("windows-1251") +url = "http://www.cbr.ru/scripts/XML_bic.asp?name=" + quote(query) +with urlopen(url) as f: + root = BeautifulSoup(f.read(), "xml") + + for i, record in enumerate(root.find_all("Record"), 1): + name = record.find("ShortName").text + bic = record.find("Bic").text + + print(f'{i}. "{name}", {bic}, {record["DU"]}') diff --git a/cbr_ru/XML_dynamic/current_usd_euro_rate_with_delta.py b/cbr_ru/XML_dynamic/current_usd_euro_rate_with_delta.py index 6e27bc9f3..c41b74398 100644 --- a/cbr_ru/XML_dynamic/current_usd_euro_rate_with_delta.py +++ b/cbr_ru/XML_dynamic/current_usd_euro_rate_with_delta.py @@ -1,7 +1,13 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" + + +from datetime import date, timedelta +from urllib.request import urlopen + +from bs4 import BeautifulSoup def current_rate(ccy_rq_id): @@ -15,38 +21,33 @@ def current_rate(ccy_rq_id): # Т.к. курс назначается не каждый день, разница может быть в несколько дней, # поэтому на всякий случай берем разницу от текущего дня и на 7 дней назад - from datetime import date, timedelta - date_req1 = (date.today() - timedelta(days=7)).strftime('%d/%m/%Y') - date_req2 = date.today().strftime('%d/%m/%Y') + date_req1 = (date.today() - timedelta(days=7)).strftime("%d/%m/%Y") + date_req2 = date.today().strftime("%d/%m/%Y") # date_req2 = (date.today() + timedelta(days=1)).strftime('%d/%m/%Y') - url = 'http://www.cbr.ru/scripts/XML_dynamic.asp?date_req1={}&date_req2={}&VAL_NM_RQ={}'.format( - date_req1, date_req2, ccy_rq_id - ) + url = f"http://www.cbr.ru/scripts/XML_dynamic.asp?date_req1={date_req1}&date_req2={date_req2}&VAL_NM_RQ={ccy_rq_id}" - from urllib.request import urlopen with urlopen(url) as f: - from bs4 import BeautifulSoup root = BeautifulSoup(f.read(), "xml") # Получаем список курсов - values = root.select('Record > Value') + values = root.select("Record > Value") if len(values) < 2: - raise Exception('Что-то пошло не так. Не хватает значений.\nurl: {}\nroot:\n{}'.format(url, root)) + raise Exception(f"Что-то пошло не так. Не хватает значений.\nurl: {url}\nroot:\n{root}" ) # Вытаскиваем последние два элемента и преобразуем в число values = [values[-2], values[-1]] - values = [float(price.text.replace(',', '.')) for price in values] + values = [float(price.text.replace(",", ".")) for price in values] delta = values[1] - values[0] return values[1], delta -if __name__ == '__main__': +if __name__ == "__main__": # R01235 -- USD, доллары, 840 - price, delta = current_rate('R01235') - print('USD: {} ({}{:.4f})'.format(price, ('+' if delta > 0 else ''), delta)) + price, delta = current_rate("R01235") + print(f"USD: {price} ({('+' if delta > 0 else '')}{delta:.4f})") # R01239 -- EUR, евро, 978 - price, delta = current_rate('R01239') - print('EUR: {} ({}{:.4f})'.format(price, ('+' if delta > 0 else ''), delta)) + price, delta = current_rate("R01239") + print(f"EUR: {price} ({('+' if delta > 0 else '')}{delta:.4f})") diff --git a/cbr_ru/xml_metall/main_old.py b/cbr_ru/xml_metall/main_old.py index 619d5694f..2cf28d1d1 100644 --- a/cbr_ru/xml_metall/main_old.py +++ b/cbr_ru/xml_metall/main_old.py @@ -1,22 +1,29 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -def gen_plot(dates, numbers, label=None, file_name='plot.png', show_plot=True): - # pip install matplotlib - import matplotlib.pyplot as plt +import os.path - from matplotlib import rcParams - rcParams['font.family'] = 'Times New Roman', 'Arial', 'Tahoma' - rcParams['font.fantasy'] = 'Times New Roman' - rcParams['axes.labelsize'] = 'large' - rcParams['savefig.dpi'] = 200 +# pip install matplotlib +import matplotlib.pyplot as plt + +import requests + +from bs4 import BeautifulSoup +from matplotlib import rcParams +from matplotlib.dates import DateFormatter + + +def gen_plot(dates, numbers, label=None, file_name="plot.png", show_plot=True): + rcParams["font.family"] = "Times New Roman", "Arial", "Tahoma" + rcParams["font.fantasy"] = "Times New Roman" + rcParams["axes.labelsize"] = "large" + rcParams["savefig.dpi"] = 200 rcParams["axes.grid"] = True - from matplotlib.dates import DateFormatter - x_axis_date_formatter = DateFormatter('%d/%m/%y') + x_axis_date_formatter = DateFormatter("%d/%m/%y") # ax = plt.subplot(211) ax = plt.subplot(111) @@ -42,40 +49,37 @@ def gen_plot(dates, numbers, label=None, file_name='plot.png', show_plot=True): return file_name -if __name__ == '__main__': +if __name__ == "__main__": from datetime import date, datetime - date_req1 = '01.01.2000' - date_req2 = date.today().strftime('%d.%m.%Y') + date_req1 = "01.01.2000" + date_req2 = date.today().strftime("%d.%m.%Y") - file_name = 'metall_{}-{}.xml'.format(date_req1, date_req2) + file_name = f"metall_{date_req1}-{date_req2}.xml" - # Кеширование. Если файла нет, то скачиваем его. - import os.path + # Кеширование. Если файла нет, то скачиваем его if not os.path.exists(file_name): - url = 'http://www.cbr.ru/scripts/xml_metall.asp?date_req1={}&date_req2={}'.format(date_req1, date_req2) + url = f"http://www.cbr.ru/scripts/xml_metall.asp?date_req1={date_req1}&date_req2={date_req2}" - with open(file_name, 'w') as f: - import requests + with open(file_name, "w") as f: rs = requests.get(url) f.write(rs.text) - with open(file_name, 'rb') as f: - from bs4 import BeautifulSoup + with open(file_name, "rb") as f: root = BeautifulSoup(f, "xml") # Code="1" -- Золото # Code="2" -- Серебро # Code="3" -- Платина # Code="4" -- Палладий - records = root.find_all('Record', attrs=dict(Code="1")) + records = root.find_all("Record", attrs=dict(Code="1")) dates = list() prices = list() for record in records: - date = datetime.strptime(record["Date"], '%d.%m.%Y').date() - price = float(record.findChild("Buy").text.replace(',', '.')) + date = datetime.strptime(record["Date"], "%d.%m.%Y").date() + price = float(record.findChild("Buy").text.replace(",", ".")) dates.append(date) prices.append(price) @@ -84,9 +88,13 @@ def gen_plot(dates, numbers, label=None, file_name='plot.png', show_plot=True): for i, record in enumerate((records[0], records[-1]), 1): # b'231,94246,67\n\n' # record["Code"] - print('{}. {}: {}'.format(i, record["Date"], record.findChild("Buy").text)) - - metall = root.findChild('Metall') - from_date = datetime.strptime(metall['FromDate'], '%Y%m%d').strftime('%d.%m.%Y') - to_date = datetime.strptime(metall['ToDate'], '%Y%m%d').strftime('%d.%m.%Y') - gen_plot(dates, prices, "Стоимость грамма золота в рублях за {} - {}".format(from_date, to_date)) + print(f"{i}. {record['Date']}: {record.findChild('Buy').text}") + + metall = root.findChild("Metall") + from_date = datetime.strptime(metall["FromDate"], "%Y%m%d").strftime("%d.%m.%Y") + to_date = datetime.strptime(metall["ToDate"], "%Y%m%d").strftime("%d.%m.%Y") + gen_plot( + dates, + prices, + f"Стоимость грамма золота в рублях за {from_date} - {to_date}", + ) diff --git a/chardet__examples/hello_world.py b/chardet__examples/hello_world.py index eec9012df..3e128b58a 100644 --- a/chardet__examples/hello_world.py +++ b/chardet__examples/hello_world.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from pathlib import Path @@ -13,7 +13,7 @@ DIR = Path(__file__).resolve().parent -for path in DIR.glob('*.txt'): +for path in DIR.glob("*.txt"): raw_data = path.read_bytes() print(path.name, chardet.detect(raw_data)) diff --git a/check_internet.py b/check_internet.py new file mode 100644 index 000000000..121b3848a --- /dev/null +++ b/check_internet.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +# SOURCE: https://stackoverflow.com/a/33117579/5909792 + + +import socket + + +DEFAULT_HOST: str = "8.8.8.8" +DEFAULT_PORT: int = 53 +DEFAULT_TIMEOUT: int = 3 + + +def check_internet( + host: str = DEFAULT_HOST, + port: int = DEFAULT_PORT, + timeout: int = DEFAULT_TIMEOUT, +) -> bool: + """ + Host: 8.8.8.8 (google-public-dns-a.google.com) + OpenPort: 53/tcp + Service: domain (DNS/TCP) + """ + try: + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.settimeout(timeout) + s.connect((host, port)) + return True + except socket.error as e: + print(f"Error: {e}") + return False + + +if __name__ == "__main__": + import argparse + import time + + parser = argparse.ArgumentParser( + description="Check internet", + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + ) + parser.add_argument( + "--host", + default=DEFAULT_HOST, + help="Host", + ) + parser.add_argument( + "--port", + type=int, + default=DEFAULT_PORT, + help="Port", + ) + parser.add_argument( + "--network_timeout_secs", + type=int, + default=DEFAULT_TIMEOUT, + help="Network timeout in seconds", + ) + parser.add_argument( + "--attempts", + type=int, + default=3, + help="Attempts to check", + ) + parser.add_argument( + "--delay_between_attempts_secs", + type=int, + default=30, + help="Delay between attempts in seconds", + ) + + args = parser.parse_args() + + for attempt in range(args.attempts): + prefix: str = f"[{attempt + 1}] " + + print(f"{prefix}Check internet") + result: bool = check_internet( + host=args.host, + port=args.port, + timeout=args.network_timeout_secs, + ) + print(f"{prefix}Result: {result}") + if result: + break + + delay: int = args.delay_between_attempts_secs + + print(f"{prefix}The next attempt through {delay} seconds\n") + time.sleep(delay) diff --git a/class_with_my_instances_property.py b/class_with_my_instances_property.py index 0841a0399..e79699e6a 100644 --- a/class_with_my_instances_property.py +++ b/class_with_my_instances_property.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" class MyMeta(type): @@ -29,4 +29,5 @@ class MyClass(object, metaclass=MyMeta): print(MyClass.instances) # [<__main__.MyClass object at 0x00514AF0>] b = MyClass() c = MyClass() -print(MyClass.instances) # [<__main__.MyClass object at 0x00514AF0>, <__main__.MyClass object at 0x00736DF0>, <__main__.MyClass object at 0x00736C30>] +print(MyClass.instances) +# [<__main__.MyClass object at 0x00514AF0>, <__main__.MyClass object at 0x00736DF0>, <__main__.MyClass object at 0x00736C30>] diff --git a/clipboard_echo_with_clip.py b/clipboard_echo_with_clip.py index 69eea8d3f..b541a2017 100644 --- a/clipboard_echo_with_clip.py +++ b/clipboard_echo_with_clip.py @@ -1,13 +1,14 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import os from tkinter import Tk # Python 3 -os.system('echo 123|clip') + +os.system("echo 123|clip") print(repr(Tk().clipboard_get())) # '123\n' diff --git a/closure__examples/counter.py b/closure__examples/counter.py index b26f68708..cdfe22053 100644 --- a/closure__examples/counter.py +++ b/closure__examples/counter.py @@ -1,13 +1,13 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://stackoverflow.com/a/141426 -def make_counter(start=0, step=1): +def make_counter(start: int | float = 0, step: int | float = 1): i = start def counter(): # counter() is a closure @@ -19,10 +19,10 @@ def counter(): # counter() is a closure return counter -def make_counter_adder(start=0): +def make_counter_adder(start: int | float = 0): i = start - def counter(step=0): # counter() is a closure + def counter(step: int | float = 0): # counter() is a closure nonlocal i i += step @@ -31,7 +31,7 @@ def counter(step=0): # counter() is a closure return counter -if __name__ == '__main__': +if __name__ == "__main__": c = make_counter() print(c(), c(), c()) # 1 2 3 diff --git a/closure__examples/hello_world.py b/closure__examples/hello_world.py index 84781ecce..f608cfadc 100644 --- a/closure__examples/hello_world.py +++ b/closure__examples/hello_world.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://ru.wikipedia.org/wiki/Замыкание_(программирование) diff --git a/codewars/Error_correction_1__Hamming_Code.py b/codewars/Error_correction_1__Hamming_Code.py index b2619e5f8..8e169fa12 100644 --- a/codewars/Error_correction_1__Hamming_Code.py +++ b/codewars/Error_correction_1__Hamming_Code.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://www.codewars.com/kata/5ef9ca8b76be6d001d5e1c3e/train/python @@ -10,7 +10,7 @@ def chunks(l, n): """Yield successive n-sized chunks from l.""" for i in range(0, len(l), n): - yield l[i: i + n] + yield l[i : i + n] # Task 1: Encode function @@ -28,9 +28,8 @@ def chunks(l, n): # --> 000111111000111000000000 000111111000000111000111 000111111111111000000111 // tripled # --> "000111111000111000000000000111111000000111000111000111111111111000000111" // concatenated def encode(text: str) -> str: - return ''.join( - ''.join(b * 3 for b in f"{ord(c):08b}") # Tripled bits - for c in text + return "".join( + "".join(b * 3 for b in f"{ord(c):08b}") for c in text # Tripled bits ) @@ -62,28 +61,31 @@ def decode(bits: str) -> str: sums = sum(map(int, tripled_bits)) # Example: 110 or 111 -> 1 and 000 -> 0 or 001 -> 0 - bit_items.append('1' if sums == 2 or sums == 3 else '0') + bit_items.append("1" if sums == 2 or sums == 3 else "0") - binary = ''.join(bit_items) + binary = "".join(bit_items) items = [] for byte in chunks(binary, 8): items.append(chr(int(byte, 2))) - return ''.join(items) + return "".join(items) -if __name__ == '__main__': - text = 'hey' +if __name__ == "__main__": + text = "hey" encoded = encode(text) print(encoded) - assert encoded == '000111111000111000000000000111111000000111000111000111111111111000000111' + assert ( + encoded + == "000111111000111000000000000111111000000111000111000111111111111000000111" + ) decoded = decode(encoded) print(decoded) assert text == decoded - invalid_encoded = '100111111000111001000010000111111000000111001111000111110110111000010111' + invalid_encoded = "100111111000111001000010000111111000000111001111000111110110111000010111" decoded = decode(invalid_encoded) print(decoded) assert text == decoded diff --git a/codewars/parse_molecule__Molecule to atoms.py b/codewars/parse_molecule__Molecule to atoms.py index 15cea451d..3484bc9eb 100644 --- a/codewars/parse_molecule__Molecule to atoms.py +++ b/codewars/parse_molecule__Molecule to atoms.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """ @@ -9,6 +9,9 @@ """ +import re +from collections import defaultdict + def get_sub_formula(formula): """ @@ -17,8 +20,7 @@ def get_sub_formula(formula): """ - import re - match = re.search('(\([a-zA-Z\d]+\))(\d*)', formula) + match = re.search(r"(\([a-zA-Z\d]+\))(\d*)", formula) if not match: return @@ -35,8 +37,7 @@ def split_by_full_tokens(formula): """ - import re - return re.findall('([A-Z][a-z]*\d*)', formula) + return re.findall(r"([A-Z][a-z]*\d*)", formula) def split_by_tokens(formula): @@ -45,8 +46,7 @@ def split_by_tokens(formula): """ - import re - return re.findall('([A-Z][a-z]*|\d+)', formula) + return re.findall(r"([A-Z][a-z]*|\d+)", formula) def complete_element_atom_number_with_one_atom(formula): @@ -56,15 +56,15 @@ def complete_element_atom_number_with_one_atom(formula): """ tokens = split_by_full_tokens(formula) - tokens = [token if token[-1] in '0123456789' else token + '1' for token in tokens] - return ''.join(tokens) + tokens = [token if token[-1] in "0123456789" else token + "1" for token in tokens] + return "".join(tokens) def parse_molecule(formula): - formula = formula.replace('[', '(').replace(']', ')') - formula = formula.replace('{', '(').replace('}', ')') + formula = formula.replace("[", "(").replace("]", ")") + formula = formula.replace("{", "(").replace("}", ")") - while '(' in formula: + while "(" in formula: match = get_sub_formula(formula) if not match: break @@ -78,25 +78,26 @@ def parse_molecule(formula): tokens = split_by_tokens(new_sub_formula) # ['K', '4', 'O', '2'] * 2 -> ['K', '8', 'O', '4'] - tokens = [str(int(token) * multiplier) if token.isdigit() else token for token in tokens] - new_sub_formula = ''.join(tokens) + tokens = [ + str(int(token) * multiplier) if token.isdigit() else token + for token in tokens + ] + new_sub_formula = "".join(tokens) formula = formula.replace(full_sub_formula, new_sub_formula) formula = complete_element_atom_number_with_one_atom(formula) tokens = split_by_tokens(formula) - from collections import defaultdict element_by_number_atom = defaultdict(int) - for i in range(0, len(tokens), 2): element_by_number_atom[tokens[i]] += int(tokens[i + 1]) return element_by_number_atom -if __name__ == '__main__': - def equals_atomically(obj1, obj2): +if __name__ == "__main__": + def equals_atomically(obj1, obj2) -> bool: if len(obj1) != len(obj2): return False for k in obj1: @@ -104,6 +105,12 @@ def equals_atomically(obj1, obj2): return False return True - assert equals_atomically(parse_molecule("H2O"), {'H': 2, 'O': 1}), "Should parse water" - assert equals_atomically(parse_molecule("Mg(OH)2"), {'Mg': 1, 'O' : 2, 'H': 2}), "Should parse magnesium hydroxide: Mg(OH)2" - assert equals_atomically(parse_molecule("K4[ON(SO3)2]2"), {'K': 4, 'O': 14, 'N': 2, 'S': 4}), "Should parse Fremy's salt: K4[ON(SO3)2]2" + assert equals_atomically( + parse_molecule("H2O"), {"H": 2, "O": 1} + ), "Should parse water" + assert equals_atomically( + parse_molecule("Mg(OH)2"), {"Mg": 1, "O": 2, "H": 2} + ), "Should parse magnesium hydroxide: Mg(OH)2" + assert equals_atomically( + parse_molecule("K4[ON(SO3)2]2"), {"K": 4, "O": 14, "N": 2, "S": 4} + ), "Should parse Fremy's salt: K4[ON(SO3)2]2" diff --git a/codingame/easy/ASCII Art.py b/codingame/easy/ASCII Art.py index 31dfb87ce..c0a2e55b0 100644 --- a/codingame/easy/ASCII Art.py +++ b/codingame/easy/ASCII Art.py @@ -1,14 +1,13 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import sys -import math - from string import ascii_lowercase + # Auto-generated code below aims at helping you parse # the standard input according to the problem statement. @@ -19,27 +18,27 @@ print(l, file=sys.stderr) print(h, file=sys.stderr) print(t, file=sys.stderr) -print('\n', file=sys.stderr) +print("\n", file=sys.stderr) ascii_rows = [input() for i in range(h)] -print('\n'.join(ascii_rows), file=sys.stderr) -print('\n', file=sys.stderr) +print("\n".join(ascii_rows), file=sys.stderr) +print("\n", file=sys.stderr) -letter_list = list(ascii_lowercase) + ['?'] +letter_list = list(ascii_lowercase) + ["?"] result_rows = list() for row in ascii_rows: - new_row = '' + new_row = "" for c in t.lower(): if c not in letter_list: - c = '?' + c = "?" index = letter_list.index(c) pos = index * l - new_row += row[pos: pos + l] + new_row += row[pos : pos + l] result_rows.append(new_row) -print('\n'.join(result_rows)) +print("\n".join(result_rows)) diff --git a/codingame/easy/Chuck Norris.py b/codingame/easy/Chuck Norris.py index fd97082da..3293d6034 100644 --- a/codingame/easy/Chuck Norris.py +++ b/codingame/easy/Chuck Norris.py @@ -1,11 +1,11 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import sys -import math + # Auto-generated code below aims at helping you parse # the standard input according to the problem statement. @@ -21,25 +21,27 @@ def Chuck_Norris_encode(text): last_c = text[0] count = 1 - result = '00 ' if last_c == '0' else '0 ' + result = "00 " if last_c == "0" else "0 " for i in range(len(text))[1:]: c = text[i] if c != last_c: - result += '0' * count + ' ' + result += "0" * count + " " count = 1 - result += '00 ' if c == '0' else '0 ' + result += "00 " if c == "0" else "0 " else: count += 1 last_c = c - result += '0' * count + result += "0" * count return result -# Бинарная строка должна содержать 7 символов, а bin в может вернуть -# строку меньшего размера, убрав ненужные нули -text = ''.join([bin(ord(c))[2:].rjust(7, '0') for c in message]) -print(Chuck_Norris_encode(text)) + +if __name__ == '__main__': + # Бинарная строка должна содержать 7 символов, а bin в может вернуть + # строку меньшего размера, убрав ненужные нули + text = "".join([bin(ord(c))[2:].rjust(7, "0") for c in message]) + print(Chuck_Norris_encode(text)) diff --git a/codingame/easy/Horse-racing Duals.py b/codingame/easy/Horse-racing Duals.py index 5b47f8166..d792a6a9a 100644 --- a/codingame/easy/Horse-racing Duals.py +++ b/codingame/easy/Horse-racing Duals.py @@ -1,12 +1,9 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -import sys -import math - # Auto-generated code below aims at helping you parse # the standard input according to the problem statement. @@ -25,4 +22,3 @@ last = i print(min_strength) - diff --git a/codingame/easy/MIME Type.py b/codingame/easy/MIME Type.py index f0327f613..9a4e15de7 100644 --- a/codingame/easy/MIME Type.py +++ b/codingame/easy/MIME Type.py @@ -1,11 +1,11 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import sys -import math + # Auto-generated code below aims at helping you parse # the standard input according to the problem statement. @@ -36,8 +36,8 @@ for file in files: try: - i = file.rindex('.') - ext = file[i + 1:].lower() + i = file.rindex(".") + ext = file[i + 1 :].lower() print(ext_mime_dict[ext]) except: print("UNKNOWN") diff --git a/codingame/easy/Mars Lander - Episode 1.py b/codingame/easy/Mars Lander - Episode 1.py index 534852efe..049b90086 100644 --- a/codingame/easy/Mars Lander - Episode 1.py +++ b/codingame/easy/Mars Lander - Episode 1.py @@ -1,11 +1,11 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import sys -import math + # Auto-generated code below aims at helping you parse # the standard input according to the problem statement. @@ -26,12 +26,16 @@ # rotate: the rotation angle in degrees (-90 to 90). # power: the thrust power (0 to 4). x, y, h_speed, v_speed, fuel, rotate, power = [int(i) for i in input().split()] - print("x={}, y={}, h_speed={}, v_speed={}, fuel={}, rotate={}, power={}".format(x, y, h_speed, v_speed, fuel, rotate, power), file=sys.stderr) + print( + f"x={x}, y={y}, h_speed={h_speed}, v_speed={v_speed}, fuel={fuel}, rotate={rotate}, power={power}", + file=sys.stderr, + ) # Write an action using print # To debug: print("Debug messages...", file=sys.stderr) - # 2 integers: rotate power. rotate is the desired rotation angle (should be 0 for level 1), power is the desired thrust power (0 to 4). + # 2 integers: rotate power. rotate is the desired rotation angle + # (should be 0 for level 1), power is the desired thrust power (0 to 4). if v_speed < -40: print("0 4") else: diff --git a/codingame/easy/Onboarding.py b/codingame/easy/Onboarding.py index c0cd45016..b3d3920c7 100644 --- a/codingame/easy/Onboarding.py +++ b/codingame/easy/Onboarding.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # game loop diff --git a/codingame/easy/Power of Thor - Episode 1.py b/codingame/easy/Power of Thor - Episode 1.py index df07b9e88..35f920cbf 100644 --- a/codingame/easy/Power of Thor - Episode 1.py +++ b/codingame/easy/Power of Thor - Episode 1.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import sys @@ -19,15 +19,15 @@ light_x, light_y, initial_tx, initial_ty = [int(i) for i in input().split()] DIRECTION_DICT = { - (-1, 0): 'W', - (0, -1): 'N', - (1, 0): 'E', - (0, 1): 'S', - - (-1, -1): 'NW', - (1, -1): 'NE', - (1, 1): 'SE', - (-1, 1): 'SW', + (-1, 0): "W", + (0, -1): "N", + (1, 0): "E", + (0, 1): "S", + + (-1, -1): "NW", + (1, -1): "NE", + (1, 1): "SE", + (-1, 1): "SW", } x, y = initial_tx, initial_ty @@ -36,7 +36,8 @@ # game loop while True: - remaining_turns = int(input()) # The remaining amount of turns Thor can move. Do not remove this line. + # The remaining amount of turns Thor can move. Do not remove this line. + remaining_turns = int(input()) # Write an action using print # To debug: print("Debug messages...", file=sys.stderr) diff --git a/codingame/easy/Temperatures.py b/codingame/easy/Temperatures.py index 94ec65529..aa3f60402 100644 --- a/codingame/easy/Temperatures.py +++ b/codingame/easy/Temperatures.py @@ -1,11 +1,11 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import sys -import math + # Auto-generated code below aims at helping you parse # the standard input according to the problem statement. @@ -14,7 +14,7 @@ temps = input() # the n temperatures expressed as integers ranging from -273 to 5526 temps = [int(i) for i in temps.strip().split()] -print('temps:', temps, file=sys.stderr) +print("temps:", temps, file=sys.stderr) # Проверка того, что 0 уже есть в списке или список температор пустой if 0 in temps or not temps: diff --git a/codingame/easy/The Descent.py b/codingame/easy/The Descent.py index 8b69326cd..b78b1c7ca 100644 --- a/codingame/easy/The Descent.py +++ b/codingame/easy/The Descent.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # game loop diff --git a/codingame/easy/defibrillators.py b/codingame/easy/defibrillators.py index 033d88267..c3feb2513 100644 --- a/codingame/easy/defibrillators.py +++ b/codingame/easy/defibrillators.py @@ -1,16 +1,16 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -import sys import math + # Auto-generated code below aims at helping you parse # the standard input according to the problem statement. -str_to_float = lambda x: float(x.replace(',', '.')) +str_to_float = lambda x: float(x.replace(",", ".")) lon = str_to_float(input()) lat = str_to_float(input()) @@ -22,7 +22,7 @@ n = int(input()) for i in range(n): - defib = input().split(';') + defib = input().split(";") name = defib[1] lon_defib = str_to_float(defib[-2]) diff --git a/codingame/medium/Bender - Episode 1.py b/codingame/medium/Bender - Episode 1.py index 835682990..bd97edcc7 100644 --- a/codingame/medium/Bender - Episode 1.py +++ b/codingame/medium/Bender - Episode 1.py @@ -1,11 +1,11 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import sys -import math + # Auto-generated code below aims at helping you parse # the standard input according to the problem statement. @@ -19,38 +19,38 @@ city_map.append(list(row)) -print('city_map:', city_map, file=sys.stderr) +print("city_map:", city_map, file=sys.stderr) # Write an action using print # To debug: print("Debug messages...", file=sys.stderr) DIRECTION_DICT = { - 'SOUTH': (1, 0), - 'EAST': (0, 1), - 'NORTH': (-1, 0), - 'WEST': (0, -1), - - (1, 0): 'SOUTH', - (0, 1): 'EAST', - (-1, 0): 'NORTH', - (0, -1): 'WEST', - - 'S': 'SOUTH', - 'E': 'EAST', - 'N': 'NORTH', - 'W': 'WEST', + "SOUTH": (1, 0), + "EAST": (0, 1), + "NORTH": (-1, 0), + "WEST": (0, -1), + + (1, 0): "SOUTH", + (0, 1): "EAST", + (-1, 0): "NORTH", + (0, -1): "WEST", + + "S": "SOUTH", + "E": "EAST", + "N": "NORTH", + "W": "WEST", } DEBUG = False -def log(*args, **kwargs): +def log(*args, **kwargs) -> None: DEBUG and print(*args, **kwargs) class Bender: - def __init__(self, city_map): + def __init__(self, city_map) -> None: self.objects_map = dict() # Соберем все объекты на карте в словарь, исключаются пустые места и стенки @@ -59,18 +59,18 @@ def __init__(self, city_map): for j in range(len(row)): cell = city_map[i][j] - if cell == '@': + if cell == "@": self.objects_map[cell] = i, j # Под Бендером пустая клетка - self.objects_map[i, j] = ' ' + self.objects_map[i, j] = " " else: self.objects_map[i, j] = cell - log('Objects map:', self.objects_map) - log('Bender pos: {}x{}'.format(self.pos_i, self.pos_j)) + log("Objects map:", self.objects_map) + log(f"Bender pos: {self.pos_i}x{self.pos_j}") - self.direction_name = 'SOUTH' + self.direction_name = "SOUTH" self.invert = False self.breaker = False @@ -81,7 +81,7 @@ def __init__(self, city_map): self.steps = list() # self.steps_log_list = list() - def _set_pos_i(self, value): + def _set_pos_i(self, value) -> None: # Устанавливаем i, и старое j self.pos = value, self.pos[1] @@ -90,7 +90,7 @@ def _get_pos_i(self): pos_i = property(_get_pos_i, _set_pos_i) - def _set_pos_j(self, value): + def _set_pos_j(self, value) -> None: # Устанавливаем старое i и j self.pos = self.pos[0], value @@ -99,11 +99,11 @@ def _get_pos_j(self): pos_j = property(_get_pos_j, _set_pos_j) - def _set_pos(self, value): - self.objects_map['@'] = value + def _set_pos(self, value) -> None: + self.objects_map["@"] = value def _get_pos(self): - return self.objects_map['@'] + return self.objects_map["@"] pos = property(_get_pos, _set_pos) @@ -116,17 +116,17 @@ def city_map(self): # Добавляем Бендера i, j = self.pos - map[i][j] = '@' + map[i][j] = "@" return map - def print_city_map(self): + def print_city_map(self) -> None: log() for row in self.city_map(): - log(*row, sep='') + log(*row, sep="") log() - def _set_direction_name(self, name): + def _set_direction_name(self, name) -> None: self._direction_name = name def _get_direction_name(self): @@ -144,40 +144,40 @@ def get_direction(direction_name): def look_around(self): return { - 'SOUTH': self.objects_map[self.pos_i + 1, self.pos_j], - 'EAST': self.objects_map[self.pos_i, self.pos_j + 1], - 'NORTH': self.objects_map[self.pos_i - 1, self.pos_j], - 'WEST': self.objects_map[self.pos_i, self.pos_j - 1], + "SOUTH": self.objects_map[self.pos_i + 1, self.pos_j], + "EAST": self.objects_map[self.pos_i, self.pos_j + 1], + "NORTH": self.objects_map[self.pos_i - 1, self.pos_j], + "WEST": self.objects_map[self.pos_i, self.pos_j - 1], } def step(self): self.print_city_map() - log('Objects map:', self.objects_map) + log("Objects map:", self.objects_map) look_around = self.look_around() - log("look_around: {} {}x{}".format(look_around, self.pos_i, self.pos_j)) - log('Current direction:', self.direction_name) + log(f"look_around: {look_around} {self.pos_i}x{self.pos_j}") + log("Current direction:", self.direction_name) # Приоритеты смены движения при встрече с препятствием: # invert=False: SOUTH -> EAST -> NORTH -> WEST # invert=True: WEST -> NORTH -> EAST -> SOUTH - priorities = ['SOUTH', 'EAST', 'NORTH', 'WEST'] + priorities = ["SOUTH", "EAST", "NORTH", "WEST"] if self.invert: priorities.reverse() - log('Current priorities:', priorities) + log("Current priorities:", priorities) priorities.remove(self.direction_name) while look_around: next_cell = look_around.pop(self.direction_name) - log('while look_around: {} "{}" {}'.format(self.direction_name, next_cell, look_around)) + log(f'while look_around: {self.direction_name} "{next_cell}" {look_around}') # Если следующий шаг не в препятствие, или препятствие и Бендер в режиме breaker - if next_cell not in ['#', 'X'] or (next_cell == 'X' and self.breaker): + if next_cell not in ["#", "X"] or (next_cell == "X" and self.breaker): break new_direction_name = priorities.pop(0) - log('look_around change direction: {} -> {}.'.format(self.direction_name, new_direction_name)) + log(f"look_around change direction: {self.direction_name} -> {new_direction_name}.") self.direction_name = new_direction_name di, dj = self.get_direction(self.direction_name) @@ -186,8 +186,8 @@ def step(self): self.pos_j += dj # Ломаем препятствие - if next_cell == 'X' and self.breaker: - self.objects_map[self.pos] = ' ' + if next_cell == "X" and self.breaker: + self.objects_map[self.pos] = " " current_cell = self.objects_map[self.pos] @@ -195,24 +195,26 @@ def step(self): self.steps.append(step) # Проверяем, что наступили на изменение шага - if current_cell in ['S', 'E', 'N', 'W']: + if current_cell in ["S", "E", "N", "W"]: # Указываем следующее направление движения self.direction_name = DIRECTION_DICT[current_cell] # Если попали на инвертирование приоритетов - elif current_cell == 'I': + elif current_cell == "I": self.invert = not self.invert # Если нашли пиво - elif current_cell == 'B': + elif current_cell == "B": self.breaker = not self.breaker - log('breaker:', self.breaker) + log("breaker:", self.breaker) # Если нашли телепорт - elif current_cell == 'T': + elif current_cell == "T": # Найдем положение другого телепорта - other_teleport_pos = [k for k, v in self.objects_map.items() if v == 'T' and k != self.pos][0] - log('teleport: {} -> {}'.format(self.pos, other_teleport_pos)) + other_teleport_pos = [ + k for k, v in self.objects_map.items() if v == "T" and k != self.pos + ][0] + log(f"teleport: {self.pos} -> {other_teleport_pos}") # Телепортируемся self.pos = other_teleport_pos @@ -225,7 +227,9 @@ def step(self): # hack for LOOP max_step_number = 200 -steps = ['LOOP'] +steps = [ + "LOOP", +] # Ходим, пока не встретим символ '$' while True: @@ -233,8 +237,8 @@ def step(self): if max_step_number <= 0: break - if bender.step() == '$': + if bender.step() == "$": steps = bender.steps break -print('\n'.join(steps)) +print("\n".join(steps)) diff --git a/codingame/medium/There is no Spoon - Episode 1.py b/codingame/medium/There is no Spoon - Episode 1.py index 88e32752e..1e69eeb9e 100644 --- a/codingame/medium/There is no Spoon - Episode 1.py +++ b/codingame/medium/There is no Spoon - Episode 1.py @@ -1,11 +1,11 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import sys -import math + # Don't let the machines win. You are humanity's last hope... @@ -18,17 +18,17 @@ for i in range(height): line = input() # width characters, each either 0 or . - print("line: {}".format(line), file=sys.stderr) + print(f"line: {line}", file=sys.stderr) matrix.append(list(line)) -print("matrix:\n{}".format(matrix), file=sys.stderr) +print(f"matrix:\n{matrix}", file=sys.stderr) # Ищем соседей справа def right_neighbor(matrix, i, j): for _j in range(j + 1, width): - if matrix[i][_j] == '0': + if matrix[i][_j] == "0": return _j, i return -1, -1 @@ -37,7 +37,7 @@ def right_neighbor(matrix, i, j): # Ищем соседей снизу def bottom_neighbor(matrix, i, j): for _i in range(i + 1, height): - if matrix[_i][j] == '0': + if matrix[_i][j] == "0": return j, _i return -1, -1 @@ -45,7 +45,7 @@ def bottom_neighbor(matrix, i, j): for i in range(height): for j in range(width): - if matrix[i][j] == '.': + if matrix[i][j] == ".": continue x2, y2 = right_neighbor(matrix, i, j) diff --git a/collector_bash_im/collector_bash_im.py b/collector_bash_im/collector_bash_im.py index c82b373d8..eddb6ae3c 100644 --- a/collector_bash_im/collector_bash_im.py +++ b/collector_bash_im/collector_bash_im.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """Скрипт собирает цитаты сайта bash.im""" @@ -9,26 +9,21 @@ import logging import sys -import time -import traceback - -from datetime import datetime - import requests -import sqlalchemy.exc +from bs4 import BeautifulSoup from sqlalchemy import create_engine, Table, Column, Integer, String, MetaData, DateTime from sqlalchemy.orm import mapper, sessionmaker -from bs4 import BeautifulSoup - -def get_logger(name, file='log.txt', encoding='utf8'): +def get_logger(name, file="log.txt", encoding="utf8"): log = logging.getLogger(name) log.setLevel(logging.DEBUG) - formatter = logging.Formatter('[%(asctime)s] %(filename)s[LINE:%(lineno)d] %(levelname)-8s %(message)s') + formatter = logging.Formatter( + "[%(asctime)s] %(filename)s[LINE:%(lineno)d] %(levelname)-8s %(message)s" + ) fh = logging.FileHandler(file, encoding=encoding) fh.setLevel(logging.DEBUG) @@ -44,41 +39,45 @@ def get_logger(name, file='log.txt', encoding='utf8'): return log -logger = get_logger('collector_bash_im') +logger = get_logger("collector_bash_im") class Quote: - def __init__(self, id, date, rating, text): + def __init__(self, id, date, rating, text) -> None: self.id = id self.date = date self.rating = rating self.text = text - def __repr__(self): - return "".format(len(self.text), **self.__dict__) + def __repr__(self) -> str: + return ( + "".format(len(self.text), **self.__dict__) + ) @property def url(self): - return 'http://bash.im/quote/' + str(self.id) + return "http://bash.im/quote/" + str(self.id) def get_session_factory(): # Создаем базу, включаем логирование и автообновление подключения каждые 2 часа (7200 секунд) engine = create_engine( # 'sqlite:///:memory:', - 'sqlite:///quotes.db', + "sqlite:///quotes.db", # echo=True, - pool_recycle=7200 + pool_recycle=7200, ) metadata = MetaData() - quotes_table = Table('Quote', metadata, - Column('id', Integer, primary_key=True), - Column('date', DateTime), - Column('rating', Integer), - Column('text', String), + quotes_table = Table( + "Quote", + metadata, + Column("id", Integer, primary_key=True), + Column("date", DateTime), + Column("rating", Integer), + Column("text", String), ) mapper(Quote, quotes_table) @@ -93,14 +92,14 @@ def get_session_factory(): query = session.query(Quote) -if __name__ == '__main__': - rs = requests.get('http://bash.im') +if __name__ == "__main__": + rs = requests.get("http://bash.im") soup = BeautifulSoup(rs.content, "lxml") # print(soup) i = 0 for quote in soup.find_all(attrs={"class": "quote"}): text = quote.find(attrs={"class": "text"}) - if text is None: + if not text: continue i += 1 diff --git a/colorama__examples/hello_world.py b/colorama__examples/hello_world.py new file mode 100644 index 000000000..a224b7fd2 --- /dev/null +++ b/colorama__examples/hello_world.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +# pip install colorama +from colorama import Fore, Back, Style + + +print(Fore.RED + "some red text") +print(Back.GREEN + "and with a green background") +print(Style.DIM + "and in dim text") +print(Style.RESET_ALL) +print("back to normal now") diff --git a/combinatorics.py b/combinatorics.py index f3df6512a..9941b8cdf 100644 --- a/combinatorics.py +++ b/combinatorics.py @@ -1,19 +1,23 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -"""У нас есть список сил и возможно комбинировать одновременно только две разные силы, -причем повторов быть не должно -- ('Огонь', 'Молния') и ('Молния', 'Огонь') -- повторы.""" +""" +У нас есть список сил и возможно комбинировать одновременно только две разные силы, +причем повторов быть не должно -- ('Огонь', 'Молния') и ('Молния', 'Огонь') -- повторы. +""" + import itertools + # Комбинации сил, максимум за раз могут две учавствовать, плюс возможны только разные -powers = ['Огонь', 'Молния', 'Лед', 'Воздух'] +powers = ["Огонь", "Молния", "Лед", "Воздух"] # Все комбинации без повторов # Если нужны комбинации с повторами, используется itertools.product(powers, repeat=2) all_combo = itertools.combinations(powers, 2) for i, combo in enumerate(all_combo, 1): - print('{}. {} и {}'.format(i, *combo)) + print("{}. {} и {}".format(i, *combo)) diff --git a/combinatorics__generate__repeat__itertools__from_000_to_111.py b/combinatorics__generate__repeat__itertools__from_000_to_111.py index 3817668b3..261ab88f8 100644 --- a/combinatorics__generate__repeat__itertools__from_000_to_111.py +++ b/combinatorics__generate__repeat__itertools__from_000_to_111.py @@ -1,12 +1,14 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import itertools -items = [''.join(i) for i in itertools.product('01', repeat=3)] + + +items = ["".join(i) for i in itertools.product("01", repeat=3)] print(items) # ['000', '001', '010', '011', '100', '101', '110', '111'] -items = [i for i in items if '11' not in i] +items = [i for i in items if "11" not in i] print(items) # ['000', '001', '010', '100', '101'] diff --git a/complex.py b/complex.py index 7342e2fe5..2b7d5b283 100644 --- a/complex.py +++ b/complex.py @@ -1,7 +1,7 @@ -__author__ = 'ipetrash' +__author__ = "ipetrash" -if __name__ == '__main__': +if __name__ == "__main__": # Комплексные числа (complex) # В Python встроены также и комплексные числа: @@ -28,4 +28,4 @@ print(abs(3 + 4j)) # Модуль комплексного числа print(pow(3 + 4j, 2)) # Возведение в степень - # Также для работы с комплексными числами используется также модуль cmath . \ No newline at end of file + # Также для работы с комплексными числами используется также модуль cmath . diff --git a/compress__decompress__bz2__bzip2.py b/compress__decompress__bz2__bzip2.py index 7f003256d..65130fa0a 100644 --- a/compress__decompress__bz2__bzip2.py +++ b/compress__decompress__bz2__bzip2.py @@ -1,17 +1,19 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://docs.python.org/3/library/bz2.html#one-shot-de-compression +import bz2 + + s_in = "HelloWorld!" * 1000000 -s_in = bytes(s_in, encoding='utf-8') +s_in = bytes(s_in, encoding="utf-8") print(len(s_in)) # 11000000 -import bz2 s_out = bz2.compress(s_in) assert bz2.decompress(s_out) == s_in print(len(s_out)) # 927 diff --git a/compress__decompress__gzip.py b/compress__decompress__gzip.py index 063edf2da..957a8cac4 100644 --- a/compress__decompress__gzip.py +++ b/compress__decompress__gzip.py @@ -1,14 +1,16 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" + + +import gzip s_in = "HelloWorld!" * 1000000 -s_in = bytes(s_in, encoding='utf-8') +s_in = bytes(s_in, encoding="utf-8") print(len(s_in)) # 11000000 -import gzip s_out = gzip.compress(s_in) assert gzip.decompress(s_out) == s_in print(len(s_out)) # 21396 diff --git a/compress__decompress__lzma.py b/compress__decompress__lzma.py index d02865aa6..9e41c115d 100644 --- a/compress__decompress__lzma.py +++ b/compress__decompress__lzma.py @@ -1,17 +1,19 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://docs.python.org/3/library/lzma.html#lzma.compress +import lzma + + s_in = "HelloWorld!" * 1000000 -s_in = bytes(s_in, encoding='utf-8') +s_in = bytes(s_in, encoding="utf-8") print(len(s_in)) # 11000000 -import lzma s_out = lzma.compress(s_in) assert lzma.decompress(s_out) == s_in print(len(s_out)) # 1744 diff --git a/compress__decompress__zlib.py b/compress__decompress__zlib.py index 938ccfc8e..949315103 100644 --- a/compress__decompress__zlib.py +++ b/compress__decompress__zlib.py @@ -1,14 +1,16 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" + + +import zlib s_in = "HelloWorld!" * 1000000 -s_in = bytes(s_in, encoding='utf-8') +s_in = bytes(s_in, encoding="utf-8") print(len(s_in)) # 11000000 -import zlib s_out = zlib.compress(s_in) assert zlib.decompress(s_out) == s_in print(len(s_out)) # 21384 diff --git a/concurrency_in_python__threading_processing/about_1__single_thread.py b/concurrency_in_python__threading_processing/about_1__single_thread.py index f5a2bcb38..b03abd482 100644 --- a/concurrency_in_python__threading_processing/about_1__single_thread.py +++ b/concurrency_in_python__threading_processing/about_1__single_thread.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://towardsdatascience.com/concurrency-in-python-e770c878ab53 @@ -16,14 +16,14 @@ # A CPU heavy calculation, just as an example. This can be anything you like -def heavy(n, myid): +def heavy(n, myid) -> None: for x in range(1, n): for y in range(1, n): - x ** y + x**y print(myid, "is done") -def sequential(n): +def sequential(n) -> None: for i in range(n): heavy(N, i) diff --git a/concurrency_in_python__threading_processing/about_2__multithreading.py b/concurrency_in_python__threading_processing/about_2__multithreading.py index 0de6b23b7..4e68c7285 100644 --- a/concurrency_in_python__threading_processing/about_2__multithreading.py +++ b/concurrency_in_python__threading_processing/about_2__multithreading.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://towardsdatascience.com/concurrency-in-python-e770c878ab53 @@ -14,7 +14,7 @@ from about_1__single_thread import heavy, WORKERS, N -def threaded(n): +def threaded(n) -> None: threads = [] for i in range(n): @@ -26,7 +26,7 @@ def threaded(n): t.join() -if __name__ == '__main__': +if __name__ == "__main__": start = time.perf_counter() threaded(WORKERS) print(f"Elapsed {time.perf_counter() - start:.2f} secs") diff --git a/concurrency_in_python__threading_processing/about_3__multiprocessing.py b/concurrency_in_python__threading_processing/about_3__multiprocessing.py index 122e4c09a..6e71b2722 100644 --- a/concurrency_in_python__threading_processing/about_3__multiprocessing.py +++ b/concurrency_in_python__threading_processing/about_3__multiprocessing.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://towardsdatascience.com/concurrency-in-python-e770c878ab53 @@ -14,7 +14,7 @@ from about_1__single_thread import heavy, WORKERS, N -def multiproc(n): +def multiproc(n) -> None: processes = [] for i in range(n): @@ -26,9 +26,8 @@ def multiproc(n): p.join() -if __name__ == '__main__': +if __name__ == "__main__": start = time.perf_counter() multiproc(WORKERS) print(f"Elapsed {time.perf_counter() - start:.2f} secs") # Elapsed 4.03 secs - diff --git a/concurrency_in_python__threading_processing/about_4__multiprocessing_with_Pool.py b/concurrency_in_python__threading_processing/about_4__multiprocessing_with_Pool.py index b376f39f7..473e2ffdf 100644 --- a/concurrency_in_python__threading_processing/about_4__multiprocessing_with_Pool.py +++ b/concurrency_in_python__threading_processing/about_4__multiprocessing_with_Pool.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://towardsdatascience.com/concurrency-in-python-e770c878ab53 @@ -14,17 +14,17 @@ from about_1__single_thread import heavy, WORKERS, N -def doit(n): +def doit(n) -> None: heavy(N, n) -def pooled(n): +def pooled(n) -> None: # By default, our pool will have processes slots with multiprocessing.Pool() as pool: - pool.map(doit, range(n)) + pool.map(doit, range(n)) -if __name__ == '__main__': +if __name__ == "__main__": start = time.perf_counter() pooled(WORKERS) print(f"Elapsed {time.perf_counter() - start:.2f} secs") diff --git a/concurrency_in_python__threading_processing/multiprocessing__examples/Manager__example.py b/concurrency_in_python__threading_processing/multiprocessing__examples/Manager__example.py index 105da41ee..7695daacc 100644 --- a/concurrency_in_python__threading_processing/multiprocessing__examples/Manager__example.py +++ b/concurrency_in_python__threading_processing/multiprocessing__examples/Manager__example.py @@ -1,17 +1,17 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from multiprocessing import Process, Manager -def f(users_data): +def f(users_data) -> None: users_data["users"][0]["coins"] += 1 -if __name__ == '__main__': +if __name__ == "__main__": manager = Manager() users_data = manager.dict() user_data = manager.dict({"id": 1, "coins": 0}) @@ -20,5 +20,5 @@ def f(users_data): p = Process(target=f, args=[users_data]) p.start() p.join() - print(users_data['users'][0]) + print(users_data["users"][0]) # {'id': 1, 'coins': 1} diff --git a/concurrency_in_python__threading_processing/multiprocessing__examples/code_execution_with_time_limit.py b/concurrency_in_python__threading_processing/multiprocessing__examples/code_execution_with_time_limit.py index 0788aa60e..e7b3623b8 100644 --- a/concurrency_in_python__threading_processing/multiprocessing__examples/code_execution_with_time_limit.py +++ b/concurrency_in_python__threading_processing/multiprocessing__examples/code_execution_with_time_limit.py @@ -1,13 +1,13 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import multiprocessing -def run(): +def run() -> None: import time i = 1 @@ -20,7 +20,7 @@ def run(): time.sleep(1) -if __name__ == '__main__': +if __name__ == "__main__": p = multiprocessing.Process(target=run) p.start() diff --git a/concurrency_in_python__threading_processing/multiprocessing__examples/create_process_get_result__communication__Pipe.py b/concurrency_in_python__threading_processing/multiprocessing__examples/create_process_get_result__communication__Pipe.py index 35e196cc4..734daa4dc 100644 --- a/concurrency_in_python__threading_processing/multiprocessing__examples/create_process_get_result__communication__Pipe.py +++ b/concurrency_in_python__threading_processing/multiprocessing__examples/create_process_get_result__communication__Pipe.py @@ -1,20 +1,20 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from multiprocessing import Process, Pipe -def f(con, name): - con.send('Hello, ' + name) +def f(con, name) -> None: + con.send("Hello, " + name) con.close() -if __name__ == '__main__': +if __name__ == "__main__": parent_conn, child_conn = Pipe() - p = Process(target=f, args=(child_conn, 'bob')) + p = Process(target=f, args=(child_conn, "bob")) p.start() print(parent_conn.recv()) # Hello, bob p.join() diff --git a/concurrency_in_python__threading_processing/multiprocessing__examples/create_process_get_result__communication__Queue.py b/concurrency_in_python__threading_processing/multiprocessing__examples/create_process_get_result__communication__Queue.py index aa56310ab..b0f74df34 100644 --- a/concurrency_in_python__threading_processing/multiprocessing__examples/create_process_get_result__communication__Queue.py +++ b/concurrency_in_python__threading_processing/multiprocessing__examples/create_process_get_result__communication__Queue.py @@ -1,19 +1,19 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from multiprocessing import Process, Queue -def f(q, name): - q.put('Hello, ' + name) +def f(q, name) -> None: + q.put("Hello, " + name) -if __name__ == '__main__': +if __name__ == "__main__": q = Queue() - p = Process(target=f, args=(q, 'bob')) + p = Process(target=f, args=(q, "bob")) p.start() print(q.get()) # Hello, bob p.join() diff --git a/concurrency_in_python__threading_processing/multiprocessing__examples/hello_world__Pool_map.py b/concurrency_in_python__threading_processing/multiprocessing__examples/hello_world__Pool_map.py index 3dc553cd3..5e9344261 100644 --- a/concurrency_in_python__threading_processing/multiprocessing__examples/hello_world__Pool_map.py +++ b/concurrency_in_python__threading_processing/multiprocessing__examples/hello_world__Pool_map.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://docs.python.org/3.6/library/multiprocessing.html @@ -14,7 +14,7 @@ def f(x): return x * x -if __name__ == '__main__': +if __name__ == "__main__": with Pool(5) as p: result = p.map(f, [1, 2, 3]) print(result) # [1, 4, 9] diff --git a/concurrency_in_python__threading_processing/multiprocessing__examples/hello_world__Process.py b/concurrency_in_python__threading_processing/multiprocessing__examples/hello_world__Process.py index 0efeac66d..c4504997e 100644 --- a/concurrency_in_python__threading_processing/multiprocessing__examples/hello_world__Process.py +++ b/concurrency_in_python__threading_processing/multiprocessing__examples/hello_world__Process.py @@ -1,17 +1,17 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from multiprocessing import Process -def f(name): - print('Hello,', name) +def f(name) -> None: + print("Hello,", name) -if __name__ == '__main__': - p = Process(target=f, args=('bob',)) +if __name__ == "__main__": + p = Process(target=f, args=("bob",)) p.start() p.join() diff --git a/concurrency_in_python__threading_processing/multiprocessing__examples/hello_world__with_daemon.py b/concurrency_in_python__threading_processing/multiprocessing__examples/hello_world__with_daemon.py index bbe07a628..2cb0f3016 100644 --- a/concurrency_in_python__threading_processing/multiprocessing__examples/hello_world__with_daemon.py +++ b/concurrency_in_python__threading_processing/multiprocessing__examples/hello_world__with_daemon.py @@ -1,22 +1,22 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from multiprocessing import Process, current_process import time -def run(): +def run() -> None: print(current_process()) - for i in 'Hello World!': + for i in "Hello World!": print(i) time.sleep(0.2) -if __name__ == '__main__': +if __name__ == "__main__": p = Process(target=run, daemon=True) p.start() diff --git a/concurrency_in_python__threading_processing/multiprocessing__examples/hello_world__without_daemon.py b/concurrency_in_python__threading_processing/multiprocessing__examples/hello_world__without_daemon.py index 69e7bc4e0..3317578cb 100644 --- a/concurrency_in_python__threading_processing/multiprocessing__examples/hello_world__without_daemon.py +++ b/concurrency_in_python__threading_processing/multiprocessing__examples/hello_world__without_daemon.py @@ -1,22 +1,22 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from multiprocessing import Process, current_process import time -def run(): +def run() -> None: print(current_process()) - for i in 'Hello World!': + for i in "Hello World!": print(i) time.sleep(0.2) -if __name__ == '__main__': +if __name__ == "__main__": p = Process(target=run, daemon=False) p.start() diff --git a/concurrency_in_python__threading_processing/multiprocessing__examples/multiprocessing__QApplication__create_child_process__with__Tkinter_and_Qt.py b/concurrency_in_python__threading_processing/multiprocessing__examples/multiprocessing__QApplication__create_child_process__with__Tkinter_and_Qt.py index 850c80d7c..08fc65fc4 100644 --- a/concurrency_in_python__threading_processing/multiprocessing__examples/multiprocessing__QApplication__create_child_process__with__Tkinter_and_Qt.py +++ b/concurrency_in_python__threading_processing/multiprocessing__examples/multiprocessing__QApplication__create_child_process__with__Tkinter_and_Qt.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from multiprocessing import Process @@ -9,24 +9,25 @@ from multiprocessing__Tkinter__in_other_process import go as go_tk -def create_qt(): - p = Process(target=go_qt, args=('Qt',)) +def create_qt() -> None: + p = Process(target=go_qt, args=("Qt",)) p.start() -def create_tk(): - p = Process(target=go_tk, args=('Tk',)) +def create_tk() -> None: + p = Process(target=go_tk, args=("Tk",)) p.start() -if __name__ == '__main__': +if __name__ == "__main__": from PyQt5.Qt import QApplication, QPushButton, QWidget, QVBoxLayout + app = QApplication([]) - button_qt = QPushButton('Create Qt') + button_qt = QPushButton("Create Qt") button_qt.clicked.connect(create_qt) - button_tk = QPushButton('Create Tk') + button_tk = QPushButton("Create Tk") button_tk.clicked.connect(create_tk) layout = QVBoxLayout() diff --git a/concurrency_in_python__threading_processing/multiprocessing__examples/multiprocessing__QApplication__in_other_process.py b/concurrency_in_python__threading_processing/multiprocessing__examples/multiprocessing__QApplication__in_other_process.py index 7c49a0231..fb2789a80 100644 --- a/concurrency_in_python__threading_processing/multiprocessing__examples/multiprocessing__QApplication__in_other_process.py +++ b/concurrency_in_python__threading_processing/multiprocessing__examples/multiprocessing__QApplication__in_other_process.py @@ -1,25 +1,28 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -def go(name): - from PyQt5.Qt import QApplication, Qt, QLabel +from PyQt5.Qt import QApplication, Qt, QLabel + + +def go(name) -> None: app = QApplication([]) mw = QLabel() mw.setAlignment(Qt.AlignCenter) mw.setMinimumSize(150, 50) - mw.setText('Hello, ' + name) + mw.setText("Hello, " + name) mw.show() app.exec() -if __name__ == '__main__': +if __name__ == "__main__": from multiprocessing import Process - p = Process(target=go, args=('bob',)) + + p = Process(target=go, args=("bob",)) p.start() # Необязательно в данном случае -- главный поток все-равно закроется после дочернего diff --git a/concurrency_in_python__threading_processing/multiprocessing__examples/multiprocessing__QApplication__in_other_process_list__Pool_map.py b/concurrency_in_python__threading_processing/multiprocessing__examples/multiprocessing__QApplication__in_other_process_list__Pool_map.py index 7a17434fa..c27cae3ae 100644 --- a/concurrency_in_python__threading_processing/multiprocessing__examples/multiprocessing__QApplication__in_other_process_list__Pool_map.py +++ b/concurrency_in_python__threading_processing/multiprocessing__examples/multiprocessing__QApplication__in_other_process_list__Pool_map.py @@ -1,12 +1,12 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -if __name__ == '__main__': +if __name__ == "__main__": from multiprocessing import Pool from multiprocessing__QApplication__in_other_process import go with Pool() as p: - p.map(go, ['Alice', 'Bob', 'World']) + p.map(go, ["Alice", "Bob", "World"]) diff --git a/concurrency_in_python__threading_processing/multiprocessing__examples/multiprocessing__Tkinter__in_other_process.py b/concurrency_in_python__threading_processing/multiprocessing__examples/multiprocessing__Tkinter__in_other_process.py index 2289017f3..4b2b670fd 100644 --- a/concurrency_in_python__threading_processing/multiprocessing__examples/multiprocessing__Tkinter__in_other_process.py +++ b/concurrency_in_python__threading_processing/multiprocessing__examples/multiprocessing__Tkinter__in_other_process.py @@ -1,23 +1,26 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -def go(name): - import tkinter as tk +import tkinter as tk + + +def go(name) -> None: app = tk.Tk() app.minsize(150, 50) - mw = tk.Label(app, text='Hello, ' + name) - mw.pack(fill='both', expand=True) + mw = tk.Label(app, text="Hello, " + name) + mw.pack(fill="both", expand=True) app.mainloop() -if __name__ == '__main__': +if __name__ == "__main__": from multiprocessing import Process - p = Process(target=go, args=('bob',)) + + p = Process(target=go, args=("bob",)) p.start() # Необязательно в данном случае -- главный поток все-равно закроется после дочернего diff --git a/concurrency_in_python__threading_processing/multiprocessing__examples/multiprocessing_with_QApplication_and_Tkinter__in_other_process.py b/concurrency_in_python__threading_processing/multiprocessing__examples/multiprocessing_with_QApplication_and_Tkinter__in_other_process.py index f17c5243a..a32d76308 100644 --- a/concurrency_in_python__threading_processing/multiprocessing__examples/multiprocessing_with_QApplication_and_Tkinter__in_other_process.py +++ b/concurrency_in_python__threading_processing/multiprocessing__examples/multiprocessing_with_QApplication_and_Tkinter__in_other_process.py @@ -1,18 +1,18 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -if __name__ == '__main__': +if __name__ == "__main__": from multiprocessing import Process from multiprocessing__QApplication__in_other_process import go as go_qt from multiprocessing__Tkinter__in_other_process import go as go_tk - p1 = Process(target=go_qt, args=('Qt',)) + p1 = Process(target=go_qt, args=("Qt",)) p1.start() - p2 = Process(target=go_tk, args=('Tk',)) + p2 = Process(target=go_tk, args=("Tk",)) p2.start() # Необязательно в данном случае -- главный поток все-равно закроется после дочернего diff --git a/concurrency_in_python__threading_processing/multiprocessing__examples/multiprocessing_with_webservers__flask.py b/concurrency_in_python__threading_processing/multiprocessing__examples/multiprocessing_with_webservers__flask.py index 153112347..486e8fe87 100644 --- a/concurrency_in_python__threading_processing/multiprocessing__examples/multiprocessing_with_webservers__flask.py +++ b/concurrency_in_python__threading_processing/multiprocessing__examples/multiprocessing_with_webservers__flask.py @@ -1,32 +1,35 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -def go(port): - from flask import Flask +import logging +import time + +import requests + +from flask import Flask + + +def go(port: int) -> None: app = Flask(__name__) - import logging logging.basicConfig(level=logging.DEBUG) @app.route("/") - def index(): - return "Hello World! (port={})".format(port) + def index() -> str: + return f"Hello World! (port={port})" app.run(port=port) -def go_parser(urls): - import time - import requests - +def go_parser(urls) -> None: while True: for url in urls: try: rs = requests.get(url) - print('Parser: {}. "{}"'.format(rs, rs.text)) + print(f'Parser: {rs}. "{rs.text}"') except: pass @@ -34,7 +37,7 @@ def go_parser(urls): time.sleep(2) -if __name__ == '__main__': +if __name__ == "__main__": # NOTE: recommended to add daemon=False or call join() for each process from multiprocessing import Process @@ -50,7 +53,7 @@ def go_parser(urls): # p = Process(target=go, args=(port,)) # p.start() - urls = ['http://127.0.0.1:5001/', 'http://127.0.0.1:5002/'] + urls = ["http://127.0.0.1:5001/", "http://127.0.0.1:5002/"] p3 = Process(target=go_parser, args=(urls,)) p3.start() diff --git a/concurrency_in_python__threading_processing/multiprocessing__examples/print_process_info.py b/concurrency_in_python__threading_processing/multiprocessing__examples/print_process_info.py index 9e91062f0..260a1ecce 100644 --- a/concurrency_in_python__threading_processing/multiprocessing__examples/print_process_info.py +++ b/concurrency_in_python__threading_processing/multiprocessing__examples/print_process_info.py @@ -1,29 +1,29 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from multiprocessing import Process import os -def info(title): +def info(title) -> None: print(title) - print('module name:', __name__) - print('parent process:', os.getppid()) - print('process id:', os.getpid()) + print("module name:", __name__) + print("parent process:", os.getppid()) + print("process id:", os.getpid()) -def f(name): - info('function f') - print('Hello,', name) +def f(name) -> None: + info("function f") + print("Hello,", name) -if __name__ == '__main__': - info('main line') +if __name__ == "__main__": + info("main line") print() - p = Process(target=f, args=('bob',)) + p = Process(target=f, args=("bob",)) p.start() p.join() diff --git a/concurrency_in_python__threading_processing/multiprocessing__examples/synchronization_between_processes.py b/concurrency_in_python__threading_processing/multiprocessing__examples/synchronization_between_processes.py index c81bcee64..e55c96cbf 100644 --- a/concurrency_in_python__threading_processing/multiprocessing__examples/synchronization_between_processes.py +++ b/concurrency_in_python__threading_processing/multiprocessing__examples/synchronization_between_processes.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://docs.python.org/3.6/library/multiprocessing.html#synchronization-between-processes @@ -11,14 +11,14 @@ import time -def f(lock, i): +def f(lock, i) -> None: with lock: - print('hello world', i) + print("hello world", i) time.sleep(1) -if __name__ == '__main__': +if __name__ == "__main__": lock = Lock() for num in range(10): diff --git a/concurrency_in_python__threading_processing/multithreading__threading__examples/ProcessPoolExecutor__examples/hello_world.py b/concurrency_in_python__threading_processing/multithreading__threading__examples/ProcessPoolExecutor__examples/hello_world.py index fe6ca0f6a..9b6a9b408 100644 --- a/concurrency_in_python__threading_processing/multithreading__threading__examples/ProcessPoolExecutor__examples/hello_world.py +++ b/concurrency_in_python__threading_processing/multithreading__threading__examples/ProcessPoolExecutor__examples/hello_world.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://docs.python.org/3/library/concurrent.futures.html#processpoolexecutor-example @@ -17,11 +17,11 @@ 112272535095293, 115280095190773, 115797848077099, - 1099726899285419 + 1099726899285419, ] -def is_prime(n): +def is_prime(n) -> bool: if n < 2: return False if n == 2: @@ -36,14 +36,14 @@ def is_prime(n): return True -def main(): +def main() -> None: # NOTE: max_workers must be defined # with concurrent.futures.ProcessPoolExecutor(max_workers=MAX_WORKERS) as executor: with concurrent.futures.ProcessPoolExecutor() as executor: for number, prime in zip(PRIMES, executor.map(is_prime, PRIMES)): - print('%d is prime: %s' % (number, prime)) + print(f"{number} is prime: {prime}") -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/concurrency_in_python__threading_processing/multithreading__threading__examples/ThreadPoolExecutor__examples/hello_world.py b/concurrency_in_python__threading_processing/multithreading__threading__examples/ThreadPoolExecutor__examples/hello_world.py index fe6989b06..508be632f 100644 --- a/concurrency_in_python__threading_processing/multithreading__threading__examples/ThreadPoolExecutor__examples/hello_world.py +++ b/concurrency_in_python__threading_processing/multithreading__threading__examples/ThreadPoolExecutor__examples/hello_world.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://docs.python.org/3/library/concurrent.futures.html#threadpoolexecutor-example @@ -12,11 +12,11 @@ URLS = [ - 'http://www.foxnews.com/', - 'http://www.cnn.com/', - 'http://europe.wsj.com/', - 'http://www.bbc.co.uk/', - 'http://some-made-up-domain.com/' + "http://www.foxnews.com/", + "http://www.cnn.com/", + "http://europe.wsj.com/", + "http://www.bbc.co.uk/", + "http://some-made-up-domain.com/", ] MAX_WORKERS = 5 @@ -40,6 +40,6 @@ def load_url(url, timeout): try: data = future.result() except Exception as e: - print(f'{url!r} generated an exception: {e}') + print(f"{url!r} generated an exception: {e}") else: - print(f'{url!r} page is {len(data)} bytes') + print(f"{url!r} page is {len(data)} bytes") diff --git a/concurrency_in_python__threading_processing/multithreading__threading__examples/ThreadPoolExecutor__examples/time_sleep.py b/concurrency_in_python__threading_processing/multithreading__threading__examples/ThreadPoolExecutor__examples/time_sleep.py index 2f9daa2a9..277878b81 100644 --- a/concurrency_in_python__threading_processing/multithreading__threading__examples/ThreadPoolExecutor__examples/time_sleep.py +++ b/concurrency_in_python__threading_processing/multithreading__threading__examples/ThreadPoolExecutor__examples/time_sleep.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://docs.python.org/3/library/concurrent.futures.html#threadpoolexecutor-example @@ -15,8 +15,8 @@ MAX_WORKERS = 5 -def run(name): - print(f'name: {name}') +def run(name) -> None: + print(f"name: {name}") time.sleep(randint(1, 4)) diff --git a/concurrency_in_python__threading_processing/multithreading__threading__examples/ThreadPoolExecutor__examples/time_sleep__as_completed.py b/concurrency_in_python__threading_processing/multithreading__threading__examples/ThreadPoolExecutor__examples/time_sleep__as_completed.py index bc0952691..28ccadb7e 100644 --- a/concurrency_in_python__threading_processing/multithreading__threading__examples/ThreadPoolExecutor__examples/time_sleep__as_completed.py +++ b/concurrency_in_python__threading_processing/multithreading__threading__examples/ThreadPoolExecutor__examples/time_sleep__as_completed.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://docs.python.org/3/library/concurrent.futures.html#threadpoolexecutor-example @@ -15,9 +15,9 @@ MAX_WORKERS = 5 -def run(name): +def run(name) -> str: time.sleep(randint(1, 4)) - return f'name: {name}' + return f"name: {name}" executor = concurrent.futures.ThreadPoolExecutor(max_workers=MAX_WORKERS) diff --git a/concurrency_in_python__threading_processing/multithreading__threading__examples/atomic_counter.py b/concurrency_in_python__threading_processing/multithreading__threading__examples/atomic_counter.py index 77c655267..c0f4b1dc8 100644 --- a/concurrency_in_python__threading_processing/multithreading__threading__examples/atomic_counter.py +++ b/concurrency_in_python__threading_processing/multithreading__threading__examples/atomic_counter.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://gist.github.com/benhoyt/8c8a8d62debe8e5aa5340373f9c509c7 @@ -40,7 +40,8 @@ class AtomicCounter: >>> counter.value 400000 """ - def __init__(self, initial=0): + + def __init__(self, initial=0) -> None: """Initialize a new atomic counter to given initial value (default 0).""" self.value = initial self._lock = threading.Lock() @@ -54,8 +55,9 @@ def increment(self, num=1): return self.value -if __name__ == '__main__': +if __name__ == "__main__": import doctest + doctest.testmod() # MORE: @@ -64,7 +66,7 @@ def increment(self, num=1): counter = AtomicCounter() - def incrementor(): + def incrementor() -> None: for i in range(NUM): counter.increment() diff --git a/concurrency_in_python__threading_processing/multithreading__threading__examples/code_execution_with_time_limit.py b/concurrency_in_python__threading_processing/multithreading__threading__examples/code_execution_with_time_limit.py index 850ed7f2e..d882d8e3b 100644 --- a/concurrency_in_python__threading_processing/multithreading__threading__examples/code_execution_with_time_limit.py +++ b/concurrency_in_python__threading_processing/multithreading__threading__examples/code_execution_with_time_limit.py @@ -1,15 +1,14 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import threading +import time -def run(): - import time - +def run() -> None: i = 1 # Бесконечный цикл @@ -20,11 +19,11 @@ def run(): time.sleep(1) -if __name__ == '__main__': +if __name__ == "__main__": thread = threading.Thread(target=run, daemon=True) thread.start() # Wait thread.join(5) - print('Quit!') + print("Quit!") diff --git a/concurrency_in_python__threading_processing/multithreading__threading__examples/example multiprocessing.dummy/hello_world.py b/concurrency_in_python__threading_processing/multithreading__threading__examples/example multiprocessing.dummy/hello_world.py index 63929c1db..130b0808b 100644 --- a/concurrency_in_python__threading_processing/multithreading__threading__examples/example multiprocessing.dummy/hello_world.py +++ b/concurrency_in_python__threading_processing/multithreading__threading__examples/example multiprocessing.dummy/hello_world.py @@ -1,11 +1,14 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" + + +from multiprocessing.dummy import Pool as ThreadPool +from urllib.request import urlopen def go(url): - from urllib.request import urlopen rs = urlopen(url) print(url, rs) @@ -13,13 +16,12 @@ def go(url): urls = [ - 'http://www.python.org', - 'http://www.python.org/about/', - 'http://www.python.org/doc/', - 'http://www.python.org/download/', + "http://www.python.org", + "http://www.python.org/about/", + "http://www.python.org/doc/", + "http://www.python.org/download/", ] -from multiprocessing.dummy import Pool as ThreadPool pool = ThreadPool() results = pool.map(go, urls) print(results) diff --git a/concurrency_in_python__threading_processing/multithreading__threading__examples/example multiprocessing.dummy/hello_world_with_result__requests.py b/concurrency_in_python__threading_processing/multithreading__threading__examples/example multiprocessing.dummy/hello_world_with_result__requests.py index 96d6642cd..f2aabafcd 100644 --- a/concurrency_in_python__threading_processing/multithreading__threading__examples/example multiprocessing.dummy/hello_world_with_result__requests.py +++ b/concurrency_in_python__threading_processing/multithreading__threading__examples/example multiprocessing.dummy/hello_world_with_result__requests.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from multiprocessing.dummy import Pool as ThreadPool @@ -9,10 +9,10 @@ urls = [ - 'http://www.python.org', - 'http://www.python.org/about/', - 'http://www.python.org/doc/', - 'http://www.python.org/download/', + "http://www.python.org", + "http://www.python.org/about/", + "http://www.python.org/doc/", + "http://www.python.org/download/", ] pool = ThreadPool() diff --git a/concurrency_in_python__threading_processing/multithreading__threading__examples/example multiprocessing.dummy/hello_world_with_result__urllib.py b/concurrency_in_python__threading_processing/multithreading__threading__examples/example multiprocessing.dummy/hello_world_with_result__urllib.py index ba9327893..5f73d5464 100644 --- a/concurrency_in_python__threading_processing/multithreading__threading__examples/example multiprocessing.dummy/hello_world_with_result__urllib.py +++ b/concurrency_in_python__threading_processing/multithreading__threading__examples/example multiprocessing.dummy/hello_world_with_result__urllib.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from multiprocessing.dummy import Pool as ThreadPool @@ -9,10 +9,10 @@ urls = [ - 'http://www.python.org', - 'http://www.python.org/about/', - 'http://www.python.org/doc/', - 'http://www.python.org/download/', + "http://www.python.org", + "http://www.python.org/about/", + "http://www.python.org/doc/", + "http://www.python.org/download/", ] pool = ThreadPool() diff --git a/concurrency_in_python__threading_processing/multithreading__threading__examples/example multiprocessing.dummy/urls.py b/concurrency_in_python__threading_processing/multithreading__threading__examples/example multiprocessing.dummy/urls.py index 826f9fd6f..2cf9018ae 100644 --- a/concurrency_in_python__threading_processing/multithreading__threading__examples/example multiprocessing.dummy/urls.py +++ b/concurrency_in_python__threading_processing/multithreading__threading__examples/example multiprocessing.dummy/urls.py @@ -1,17 +1,20 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" +import time + from urllib.request import urlopen from multiprocessing.dummy import Pool as ThreadPool + urls = [ - 'http://www.python.org', - 'http://www.python.org/about/', - 'http://www.python.org/doc/', - 'http://www.python.org/download/', + "http://www.python.org", + "http://www.python.org/about/", + "http://www.python.org/doc/", + "http://www.python.org/download/", # 'http://www.onlamp.com/pub/a/python/2003/04/17/metaclasses.html', # 'http://www.python.org/getit/', # 'http://www.python.org/community/', @@ -22,27 +25,26 @@ # 'http://docs.python.org/devguide/', # 'http://www.python.org/community/awards/' # etc.. - ] - - -import time +] t = time.clock() results = [] for url in urls: result = urlopen(url) results.append(result) -print('Single thread: {:.3f} seconds'.format(time.clock() - t)) +print(f"Single thread: {time.clock() - t:.3f} seconds") # ------- VERSUS ------- # -def go(count=1): + +def go(count=1) -> None: t = time.clock() pool = ThreadPool(count) results = pool.map(urlopen, urls) # pool.close() # pool.join() - print('{} Pool: {:.3f} seconds'.format(count, time.clock() - t)) + print(f"{count} Pool: {time.clock() - t:.3f} seconds") + # ------- 1 Pool ------- # go() diff --git a/concurrency_in_python__threading_processing/multithreading__threading__examples/flask__threaded/flask_test_script.py b/concurrency_in_python__threading_processing/multithreading__threading__examples/flask__threaded/flask_test_script.py index fb3c257ec..ff6ab87bf 100644 --- a/concurrency_in_python__threading_processing/multithreading__threading__examples/flask__threaded/flask_test_script.py +++ b/concurrency_in_python__threading_processing/multithreading__threading__examples/flask__threaded/flask_test_script.py @@ -1,20 +1,19 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import requests # With threaded for _ in range(5): - rs = requests.get('http://127.0.0.1:6000/') + rs = requests.get("http://127.0.0.1:6000/") print(rs.text) print() # Without threaded for _ in range(5): - rs = requests.get('http://127.0.0.1:6001/') + rs = requests.get("http://127.0.0.1:6001/") print(rs.text) - diff --git a/concurrency_in_python__threading_processing/multithreading__threading__examples/flask__threaded/threaded__with.py b/concurrency_in_python__threading_processing/multithreading__threading__examples/flask__threaded/threaded__with.py index 77ae8bceb..31c26c6a2 100644 --- a/concurrency_in_python__threading_processing/multithreading__threading__examples/flask__threaded/threaded__with.py +++ b/concurrency_in_python__threading_processing/multithreading__threading__examples/flask__threaded/threaded__with.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import threading @@ -12,9 +12,9 @@ @app.route("/") -def index(): - return "Current thread: {}".format(threading.current_thread()) +def index() -> str: + return f"Current thread: {threading.current_thread()}" -if __name__ == '__main__': +if __name__ == "__main__": app.run(port=6000) diff --git a/concurrency_in_python__threading_processing/multithreading__threading__examples/flask__threaded/threaded__without.py b/concurrency_in_python__threading_processing/multithreading__threading__examples/flask__threaded/threaded__without.py index db1096552..0e80e4203 100644 --- a/concurrency_in_python__threading_processing/multithreading__threading__examples/flask__threaded/threaded__without.py +++ b/concurrency_in_python__threading_processing/multithreading__threading__examples/flask__threaded/threaded__without.py @@ -1,18 +1,20 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" +import threading from flask import Flask + + app = Flask(__name__) @app.route("/") -def index(): - import threading - return "Current thread: {}".format(threading.current_thread()) +def index() -> str: + return f"Current thread: {threading.current_thread()}" -if __name__ == '__main__': +if __name__ == "__main__": app.run(port=6001) diff --git a/concurrency_in_python__threading_processing/multithreading__threading__examples/hello_world__with_daemon.py b/concurrency_in_python__threading_processing/multithreading__threading__examples/hello_world__with_daemon.py index f820e00bd..1b93b6521 100644 --- a/concurrency_in_python__threading_processing/multithreading__threading__examples/hello_world__with_daemon.py +++ b/concurrency_in_python__threading_processing/multithreading__threading__examples/hello_world__with_daemon.py @@ -1,21 +1,21 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import threading import time -def run(): +def run() -> None: print(threading.current_thread()) - for i in 'Hello World!': + for i in "Hello World!": print(i) time.sleep(0.2) -if __name__ == '__main__': +if __name__ == "__main__": thread = threading.Thread(target=run, daemon=True) thread.start() diff --git a/concurrency_in_python__threading_processing/multithreading__threading__examples/hello_world__without_daemon.py b/concurrency_in_python__threading_processing/multithreading__threading__examples/hello_world__without_daemon.py index 213a22e49..94303fb54 100644 --- a/concurrency_in_python__threading_processing/multithreading__threading__examples/hello_world__without_daemon.py +++ b/concurrency_in_python__threading_processing/multithreading__threading__examples/hello_world__without_daemon.py @@ -1,21 +1,21 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import threading import time -def run(): +def run() -> None: print(threading.current_thread()) - for i in 'Hello World!': + for i in "Hello World!": print(i) time.sleep(0.2) -if __name__ == '__main__': +if __name__ == "__main__": thread = threading.Thread(target=run, daemon=False) thread.start() diff --git a/concurrency_in_python__threading_processing/multithreading__threading__examples/mini_example__with_threading.py b/concurrency_in_python__threading_processing/multithreading__threading__examples/mini_example__with_threading.py index f098b1c74..579a59232 100644 --- a/concurrency_in_python__threading_processing/multithreading__threading__examples/mini_example__with_threading.py +++ b/concurrency_in_python__threading_processing/multithreading__threading__examples/mini_example__with_threading.py @@ -1,28 +1,28 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import threading +import time -def run(name='main', sleep_seconds=None): +def run(name="main", sleep_seconds=None) -> None: if sleep_seconds: - import time time.sleep(sleep_seconds) - print('start: "{}": {}'.format(name, threading.current_thread())) + print(f'start: "{name}": {threading.current_thread()}') -if __name__ == '__main__': +if __name__ == "__main__": run() - thread = threading.Thread(target=run, args=('thread #1', 3)) + thread = threading.Thread(target=run, args=("thread #1", 3)) thread.start() - thread = threading.Thread(target=run, args=('thread #2', 1)) + thread = threading.Thread(target=run, args=("thread #2", 1)) thread.start() - thread = threading.Thread(target=run, args=('thread #3', 0)) + thread = threading.Thread(target=run, args=("thread #3", 0)) thread.start() diff --git a/concurrency_in_python__threading_processing/multithreading__threading__examples/race_condition.py b/concurrency_in_python__threading_processing/multithreading__threading__examples/race_condition.py index 7538bdccc..0a09c9fc3 100644 --- a/concurrency_in_python__threading_processing/multithreading__threading__examples/race_condition.py +++ b/concurrency_in_python__threading_processing/multithreading__threading__examples/race_condition.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from multiprocessing.dummy import Pool as ThreadPool @@ -9,46 +9,44 @@ number = 0 -DATA = { - 'number': 0 -} +DATA = {"number": 0} lock = threading.Lock() -def inc(*args): +def inc(*args) -> None: global number - DATA['number'] += 1 + DATA["number"] += 1 number += 1 -def inc_lock(*args): +def inc_lock(*args) -> None: global number with lock: number += 1 - DATA['number'] += 1 + DATA["number"] += 1 max_number = 1_000_000 # Only one thread -- main -number = DATA['number'] = 0 +number = DATA["number"] = 0 for _ in range(max_number): inc() -print(number, DATA['number']) +print(number, DATA["number"]) # 1000000 1000000 # Many threads -number = DATA['number'] = 0 +number = DATA["number"] = 0 pool = ThreadPool() pool.map(inc, range(max_number)) -print(number, DATA['number']) +print(number, DATA["number"]) # 819903 459802 # Many threads -number = DATA['number'] = 0 +number = DATA["number"] = 0 pool = ThreadPool() pool.map(inc_lock, range(max_number)) -print(number, DATA['number']) +print(number, DATA["number"]) # 1000000 1000000 diff --git a/concurrency_in_python__threading_processing/multithreading__threading__examples/sync_vs_async__with_requests_and_bs4.py b/concurrency_in_python__threading_processing/multithreading__threading__examples/sync_vs_async__with_requests_and_bs4.py index 70f20e689..a61825691 100644 --- a/concurrency_in_python__threading_processing/multithreading__threading__examples/sync_vs_async__with_requests_and_bs4.py +++ b/concurrency_in_python__threading_processing/multithreading__threading__examples/sync_vs_async__with_requests_and_bs4.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from multiprocessing.dummy import Pool as ThreadPool @@ -13,20 +13,22 @@ def go(url): rs = requests.get(url) - root = BeautifulSoup(rs.content, 'html.parser') - return root.select_one('.user-details > a').text.strip() + root = BeautifulSoup(rs.content, "html.parser") + return root.select_one(".user-details > a").text.strip() urls = [ - 'https://ru.stackoverflow.com/questions/962400', 'https://ru.stackoverflow.com/questions/962311', - 'https://ru.stackoverflow.com/questions/962128', 'https://ru.stackoverflow.com/questions/962396', - 'https://ru.stackoverflow.com/questions/962349' + "https://ru.stackoverflow.com/questions/962400", + "https://ru.stackoverflow.com/questions/962311", + "https://ru.stackoverflow.com/questions/962128", + "https://ru.stackoverflow.com/questions/962396", + "https://ru.stackoverflow.com/questions/962349", ] t = time.time() result = [go(url) for url in urls] print(result) -print('Elapsed {:.3f} secs'.format(time.time() - t)) +print(f"Elapsed {time.time() - t:.3f} secs") # ['Streletz', 'Kromster', 'Stepan Kasyanenko', 'Kromster', 'JamesJGoodwin'] # Elapsed 6.030 secs @@ -36,6 +38,6 @@ def go(url): pool = ThreadPool() result = pool.map(go, urls) print(result) -print('Elapsed {:.3f} secs'.format(time.time() - t)) +print(f"Elapsed {time.time() - t:.3f} secs") # ['Streletz', 'Kromster', 'Stepan Kasyanenko', 'Kromster', 'JamesJGoodwin'] # Elapsed 3.203 secs diff --git a/concurrency_in_python__threading_processing/multithreading__threading__examples/thread__with__callback.py b/concurrency_in_python__threading_processing/multithreading__threading__examples/thread__with__callback.py index 8112fe8c4..15a3a15b3 100644 --- a/concurrency_in_python__threading_processing/multithreading__threading__examples/thread__with__callback.py +++ b/concurrency_in_python__threading_processing/multithreading__threading__examples/thread__with__callback.py @@ -1,20 +1,20 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import time from threading import Thread -def go(callback_func): +def go(callback_func) -> None: while True: time.sleep(2) callback_func(":)") -def it_callback(s): +def it_callback(s) -> None: global status status = s diff --git a/concurrency_in_python__threading_processing/multithreading__threading__examples/threading_with_webservers__flask.py b/concurrency_in_python__threading_processing/multithreading__threading__examples/threading_with_webservers__flask.py index 5fc19cd3f..e6fdcdda9 100644 --- a/concurrency_in_python__threading_processing/multithreading__threading__examples/threading_with_webservers__flask.py +++ b/concurrency_in_python__threading_processing/multithreading__threading__examples/threading_with_webservers__flask.py @@ -1,22 +1,22 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" +import logging import threading +from flask import Flask -def run(port=80): - from flask import Flask - app = Flask(__name__) - import logging +def run(port: int = 80) -> None: + app = Flask(__name__) logging.basicConfig(level=logging.DEBUG) @app.route("/") def index(): - html = "Hello World! (port={})".format(port) + html = f"Hello World! (port={port})" print(html) return html @@ -24,13 +24,13 @@ def index(): app.run(port=port) -if __name__ == '__main__': +if __name__ == "__main__": # NOTE: recommended to add daemon=False or call join() for each thread - thread = threading.Thread(target=run, args=(5000, )) + thread = threading.Thread(target=run, args=(5000,)) thread.start() - thread = threading.Thread(target=run, args=(5001, )) + thread = threading.Thread(target=run, args=(5001,)) thread.start() thread = threading.Thread(target=run, args=(5002,)) diff --git a/concurrency_in_python__threading_processing/multithreading__threading__examples/timeout_run_function.py b/concurrency_in_python__threading_processing/multithreading__threading__examples/timeout_run_function.py index 2b3d575be..f64ab1a3a 100644 --- a/concurrency_in_python__threading_processing/multithreading__threading__examples/timeout_run_function.py +++ b/concurrency_in_python__threading_processing/multithreading__threading__examples/timeout_run_function.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import time @@ -9,28 +9,28 @@ class User: - def __init__(self, name): + def __init__(self, name) -> None: self.name = name - def post_msg(self): + def post_msg(self) -> None: time.sleep(3) # Написать пользователю - print('Hi, {}! current_thread: {}'.format(self.name, current_thread())) + print(f"Hi, {self.name}! current_thread: {current_thread()}") -user_1 = User('Vasya') +user_1 = User("Vasya") user_1.post_msg() -user_2 = User('Petya') +user_2 = User("Petya") Thread(target=user_2.post_msg).start() -def foo(name): +def foo(name) -> None: time.sleep(5) # Написать пользователю - print('Hi, {}! current_thread: {}'.format(name, current_thread())) + print(f"Hi, {name}! current_thread: {current_thread()}") -Thread(target=lambda: foo('Thread')).start() +Thread(target=lambda: foo("Thread")).start() diff --git a/confucius_quotes.py b/confucius_quotes.py index f6ff925cd..0aedfa083 100644 --- a/confucius_quotes.py +++ b/confucius_quotes.py @@ -1,25 +1,29 @@ -from urllib.parse import quote -from grab import Grab +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +__author__ = "ipetrash" -__author__ = 'ipetrash' # Скрипт качает цитаты Конфуция из викицитатника. +from urllib.parse import quote +from grab import Grab + + def get_confucius_quotes(): - url = quote('http://ru.wikiquote.org/wiki/Конфуций', safe='/:') + url = quote("http://ru.wikiquote.org/wiki/Конфуций", safe="/:") g = Grab() g.go(url) quotes = list() - for el in g.doc.select('//h2/following-sibling::ul/li'): + for el in g.doc.select("//h2/following-sibling::ul/li"): quotes.append(el.text()) return quotes -if __name__ == '__main__': +if __name__ == "__main__": for q in get_confucius_quotes(): - print("'{}'".format(q)) \ No newline at end of file + print(f"'{q}'") diff --git a/connect_and_download_file_from_ftp.py b/connect_and_download_file_from_ftp.py index d816dc627..306ad7b7b 100644 --- a/connect_and_download_file_from_ftp.py +++ b/connect_and_download_file_from_ftp.py @@ -1,29 +1,30 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from ftplib import FTP + # Connect to host, default port -ftp = FTP('ftp.debian.org') +ftp = FTP("ftp.debian.org") print(ftp.host) # User anonymous, passwd anonymous print(ftp.login()) # Change into "debian" directory -print(ftp.cwd('debian')) +print(ftp.cwd("debian")) # List directory contents -print(ftp.retrlines('LIST')) +print(ftp.retrlines("LIST")) # Show file content -print(ftp.retrbinary('RETR README', lambda data: print(repr(data)))) +print(ftp.retrbinary("RETR README", lambda data: print(repr(data)))) # Download file -print(ftp.retrbinary('RETR README', open('README', 'wb').write)) +print(ftp.retrbinary("RETR README", open("README", "wb").write)) # Send a QUIT command to the server and close the connection print(ftp.quit()) diff --git a/console__clear_line__cr_CARRIAGE_RETURN.py b/console__clear_line__cr_CARRIAGE_RETURN.py index c819c9505..d3861816a 100644 --- a/console__clear_line__cr_CARRIAGE_RETURN.py +++ b/console__clear_line__cr_CARRIAGE_RETURN.py @@ -1,14 +1,15 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -import time import sys +import time + for ips in (10254, 1, 10, 1, 10254, 999): - sys.stdout.write('\r' + f'Current value [{ips}]' + ' ' * 100) + sys.stdout.write("\r" + f"Current value [{ips}]" + " " * 100) sys.stdout.flush() time.sleep(0.5) diff --git a/console_change_data_in_line.py b/console_change_data_in_line.py index 9346623e7..2e79600ef 100644 --- a/console_change_data_in_line.py +++ b/console_change_data_in_line.py @@ -1,20 +1,20 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import sys import time -def f1(n): +def f1(n) -> None: last_num_chars = 0 write, flush = sys.stdout.write, sys.stdout.flush while n >= 0: - write('\r' * last_num_chars) - last_num_chars = write('[1] Left: ' + str(n)) + write("\r" * last_num_chars) + last_num_chars = write("[1] Left: " + str(n)) flush() time.sleep(1) @@ -23,13 +23,13 @@ def f1(n): print() -def f2(n): +def f2(n) -> None: last_num_chars = 0 write, flush = sys.stdout.write, sys.stdout.flush while n >= 0: - write('\b' * last_num_chars) - last_num_chars = write('[2] Left: ' + str(n)) + write("\b" * last_num_chars) + last_num_chars = write("[2] Left: " + str(n)) flush() time.sleep(1) diff --git a/contextlib__redirect__stdout_stderr/catch_stdout_and_stderr__Exception.py b/contextlib__redirect__stdout_stderr/catch_stdout_and_stderr__Exception.py index f256b64af..d5c7d14b1 100644 --- a/contextlib__redirect__stdout_stderr/catch_stdout_and_stderr__Exception.py +++ b/contextlib__redirect__stdout_stderr/catch_stdout_and_stderr__Exception.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from contextlib import redirect_stdout, redirect_stderr @@ -14,8 +14,8 @@ with redirect_stdout(f), redirect_stderr(f): try: - print('123') - print('abc') + print("123") + print("abc") 1 / 0 except: print(traceback.format_exc(), file=sys.stderr) diff --git a/contextlib__redirect__stdout_stderr/redirect__stdout_to_stderr.py b/contextlib__redirect__stdout_stderr/redirect__stdout_to_stderr.py index b37366cb8..4f119e3c1 100644 --- a/contextlib__redirect__stdout_stderr/redirect__stdout_to_stderr.py +++ b/contextlib__redirect__stdout_stderr/redirect__stdout_to_stderr.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import sys diff --git a/contextlib__redirect__stdout_stderr/redirect__to_file.py b/contextlib__redirect__stdout_stderr/redirect__to_file.py index 90284ef2c..9f293c9c8 100644 --- a/contextlib__redirect__stdout_stderr/redirect__to_file.py +++ b/contextlib__redirect__stdout_stderr/redirect__to_file.py @@ -1,12 +1,12 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from contextlib import redirect_stdout # How to write help() to a file -with open('help.txt', 'w') as f, redirect_stdout(f): +with open("help.txt", "w") as f, redirect_stdout(f): help(pow) diff --git a/contextlib__redirect__stdout_stderr/redirect__to_file_like_object.py b/contextlib__redirect__stdout_stderr/redirect__to_file_like_object.py index c1d00eb7a..db0959749 100644 --- a/contextlib__redirect__stdout_stderr/redirect__to_file_like_object.py +++ b/contextlib__redirect__stdout_stderr/redirect__to_file_like_object.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from io import StringIO @@ -9,10 +9,10 @@ with StringIO() as f, redirect_stdout(f): - print('Hello ', end='') + print("Hello ", end="") - def foo(): - print('World') + def foo() -> None: + print("World") foo() diff --git a/convert_image_to_ico/main.py b/convert_image_to_ico/main.py index 8dd63a682..6fc355776 100644 --- a/convert_image_to_ico/main.py +++ b/convert_image_to_ico/main.py @@ -1,11 +1,13 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -def convert_image_to_ico(file_name, file_name_ico, icon_sizes=None): - from PIL import Image +from PIL import Image + + +def convert_image_to_ico(file_name, file_name_ico, icon_sizes=None) -> None: img = Image.open(file_name) if icon_sizes: @@ -14,9 +16,13 @@ def convert_image_to_ico(file_name, file_name_ico, icon_sizes=None): img.save(file_name_ico) -if __name__ == '__main__': - file_name = 'image.jpg' +if __name__ == "__main__": + file_name = "image.jpg" - convert_image_to_ico(file_name, 'logo.ico') - convert_image_to_ico(file_name, 'logo2.ico', icon_sizes=[(16, 16), (32, 32), (48, 48), (64, 64)]) - convert_image_to_ico(file_name, 'logo3.ico', icon_sizes=[(16, 16), (32, 32)]) + convert_image_to_ico(file_name, "logo.ico") + convert_image_to_ico( + file_name, + "logo2.ico", + icon_sizes=[(16, 16), (32, 32), (48, 48), (64, 64)], + ) + convert_image_to_ico(file_name, "logo3.ico", icon_sizes=[(16, 16), (32, 32)]) diff --git a/convert_numpy_array_to_QImage.py b/convert_numpy_array_to_QImage.py index ddaac2189..18d905732 100644 --- a/convert_numpy_array_to_QImage.py +++ b/convert_numpy_array_to_QImage.py @@ -1,15 +1,16 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" +from datetime import datetime + import numpy as np + from PIL import ImageGrab from PyQt5.QtGui import qRgb, QImage -import datetime as DT - GRAY_COLOR_TABLE = [qRgb(i, i, i) for i in range(256)] @@ -21,31 +22,49 @@ def convert_numpy_array_to_QImage(numpy_array): height, width = numpy_array.shape[:2] if len(numpy_array.shape) == 2: - img = QImage(numpy_array.data, width, height, numpy_array.strides[0], QImage.Format_Indexed8) + img = QImage( + numpy_array.data, + width, + height, + numpy_array.strides[0], + QImage.Format_Indexed8, + ) img.setColorTable(GRAY_COLOR_TABLE) return img elif len(numpy_array.shape) == 3: if numpy_array.shape[2] == 3: - img = QImage(numpy_array.data, width, height, numpy_array.strides[0], QImage.Format_RGB888) + img = QImage( + numpy_array.data, + width, + height, + numpy_array.strides[0], + QImage.Format_RGB888, + ) return img elif numpy_array.shape[2] == 4: - img = QImage(numpy_array.data, width, height, numpy_array.strides[0], QImage.Format_ARGB32) + img = QImage( + numpy_array.data, + width, + height, + numpy_array.strides[0], + QImage.Format_ARGB32, + ) return img -if __name__ == '__main__': +if __name__ == "__main__": print_screen_pil = ImageGrab.grab() width, height = print_screen_pil.size[0], print_screen_pil.size[1] - print_screen_numpy = np.array(print_screen_pil.getdata(), dtype='uint8') + print_screen_numpy = np.array(print_screen_pil.getdata(), dtype="uint8") print_screen_numpy = print_screen_numpy.reshape((height, width, 3)) img = convert_numpy_array_to_QImage(print_screen_numpy) print(img.size()) - file_name = f"print_screen_{DT.datetime.now():%Y-%m-%d_%H%M%S}.png" + file_name = f"print_screen_{datetime.now():%Y-%m-%d_%H%M%S}.png" print(file_name) img.save(file_name) diff --git a/converter mils - mm/main.py b/converter mils - mm/main.py index 0fc3e048d..cc9fca452 100644 --- a/converter mils - mm/main.py +++ b/converter mils - mm/main.py @@ -1,22 +1,22 @@ -__author__ = 'ipetrash' +__author__ = "ipetrash" -def mils2mm(mils): +def mils2mm(mils: float) -> float: """Функция конвертирует mils (1/1000 дюйма) в mm (миллиметры).""" mm = (mils / 1000) * 25.4 return round(mm, 2) -def mm2mils(mm): +def mm2mils(mm: float) -> float: """Функция конвертирует mm (миллиметры) в mils (1/1000 дюйма).""" mils = (mm * 1000) / 25.4 return round(mils, 2) -if __name__ == '__main__': +if __name__ == "__main__": mm = 50.0 mils = 1968.0 - print('{} mm -> {} mils'.format(mm, mm2mils(mm))) - print('{} mils -> {} mm'.format(mils, mils2mm(mils))) \ No newline at end of file + print(f"{mm} mm -> {mm2mils(mm)} mils") + print(f"{mils} mils -> {mils2mm(mils)} mm") diff --git a/copy2clipboard.py b/copy2clipboard.py index e1b073812..95f5988f2 100644 --- a/copy2clipboard.py +++ b/copy2clipboard.py @@ -1,33 +1,34 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -def to(text): +try: + from PyQt5.QtGui import QGuiApplication as QApplication + +except ImportError: try: - from PyQt5.QtGui import QGuiApplication as QApplication + from PyQt4.QtGui import QApplication except ImportError: - try: - from PyQt4.QtGui import QApplication + from PySide.QtGui import QApplication - except ImportError: - from PySide.QtGui import QApplication +def to(text: str) -> None: app = QApplication([]) app.clipboard().setText(text) app = None -if __name__ == '__main__': +if __name__ == "__main__": import sys + import os + if len(sys.argv) > 1: - text = ' '.join(sys.argv[1:]) - print('Text: "{}"'.format(text)) + text = " ".join(sys.argv[1:]) + print(f'Text: "{text}"') to(text) - else: - import os file_name = os.path.basename(sys.argv[0]) - print('usage: {} [-h] text'.format(file_name)) + print(f"usage: {file_name} [-h] text") diff --git a/copy2clipboard__via_pyperclip.py b/copy2clipboard__via_pyperclip.py index 912bc9fea..7db33e437 100644 --- a/copy2clipboard__via_pyperclip.py +++ b/copy2clipboard__via_pyperclip.py @@ -1,23 +1,25 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -def to(text): - import pyperclip +import pyperclip + + +def to(text: str) -> None: pyperclip.copy(text) pyperclip.paste() -if __name__ == '__main__': +if __name__ == "__main__": import sys + import os + if len(sys.argv) > 1: - text = ' '.join(sys.argv[1:]) - print('Text: "{}"'.format(text)) + text = " ".join(sys.argv[1:]) + print(f'Text: "{text}"') to(text) - else: - import os file_name = os.path.basename(sys.argv[0]) - print('usage: {} [-h] text'.format(file_name)) + print(f"usage: {file_name} [-h] text") diff --git a/copy_example.py b/copy_example.py index 26d46d9c2..b511a48da 100644 --- a/copy_example.py +++ b/copy_example.py @@ -1,21 +1,45 @@ -import copy +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" -__author__ = 'ipetrash' """RU: Пример использования модуля copy.""" -# TODO: https://docs.python.org/3.4/library/copy.html -# TODO: больше примеров -if __name__ == '__main__': - a = [2, 3, [3.5, 3.6, [3.61, 3.62]], 4, 5] - print(a, type(a), hex(id(a)), sep=', ') +import copy + + +complex_data = [ + 2, + 3, + [ + 3.5, + 3.6, + [3.61, 3.62], + ], + dict( + a=1, + b="2", + c=[True, None], + ), + 4, + 5, +] + + +def _print_complex_data(data) -> None: + print(data, id(data)) + print(data[2], id(data[2])) + print(data[2][2], id(data[2][2])) + print(data[3], id(data[3])) + print(data[3]["c"], id(data[3]["c"])) + - b = copy.deepcopy(a) - print(b, type(b), hex(id(b)), sep=', ') +_print_complex_data(complex_data) +print() - c = [2, 3, 4, 5] - print(c, type(c), hex(id(c)), sep=', ') +_print_complex_data(copy.copy(complex_data)) +print() - d = copy.copy(c) - print(d, type(d), hex(id(d)), sep=', ') \ No newline at end of file +_print_complex_data(copy.deepcopy(complex_data)) diff --git a/cors-anywhere-webserver/main.py b/cors-anywhere-webserver/main.py index 840714b06..10bac939b 100644 --- a/cors-anywhere-webserver/main.py +++ b/cors-anywhere-webserver/main.py @@ -1,27 +1,29 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -# TODO: append logging +import re +from urllib.request import urlopen, Request import flask + + +# TODO: append logging app = flask.Flask(__name__) app.debug = True -from urllib.request import urlopen, Request - -@app.route('/') +@app.route("/") def index(): - url = flask.request.args.get('url') - print('URL:', url) - if url is None: - return "Append url, please: {}?url=<your_url>".format(flask.request.host_url) + url = flask.request.args.get("url") + print("URL:", url) + if not url: + return f"Append url, please: {flask.request.host_url}?url=<your_url>" headers = dict() - headers['Origin'] = flask.request.host_url + headers["Origin"] = flask.request.host_url request = Request(url, headers=headers) @@ -31,12 +33,11 @@ def index(): # Нужно узнать encoding, для этого вытаскиваем xml-декларацию, а из нее уже значение encoding try: - s_index = content.find(b'') + s_index = content.find(b"") if s_index != -1 and e_index != -1: - declaration = content[s_index: e_index + len(b'?>')].decode('utf-8') + declaration = content[s_index : e_index + len(b"?>")].decode("utf-8") - import re match = re.search(r'encoding="(.+)"', declaration) if match: print(match.group(1)) @@ -49,7 +50,7 @@ def index(): rs = flask.Response(content) rs.headers.extend(headers) - rs.headers['Access-Control-Allow-Origin'] = '*' + rs.headers["Access-Control-Allow-Origin"] = "*" print(rs.headers) return rs @@ -59,4 +60,4 @@ def index(): app.run() # # Public IP - # app.run(host='0.0.0.0') \ No newline at end of file + # app.run(host='0.0.0.0') diff --git a/cpu_info/hello_world__cpuinfo.py b/cpu_info/hello_world__cpuinfo.py new file mode 100644 index 000000000..702cfc600 --- /dev/null +++ b/cpu_info/hello_world__cpuinfo.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +# pip install py-cpuinfo +import cpuinfo + + +info = cpuinfo.get_cpu_info() +print(info["brand_raw"]) +# AMD Ryzen 7 3700X 8-Core Processor diff --git a/crash_on_windows.py b/crash_on_windows.py new file mode 100644 index 000000000..a22874ffe --- /dev/null +++ b/crash_on_windows.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from ctypes import wintypes, windll, c_void_p, c_size_t, POINTER, c_ubyte, cast + + +def main() -> None: + # Define constants + FILE_MAP_ALL_ACCESS = 983071 + PAGE_READWRITE = 4 + + # Configure function arguments + windll.kernel32.CreateFileMappingA.argtypes = [ + wintypes.HANDLE, + c_void_p, + wintypes.DWORD, + wintypes.DWORD, + wintypes.DWORD, + wintypes.LPCSTR, + ] + windll.kernel32.CreateFileMappingA.restype = wintypes.HANDLE + + windll.kernel32.MapViewOfFile.argtypes = [ + wintypes.HANDLE, + wintypes.DWORD, + wintypes.DWORD, + wintypes.DWORD, + c_size_t, + ] + windll.kernel32.MapViewOfFile.restypes = wintypes.LPVOID + + # Open shared-memory + handle = windll.kernel32.CreateFileMappingA( + -1, None, PAGE_READWRITE, 0, 1024 * 1024, b"TestSHMEM" + ) + + # Obtain pointer to SHMEM buffer + ptr = windll.kernel32.MapViewOfFile(handle, FILE_MAP_ALL_ACCESS, 0, 0, 1024 * 1024) + arr = cast(ptr, POINTER(c_ubyte)) + + print(arr[0]) + # Process finished with exit code -1073741819 (0xC0000005) + + +if __name__ == "__main__": + main() diff --git a/crc32__examples/main.py b/crc32__examples/main.py index 1e95b86d9..ac2161cba 100644 --- a/crc32__examples/main.py +++ b/crc32__examples/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import zlib @@ -16,15 +16,15 @@ def crc32_hex_from_bytes(data: bytes) -> str: def crc32_hex_from_file(filename): - with open(filename, 'rb') as f: + with open(filename, "rb") as f: buf = f.read() return crc32_hex_from_bytes(buf) -if __name__ == '__main__': - print(crc32_from_bytes(b'hello-world')) # 2983461467 - print(crc32_hex_from_bytes(b'hello-world')) # B1D4025B +if __name__ == "__main__": + print(crc32_from_bytes(b"hello-world")) # 2983461467 + print(crc32_hex_from_bytes(b"hello-world")) # B1D4025B print() - print(crc32_hex_from_file('1.csv')) # 7EB0F2B2 - print(crc32_hex_from_file('2.csv')) # 5D884C77 + print(crc32_hex_from_file("1.csv")) # 7EB0F2B2 + print(crc32_hex_from_file("2.csv")) # 5D884C77 diff --git a/create_file_without_file_name.py b/create_file_without_file_name.py index 3c03281f1..ed18c01d1 100644 --- a/create_file_without_file_name.py +++ b/create_file_without_file_name.py @@ -1,9 +1,9 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -open('.txt', 'wb') -open('.this', 'wb') -open('.git', 'wb') +open(".txt", "wb") +open(".this", "wb") +open(".git", "wb") diff --git a/create_massive/create_massive.py b/create_massive/create_massive.py index 302053adc..8ee290a34 100644 --- a/create_massive/create_massive.py +++ b/create_massive/create_massive.py @@ -1,4 +1,4 @@ -__author__ = 'ipetrash' +__author__ = "ipetrash" def create_massive(rows, cols, init_value): @@ -16,11 +16,11 @@ def create_massive_2(rows, cols, init_value): return massive -if __name__ == '__main__': +if __name__ == "__main__": rows = 5 cols = 3 massive = create_massive(rows, cols, None) massive_2 = create_massive_2(rows, cols, 1) print(massive) - print(massive_2) \ No newline at end of file + print(massive_2) diff --git a/create_thumbnail/main.py b/create_thumbnail/main.py index 38388311d..a74577e75 100644 --- a/create_thumbnail/main.py +++ b/create_thumbnail/main.py @@ -1,27 +1,27 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -SIZE_THUMBS = (75, 75) -FROM_DIR = 'images' -TO_DIR = 'thumbs' +import glob +import os +from PIL import Image -if __name__ == '__main__': - import os - if not os.path.exists(TO_DIR): - os.mkdir(TO_DIR) - import glob - for filename in glob.glob(FROM_DIR + "/*.jpg"): - filename_thumbnail = os.path.join(TO_DIR, os.path.split(filename)[1]) - print('{} -> {}'.format(filename, filename_thumbnail)) +SIZE_THUMBS = 75, 75 +FROM_DIR = "images" +TO_DIR = "thumbs" - from PIL import Image - im = Image.open(filename) - im.thumbnail(SIZE_THUMBS) - im.save(filename_thumbnail) +if not os.path.exists(TO_DIR): + os.mkdir(TO_DIR) +for filename in glob.glob(FROM_DIR + "/*.jpg"): + filename_thumbnail = os.path.join(TO_DIR, os.path.split(filename)[1]) + print(f"{filename} -> {filename_thumbnail}") + + im = Image.open(filename) + im.thumbnail(SIZE_THUMBS) + im.save(filename_thumbnail) diff --git a/create_vars__use_globals.py b/create_vars__use_globals.py index 11beb5549..78c52f113 100644 --- a/create_vars__use_globals.py +++ b/create_vars__use_globals.py @@ -1,10 +1,10 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -globals()['abc'] = 123 +globals()["abc"] = 123 print(abc) # 123 for key, value in {"a": 1, "b": 2}.items(): @@ -18,12 +18,12 @@ number = 0 -def counter(): +def counter() -> None: # If not exists global # if 'number' not in globals(): # globals()['number'] = 0 - globals()['number'] += 1 + globals()["number"] += 1 counter() diff --git a/cron_converter__examples/from_jenkins.py b/cron_converter__examples/from_jenkins.py new file mode 100644 index 000000000..4260a40a4 --- /dev/null +++ b/cron_converter__examples/from_jenkins.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import re + + +def do_convert(cron: str) -> str: + match cron: + case "@hourly": + cron = "H * * * *" + + case "@daily" | "@midnight": + cron = "H 0 * * *" + + case "@weekly": + cron = "H H * * H" + + case "@monthly": + cron = "H H H * *" + + case "@yearly" | "@annually": + cron = "0 0 1 1 *" + + # NOTE: "H(0-29)/10 * * * *" -> "0-29/10 * * * *" + cron = re.sub(r"H\((.+?)\)", r"\1", cron) + + parts: list[str] = cron.split() + + def _process(value: str, default_value: str = "0") -> str: + return value.replace("H/", "*/").replace("H", default_value) + + # Minute + parts[0] = _process(parts[0]) + + # Hour + parts[1] = _process(parts[1]) + + # Day (month). Тут диапазон начинается с 1 + parts[2] = _process(parts[2], default_value="1") + + # Month. Тут диапазон начинается с 1 + parts[3] = _process(parts[3], default_value="1") + + # Day (week) + parts[4] = _process(parts[4]) + + cron = " ".join(parts) + + return cron + + +if __name__ == "__main__": + from datetime import datetime + + # pip install cron-converter + from cron_converter import Cron + + cron = "H */8 * * *" + cron = do_convert(cron) + cron_instance = Cron(cron) + + print(f"Cron: {cron_instance}") + + start_date = datetime.now() + print(f"Start date: {start_date}") + + schedule = cron_instance.schedule(start_date) + print(f"Next: {schedule.next().isoformat()}") + print(f"Next: {schedule.next().isoformat()}") + print(f"Next: {schedule.next().isoformat()}") + print(f"Next: {schedule.next().isoformat()}") + print(f"Next: {schedule.next().isoformat()}") diff --git a/cron_converter__examples/hello_world.py b/cron_converter__examples/hello_world.py new file mode 100644 index 000000000..016e5c170 --- /dev/null +++ b/cron_converter__examples/hello_world.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from datetime import datetime + +# pip install cron-converter +from cron_converter import Cron + + +print("Every hour") +cron = "0 * * * *" +cron_instance = Cron(cron) + +print(f"Cron: {cron_instance}") + +start_date = datetime.now() +print(f"Start date: {start_date}") + +schedule = cron_instance.schedule(start_date) +print(f"Next: {schedule.next().isoformat()}") +print(f"Next: {schedule.next().isoformat()}") +print(f"Next: {schedule.next().isoformat()}") + +print() + +print("Every 8 hours") +cron = "0 */8 * * *" +cron_instance = Cron(cron) + +print(f"Cron: {cron_instance}") + +start_date = datetime.now() +print(f"Start date: {start_date}") + +schedule = cron_instance.schedule(start_date) +print(f"Next: {schedule.next().isoformat()}") +print(f"Next: {schedule.next().isoformat()}") +print(f"Next: {schedule.next().isoformat()}") + +print() + +print("Every 00:00") +cron = "0 0 * * *" +cron_instance = Cron(cron) + +print(f"Cron: {cron_instance}") + +start_date = datetime.now() +print(f"Start date: {start_date}") + +schedule = cron_instance.schedule(start_date) +print(f"Next: {schedule.next().isoformat()}") +print(f"Next: {schedule.next().isoformat()}") +print(f"Next: {schedule.next().isoformat()}") + +print() + +print("Every 00:00 at Saturday") +cron = "0 0 * * 6" +cron_instance = Cron(cron) + +print(f"Cron: {cron_instance}") + +start_date = datetime.now() +print(f"Start date: {start_date}") + +schedule = cron_instance.schedule(start_date) +print(f"Next: {schedule.next().isoformat()}") +print(f"Next: {schedule.next().isoformat()}") +print(f"Next: {schedule.next().isoformat()}") diff --git a/cron_converter__examples/test_from_jenkins.py b/cron_converter__examples/test_from_jenkins.py new file mode 100644 index 000000000..52ce933fb --- /dev/null +++ b/cron_converter__examples/test_from_jenkins.py @@ -0,0 +1,400 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from datetime import datetime +from unittest import TestCase + +from cron_converter import Cron + +from from_jenkins import do_convert + + +class Test(TestCase): + def test_do_convert_every_15_minutes(self) -> None: + # Every fifteen minutes + cron = "H/15 * * * *" + + cron = do_convert(cron) + + cron_instance = Cron(cron) + + start_date_str = "2024-01-01T12:00:00" + schedule = cron_instance.schedule(datetime.fromisoformat(start_date_str)) + + actual: list[str] = [ + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + ] + expected = ["2024-01-01T12:00:00", "2024-01-01T12:15:00", "2024-01-01T12:30:00"] + + self.assertEqual(actual, expected) + + def test_do_convert_every_1_hours(self) -> None: + cron = "H * * * *" + + cron = do_convert(cron) + + cron_instance = Cron(cron) + + start_date_str = "2024-01-01T12:00:00" + schedule = cron_instance.schedule(datetime.fromisoformat(start_date_str)) + + actual: list[str] = [ + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + ] + expected = ["2024-01-01T12:00:00", "2024-01-01T13:00:00", "2024-01-01T14:00:00"] + + self.assertEqual(actual, expected) + + def test_do_convert_every_8_hours(self) -> None: + cron = "H */8 * * *" + + cron = do_convert(cron) + + cron_instance = Cron(cron) + + start_date_str = "2024-01-01T12:00:00" + schedule = cron_instance.schedule(datetime.fromisoformat(start_date_str)) + + actual: list[str] = [ + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + ] + expected = [ + "2024-01-01T16:00:00", + "2024-01-02T00:00:00", + "2024-01-02T08:00:00", + "2024-01-02T16:00:00", + "2024-01-03T00:00:00", + ] + + self.assertEqual(actual, expected) + + def test_do_convert_every_24_hours(self) -> None: + cron = "H 0 * * *" + + cron = do_convert(cron) + + cron_instance = Cron(cron) + + start_date_str = "2024-01-01T12:00:00" + schedule = cron_instance.schedule(datetime.fromisoformat(start_date_str)) + + actual: list[str] = [ + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + ] + expected = ["2024-01-02T00:00:00", "2024-01-03T00:00:00", "2024-01-04T00:00:00"] + + self.assertEqual(actual, expected) + + def test_do_convert_hourly(self) -> None: + cron = "@hourly" + + cron = do_convert(cron) + + cron_instance = Cron(cron) + + start_date_str = "2024-01-01T12:00:00" + schedule = cron_instance.schedule(datetime.fromisoformat(start_date_str)) + + actual: list[str] = [ + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + ] + expected = ["2024-01-01T12:00:00", "2024-01-01T13:00:00", "2024-01-01T14:00:00"] + + self.assertEqual(actual, expected) + + def test_do_convert_daily(self) -> None: + cron = "@daily" + + cron = do_convert(cron) + + cron_instance = Cron(cron) + + start_date_str = "2024-01-01T12:00:00" + schedule = cron_instance.schedule(datetime.fromisoformat(start_date_str)) + + actual: list[str] = [ + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + ] + expected = ["2024-01-02T00:00:00", "2024-01-03T00:00:00", "2024-01-04T00:00:00"] + + self.assertEqual(actual, expected) + + def test_do_convert_midnight(self) -> None: + cron = "@midnight" + + cron = do_convert(cron) + + cron_instance = Cron(cron) + + start_date_str = "2024-01-01T12:00:00" + schedule = cron_instance.schedule(datetime.fromisoformat(start_date_str)) + + actual: list[str] = [ + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + ] + expected = [ + "2024-01-02T00:00:00", + "2024-01-03T00:00:00", + "2024-01-04T00:00:00", + "2024-01-05T00:00:00", + "2024-01-06T00:00:00", + "2024-01-07T00:00:00", + ] + + self.assertEqual(actual, expected) + + def test_do_convert_weekly(self) -> None: + cron = "@weekly" + + cron = do_convert(cron) + + cron_instance = Cron(cron) + + start_date_str = "2024-01-01T12:00:00" + schedule = cron_instance.schedule(datetime.fromisoformat(start_date_str)) + + actual: list[str] = [ + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + ] + expected = [ + "2024-01-07T00:00:00", + "2024-01-14T00:00:00", + "2024-01-21T00:00:00", + "2024-01-28T00:00:00", + "2024-02-04T00:00:00", + "2024-02-11T00:00:00", + ] + + self.assertEqual(actual, expected) + + def test_do_convert_monthly(self) -> None: + cron = "@monthly" + + cron = do_convert(cron) + + cron_instance = Cron(cron) + + start_date_str = "2024-01-01T12:00:00" + schedule = cron_instance.schedule(datetime.fromisoformat(start_date_str)) + + actual: list[str] = [ + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + ] + expected = [ + "2024-02-01T00:00:00", + "2024-03-01T00:00:00", + "2024-04-01T00:00:00", + "2024-05-01T00:00:00", + "2024-06-01T00:00:00", + "2024-07-01T00:00:00", + ] + + self.assertEqual(actual, expected) + + def test_do_convert_yearly(self) -> None: + cron = "@yearly" + + cron = do_convert(cron) + + cron_instance = Cron(cron) + + start_date_str = "2024-01-01T12:00:00" + schedule = cron_instance.schedule(datetime.fromisoformat(start_date_str)) + + actual: list[str] = [ + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + ] + expected = ["2025-01-01T00:00:00", "2026-01-01T00:00:00", "2027-01-01T00:00:00"] + + self.assertEqual(actual, expected) + + def test_do_convert_annually(self) -> None: + cron = "@annually" + + cron = do_convert(cron) + + cron_instance = Cron(cron) + + start_date_str = "2024-01-01T12:00:00" + schedule = cron_instance.schedule(datetime.fromisoformat(start_date_str)) + + actual: list[str] = [ + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + ] + expected = ["2025-01-01T00:00:00", "2026-01-01T00:00:00", "2027-01-01T00:00:00"] + + self.assertEqual(actual, expected) + + def test_do_convert_complex_1(self) -> None: + # Every ten minutes in the first half of every hour + cron = "H(0-29)/10 * * * *" + + cron = do_convert(cron) + + cron_instance = Cron(cron) + + start_date_str = "2024-01-01T12:00:00" + schedule = cron_instance.schedule(datetime.fromisoformat(start_date_str)) + + actual: list[str] = [ + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + ] + expected = [ + "2024-01-01T12:00:00", + "2024-01-01T12:10:00", + "2024-01-01T12:20:00", + "2024-01-01T13:00:00", + "2024-01-01T13:10:00", + "2024-01-01T13:20:00", + "2024-01-01T14:00:00", + "2024-01-01T14:10:00", + "2024-01-01T14:20:00", + ] + + self.assertEqual(actual, expected) + + def test_do_convert_complex_2(self) -> None: + # Once every two hours at 45 minutes past the hour starting at 9:45 AM and finishing at 3:45 PM every weekday + cron = "45 9-16/2 * * 1-5" + + cron = do_convert(cron) + + cron_instance = Cron(cron) + + start_date_str = "2024-01-04T12:00:00" + schedule = cron_instance.schedule(datetime.fromisoformat(start_date_str)) + + actual: list[str] = [ + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + ] + expected = [ + "2024-01-04T13:45:00", + "2024-01-04T15:45:00", + "2024-01-05T09:45:00", + "2024-01-05T11:45:00", + "2024-01-05T13:45:00", + "2024-01-05T15:45:00", + "2024-01-08T09:45:00", + "2024-01-08T11:45:00", + ] + + self.assertEqual(actual, expected) + + def test_do_convert_complex_3(self) -> None: + # Once in every two hour slot between 8 AM and 4 PM every weekday + cron = "H H(8-15)/2 * * 1-5" + + cron = do_convert(cron) + + cron_instance = Cron(cron) + + start_date_str = "2024-01-04T12:00:00" + schedule = cron_instance.schedule(datetime.fromisoformat(start_date_str)) + + actual: list[str] = [ + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + ] + expected = [ + "2024-01-04T12:00:00", + "2024-01-04T14:00:00", + "2024-01-05T08:00:00", + "2024-01-05T10:00:00", + "2024-01-05T12:00:00", + "2024-01-05T14:00:00", + "2024-01-08T08:00:00", + "2024-01-08T10:00:00", + ] + + self.assertEqual(actual, expected) + + def test_do_convert_complex_4(self) -> None: + # Once a day on the 1st and 15th of every month except December + cron = "H H 1,15 1-11 *" + + cron = do_convert(cron) + + cron_instance = Cron(cron) + + start_date_str = "2023-10-01T12:00:00" + schedule = cron_instance.schedule(datetime.fromisoformat(start_date_str)) + + actual: list[str] = [ + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + ] + expected = [ + "2023-10-15T00:00:00", + "2023-11-01T00:00:00", + "2023-11-15T00:00:00", + "2024-01-01T00:00:00", + "2024-01-15T00:00:00", + "2024-02-01T00:00:00", + "2024-02-15T00:00:00", + "2024-03-01T00:00:00", + ] + + self.assertEqual(actual, expected) diff --git a/crypto__gost/pygost__example__gostR34.11-94.py b/crypto__gost/pygost__example__gostR34.11-94.py index 17553577d..827739f76 100644 --- a/crypto__gost/pygost__example__gostR34.11-94.py +++ b/crypto__gost/pygost__example__gostR34.11-94.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from pygost import gost341194 @@ -10,16 +10,24 @@ def get_digest(data, sbox=gost341194.DEFAULT_SBOX): if isinstance(data, str): - data = data.encode('utf-8') + data = data.encode("utf-8") gost_digest = gost341194.GOST341194(data, sbox) - return gost_digest.digest(), gost_digest.hexdigest(), base64.b64encode(gost_digest.digest()) + return ( + gost_digest.digest(), + gost_digest.hexdigest(), + base64.b64encode(gost_digest.digest()), + ) + text = """RCPT00001ОтправительMNSV10000Государственная информационная система жилищно-коммунального хозяйства735111111ПолучательGISGMPGFNCREQUEST2018-02-28T16:30:39.877+05:0061234567890""" -text = "RCPT00001ОтправительMNSV10000Государственная информационная система жилищно-коммунального хозяйства735111111ПолучательGISGMPGFNCREQUEST2018-02-28T16:30:39.877+05:0061234567890" +text = 'RCPT00001ОтправительMNSV10000Государственная информационная система жилищно-коммунального хозяйства735111111ПолучательGISGMPGFNCREQUEST2018-02-28T16:30:39.877+05:0061234567890' # print(get_digest(text)[2]) -print(get_digest(text, 'GostR3411_94_CryptoProParamSet')[2]) # /zP/7KpLyXyg6FpKsAjNNSMe3pvPjvfx24X9RdI9AYg= +print( + get_digest(text, "GostR3411_94_CryptoProParamSet")[2] +) +# /zP/7KpLyXyg6FpKsAjNNSMe3pvPjvfx24X9RdI9AYg= # # text = """\ # # diff --git a/cryptography__examples/hello_world.py b/cryptography__examples/hello_world.py index 7b34fe964..2237ceb53 100644 --- a/cryptography__examples/hello_world.py +++ b/cryptography__examples/hello_world.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/pyca/cryptography diff --git a/css_to_xpath__gui/examples.py b/css_to_xpath__gui/examples.py index 985a62e51..221af53b9 100644 --- a/css_to_xpath__gui/examples.py +++ b/css_to_xpath__gui/examples.py @@ -1,27 +1,39 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install cssselect from cssselect import HTMLTranslator + + css_to_xpath = HTMLTranslator(xhtml=True).css_to_xpath -if __name__ == '__main__': - xpath_expr = css_to_xpath('div#main > a[href]') +if __name__ == "__main__": + xpath_expr = css_to_xpath("div#main > a[href]") print(xpath_expr) # descendant-or-self::div[@id = 'main']/a[@href] - xpath_expr = css_to_xpath('div') + xpath_expr = css_to_xpath("div") print(xpath_expr) # descendant-or-self::div - xpath_expr = css_to_xpath('table:nth-last-child(1)') + xpath_expr = css_to_xpath("table:nth-last-child(1)") print(xpath_expr) # descendant-or-self::table[count(following-sibling::*) = 0] print() - for item in ('#title', '#head', '#heading', '.pageTitle', '.news_title', '.title', '.head', - '.heading', '.contentheading', '.small_header_red'): + for item in ( + "#title", + "#head", + "#heading", + ".pageTitle", + ".news_title", + ".title", + ".head", + ".heading", + ".contentheading", + ".small_header_red", + ): xpath_expr = css_to_xpath(item) print(xpath_expr) diff --git a/css_to_xpath__gui/main.py b/css_to_xpath__gui/main.py index 366519e86..7a78a57ce 100644 --- a/css_to_xpath__gui/main.py +++ b/css_to_xpath__gui/main.py @@ -1,36 +1,38 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import traceback +import sys from PyQt5 import Qt # pip install cssselect from cssselect import HTMLTranslator + + css_to_xpath = HTMLTranslator(xhtml=True).css_to_xpath -def log_uncaught_exceptions(ex_cls, ex, tb): - text = '{}: {}:\n'.format(ex_cls.__name__, ex) - text += ''.join(traceback.format_tb(tb)) +def log_uncaught_exceptions(ex_cls, ex, tb) -> None: + text = f"{ex_cls.__name__}: {ex}:\n" + text += "".join(traceback.format_tb(tb)) print(text) - Qt.QMessageBox.critical(None, 'Error', text) + Qt.QMessageBox.critical(None, "Error", text) sys.exit(1) -import sys sys.excepthook = log_uncaught_exceptions class MainWindow(Qt.QWidget): - def __init__(self): + def __init__(self) -> None: super().__init__() - self.setWindowTitle('css_to_xpath__gui') + self.setWindowTitle("css_to_xpath__gui") self.text_edit_input = Qt.QPlainTextEdit() self.text_edit_output = Qt.QPlainTextEdit() @@ -38,11 +40,13 @@ def __init__(self): self.label_error = Qt.QLabel() self.label_error.setStyleSheet("QLabel { color : red; }") self.label_error.setTextInteractionFlags(Qt.Qt.TextSelectableByMouse) - self.label_error.setSizePolicy(Qt.QSizePolicy.Expanding, Qt.QSizePolicy.Preferred) + self.label_error.setSizePolicy( + Qt.QSizePolicy.Expanding, Qt.QSizePolicy.Preferred + ) - self.button_detail_error = Qt.QPushButton('...') + self.button_detail_error = Qt.QPushButton("...") self.button_detail_error.setFixedSize(20, 20) - self.button_detail_error.setToolTip('Detail error') + self.button_detail_error.setToolTip("Detail error") self.button_detail_error.hide() self.last_error_message = None @@ -67,7 +71,7 @@ def __init__(self): self.setLayout(layout) - def on_process(self): + def on_process(self) -> None: self.text_edit_output.clear() self.label_error.clear() self.button_detail_error.hide() @@ -94,13 +98,13 @@ def on_process(self): self.last_detail_error_message = str(tb) self.button_detail_error.show() - self.label_error.setText('Error: ' + self.last_error_message) + self.label_error.setText("Error: " + self.last_error_message) - def show_detail_error_message(self): - message = self.last_error_message + '\n\n' + self.last_detail_error_message + def show_detail_error_message(self) -> None: + message = self.last_error_message + "\n\n" + self.last_detail_error_message mb = Qt.QErrorMessage() - mb.setWindowTitle('Error') + mb.setWindowTitle("Error") # Сообщение ошибки содержит отступы, символы-переходы на следующую строку, # которые поломаются при вставке через QErrorMessage.showMessage, и нет возможности # выбрать тип текста, то делаем такой хак. @@ -109,7 +113,7 @@ def show_detail_error_message(self): mb.exec_() -if __name__ == '__main__': +if __name__ == "__main__": app = Qt.QApplication([]) mw = MainWindow() @@ -117,6 +121,6 @@ def show_detail_error_message(self): mw.show() # For example - mw.text_edit_input.setPlainText('div#main > a[href]') + mw.text_edit_input.setPlainText("div#main > a[href]") app.exec_() diff --git a/csv__examples/reader_csv.py b/csv__examples/reader_csv.py index 23437bf69..297307cb6 100644 --- a/csv__examples/reader_csv.py +++ b/csv__examples/reader_csv.py @@ -1,12 +1,13 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import csv -with open('input.csv') as f: + +with open("input.csv") as f: csv_reader = csv.reader(f, delimiter=";") for row in csv_reader: print(row) diff --git a/csv__examples/reader_csv__as_dict__DictReader.py b/csv__examples/reader_csv__as_dict__DictReader.py index 3710c8ef0..2dc9f6edc 100644 --- a/csv__examples/reader_csv__as_dict__DictReader.py +++ b/csv__examples/reader_csv__as_dict__DictReader.py @@ -1,12 +1,13 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import csv -with open('input.csv') as f: + +with open("input.csv") as f: csv_reader = csv.DictReader(f, delimiter=";") for row in csv_reader: - print(row['name'], row['address']) + print(row["name"], row["address"]) diff --git a/csv__examples/reader_csv__auto_dialect__using_csv_Sniffer.py b/csv__examples/reader_csv__auto_dialect__using_csv_Sniffer.py index cf0946a8b..88b9d3898 100644 --- a/csv__examples/reader_csv__auto_dialect__using_csv_Sniffer.py +++ b/csv__examples/reader_csv__auto_dialect__using_csv_Sniffer.py @@ -1,15 +1,16 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import csv -with open('input.csv') as f: + +with open("input.csv") as f: dialect = csv.Sniffer().sniff(f.readline()) # # With delimiters: - # dialect = csv.Sniffer().sniff(f.readline(), delimiters=[',', ';']) + # dialect = csv.Sniffer().sniff(f.readline(), delimiters=[",", ";"]) f.seek(0) csv_reader = csv.reader(f, dialect=dialect) diff --git a/csv__examples/reader_csv__from_text.py b/csv__examples/reader_csv__from_text.py index 16058dee3..5e12d0830 100644 --- a/csv__examples/reader_csv__from_text.py +++ b/csv__examples/reader_csv__from_text.py @@ -1,10 +1,12 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import csv +from io import StringIO + text = """\ id;name;address;zip @@ -20,11 +22,8 @@ print() # Variant 2 -from io import StringIO - csv_reader = csv.reader(StringIO(text), delimiter=";") for row in csv_reader: print(row) print() - diff --git a/csv__examples/writer_csv.py b/csv__examples/writer_csv.py index b188102d4..1899a0e51 100644 --- a/csv__examples/writer_csv.py +++ b/csv__examples/writer_csv.py @@ -1,14 +1,16 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://docs.python.org/3/library/csv.html#csv.writer import csv -with open('eggs.csv', 'w', newline='') as csvfile: + + +with open("eggs.csv", "w", newline="") as csvfile: writer = csv.writer(csvfile) - writer.writerow(['Spam'] * 5 + ['Baked Beans']) - writer.writerow(['Spam', 'Lovely Spam', 'Wonderful Spam']) + writer.writerow(["Spam"] * 5 + ["Baked Beans"]) + writer.writerow(["Spam", "Lovely Spam", "Wonderful Spam"]) diff --git a/csv__examples/writer_csv__DictWriter.py b/csv__examples/writer_csv__DictWriter.py index 22992e0ee..498540469 100644 --- a/csv__examples/writer_csv__DictWriter.py +++ b/csv__examples/writer_csv__DictWriter.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://docs.python.org/3/library/csv.html#csv.DictWriter @@ -9,12 +9,13 @@ import csv -with open('names.csv', 'w', encoding='utf-8', newline='') as csvfile: - fieldnames = ['first_name', 'last_name'] + +with open("names.csv", "w", encoding="utf-8", newline="") as csvfile: + fieldnames = ["first_name", "last_name"] writer = csv.DictWriter(csvfile, fieldnames=fieldnames) writer.writeheader() - writer.writerow({'first_name': 'Baked', 'last_name': 'Beans'}) - writer.writerow({'first_name': 'Lovely', 'last_name': 'Spam'}) - writer.writerow({'first_name': 'Wonderful', 'last_name': 'Spam'}) + writer.writerow({"first_name": "Baked", "last_name": "Beans"}) + writer.writerow({"first_name": "Lovely", "last_name": "Spam"}) + writer.writerow({"first_name": "Wonderful", "last_name": "Spam"}) diff --git a/csv__examples/writer_list_of_dict.py b/csv__examples/writer_list_of_dict.py index a91961e58..eaaebbaf5 100644 --- a/csv__examples/writer_list_of_dict.py +++ b/csv__examples/writer_list_of_dict.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://docs.python.org/3/library/csv.html#csv.DictWriter @@ -10,13 +10,14 @@ import csv + items = [ - {'name': 'bob', 'age': 25, 'weight': 200}, - {'name': 'jim', 'age': 31, 'weight': 180} + {"name": "bob", "age": 25, "weight": 200}, + {"name": "jim", "age": 31, "weight": 180}, ] keys = items[0].keys() -with open('people.csv', 'w', encoding='utf-8', newline='') as f: +with open("people.csv", "w", encoding="utf-8", newline="") as f: dict_writer = csv.DictWriter(f, keys) dict_writer.writeheader() dict_writer.writerows(items) diff --git a/current_time.py b/current_time.py index 0ea0add71..1e13046dc 100644 --- a/current_time.py +++ b/current_time.py @@ -1,19 +1,20 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" + + +# https://docs.python.org/3.4/library/datetime.html +# https://docs.python.org/3.4/library/time.html import datetime as dt import time -# https://docs.python.org/3.4/library/datetime.html -# https://docs.python.org/3.4/library/time.html -if __name__ == '__main__': - while True: - cur_time = dt.datetime.now().time() - print("Current time is: %s" % cur_time.strftime("%H:%M:%S"), end='\r') +while True: + cur_time = dt.datetime.now().time() + print("Current time is: %s" % cur_time.strftime("%H:%M:%S"), end="\r") - # every 0.5 second (500 millisecond) - time.sleep(0.5) + # every 0.5 second (500 millisecond) + time.sleep(0.5) diff --git a/currying__examples/hello_world.py b/currying__examples/hello_world.py index 03b5f78d8..32969378e 100644 --- a/currying__examples/hello_world.py +++ b/currying__examples/hello_world.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://ru.wikipedia.org/wiki/Каррирование @@ -15,9 +15,11 @@ def foo(x): def a(y): def b(z): return my_sum(x, y, z) + return b + return a -print(foo(1)(2)(3)) # 6 +print(foo(1)(2)(3)) # 6 print(foo("1")("2")("3")) # 123 diff --git a/curtain for sleeping.py b/curtain for sleeping.py index eaffa8924..e9744f935 100644 --- a/curtain for sleeping.py +++ b/curtain for sleeping.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """Программа при клике на кнопку разворачивается на весь экран, закрашивается черным цветом. При @@ -10,25 +10,40 @@ try: - from PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton, QSizePolicy, QApplication + from PyQt5.QtWidgets import ( + QWidget, + QVBoxLayout, + QPushButton, + QSizePolicy, + QApplication, + ) from PyQt5.QtGui import QPainter from PyQt5.QtCore import Qt, QTimer except ImportError: - from PyQt4.QtGui import QWidget, QPainter, QVBoxLayout, QPushButton, QSizePolicy, QApplication + from PyQt4.QtGui import ( + QWidget, + QPainter, + QVBoxLayout, + QPushButton, + QSizePolicy, + QApplication, + ) from PyQt4.QtCore import Qt, QTimer class CurtainWidget(QWidget): - def __init__(self): + def __init__(self) -> None: super().__init__() - self.setWindowTitle('Curtain for sleeping') + self.setWindowTitle("Curtain for sleeping") self._flags = self.windowFlags() - self._activate_button = QPushButton('Activate curtain for sleeping') - self._activate_button.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) + self._activate_button = QPushButton("Activate curtain for sleeping") + self._activate_button.setSizePolicy( + QSizePolicy.Expanding, QSizePolicy.Expanding + ) self._activate_button.clicked.connect(self._activate) # Таймер не дает, в течении 2 секунд, движением мышки вернуть окно в нормальный вид @@ -45,10 +60,10 @@ def __init__(self): self.setMouseTracking(True) - def _activate(self, _): + def _activate(self, _) -> None: self.showFullScreen() - def showNormal(self): + def showNormal(self) -> None: self._activate_button.show() self.unsetCursor() @@ -58,7 +73,7 @@ def showNormal(self): self.setWindowFlags(self._flags) self.show() - def showFullScreen(self): + def showFullScreen(self) -> None: self._activate_button.hide() self.setCursor(Qt.BlankCursor) self._timer_block_normal.start() @@ -68,13 +83,13 @@ def showFullScreen(self): super().showFullScreen() - def mouseMoveEvent(self, event): + def mouseMoveEvent(self, event) -> None: if not self._timer_block_normal.isActive() and self.isFullScreen(): self.showNormal() super().mouseMoveEvent(event) - def paintEvent(self, event): + def paintEvent(self, event) -> None: painter = QPainter(self) painter.setBrush(Qt.black) painter.setPen(Qt.black) @@ -88,4 +103,3 @@ def paintEvent(self, event): widget.show() app.exec() - diff --git a/custom_with__context_manager/sqlite_execute.py b/custom_with__context_manager/sqlite_execute.py index 3af17175a..c27bb8f52 100644 --- a/custom_with__context_manager/sqlite_execute.py +++ b/custom_with__context_manager/sqlite_execute.py @@ -1,17 +1,22 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -def old_old_variant(): - import sqlite3 +import sqlite3 + +from types import TracebackType +from typing import Optional, Type + + +def old_old_variant() -> None: connect = sqlite3.connect(":memory:") try: - print(connect.execute('SELECT sqlite_version();').fetchone()) + print(connect.execute("SELECT sqlite_version();").fetchone()) - connect.executescript('''\ + connect.executescript("""\ CREATE TABLE Test ( id INTEGER PRIMARY KEY, name TEXT @@ -20,9 +25,9 @@ def old_old_variant(): INSERT INTO Test (name) VALUES ('One'); INSERT INTO Test (name) VALUES ('Two'); INSERT INTO Test (name) VALUES ('Three'); - ''') + """) - print(connect.execute('select * from Test;').fetchall()) + print(connect.execute("select * from Test;").fetchall()) print() connect.commit() @@ -31,13 +36,11 @@ def old_old_variant(): connect.close() -def old_variant(): - import sqlite3 - +def old_variant() -> None: with sqlite3.connect(":memory:") as connect: - print(connect.execute('SELECT sqlite_version();').fetchone()) + print(connect.execute("SELECT sqlite_version();").fetchone()) - connect.executescript('''\ + connect.executescript("""\ CREATE TABLE Test ( id INTEGER PRIMARY KEY, name TEXT @@ -46,31 +49,30 @@ def old_variant(): INSERT INTO Test (name) VALUES ('One'); INSERT INTO Test (name) VALUES ('Two'); INSERT INTO Test (name) VALUES ('Three'); - ''') + """) - print(connect.execute('select * from Test;').fetchall()) + print(connect.execute("select * from Test;").fetchall()) print() connect.commit() class SQLite3Connect(object): - def __init__(self, database): - import sqlite3 + def __init__(self, database) -> None: self._connect = sqlite3.connect(database) def __enter__(self): return self._connect - def __exit__(self, exc_type, exc_value, exc_traceback): + def __exit__(self, exc_type: Optional[Type[BaseException]], exc_value: Optional[BaseException], exc_traceback: Optional[TracebackType]) -> None: self._connect.close() -def new_variant(): +def new_variant() -> None: with SQLite3Connect(":memory:") as connect: - print(connect.execute('SELECT sqlite_version();').fetchone()) + print(connect.execute("SELECT sqlite_version();").fetchone()) - connect.executescript('''\ + connect.executescript("""\ CREATE TABLE Test ( id INTEGER PRIMARY KEY, name TEXT @@ -79,15 +81,15 @@ def new_variant(): INSERT INTO Test (name) VALUES ('One'); INSERT INTO Test (name) VALUES ('Two'); INSERT INTO Test (name) VALUES ('Three'); - ''') + """) - print(connect.execute('select * from Test;').fetchall()) + print(connect.execute("select * from Test;").fetchall()) print() connect.commit() -if __name__ == '__main__': +if __name__ == "__main__": old_old_variant() old_variant() new_variant() diff --git a/custom_with__context_manager/time_this_using_with.py b/custom_with__context_manager/time_this_using_with.py deleted file mode 100644 index 41d0a8623..000000000 --- a/custom_with__context_manager/time_this_using_with.py +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -__author__ = 'ipetrash' - - -import time - - -class TimeThis(object): - def __init__(self, title="TimeThis"): - self.title = title - self.start_time = None - - def __enter__(self): - self.start_time = time.clock() - return self - - def __exit__(self, exc_type, exc_value, exc_traceback): - print('[{}] total time: {:.3f} sec'.format(self.title, time.clock() - self.start_time)) - - -if __name__ == '__main__': - with TimeThis(): - time.sleep(1) - - with TimeThis("Test"): - text = '' - for i in range(10 ** 6): - text += str(i) - - with TimeThis("Test"): - items = [] - for i in range(10 ** 6): - items.append(str(i)) - - text = ''.join(items) diff --git a/custom_with__context_manager/time_this_using_with__class.py b/custom_with__context_manager/time_this_using_with__class.py new file mode 100644 index 000000000..c907ff1df --- /dev/null +++ b/custom_with__context_manager/time_this_using_with__class.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from timeit import default_timer +from types import TracebackType +from typing import Optional, Type + + +class TimeThis: + def __init__(self, title: str = "TimeThis") -> None: + self.title: str = title + self.start_time: float = 0.0 + + def __enter__(self) -> "TimeThis": + self.start_time = default_timer() + return self + + def __exit__(self, exc_type: Optional[Type[BaseException]], exc_value: Optional[BaseException], exc_traceback: Optional[TracebackType]) -> None: + print(f"[{self.title}] total time: {default_timer() - self.start_time:.3f} sec") + + +if __name__ == "__main__": + import time + + with TimeThis(): + time.sleep(1) + + with TimeThis("Test"): + text = "" + for i in range(10**5): + text += str(i) + + with TimeThis("Test"): + items = [] + for i in range(10**5): + items.append(str(i)) + + text = "".join(items) diff --git a/custom_with__context_manager/time_this_using_with__function.py b/custom_with__context_manager/time_this_using_with__function.py new file mode 100644 index 000000000..634d3e8ae --- /dev/null +++ b/custom_with__context_manager/time_this_using_with__function.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from contextlib import contextmanager +from timeit import default_timer + + +@contextmanager +def time_this(title: str = "TimeThis"): + start_time: float = default_timer() + try: + yield + finally: + print(f"[{title}] total time: {default_timer() - start_time:.3f} sec") + + +if __name__ == "__main__": + import time + + with time_this(): + time.sleep(1) + + with time_this("Test"): + text = "" + for i in range(10**5): + text += str(i) + + with time_this("Test"): + items = [] + for i in range(10**5): + items.append(str(i)) + + text = "".join(items) diff --git a/cydoomgeneric__examples/.gitignore b/cydoomgeneric__examples/.gitignore new file mode 100644 index 000000000..e414a02c6 --- /dev/null +++ b/cydoomgeneric__examples/.gitignore @@ -0,0 +1,2 @@ +*.wad +.savegame/ \ No newline at end of file diff --git a/cydoomgeneric__examples/README.md b/cydoomgeneric__examples/README.md new file mode 100644 index 000000000..b507075b9 --- /dev/null +++ b/cydoomgeneric__examples/README.md @@ -0,0 +1 @@ +https://github.com/wojciech-graj/cydoomgeneric \ No newline at end of file diff --git a/cydoomgeneric__examples/demopygame.py b/cydoomgeneric__examples/demopygame.py new file mode 100644 index 000000000..1b4b5b791 --- /dev/null +++ b/cydoomgeneric__examples/demopygame.py @@ -0,0 +1,79 @@ +""" + Copyright(C) 2024 Wojciech Graj + Copyright(C) 2024 Miika Lönnqvist + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. +""" + +import sys +from typing import Optional + +import numpy as np +import pygame + +import cydoomgeneric as cdg + +keymap = { + pygame.K_LEFT: cdg.Keys.LEFTARROW, + pygame.K_RIGHT: cdg.Keys.RIGHTARROW, + pygame.K_UP: cdg.Keys.UPARROW, + pygame.K_DOWN: cdg.Keys.DOWNARROW, + pygame.K_COMMA: cdg.Keys.STRAFE_L, + pygame.K_PERIOD: cdg.Keys.STRAFE_R, + pygame.K_LCTRL: cdg.Keys.FIRE, + pygame.K_SPACE: cdg.Keys.USE, + pygame.K_RSHIFT: cdg.Keys.RSHIFT, + pygame.K_RETURN: cdg.Keys.ENTER, + pygame.K_ESCAPE: cdg.Keys.ESCAPE, +} + + +class PygameDoom: + + def __init__(self) -> None: + self._resx = 640 + self._resy = 400 + pygame.init() + self._screen = pygame.display.set_mode((self._resx, self._resy)) + + def draw_frame(self, pixels: np.ndarray) -> None: + pixels = np.rot90(pixels) + pixels = np.flipud(pixels) + pygame.surfarray.blit_array(self._screen, pixels[:, :, [2, 1, 0]]) + pygame.display.flip() + + def get_key(self) -> Optional[tuple[int, int]]: + for event in pygame.event.get(): + if event.type == pygame.QUIT: + sys.exit() + + if event.type == pygame.KEYDOWN: + if event.key in keymap: + return 1, keymap[event.key] + + if event.type == pygame.KEYUP: + if event.key in keymap: + return 0, keymap[event.key] + + return None + + def set_window_title(self, t: str) -> None: + pygame.display.set_caption(t) + + +if __name__ == "__main__": + g = PygameDoom() + cdg.init(g._resx, + g._resy, + g.draw_frame, + g.get_key, + set_window_title=g.set_window_title) + cdg.main() diff --git a/cydoomgeneric__examples/download_DOOM1_WAD.py b/cydoomgeneric__examples/download_DOOM1_WAD.py new file mode 100644 index 000000000..7c8816c38 --- /dev/null +++ b/cydoomgeneric__examples/download_DOOM1_WAD.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +# SOURCE: https://github.com/nneonneo/universal-doom/blob/main/DOOM1.WAD + + +from pathlib import Path +from urllib.request import urlretrieve + + +url = "https://github.com/nneonneo/universal-doom/raw/refs/heads/main/DOOM1.WAD" +path = Path(__file__).parent.resolve() / Path(url).name +print(f"Download to {path}") + +urlretrieve(url, path) diff --git a/cydoomgeneric__examples/pyqt5.py b/cydoomgeneric__examples/pyqt5.py new file mode 100644 index 000000000..153a54342 --- /dev/null +++ b/cydoomgeneric__examples/pyqt5.py @@ -0,0 +1,177 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import sys +import traceback + +import cydoomgeneric as cdg +import numpy as np + +from PyQt5.QtCore import Qt, QThread, pyqtSignal +from PyQt5.QtGui import QImage, QPainter, QKeyEvent, QPaintEvent +from PyQt5.QtWidgets import QApplication, QWidget, QMessageBox + + +def log_uncaught_exceptions(ex_cls, ex, tb) -> None: + text = f"{ex_cls.__name__}: {ex}\n" + text += "".join(traceback.format_tb(tb)) + print(text) + + if isinstance(ex, KeyboardInterrupt): + QApplication.instance().quit() + return + + if QApplication.instance(): + msg_box = QMessageBox( + QMessageBox.Critical, + "Ошибка", + f"Ошибка: {ex}", + ) + msg_box.setDetailedText(text) + msg_box.setStandardButtons(QMessageBox.Ok) + msg_box.exec() + + +sys.excepthook = log_uncaught_exceptions + + +def numpy_array_to_QImage(numpy_array: np.ndarray) -> QImage: + height, width = numpy_array.shape[:2] + data: bytes = numpy_array.data.tobytes() + + return QImage( + data, + width, + height, + QImage.Format_RGB32, + ) + + +def get_key(event: QKeyEvent) -> int | None: + if event.modifiers() & Qt.ControlModifier: + return cdg.Keys.RCTRL + + if event.modifiers() & Qt.ShiftModifier: + return cdg.Keys.RSHIFT + + if event.modifiers() & Qt.AltModifier: + return cdg.Keys.LALT + + match event.key(): + case Qt.Key_W | Qt.Key_Up: + return cdg.Keys.UPARROW + case Qt.Key_A: + return cdg.Keys.STRAFE_L + case Qt.Key_D: + return cdg.Keys.STRAFE_R + case Qt.Key_S | Qt.Key_Down: + return cdg.Keys.DOWNARROW + case Qt.Key_Left: + return cdg.Keys.LEFTARROW + case Qt.Key_Right: + return cdg.Keys.RIGHTARROW + case Qt.Key_E: + return cdg.Keys.USE + case Qt.Key_Space: + return cdg.Keys.FIRE + case Qt.Key_Return: + return cdg.Keys.ENTER + case Qt.Key_Escape: + return cdg.Keys.ESCAPE + + +KEY_PRESSED: dict[int, bool] = dict() + + +class CyDoomGenericThread(QThread): + about_draw_frame = pyqtSignal(np.ndarray) + about_set_window_title = pyqtSignal(str) + + def __init__( + self, + path_wad: str, + width: int, + height: int, + ) -> None: + super().__init__() + + self.path_wad = path_wad + self.width = width + self.height = height + + def get_key(self) -> tuple[int, int] | None: + if not KEY_PRESSED: + return + + key, is_pressed = KEY_PRESSED.popitem() + return int(is_pressed), key + + def run(self) -> None: + cdg.init( + self.width, + self.height, + draw_frame=self.about_draw_frame.emit, + get_key=self.get_key, + set_window_title=self.about_set_window_title.emit, + ) + cdg.main(argv=["cydoomgeneric", "-iwad", self.path_wad]) + + +class WidgetDoom(QWidget): + def __init__(self, path_wad: str) -> None: + super().__init__() + + self._resx = 640 + self._resy = 400 + + self.thread_engine = CyDoomGenericThread( + path_wad=path_wad, + width=self._resx, + height=self._resy, + ) + self.thread_engine.about_draw_frame.connect(self.draw_frame) + self.thread_engine.about_set_window_title.connect(self.setWindowTitle) + # TODO: + # self.thread_engine.finished.connect(self.close) + self.thread_engine.start() + + self.img: QImage | None = None + + self.setFixedSize(self._resx, self._resy) + + def draw_frame(self, pixels: np.ndarray) -> None: + self.img = numpy_array_to_QImage(pixels) + self.update() + + def keyPressEvent(self, event: QKeyEvent) -> None: + key = get_key(event) + if key is not None: + KEY_PRESSED[key] = True + + def keyReleaseEvent(self, event: QKeyEvent) -> None: + key = get_key(event) + if key is not None: + KEY_PRESSED[key] = False + + def paintEvent(self, event: QPaintEvent) -> None: + if not self.img: + return + + p = QPainter(self) + p.drawImage(0, 0, self.img) + + +if __name__ == "__main__": + from pathlib import Path + + path_wad = str(Path(__file__).parent.resolve() / "DOOM1.WAD") + + app = QApplication([]) + + g = WidgetDoom(path_wad=path_wad) + g.show() + + app.exec() diff --git a/cydoomgeneric__examples/run_demopygame.py b/cydoomgeneric__examples/run_demopygame.py new file mode 100644 index 000000000..fcaa406cc --- /dev/null +++ b/cydoomgeneric__examples/run_demopygame.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import cydoomgeneric as cdg +from demopygame import PygameDoom + + +g = PygameDoom() +cdg.init( + g._resx, + g._resy, + g.draw_frame, + g.get_key, + set_window_title=g.set_window_title, +) +cdg.main(argv=["cydoomgeneric", "-iwad", "DOOM1.WAD"]) diff --git a/darknet-deeplearning-useful-scripts/change-quality.py b/darknet-deeplearning-useful-scripts/change-quality.py index 70dd47f54..8f382c7fc 100644 --- a/darknet-deeplearning-useful-scripts/change-quality.py +++ b/darknet-deeplearning-useful-scripts/change-quality.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import glob diff --git a/darknet-deeplearning-useful-scripts/create-train-list.py b/darknet-deeplearning-useful-scripts/create-train-list.py index 5712eb604..b4ef77612 100644 --- a/darknet-deeplearning-useful-scripts/create-train-list.py +++ b/darknet-deeplearning-useful-scripts/create-train-list.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import glob @@ -12,6 +12,6 @@ cur_dir = dirname(sys.argv[0]) -with open('train.txt', 'w') as f: +with open("train.txt", "w") as f: for file_name in glob.glob(f"{cur_dir}/*.png"): f.write(f"{file_name}\n") diff --git a/darknet-deeplearning-useful-scripts/remove-bad-images.py b/darknet-deeplearning-useful-scripts/remove-bad-images.py index b8f4f935d..28965c49e 100644 --- a/darknet-deeplearning-useful-scripts/remove-bad-images.py +++ b/darknet-deeplearning-useful-scripts/remove-bad-images.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import os @@ -15,7 +15,7 @@ try: image = Image.open(file_name) if not image.verify(): - raise ValueError(f'bad img {file_name}') + raise ValueError(f"bad img {file_name}") except: print(f"{file_name} looks bad") try: diff --git a/darknet-deeplearning-useful-scripts/remove-empty-png.py b/darknet-deeplearning-useful-scripts/remove-empty-png.py index 6a53330c7..9c026b1f3 100644 --- a/darknet-deeplearning-useful-scripts/remove-empty-png.py +++ b/darknet-deeplearning-useful-scripts/remove-empty-png.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import os @@ -9,7 +9,7 @@ for file_name in glob.glob("*.png"): - img = file_name.split('.')[0] + ".txt" + img = file_name.split(".")[0] + ".txt" if os.path.isfile(img): continue diff --git a/darknet-deeplearning-useful-scripts/remove-empty-txt.py b/darknet-deeplearning-useful-scripts/remove-empty-txt.py index 64e8a97be..afb9dc6a5 100644 --- a/darknet-deeplearning-useful-scripts/remove-empty-txt.py +++ b/darknet-deeplearning-useful-scripts/remove-empty-txt.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import os @@ -9,7 +9,7 @@ for file_name in glob.glob("*.txt"): - img = file_name.split('.')[0] + ".png" + img = file_name.split(".")[0] + ".png" if os.path.isfile(img): continue diff --git a/darknet-deeplearning-useful-scripts/scour-templates.py b/darknet-deeplearning-useful-scripts/scour-templates.py index 969080cef..4da1f08e3 100644 --- a/darknet-deeplearning-useful-scripts/scour-templates.py +++ b/darknet-deeplearning-useful-scripts/scour-templates.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import os @@ -11,7 +11,7 @@ print("reading") templates = [ - int(basename(f).split('.')[0]) + int(basename(f).split(".")[0]) for f in glob.glob("*.png") ] @@ -24,10 +24,10 @@ for i in templates: try: if i != index: - os.rename(f'{i}.txt', f'{index}.txt') - os.rename(f'{i}.png', f'{index}.png') + os.rename(f"{i}.txt", f"{index}.txt") + os.rename(f"{i}.png", f"{index}.png") except OSError as err: - print(f'bad index {i}') + print(f"bad index {i}") raise err index += 1 diff --git a/datetime__current_week_number.py b/datetime__current_week_number.py index e3da8e03c..b011871c8 100644 --- a/datetime__current_week_number.py +++ b/datetime__current_week_number.py @@ -1,10 +1,11 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -import datetime as DT +from datetime import datetime -week_number = DT.datetime.now().isocalendar()[1] -print('week_number:', week_number) + +week_number = datetime.now().isocalendar()[1] +print("week_number:", week_number) diff --git a/datetime__get_prev_month.py b/datetime__get_prev_month.py index 00d8ce655..f05c01e88 100644 --- a/datetime__get_prev_month.py +++ b/datetime__get_prev_month.py @@ -1,14 +1,14 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://stackoverflow.com/a/7153449/5909792 -import datetime as DT +from datetime import date, timedelta -prev_month = DT.date.today().replace(day=1) - DT.timedelta(days=1) +prev_month = date.today().replace(day=1) - timedelta(days=1) print(prev_month) diff --git a/datetime_example/correct_datetime.py b/datetime_example/correct_datetime.py new file mode 100644 index 000000000..a70224d26 --- /dev/null +++ b/datetime_example/correct_datetime.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from datetime import datetime, date, timedelta + + +# Текущий день может отсутствовать, поэтому ищем ближайший день +# Например, если день 31, то ищем ближайший день 30 +def correct_datetime( + dt: datetime | date, + target_year: int, + target_month: int, + target_day: int | None = None, +) -> datetime | date: + # TODO: Можно ли упростить проверку, начиная ее для дня больше 28? + # target_day больше нужен для случаев, если день больше 28 + # Можно явно перебирать дни вместе с месяцем и годом + while True: + try: + dt = dt.replace(year=target_year, month=target_month) + break + except ValueError: + dt -= timedelta(days=1) + + if target_day is not None: + # Попробуем сразу указать target_day + try: + return dt.replace(day=target_day) + except ValueError: + pass + + # Стараемся найти день, ближайший к target_day + start_day = min(dt.day, target_day) + end_day = max(dt.day, target_day) + + for day in reversed(range(start_day, end_day + 1)): + try: + return dt.replace(day=day) + except ValueError: + pass + + return dt + + +if __name__ == "__main__": + dt = datetime(year=2023, month=1, day=1, hour=12, minute=34, second=56) + + assert isinstance( + correct_datetime(dt, target_year=2023, target_month=1), + datetime, + ) + assert isinstance( + correct_datetime(dt.date(), target_year=2023, target_month=1), + date, + ) + + assert correct_datetime(dt, dt.year, dt.month, dt.day) == dt + assert correct_datetime(dt, dt.year, dt.month, dt.day + 1) == dt.replace( + day=dt.day + 1 + ) + + assert correct_datetime(dt.date(), dt.year, dt.month, dt.day) == dt.date() + assert correct_datetime( + dt.date(), dt.year, dt.month, dt.day + 1 + ) == dt.date().replace(day=dt.day + 1) + + assert correct_datetime( + datetime(year=2023, month=1, day=1, hour=12, minute=34, second=56), + target_year=2025, + target_month=4, + ) == datetime(year=2025, month=4, day=1, hour=12, minute=34, second=56) + assert correct_datetime( + datetime(year=2023, month=1, day=1, hour=12, minute=34, second=56), + target_year=2025, + target_month=4, + target_day=30, + ) == datetime(year=2025, month=4, day=30, hour=12, minute=34, second=56) + assert correct_datetime( + datetime(year=2023, month=1, day=1, hour=12, minute=34, second=56), + target_year=2025, + target_month=4, + target_day=99, + ) == datetime(year=2025, month=4, day=30, hour=12, minute=34, second=56) + + # Leap year + assert correct_datetime( + datetime(year=2024, month=2, day=29, hour=12, minute=34, second=56), + target_year=2025, + target_month=2, + ) == datetime(year=2025, month=2, day=28, hour=12, minute=34, second=56) + + assert correct_datetime( + datetime(year=2024, month=1, day=31, hour=12, minute=34, second=56), + target_year=2024, + target_month=2, + ) == datetime(year=2024, month=2, day=29, hour=12, minute=34, second=56) + assert correct_datetime( + datetime(year=2024, month=1, day=31, hour=12, minute=34, second=56), + target_year=2024, + target_month=2, + target_day=28, + ) == datetime(year=2024, month=2, day=28, hour=12, minute=34, second=56) + assert correct_datetime( + datetime(year=2024, month=2, day=28, hour=12, minute=34, second=56), + target_year=2024, + target_month=1, + target_day=31, + ) == datetime(year=2024, month=1, day=31, hour=12, minute=34, second=56) diff --git a/datetime_example/datetime_example.py b/datetime_example/datetime_example.py index 8e68b2e19..42eef045d 100644 --- a/datetime_example/datetime_example.py +++ b/datetime_example/datetime_example.py @@ -1,4 +1,4 @@ -__author__ = 'ipetrash' +__author__ = "ipetrash" # TODO: больше примеров @@ -6,16 +6,26 @@ # http://pythonworld.ru/moduli/modul-datetime.html -from datetime import date +from datetime import datetime, date, timedelta -if __name__ == '__main__': - # Dates are easily constructed and formatted - now = date.today() - print(now) - print(now.strftime("%m-%d-%y. %d %b %Y is a %A on the %d day of %B.")) +# Dates are easily constructed and formatted +now = datetime.today() +print(now) +print(now.strftime("%m-%d-%y. %d %b %Y is a %A on the %d day of %B.")) - # Dates support calendar arithmetic - birthday = date(1964, 7, 31) - age = now - birthday - print(age.days) +# Dates support calendar arithmetic +birthday: date = datetime(year=1964, month=7, day=31) +age: timedelta = now - birthday +print(age.days) + +print() + +now = date.today() +print(now) +print(now.strftime("%m-%d-%y. %d %b %Y is a %A on the %d day of %B.")) + +# Dates support calendar arithmetic +birthday: date = date(year=1964, month=7, day=31) +age: timedelta = now - birthday +print(age.days) diff --git a/datetime_example/get_human_delta.py b/datetime_example/get_human_delta.py new file mode 100644 index 000000000..d7c13c4d3 --- /dev/null +++ b/datetime_example/get_human_delta.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from datetime import timedelta + + +def get_human_delta(delta: timedelta) -> str: + days = delta.days + + seconds_remainder = delta.seconds + hours, seconds_remainder = divmod(seconds_remainder, 3600) + minutes, seconds = divmod(seconds_remainder, 60) + + years, days = divmod(days, 365) + lines: list[str] = [] + if years > 0: + lines.append(f"{years} year{'s' if years > 1 else ''}") + + if days > 0: + lines.append(f"{days} day{'s' if days > 1 else ''}") + + lines.append(f"{hours:02d}:{minutes:02d}:{seconds:02d}") + return ", ".join(lines) + + +if __name__ == "__main__": + for expected, delta in [ + ("00:00:05", timedelta(seconds=5)), + ("02:30:00", timedelta(hours=2, minutes=30, seconds=0)), + ("02:30:00", timedelta(hours=2, minutes=30, seconds=0, microseconds=777)), + ("1 day, 02:30:00", timedelta(days=1, hours=2, minutes=30, seconds=0)), + ("2 days, 02:30:00", timedelta(days=2, hours=2, minutes=30, seconds=0)), + ("1 year, 35 days, 02:01:00", timedelta(days=400, hours=2, minutes=1)), + ("2 years, 170 days, 02:01:00", timedelta(days=900, hours=2, minutes=1)), + ]: + human_delta: str = get_human_delta(delta) + print(human_delta) + assert expected == human_delta diff --git a/datetime_example/parse_text.py b/datetime_example/parse_text.py index 19f811368..402dccbc9 100644 --- a/datetime_example/parse_text.py +++ b/datetime_example/parse_text.py @@ -1,25 +1,25 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from datetime import datetime -def get_age(text): +def get_age(text: str) -> int: date_text = text.split()[0] - dt = datetime.strptime(date_text, '%d/%m/%Y') + dt = datetime.strptime(date_text, "%d/%m/%Y") years_delta = datetime.today() - dt - return int(years_delta.days / 365) + return years_delta.days // 365 -if __name__ == '__main__': - text = '31/07/1972 (45 years old)' +if __name__ == "__main__": + text = "31/07/1972 (45 years old)" age = get_age(text) print(age) # 45 - text = '18/08/1992' + text = "18/08/1992" age = get_age(text) print(age) # 25 diff --git a/datetime_strptime__and__setlocale.py b/datetime_strptime__and__setlocale.py index 2d4db7baf..1e69345ba 100644 --- a/datetime_strptime__and__setlocale.py +++ b/datetime_strptime__and__setlocale.py @@ -1,27 +1,31 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -import datetime as DT import locale +from datetime import datetime -def check(): +def check() -> None: try: - print(DT.datetime.strptime("Fri, 06-Nov-2020 21:36:45 GMT", "%a, %d-%b-%Y %H:%M:%S %Z")) + print( + datetime.strptime( + "Fri, 06-Nov-2020 21:36:45 GMT", "%a, %d-%b-%Y %H:%M:%S %Z" + ) + ) except Exception as e: - print(f'[-] {e}') + print(f"[-] {e}") check() # 2020-11-06 21:36:45 -locale.setlocale(locale.LC_TIME, 'ru') +locale.setlocale(locale.LC_TIME, "ru") check() # [-] time data 'Fri, 06-Nov-2020 21:36:45 GMT' does not match format '%a, %d-%b-%Y %H:%M:%S %Z' -locale.setlocale(locale.LC_TIME, 'C') +locale.setlocale(locale.LC_TIME, "C") check() # 2020-11-06 21:36:45 diff --git a/decode_base64_with_Incorrect_padding.py b/decode_base64_with_Incorrect_padding.py index e6bf81646..8fea6ebef 100644 --- a/decode_base64_with_Incorrect_padding.py +++ b/decode_base64_with_Incorrect_padding.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://stackoverflow.com/a/32517907/5909792 @@ -11,21 +11,25 @@ def base64_decode(text: str) -> bytes: - text += "=" * (- len(text) % 4) + text += "=" * (-len(text) % 4) return base64.b64decode(text) -if __name__ == '__main__': - bad_b64_string = '7P1pbxvJtjXqfr+/Qnd92huoKrFVk8ABXhUl1+td7iDJ68L3+IBISbTMpYa+pMQq1cH57zeSndjMmZEyqYwYkwN7G7UsU1SMZIQ4Hmb3f9Yq1cNfK41fK7Wd6l5SqSTNyv' +if __name__ == "__main__": + bad_b64_string = "7P1pbxvJtjXqfr+/Qnd92huoKrFVk8ABXhUl1+td7iDJ68L3+IBISbTMpYa+pMQq1cH57zeSndjMmZEyqYwYkwN7G7UsU1SMZIQ4Hmb3f9Yq1cNfK41fK7Wd6l5SqSTNyv" try: print(base64.b64decode(bad_b64_string)) except Exception as e: print(e) # Incorrect padding - print(base64_decode(bad_b64_string)) # b'\xec\xfdio\x1b\xc9\xb65\xea~\xbf\xbfBw}\xda\x1b\xa8*\xb1U\x93\xc0\x01^\x15%\xd7\xeb]\xee \xc9\xeb\xc2\xf7\xf8\x80HI\xb4\xcc\xa5\x86\xbe\xa4\xc4*\xd5\xc1\xf9\xef7\x92\x9d\xd8\xcc\x99\x912\xa9\x8c\x18\x93\x03{\x1b\xb5,ST\x8cd\x848\x1ef\xf7\x7f\xd6*\xd5\xc3_+\x8d_+\xb5\x9d\xea^R\xa9$\xcd\xca' + print(base64_decode(bad_b64_string)) + # b'\xec\xfdio\x1b\xc9\xb65\xea~\xbf\xbfBw}\xda\x1b\xa8*\xb1U\x93\xc0\x01^\x15%\xd7\xeb]\xee \xc9\xeb\xc2\xf7\xf8\x80HI\xb4\xcc\xa5\x86\xbe\xa4\xc4*\xd5\xc1\xf9\xef7\x92\x9d\xd8\xcc\x99\x912\xa9\x8c\x18\x93\x03{\x1b\xb5,ST\x8cd\x848\x1ef\xf7\x7f\xd6*\xd5\xc3_+\x8d_+\xb5\x9d\xea^R\xa9$\xcd\xca' print() - good_b64_string = '7P1pbxvJtjXqfr+/Qnd92huoKrFVk8ABXhUl1+td7iDJ68L3+IBISbTMpYa+pMQq1cH57zeSndjMmZEyqYwYkwN7G7UsU1SMZIQ4Hmb3f9Yq1cNfK41fK7Wd6l5SqSTNyv==' - print(base64.b64decode(good_b64_string)) # b'\xec\xfdio\x1b\xc9\xb65\xea~\xbf\xbfBw}\xda\x1b\xa8*\xb1U\x93\xc0\x01^\x15%\xd7\xeb]\xee \xc9\xeb\xc2\xf7\xf8\x80HI\xb4\xcc\xa5\x86\xbe\xa4\xc4*\xd5\xc1\xf9\xef7\x92\x9d\xd8\xcc\x99\x912\xa9\x8c\x18\x93\x03{\x1b\xb5,ST\x8cd\x848\x1ef\xf7\x7f\xd6*\xd5\xc3_+\x8d_+\xb5\x9d\xea^R\xa9$\xcd\xca' - print(base64_decode(good_b64_string)) # b'\xec\xfdio\x1b\xc9\xb65\xea~\xbf\xbfBw}\xda\x1b\xa8*\xb1U\x93\xc0\x01^\x15%\xd7\xeb]\xee \xc9\xeb\xc2\xf7\xf8\x80HI\xb4\xcc\xa5\x86\xbe\xa4\xc4*\xd5\xc1\xf9\xef7\x92\x9d\xd8\xcc\x99\x912\xa9\x8c\x18\x93\x03{\x1b\xb5,ST\x8cd\x848\x1ef\xf7\x7f\xd6*\xd5\xc3_+\x8d_+\xb5\x9d\xea^R\xa9$\xcd\xca' + good_b64_string = "7P1pbxvJtjXqfr+/Qnd92huoKrFVk8ABXhUl1+td7iDJ68L3+IBISbTMpYa+pMQq1cH57zeSndjMmZEyqYwYkwN7G7UsU1SMZIQ4Hmb3f9Yq1cNfK41fK7Wd6l5SqSTNyv==" + print(base64.b64decode(good_b64_string)) + # b'\xec\xfdio\x1b\xc9\xb65\xea~\xbf\xbfBw}\xda\x1b\xa8*\xb1U\x93\xc0\x01^\x15%\xd7\xeb]\xee \xc9\xeb\xc2\xf7\xf8\x80HI\xb4\xcc\xa5\x86\xbe\xa4\xc4*\xd5\xc1\xf9\xef7\x92\x9d\xd8\xcc\x99\x912\xa9\x8c\x18\x93\x03{\x1b\xb5,ST\x8cd\x848\x1ef\xf7\x7f\xd6*\xd5\xc3_+\x8d_+\xb5\x9d\xea^R\xa9$\xcd\xca' + + print(base64_decode(good_b64_string)) + # b'\xec\xfdio\x1b\xc9\xb65\xea~\xbf\xbfBw}\xda\x1b\xa8*\xb1U\x93\xc0\x01^\x15%\xd7\xeb]\xee \xc9\xeb\xc2\xf7\xf8\x80HI\xb4\xcc\xa5\x86\xbe\xa4\xc4*\xd5\xc1\xf9\xef7\x92\x9d\xd8\xcc\x99\x912\xa9\x8c\x18\x93\x03{\x1b\xb5,ST\x8cd\x848\x1ef\xf7\x7f\xd6*\xd5\xc3_+\x8d_+\xb5\x9d\xea^R\xa9$\xcd\xca' diff --git a/delete_sublist.py b/delete_sublist.py index 8eda227f4..a8bdc3e50 100644 --- a/delete_sublist.py +++ b/delete_sublist.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # http://ru.stackoverflow.com/questions/495034 @@ -18,18 +18,18 @@ def find_sublist(l, m): for i in range(len(l)): try: - if tuple(l[i: i + len(m)]) == tuple(m): + if tuple(l[i : i + len(m)]) == tuple(m): yield i, i + len(m) except IndexError: pass -def delete_sublist(l, m): +def delete_sublist(l, m) -> None: for i, j in find_sublist(l, m): - del l[i: j] + del l[i:j] -print('Before: {}.'.format(l)) +print(f"Before: {l}.") delete_sublist(l, m) -print('After: {}.'.format(l)) +print(f"After: {l}.") diff --git a/deputies_by_factions.py b/deputies_by_factions.py index 60b03922c..6446371b9 100644 --- a/deputies_by_factions.py +++ b/deputies_by_factions.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import sys @@ -12,11 +12,11 @@ browser = RoboBrowser( - user_agent='Mozilla/5.0 (Windows NT 6.1; WOW64; rv:45.0) Gecko/20100101 Firefox/45.0', - parser='lxml' + user_agent="Mozilla/5.0 (Windows NT 6.1; WOW64; rv:45.0) Gecko/20100101 Firefox/45.0", + parser="lxml", ) -url = 'http://www.duma.gov.ru/structure/deputies/?letter=%D0%92%D1%81%D0%B5&by=name&order=asc' +url = "http://www.duma.gov.ru/structure/deputies/?letter=%D0%92%D1%81%D0%B5&by=name&order=asc" browser.open(url) if not browser.response.ok: print(browser.response.status_code, browser.response.reason) @@ -27,21 +27,23 @@ total = 0 # Парсинг таблицы депутатов -for i, tr in enumerate(browser.select('#lists_list_elements_35 tr')[1:], 1): - td_list = tr.select('td') +for i, tr in enumerate(browser.select("#lists_list_elements_35 tr")[1:], 1): + td_list = tr.select("td") user = td_list[1].text faction = td_list[2].text user_by_factions[faction].append(user) total += 1 -print(f'Total: {total}') +print(f"Total: {total}") # Сортировка словаря по количеству человек в партии -for faction, users in sorted(user_by_factions.items(), key=lambda x: len(x[1]), reverse=True): - print(f'{faction} ({len(users)}):') +for faction, users in sorted( + user_by_factions.items(), key=lambda x: len(x[1]), reverse=True +): + print(f"{faction} ({len(users)}):") for i, user in enumerate(users, 1): - print(f' {i}. {user}') + print(f" {i}. {user}") print() diff --git a/design_patterns__examples/Abstract Factory/example.py b/design_patterns__examples/Abstract Factory/example.py index 2b889fe84..5566162ff 100644 --- a/design_patterns__examples/Abstract Factory/example.py +++ b/design_patterns__examples/Abstract Factory/example.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: Design Patterns: Abstract Factory - Абстрактная фабрика @@ -40,22 +40,22 @@ def create_button(self) -> IButton: class WinLabel(ILabel): - def paint(self): + def paint(self) -> None: print("WinLabel") class OSXLabel(ILabel): - def paint(self): + def paint(self) -> None: print("OSXLabel") class WinButton(IButton): - def paint(self): + def paint(self) -> None: print("WinButton") class OSXButton(IButton): - def paint(self): + def paint(self) -> None: print("OSXButton") @@ -75,7 +75,7 @@ def create_button(self) -> IButton: return OSXButton() -if __name__ == '__main__': +if __name__ == "__main__": def random_appearance() -> str: import random return random.choice(["OSX", "Windows", "error"]) diff --git a/design_patterns__examples/Adapter/example__using_composition.py b/design_patterns__examples/Adapter/example__using_composition.py index de3f735a3..008874430 100644 --- a/design_patterns__examples/Adapter/example__using_composition.py +++ b/design_patterns__examples/Adapter/example__using_composition.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: Design Patterns: Adapter - Адаптер @@ -18,17 +18,17 @@ def get_picture(self): class GameConsole: - def create_game_picture(self): - return 'picture from console' + def create_game_picture(self) -> str: + return "picture from console" class Antenna: - def create_wave_picture(self): - return 'picture from wave' + def create_wave_picture(self) -> str: + return "picture from wave" class SourceGameConsoleAdapter(SourceAdapter): - def __init__(self, game_console: GameConsole): + def __init__(self, game_console: GameConsole) -> None: self.game_console = game_console def get_picture(self): @@ -36,7 +36,7 @@ def get_picture(self): class SourceAntennaAdapter(SourceAdapter): - def __init__(self, antenna: Antenna): + def __init__(self, antenna: Antenna) -> None: self.antenna = antenna def get_picture(self): @@ -44,14 +44,14 @@ def get_picture(self): class TV: - def __init__(self, source: SourceAdapter): + def __init__(self, source: SourceAdapter) -> None: self.source = source def show_picture(self): return self.source.get_picture() -if __name__ == '__main__': +if __name__ == "__main__": g = SourceGameConsoleAdapter(GameConsole()) game_tv = TV(g) print(game_tv.show_picture()) diff --git a/design_patterns__examples/Adapter/example__using_inheritance.py b/design_patterns__examples/Adapter/example__using_inheritance.py index f4a7eafa2..6fff68655 100644 --- a/design_patterns__examples/Adapter/example__using_inheritance.py +++ b/design_patterns__examples/Adapter/example__using_inheritance.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: Design Patterns: Adapter - Адаптер @@ -18,13 +18,13 @@ def get_picture(self): class GameConsole: - def create_game_picture(self): - return 'picture from console' + def create_game_picture(self) -> str: + return "picture from console" class Antenna: - def create_wave_picture(self): - return 'picture from wave' + def create_wave_picture(self) -> str: + return "picture from wave" class SourceGameConsoleAdapter(SourceAdapter, GameConsole): @@ -38,14 +38,14 @@ def get_picture(self): class TV: - def __init__(self, source: SourceAdapter): + def __init__(self, source: SourceAdapter) -> None: self.source = source def show_picture(self): return self.source.get_picture() -if __name__ == '__main__': +if __name__ == "__main__": g = SourceGameConsoleAdapter() game_tv = TV(g) print(game_tv.show_picture()) diff --git a/design_patterns__examples/Bridge/example_1/devices/__init__.py b/design_patterns__examples/Bridge/example_1/devices/__init__.py index 06334aa61..f25732790 100644 --- a/design_patterns__examples/Bridge/example_1/devices/__init__.py +++ b/design_patterns__examples/Bridge/example_1/devices/__init__.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' - +__author__ = "ipetrash" diff --git a/design_patterns__examples/Bridge/example_1/devices/device.py b/design_patterns__examples/Bridge/example_1/devices/device.py index d78717460..9a7a82e9c 100644 --- a/design_patterns__examples/Bridge/example_1/devices/device.py +++ b/design_patterns__examples/Bridge/example_1/devices/device.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from abc import ABC, abstractmethod diff --git a/design_patterns__examples/Bridge/example_1/devices/radio.py b/design_patterns__examples/Bridge/example_1/devices/radio.py index d3b88434f..d639c3eba 100644 --- a/design_patterns__examples/Bridge/example_1/devices/radio.py +++ b/design_patterns__examples/Bridge/example_1/devices/radio.py @@ -1,45 +1,45 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from devices.device import Device class Radio(Device): - def __init__(self): + def __init__(self) -> None: self._on = False self._volume = 30 self._channel = 1 - + def is_enabled(self) -> bool: return self._on - - def enable(self): + + def enable(self) -> None: self._on = True - def disable(self): + def disable(self) -> None: self._on = False - + def get_volume(self) -> int: return self._volume - - def set_volume(self, volume: int): + + def set_volume(self, volume: int) -> None: if volume > 100: self._volume = 100 elif volume < 0: self._volume = 0 else: self._volume = volume - + def get_channel(self) -> int: return self._channel - - def set_channel(self, channel: int): + + def set_channel(self, channel: int) -> None: self._channel = channel - - def print_status(self): + + def print_status(self) -> None: print("------------------------------------") print("| I'm radio.") print("| I'm " + ("enabled" if self._on else "disabled")) diff --git a/design_patterns__examples/Bridge/example_1/devices/tv.py b/design_patterns__examples/Bridge/example_1/devices/tv.py index c11592340..5adfc0437 100644 --- a/design_patterns__examples/Bridge/example_1/devices/tv.py +++ b/design_patterns__examples/Bridge/example_1/devices/tv.py @@ -1,45 +1,45 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from .device import Device class Tv(Device): - def __init__(self): + def __init__(self) -> None: self._on = False self._volume = 30 self._channel = 1 - + def is_enabled(self) -> bool: return self._on - - def enable(self): + + def enable(self) -> None: self._on = True - def disable(self): + def disable(self) -> None: self._on = False - + def get_volume(self) -> int: return self._volume - - def set_volume(self, volume: int): + + def set_volume(self, volume: int) -> None: if volume > 100: self._volume = 100 elif volume < 0: self._volume = 0 else: self._volume = volume - + def get_channel(self) -> int: return self._channel - - def set_channel(self, channel: int): + + def set_channel(self, channel: int) -> None: self._channel = channel - - def print_status(self): + + def print_status(self) -> None: print("------------------------------------") print("| I'm TV set.") print("| I'm " + ("enabled" if self._on else "disabled")) diff --git a/design_patterns__examples/Bridge/example_1/main.py b/design_patterns__examples/Bridge/example_1/main.py index bef6de6f3..745a2a1f5 100644 --- a/design_patterns__examples/Bridge/example_1/main.py +++ b/design_patterns__examples/Bridge/example_1/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: Design Patterns: Bridge — Мост @@ -17,7 +17,7 @@ from remotes.advanced_remote import AdvancedRemote -def test_device(device): +def test_device(device) -> None: print("Tests with basic remote.") basic_remote = BasicRemote(device) basic_remote.power() @@ -30,6 +30,6 @@ def test_device(device): device.print_status() -if __name__ == '__main__': +if __name__ == "__main__": test_device(Tv()) test_device(Radio()) diff --git a/design_patterns__examples/Bridge/example_1/remotes/__init__.py b/design_patterns__examples/Bridge/example_1/remotes/__init__.py index 06334aa61..f25732790 100644 --- a/design_patterns__examples/Bridge/example_1/remotes/__init__.py +++ b/design_patterns__examples/Bridge/example_1/remotes/__init__.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' - +__author__ = "ipetrash" diff --git a/design_patterns__examples/Bridge/example_1/remotes/advanced_remote.py b/design_patterns__examples/Bridge/example_1/remotes/advanced_remote.py index 59cd32213..c0367b280 100644 --- a/design_patterns__examples/Bridge/example_1/remotes/advanced_remote.py +++ b/design_patterns__examples/Bridge/example_1/remotes/advanced_remote.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from remotes.basic_remote import BasicRemote @@ -10,6 +10,6 @@ class AdvancedRemote(BasicRemote): """Улучшенный пульт""" - def mute(self): + def mute(self) -> None: print("Remote: mute") self._device.set_volume(0) diff --git a/design_patterns__examples/Bridge/example_1/remotes/basic_remote.py b/design_patterns__examples/Bridge/example_1/remotes/basic_remote.py index 2f7f821f2..80b681269 100644 --- a/design_patterns__examples/Bridge/example_1/remotes/basic_remote.py +++ b/design_patterns__examples/Bridge/example_1/remotes/basic_remote.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from remotes.remote import Remote @@ -11,28 +11,28 @@ class BasicRemote(Remote): """Стандартный пульт""" - def __init__(self, device: Device): + def __init__(self, device: Device) -> None: self._device = device - def power(self): + def power(self) -> None: print("Remote: power toggle") if self._device.is_enabled(): self._device.disable() else: self._device.enable() - - def volume_down(self): + + def volume_down(self) -> None: print("Remote: volume down") self._device.set_volume(self._device.get_volume() - 10) - def volume_up(self): + def volume_up(self) -> None: print("Remote: volume up") self._device.set_volume(self._device.get_volume() + 10) - def channel_down(self): + def channel_down(self) -> None: print("Remote: channel down") self._device.set_channel(self._device.get_channel() - 1) - def channel_up(self): + def channel_up(self) -> None: print("Remote: channel up") self._device.set_channel(self._device.get_channel() + 1) diff --git a/design_patterns__examples/Bridge/example_1/remotes/remote.py b/design_patterns__examples/Bridge/example_1/remotes/remote.py index 3c7b49a51..5baeddbed 100644 --- a/design_patterns__examples/Bridge/example_1/remotes/remote.py +++ b/design_patterns__examples/Bridge/example_1/remotes/remote.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from abc import ABC, abstractmethod diff --git a/design_patterns__examples/Bridge/example_2.py b/design_patterns__examples/Bridge/example_2.py index 1679b0c77..3e2248dd7 100644 --- a/design_patterns__examples/Bridge/example_2.py +++ b/design_patterns__examples/Bridge/example_2.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: Design Patterns: Bridge — Мост @@ -15,47 +15,51 @@ class Drawer(ABC): @abstractmethod def draw_circle(self, x: int, y: int, radius: int): pass - + class SmallCircleDrawer(Drawer): RADIUS_MULTIPLIER = 0.25 - - def draw_circle(self, x: int, y: int, radius: int): - print(f"Small circle center = {x},{y} radius = {radius * self.RADIUS_MULTIPLIER}") + + def draw_circle(self, x: int, y: int, radius: int) -> None: + print( + f"Small circle center = {x},{y} radius = {radius * self.RADIUS_MULTIPLIER}" + ) class LargeCircleDrawer(Drawer): RADIUS_MULTIPLIER = 10 - - def draw_circle(self, x: int, y: int, radius: int): - print(f"Large circle center = {x},{y} radius = {radius * self.RADIUS_MULTIPLIER}") - + + def draw_circle(self, x: int, y: int, radius: int) -> None: + print( + f"Large circle center = {x},{y} radius = {radius * self.RADIUS_MULTIPLIER}" + ) + class Shape(ABC): - def __init__(self, drawer: Drawer): + def __init__(self, drawer: Drawer) -> None: self._drawer = drawer - + @abstractmethod - def draw(self): + def draw(self) -> None: pass - + @abstractmethod - def enlarge_radius(self, multiplier: int): + def enlarge_radius(self, multiplier: int) -> None: pass - + class Circle(Shape): - def __init__(self, x: int, y: int, radius: int, drawer: Drawer): + def __init__(self, x: int, y: int, radius: int, drawer: Drawer) -> None: super().__init__(drawer) self._x = x self._y = y self._radius = radius - def draw(self): + def draw(self) -> None: self._drawer.draw_circle(self._x, self._y, self._radius) - def enlarge_radius(self, multiplier: int): + def enlarge_radius(self, multiplier: int) -> None: self._radius *= multiplier def get_x(self) -> int: @@ -67,17 +71,17 @@ def get_y(self) -> int: def get_radius(self) -> int: return self._radius - def set_x(self, x: int): + def set_x(self, x: int) -> None: self._x = x - def set_y(self, y: int): + def set_y(self, y: int) -> None: self._y = y - def set_radius(self, radius: int): + def set_radius(self, radius: int) -> None: self._radius = radius -if __name__ == '__main__': +if __name__ == "__main__": shapes = [ Circle(5, 10, 10, LargeCircleDrawer()), Circle(20, 30, 100, SmallCircleDrawer()), diff --git a/design_patterns__examples/Builder/example.py b/design_patterns__examples/Builder/example.py index 556645e3f..ea97765c8 100644 --- a/design_patterns__examples/Builder/example.py +++ b/design_patterns__examples/Builder/example.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: Design Patterns: Builder — Строитель @@ -13,91 +13,91 @@ # "Product" class Pizza: - def __init__(self): + def __init__(self) -> None: self._dough = "" self._sauce = "" self._topping = "" - def set_dough(self, dough: str): + def set_dough(self, dough: str) -> None: self._dough = dough - - def set_sauce(self, sauce: str): + + def set_sauce(self, sauce: str) -> None: self._sauce = sauce - def set_topping(self, topping: str): + def set_topping(self, topping: str) -> None: self._topping = topping - def __str__(self): + def __str__(self) -> str: return f'Pizza(dough="{self._dough}, sauce="{self._sauce}, topping="{self._topping}")' # "Abstract Builder" class PizzaBuilder(ABC): - def __init__(self): + def __init__(self) -> None: self._pizza = None def get_pizza(self) -> Pizza: return self._pizza - def create_new_pizza_product(self): + def create_new_pizza_product(self) -> None: self._pizza = Pizza() @abstractmethod - def build_dough(self): + def build_dough(self) -> None: pass @abstractmethod - def build_sauce(self): + def build_sauce(self) -> None: pass @abstractmethod - def build_topping(self): + def build_topping(self) -> None: pass # "ConcreteBuilder" class HawaiianPizzaBuilder(PizzaBuilder): - def build_dough(self): + def build_dough(self) -> None: self._pizza.set_dough("cross") - def build_sauce(self): + def build_sauce(self) -> None: self._pizza.set_sauce("mild") - def build_topping(self): + def build_topping(self) -> None: self._pizza.set_topping("ham+pineapple") # "ConcreteBuilder" class SpicyPizzaBuilder(PizzaBuilder): - def build_dough(self): + def build_dough(self) -> None: self._pizza.set_dough("pan baked") - def build_sauce(self): + def build_sauce(self) -> None: self._pizza.set_sauce("hot") - def build_topping(self): + def build_topping(self) -> None: self._pizza.set_topping("pepperoni+salami") # "Director" class Waiter: - def __init__(self): + def __init__(self) -> None: self.pizza_builder = None - def set_pizza_builder(self, pb: PizzaBuilder): + def set_pizza_builder(self, pb: PizzaBuilder) -> None: self.pizza_builder = pb def get_pizza(self) -> Pizza: return self.pizza_builder.get_pizza() - def construct_pizza(self): + def construct_pizza(self) -> None: self.pizza_builder.create_new_pizza_product() self.pizza_builder.build_dough() self.pizza_builder.build_sauce() self.pizza_builder.build_topping() -if __name__ == '__main__': +if __name__ == "__main__": # A customer ordering a pizza. hawaiianPizzaBuilder = HawaiianPizzaBuilder() diff --git a/design_patterns__examples/Builder/example2.py b/design_patterns__examples/Builder/example2.py new file mode 100644 index 000000000..9acb0c03d --- /dev/null +++ b/design_patterns__examples/Builder/example2.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from typing import Any, Self + + +class Foo: + def __init__(self) -> None: + self.items = [] + self.key_by_value = dict() + + def add_item(self, value) -> Self: + self.items.append(value) + return self + + def add_items(self, values) -> Self: + self.items += values + return self + + def set_value(self, key, value) -> Self: + self.key_by_value[key] = value + return self + + def get_value(self, key) -> Any: + return self.key_by_value[key] + + def __repr__(self) -> str: + return f"Foo" + + +foo = Foo().add_item(1).add_items("abc").set_value("x", 1).set_value("x[]", [1, 2, 3]) + +print(foo) +print(foo.items) +print(foo.key_by_value) +print(foo.get_value("x")) +print(foo.get_value("x[]")) diff --git a/design_patterns__examples/Builder/example_wok.py b/design_patterns__examples/Builder/example_wok.py index e961d901c..45e91d4f3 100644 --- a/design_patterns__examples/Builder/example_wok.py +++ b/design_patterns__examples/Builder/example_wok.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: Design Patterns: Builder — Строитель @@ -54,56 +54,92 @@ class WokTopping(NamedTuple): weight: int = 5 -WokUdon = WokBase.Factory.create(name='Удон', description='Пшеничная лапша + свежие овощи') -WokUdonSmall = WokBase.Factory.create_small(name='Удон (эконом)', description='Пшеничная лапша + свежие овощи') - -WokSoba = WokBase.Factory.create(name='Соба', description='Гречневая лапша + свежие овощи') -WokSobaSmall = WokBase.Factory.create_small(name='Соба (эконом)', description='Гречневая лапша + свежие овощи') - -WokRamen = WokBase.Factory.create_small(name='Рамен', description='Яичная лапша + свежие овощи') -WokRamenSmall = WokBase.Factory.create_small(name='Рамен (эконом)', description='Яичная лапша + свежие овощи') - -WokHarusame = WokBase.Factory.create(name='Харусаме', description='Рисовая лапша + свежие овощи') -WokHarusameSmall = WokBase.Factory.create_small(name='Харусаме (эконом)', description='Рисовая лапша + свежие овощи') - -WokRice = WokBase.Factory.create(name='Рис', description='Цельнозерный рис + свежие овощи') -WokRiceSmall = WokBase.Factory.create_small(name='Рис (эконом)', description='Цельнозерный рис + свежие овощи') - - -WokAdditiveChicken = WokAdditive.Factory.create(name='Курица', price=58) -WokAdditiveChickenSmall = WokAdditive.Factory.create_small(name='Курица (эконом)', price=38) - -WokAdditiveBeef = WokAdditive.Factory.create(name='Говядина', price=84) -WokAdditiveBeefSmall = WokAdditive.Factory.create_small(name='Говядина (эконом)', price=64) - -WokAdditiveShrimp = WokAdditive.Factory.create(name='Креветка', price=109) -WokAdditiveShrimpSmall = WokAdditive.Factory.create_small(name='Креветка (эконом)', price=89) - -WokAdditiveSquid = WokAdditive.Factory.create(name='Кальмар', price=95) -WokAdditiveSquidSmall = WokAdditive.Factory.create_small(name='Кальмар (эконом)', price=75) - -WokAdditiveSeafood = WokAdditive.Factory.create(name='Морепродукты', price=94) -WokAdditiveSeafoodSmall = WokAdditive.Factory.create_small(name='Морепродукты (эконом)', price=74) - -WokAdditiveSalmon = WokAdditive.Factory.create(name='Лосось', price=102) -WokAdditiveSalmonSmall = WokAdditive.Factory.create_small(name='Лосось (эконом)', price=82) - -WokAdditiveShiitakeMushrooms = WokAdditive.Factory.create(name='Грибы Шиитаке', price=44) -WokAdditiveShiitakeMushroomsSmall = WokAdditive.Factory.create_small(name='Грибы Шиитаке (эконом)', price=24) - - -WokSauceCreamy = WokSauce(name='Сливочный соус') -WokSauceOyster = WokSauce(name='Устричный соус') -WokSauceCheese = WokSauce(name='Сырный соус') -WokSauceTeriyaki = WokSauce(name='Терияки соус') -WokSauceAcute = WokSauce(name='Острый соус') - - -WokToppingFriedOnions = WokTopping(name='Лук обжаренный') -WokToppingGreenOnion = WokTopping(name='Лук зеленый') -WokToppingRoastedSesame = WokTopping(name='Кунжут обжаренный') -WokToppingChilli = WokTopping(name='Перец чили') -WokToppingParmesanCheese = WokTopping(name='Сыр пармезан') +WokUdon = WokBase.Factory.create( + name="Удон", description="Пшеничная лапша + свежие овощи" +) +WokUdonSmall = WokBase.Factory.create_small( + name="Удон (эконом)", description="Пшеничная лапша + свежие овощи" +) + +WokSoba = WokBase.Factory.create( + name="Соба", description="Гречневая лапша + свежие овощи" +) +WokSobaSmall = WokBase.Factory.create_small( + name="Соба (эконом)", description="Гречневая лапша + свежие овощи" +) + +WokRamen = WokBase.Factory.create_small( + name="Рамен", description="Яичная лапша + свежие овощи" +) +WokRamenSmall = WokBase.Factory.create_small( + name="Рамен (эконом)", description="Яичная лапша + свежие овощи" +) + +WokHarusame = WokBase.Factory.create( + name="Харусаме", description="Рисовая лапша + свежие овощи" +) +WokHarusameSmall = WokBase.Factory.create_small( + name="Харусаме (эконом)", description="Рисовая лапша + свежие овощи" +) + +WokRice = WokBase.Factory.create( + name="Рис", description="Цельнозерный рис + свежие овощи" +) +WokRiceSmall = WokBase.Factory.create_small( + name="Рис (эконом)", description="Цельнозерный рис + свежие овощи" +) + + +WokAdditiveChicken = WokAdditive.Factory.create(name="Курица", price=58) +WokAdditiveChickenSmall = WokAdditive.Factory.create_small( + name="Курица (эконом)", price=38 +) + +WokAdditiveBeef = WokAdditive.Factory.create(name="Говядина", price=84) +WokAdditiveBeefSmall = WokAdditive.Factory.create_small( + name="Говядина (эконом)", price=64 +) + +WokAdditiveShrimp = WokAdditive.Factory.create(name="Креветка", price=109) +WokAdditiveShrimpSmall = WokAdditive.Factory.create_small( + name="Креветка (эконом)", price=89 +) + +WokAdditiveSquid = WokAdditive.Factory.create(name="Кальмар", price=95) +WokAdditiveSquidSmall = WokAdditive.Factory.create_small( + name="Кальмар (эконом)", price=75 +) + +WokAdditiveSeafood = WokAdditive.Factory.create(name="Морепродукты", price=94) +WokAdditiveSeafoodSmall = WokAdditive.Factory.create_small( + name="Морепродукты (эконом)", price=74 +) + +WokAdditiveSalmon = WokAdditive.Factory.create(name="Лосось", price=102) +WokAdditiveSalmonSmall = WokAdditive.Factory.create_small( + name="Лосось (эконом)", price=82 +) + +WokAdditiveShiitakeMushrooms = WokAdditive.Factory.create( + name="Грибы Шиитаке", price=44 +) +WokAdditiveShiitakeMushroomsSmall = WokAdditive.Factory.create_small( + name="Грибы Шиитаке (эконом)", price=24 +) + + +WokSauceCreamy = WokSauce(name="Сливочный соус") +WokSauceOyster = WokSauce(name="Устричный соус") +WokSauceCheese = WokSauce(name="Сырный соус") +WokSauceTeriyaki = WokSauce(name="Терияки соус") +WokSauceAcute = WokSauce(name="Острый соус") + + +WokToppingFriedOnions = WokTopping(name="Лук обжаренный") +WokToppingGreenOnion = WokTopping(name="Лук зеленый") +WokToppingRoastedSesame = WokTopping(name="Кунжут обжаренный") +WokToppingChilli = WokTopping(name="Перец чили") +WokToppingParmesanCheese = WokTopping(name="Сыр пармезан") class Wok: @@ -113,29 +149,25 @@ class Wok: sauce_additional: WokSauce = None topping: List[WokTopping] = None - def __init__(self): + def __init__(self) -> None: self.topping = [] def get_order_text(self) -> str: - """ Текст заказа """ + """Текст заказа""" - text = 'Заказ:\n' + text = "Заказ:\n" for item in self.get_order_items(): - text += f' {item.name:25} : {item.price} рублей\n' + text += f" {item.name:25} : {item.price} рублей\n" - text += ' {:25} : {} рублей'.format('', self.get_order_price()) + text += f" {'':25} : {self.get_order_price()} рублей" return text def get_order_items(self) -> list: - """ Элементы заказа """ + """Элементы заказа""" - items = [ - self.base, - self.additive, - self.sauce - ] + items = [self.base, self.additive, self.sauce] if self.sauce_additional: items.append(self.sauce_additional) @@ -145,87 +177,95 @@ def get_order_items(self) -> list: return items def get_order_price(self) -> int: - """ Стоимость заказа """ + """Стоимость заказа""" return sum(x.price for x in self.get_order_items()) def get_order_weight(self) -> int: - """ Примерный вес заказа """ + """Примерный вес заказа""" return sum(x.weight for x in self.get_order_items()) class Builder: - def __init__(self): + def __init__(self) -> None: self.wok = Wok() - def set_base(self, base: WokBase) -> 'Builder': - """ Основа """ + def set_base(self, base: WokBase) -> "Builder": + """Основа""" self.wok.base = base return self - def set_additive(self, additive: WokAdditive) -> 'Builder': - """ Добавка """ + def set_additive(self, additive: WokAdditive) -> "Builder": + """Добавка""" self.wok.additive = additive return self - def set_sauce(self, sauce: WokSauce) -> 'Builder': - """ Соус """ + def set_sauce(self, sauce: WokSauce) -> "Builder": + """Соус""" self.wok.sauce = sauce._replace(price=0) return self - def set_sauce_additional(self, sauce: WokSauce) -> 'Builder': - """ Дополнительный соус """ + def set_sauce_additional(self, sauce: WokSauce) -> "Builder": + """Дополнительный соус""" self.wok.sauce_additional = sauce return self - def add_topping(self, topping: WokTopping) -> 'Builder': - """ Топпинг """ + def add_topping(self, topping: WokTopping) -> "Builder": + """Топпинг""" self.wok.topping.append(topping) return self - def build(self) -> 'Wok': + def build(self) -> "Wok": return self.wok class WokDirector: @staticmethod def make_economy() -> Wok: - return Wok.Builder()\ - .set_base(WokSoba) \ - .set_additive(WokAdditiveChickenSmall) \ - .set_sauce(WokSauceCheese) \ + return ( + Wok.Builder() + .set_base(WokSoba) + .set_additive(WokAdditiveChickenSmall) + .set_sauce(WokSauceCheese) .build() + ) @staticmethod def make_business_lunch() -> Wok: - return Wok.Builder()\ - .set_base(WokUdon) \ - .set_additive(WokAdditiveChicken) \ - .set_sauce(WokSauceCreamy) \ + return ( + Wok.Builder() + .set_base(WokUdon) + .set_additive(WokAdditiveChicken) + .set_sauce(WokSauceCreamy) .build() + ) @staticmethod def make_vegetarian() -> Wok: - return Wok.Builder()\ - .set_base(WokUdon) \ - .set_additive(WokAdditiveShiitakeMushrooms) \ - .set_sauce(WokSauceTeriyaki) \ - .add_topping(WokToppingGreenOnion) \ + return ( + Wok.Builder() + .set_base(WokUdon) + .set_additive(WokAdditiveShiitakeMushrooms) + .set_sauce(WokSauceTeriyaki) + .add_topping(WokToppingGreenOnion) .build() + ) -if __name__ == '__main__': - wok = Wok.Builder()\ - .set_base(WokUdon)\ - .set_additive(WokAdditiveBeef)\ - .set_sauce(WokSauceCheese)\ +if __name__ == "__main__": + wok = ( + Wok.Builder() + .set_base(WokUdon) + .set_additive(WokAdditiveBeef) + .set_sauce(WokSauceCheese) .build() - print(wok.get_order_price()) # 213 + ) + print(wok.get_order_price()) # 213 print(wok.get_order_weight()) # 350 print(wok.get_order_text()) # Заказ: @@ -235,15 +275,17 @@ def make_vegetarian() -> Wok: # : 213 рублей print() - wok = Wok.Builder()\ - .set_base(WokSoba)\ - .set_additive(WokAdditiveBeef)\ - .set_sauce(WokSauceCheese)\ - .add_topping(WokToppingFriedOnions)\ - .add_topping(WokToppingGreenOnion)\ - .add_topping(WokToppingChilli)\ + wok = ( + Wok.Builder() + .set_base(WokSoba) + .set_additive(WokAdditiveBeef) + .set_sauce(WokSauceCheese) + .add_topping(WokToppingFriedOnions) + .add_topping(WokToppingGreenOnion) + .add_topping(WokToppingChilli) .build() - print(wok.get_order_price()) # 243 + ) + print(wok.get_order_price()) # 243 print(wok.get_order_weight()) # 365 print(wok.get_order_text()) # Заказ: @@ -255,7 +297,7 @@ def make_vegetarian() -> Wok: # Перец чили : 10 рублей # : 243 рублей - print('\n') + print("\n") print(WokDirector.make_economy().get_order_text()) print(WokDirector.make_business_lunch().get_order_text()) diff --git a/design_patterns__examples/Chain of responsibility/example_1.py b/design_patterns__examples/Chain of responsibility/example_1.py index 9f27c3a26..388756434 100644 --- a/design_patterns__examples/Chain of responsibility/example_1.py +++ b/design_patterns__examples/Chain of responsibility/example_1.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: Design Patterns: Chain of responsibility — Цепочка обязанностей @@ -18,17 +18,17 @@ class Handler(ABC): Он также объявляет метод для обработки. """ - _next_handler: 'Handler' = None + _next_handler: "Handler" = None - def set_next(self, handler: 'Handler') -> 'Handler': + def set_next(self, handler: "Handler") -> "Handler": self._next_handler = handler return self._next_handler @abstractmethod - def handle(self, obj): + def handle(self, obj) -> None: pass - def next_handle(self, obj): + def next_handle(self, obj) -> None: # Вызываем следующий обработки if self._next_handler: self._next_handler.handle(obj) @@ -43,7 +43,7 @@ def handle(self, obj): class IsNotStringHandler(Handler): - def handle(self, obj): + def handle(self, obj) -> None: if type(obj) != str: raise Exception(f"Object {repr(obj)} is not string!") @@ -51,35 +51,36 @@ def handle(self, obj): class IsNotMatchReHandler(Handler): - def __init__(self, re_pattern: str): + def __init__(self, re_pattern: str) -> None: self._re_pattern = re_pattern - def handle(self, obj): + def handle(self, obj) -> None: if not re.search(self._re_pattern, obj): - raise Exception(f'String {repr(obj)} is not matching by regexp: "{self._re_pattern}"!') + raise Exception( + f'String {repr(obj)} is not matching by regexp: "{self._re_pattern}"!' + ) self.next_handle(obj) -if __name__ == '__main__': - def client_code(handler: Handler): - for obj in [None, '123', '456', '111', 456]: - print(f'Object {repr(obj)} is ', end='') +if __name__ == "__main__": + def client_code(handler: Handler) -> None: + for obj in [None, "123", "456", "111", 456]: + print(f"Object {repr(obj)} is ", end="") try: handler.handle(obj) - print('ok!') + print("ok!") except Exception as e: print(f'fail -> "{e}"') - is_not_none = IsNotNoneHandler() is_not_string = IsNotStringHandler() - is_not_match_re_1 = IsNotMatchReHandler("\d") + is_not_match_re_1 = IsNotMatchReHandler(r"\d") is_not_match_re_2 = IsNotMatchReHandler("1..") - is_not_match_re_3 = IsNotMatchReHandler("\d{3}") + is_not_match_re_3 = IsNotMatchReHandler(r"\d{3}") is_not_match_re_1.set_next(is_not_match_re_2).set_next(is_not_match_re_3) # Check only None diff --git a/design_patterns__examples/Chain of responsibility/example_2.py b/design_patterns__examples/Chain of responsibility/example_2.py index 3a440a4cf..b1b310fe3 100644 --- a/design_patterns__examples/Chain of responsibility/example_2.py +++ b/design_patterns__examples/Chain of responsibility/example_2.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: Design Patterns: Chain of responsibility — Цепочка обязанностей @@ -10,44 +10,44 @@ class Car: - def __init__(self): + def __init__(self) -> None: self.name = None self.km = 11100 self.fuel = 5 self.oil = 5 -def handle_fuel(car): +def handle_fuel(car) -> None: if car.fuel < 10: print("Added fuel") car.fuel = 100 -def handle_km(car): +def handle_km(car) -> None: if car.km > 10000: print("Made a car test.") car.km = 0 -def handle_oil(car): +def handle_oil(car) -> None: if car.oil < 10: print("Added oil") car.oil = 100 class Garage: - def __init__(self): + def __init__(self) -> None: self.handlers = [] - def add_handler(self, handler): + def add_handler(self, handler) -> None: self.handlers.append(handler) - def handle_car(self, car): + def handle_car(self, car) -> None: for handler in self.handlers: handler(car) -if __name__ == '__main__': +if __name__ == "__main__": handlers = [handle_fuel, handle_km, handle_oil] garage = Garage() diff --git a/design_patterns__examples/Chain of responsibility/example_3.py b/design_patterns__examples/Chain of responsibility/example_3.py index f1877f420..7b6461f84 100644 --- a/design_patterns__examples/Chain of responsibility/example_3.py +++ b/design_patterns__examples/Chain of responsibility/example_3.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: Design Patterns: Chain of responsibility — Цепочка обязанностей @@ -22,6 +22,7 @@ class LogLevel(IntEnum): """ Log Levels Enum. """ + NONE = auto() INFO = auto() DEBUG = auto() @@ -37,7 +38,7 @@ class Logger(ABC): Abstract handler in chain of responsibility pattern. """ - def __init__(self, levels: List[LogLevel]): + def __init__(self, levels: List[LogLevel]) -> None: """ Initialize new logger @@ -48,7 +49,7 @@ def __init__(self, levels: List[LogLevel]): self._next = None self._log_levels = list(levels) - def set_next(self, next_logger: 'Logger') -> 'Logger': + def set_next(self, next_logger: "Logger") -> "Logger": """ Set next responsible logger in the chain. @@ -61,7 +62,7 @@ def set_next(self, next_logger: 'Logger') -> 'Logger': self._next = next_logger return self._next - def message(self, msg: str, severity: LogLevel): + def message(self, msg: str, severity: LogLevel) -> None: """ Message writer handler. @@ -90,7 +91,7 @@ def write_message(self, msg: str): class ConsoleLogger(Logger): - def write_message(self, msg: str): + def write_message(self, msg: str) -> None: """ Overrides parent's abstract method to write to console. @@ -107,7 +108,8 @@ class EmailLogger(Logger): Args: msg (str): Message string. """ - def write_message(self, msg: str): + + def write_message(self, msg: str) -> None: print("Sending via email:", msg) @@ -118,11 +120,12 @@ class FileLogger(Logger): Args: msg (str): Message string. """ - def write_message(self, msg: str): + + def write_message(self, msg: str) -> None: print("Writing to log file:", msg) -def main(): +def main() -> None: """ Building the chain of responsibility. """ diff --git a/design_patterns__examples/Chain of responsibility/example_4.py b/design_patterns__examples/Chain of responsibility/example_4.py index 0bd1e40e7..4e1f86e83 100644 --- a/design_patterns__examples/Chain of responsibility/example_4.py +++ b/design_patterns__examples/Chain of responsibility/example_4.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: Design Patterns: Chain of responsibility — Цепочка обязанностей @@ -14,7 +14,7 @@ # Вспомогательный класс, описывающий некоторое преступление class CriminalAction: - def __init__(self, complexity: int, description: str): + def __init__(self, complexity: int, description: str) -> None: # Сложность дела self.complexity = complexity @@ -24,10 +24,10 @@ def __init__(self, complexity: int, description: str): # Абстрактный полицейский, который может заниматься расследованием преступлений class Policeman(ABC): - def __init__(self, deduction: int): + def __init__(self, deduction: int) -> None: # Дедукция (умение распутывать сложные дела) у данного полицейского self.deduction = deduction - + # Более умелый полицейский, который получит дело, если для текущего оно слишком сложное self.next: Policeman = None @@ -38,45 +38,55 @@ def _investigate_сoncrete(self, description: str): # Добавляет в цепочку ответственности более опытного полицейского, который сможет принять на себя # расследование, если текущий не справится - def set_next(self, policeman: 'Policeman') -> 'Policeman': + def set_next(self, policeman: "Policeman") -> "Policeman": self.next = policeman return self.next # Полицейский начинает расследование или, если дело слишком сложное, передает ее более опытному коллеге - def investigate(self, criminal_action: CriminalAction): + def investigate(self, criminal_action: CriminalAction) -> None: if self.deduction < criminal_action.complexity: if self.next: self.next.investigate(criminal_action) else: print("Это дело не раскрыть никому.") - + else: self._investigate_сoncrete(criminal_action.description) class MartinRiggs(Policeman): - def _investigate_сoncrete(self, description: str): - print("Расследование по делу \"" + description + "\" ведет сержант Мартин Риггс") + def _investigate_сoncrete(self, description: str) -> None: + print('Расследование по делу "' + description + '" ведет сержант Мартин Риггс') class JohnMcClane(Policeman): - def _investigate_сoncrete(self, description: str): - print("Расследование по делу \"" + description + "\" ведет детектив Джон Макклейн") + def _investigate_сoncrete(self, description: str) -> None: + print( + 'Расследование по делу "' + description + '" ведет детектив Джон Макклейн' + ) class VincentHanna(Policeman): - def _investigate_сoncrete(self, description: str): - print("Расследование по делу \"" + description + "\" ведет лейтенант Винсент Ханна") + def _investigate_сoncrete(self, description: str) -> None: + print( + 'Расследование по делу "' + description + '" ведет лейтенант Винсент Ханна' + ) -if __name__ == '__main__': +if __name__ == "__main__": print("OUTPUT:") policeman = MartinRiggs(3) # Полицейский с наименьшим навыком ведения расследований # Добавляем ему двух опытных коллег policeman.set_next(JohnMcClane(5)).set_next(VincentHanna(8)) - policeman.investigate(CriminalAction(2, "Торговля наркотиками из Вьетнама")) - policeman.investigate(CriminalAction(7, "Дерзкое ограбление банка в центре Лос-Анджелеса")) - policeman.investigate(CriminalAction(5, "Серия взрывов в центре Нью-Йорка")) + policeman.investigate( + CriminalAction(2, "Торговля наркотиками из Вьетнама") + ) + policeman.investigate( + CriminalAction(7, "Дерзкое ограбление банка в центре Лос-Анджелеса") + ) + policeman.investigate( + CriminalAction(5, "Серия взрывов в центре Нью-Йорка") + ) # OUTPUT: # Расследование по делу "Торговля наркотиками из Вьетнама" ведет сержант Мартин Риггс diff --git a/design_patterns__examples/Chain of responsibility/example_5.py b/design_patterns__examples/Chain of responsibility/example_5.py index e80e0f892..bec7ed0cc 100644 --- a/design_patterns__examples/Chain of responsibility/example_5.py +++ b/design_patterns__examples/Chain of responsibility/example_5.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: Design Patterns: Chain of responsibility — Цепочка обязанностей @@ -20,7 +20,7 @@ class Handler(ABC): """ @abstractmethod - def set_next(self, handler: 'Handler') -> 'Handler': + def set_next(self, handler: "Handler") -> "Handler": pass @abstractmethod @@ -89,7 +89,7 @@ def client_code(handler: Handler) -> None: """ for food in ["Nut", "Banana", "Cup of coffee"]: - print(f'\nClient: Who wants a {food}?') + print(f"\nClient: Who wants a {food}?") result = handler.handle(food) if result: print(f" {result}", end="") @@ -103,11 +103,11 @@ def print_chain(handler: Handler) -> None: chain = [] while handler: - name = handler.__class__.__name__.replace('Handler', '') + name = handler.__class__.__name__.replace("Handler", "") chain.append(name) handler = handler._next_handler - print('Chain: ' + ' > '.join(chain)) + print("Chain: " + " > ".join(chain)) if __name__ == "__main__": @@ -127,7 +127,7 @@ def print_chain(handler: Handler) -> None: # Клиент должен иметь возможность отправлять запрос любому обработчику, а не # только первому в цепочке. - print('OUTPUT:') + print("OUTPUT:") print("Chain: Monkey > Squirrel > Dog") client_code(monkey) diff --git a/design_patterns__examples/Chain of responsibility/example_6.py b/design_patterns__examples/Chain of responsibility/example_6.py index b8e2c7458..0b5be3e07 100644 --- a/design_patterns__examples/Chain of responsibility/example_6.py +++ b/design_patterns__examples/Chain of responsibility/example_6.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: Design Patterns: Chain of responsibility — Цепочка обязанностей @@ -20,33 +20,33 @@ def get_timestamp() -> int: # Базовый класс цепочки. class Middleware(ABC): - def __init__(self): - self._next: 'Middleware' = None - + def __init__(self) -> None: + self._next: "Middleware" = None + # Помогает строить цепь из объектов-проверок. - def link_with(self, next: 'Middleware') -> 'Middleware': + def link_with(self, next: "Middleware") -> "Middleware": self._next = next return next - + # Подклассы реализуют в этом методе конкретные проверки. @abstractmethod def check(self, email: str, password: str) -> bool: pass - + # Запускает проверку в следующем объекте или завершает проверку, если мы в # последнем элементе цепи. def _check_next(self, email: str, password: str) -> bool: if not self._next: return True - + return self._next.check(email, password) # Конкретный элемент цепи обрабатывает запрос по-своему. class ThrottlingMiddleware(Middleware): - def __init__(self, request_per_minute: int): + def __init__(self, request_per_minute: int) -> None: super().__init__() - + self._request: int = 0 self._request_per_minute: int = request_per_minute self._current_time: int = get_timestamp() @@ -62,30 +62,30 @@ def check(self, email: str, password: str) -> bool: self._current_time = get_timestamp() self._request += 1 - + if self._request > self._request_per_minute: print("Request limit exceeded!") return False - + return self._check_next(email, password) # Конкретный элемент цепи обрабатывает запрос по-своему. class UserExistsMiddleware(Middleware): - def __init__(self, server: 'Server'): + def __init__(self, server: "Server") -> None: super().__init__() - + self._server: Server = server def check(self, email: str, password: str) -> bool: if not self._server.has_email(email): print("This email is not registered!") return False - + if not self._server.is_valid_password(email, password): print("Wrong password!") return False - + return self._check_next(email, password) @@ -95,22 +95,22 @@ def check(self, email: str, password: str) -> bool: if email == "admin@example.com": print("Hello, admin!") return True - + print("Hello, user!") return self._check_next(email, password) # Класс сервера. class Server: - def __init__(self): + def __init__(self) -> None: self._users: Dict[str, str] = dict() self._middleware: Middleware = None - + # Клиент подаёт готовую цепочку в сервер. Это увеличивает гибкость и # упрощает тестирование класса сервера. - def set_middleware(self, middleware: Middleware): + def set_middleware(self, middleware: Middleware) -> None: self._middleware = middleware - + # Сервер получает email и пароль от клиента и запускает проверку # авторизации у цепочки. def log_in(self, email: str, password: str) -> bool: @@ -120,10 +120,10 @@ def log_in(self, email: str, password: str) -> bool: # Здесь должен быть какой-то полезный код, работающий для # авторизированных пользователей. return True - + return False - def register(self, email: str, password: str): + def register(self, email: str, password: str) -> None: self._users[email] = password def has_email(self, email: str) -> bool: @@ -131,9 +131,9 @@ def has_email(self, email: str) -> bool: def is_valid_password(self, email: str, password: str) -> bool: return self._users.get(email) == password - -if __name__ == '__main__': + +if __name__ == "__main__": server = Server() server.register("admin@example.com", "admin_pass") server.register("user@example.com", "user_pass") @@ -142,14 +142,12 @@ def is_valid_password(self, email: str, password: str) -> bool: # Проверки связаны в одну цепь. Клиент может строить различные цепи, # используя одни и те же компоненты. middleware = ThrottlingMiddleware(request_per_minute=2) - middleware\ - .link_with(UserExistsMiddleware(server))\ - .link_with(RoleCheckMiddleware()) + middleware.link_with(UserExistsMiddleware(server)).link_with(RoleCheckMiddleware()) # Сервер получает цепочку от клиентского кода. server.set_middleware(middleware) - - print('OUTPUT:') + + print("OUTPUT:") while True: email = input("Enter email: ") diff --git a/design_patterns__examples/Command/example.py b/design_patterns__examples/Command/example.py new file mode 100644 index 000000000..e21d52dd6 --- /dev/null +++ b/design_patterns__examples/Command/example.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +# SOURCE: Design Patterns: Command — Команда +# SOURCE: https://ru.wikipedia.org/wiki/Команда_(шаблон_проектирования) +# SOURCE: https://javarush.ru/groups/posts/584-patternih-proektirovanija + + +from abc import ABC, abstractmethod + + +class Command(ABC): + @abstractmethod + def execute(self): + pass + + +class Car: + def start_engine(self) -> None: + print("Запустить двигатель") + + def stop_engine(self) -> None: + print("Остановить двигатель") + + +class StartCar(Command): + def __init__(self, car: Car) -> None: + self.car: Car = car + + def execute(self) -> None: + self.car.start_engine() + + +class StopCar(Command): + def __init__(self, car: Car) -> None: + self.car: Car = car + + def execute(self) -> None: + self.car.stop_engine() + + +class CarInvoker: + def __init__(self, command: Command) -> None: + self.command: Command = command + + def execute(self) -> None: + self.command.execute() + + +if __name__ == "__main__": + car = Car() + + startCar = StartCar(car) + stopCar = StopCar(car) + + carInvoker = CarInvoker(startCar) + carInvoker.execute() + + stopCar.execute() diff --git a/design_patterns__examples/Composite/example.py b/design_patterns__examples/Composite/example.py index 6c10f9f88..0c3b6fc2c 100644 --- a/design_patterns__examples/Composite/example.py +++ b/design_patterns__examples/Composite/example.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: Design Patterns: Composite - Компоновщик @@ -19,69 +19,69 @@ def draw(self, *args, **kwargs): class CompositeGraphic(Graphic): - def __init__(self): + def __init__(self) -> None: self._child_graphics: List[Graphic] = [] - def draw(self, *args, **kwargs): + def draw(self, *args, **kwargs) -> None: for graphic in self._child_graphics: graphic.draw(*args, **kwargs) # Adds the graphic to the composition - def add(self, graphic: Graphic): + def add(self, graphic: Graphic) -> None: if graphic in self._child_graphics: return self._child_graphics.append(graphic) # Removes the graphic from the composition - def remove(self, graphic: Graphic): + def remove(self, graphic: Graphic) -> None: self._child_graphics.remove(graphic) class Ellipse(Graphic): - def __init__(self, x, y, rx, ry): + def __init__(self, x, y, rx, ry) -> None: self.x = x self.y = y self.rx = rx self.ry = ry - def draw(self, *args, **kwargs): - print(f'Ellipse: x={self.x}, y={self.y}, rx={self.rx}, ry={self.ry}') + def draw(self, *args, **kwargs) -> None: + print(f"Ellipse: x={self.x}, y={self.y}, rx={self.rx}, ry={self.ry}") class Point(Graphic): - def __init__(self, x, y): + def __init__(self, x, y) -> None: self.x = x self.y = y - def draw(self, *args, **kwargs): - print(f'Point: x={self.x}, y={self.y}') + def draw(self, *args, **kwargs) -> None: + print(f"Point: x={self.x}, y={self.y}") class Rect(Graphic): - def __init__(self, x, y, w, h): + def __init__(self, x, y, w, h) -> None: self.x = x self.y = y self.w = w self.h = h - def draw(self, *args, **kwargs): - print(f'Rect: x={self.x}, y={self.y}, w={self.w}, h={self.h}') + def draw(self, *args, **kwargs) -> None: + print(f"Rect: x={self.x}, y={self.y}, w={self.w}, h={self.h}") class Line(Graphic): - def __init__(self, x1, y1, x2, y2): + def __init__(self, x1, y1, x2, y2) -> None: self.x1 = x1 self.y1 = y1 self.x2 = x2 self.y2 = y2 - def draw(self, *args, **kwargs): - print(f'Line: x1={self.x1}, y1={self.y1}, x2={self.x2}, y2={self.y2}') + def draw(self, *args, **kwargs) -> None: + print(f"Line: x1={self.x1}, y1={self.y1}, x2={self.x2}, y2={self.y2}") -if __name__ == '__main__': +if __name__ == "__main__": ellipse_1 = Ellipse(x=50, y=50, rx=10, ry=15) ellipse_2 = Ellipse(x=10, y=5, rx=40, ry=50) diff --git a/design_patterns__examples/Composite/example__pyqt_draw.py b/design_patterns__examples/Composite/example__pyqt_draw.py index a672a6d23..a9e97de75 100644 --- a/design_patterns__examples/Composite/example__pyqt_draw.py +++ b/design_patterns__examples/Composite/example__pyqt_draw.py @@ -1,118 +1,119 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: Design Patterns: Composite - Компоновщик # SOURCE: https://ru.wikipedia.org/wiki/Компоновщик_(шаблон_проектирования) +import traceback +import sys + from abc import ABC, abstractmethod from typing import List from PyQt5.Qt import * -def log_uncaught_exceptions(ex_cls, ex, tb): - text = '{}: {}:\n'.format(ex_cls.__name__, ex) - import traceback - text += ''.join(traceback.format_tb(tb)) +def log_uncaught_exceptions(ex_cls, ex, tb) -> None: + text = f"{ex_cls.__name__}: {ex}:\n" + text += "".join(traceback.format_tb(tb)) print(text) - QMessageBox.critical(None, 'Error', text) + QMessageBox.critical(None, "Error", text) sys.exit(1) -import sys sys.excepthook = log_uncaught_exceptions class Graphic(ABC): @abstractmethod - def draw(self, painter: QPainter): + def draw(self, painter: QPainter) -> None: pass class CompositeGraphic(Graphic): - def __init__(self): + def __init__(self) -> None: self._child_graphics: List[Graphic] = [] - def draw(self, painter: QPainter): + def draw(self, painter: QPainter) -> None: for graphic in self._child_graphics: graphic.draw(painter) # Adds the graphic to the composition - def add(self, graphic: Graphic): + def add(self, graphic: Graphic) -> None: if graphic in self._child_graphics: return self._child_graphics.append(graphic) # Removes the graphic from the composition - def remove(self, graphic: Graphic): + def remove(self, graphic: Graphic) -> None: self._child_graphics.remove(graphic) class Ellipse(Graphic): - def __init__(self, x, y, rx, ry): + def __init__(self, x, y, rx, ry) -> None: self.x = x self.y = y self.rx = rx self.ry = ry - def draw(self, painter: QPainter): + def draw(self, painter: QPainter) -> None: painter.drawEllipse(self.x, self.y, self.rx, self.ry) class Point(Graphic): - def __init__(self, x, y): + def __init__(self, x, y) -> None: self.x = x self.y = y - def draw(self, painter: QPainter): + def draw(self, painter: QPainter) -> None: painter.drawPoint(self.x, self.y) class Rect(Graphic): - def __init__(self, x, y, w, h): + def __init__(self, x, y, w, h) -> None: self.x = x self.y = y self.w = w self.h = h - def draw(self, painter: QPainter): + def draw(self, painter: QPainter) -> None: painter.drawRect(self.x, self.y, self.w, self.h) class Line(Graphic): - def __init__(self, x1, y1, x2, y2): + def __init__(self, x1, y1, x2, y2) -> None: self.x1 = x1 self.y1 = y1 self.x2 = x2 self.y2 = y2 - def draw(self, painter: QPainter): + def draw(self, painter: QPainter) -> None: painter.drawLine(self.x1, self.y1, self.x2, self.y2) class CanvasWidget(QWidget): - def __init__(self, graphic: Graphic): + def __init__(self, graphic: Graphic) -> None: super().__init__() - self.setWindowTitle('Canvas') + self.setWindowTitle("Canvas") self.graphic = graphic - def paintEvent(self, event): + def paintEvent(self, event) -> None: painter = QPainter(self) painter.setBrush(Qt.green) self.graphic.draw(painter) -if __name__ == '__main__': +if __name__ == "__main__": ellipse_1 = Ellipse(x=50, y=50, rx=10, ry=15) ellipse_2 = Ellipse(x=10, y=5, rx=40, ry=50) diff --git a/design_patterns__examples/Decorator/example_1.py b/design_patterns__examples/Decorator/example_1.py index d64a5ee20..12edf7a6f 100644 --- a/design_patterns__examples/Decorator/example_1.py +++ b/design_patterns__examples/Decorator/example_1.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: Design Patterns: Decorator — Декоратор @@ -23,7 +23,7 @@ def run(self, text: str) -> str: class BaseDecorator(IOperation): - def __init__(self, operation: IOperation): + def __init__(self, operation: IOperation) -> None: self._wrapped = operation @@ -42,8 +42,8 @@ def run(self, text: str) -> str: return self._wrapped.run(text).upper() -if __name__ == '__main__': - text = 'Hello World!' +if __name__ == "__main__": + text = "Hello World!" operation = SimpleOperation() print(operation.run(text)) # Hello World! diff --git a/design_patterns__examples/Decorator/example_2.py b/design_patterns__examples/Decorator/example_2.py index bb01161b8..87a965c17 100644 --- a/design_patterns__examples/Decorator/example_2.py +++ b/design_patterns__examples/Decorator/example_2.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: Design Patterns: Decorator — Декоратор @@ -10,49 +10,49 @@ class Notifier: - def send(self, message: str): + def send(self, message: str) -> None: print(f'[Email] Send "{message}".') class BaseDecorator(Notifier): - def __init__(self, notifier: Notifier): + def __init__(self, notifier: Notifier) -> None: self._wrapped = notifier class SMSDecorator(BaseDecorator): - def send(self, message: str): + def send(self, message: str) -> None: self._wrapped.send(message) print(f'[SMS] send "{message}".') class FacebookDecorator(BaseDecorator): - def send(self, message: str): + def send(self, message: str) -> None: self._wrapped.send(message) print(f'[Facebook] send "{message}".') class SlackDecorator(BaseDecorator): - def send(self, message: str): + def send(self, message: str) -> None: self._wrapped.send(message) print(f'[Slack] send "{message}".') class Application: - def __init__(self): + def __init__(self) -> None: self._notifier: Notifier = None - def set_notifier(self, notifier: Notifier): + def set_notifier(self, notifier: Notifier) -> None: self._notifier = notifier - def about_alert(self): + def about_alert(self) -> None: if self._notifier: - self._notifier.send('Alert!') + self._notifier.send("Alert!") -if __name__ == '__main__': +if __name__ == "__main__": sms_enabled = True facebook_enabled = False slack_enabled = True @@ -78,7 +78,7 @@ def about_alert(self): print() notifier = Notifier() - notifier.send('Test!') + notifier.send("Test!") # [Email] Send "Test!". print() @@ -86,7 +86,7 @@ def about_alert(self): notifier = SMSDecorator(notifier) notifier = FacebookDecorator(notifier) notifier = SlackDecorator(notifier) - notifier.send('Test!') + notifier.send("Test!") # [Email] Send "Test!". # [SMS] send "Test!". # [Facebook] send "Test!". diff --git a/design_patterns__examples/Facade/example_1.py b/design_patterns__examples/Facade/example_1.py index 0ce24b4be..9142444d4 100644 --- a/design_patterns__examples/Facade/example_1.py +++ b/design_patterns__examples/Facade/example_1.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: Design Patterns: Facade — Фасад @@ -15,13 +15,13 @@ # Class CPU, отвечает за работу процессора class CPU: - def freeze(self): + def freeze(self) -> None: pass - def jump(self, position: int): + def jump(self, position: int) -> None: pass - def execute(self): + def execute(self) -> None: pass @@ -29,7 +29,7 @@ def execute(self): class Memory: BOOT_ADDRESS = 0x0005 - def load(self, position: int, data: bytes): + def load(self, position: int, data: bytes) -> None: pass @@ -46,23 +46,25 @@ def read(self, lba: int, size: int) -> bytes: # В качестве унифицированного объекта выступает Компьютер. # За этим объектом будут скрыты, все детали работы его внутренних частей. class Computer: - def __init__(self): + def __init__(self) -> None: self._cpu = CPU() self._memory = Memory() self._hard_drive = HardDrive() # Упрощённая обработка поведения "запуск компьютера" - def start_computer(self): + def start_computer(self) -> None: self._cpu.freeze() self._memory.load( self._memory.BOOT_ADDRESS, - self._hard_drive.read(self._hard_drive.BOOT_SECTOR, self._hard_drive.SECTOR_SIZE) + self._hard_drive.read( + self._hard_drive.BOOT_SECTOR, self._hard_drive.SECTOR_SIZE + ), ) self._cpu.jump(self._memory.BOOT_ADDRESS) self._cpu.execute() -if __name__ == '__main__': +if __name__ == "__main__": # Пользователям компьютера предоставляется Фасад (компьютер), # который скрывает все сложность работы с отдельными компонентами. computer = Computer() diff --git a/design_patterns__examples/Facade/example_2.py b/design_patterns__examples/Facade/example_2.py index 22299ef6b..33a3c32dd 100644 --- a/design_patterns__examples/Facade/example_2.py +++ b/design_patterns__examples/Facade/example_2.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: Design Patterns: Facade — Фасад @@ -76,7 +76,7 @@ def operation(self) -> str: self._subsystem2.operation_ready(), "Facade orders subsystems to perform the action:", self._subsystem1.operation_go(), - self._subsystem2.operation_fire() + self._subsystem2.operation_fire(), ] return "\n".join(results) diff --git a/design_patterns__examples/Facade/example_3.py b/design_patterns__examples/Facade/example_3.py index 9621c2cfb..468c49830 100644 --- a/design_patterns__examples/Facade/example_3.py +++ b/design_patterns__examples/Facade/example_3.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: Design Patterns: Facade — Фасад @@ -14,67 +14,67 @@ # Абстрактный музыкант - не является обязательной составляющей паттерна, введен для упрощения кода class Musician(ABC): - def __init__(self, name: str): + def __init__(self, name: str) -> None: self.name = name - - def output(self, text: str): + + def output(self, text: str) -> None: print(self.name + " " + text + ".") - + # Конкретные музыканты class Vocalist(Musician): - def sing_couplet(self, couplet_number: int): + def sing_couplet(self, couplet_number: int) -> None: self.output("спел куплет №" + str(couplet_number)) - - def sing_chorus(self): + + def sing_chorus(self) -> None: self.output("спел припев") class Guitarist(Musician): - def play_cool_opening(self): + def play_cool_opening(self) -> None: self.output("начинает с крутого вступления") - - def play_cool_riffs(self): + + def play_cool_riffs(self) -> None: self.output("играет крутые риффы") - def play_another_cool_riffs(self): + def play_another_cool_riffs(self) -> None: self.output("играет другие крутые риффы") - def play_incredibly_cool_solo(self): + def play_incredibly_cool_solo(self) -> None: self.output("выдает невероятно крутое соло") - def play_final_accord(self): + def play_final_accord(self) -> None: self.output("заканчивает песню мощным аккордом") - + class Bassist(Musician): - def follow_the_drums(self): + def follow_the_drums(self) -> None: self.output("следует за барабанами") - - def change_rhythm(self, type_rhythm: str): + + def change_rhythm(self, type_rhythm: str) -> None: self.output("перешел на ритм " + type_rhythm + "a") - def stop_playing(self): + def stop_playing(self) -> None: self.output("заканчивает играть") class Drummer(Musician): - def start_playing(self): + def start_playing(self) -> None: self.output("начинает играть") - - def stop_playing(self): + + def stop_playing(self) -> None: self.output("заканчивает играть") - + # Фасад, в данном случае - знаменитая рок-группа class BlackSabbath: - def __init__(self): + def __init__(self) -> None: self.vocalist = Vocalist("Оззи Осборн") self.guitarist = Guitarist("Тони Айомми") self.bassist = Bassist("Гизер Батлер") self.drummer = Drummer("Билл Уорд") - - def play_cool_song(self): + + def play_cool_song(self) -> None: self.guitarist.play_cool_opening() self.drummer.start_playing() self.bassist.follow_the_drums() @@ -103,11 +103,11 @@ def play_cool_song(self): self.guitarist.play_final_accord() -if __name__ == '__main__': +if __name__ == "__main__": print("OUTPUT:") band = BlackSabbath() band.play_cool_song() - + # OUTPUT: # Тони Айомми начинает с крутого вступления. # Билл Уорд начинает играть. diff --git a/design_patterns__examples/Factory/example_1.py b/design_patterns__examples/Factory/example_1.py index 6f30a0f54..ab5e6a9e1 100644 --- a/design_patterns__examples/Factory/example_1.py +++ b/design_patterns__examples/Factory/example_1.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://ru.wikipedia.org/wiki/Фабричный_метод_(шаблон_проектирования) @@ -19,13 +19,13 @@ def parse(self, data: str) -> object: class ParserFactory: @staticmethod def get_parser(name_parser: str) -> Parser: - if name_parser == 'XML': + if name_parser == "XML": return ParserXML() - elif name_parser == 'JSON': + elif name_parser == "JSON": return ParserJSON() - elif name_parser == 'CSV': + elif name_parser == "CSV": return ParserCSV() raise Exception(f'Unsupported parser "{name_parser}"') @@ -34,34 +34,38 @@ def get_parser(name_parser: str) -> Parser: class ParserXML(Parser): def parse(self, data: str) -> object: from bs4 import BeautifulSoup - root = BeautifulSoup(data, 'xml') + + root = BeautifulSoup(data, "xml") return root class ParserJSON(Parser): def parse(self, data: str) -> object: import json + return json.loads(data) class ParserCSV(Parser): def parse(self, data: str) -> object: import csv + csv_reader = csv.reader(data.splitlines(), delimiter=";") return list(csv_reader) -if __name__ == '__main__': - result = ParserFactory.get_parser('XML').parse('123') +if __name__ == "__main__": + result = ParserFactory.get_parser("XML").parse("123") print(result) # '\n123' - json_parser = ParserFactory.get_parser('JSON') - result = json_parser.parse('[null, null, null]') + json_parser = ParserFactory.get_parser("JSON") + result = json_parser.parse("[null, null, null]") print(result) # [None, None, None] result = json_parser.parse('["a", ["b", "c"], null]') print(result) # ['a', ['b', 'c'], None] - csv_parser = ParserFactory.get_parser('CSV') - result = csv_parser.parse('1;vasya;moscow;11111\n2;oleg;sochi;22222') - print(result) # [['1', 'vasya', 'moscow', '11111'], ['2', 'oleg', 'sochi', '22222']] + csv_parser = ParserFactory.get_parser("CSV") + result = csv_parser.parse("1;vasya;moscow;11111\n2;oleg;sochi;22222") + print(result) + # [['1', 'vasya', 'moscow', '11111'], ['2', 'oleg', 'sochi', '22222']] diff --git a/design_patterns__examples/Factory/example_2.py b/design_patterns__examples/Factory/example_2.py index daf786a1d..ae56828b1 100644 --- a/design_patterns__examples/Factory/example_2.py +++ b/design_patterns__examples/Factory/example_2.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://ru.wikipedia.org/wiki/Фабричный_метод_(шаблон_проектирования) @@ -12,32 +12,32 @@ class Animal(ABC): @staticmethod - def initial(animal: str) -> 'Animal': - if animal == 'Lion': + def initial(animal: str) -> "Animal": + if animal == "Lion": return Lion() - elif animal == 'Cat': + elif animal == "Cat": return Cat() raise Exception(f'Unsupported animal "{animal}"') @abstractmethod - def voice(self): + def voice(self) -> None: pass class Lion(Animal): - def voice(self): + def voice(self) -> None: print("Rrrrrrrr i'm the lion") class Cat(Animal): - def voice(self): + def voice(self) -> None: print("Meow, meow i'm the kitty") -if __name__ == '__main__': - animal_1 = Animal.initial('Lion') - animal_2 = Animal.initial('Cat') +if __name__ == "__main__": + animal_1 = Animal.initial("Lion") + animal_2 = Animal.initial("Cat") animal_1.voice() animal_2.voice() diff --git a/design_patterns__examples/Flyweight/example_1.py b/design_patterns__examples/Flyweight/example_1.py index 24695c80a..5f91166f1 100644 --- a/design_patterns__examples/Flyweight/example_1.py +++ b/design_patterns__examples/Flyweight/example_1.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: Design Patterns: Flyweight — Приспособленец @@ -11,34 +11,31 @@ # SOURCE: https://javarush.ru/groups/posts/584-patternih-proektirovanija -from typing import List - - class Flyweight: - def __init__(self, row: int): + def __init__(self, row: int) -> None: self.row = row print("ctor:", self.row) - - def report(self, col: int): - print(" {}{}".format(self.row, col), end='') + + def report(self, col: int) -> None: + print(f" {self.row}{col}", end="") class Factory: - def __init__(self, max_rows: int): - self._pool: List[Flyweight] = [None] * max_rows - + def __init__(self, max_rows: int) -> None: + self._pool: list[Flyweight | None] = [None] * max_rows + def get_flyweight(self, row: int) -> Flyweight: if self._pool[row] is None: self._pool[row] = Flyweight(row) - + return self._pool[row] - -if __name__ == '__main__': + +if __name__ == "__main__": rows = 5 the_factory = Factory(rows) for i in range(rows): for j in range(rows): the_factory.get_flyweight(i).report(j) - + print() diff --git a/design_patterns__examples/Flyweight/example_2.py b/design_patterns__examples/Flyweight/example_2.py index eaeca5a1d..f945f6de2 100644 --- a/design_patterns__examples/Flyweight/example_2.py +++ b/design_patterns__examples/Flyweight/example_2.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: Design Patterns: Flyweight — Приспособленец @@ -9,7 +9,6 @@ from abc import ABC -from typing import Dict # "Flyweight" @@ -21,40 +20,40 @@ class Character(ABC): descent: int point_size: int - def display(self, point_size: int): + def display(self, point_size: int) -> None: self.point_size = point_size - print('{} (point_size {})'.format(self.symbol, self.point_size)) + print(f"{self.symbol} (point_size {self.point_size})") # "FlyweightFactory" class CharacterFactory: - def __init__(self): - self._characters: Dict[str, Character] = dict() + def __init__(self) -> None: + self._characters: dict[str, Character] = dict() def get_character(self, key: str) -> Character: # Uses "lazy initialization" character = self._characters.get(key) if character is None: - if key == 'A': + if key == "A": character = CharacterA() - elif key == 'B': + elif key == "B": character = CharacterB() # ... - elif key == 'Z': + elif key == "Z": character = CharacterZ() self._characters[key] = character return character - - + + # "ConcreteFlyweight" class CharacterA(Character): - def __init__(self): - self.symbol = 'A' + def __init__(self) -> None: + self.symbol = "A" self.height = 100 self.width = 120 self.ascent = 70 @@ -63,8 +62,8 @@ def __init__(self): # "ConcreteFlyweight" class CharacterB(Character): - def __init__(self): - self.symbol = 'B' + def __init__(self) -> None: + self.symbol = "B" self.height = 100 self.width = 140 self.ascent = 72 @@ -76,15 +75,15 @@ def __init__(self): # "ConcreteFlyweight" class CharacterZ(Character): - def __init__(self): - self.symbol = 'Z' + def __init__(self) -> None: + self.symbol = "Z" self.height = 100 self.width = 100 self.ascent = 68 self.descent = 0 -if __name__ == '__main__': +if __name__ == "__main__": # Build a document with text chars = "AAZZBBZB" diff --git a/design_patterns__examples/Mediator/example_1.py b/design_patterns__examples/Mediator/example_1.py index 4f0c936b9..2309230b6 100644 --- a/design_patterns__examples/Mediator/example_1.py +++ b/design_patterns__examples/Mediator/example_1.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: Design Patterns: Mediator — Посредник @@ -28,13 +28,13 @@ def notify(self, sender: object, event: str): # Конкретные Посредники реализуют совместное поведение, координируя отдельные компоненты. class ConcreteMediator(IMediator): - def __init__(self, component1, component2): + def __init__(self, component1, component2) -> None: self._component1 = component1 self._component1.set_mediator(self) self._component2 = component2 self._component2.set_mediator(self) - def notify(self, sender: object, event: str): + def notify(self, sender: object, event: str) -> None: print(f'[+] Mediator notify(sender={sender}, event="{event}")') if event == "A": @@ -43,7 +43,7 @@ def notify(self, sender: object, event: str): elif event == "B": print("[+] Mediator reacts on B and triggers following operations:") - print('[-] Nothing!') + print("[-] Nothing!") elif event == "D": print("[+] Mediator reacts on D and triggers following operations:") @@ -57,36 +57,36 @@ class BaseComponent(ABC): посредника внутри объектов компонентов. """ - def __init__(self, mediator: IMediator = None): + def __init__(self, mediator: IMediator = None) -> None: self._mediator = mediator - def set_mediator(self, mediator: IMediator): + def set_mediator(self, mediator: IMediator) -> None: self._mediator = mediator # Конкретные Компоненты реализуют различную функциональность. Они не зависят от других # компонентов. Они также не зависят от каких-либо конкретных классов посредников. class Component1(BaseComponent): - def do_a(self): + def do_a(self) -> None: print("Component 1 does A.") self._mediator.notify(self, "A") - def do_b(self): + def do_b(self) -> None: print("Component 1 does B.") self._mediator.notify(self, "B") class Component2(BaseComponent): - def do_c(self): + def do_c(self) -> None: print("Component 2 does C.") self._mediator.notify(self, "C") - def do_d(self): + def do_d(self) -> None: print("Component 2 does D.") self._mediator.notify(self, "D") -if __name__ == '__main__': +if __name__ == "__main__": # Клиентский код component1 = Component1() component2 = Component2() diff --git a/design_patterns__examples/Mediator/example_2.py b/design_patterns__examples/Mediator/example_2.py index 2a10d1b8f..f0ccd146b 100644 --- a/design_patterns__examples/Mediator/example_2.py +++ b/design_patterns__examples/Mediator/example_2.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: Design Patterns: Mediator — Посредник @@ -11,19 +11,19 @@ class Mediator: @staticmethod - def send_message(user: 'User', msg: str): - print(user.name + ": " + msg) - + def send_message(user: "User", msg: str) -> None: + print(f"{user.name}: {msg}") + class User: - def __init__(self, name: str): + def __init__(self, name: str) -> None: self.name = name - - def send_message(self, msg: str): + + def send_message(self, msg: str) -> None: Mediator.send_message(self, msg) -if __name__ == '__main__': +if __name__ == "__main__": user1 = User("user1") user2 = User("user2") user1.send_message("message1") diff --git a/design_patterns__examples/Mediator/example_notes__pyqt.py b/design_patterns__examples/Mediator/example_notes__pyqt.py index 5d005218b..e0facea05 100644 --- a/design_patterns__examples/Mediator/example_notes__pyqt.py +++ b/design_patterns__examples/Mediator/example_notes__pyqt.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: Design Patterns: Mediator — Посредник @@ -9,9 +9,11 @@ # SOURCE: https://refactoring.guru/ru/design-patterns/mediator/java/example -from abc import abstractmethod -from typing import List, Any import traceback +import sys + +from abc import abstractmethod +from typing import Any try: @@ -28,29 +30,28 @@ from PySide.QtCore import * -def log_uncaught_exceptions(ex_cls, ex, tb): - text = '{}: {}:\n'.format(ex_cls.__name__, ex) - text += ''.join(traceback.format_tb(tb)) +def log_uncaught_exceptions(ex_cls, ex, tb) -> None: + text = f"{ex_cls.__name__}: {ex}:\n" + text += "".join(traceback.format_tb(tb)) print(text) - QMessageBox.critical(None, 'Error', text) + QMessageBox.critical(None, "Error", text) sys.exit(1) -import sys sys.excepthook = log_uncaught_exceptions # Класс заметок class Note: - def __init__(self): + def __init__(self) -> None: self._name = "note" - self._text = '' + self._text = "" - def setName(self, name: str): + def setName(self, name: str) -> None: self._name = name - def setText(self, text: str): + def setText(self, text: str) -> None: self._text = text def getName(self) -> str: @@ -61,12 +62,12 @@ def getText(self) -> str: class DefaultListModel(QAbstractListModel): - def __init__(self): + def __init__(self) -> None: super().__init__() - self.items: List[Note] = [] + self.items: list[Note] = [] - def rowCount(self, parent: QModelIndex=None): + def rowCount(self, parent: QModelIndex = None) -> int: return len(self.items) def data(self, index: QModelIndex, role: int = Qt.DisplayRole) -> Any: @@ -74,16 +75,16 @@ def data(self, index: QModelIndex, role: int = Qt.DisplayRole) -> Any: return QVariant() element = self.items[index.row()] - + if role == Qt.DisplayRole: return element.getName() - + return QVariant() def get_element(self, index: int) -> Note: return self.items[index] - def addElement(self, element: Note): + def addElement(self, element: Note) -> None: length = self.rowCount() self.beginInsertRows(QModelIndex(), length, length) @@ -93,7 +94,7 @@ def addElement(self, element: Note): # Говорим view, что данные изменились self.dataChanged.emit(self.createIndex(length, 0), self.createIndex(length, 0)) - def removeElement(self, index: int): + def removeElement(self, index: int) -> None: self.beginRemoveRows(QModelIndex(), index, index) self.items.pop(index) self.endRemoveRows() @@ -101,64 +102,64 @@ def removeElement(self, index: int): # Говорим view, что данные изменились self.dataChanged.emit(self.createIndex(index, 0), self.createIndex(index, 0)) - def get_items(self) -> List[Note]: + def get_items(self) -> list[Note]: return self.items # Общий интерфейс посредников. class Mediator: @abstractmethod - def addNewNote(self, note: Note): + def addNewNote(self, note: Note) -> None: pass @abstractmethod - def deleteNote(self): + def deleteNote(self) -> None: pass @abstractmethod - def getInfoFromList(self, note: Note): + def getInfoFromList(self, note: Note) -> None: pass @abstractmethod - def saveChanges(self): + def saveChanges(self) -> None: pass @abstractmethod - def markNote(self): + def markNote(self) -> None: pass @abstractmethod - def clear(self): + def clear(self) -> None: pass @abstractmethod - def sendToFilter(self, listModel: DefaultListModel): + def sendToFilter(self, listModel: DefaultListModel) -> None: pass @abstractmethod - def setElementsList(self, listModel: DefaultListModel): + def setElementsList(self, listModel: DefaultListModel) -> None: pass @abstractmethod - def registerComponent(self, component: 'Component'): + def registerComponent(self, component: "Component") -> None: pass @abstractmethod - def hideElements(self, flag: bool): + def hideElements(self, flag: bool) -> None: pass @abstractmethod - def createGUI(self): + def createGUI(self) -> None: pass class Component: """Общий класс компонентов.""" - def __init__(self): + def __init__(self) -> None: self._mediator: Mediator = None - def setMediator(self, mediator: Mediator): + def setMediator(self, mediator: Mediator) -> None: self._mediator = mediator @abstractmethod @@ -169,7 +170,7 @@ def getName(self) -> str: # Конкретные компоненты никак не связаны между собой. У них есть только один # канал общения – через отправку уведомлений посреднику. class AddButton(QPushButton, Component): - def __init__(self): + def __init__(self) -> None: super().__init__("Add") # При клике на кнопку вызываем метод посредника @@ -182,7 +183,7 @@ def getName(self) -> str: # Конкретные компоненты никак не связаны между собой. У них есть только один # канал общения – через отправку уведомлений посреднику. class DeleteButton(QPushButton, Component): - def __init__(self): + def __init__(self) -> None: super().__init__("Del") # При клике на кнопку вызываем метод посредника @@ -195,21 +196,21 @@ def getName(self) -> str: # Конкретные компоненты никак не связаны между собой. У них есть только один # канал общения – через отправку уведомлений посреднику. class Filter(QLineEdit, Component): - def __init__(self): + def __init__(self) -> None: super().__init__() self._listModel: DefaultListModel = None - def setList(self, listModel: DefaultListModel): + def setList(self, listModel: DefaultListModel) -> None: self._listModel = listModel - def keyPressEvent(self, event: 'QKeyEvent'): + def keyPressEvent(self, event: "QKeyEvent") -> None: super().keyPressEvent(event) start = self.text() self._searchElements(start) - def _searchElements(self, text: str): + def _searchElements(self, text: str) -> None: if self._listModel is None: return @@ -234,13 +235,13 @@ def getName(self) -> str: # Конкретные компоненты никак не связаны между собой. У них есть только один # канал общения – через отправку уведомлений посреднику. class ListNote(QListView, Component): - def __init__(self, listModel: DefaultListModel): + def __init__(self, listModel: DefaultListModel) -> None: super().__init__() self._listModel = listModel self.setModel(self._listModel) - def addElement(self, note: Note): + def addElement(self, note: Note) -> None: self._listModel.addElement(note) index = self._listModel.rowCount() - 1 index = self._listModel.index(index, 0) @@ -249,7 +250,7 @@ def addElement(self, note: Note): self._mediator.sendToFilter(self._listModel) - def deleteElement(self): + def deleteElement(self) -> None: if not self.currentIndex().isValid(): return @@ -267,12 +268,12 @@ def getCurrentElement(self) -> Note: def getName(self) -> str: return "List" - + # Конкретные компоненты никак не связаны между собой. У них есть только один # канал общения – через отправку уведомлений посреднику. class SaveButton(QPushButton, Component): - def __init__(self): + def __init__(self) -> None: super().__init__("Save") # При клике на кнопку вызываем метод посредника @@ -285,7 +286,7 @@ def getName(self) -> str: # Конкретные компоненты никак не связаны между собой. У них есть только один # канал общения – через отправку уведомлений посреднику. class TextBox(QTextEdit, Component): - def keyPressEvent(self, event: 'QKeyEvent'): + def keyPressEvent(self, event: "QKeyEvent") -> None: super().keyPressEvent(event) self._mediator.markNote() @@ -297,7 +298,7 @@ def getName(self) -> str: # Конкретные компоненты никак не связаны между собой. У них есть только один # канал общения – через отправку уведомлений посреднику. class Title(QLineEdit, Component): - def keyPressEvent(self, event: 'QKeyEvent'): + def keyPressEvent(self, event: "QKeyEvent") -> None: super().keyPressEvent(event) self._mediator.markNote() @@ -310,7 +311,7 @@ def getName(self) -> str: # код посредника. Он получает извещения от своих компонентов и знает как на них # реагировать. class Editor(Mediator): - def __init__(self): + def __init__(self) -> None: self._title: Title = None self._textBox: TextBox = None self._add: AddButton = None @@ -326,22 +327,22 @@ def __init__(self): self._mainWindow = None # Здесь происходит регистрация компонентов посредником. - def registerComponent(self, component: Component): + def registerComponent(self, component: Component) -> None: component.setMediator(self) - if component.getName() == 'AddButton': + if component.getName() == "AddButton": self._add = component - elif component.getName() == 'DelButton': + elif component.getName() == "DelButton": self._del = component - elif component.getName() == 'Filter': + elif component.getName() == "Filter": self._filter = component - elif component.getName() == 'List': + elif component.getName() == "List": self._list: ListNote = component - def foo(*args): + def foo(*args) -> None: empty = len(self._list.selectedIndexes()) == 0 self.hideElements(empty) @@ -352,20 +353,20 @@ def foo(*args): # Вызываем нашу функцию при изменении выделения элементов в списке self._list.clicked.connect(foo) - elif component.getName() == 'SaveButton': + elif component.getName() == "SaveButton": self._save = component - elif component.getName() == 'TextBox': + elif component.getName() == "TextBox": self._textBox = component - elif component.getName() == 'Title': + elif component.getName() == "Title": self._title = component # # Разнообразные методы общения с компонентами. # - def addNewNote(self, note: Note): + def addNewNote(self, note: Note) -> None: # Инкрементальные названия text = note.getName() + str(self._list.model().rowCount() + 1) note.setName(text) @@ -374,14 +375,14 @@ def addNewNote(self, note: Note): self._textBox.setText("") self._list.addElement(note) - def deleteNote(self): + def deleteNote(self) -> None: self._list.deleteElement() - def getInfoFromList(self, note: Note): - self._title.setText(note.getName().replace('*', ' ')) + def getInfoFromList(self, note: Note) -> None: + self._title.setText(note.getName().replace("*", " ")) self._textBox.setText(note.getText()) - def saveChanges(self): + def saveChanges(self) -> None: try: note = self._list.getCurrentElement() note.setName(self._title.text()) @@ -392,7 +393,7 @@ def saveChanges(self): except Exception: traceback.print_exc() - def markNote(self): + def markNote(self) -> None: try: note = self._list.getCurrentElement() name = note.getName() @@ -407,18 +408,18 @@ def markNote(self): except Exception: traceback.print_exc() - def clear(self): + def clear(self) -> None: self._title.setText("") self._textBox.setText("") - def sendToFilter(self, listModel: DefaultListModel): + def sendToFilter(self, listModel: DefaultListModel) -> None: self._filter.setList(listModel) - def setElementsList(self, listModel: DefaultListModel): + def setElementsList(self, listModel: DefaultListModel) -> None: self._list.setModel(listModel) self._list.update() - def hideElements(self, flag: bool): + def hideElements(self, flag: bool) -> None: self._titleLabel.setVisible(not flag) self._textLabel.setVisible(not flag) self._title.setVisible(not flag) @@ -426,7 +427,7 @@ def hideElements(self, flag: bool): self._save.setVisible(not flag) self._label.setVisible(flag) - def createGUI(self): + def createGUI(self) -> None: # notes_side notes_side_filter_layout = QHBoxLayout() notes_side_filter_layout.addWidget(QLabel("Filter:")) @@ -466,7 +467,7 @@ def createGUI(self): splitter.addWidget(notes_editor) self._mainWindow = QMainWindow() - self._mainWindow.setWindowTitle('Notes') + self._mainWindow.setWindowTitle("Notes") self._mainWindow.setCentralWidget(splitter) # По умолчанию прячем элементы в редакторе заметки @@ -475,7 +476,7 @@ def createGUI(self): self._mainWindow.show() -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) mediator = Editor() diff --git a/design_patterns__examples/Observer/example_1.py b/design_patterns__examples/Observer/example_1.py index 09dea5e14..2585c4c06 100644 --- a/design_patterns__examples/Observer/example_1.py +++ b/design_patterns__examples/Observer/example_1.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: Design Patterns: Observer — Наблюдатель @@ -12,13 +12,13 @@ from abc import ABC, abstractmethod -# В примере описывается получение данных от метеорологической станции (класс weather_data, рассылатель событий) и -# использование их для вывода на экран (класс CurrentConditionsDisplay, слушатель событий). +# В примере описывается получение данных от метеорологической станции (класс weather_data, рассылатель событий) и +# использование их для вывода на экран (класс CurrentConditionsDisplay, слушатель событий). # Слушатель регистрируется у наблюдателя с помощью метода register_observer (при этом слушатель заносится в # список observers). Регистрация происходит в момент создания объекта currentDisplay, т.к. метод register_observer # применяется в конструкторе. -# При изменении погодных данных вызывается метод notify_observers, который в свою очередь вызывает метод update -# у всех слушателей, передавая им обновлённые данные. +# При изменении погодных данных вызывается метод notify_observers, который в свою очередь вызывает метод update +# у всех слушателей, передавая им обновлённые данные. class Observer(ABC): @@ -42,23 +42,23 @@ def notify_observers(self): class WeatherData(Observable): - def __init__(self): + def __init__(self) -> None: self.observers = [] self.temperature: float = None self.humidity: float = None self.pressure: int = None - def register_observer(self, o: Observer): + def register_observer(self, o: Observer) -> None: self.observers.append(o) - def remove_observer(self, o: Observer): + def remove_observer(self, o: Observer) -> None: self.observers.remove(o) - def notify_observers(self): + def notify_observers(self) -> None: for observer in self.observers: observer.update(self.temperature, self.humidity, self.pressure) - def set_measurements(self, temperature: float, humidity: float, pressure: int): + def set_measurements(self, temperature: float, humidity: float, pressure: int) -> None: self.temperature = temperature self.humidity = humidity self.pressure = pressure @@ -66,7 +66,7 @@ def set_measurements(self, temperature: float, humidity: float, pressure: int): class CurrentConditionsDisplay(Observer): - def __init__(self, weather_data: WeatherData): + def __init__(self, weather_data: WeatherData) -> None: self.weather_data = weather_data self.weather_data.register_observer(self) @@ -74,23 +74,24 @@ def __init__(self, weather_data: WeatherData): self.humidity: float = None self.pressure: int = None - def update(self, temperature: float, humidity: float, pressure: int): + def update(self, temperature: float, humidity: float, pressure: int) -> None: self.temperature = temperature self.humidity = humidity self.pressure = pressure self.display() - def display(self): - print("Сейчас значения: {:.1f} градусов цельсия и {:.1f}% влажности. Давление {} мм рт. ст.".format( - self.temperature, self.humidity, self.pressure - )) + def display(self) -> None: + print( + f"Сейчас значения: {self.temperature:.1f} градусов цельсия и {self.humidity:.1f}% влажности. " + f"Давление {self.pressure} мм рт. ст." + ) -if __name__ == '__main__': +if __name__ == "__main__": weather_data = WeatherData() current_display = CurrentConditionsDisplay(weather_data) - + weather_data.set_measurements(temperature=29, humidity=65, pressure=745) weather_data.set_measurements(temperature=39, humidity=70, pressure=760) weather_data.set_measurements(temperature=42, humidity=72, pressure=763) diff --git a/design_patterns__examples/Observer/example_2.py b/design_patterns__examples/Observer/example_2.py index fff55ab4a..ca034cda3 100644 --- a/design_patterns__examples/Observer/example_2.py +++ b/design_patterns__examples/Observer/example_2.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: Design Patterns: Observer — Наблюдатель @@ -34,7 +34,7 @@ def __init__(self) -> None: """ Constructor. """ - self.observers = [] # инициализация списка наблюдателей + self.observers = [] # инициализация списка наблюдателей def register(self, observer: Observer) -> None: """ @@ -80,15 +80,15 @@ def update(self, message: str) -> None: """ Получение очередной новости """ - print('{} узнал следующее: {}'.format(self.name, message)) + print(f"{self.name} узнал следующее: {message}") -if __name__ == '__main__': - newspaper = Newspaper() # Создаем небольшую газету - newspaper.register(Citizen('Иван')) # Добавляем двух человек, которые - newspaper.register(Citizen('Василий')) # ... ее регулярно выписывают +if __name__ == "__main__": + newspaper = Newspaper() # Создаем небольшую газету + newspaper.register(Citizen("Иван")) # Добавляем двух человек, которые + newspaper.register(Citizen("Василий")) # ... ее регулярно выписывают # ... и вбрасываем очередную газетную утку - newspaper.add_news('Наблюдатель - поведенческий шаблон проектирования') + newspaper.add_news("Наблюдатель - поведенческий шаблон проектирования") # Иван узнал следующее: Наблюдатель - поведенческий шаблон проектирования # Василий узнал следующее: Наблюдатель - поведенческий шаблон проектирования diff --git a/design_patterns__examples/Observer/example_3.py b/design_patterns__examples/Observer/example_3.py index 515c339b9..dacd8521d 100644 --- a/design_patterns__examples/Observer/example_3.py +++ b/design_patterns__examples/Observer/example_3.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: Design Patterns: Observer — Наблюдатель @@ -11,7 +11,6 @@ from abc import ABC, abstractmethod from random import randrange -from typing import List class Observer(ABC): @@ -21,7 +20,7 @@ class Observer(ABC): """ @abstractmethod - def update(self, subject: 'Subject') -> None: + def update(self, subject: "Subject") -> None: """ Получить обновление от субъекта. """ @@ -67,7 +66,7 @@ class ConcreteSubject(Subject): подписчикам. """ - _observers: List[Observer] = [] + _observers: list[Observer] = [] """ Список подписчиков. В реальной жизни список подписчиков может храниться в более подробном виде (классифицируется по типу события и т.д.) diff --git a/design_patterns__examples/Observer/example_4.py b/design_patterns__examples/Observer/example_4.py index 276b1a149..c797b1f59 100644 --- a/design_patterns__examples/Observer/example_4.py +++ b/design_patterns__examples/Observer/example_4.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: Design Patterns: Observer — Наблюдатель @@ -21,7 +21,7 @@ from abc import ABC, abstractmethod -from typing import IO, List, Dict +from typing import IO class EventListener(ABC): @@ -31,34 +31,34 @@ def update(self, event_type: str, file: IO): class EventManager: - def __init__(self, *operations): - self.listeners: Dict[str, List[EventListener]] = dict() + def __init__(self, *operations) -> None: + self.listeners: dict[str, list[EventListener]] = dict() for operation in operations: self.listeners[operation] = [] - def subscribe(self, event_type: str, listener: EventListener): + def subscribe(self, event_type: str, listener: EventListener) -> None: items = self.listeners[event_type] items.append(listener) - def unsubscribe(self, event_type: str, listener: EventListener): + def unsubscribe(self, event_type: str, listener: EventListener) -> None: items = self.listeners[event_type] if listener in items: items.remove(listener) - def notify(self, event_type: str, file: IO): + def notify(self, event_type: str, file: IO) -> None: items = self.listeners[event_type] for listener in items: listener.update(event_type, file) class Editor: - def __init__(self): + def __init__(self) -> None: self.file: IO = None self.events = EventManager("open", "save") - - def open_file(self, file_path: str): - self.file = open(file_path, 'w', encoding='utf-8') + + def open_file(self, file_path: str) -> None: + self.file = open(file_path, "w", encoding="utf-8") self.events.notify("open", self.file) def save_file(self): @@ -69,24 +69,28 @@ def save_file(self): class EmailNotificationListener(EventListener): - def __init__(self, email: str): + def __init__(self, email: str) -> None: self.email = email - def update(self, event_type: str, file: IO): - print(f"Email to {self.email}: Someone has performed {event_type} " - f"operation with the following file: {file.name}") - + def update(self, event_type: str, file: IO) -> None: + print( + f"Email to {self.email}: Someone has performed {event_type} " + f"operation with the following file: {file.name}" + ) + class LogOpenListener(EventListener): - def __init__(self, file_name: str): + def __init__(self, file_name: str) -> None: # self.log: IO = open(file_name, encoding='utf-8') self.file_name = file_name - def update(self, event_type: str, file: IO): + def update(self, event_type: str, file: IO) -> None: # print(f"Save to log {self.log}: Someone has performed {event_type} " # f"operation with the following file: {file.name}") - print(f"Save to log {self.file_name}: Someone has performed {event_type} " - f"operation with the following file: {file.name}") + print( + f"Save to log {self.file_name}: Someone has performed {event_type} " + f"operation with the following file: {file.name}" + ) if __name__ == "__main__": diff --git a/design_patterns__examples/Prototype/example.py b/design_patterns__examples/Prototype/example.py index b9096fd88..d9793cacf 100644 --- a/design_patterns__examples/Prototype/example.py +++ b/design_patterns__examples/Prototype/example.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: Design Patterns: Prototype - Прототип @@ -9,46 +9,47 @@ import copy +from typing import Any class Prototype: - def __init__(self): + def __init__(self) -> None: self._objects = dict() - def register_object(self, name, obj): + def register_object(self, name, obj) -> None: """Register an object""" self._objects[name] = obj - def unregister_object(self, name): + def unregister_object(self, name) -> None: """Unregister an object""" self._objects.pop(name) - def clone(self, name, **attr): + def clone(self, name, **attr) -> Any: """Clone a registered object and update inner attributes dictionary""" obj = copy.deepcopy(self._objects.get(name)) obj.__dict__.update(attr) return obj -if __name__ == '__main__': +if __name__ == "__main__": + class A: - def __init__(self): + def __init__(self) -> None: self.x = 3 self.y = 8 self.z = 15 self.garbage = [38, 11, 19] - def __str__(self): - return 'A({}, {}, {}, {})'.format(self.x, self.y, self.z, self.garbage) - + def __str__(self) -> str: + return f"A({self.x}, {self.y}, {self.z}, {self.garbage})" a = A() prototype = Prototype() - prototype.register_object('object_a', a) + prototype.register_object("object_a", a) - b = prototype.clone('object_a') - c = prototype.clone('object_a', x=1, y=2, garbage=[88, 1]) + b = prototype.clone("object_a") + c = prototype.clone("object_a", x=1, y=2, garbage=[88, 1]) for x in (a, b, c): print(x) diff --git a/design_patterns__examples/Proxy/example.py b/design_patterns__examples/Proxy/example.py index 08fd3f72a..2c520c2b7 100644 --- a/design_patterns__examples/Proxy/example.py +++ b/design_patterns__examples/Proxy/example.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: Design Patterns: Proxy - Заместитель @@ -43,7 +43,7 @@ def div(self, x, y): class MathProxy(IMath): """Прокси""" - def __init__(self): + def __init__(self) -> None: self.math = None # Быстрые операции - не требуют реального субъекта @@ -62,7 +62,7 @@ def mul(self, x, y): def div(self, x, y): if y == 0: - return float('inf') # Вернуть positive infinity + return float("inf") # Вернуть positive infinity if not self.math: self.math = Math() @@ -70,10 +70,10 @@ def div(self, x, y): return self.math.div(x, y) -if __name__ == '__main__': +if __name__ == "__main__": p = MathProxy() x, y = 4, 2 - print('4 + 2 =', p.add(x, y)) - print('4 - 2 =', p.sub(x, y)) - print('4 * 2 =', p.mul(x, y)) - print('4 / 2 =', p.div(x, y)) + print("4 + 2 =", p.add(x, y)) + print("4 - 2 =", p.sub(x, y)) + print("4 * 2 =", p.mul(x, y)) + print("4 / 2 =", p.div(x, y)) diff --git a/design_patterns__examples/Proxy/example__cached.py b/design_patterns__examples/Proxy/example__cached.py index d110913a8..e3c7ee6d7 100644 --- a/design_patterns__examples/Proxy/example__cached.py +++ b/design_patterns__examples/Proxy/example__cached.py @@ -1,10 +1,13 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from abc import ABC, abstractmethod +from types import TracebackType +from typing import Optional, Type + import requests @@ -33,7 +36,7 @@ def get_status_code(self, url: str) -> int: class GoUrlCachedProxy(IGoUrl): """Прокси""" - def __init__(self): + def __init__(self) -> None: self._url = GoUrl() self._cache = dict() self._cache_status_code = dict() @@ -57,18 +60,23 @@ def get_status_code(self, url: str) -> int: return code -if __name__ == '__main__': - import time +if __name__ == "__main__": + from timeit import default_timer class TimeThis: def __enter__(self): - self.start_time = time.clock() + self.start_time = default_timer() return self - def __exit__(self, exc_type, exc_value, exc_traceback): - print('Elapsed time: {:.6f} sec'.format(time.clock() - self.start_time)) + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc_value: Optional[BaseException], + exc_traceback: Optional[TracebackType], + ) -> None: + print(f"Elapsed time: {default_timer() - self.start_time:.6f} sec") - url = 'https://github.com/gil9red' + url = "https://github.com/gil9red" go_url = GoUrl() @@ -88,7 +96,7 @@ def __exit__(self, exc_type, exc_value, exc_traceback): print(rs, rs.status_code, code, rs.content) print() - print('Cached proxy:') + print("Cached proxy:") go_url = GoUrlCachedProxy() diff --git a/design_patterns__examples/Proxy/example__logged.py b/design_patterns__examples/Proxy/example__logged.py index 0d8017d73..10e59266e 100644 --- a/design_patterns__examples/Proxy/example__logged.py +++ b/design_patterns__examples/Proxy/example__logged.py @@ -1,28 +1,36 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" +import logging +import sys + +from logging.handlers import RotatingFileHandler +from types import TracebackType +from typing import Optional, Type + from example__cached import IGoUrl, GoUrl, GoUrlCachedProxy, requests -def get_logger(name, file='log.txt', encoding='utf-8'): - import logging +def get_logger(name, file="log.txt", encoding="utf-8"): log = logging.getLogger(name) log.setLevel(logging.DEBUG) - formatter = logging.Formatter('[%(asctime)s] %(filename)s:%(lineno)d %(levelname)-8s %(message)s') + formatter = logging.Formatter( + "[%(asctime)s] %(filename)s:%(lineno)d %(levelname)-8s %(message)s" + ) # Simple file handler # fh = logging.FileHandler(file, encoding=encoding) # or: - from logging.handlers import RotatingFileHandler - fh = RotatingFileHandler(file, maxBytes=10000000, backupCount=5, encoding=encoding) + fh = RotatingFileHandler( + file, maxBytes=10_000_000, backupCount=5, encoding=encoding + ) fh.setFormatter(formatter) log.addHandler(fh) - import sys sh = logging.StreamHandler(stream=sys.stdout) sh.setFormatter(formatter) log.addHandler(sh) @@ -33,9 +41,9 @@ def get_logger(name, file='log.txt', encoding='utf-8'): class GoUrlLoggedProxy(IGoUrl): """Прокси""" - _LOGGER = get_logger('GoUrlLoggedProxy') + _LOGGER = get_logger("GoUrlLoggedProxy") - def __init__(self, go_url: IGoUrl = None): + def __init__(self, go_url: IGoUrl = None) -> None: if go_url is None: go_url = GoUrl() @@ -57,22 +65,27 @@ def get_status_code(self, url: str) -> int: return code - def _log(self, text: str): + def _log(self, text: str) -> None: GoUrlLoggedProxy._LOGGER.debug(text) -if __name__ == '__main__': - import time +if __name__ == "__main__": + from timeit import default_timer class TimeThis: def __enter__(self): - self.start_time = time.clock() + self.start_time = default_timer() return self - def __exit__(self, exc_type, exc_value, exc_traceback): - print('Elapsed time: {:.6f} sec'.format(time.clock() - self.start_time)) + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc_value: Optional[BaseException], + exc_traceback: Optional[TracebackType], + ) -> None: + print(f"Elapsed time: {default_timer() - self.start_time:.6f} sec") - url = 'https://github.com/gil9red' + url = "https://github.com/gil9red" go_url = GoUrl() @@ -82,7 +95,7 @@ def __exit__(self, exc_type, exc_value, exc_traceback): print(rs, rs.status_code, code, rs.content) print() - print('Logged proxy:') + print("Logged proxy:") go_url = GoUrlLoggedProxy() @@ -92,7 +105,7 @@ def __exit__(self, exc_type, exc_value, exc_traceback): print(rs, rs.status_code, code, rs.content) print() - print('Cached proxy with Logged proxy:') + print("Cached proxy with Logged proxy:") go_url = GoUrlLoggedProxy(go_url=GoUrlCachedProxy()) diff --git a/design_patterns__examples/Proxy/example__with_abc.py b/design_patterns__examples/Proxy/example__with_abc.py index 85dcb3baa..30ff7f6b4 100644 --- a/design_patterns__examples/Proxy/example__with_abc.py +++ b/design_patterns__examples/Proxy/example__with_abc.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://ru.wikipedia.org/wiki/Заместитель_(шаблон_проектирования) @@ -49,7 +49,7 @@ def div(self, x, y): class MathProxy(IMath): """Прокси""" - def __init__(self): + def __init__(self) -> None: self.math = None # Быстрые операции - не требуют реального субъекта @@ -68,7 +68,7 @@ def mul(self, x, y): def div(self, x, y): if y == 0: - return float('inf') # Вернуть positive infinity + return float("inf") # Вернуть positive infinity if not self.math: self.math = Math() @@ -76,10 +76,10 @@ def div(self, x, y): return self.math.div(x, y) -if __name__ == '__main__': +if __name__ == "__main__": p = MathProxy() x, y = 4, 2 - print('4 + 2 =', p.add(x, y)) - print('4 - 2 =', p.sub(x, y)) - print('4 * 2 =', p.mul(x, y)) - print('4 / 2 =', p.div(x, y)) + print("4 + 2 =", p.add(x, y)) + print("4 - 2 =", p.sub(x, y)) + print("4 * 2 =", p.mul(x, y)) + print("4 / 2 =", p.div(x, y)) diff --git a/design_patterns__examples/Singleton/example.py b/design_patterns__examples/Singleton/example.py index ec0c137cf..92a30f0e4 100644 --- a/design_patterns__examples/Singleton/example.py +++ b/design_patterns__examples/Singleton/example.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://www.python.org/dev/peps/pep-0318/#examples @@ -14,10 +14,12 @@ def get_instance(): if cls not in instances: instances[cls] = cls() return instances[cls] + return get_instance -if __name__ == '__main__': +if __name__ == "__main__": + @singleton class MyClass: pass diff --git a/design_patterns__examples/Strategy/example.py b/design_patterns__examples/Strategy/example.py index 30ed9216b..dddbba159 100644 --- a/design_patterns__examples/Strategy/example.py +++ b/design_patterns__examples/Strategy/example.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: Design Patterns: Strategy — Стратегия @@ -19,27 +19,27 @@ def download(self, file: str): class DownloadWindowsStrategy(Strategy): - def download(self, file: str): + def download(self, file: str) -> None: print("Windows download: " + file) class DownloadLinuxStrategy(Strategy): - def download(self, file: str): + def download(self, file: str) -> None: print("Linux download: " + file) class Context: - def __init__(self, strategy: Strategy): + def __init__(self, strategy: Strategy) -> None: self._strategy = strategy - def set_strategy(self, strategy: Strategy): + def set_strategy(self, strategy: Strategy) -> None: self._strategy = strategy - def download(self, file: str): + def download(self, file: str) -> None: self._strategy.download(file) -if __name__ == '__main__': +if __name__ == "__main__": context = Context(DownloadWindowsStrategy()) context.download("file.txt") # Windows download: file.txt print() diff --git a/design_patterns__examples/Strategy/example_1.py b/design_patterns__examples/Strategy/example_1.py index 9ac1136ec..5519920bb 100644 --- a/design_patterns__examples/Strategy/example_1.py +++ b/design_patterns__examples/Strategy/example_1.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: Design Patterns: Strategy — Стратегия @@ -28,17 +28,17 @@ def action(self, a, b): class Context: - def __init__(self, strategy: Strategy): + def __init__(self, strategy: Strategy) -> None: self._strategy = strategy - def set_strategy(self, strategy: Strategy): + def set_strategy(self, strategy: Strategy) -> None: self._strategy = strategy def action(self, a, b): return self._strategy.action(a, b) -if __name__ == '__main__': +if __name__ == "__main__": context = Context(AdditionStrategy()) print(context.action(10, 5)) # 15 print() diff --git a/design_patterns__examples/Template method/example_1.py b/design_patterns__examples/Template method/example_1.py index f31c7c2c5..8ae667269 100644 --- a/design_patterns__examples/Template method/example_1.py +++ b/design_patterns__examples/Template method/example_1.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: Design Patterns: Template method — Шаблонный метод @@ -29,10 +29,10 @@ def hit_and_run(self) -> None: """ Шаблонный метод """ - self._move('вперед') + self._move("вперед") self._stop() self._attack() - self._move('назад') + self._move("назад") @abstractmethod def _attack(self) -> None: @@ -48,7 +48,7 @@ def _move(self, direction: str) -> None: :param direction: направление движения """ - self._output('движется {} со скоростью {}'.format(direction, self._speed)) + self._output(f"движется {direction} со скоростью {self._speed}") def _output(self, message: str) -> None: """ @@ -56,7 +56,7 @@ def _output(self, message: str) -> None: :param message: выводимое сообщение """ - print('Отряд типа {} {}'.format(self.__class__.__name__, message)) + print(f"Отряд типа {self.__class__.__name__} {message}") class Archers(Unit): @@ -65,10 +65,10 @@ class Archers(Unit): """ def _attack(self) -> None: - self._output('обстреливает врага') + self._output("обстреливает врага") def _stop(self) -> None: - self._output('останавливается в 100 шагах от врага') + self._output("останавливается в 100 шагах от врага") class Cavarlymen(Unit): @@ -77,14 +77,14 @@ class Cavarlymen(Unit): """ def _attack(self) -> None: - self._output('на полном скаку врезается во вражеский строй') + self._output("на полном скаку врезается во вражеский строй") def _stop(self) -> None: - self._output('летит вперед, не останавливаясь') + self._output("летит вперед, не останавливаясь") -if __name__ == '__main__': - print('OUTPUT:') +if __name__ == "__main__": + print("OUTPUT:") archers = Archers(4) archers.hit_and_run() cavarlymen = Cavarlymen(8) diff --git a/design_patterns__examples/Template method/example_2.py b/design_patterns__examples/Template method/example_2.py index e3271ea52..6c6b81956 100644 --- a/design_patterns__examples/Template method/example_2.py +++ b/design_patterns__examples/Template method/example_2.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: Design Patterns: Template method — Шаблонный метод @@ -18,22 +18,22 @@ def end_of_game(self) -> bool: pass @abstractmethod - def initialize_game(self): + def initialize_game(self) -> None: pass @abstractmethod - def make_play(self, player: int): + def make_play(self, player: int) -> None: pass @abstractmethod - def print_winner(self): + def print_winner(self) -> None: pass # A template method : - def play_one_game(self, players_count: int): + def play_one_game(self, players_count: int) -> None: self.players_count = players_count self.initialize_game() - + j = 0 while not self.end_of_game(): @@ -46,18 +46,18 @@ def play_one_game(self, players_count: int): # Now we can extend this class in order to implement actual games: class Monopoly(GameObject): # Implementation of necessary concrete methods - def initialize_game(self): + def initialize_game(self) -> None: # Initialize money ... - def make_play(self, player: int): + def make_play(self, player: int) -> None: # Process one turn of player ... def end_of_game(self) -> bool: return True - def print_winner(self): + def print_winner(self) -> None: # Display who won ... @@ -67,11 +67,11 @@ def print_winner(self): class Chess(GameObject): # Implementation of necessary concrete methods - def initialize_game(self): + def initialize_game(self) -> None: # Put the pieces on the board ... - def make_play(self, player: int): + def make_play(self, player: int) -> None: # Process a turn for the player ... @@ -79,7 +79,7 @@ def end_of_game(self) -> bool: # Return true if in Checkmate or Stalemate has been reached return True - def print_winner(self): + def print_winner(self) -> None: # Display the winning player ... @@ -87,7 +87,7 @@ def print_winner(self): # ... -if __name__ == '__main__': +if __name__ == "__main__": game = Monopoly() game.play_one_game(2) diff --git a/design_patterns__examples/Template method/example_3.py b/design_patterns__examples/Template method/example_3.py index ff82da0ef..6ea680ff2 100644 --- a/design_patterns__examples/Template method/example_3.py +++ b/design_patterns__examples/Template method/example_3.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: Design Patterns: Template method — Шаблонный метод @@ -19,7 +19,7 @@ class Network(ABC): user_name: str password: str - + # Публикация данных в любой сети. def post(self, message: str) -> bool: # Проверка данных пользователя перед постом в соцсеть. Каждая сеть для @@ -29,7 +29,7 @@ def post(self, message: str) -> bool: result = self.send_data(message.encode()) self.log_out() return result - + return False @abstractmethod @@ -41,20 +41,20 @@ def send_data(self, data: bytes) -> bool: pass @abstractmethod - def log_out(self): + def log_out(self) -> None: pass # Класс социальной сети. class Facebook(Network): - def __init__(self, user_name: str, password: str): + def __init__(self, user_name: str, password: str) -> None: self.user_name = user_name self.password = password def log_in(self, user_name: str, password: str) -> bool: print("\nChecking user's parameters") print("Name: " + self.user_name) - print("Password: " + ('*' * len(self.password)), end='') + print("Password: " + ("*" * len(self.password)), end="") self._simulate_network_latency() print("\n\nlog_in success on Facebook") @@ -68,34 +68,34 @@ def send_data(self, data: bytes) -> bool: return False - def log_out(self): + def log_out(self) -> None: print("User: '" + self.user_name + "' was logged out from Facebook") - def _simulate_network_latency(self): + def _simulate_network_latency(self) -> None: print() try: i = 0 while i < 10: - print(".", end='') + print(".", end="") time.sleep(0.5) i += 1 - + except Exception as e: print(e) # Класс социальной сети. class Twitter(Network): - def __init__(self, user_name: str, password: str): + def __init__(self, user_name: str, password: str) -> None: self.user_name = user_name self.password = password def log_in(self, user_name: str, password: str) -> bool: print("\nChecking user's parameters") print("Name: " + self.user_name) - print("Password: " + ('*' * len(self.password)), end='') - + print("Password: " + ("*" * len(self.password)), end="") + self._simulate_network_latency() print("\n\nlog_in success on Twitter") return True @@ -108,31 +108,33 @@ def send_data(self, data: bytes) -> bool: return False - def log_out(self): + def log_out(self) -> None: print("User: '" + self.user_name + "' was logged out from Twitter") - def _simulate_network_latency(self): + def _simulate_network_latency(self) -> None: print() try: i = 0 while i < 10: - print(".", end='') + print(".", end="") time.sleep(0.5) i += 1 except Exception as e: print(e) - -if __name__ == '__main__': + +if __name__ == "__main__": user_name = input("Input user name: ") password = input("Input password: ") # Вводим сообщение. message = input("Input message: ") - choice = input("\nChoose social network for posting message.\n1 - Facebook\n2 - Twitter\n") + choice = input( + "\nChoose social network for posting message.\n1 - Facebook\n2 - Twitter\n" + ) network = None # Создаем сетевые объекты и публикуем пост. @@ -143,7 +145,7 @@ def _simulate_network_latency(self): network = Twitter(user_name, password) else: - print('Unknown network!') + print("Unknown network!") sys.exit() - + network.post(message) diff --git a/design_patterns__examples/Template method/example_4.py b/design_patterns__examples/Template method/example_4.py index dd48f5545..31c765e05 100644 --- a/design_patterns__examples/Template method/example_4.py +++ b/design_patterns__examples/Template method/example_4.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: Design Patterns: Template def — Шаблонный метод @@ -10,7 +10,7 @@ from abc import ABC, abstractmethod -from typing import Optional, List +from typing import Optional class Map: @@ -19,7 +19,7 @@ def center(self) -> ...: class GameAI(ABC): - def __init__(self): + def __init__(self) -> None: self.scouts = [] self.warriors = [] self.map = Map() @@ -27,28 +27,28 @@ def __init__(self): # Шаблонный метод должен быть задан в базовом классе. Он # состоит из вызовов методов в определённом порядке. Чаще # всего эти методы являются шагами некоего алгоритма. - def turn(self): + def turn(self) -> None: self.collect_resources() self.build_structures() self.build_units() self.attack() # Некоторые из этих методов могут быть реализованы прямо в базовом классе. - def collect_resources(self): + def collect_resources(self) -> None: for s in self.build_structures(): s.collect() # А некоторые могут быть полностью абстрактными. @abstractmethod - def build_structures(self) -> List['Structure']: + def build_structures(self) -> list["Structure"]: pass @abstractmethod - def build_units(self): + def build_units(self) -> None: pass # Кстати, шаблонных методов в классе может быть несколько. - def attack(self): + def attack(self) -> None: enemy = self.closest_enemy() if enemy is None: @@ -56,22 +56,22 @@ def attack(self): else: self.send_warriors(enemy.position) - def closest_enemy(self) -> Optional['Enemy']: + def closest_enemy(self) -> Optional["Enemy"]: ... @abstractmethod - def send_scouts(self, position): + def send_scouts(self, position) -> None: pass @abstractmethod - def send_warriors(self, position): + def send_warriors(self, position) -> None: pass # Подклассы могут предоставлять свою реализацию шагов # алгоритма, не изменяя сам шаблонный метод. class OrcsAI(GameAI): - def build_structures(self) -> List['Structure']: + def build_structures(self) -> list["Structure"]: structures = [] there_are_some_resources: bool = ... @@ -82,7 +82,7 @@ def build_structures(self) -> List['Structure']: return structures - def build_units(self): + def build_units(self) -> None: there_are_plenty_of_resources: bool = ... there_are_no_scouts: bool = ... @@ -96,12 +96,12 @@ def build_units(self): # ... - def send_scouts(self, position): + def send_scouts(self, position) -> None: if self.scouts: # Отправить разведчиков на позицию. ... - def send_warriors(self, position): + def send_warriors(self, position) -> None: if len(self.warriors) > 5: # Отправить воинов на позицию. ... @@ -110,30 +110,30 @@ def send_warriors(self, position): # Подклассы могут не только реализовывать абстрактные шаги, но # и переопределять шаги, уже реализованные в базовом классе. class MonstersAI(GameAI): - def collect_resources(self): + def collect_resources(self) -> None: # Ничего не делать. pass - def build_structures(self): + def build_structures(self) -> None: # Ничего не делать. pass - def build_units(self): + def build_units(self) -> None: # Ничего не делать. pass - def send_scouts(self, position): + def send_scouts(self, position) -> None: if self.scouts: # Отправить разведчиков на позицию. ... - def send_warriors(self, position): + def send_warriors(self, position) -> None: if len(self.warriors) > 5: # Отправить воинов на позицию. ... -if __name__ == '__main__': +if __name__ == "__main__": ai = MonstersAI() ai.turn() diff --git a/destroy_chain_balls.py b/destroy_chain_balls.py index b966d5057..a38c2b0a2 100644 --- a/destroy_chain_balls.py +++ b/destroy_chain_balls.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" def destroy_chain_balls(balls): @@ -41,27 +41,27 @@ def destroy_chain_balls(balls): return balls -if __name__ == '__main__': +if __name__ == "__main__": balls = [0, 1, 2, 3] balls_2 = destroy_chain_balls(balls) - print("{} -> {}, уничтожено: {}".format(balls, balls_2, len(balls) - len(balls_2))) + print(f"{balls} -> {balls_2}, уничтожено: {len(balls) - len(balls_2)}") balls = [0, 1, 2, 2, 1, 2, 3] balls_2 = destroy_chain_balls(balls) - print("{} -> {}, уничтожено: {}".format(balls, balls_2, len(balls) - len(balls_2))) + print(f"{balls} -> {balls_2}, уничтожено: {len(balls) - len(balls_2)}") balls = [0, 1, 2, 2, 1, 1, 1, 2, 3] balls_2 = destroy_chain_balls(balls) - print("{} -> {}, уничтожено: {}".format(balls, balls_2, len(balls) - len(balls_2))) + print(f"{balls} -> {balls_2}, уничтожено: {len(balls) - len(balls_2)}") balls = [1, 3, 3, 3, 2] balls_2 = destroy_chain_balls(balls) - print("{} -> {}, уничтожено: {}".format(balls, balls_2, len(balls) - len(balls_2))) + print(f"{balls} -> {balls_2}, уничтожено: {len(balls) - len(balls_2)}") balls = [1, 1, 1] balls_2 = destroy_chain_balls(balls) - print("{} -> {}, уничтожено: {}".format(balls, balls_2, len(balls) - len(balls_2))) + print(f"{balls} -> {balls_2}, уничтожено: {len(balls) - len(balls_2)}") balls = [1, 1] balls_2 = destroy_chain_balls(balls) - print("{} -> {}, уничтожено: {}".format(balls, balls_2, len(balls) - len(balls_2))) + print(f"{balls} -> {balls_2}, уничтожено: {len(balls) - len(balls_2)}") diff --git a/detection_of_site_changes_Unistream/gui.py b/detection_of_site_changes_Unistream/gui.py index 0d594e234..afaf178f9 100644 --- a/detection_of_site_changes_Unistream/gui.py +++ b/detection_of_site_changes_Unistream/gui.py @@ -1,12 +1,13 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """Скрипт создает окно, позволяющее просматривать все ревизии и их различие.""" +import os import sys from PySide.QtGui import * @@ -21,10 +22,10 @@ class MainWindow(QMainWindow): - def __init__(self): + def __init__(self) -> None: super().__init__() - self.setWindowTitle('detection_of_site_changes_Unistream') + self.setWindowTitle("detection_of_site_changes_Unistream") self.revision_list_widget = QListWidget() self.revision_list_widget.itemDoubleClicked.connect(self._show_last_diff) @@ -47,28 +48,31 @@ def __init__(self): if len(items) > 1: for item in items: # Придаем элементу ревизии цвет, зависящий от первых 6-ти символов его хеша - color = QColor('#' + text_hash[:6]) + color = QColor("#" + text_hash[:6]) item.setBackground(color) self.setCentralWidget(self.revision_list_widget) - def _show_last_diff(self, item): + def _show_last_diff(self, item) -> None: row = self.revision_list_widget.row(item) - file_name_a = 'file_a' - file_name_b = 'file_b' + file_name_a = "file_a" + file_name_b = "file_b" - file_a_text = self.revision_list_widget.item(row - 1).data(Qt.UserRole).text if row > 0 else '' + file_a_text = ( + self.revision_list_widget.item(row - 1).data(Qt.UserRole).text + if row > 0 + else "" + ) file_b_text = self.revision_list_widget.item(row).data(Qt.UserRole).text - with open(file_name_a, mode='w', encoding='utf-8') as f: + with open(file_name_a, mode="w", encoding="utf-8") as f: f.write(file_a_text) - with open(file_name_b, mode='w', encoding='utf-8') as f: + with open(file_name_b, mode="w", encoding="utf-8") as f: f.write(file_b_text) - import os - os.system('kdiff3 {} {}'.format(file_name_a, file_name_b)) + os.system(f"kdiff3 {file_name_a} {file_name_b}") if os.path.exists(file_name_a): os.remove(file_name_a) @@ -78,7 +82,7 @@ def _show_last_diff(self, item): # TODO: выделять одинаковые элементы одним цветом -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication(sys.argv) mw = MainWindow() diff --git a/detection_of_site_changes_Unistream/main.py b/detection_of_site_changes_Unistream/main.py index f190d6a82..bfb3676bb 100644 --- a/detection_of_site_changes_Unistream/main.py +++ b/detection_of_site_changes_Unistream/main.py @@ -1,50 +1,64 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" +import difflib +import hashlib +import logging +import os +import sys + +from PySide.QtGui import QApplication +from PySide.QtCore import QEventLoop +from PySide.QtWebKit import QWebSettings, QWebPage, QWebView +from PySide.QtNetwork import QNetworkProxyFactory + +from sqlalchemy import create_engine, Column, Integer, String, DateTime +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import sessionmaker + # NOTE: костыль для винды, для исправления проблем с исключениями # при выводе юникодных символов в консоль винды -import sys -if sys.platform == 'win32': +if sys.platform == "win32": import codecs + # Для винды кодировкой консоли будет cp866 - sys.stdout = codecs.getwriter(sys.stdout.encoding)(sys.stdout.detach(), 'backslashreplace') - sys.stderr = codecs.getwriter(sys.stderr.encoding)(sys.stderr.detach(), 'backslashreplace') + sys.stdout = codecs.getwriter(sys.stdout.encoding)( + sys.stdout.detach(), "backslashreplace" + ) + sys.stderr = codecs.getwriter(sys.stderr.encoding)( + sys.stderr.detach(), "backslashreplace" + ) + -import os DIR = os.path.dirname(__file__) - -import logging + + logging.basicConfig( level=logging.DEBUG, - format='[%(asctime)s] %(filename)s[LINE:%(lineno)d] %(levelname)-8s %(message)s', + format="[%(asctime)s] %(filename)s[LINE:%(lineno)d] %(levelname)-8s %(message)s", handlers=[ - logging.FileHandler(os.path.join(DIR, 'log'), encoding='utf8'), + logging.FileHandler(os.path.join(DIR, "log"), encoding="utf8"), logging.StreamHandler(stream=sys.stdout), ], ) -def get_site_text(url='https://test.api.unistream.com/help/index.html'): +def get_site_text(url="https://test.api.unistream.com/help/index.html"): """Функция возвращает содержимое по указанному url.""" - import sys - - from PySide.QtGui import QApplication - from PySide.QtCore import QEventLoop - from PySide.QtWebKit import QWebSettings, QWebPage, QWebView - from PySide.QtNetwork import QNetworkProxyFactory - # Чтобы не было проблем запуска компов с прокси: QNetworkProxyFactory.setUseSystemConfiguration(True) - QWebSettings.globalSettings().setAttribute(QWebSettings.DeveloperExtrasEnabled, True) + QWebSettings.globalSettings().setAttribute( + QWebSettings.DeveloperExtrasEnabled, True + ) class WebPage(QWebPage): - def userAgentForUrl(self, url): - return 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:45.0) Gecko/20100101 Firefox/45.0' + def userAgentForUrl(self, url) -> str: + return "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:45.0) Gecko/20100101 Firefox/45.0" if QApplication.instance() is None: QApplication(sys.argv) @@ -66,7 +80,6 @@ def userAgentForUrl(self, url): def get_hash_from_str(text): """Функция возвращает хеш от строки в виде HEX чисел, используя алгоритм sha1.""" - import hashlib alg = hashlib.sha1() alg.update(text.encode()) return alg.hexdigest().upper() @@ -78,15 +91,13 @@ def get_diff(str_1, str_2, full=True): """ - logging.debug('x1') - import difflib - - logging.debug('x2') + logging.debug("x1") + logging.debug("x2") diff_html = "" - logging.debug('x3') + logging.debug("x3") theDiffs = difflib.ndiff(str_1.splitlines(), str_2.splitlines()) - logging.debug('x4') + logging.debug("x4") theDiffs = list(theDiffs) print(theDiffs) for eachDiff in theDiffs: @@ -94,18 +105,20 @@ def get_diff(str_1, str_2, full=True): diff_html += "%s
" % eachDiff[1:].strip() elif eachDiff[0] == "+": diff_html += "%s
" % eachDiff[1:].strip() - logging.debug('x5') + logging.debug("x5") print(112121, diff_html) if full: - return """ """ + diff_html + "" + return ( + """ """ + + diff_html + + "" + ) else: return diff_html -from sqlalchemy import Column, Integer, String, DateTime -from sqlalchemy.ext.declarative import declarative_base Base = declarative_base() @@ -116,7 +129,7 @@ class TextRevision(Base): """ - __tablename__ = 'TextRevision' + __tablename__ = "TextRevision" id = Column(Integer, primary_key=True) @@ -136,7 +149,7 @@ class TextRevision(Base): # Содержимым является только разница diff = Column(String) - def __init__(self, new_text, old_text=''): + def __init__(self, new_text, old_text="") -> None: """ Конструктор принимает контент и сравниваемый контент, запоминает хеш содержимого, текущую дату и время и результат сравнения @@ -153,25 +166,23 @@ def __init__(self, new_text, old_text=''): self.diff_full = get_diff(old_text, new_text) self.diff = get_diff(old_text, new_text, full=False) - def __repr__(self): - return "".format(self.id, self.datetime, self.text_hash) + def __repr__(self) -> str: + return f"" def get_session(): - DB_FILE_NAME = 'sqlite:///' + os.path.join(DIR, 'database') + DB_FILE_NAME = "sqlite:///" + os.path.join(DIR, "database") # DB_FILE_NAME = 'sqlite:///:memory:' # Создаем базу, включаем логирование и автообновление подключения каждые 2 часа (7200 секунд) - from sqlalchemy import create_engine engine = create_engine( DB_FILE_NAME, # echo=True, - pool_recycle=7200 + pool_recycle=7200, ) Base.metadata.create_all(engine) - from sqlalchemy.orm import sessionmaker Session = sessionmaker(bind=engine) return Session() @@ -189,9 +200,7 @@ def add_text_revision(text): """Функция добавляет новую ревизию, предварительно сравнив ее с предыдущей.""" last = get_last_revision() - logging.debug('Последняя запись: %s.', last) - - text_revision = None + logging.debug("Последняя запись: %s.", last) # Если таблица пуста if last is None: @@ -199,32 +208,32 @@ def add_text_revision(text): else: # Если хеши текстов отличаются, добавляем новую ревизию if last.text_hash != get_hash_from_str(text): - logging.debug('Обнаружено изменение, создаю ревизию.') + logging.debug("Обнаружено изменение, создаю ревизию.") text_revision = TextRevision(text, last.text) - logging.debug('-') + logging.debug("-") else: - logging.debug('Одинаковые значения, пропускаю добавление.') + logging.debug("Одинаковые значения, пропускаю добавление.") return - logging.debug('@') + logging.debug("@") if text_revision: - logging.debug('add') + logging.debug("add") session.add(text_revision) - logging.debug('commit') + logging.debug("commit") session.commit() - logging.debug('return') + logging.debug("return") return text_revision -if __name__ == '__main__': - logging.debug('Запуск.') +if __name__ == "__main__": + logging.debug("Запуск.") import time while True: try: - logging.debug('Проверка сайта.') + logging.debug("Проверка сайта.") # text = "dfsdfsdfsdfsdf" # text += get_site_text() @@ -243,6 +252,7 @@ def add_text_revision(text): # "createTime": "2016-06-22T10:13:42.0888396+03:00", # "templateId": "e6635cf6-47c3-4164-8173-cb8fd249604a" import re + text = re.sub(r'"((?i)OperationId)": ".+?"', r'"\1": ""', text) text = re.sub(r'"((?i)CommandId)": ".+?"', r'"\1": ""', text) text = re.sub(r'"((?i)cashierUniqueId)": ".+?"', r'"\1": ""', text) @@ -253,7 +263,7 @@ def add_text_revision(text): continue add_text_revision(text) - logging.debug('Проверка закончена.') + logging.debug("Проверка закончена.") # last = get_last_revision() # print(len(last.text)) @@ -264,7 +274,7 @@ def add_text_revision(text): # Задержка каждые 7 часов time.sleep(60 * 60 * 7) except Exception: - logging.exception('Error:') + logging.exception("Error:") # last = get_last_revision() # print(len(last.text)) diff --git a/detection_of_site_changes_Unistream/print_all_revision.py b/detection_of_site_changes_Unistream/print_all_revision.py index 2b1e665bc..ae9b38c66 100644 --- a/detection_of_site_changes_Unistream/print_all_revision.py +++ b/detection_of_site_changes_Unistream/print_all_revision.py @@ -1,13 +1,13 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """Скрипт выводит все текстовые ревизии.""" -if __name__ == '__main__': +if __name__ == "__main__": from main import session, TextRevision text_revisions = session.query(TextRevision).all() diff --git a/detection_of_site_changes_Unistream/remove_last_diff.py b/detection_of_site_changes_Unistream/remove_last_diff.py index e32e6e328..9bca996c2 100644 --- a/detection_of_site_changes_Unistream/remove_last_diff.py +++ b/detection_of_site_changes_Unistream/remove_last_diff.py @@ -1,13 +1,13 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """Скрипт удаляет последнюю ревизию.""" -if __name__ == '__main__': +if __name__ == "__main__": from main import session, TextRevision text_revision = session.query(TextRevision).order_by(TextRevision.id.desc()).first() diff --git a/detection_of_site_changes_Unistream/show_last_diff.py b/detection_of_site_changes_Unistream/show_last_diff.py index 7ea360b09..73f8ecb15 100644 --- a/detection_of_site_changes_Unistream/show_last_diff.py +++ b/detection_of_site_changes_Unistream/show_last_diff.py @@ -1,13 +1,14 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """Скрипт находит последние две ревизии, сохраняет их текст в файлы и сравнивает с помощью kdiff3.""" -if __name__ == '__main__': +if __name__ == "__main__": + import os from main import session, TextRevision # TODO: нет смысла запрашивать все, если нужны только 2 последнии @@ -15,17 +16,16 @@ if len(text_revisions) >= 2: file_a, file_b = text_revisions[-2:] - file_name_a = 'file_a' - file_name_b = 'file_b' + file_name_a = "file_a" + file_name_b = "file_b" - with open(file_name_a, mode='w', encoding='utf-8') as f: + with open(file_name_a, mode="w", encoding="utf-8") as f: f.write(file_a.text) - with open(file_name_b, mode='w', encoding='utf-8') as f: + with open(file_name_b, mode="w", encoding="utf-8") as f: f.write(file_b.text) - import os - os.system('kdiff3 {} {}'.format(file_name_a, file_name_b)) + os.system(f"kdiff3 {file_name_a} {file_name_b}") if os.path.exists(file_name_a): os.remove(file_name_a) diff --git a/determines the type of image/main.py b/determines the type of image/main.py index 33b7dc80c..c56b0450b 100644 --- a/determines the type of image/main.py +++ b/determines the type of image/main.py @@ -1,11 +1,13 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import glob -for file_name in glob.glob('img/*.txt'): - import imghdr +import imghdr + + +for file_name in glob.glob("img/*.txt"): img_type = imghdr.what(file_name) - print('{} -> {}'.format(file_name, img_type)) + print(f"{file_name} -> {img_type}") diff --git a/dict_to_url_params.py b/dict_to_url_params.py index dbfc2d303..c30173bf5 100644 --- a/dict_to_url_params.py +++ b/dict_to_url_params.py @@ -1,23 +1,23 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" def dict_to_url_params(json_data, root): - def deep(node, root, items): + def deep(node, root, items) -> None: if isinstance(node, list): for i, value in enumerate(node): - node_root = root + f'[{i}]' + node_root = root + f"[{i}]" deep(value, node_root, items) elif isinstance(node, dict): for key, value in node.items(): - node_root = root + f'[{key}]' + node_root = root + f"[{key}]" deep(value, node_root, items) else: - root += '=' + node + root += "=" + node items.append(root) items = [] @@ -27,7 +27,7 @@ def deep(node, root, items): return items -if __name__ == '__main__': +if __name__ == "__main__": JSON_FORM_DATA = { "add": [ { @@ -36,9 +36,7 @@ def deep(node, root, items): "created_at": "1529007000", "incoming_entities": { "leads": [ - { - "name": "Покупка" - }, + {"name": "Покупка"}, ], "contacts": [ { @@ -48,48 +46,37 @@ def deep(node, root, items): { "id": "382707", "values": [ - { - "value": "+77777777777", - "enum": "WORK" - } - ] + {"value": "+77777777777", "enum": "WORK"} + ], }, { "id": "389993", - "values": [ - { - "value": "sfgh3gh233h3h3h3" - } - ] + "values": [{"value": "sfgh3gh233h3h3h3"}], }, { "id": "389995", - "values": [ - { - "value": "Обратный звонок" - } - ] - } - ] + "values": [{"value": "Обратный звонок"}], + }, + ], } - ] + ], }, "incoming_lead_info": { "form_id": "329248", "form_page": "vdtest.ru", "ip": "127.0.0.1", - "service_code": "QkKwSam8" - } + "service_code": "QkKwSam8", + }, } ] } - items = dict_to_url_params(JSON_FORM_DATA['add'], root='add') + items = dict_to_url_params(JSON_FORM_DATA["add"], root="add") - params = '&'.join(items) - print('Params: ' + params) + params = "&".join(items) + print("Params: " + params) print() - print('Params:') + print("Params:") for x in items: print(x) diff --git a/diff_between_dates.py b/diff_between_dates.py index 6b4d3623a..78b38de22 100644 --- a/diff_between_dates.py +++ b/diff_between_dates.py @@ -1,24 +1,24 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from datetime import datetime items = [ - '23/03/2007', - '05/12/2007', - '22/08/2008', - '02/10/2009', + "23/03/2007", + "05/12/2007", + "22/08/2008", + "02/10/2009", ] for i in range(len(items) - 1): date_str_1, date_str_2 = items[i], items[i + 1] - date_1 = datetime.strptime(date_str_1, '%d/%m/%Y') - date_2 = datetime.strptime(date_str_2, '%d/%m/%Y') + date_1 = datetime.strptime(date_str_1, "%d/%m/%Y") + date_2 = datetime.strptime(date_str_2, "%d/%m/%Y") days = (date_2 - date_1).days - print('{} - {} -> {} days'.format(date_str_1, date_str_2, days)) + print(f"{date_str_1} - {date_str_2} -> {days} days") diff --git a/diff_iterables.py b/diff_iterables.py new file mode 100644 index 000000000..bde957abb --- /dev/null +++ b/diff_iterables.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from dataclasses import dataclass +from typing import Iterable + + +@dataclass +class Result: + added: list + deleted: list + + +def diff_iterables(iterable_1: Iterable, iterable_2: Iterable) -> Result: + return Result( + added=[x for x in iterable_2 if x not in iterable_1], + deleted=[x for x in iterable_1 if x not in iterable_2], + ) + + +if __name__ == "__main__": + items_1 = "01234" + items_2 = "12456" + result = diff_iterables(items_1, items_2) + print(result) + assert ( + Result( + added=["5", "6"], + deleted=["0", "3"], + ) + == result + ) + # Result(added=['5', '6'], deleted=['0', '3']) + + assert ( + Result(added=[], deleted=[]) + == diff_iterables(items_1, items_1) + ) + + assert ( + Result(added=["4"], deleted=[]) + == diff_iterables("123", "1234") + ) + + assert ( + Result(added=[], deleted=["3"]) + == diff_iterables("123", "12") + ) diff --git a/diff_times.py b/diff_times.py index 2df9b7268..c4c843aeb 100644 --- a/diff_times.py +++ b/diff_times.py @@ -1,14 +1,15 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -import datetime as DT +from datetime import datetime + check_hours, check_minutes = 16, 29 -dt = DT.datetime.now() +dt = datetime.now() check_dt = dt.replace(hour=check_hours, minute=check_minutes) if dt > check_dt: diff --git a/difflib__examples/hello_world.py b/difflib__examples/hello_world.py index 42d75feca..a31140811 100644 --- a/difflib__examples/hello_world.py +++ b/difflib__examples/hello_world.py @@ -1,18 +1,33 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import difflib words = [ - 'смыло', 'соло', 'вяло', 'мяло', 'смола', 'поле', 'воля', 'с мола', 'была', - 'стела', 'с мела', 'смело' + "смыло", + "соло", + "вяло", + "мяло", + "смола", + "поле", + "воля", + "с мола", + "была", + "стела", + "с мела", + "смело", ] -word = 'смело' +word = "смело" -print(difflib.get_close_matches(word, words)) # ['смело', 'смыло', 'с мела'] -print(difflib.get_close_matches(word, words, cutoff=0.6)) # ['смело', 'смыло', 'с мела'] -print(difflib.get_close_matches(word, words, cutoff=0.8)) # ['смело', 'смыло'] +print(difflib.get_close_matches(word, words)) +# ['смело', 'смыло', 'с мела'] + +print(difflib.get_close_matches(word, words, cutoff=0.6)) +# ['смело', 'смыло', 'с мела'] + +print(difflib.get_close_matches(word, words, cutoff=0.8)) +# ['смело', 'смыло'] diff --git a/digital_pyramid.py b/digital_pyramid.py index ccb94077c..9a3ad6409 100644 --- a/digital_pyramid.py +++ b/digital_pyramid.py @@ -1,15 +1,15 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" n = 9 for i in range(1, n + 1): - left = ''.join(map(str, range(1, i))) - spaces = ' ' * (n - i) - print(f'{spaces}{left}{i}{left[::-1]}') + left = "".join(map(str, range(1, i))) + spaces = " " * (n - i) + print(f"{spaces}{left}{i}{left[::-1]}") # 1 # 121 @@ -26,7 +26,7 @@ x = "" for i in range(1, n + 1): x += str(i) - print(' ' * (n-i) + x + x[-2::-1]) + print(" " * (n - i) + x + x[-2::-1]) # 1 # 121 # 12321 diff --git a/dis__bytecode__is_empty_function.py b/dis__bytecode__is_empty_function.py index 3edd59b9f..7f54f9bea 100644 --- a/dis__bytecode__is_empty_function.py +++ b/dis__bytecode__is_empty_function.py @@ -1,27 +1,30 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" + + +import dis # https://ru.stackoverflow.com/a/767523/201445 def check_is_empty_function(function): - import dis instructions = dis.get_instructions(function) instr = next(instructions, None) - if instr is None or instr.opname != 'LOAD_CONST' or instr.argrepr != 'None': + if instr is None or instr.opname != "LOAD_CONST" or instr.argrepr != "None": return False instr = next(instructions, None) - return instr and (instr.opname == 'RETURN_VALUE') + return instr and (instr.opname == "RETURN_VALUE") + +if __name__ == "__main__": -if __name__ == '__main__': - def foo(): + def foo() -> None: pass - def foo2(): + def foo2() -> int: return 1 - print('Empty:', check_is_empty_function(foo)) - print('Empty:', check_is_empty_function(foo2)) + print("Empty:", check_is_empty_function(foo)) + print("Empty:", check_is_empty_function(foo2)) diff --git a/disabling_redirection/requests_example.py b/disabling_redirection/requests_example.py index c1d2b86d2..86dbab794 100644 --- a/disabling_redirection/requests_example.py +++ b/disabling_redirection/requests_example.py @@ -1,16 +1,16 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import requests -rs = requests.get('http://ya.ru/') +rs = requests.get("http://ya.ru/") print(rs.status_code, rs.url) # 200 https://ya.ru/ -rs = requests.get('http://ya.ru/', allow_redirects=False) +rs = requests.get("http://ya.ru/", allow_redirects=False) print(rs.status_code, rs.url) # 302 http://ya.ru/ diff --git a/disabling_redirection/urllib_example.py b/disabling_redirection/urllib_example.py index 15e24979d..57de554d9 100644 --- a/disabling_redirection/urllib_example.py +++ b/disabling_redirection/urllib_example.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import urllib.request @@ -14,13 +14,13 @@ def http_error_301(self, req, fp, code, msg, headers): http_error_302 = http_error_303 = http_error_307 = http_error_301 -rs = urllib.request.urlopen('http://ya.ru/') +rs = urllib.request.urlopen("http://ya.ru/") print(rs.code, rs.url) # 200 https://ya.ru/ opener = urllib.request.build_opener(NoRedirectHandler()) urllib.request.install_opener(opener) -rs = urllib.request.urlopen('http://ya.ru/') +rs = urllib.request.urlopen("http://ya.ru/") print(rs.code, rs.url) # 302 http://ya.ru/ diff --git a/do_krakozabry.py b/do_krakozabry.py index 41beadf97..c4216acd0 100644 --- a/do_krakozabry.py +++ b/do_krakozabry.py @@ -1,13 +1,13 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -encoding_1 = 'utf-8' -encoding_2 = 'cp1251' +encoding_1 = "utf-8" +encoding_2 = "cp1251" -text = 'Ну и правильно, что обращаешься за помощью на стековерфлоу' +text = "Ну и правильно, что обращаешься за помощью на стековерфлоу" # РќСѓ Рё правильно, что обращаешься Р·Р° помощью РЅР° стековерфлоу print(text.encode(encoding_1).decode(encoding_2)) diff --git a/download_and_solving_algebraic_expressions.py b/download_and_solving_algebraic_expressions.py index 7992a4b7b..8b6a9a02a 100644 --- a/download_and_solving_algebraic_expressions.py +++ b/download_and_solving_algebraic_expressions.py @@ -1,19 +1,23 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # Скачивание файла import requests -rs = requests.get('https://cloclo18.datacloudmail.ru/weblink/view/emCb/gXFkchRJ2?etag=7706DA739680EAC4A5B9044E9767047365988F54&key=a91762c6f1d8a559f6d780934b2509c728b33df4') + + +rs = requests.get( + "https://cloclo18.datacloudmail.ru/weblink/view/emCb/gXFkchRJ2?etag=7706DA739680EAC4A5B9044E9767047365988F54&key=a91762c6f1d8a559f6d780934b2509c728b33df4" +) # Получение текста, разделение его построчно, пронумерование -for i, line in enumerate(rs.text.split('\n'), 1): +for i, line in enumerate(rs.text.split("\n"), 1): # Если строка пустая if not line: continue # Обрезание первого символа, удаление ' ', '\n', '\r' и т.п. line = line[1:].strip() - print('{}. {} = {}'.format(i, line, eval(line))) + print(f"{i}. {line} = {eval(line)}") diff --git a/download_file/download_file.py b/download_file/download_file.py index 901e692c7..bb3e5e297 100644 --- a/download_file/download_file.py +++ b/download_file/download_file.py @@ -1,65 +1,71 @@ -__author__ = 'ipetrash' +__author__ = "ipetrash" """Способы скачивания файлов с сайтов.""" +import time +from urllib.request import urlopen, urlretrieve + +# pip install httplib2 +import httplib2 + +# pip install requests +import requests + +# pip install grab +from grab import Grab + + def timer(f): def wrapper(*args, **kwargs): - import time t = time.time() r = f(*args, **kwargs) - print("Время выполнения функции: %f сек." % (time.time() - t)) + print(f"Время выполнения функции: {time.time() - t:f} сек.") return r + return wrapper @timer -def way1(url, file_name): - from urllib.request import urlopen +def way1(url: str, file_name: str) -> None: resource = urlopen(url) - with open(file_name, 'wb') as f: + with open(file_name, "wb") as f: f.write(resource.read()) @timer -def way2(url, file_name): - from urllib.request import urlretrieve +def way2(url: str, file_name: str) -> None: urlretrieve(url, file_name) @timer -def way3(url, file_name): - import requests - +def way3(url: str, file_name: str) -> None: p = requests.get(url) with open(file_name, "wb") as f: f.write(p.content) @timer -def way4(url, file_name): - import httplib2 - - h = httplib2.Http('.cache') +def way4(url: str, file_name: str) -> None: + h = httplib2.Http(".cache") response, content = h.request(url) - with open(file_name, 'wb') as f: + with open(file_name, "wb") as f: f.write(content) @timer -def way5(url, file_name): - from grab import Grab +def way5(url: str, file_name: str) -> None: g = Grab() g.go(url) g.response.save(file_name) -if __name__ == '__main__': - url_img = 'http://shikimori.org/images/character/original/55741.jpg' +if __name__ == "__main__": + url_img = "https://github.com/gil9red/telegram__random_bashim_bot/blob/b24139b536abf5b217b316405bbe224bc473fbfa/screenshots/screenshot_comics.jpg" way1(url_img, "img1.jpg") way2(url_img, "img2.jpg") way3(url_img, "img3.jpg") way4(url_img, "img4.jpg") - way5(url_img, "img5.jpg") \ No newline at end of file + way5(url_img, "img5.jpg") diff --git a/download_file/with_progress.py b/download_file/with_progress.py index 805c5ae9a..4eccbcc5a 100644 --- a/download_file/with_progress.py +++ b/download_file/with_progress.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import sys @@ -9,7 +9,7 @@ from threading import Thread -def reporthook(blocknum, blocksize, totalsize): +def reporthook(blocknum, blocksize, totalsize) -> None: readsofar = blocknum * blocksize if totalsize > 0: percent = readsofar * 100.0 / totalsize @@ -27,8 +27,11 @@ def reporthook(blocknum, blocksize, totalsize): sys.stdout.write(f"read {readsofar}\n") -def download(url: str, file_name: str = None, as_thread=False, callback_func=None) -> str: +def download( + url: str, file_name: str = None, as_thread=False, callback_func=None +) -> str: if as_thread: + def run(url, file_name, reporthook, callback_func): local_file_name, _ = urlretrieve(url, file_name, reporthook=reporthook) if callable(callback_func): @@ -42,17 +45,22 @@ def run(url, file_name, reporthook, callback_func): return urlretrieve(url, file_name, reporthook=reporthook)[0] -if __name__ == '__main__': - URL = 'https://codeload.github.com/gil9red/SimplePyScripts/zip/master' +if __name__ == "__main__": + URL = "https://codeload.github.com/gil9red/SimplePyScripts/zip/master" print(download(URL)) print() - print(download(URL, 'SimplePyScripts.zip')) + print(download(URL, "SimplePyScripts.zip")) + + print("\n") + sys.stderr.write("Threading...\n") - print('\n') - sys.stderr.write('Threading...\n') + print(download(URL, "SimplePyScripts.zip", as_thread=True)) - print(download(URL, 'SimplePyScripts.zip', as_thread=True)) + def callback_func(file_name: str) -> None: + print("File name:", file_name) - def callback_func(file_name: str): - print('File name:', file_name) - print(download(URL, 'SimplePyScripts.zip', as_thread=True, callback_func=callback_func)) + print( + download( + URL, "SimplePyScripts.zip", as_thread=True, callback_func=callback_func + ) + ) diff --git a/download_file_with_progressbar/download_in_file__using_tqdm.py b/download_file_with_progressbar/download_in_file__using_tqdm.py index 92c2a1d6b..9043974a3 100644 --- a/download_file_with_progressbar/download_in_file__using_tqdm.py +++ b/download_file_with_progressbar/download_in_file__using_tqdm.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import os @@ -13,26 +13,29 @@ # pip install requests import requests -from utils import sizeof_fmt +# pip install humanize +from humanize import naturalsize as sizeof_fmt -url = 'https://github.com/gil9red/NotesManager/raw/master/bin.rar' +url = "https://github.com/gil9red/NotesManager/raw/master/bin.rar" # Streaming, so we can iterate over the response. rs = requests.get(url, stream=True) # Total size in bytes. -total_size = int(rs.headers.get('content-length', 0)) -print('From content-length:', sizeof_fmt(total_size)) +total_size = int(rs.headers.get("content-length", 0)) +print("From content-length:", sizeof_fmt(total_size)) chunk_size = 1024 num_bars = int(total_size / chunk_size) file_name = os.path.basename(url) -with open(file_name, mode='wb') as f: - for data in tqdm(rs.iter_content(chunk_size), total=num_bars, unit='KB', file=sys.stdout): +with open(file_name, mode="wb") as f: + for data in tqdm( + rs.iter_content(chunk_size), total=num_bars, unit="KB", file=sys.stdout + ): f.write(data) # Read from file -file_data = open(file_name, mode='rb').read() -print('File data size:', sizeof_fmt(len(file_data))) +file_data = open(file_name, mode="rb").read() +print("File data size:", sizeof_fmt(len(file_data))) diff --git a/download_file_with_progressbar/download_in_memory__using_tqdm.py b/download_file_with_progressbar/download_in_memory__using_tqdm.py index 72f57eb58..edf6e48ea 100644 --- a/download_file_with_progressbar/download_in_memory__using_tqdm.py +++ b/download_file_with_progressbar/download_in_memory__using_tqdm.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import io @@ -13,24 +13,27 @@ # pip install requests import requests -from utils import sizeof_fmt +# pip install humanize +from humanize import naturalsize as sizeof_fmt -url = 'https://github.com/gil9red/NotesManager/raw/master/bin.rar' +url = "https://github.com/gil9red/NotesManager/raw/master/bin.rar" # Streaming, so we can iterate over the response. rs = requests.get(url, stream=True) # Total size in bytes. -total_size = int(rs.headers.get('content-length', 0)) -print('From content-length:', sizeof_fmt(total_size)) +total_size = int(rs.headers.get("content-length", 0)) +print("From content-length:", sizeof_fmt(total_size)) bytes_buffer = io.BytesIO() chunk_size = 1024 num_bars = int(total_size / chunk_size) -for data in tqdm(rs.iter_content(chunk_size), total=num_bars, unit='KB', file=sys.stdout): +for data in tqdm( + rs.iter_content(chunk_size), total=num_bars, unit="KB", file=sys.stdout +): bytes_buffer.write(data) file_data = bytes_buffer.getvalue() -print('File data size:', sizeof_fmt(len(file_data))) +print("File data size:", sizeof_fmt(len(file_data))) diff --git a/download_volume_readmanga.py b/download_volume_readmanga.py index 5bb3172ba..b29ce0054 100644 --- a/download_volume_readmanga.py +++ b/download_volume_readmanga.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """Скрипт для скачивания главы по указанному url.""" @@ -9,39 +9,45 @@ import ast import re +import zipfile import requests -PATTERN = re.compile(r'\.init\(.*(\[\[.+\]\]).*\)') + +PATTERN = re.compile(r"\.init\(.*(\[\[.+\]\]).*\)") def get_url_images(url): - print('Start get_url_images with url:', url) + print("Start get_url_images with url:", url) rs = requests.get(url) match = PATTERN.search(rs.text) if not match: - raise Exception('Не получилось из страницы вытащить список картинок главы. ' - 'Используемое регулярное выражение: ', PATTERN.pattern) + raise Exception( + "Не получилось из страницы вытащить список картинок главы. " + "Используемое регулярное выражение: ", + PATTERN.pattern, + ) match = match.group(1) - print('Match:', match) + print("Match:", match) urls = ast.literal_eval(match) - print('After parse match:', urls) + print("After parse match:", urls) return [i[0] + i[2] for i in urls] -def save_urls_to_zip(zip_file_name, urls): +def save_urls_to_zip(zip_file_name, urls) -> None: if not urls: - print('Cписок изображений пустой.') + print("Cписок изображений пустой.") return # Создаем архив, у которого именем будет номер главы - import zipfile - with zipfile.ZipFile(zip_file_name, mode='w', compression=zipfile.ZIP_DEFLATED) as f: + with zipfile.ZipFile( + zip_file_name, mode="w", compression=zipfile.ZIP_DEFLATED + ) as f: import os from urllib.request import urlretrieve @@ -62,21 +68,24 @@ def save_urls_to_zip(zip_file_name, urls): os.remove(file_name) -if __name__ == '__main__': - url = 'https://readmanga.live/one_punch_man__A1bc88e/vol1/1' +if __name__ == "__main__": + import traceback + from pathlib import Path + + url = "https://readmanga.live/one_punch_man__A1bc88e/vol1/1" try: urls = get_url_images(url) - print(f'Urls ({len(urls)}):') + print(f"Urls ({len(urls)}):") for i, x in enumerate(urls, 1): - print(f'{i}. {x}') + print(f"{i}. {x}") - from pathlib import Path - url_file_name = url.replace('http://', '').replace('https://', '').replace('/', '_') - file_name = Path(__file__).resolve().name + ' - ' + url_file_name + '.zip' + url_file_name = ( + url.replace("http://", "").replace("https://", "").replace("/", "_") + ) + file_name = Path(__file__).resolve().name + " - " + url_file_name + ".zip" save_urls_to_zip(file_name, urls) - print('Save to filename:', file_name) + print("Save to filename:", file_name) except Exception as e: - import traceback - print('Error: {}\n\n{}'.format(e, traceback.format_exc())) + print(f"Error: {e}\n\n{traceback.format_exc()}") diff --git a/downloads_database_domains/downloads_database_domains.py b/downloads_database_domains/downloads_database_domains.py index 79e753477..fad9e1222 100644 --- a/downloads_database_domains/downloads_database_domains.py +++ b/downloads_database_domains/downloads_database_domains.py @@ -1,24 +1,25 @@ -__author__ = 'ipetrash' +__author__ = "ipetrash" """Скрипт скачивает базу доменов в зоне ru и выводит ее в лог""" -if __name__ == '__main__': - from urllib.request import urlretrieve - from os.path import basename - url = 'https://partner.r01.ru/zones/ru_domains.gz' - file_name = basename(url) +import gzip - # Скачиваем архив - urlretrieve(url, file_name) +from urllib.request import urlretrieve +from os.path import basename - import gzip - # Открытие архива - with gzip.open(file_name, 'rb') as f: - c = 1 - # Построчный вывод архивированного файла - for line in f: - print("{} {}".format(c, line)) - c += 1 \ No newline at end of file +url = "https://partner.r01.ru/zones/ru_domains.gz" +file_name = basename(url) + +# Скачиваем архив +urlretrieve(url, file_name) + +# Открытие архива +with gzip.open(file_name, "rb") as f: + c = 1 + # Построчный вывод архивированного файла + for line in f: + print(f"{c} {line}") + c += 1 diff --git a/dpath__examples__search_filter__dict_json/main.py b/dpath__examples__search_filter__dict_json/main.py index fb3cacfe5..8ad00cabf 100644 --- a/dpath__examples__search_filter__dict_json/main.py +++ b/dpath__examples__search_filter__dict_json/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install dpath @@ -13,15 +13,15 @@ "3": 2, "43": 30, "c": [], - "d": ['red', 'buggy', 'bumpers'], + "d": ["red", "buggy", "bumpers"], } } } -print(dpath.util.get(x, 'a/b/d')) # ['red', 'buggy', 'bumpers'] -print(dpath.util.get(x, 'a/b/d/0')) # red -print(dpath.util.get(x, 'a/b/d/1')) # buggy -print(dpath.util.get(x, 'a/b/43')) # 30 +print(dpath.util.get(x, "a/b/d")) # ['red', 'buggy', 'bumpers'] +print(dpath.util.get(x, "a/b/d/0")) # red +print(dpath.util.get(x, "a/b/d/1")) # buggy +print(dpath.util.get(x, "a/b/43")) # 30 print() print(dpath.util.values(x, "**/43")) # [30] @@ -37,12 +37,12 @@ def afilter(x): return str(x).isdecimal() -result = dpath.util.search(x, '**', afilter=afilter) +result = dpath.util.search(x, "**", afilter=afilter) print(result) # {'a': {'b': {'3': 2, '43': 30}}} # Фильтрация через лябмды: -result = dpath.util.search(x, '**', afilter=lambda x: str(x).isdecimal()) +result = dpath.util.search(x, "**", afilter=lambda x: str(x).isdecimal()) print(result) # {'a': {'b': {'3': 2, '43': 30}}} -result = list(dpath.util.search(x, '**', yielded=True, afilter=afilter)) +result = list(dpath.util.search(x, "**", yielded=True, afilter=afilter)) print(result) # [('a/b/3', 2), ('a/b/43', 30)] diff --git a/draw fractal/Apollon_Set/Apollon_Set__PIL.py b/draw fractal/Apollon_Set/Apollon_Set__PIL.py index 2eaa0b91a..0359ebadc 100644 --- a/draw fractal/Apollon_Set/Apollon_Set__PIL.py +++ b/draw fractal/Apollon_Set/Apollon_Set__PIL.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """ @@ -63,39 +63,41 @@ # ReadKey # end. -from math import * import random +from math import * +from PIL import Image, ImageDraw -def draw_apollon_set(draw_by_image, step): + +def draw_apollon_set(draw_by_image, step) -> None: x = 0.2 y = 0.3 - + r = sqrt(3) sqr = lambda x: x * x for i in range(step): a = random.random() - a0 = 3*(1+r-x)/(sqr(1+r-x)+sqr(y))-(1+r)/(2+r) - b0 = 3*y/(sqr(1+r-x)+sqr(y)) - if 1/3 >= a >= 0: + a0 = 3 * (1 + r - x) / (sqr(1 + r - x) + sqr(y)) - (1 + r) / (2 + r) + b0 = 3 * y / (sqr(1 + r - x) + sqr(y)) + if 1 / 3 >= a >= 0: x1 = a0 y1 = b0 - a1 = -1/2 - b1 = r/2 - a2 = -1/2 - b2 = -r/2 - f1x = a0/(sqr(a0)+sqr(b0)) - f1y = -b0/(sqr(a0)+sqr(b0)) - if 2/3 >= a > 1/3: - x1 = f1x*a1-f1y*b1 - y1 = f1x*b1+f1y*a1 + a1 = -1 / 2 + b1 = r / 2 + a2 = -1 / 2 + b2 = -r / 2 + f1x = a0 / (sqr(a0) + sqr(b0)) + f1y = -b0 / (sqr(a0) + sqr(b0)) + if 2 / 3 >= a > 1 / 3: + x1 = f1x * a1 - f1y * b1 + y1 = f1x * b1 + f1y * a1 - if 3/3 >= a > 2/3: - x1 = f1x*a2-f1y*b2 - y1 = f1x*b2+f1y*a2 + if 3 / 3 >= a > 2 / 3: + x1 = f1x * a2 - f1y * b2 + y1 = f1x * b2 + f1y * a2 x = x1 y = y1 @@ -103,12 +105,11 @@ def draw_apollon_set(draw_by_image, step): draw_by_image.point((320 + x * 50, 240 + y * 50), "red") -if __name__ == '__main__': - from PIL import Image, ImageDraw +if __name__ == "__main__": img = Image.new("RGB", (650, 500), "white") # Каждый step это одна точка step = 50000 draw_apollon_set(ImageDraw.Draw(img), step) - img.save('img.png') + img.save("img.png") diff --git a/draw fractal/Cantor_dust/Cantor_dust__PIL.py b/draw fractal/Cantor_dust/Cantor_dust__PIL.py index 939861cf6..3dd63d6aa 100644 --- a/draw fractal/Cantor_dust/Cantor_dust__PIL.py +++ b/draw fractal/Cantor_dust/Cantor_dust__PIL.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """ @@ -37,22 +37,24 @@ # end. -def draw_cantor_dust(draw_by_image): - def draw(x, y, size): +from PIL import Image, ImageDraw + + +def draw_cantor_dust(draw_by_image) -> None: + def draw(x, y, size) -> None: if size > 1: s = size / 3 draw(x, y + 20, s) draw(x + s * 2, y + 20, s) - draw_by_image.rectangle((x, y, x + size, y + 5), 'black') + draw_by_image.rectangle((x, y, x + size, y + 5), "black") draw(10, 30, 500) -if __name__ == '__main__': - from PIL import Image, ImageDraw +if __name__ == "__main__": img = Image.new("RGB", (520, 160), "white") draw_cantor_dust(ImageDraw.Draw(img)) - img.save('img.png') + img.save("img.png") diff --git a/draw fractal/Circular_fractal/Circular_fractal__PIL.py b/draw fractal/Circular_fractal/Circular_fractal__PIL.py index 87e1acbb7..28735840b 100644 --- a/draw fractal/Circular_fractal/Circular_fractal__PIL.py +++ b/draw fractal/Circular_fractal/Circular_fractal__PIL.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """ @@ -36,9 +36,10 @@ from math import * +from PIL import Image, ImageDraw -def draw_circular_fractal(draw_by_image, x, y, size): +def draw_circular_fractal(draw_by_image, x, y, size) -> None: m = 6 n = 3 @@ -51,19 +52,18 @@ def draw_circular_fractal(draw_by_image, x, y, size): draw_by_image, x - s2 * sin(2 * pi / m * i), y + s2 * cos(2 * pi / m * i), - s1 + s1, ) draw_circular_fractal(draw_by_image, x, y, s1) bbox = (x - size, y - size, x + size, y + size) - draw_by_image.ellipse(bbox, outline='black') + draw_by_image.ellipse(bbox, outline="black") -if __name__ == '__main__': - from PIL import Image, ImageDraw +if __name__ == "__main__": img = Image.new("RGB", (320 * 2, 240 * 2), "white") draw_circular_fractal(ImageDraw.Draw(img), 320, 240, 200) - img.save('img.png') + img.save("img.png") diff --git a/draw fractal/Dragon_curve_1/Dragon_curve_1__PIL.py b/draw fractal/Dragon_curve_1/Dragon_curve_1__PIL.py index c093eb58f..dad5d9eb2 100644 --- a/draw fractal/Dragon_curve_1/Dragon_curve_1__PIL.py +++ b/draw fractal/Dragon_curve_1/Dragon_curve_1__PIL.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """ @@ -37,7 +37,10 @@ # End. -def draw_dragon_curve_1(draw_by_image, x1, y1, x2, y2, k): +from PIL import Image, ImageDraw + + +def draw_dragon_curve_1(draw_by_image, x1, y1, x2, y2, k) -> None: if k > 0: xn = (x1 + x2) // 2 + (y2 - y1) // 2 yn = (y1 + y2) // 2 - (x2 - x1) // 2 @@ -45,15 +48,14 @@ def draw_dragon_curve_1(draw_by_image, x1, y1, x2, y2, k): draw_dragon_curve_1(draw_by_image, x2, y2, xn, yn, k - 1) else: - draw_by_image.line((x1, y1, x2, y2), 'black') + draw_by_image.line((x1, y1, x2, y2), "black") -if __name__ == '__main__': - from PIL import Image, ImageDraw +if __name__ == "__main__": img = Image.new("RGB", (700, 512), "white") # Глубина фрактала z = 14 draw_dragon_curve_1(ImageDraw.Draw(img), 200, 300, 500, 300, z) - img.save('img.png') + img.save("img.png") diff --git a/draw fractal/Fern/Fern__PIL.py b/draw fractal/Fern/Fern__PIL.py index c6540ea94..5353cc35b 100644 --- a/draw fractal/Fern/Fern__PIL.py +++ b/draw fractal/Fern/Fern__PIL.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """ @@ -46,7 +46,10 @@ # end. -def draw_fern(draw_by_image, width, height): +from PIL import Image, ImageDraw + + +def draw_fern(draw_by_image, width, height) -> None: n = 255 cx = 0.251 @@ -70,10 +73,9 @@ def draw_fern(draw_by_image, width, height): draw_by_image.point((ix, iy), color) -if __name__ == '__main__': - from PIL import Image, ImageDraw +if __name__ == "__main__": img = Image.new("RGB", (300, 300), "white") draw_fern(ImageDraw.Draw(img), img.width, img.height) - img.save('img.png') + img.save("img.png") diff --git a/draw fractal/Fern_2/Fern_2__PIL.py b/draw fractal/Fern_2/Fern_2__PIL.py index e8b7c59a5..9662a6311 100644 --- a/draw fractal/Fern_2/Fern_2__PIL.py +++ b/draw fractal/Fern_2/Fern_2__PIL.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """ @@ -43,13 +43,14 @@ from math import * +from PIL import Image, ImageDraw -def draw_fern_2(draw_by_image): - def line_to(x, y, l, u): - draw_by_image.line((x, y, x + l * cos(u), y - l * sin(u)), 'black') +def draw_fern_2(draw_by_image) -> None: + def line_to(x, y, l, u) -> None: + draw_by_image.line((x, y, x + l * cos(u), y - l * sin(u)), "black") - def draw(x, y, l, u): + def draw(x, y, l, u) -> None: if l > 1: line_to(x, y, l, u) @@ -63,10 +64,9 @@ def draw(x, y, l, u): draw(320, 460, 140, pi / 2) -if __name__ == '__main__': - from PIL import Image, ImageDraw +if __name__ == "__main__": img = Image.new("RGB", (730, 500), "white") draw_fern_2(ImageDraw.Draw(img)) - img.save('img.png') + img.save("img.png") diff --git a/draw fractal/Fingerprint/Fingerprint__PIL.py b/draw fractal/Fingerprint/Fingerprint__PIL.py index 08f1960ac..ad6415f4f 100644 --- a/draw fractal/Fingerprint/Fingerprint__PIL.py +++ b/draw fractal/Fingerprint/Fingerprint__PIL.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """ @@ -12,11 +12,11 @@ # Оригинал: http://www.cyberforum.ru/pascalabc/thread994987.html # uses GraphABC; -# +# # const # n=255; # max=10; -# +# # var # x,y,x1,y1,cx,cy: real; # i,ix,iy: integer; @@ -45,7 +45,10 @@ # end. -def draw_fingerprint(draw_by_image, width, height): +from PIL import Image, ImageDraw + + +def draw_fingerprint(draw_by_image, width, height) -> None: n = 255 max = 10 @@ -68,17 +71,16 @@ def draw_fingerprint(draw_by_image, width, height): y = y1 if i >= n: - color = 'red' + color = "red" else: color = (255, 255 - i, 255 - i) draw_by_image.point((ix, iy), color) -if __name__ == '__main__': - from PIL import Image, ImageDraw +if __name__ == "__main__": img = Image.new("RGB", (400, 300), "white") draw_fingerprint(ImageDraw.Draw(img), img.width, img.height) - img.save('img.png') + img.save("img.png") diff --git a/draw fractal/Fractal_tree/Fractal_tree__PIL.py b/draw fractal/Fractal_tree/Fractal_tree__PIL.py index d90b67bcb..97316863e 100644 --- a/draw fractal/Fractal_tree/Fractal_tree__PIL.py +++ b/draw fractal/Fractal_tree/Fractal_tree__PIL.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """ @@ -58,27 +58,29 @@ # End. -from math import * import random +from math import * + +from PIL import Image, ImageDraw -def draw_fractal_tree(draw_by_image, x, y, a, l): +def draw_fractal_tree(draw_by_image, x, y, a, l) -> None: if l < 8: return - x1 = x + l*cos(a) - y1 = y + l*sin(a) + x1 = x + l * cos(a) + y1 = y + l * sin(a) p = 100 if l > 100 else l if p < 40: # Генерация листьев - color = 'lime' if random.random() > 0.5 else 'green' + color = "lime" if random.random() > 0.5 else "green" for i in range(3): draw_by_image.line((x + i, y, x1, y1), color) else: # Генерация веток - color = 'brown' + color = "brown" for i in range(p // 6): draw_by_image.line((x + i - (p / 12), y, x1, y1), color) @@ -96,10 +98,9 @@ def draw_fractal_tree(draw_by_image, x, y, a, l): draw_fractal_tree(draw_by_image, x1, y1, a1, p - 5 - random.randrange(30)) -if __name__ == '__main__': - from PIL import Image, ImageDraw +if __name__ == "__main__": img = Image.new("RGB", (700, 600), "white") draw_fractal_tree(ImageDraw.Draw(img), 350, 580, 3 * pi / 2, 200) - img.save('img.png') + img.save("img.png") diff --git a/draw fractal/Fractal_tree/Fractal_tree__Qt_gui.py b/draw fractal/Fractal_tree/Fractal_tree__Qt_gui.py index 4810af984..7495fbfde 100644 --- a/draw fractal/Fractal_tree/Fractal_tree__Qt_gui.py +++ b/draw fractal/Fractal_tree/Fractal_tree__Qt_gui.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """ @@ -9,33 +9,65 @@ """ +import math +import io + +from PIL import Image, ImageDraw + try: - from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QPushButton, QVBoxLayout, QSizePolicy + from PyQt5.QtWidgets import ( + QApplication, + QWidget, + QLabel, + QPushButton, + QVBoxLayout, + QSizePolicy, + ) from PyQt5.QtGui import QImage, QPixmap, QPainter from PyQt5.QtCore import Qt except ImportError: try: - from PyQt4.QtGui import (QImage, QPainter, QApplication, QWidget, - QLabel, QPushButton, QVBoxLayout, QSizePolicy, QPixmap) + from PyQt4.QtGui import ( + QImage, + QPainter, + QApplication, + QWidget, + QLabel, + QPushButton, + QVBoxLayout, + QSizePolicy, + QPixmap, + ) from PyQt4.QtCore import Qt except ImportError: - from PySide.QtGui import (QImage, QPainter, QApplication, QWidget, - QLabel, QPushButton, QVBoxLayout, QSizePolicy, QPixmap) + from PySide.QtGui import ( + QImage, + QPainter, + QApplication, + QWidget, + QLabel, + QPushButton, + QVBoxLayout, + QSizePolicy, + QPixmap, + ) from PySide.QtCore import Qt +from Fractal_tree__PIL import draw_fractal_tree + class Widget(QWidget): - def __init__(self): + def __init__(self) -> None: super().__init__() - self.setWindowTitle('Fractal tree') + self.setWindowTitle("Fractal tree") self.img_label = QLabel() self.img_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) - self.generate_tree_button = QPushButton('Generate Tree') + self.generate_tree_button = QPushButton("Generate Tree") self.generate_tree_button.clicked.connect(self.generate_tree) main_layout = QVBoxLayout() @@ -44,18 +76,13 @@ def __init__(self): self.setLayout(main_layout) - def generate_tree(self): - import math - - from PIL import Image, ImageDraw + def generate_tree(self) -> None: img = Image.new("RGB", (700, 600), "white") - from Fractal_tree__PIL import draw_fractal_tree draw_fractal_tree(ImageDraw.Draw(img), 350, 580, 3 * math.pi / 2, 200) - import io img_bytes_io = io.BytesIO() - img.save(img_bytes_io, format='PNG') + img.save(img_bytes_io, format="PNG") img_bytes = img_bytes_io.getvalue() img = QPixmap() @@ -64,7 +91,7 @@ def generate_tree(self): self.img_label.setPixmap(img) -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) w = Widget() diff --git a/draw fractal/Gosper_curve/Gosper_curve__PIL.py b/draw fractal/Gosper_curve/Gosper_curve__PIL.py index caf52bf31..dd23d5b81 100644 --- a/draw fractal/Gosper_curve/Gosper_curve__PIL.py +++ b/draw fractal/Gosper_curve/Gosper_curve__PIL.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """ @@ -58,29 +58,32 @@ import math +from PIL import Image, ImageDraw -def draw_gosper_curve(draw_by_image, step): - def draw(x, y, l, u, t, q): +def draw_gosper_curve(draw_by_image, step) -> None: + def draw(x, y, l, u, t, q) -> None: if t > 0: if q == 1: x += l * math.cos(u) y -= l * math.sin(u) u += math.pi - - u -= 2*math.pi/19 + + u -= 2 * math.pi / 19 l /= math.sqrt(7) - x, y = draw2(x, y, l, u, t-1, 0) - x, y = draw2(x, y, l, u+math.pi/3, t-1, 1) - x, y = draw2(x, y, l, u+math.pi, t-1, 1) - x, y = draw2(x, y, l, u+2*math.pi/3, t-1, 0) - x, y = draw2(x, y, l, u, t-1, 0) - x, y = draw2(x, y, l, u, t-1, 0) - _, _ = draw2(x, y, l, u-math.pi/3, t-1, 1) + x, y = draw2(x, y, l, u, t - 1, 0) + x, y = draw2(x, y, l, u + math.pi / 3, t - 1, 1) + x, y = draw2(x, y, l, u + math.pi, t - 1, 1) + x, y = draw2(x, y, l, u + 2 * math.pi / 3, t - 1, 0) + x, y = draw2(x, y, l, u, t - 1, 0) + x, y = draw2(x, y, l, u, t - 1, 0) + _, _ = draw2(x, y, l, u - math.pi / 3, t - 1, 1) else: - draw_by_image.line((x, y, x + math.cos(u) * l, y - math.sin(u) * l), fill="black") + draw_by_image.line( + (x, y, x + math.cos(u) * l, y - math.sin(u) * l), fill="black" + ) def draw2(x, y, l, u, t, q): draw(x, y, l, u, t, q) @@ -89,12 +92,11 @@ def draw2(x, y, l, u, t, q): draw(100, 355, 400, 0, step, 0) -if __name__ == '__main__': - from PIL import Image, ImageDraw +if __name__ == "__main__": img = Image.new("RGB", (650, 500), "white") draw_by_image = ImageDraw.Draw(img) step = 4 draw_gosper_curve(draw_by_image, step) - img.save('img.png') + img.save("img.png") diff --git a/draw fractal/Ice_fractal_1/Circular_fractal_1__PIL.py b/draw fractal/Ice_fractal_1/Circular_fractal_1__PIL.py index 0df00cc7b..a4628ee1c 100644 --- a/draw fractal/Ice_fractal_1/Circular_fractal_1__PIL.py +++ b/draw fractal/Ice_fractal_1/Circular_fractal_1__PIL.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """ @@ -46,35 +46,35 @@ from math import * +from PIL import Image, ImageDraw -def draw_ice_fractal_1(draw_by_image, step): +def draw_ice_fractal_1(draw_by_image, step) -> None: def draw2(x, y, l, u, t): draw(x, y, l, u, t) return x + l * cos(u), y - l * sin(u) - def draw(x, y, l, u, t): + def draw(x, y, l, u, t) -> None: if t > 0: l *= 0.5 - x, y = draw2(x, y, l, u, t-1) - x, y = draw2(x, y, l*0.8, u+pi/2, t-1) - x, y = draw2(x, y, l*0.8, u-pi/2, t-1) - _, _ = draw2(x, y, l, u, t-1) + x, y = draw2(x, y, l, u, t - 1) + x, y = draw2(x, y, l * 0.8, u + pi / 2, t - 1) + x, y = draw2(x, y, l * 0.8, u - pi / 2, t - 1) + _, _ = draw2(x, y, l, u, t - 1) else: - draw_by_image.line((x, y, x+cos(u)*l, y-sin(u)*l), 'black') + draw_by_image.line((x, y, x + cos(u) * l, y - sin(u) * l), "black") draw(410, 10, 400, -pi, step) draw(10, 410, 400, 0, step) - draw(10, 10, 400, -pi/2, step) - draw(410, 410, 400, pi/2, step) + draw(10, 10, 400, -pi / 2, step) + draw(410, 410, 400, pi / 2, step) -if __name__ == '__main__': - from PIL import Image, ImageDraw +if __name__ == "__main__": img = Image.new("RGB", (420, 420), "white") step = 5 draw_ice_fractal_1(ImageDraw.Draw(img), step) - img.save('img.png') + img.save("img.png") diff --git a/draw fractal/Ice_fractal_2/Circular_fractal_2__PIL.py b/draw fractal/Ice_fractal_2/Circular_fractal_2__PIL.py index 7aa5cbc27..38e5d0d56 100644 --- a/draw fractal/Ice_fractal_2/Circular_fractal_2__PIL.py +++ b/draw fractal/Ice_fractal_2/Circular_fractal_2__PIL.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """ @@ -48,36 +48,36 @@ from math import * +from PIL import Image, ImageDraw -def draw_ice_fractal_2(draw_by_image, step): +def draw_ice_fractal_2(draw_by_image, step) -> None: def draw2(x, y, l, u, t): draw(x, y, l, u, t) return x + l * cos(u), y - l * sin(u) - def draw(x, y, l, u, t): + def draw(x, y, l, u, t) -> None: if t > 0: l *= 0.5 - x, y = draw2(x, y, l, u, t-1) - x, y = draw2(x, y, l*0.45, u+2*pi/3, t-1) - x, y = draw2(x, y, l*0.45, u-pi/3, t-1) - x, y = draw2(x, y, l*0.45, u+pi/3, t-1) - x, y = draw2(x, y, l*0.45, u-2*pi/3, t-1) - _, _ = draw2(x, y, l, u, t-1) + x, y = draw2(x, y, l, u, t - 1) + x, y = draw2(x, y, l * 0.45, u + 2 * pi / 3, t - 1) + x, y = draw2(x, y, l * 0.45, u - pi / 3, t - 1) + x, y = draw2(x, y, l * 0.45, u + pi / 3, t - 1) + x, y = draw2(x, y, l * 0.45, u - 2 * pi / 3, t - 1) + _, _ = draw2(x, y, l, u, t - 1) else: - draw_by_image.line((x, y, x+cos(u)*l, y-sin(u)*l), 'black') + draw_by_image.line((x, y, x + cos(u) * l, y - sin(u) * l), "black") - draw(210, 8, 400, -2*pi/3, step) + draw(210, 8, 400, -2 * pi / 3, step) draw(10, 354, 400, 0, step) - draw(410, 354, 400, 2*pi/3, step) + draw(410, 354, 400, 2 * pi / 3, step) -if __name__ == '__main__': - from PIL import Image, ImageDraw +if __name__ == "__main__": img = Image.new("RGB", (420, 420), "white") step = 6 draw_ice_fractal_2(ImageDraw.Draw(img), step) - img.save('img.png') + img.save("img.png") diff --git a/draw fractal/Koch_curve/Koch_curve__PIL.py b/draw fractal/Koch_curve/Koch_curve__PIL.py index 267672660..db728139f 100644 --- a/draw fractal/Koch_curve/Koch_curve__PIL.py +++ b/draw fractal/Koch_curve/Koch_curve__PIL.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """ @@ -57,7 +57,10 @@ # ?> -def draw_koch(draw, xa, ya, xe, ye, n): +from PIL import Image, ImageDraw + + +def draw_koch(draw, xa, ya, xe, ye, n) -> None: """ Draws koch curve between two points. @@ -70,11 +73,11 @@ def draw_koch(draw, xa, ya, xe, ye, n): # / \ # A---B D---E - xb = xa + (xe - xa) * 1/3 - yb = ya + (ye - ya) * 1/3 + xb = xa + (xe - xa) * 1 / 3 + yb = ya + (ye - ya) * 1 / 3 - xd = xa + (xe - xa) * 2/3 - yd = ya + (ye - ya) * 2/3 + xd = xa + (xe - xa) * 2 / 3 + yd = ya + (ye - ya) * 2 / 3 cos60 = 0.5 sin60 = -0.866 @@ -87,11 +90,10 @@ def draw_koch(draw, xa, ya, xe, ye, n): draw_koch(draw, xd, yd, xe, ye, n - 1) -if __name__ == '__main__': - from PIL import Image, ImageDraw +if __name__ == "__main__": img = Image.new("RGB", (600, 200), "white") step = 4 draw_koch(ImageDraw.Draw(img), 0, img.height - 1, img.width, img.height - 1, step) - img.save('img.png') + img.save("img.png") diff --git a/draw fractal/Koch_curve/Koch_curve__Qt.py b/draw fractal/Koch_curve/Koch_curve__Qt.py index 0eafa962f..90aa52c09 100644 --- a/draw fractal/Koch_curve/Koch_curve__Qt.py +++ b/draw fractal/Koch_curve/Koch_curve__Qt.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """ @@ -57,7 +57,7 @@ # ?> -def draw_koch(painter, xa, ya, xe, ye, n): +def draw_koch(painter, xa, ya, xe, ye, n) -> None: """ Draws koch curve between two points. @@ -70,11 +70,11 @@ def draw_koch(painter, xa, ya, xe, ye, n): # / \ # A---B D---E - xb = xa + (xe - xa) * 1/3 - yb = ya + (ye - ya) * 1/3 + xb = xa + (xe - xa) * 1 / 3 + yb = ya + (ye - ya) * 1 / 3 - xd = xa + (xe - xa) * 2/3 - yd = ya + (ye - ya) * 2/3 + xd = xa + (xe - xa) * 2 / 3 + yd = ya + (ye - ya) * 2 / 3 cos60 = 0.5 sin60 = -0.866 @@ -101,7 +101,7 @@ def draw_koch(painter, xa, ya, xe, ye, n): from PySide.QtCore import Qt -if __name__ == '__main__': +if __name__ == "__main__": img = QImage(600, 200, QImage.Format_RGB16) img.fill(Qt.white) @@ -111,4 +111,4 @@ def draw_koch(painter, xa, ya, xe, ye, n): painter.setPen(Qt.black) draw_koch(painter, 0, img.height() - 1, img.width(), img.height() - 1, step) - img.save('img.png') + img.save("img.png") diff --git a/draw fractal/Koch_curve/Koch_curve__Qt_gui.py b/draw fractal/Koch_curve/Koch_curve__Qt_gui.py index 17b98c7cc..0996c3e50 100644 --- a/draw fractal/Koch_curve/Koch_curve__Qt_gui.py +++ b/draw fractal/Koch_curve/Koch_curve__Qt_gui.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """ @@ -57,7 +57,7 @@ # ?> -def draw_koch(painter, xa, ya, xe, ye, n): +def draw_koch(painter, xa, ya, xe, ye, n) -> None: """ Draws koch curve between two points. @@ -70,11 +70,11 @@ def draw_koch(painter, xa, ya, xe, ye, n): # / \ # A---B D---E - xb = xa + (xe - xa) * 1/3 - yb = ya + (ye - ya) * 1/3 + xb = xa + (xe - xa) * 1 / 3 + yb = ya + (ye - ya) * 1 / 3 - xd = xa + (xe - xa) * 2/3 - yd = ya + (ye - ya) * 2/3 + xd = xa + (xe - xa) * 2 / 3 + yd = ya + (ye - ya) * 2 / 3 cos60 = 0.5 sin60 = -0.866 @@ -86,28 +86,54 @@ def draw_koch(painter, xa, ya, xe, ye, n): draw_koch(painter, xc, yc, xd, yd, n - 1) draw_koch(painter, xd, yd, xe, ye, n - 1) + try: - from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QSpinBox, QVBoxLayout, QSizePolicy + from PyQt5.QtWidgets import ( + QApplication, + QWidget, + QLabel, + QSpinBox, + QVBoxLayout, + QSizePolicy, + ) from PyQt5.QtGui import QImage, QPixmap, QPainter from PyQt5.QtCore import Qt except ImportError: try: - from PyQt4.QtGui import (QImage, QPainter, QApplication, QWidget, - QLabel, QSpinBox, QVBoxLayout, QSizePolicy, QPixmap) + from PyQt4.QtGui import ( + QImage, + QPainter, + QApplication, + QWidget, + QLabel, + QSpinBox, + QVBoxLayout, + QSizePolicy, + QPixmap, + ) from PyQt4.QtCore import Qt except ImportError: - from PySide.QtGui import (QImage, QPainter, QApplication, QWidget, - QLabel, QSpinBox, QVBoxLayout, QSizePolicy, QPixmap) + from PySide.QtGui import ( + QImage, + QPainter, + QApplication, + QWidget, + QLabel, + QSpinBox, + QVBoxLayout, + QSizePolicy, + QPixmap, + ) from PySide.QtCore import Qt class Widget(QWidget): - def __init__(self): + def __init__(self) -> None: super().__init__() - self.setWindowTitle('Koch_curve snowflake') + self.setWindowTitle("Koch_curve snowflake") self.img_label = QLabel() self.img_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) @@ -118,7 +144,7 @@ def __init__(self): self.step_spinbox.valueChanged.connect(self.draw_by_step) commands_layout = QVBoxLayout() - commands_layout.addWidget(QLabel('Step:')) + commands_layout.addWidget(QLabel("Step:")) commands_layout.addWidget(self.step_spinbox) main_layout = QVBoxLayout() @@ -129,7 +155,7 @@ def __init__(self): self.draw_by_step(self.step_spinbox.value()) - def draw_by_step(self, step): + def draw_by_step(self, step) -> None: img = QImage(600, 200, QImage.Format_RGB16) img.fill(Qt.white) @@ -143,7 +169,7 @@ def draw_by_step(self, step): painter.end() -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) w = Widget() diff --git a/draw fractal/Koch_snowflake/Koch_snowflake__PIL.py b/draw fractal/Koch_snowflake/Koch_snowflake__PIL.py index dc3153225..8a97d9a14 100644 --- a/draw fractal/Koch_snowflake/Koch_snowflake__PIL.py +++ b/draw fractal/Koch_snowflake/Koch_snowflake__PIL.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """ @@ -47,17 +47,20 @@ import math +from PIL import Image, ImageDraw -def draw_snowflake_koch(draw_by_image, step): +def draw_snowflake_koch(draw_by_image, step) -> None: """ Draws koch snowflake. """ - def draw(x, y, l, u, t): + def draw(x, y, l, u, t) -> None: if t == 0: - draw_by_image.line((x, y, x + math.cos(u) * l, y - math.sin(u) * l), fill="black") + draw_by_image.line( + (x, y, x + math.cos(u) * l, y - math.sin(u) * l), fill="black" + ) else: l /= 3 x, y = draw2(x, y, l, u, t - 1) @@ -74,12 +77,11 @@ def draw2(x, y, l, u, t): draw(210, 8, 400, -math.pi / 3, step) -if __name__ == '__main__': - from PIL import Image, ImageDraw +if __name__ == "__main__": img = Image.new("RGB", (425, 500), "white") draw_by_image = ImageDraw.Draw(img) step = 4 draw_snowflake_koch(draw_by_image, step) - img.save('img.png') + img.save("img.png") diff --git a/draw fractal/Levy_curve/Levy_curve__PIL.py b/draw fractal/Levy_curve/Levy_curve__PIL.py index 5484a06d5..50cb3752d 100644 --- a/draw fractal/Levy_curve/Levy_curve__PIL.py +++ b/draw fractal/Levy_curve/Levy_curve__PIL.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """ @@ -53,9 +53,10 @@ import random +from PIL import Image, ImageDraw -def draw_levy(draw): +def draw_levy(draw) -> None: iter = 50000 mx = 200 @@ -68,22 +69,21 @@ def draw_levy(draw): for k in range(iter): p = random.random() t = x - if p <= 1/2: - x = 0.5*x - 0.5*y - y = 0.5*t + 0.5*y + if p <= 1 / 2: + x = 0.5 * x - 0.5 * y + y = 0.5 * t + 0.5 * y else: - x = 0.5*x + 0.5*y + 0.5; - y = -0.5*t + 0.5*y + 0.5; + x = 0.5 * x + 0.5 * y + 0.5 + y = -0.5 * t + 0.5 * y + 0.5 draw.point((mx + rad * x, my - rad * y), "blue") -if __name__ == '__main__': - from PIL import Image, ImageDraw +if __name__ == "__main__": img = Image.new("RGB", (650, 450), "white") step = 10 draw_levy(ImageDraw.Draw(img)) - img.save('img.png') + img.save("img.png") diff --git a/draw fractal/Mandelbrot_Set_1/Mandelbrot_Set_1__PIL.py b/draw fractal/Mandelbrot_Set_1/Mandelbrot_Set_1__PIL.py index 09da2d747..5c6a3ed5b 100644 --- a/draw fractal/Mandelbrot_Set_1/Mandelbrot_Set_1__PIL.py +++ b/draw fractal/Mandelbrot_Set_1/Mandelbrot_Set_1__PIL.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """ @@ -45,24 +45,27 @@ # end. -def draw_mandelbrot_set_1(draw_by_image, width, height): +from PIL import Image, ImageDraw + + +def draw_mandelbrot_set_1(draw_by_image, width, height) -> None: n = 255 max = 10 - + for ix in range(width): for iy in range(height): x = 0 y = 0 cx = 0.002 * (ix - 720) cy = 0.002 * (iy - 150) - + for i in range(n): x1 = x * x - y * y + cx y1 = 2 * x * y + cy - + if x1 > max or y1 > max: break - + x = x1 y = y1 @@ -74,10 +77,9 @@ def draw_mandelbrot_set_1(draw_by_image, width, height): draw_by_image.point((ix, iy), color) -if __name__ == '__main__': - from PIL import Image, ImageDraw +if __name__ == "__main__": img = Image.new("RGB", (400, 300), "white") draw_mandelbrot_set_1(ImageDraw.Draw(img), img.width, img.height) - img.save('img.png') + img.save("img.png") diff --git a/draw fractal/Mandelbrot_Set_2/Mandelbrot_Set_2__PIL.py b/draw fractal/Mandelbrot_Set_2/Mandelbrot_Set_2__PIL.py index 285d37654..d03467eb2 100644 --- a/draw fractal/Mandelbrot_Set_2/Mandelbrot_Set_2__PIL.py +++ b/draw fractal/Mandelbrot_Set_2/Mandelbrot_Set_2__PIL.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """ @@ -45,24 +45,27 @@ # end. -def draw_mandelbrot_set_2(draw_by_image, width, height): +from PIL import Image, ImageDraw + + +def draw_mandelbrot_set_2(draw_by_image, width, height) -> None: n = 255 max = 10 - + for ix in range(width): for iy in range(height): x = 0 y = 0 cx = 0.005 * (ix - 365) cy = 0.005 * (iy - 300) - + for i in range(n): x1 = x * x - y * y + cx y1 = 2 * x * y + cy - + if x1 > max or y1 > max: break - + x = x1 y = y1 @@ -74,10 +77,9 @@ def draw_mandelbrot_set_2(draw_by_image, width, height): draw_by_image.point((ix, iy), color) -if __name__ == '__main__': - from PIL import Image, ImageDraw +if __name__ == "__main__": img = Image.new("RGB", (600, 600), "white") draw_mandelbrot_set_2(ImageDraw.Draw(img), img.width, img.height) - img.save('img.png') + img.save("img.png") diff --git a/draw fractal/Minkowski_curve/Minkowski_curve__PIL.py b/draw fractal/Minkowski_curve/Minkowski_curve__PIL.py index fd7e89990..f8f631a4e 100644 --- a/draw fractal/Minkowski_curve/Minkowski_curve__PIL.py +++ b/draw fractal/Minkowski_curve/Minkowski_curve__PIL.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """ @@ -76,7 +76,10 @@ # ?> -def draw_minkowski(draw, xa, ya, xi, yi, n): +from PIL import Image, ImageDraw + + +def draw_minkowski(draw, xa, ya, xi, yi, n) -> None: """ Draws minkowski curve between two points. @@ -91,14 +94,14 @@ def draw_minkowski(draw, xa, ya, xi, yi, n): # | | # F---G - xb = xa + (xi - xa) * 1/4 - yb = ya + (yi - ya) * 1/4 + xb = xa + (xi - xa) * 1 / 4 + yb = ya + (yi - ya) * 1 / 4 - xe = xa + (xi - xa) * 2/4 - ye = ya + (yi - ya) * 2/4 + xe = xa + (xi - xa) * 2 / 4 + ye = ya + (yi - ya) * 2 / 4 - xh = xa + (xi - xa) * 3/4 - yh = ya + (yi - ya) * 3/4 + xh = xa + (xi - xa) * 3 / 4 + yh = ya + (yi - ya) * 3 / 4 cos90 = 0 sin90 = -1 @@ -125,11 +128,12 @@ def draw_minkowski(draw, xa, ya, xi, yi, n): draw_minkowski(draw, xh, yh, xi, yi, n - 1) -if __name__ == '__main__': - from PIL import Image, ImageDraw +if __name__ == "__main__": img = Image.new("RGB", (600, 400), "white") step = 3 - draw_minkowski(ImageDraw.Draw(img), 0, img.height / 2, img.width, img.height / 2, step) + draw_minkowski( + ImageDraw.Draw(img), 0, img.height / 2, img.width, img.height / 2, step + ) - img.save('img.png') + img.save("img.png") diff --git a/draw fractal/Monkey_tree/Monkey_tree__PIL.py b/draw fractal/Monkey_tree/Monkey_tree__PIL.py index 3e9ce4b9f..e1d71f847 100644 --- a/draw fractal/Monkey_tree/Monkey_tree__PIL.py +++ b/draw fractal/Monkey_tree/Monkey_tree__PIL.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """ @@ -74,56 +74,56 @@ from math import * +from PIL import Image, ImageDraw -def draw_monkey_tree(draw_by_image): - def Draw2(x, y, l, u, t, q, s): - Draw(x, y, l, u, t, q, s) - return x + l*cos(u), y - l*sin(u) - - def Draw(x, y, l, u, t, q,s): +def draw_monkey_tree(draw_by_image) -> None: + def draw2(x, y, l, u, t, q, s): + draw(x, y, l, u, t, q, s) + return x + l * cos(u), y - l * sin(u) + + def draw(x, y, l, u, t, q, s) -> None: if t > 0: if q == 1: - x += l*cos(u) - y -= l*sin(u) + x += l * cos(u) + y -= l * sin(u) s = -s u = u + pi elif q == 3: - x += l*cos(u) - y -= l*sin(u) + x += l * cos(u) + y -= l * sin(u) s = s u = u + pi elif q == 2: s = -s - + elif q == 0: s = s l /= 3 - x, y = Draw2(x, y, l, u+s*pi/3, t-1, 2, s) - x, y = Draw2(x, y, l, u+s*pi/3, t-1, 1, s) - x, y = Draw2(x, y, l, u, t-1, 0, s) - x, y = Draw2(x, y, l, u-s*pi/3, t-1, 1, s) - x, y = Draw2(x, y, l*sqrt(3)/3, u-s*7*pi/6, t-1, 1, s) - x, y = Draw2(x, y, l*sqrt(3)/3, u-s*7*pi/6, t-1, 2, s) - x, y = Draw2(x, y, l*sqrt(3)/3, u-s*5*pi/6, t-1, 3, s) - x, y = Draw2(x, y, l*sqrt(3)/3, u-s*pi/2, t-1, 3, s) - x, y = Draw2(x, y, l*sqrt(3)/3, u-s*pi/2, t-1, 0, s) - x, y = Draw2(x, y, l, u, t-1, 3, s) - _, _ = Draw2(x, y, l, u, t-1, 0, s) + x, y = draw2(x, y, l, u + s * pi / 3, t - 1, 2, s) + x, y = draw2(x, y, l, u + s * pi / 3, t - 1, 1, s) + x, y = draw2(x, y, l, u, t - 1, 0, s) + x, y = draw2(x, y, l, u - s * pi / 3, t - 1, 1, s) + x, y = draw2(x, y, l * sqrt(3) / 3, u - s * 7 * pi / 6, t - 1, 1, s) + x, y = draw2(x, y, l * sqrt(3) / 3, u - s * 7 * pi / 6, t - 1, 2, s) + x, y = draw2(x, y, l * sqrt(3) / 3, u - s * 5 * pi / 6, t - 1, 3, s) + x, y = draw2(x, y, l * sqrt(3) / 3, u - s * pi / 2, t - 1, 3, s) + x, y = draw2(x, y, l * sqrt(3) / 3, u - s * pi / 2, t - 1, 0, s) + x, y = draw2(x, y, l, u, t - 1, 3, s) + _, _ = draw2(x, y, l, u, t - 1, 0, s) else: - draw_by_image.line((x, y, x + cos(u)*l, y - sin(u)*l), 'black') + draw_by_image.line((x, y, x + cos(u) * l, y - sin(u) * l), "black") - Draw(50, 365, 430, 0, 3, 0, 1) + draw(50, 365, 430, 0, 3, 0, 1) -if __name__ == '__main__': - from PIL import Image, ImageDraw +if __name__ == "__main__": img = Image.new("RGB", (520, 500), "white") draw_monkey_tree(ImageDraw.Draw(img)) - img.save('img.png') + img.save("img.png") diff --git a/draw fractal/Pythagoras_tree_2/Pythagoras_tree_2__PIL.py b/draw fractal/Pythagoras_tree_2/Pythagoras_tree_2__PIL.py index 547753bb9..05c44e2a6 100644 --- a/draw fractal/Pythagoras_tree_2/Pythagoras_tree_2__PIL.py +++ b/draw fractal/Pythagoras_tree_2/Pythagoras_tree_2__PIL.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """ @@ -42,13 +42,14 @@ from math import * +from PIL import Image, ImageDraw -def draw_pythagoras_tree_2(draw_by_image): - def line_to(x, y, l, u): - draw_by_image.line((x, y, x + l * cos(u), y - l * sin(u)), 'black') +def draw_pythagoras_tree_2(draw_by_image) -> None: + def line_to(x, y, l, u) -> None: + draw_by_image.line((x, y, x + l * cos(u), y - l * sin(u)), "black") - def draw(x, y, l, u): + def draw(x, y, l, u) -> None: if l > 3: l *= 0.7 line_to(x, y, l, u) @@ -62,10 +63,9 @@ def draw(x, y, l, u): draw(320, 460, 200, pi / 2) -if __name__ == '__main__': - from PIL import Image, ImageDraw +if __name__ == "__main__": img = Image.new("RGB", (730, 500), "white") draw_pythagoras_tree_2(ImageDraw.Draw(img)) - img.save('img.png') + img.save("img.png") diff --git a/draw fractal/Sierpinski_carpet/Sierpinski_carpet__PIL.py b/draw fractal/Sierpinski_carpet/Sierpinski_carpet__PIL.py index d58b68422..4ecafccf6 100644 --- a/draw fractal/Sierpinski_carpet/Sierpinski_carpet__PIL.py +++ b/draw fractal/Sierpinski_carpet/Sierpinski_carpet__PIL.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """ @@ -47,31 +47,33 @@ # End. -def draw_sierpinski_carpet(draw_by_image, Z): - def serp(x1, y1, x2, y2, n): +from PIL import Image, ImageDraw + + +def draw_sierpinski_carpet(draw_by_image, Z) -> None: + def serp(x1, y1, x2, y2, n) -> None: if n > 0: - x1n = 2*x1/3 + x2 / 3 - x2n = x1/3 + 2*x2 / 3 - y1n = 2*y1/3 + y2 / 3 - y2n = y1/3+2*y2 / 3 + x1n = 2 * x1 / 3 + x2 / 3 + x2n = x1 / 3 + 2 * x2 / 3 + y1n = 2 * y1 / 3 + y2 / 3 + y2n = y1 / 3 + 2 * y2 / 3 draw_by_image.rectangle((x1n, y1n, x2n, y2n), fill="white", outline="black") - serp(x1, y1, x1n, y1n, n-1) - serp(x1n, y1, x2n, y1n, n-1) - serp(x2n, y1, x2, y1n, n-1) - serp(x1, y1n, x1n, y2n, n-1) - serp(x2n, y1n, x2, y2n, n-1) - serp(x1, y2n, x1n, y2, n-1) - serp(x1n, y2n, x2n, y2, n-1) - serp(x2n, y2n, x2, y2, n-1) + serp(x1, y1, x1n, y1n, n - 1) + serp(x1n, y1, x2n, y1n, n - 1) + serp(x2n, y1, x2, y1n, n - 1) + serp(x1, y1n, x1n, y2n, n - 1) + serp(x2n, y1n, x2, y2n, n - 1) + serp(x1, y2n, x1n, y2, n - 1) + serp(x1n, y2n, x2n, y2, n - 1) + serp(x2n, y2n, x2, y2, n - 1) draw_by_image.rectangle((20, 20, 460, 460), fill="white", outline="black") serp(20, 20, 460, 460, Z) -if __name__ == '__main__': - from PIL import Image, ImageDraw +if __name__ == "__main__": img = Image.new("RGB", (500, 500), "white") # Глубина фрактала @@ -79,4 +81,4 @@ def serp(x1, y1, x2, y2, n): draw_sierpinski_carpet(ImageDraw.Draw(img), Z) - img.save('img.png') + img.save("img.png") diff --git a/draw fractal/Sierpinski_triangle/Sierpinski_triangle__PIL.py b/draw fractal/Sierpinski_triangle/Sierpinski_triangle__PIL.py index 2034e9e08..7037a8d6e 100644 --- a/draw fractal/Sierpinski_triangle/Sierpinski_triangle__PIL.py +++ b/draw fractal/Sierpinski_triangle/Sierpinski_triangle__PIL.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """ @@ -49,15 +49,18 @@ # End. -def draw_sierpinski_triangle(draw_by_image, Z): +from PIL import Image, ImageDraw + + +def draw_sierpinski_triangle(draw_by_image, Z) -> None: color = "black" - def tr(x1, y1, x2, y2, x3, y3): + def tr(x1, y1, x2, y2, x3, y3) -> None: draw_by_image.line((x1, y1, x2, y2), color) draw_by_image.line((x2, y2, x3, y3), color) draw_by_image.line((x3, y3, x1, y1), color) - def draw(x1, y1, x2, y2, x3, y3, n): + def draw(x1, y1, x2, y2, x3, y3, n) -> None: if n > 0: x1n = (x1 + x2) / 2 y1n = (y1 + y2) / 2 @@ -75,8 +78,7 @@ def draw(x1, y1, x2, y2, x3, y3, n): draw(320, 10, 600, 470, 40, 470, Z) -if __name__ == '__main__': - from PIL import Image, ImageDraw +if __name__ == "__main__": img = Image.new("RGB", (650, 500), "white") # Глубина фрактала @@ -84,4 +86,4 @@ def draw(x1, y1, x2, y2, x3, y3, n): draw_sierpinski_triangle(ImageDraw.Draw(img), Z) - img.save('img.png') + img.save("img.png") diff --git a/draw fractal/Snowflake/Snowflake__PIL.py b/draw fractal/Snowflake/Snowflake__PIL.py index 1ba2a3985..28ca5b02c 100644 --- a/draw fractal/Snowflake/Snowflake__PIL.py +++ b/draw fractal/Snowflake/Snowflake__PIL.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """ @@ -36,31 +36,31 @@ from math import * +from PIL import Image, ImageDraw -def draw_snowflake(draw_by_image, width, height, count): - def draw(x0, y0, r, n): +def draw_snowflake(draw_by_image, width, height, count) -> None: + def draw(x0, y0, r, n) -> None: t = 2 * pi / count for i in range(count): x = x0 + r * cos(i * t) y = y0 - r * sin(i * t) - draw_by_image.line((x0, y0, x, y), 'black') + draw_by_image.line((x0, y0, x, y), "black") if n > 1: - draw(x, y, r // 5, n-1) + draw(x, y, r // 5, n - 1) x = width // 2 y = height // 2 draw(x, y, 180, 4) -if __name__ == '__main__': - from PIL import Image, ImageDraw +if __name__ == "__main__": img = Image.new("RGB", (500, 500), "white") # Количество повторений count = 8 draw_snowflake(ImageDraw.Draw(img), img.width, img.height, count) - img.save('img.png') + img.save("img.png") diff --git a/draw_handman.py b/draw_handman.py index 943865c0d..d218b19d6 100644 --- a/draw_handman.py +++ b/draw_handman.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" def draw_handman(number_wrong_answer, max_wrong_answer=5): @@ -20,18 +20,18 @@ def draw_handman(number_wrong_answer, max_wrong_answer=5): return handman_text part = int(max_wrong_answer / number_wrong_answer) - return '\n'.join(handman_text.split('\n')[:max_wrong_answer - part + 1]) + return "\n".join(handman_text.split("\n")[: max_wrong_answer - part + 1]) -if __name__ == '__main__': +if __name__ == "__main__": print(draw_handman(0)) - print('-' * 15) + print("-" * 15) print(draw_handman(1)) - print('-' * 15) + print("-" * 15) print(draw_handman(2)) - print('-' * 15) + print("-" * 15) print(draw_handman(3)) - print('-' * 15) + print("-" * 15) print(draw_handman(4)) - print('-' * 15) + print("-" * 15) print(draw_handman(5)) diff --git a/draw_wave.py b/draw_wave.py index cef4b86ea..a84976c18 100644 --- a/draw_wave.py +++ b/draw_wave.py @@ -2,26 +2,21 @@ # -*- coding: utf-8 -*- -__author__ = 'igrishaev' +__author__ = "igrishaev" """Рисование волны wav-файла. Взято из http://habrahabr.ru/post/113239/""" -import wave - import math +import wave import numpy as np - import matplotlib.pyplot as plt import matplotlib.ticker as ticker -types = { - 1: np.int8, - 2: np.int16, - 4: np.int32 -} + +types = {1: np.int8, 2: np.int16, 4: np.int32} duration = nframes = k = peak = None @@ -49,7 +44,7 @@ def format_db(x, pos=None): return int(db) -def draw(file_name): +def draw(file_name) -> None: wav = wave.open(file_name, mode="r") global nframes, k, peak, duration (nchannels, sampwidth, framerate, nframes, comptype, compname) = wav.getparams() @@ -58,7 +53,7 @@ def draw(file_name): w, h = 800, 300 k = nframes / w / 32 DPI = 72 - peak = 256 ** sampwidth / 2 + peak = 256**sampwidth / 2 content = wav.readframes(nframes) samples = np.fromstring(content, dtype=types[sampwidth]) diff --git a/dynamic_methods_link_call.py b/dynamic_methods_link_call.py index af2c0562b..7755ae318 100644 --- a/dynamic_methods_link_call.py +++ b/dynamic_methods_link_call.py @@ -1,33 +1,33 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" class CallBuilder: - def __init__(self, part=None, sep=''): + def __init__(self, part=None, sep="") -> None: self._part = part self._sep = sep def __getattr__(self, part): return CallBuilder( - part=((self._part + self._sep) if self._part else '') + part, - sep=self._sep + part=((self._part + self._sep) if self._part else "") + part, + sep=self._sep, ) def __call__(self, **kwargs): return self._part -if __name__ == '__main__': +if __name__ == "__main__": builder = CallBuilder() result = builder.H.e.l.l.o._.World() print(result) # Hello_World - builder = CallBuilder(sep='.') + builder = CallBuilder(sep=".") result = builder.H.e.l.l.o._.World() print(result) # H.e.l.l.o._.World - builder = CallBuilder(sep='.') + builder = CallBuilder(sep=".") result = builder.H.e.l.l.o._.W.o.r.l.d() print(result) # H.e.l.l.o._.W.o.r.l.d diff --git a/earth_console_animations.py b/earth_console_animations.py index b57d38460..02a11123c 100644 --- a/earth_console_animations.py +++ b/earth_console_animations.py @@ -1,15 +1,17 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/Dark-Princ3/X-tra-Telegram/blob/aa628465dddaf06383c84914df70c89a4d4fbf15/userbot/plugins/Earth.py#L13 + import time + items = "🌏🌍🌎🌎🌍🌏🌍🌎" for i in range(48): - print('\r' + ' ' * 100 + '\r', end='') - print(items[i % len(items)], end='') + print("\r" + " " * 100 + "\r", end="") + print(items[i % len(items)], end="") time.sleep(0.1) diff --git a/effect_of_vanishing_photos/effect_of_vanishing_photos.py b/effect_of_vanishing_photos/effect_of_vanishing_photos.py index f769b1725..37ec67af4 100644 --- a/effect_of_vanishing_photos/effect_of_vanishing_photos.py +++ b/effect_of_vanishing_photos/effect_of_vanishing_photos.py @@ -1,13 +1,15 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -"""Эффект исчезновения фотографии +""" +Эффект исчезновения фотографии Кликая на области на фотографии запускаются процессы плавного увеличения прозрачности пикселей, эффект как круги воды, будут расходиться пока не -закончатся непрозрачные пиксели""" +закончатся непрозрачные пиксели +""" import sys @@ -29,12 +31,12 @@ from PySide.QtCore import * -def log_uncaught_exceptions(ex_cls, ex, tb): - text = '{}: {}:\n'.format(ex_cls.__name__, ex) - text += ''.join(traceback.format_tb(tb)) +def log_uncaught_exceptions(ex_cls, ex, tb) -> None: + text = f"{ex_cls.__name__}: {ex}:\n" + text += "".join(traceback.format_tb(tb)) print(text) - QMessageBox.critical(None, 'Error', text) + QMessageBox.critical(None, "Error", text) sys.exit(1) @@ -43,14 +45,14 @@ def log_uncaught_exceptions(ex_cls, ex, tb): class Timer(QTimer): class Circle: - def __init__(self, pos_center): + def __init__(self, pos_center) -> None: self.pos_center = pos_center self.radii = 1 - def next(self): + def next(self) -> None: self.radii += 1 - def __init__(self, widget, image): + def __init__(self, widget, image) -> None: super().__init__() self.circle_list = list() @@ -66,10 +68,10 @@ def __init__(self, widget, image): self.painter.setPen(Qt.NoPen) self.painter.setBrush(Qt.transparent) - def add(self, pos_center): + def add(self, pos_center) -> None: self.circle_list.append(Timer.Circle(pos_center)) - def tick(self): + def tick(self) -> None: for circle in self.circle_list: self.painter.drawEllipse(circle.pos_center, circle.radii, circle.radii) circle.next() @@ -78,23 +80,23 @@ def tick(self): class Widget(QWidget): - def __init__(self): + def __init__(self) -> None: super().__init__() - self.setWindowTitle('effect_of_vanishing_photos.py') + self.setWindowTitle("effect_of_vanishing_photos.py") - self.im = QImage('im.png') + self.im = QImage("im.png") self.resize(self.im.size()) self.timer = Timer(self, self.im) self.timer.start() - def mouseReleaseEvent(self, event): + def mouseReleaseEvent(self, event) -> None: super().mouseReleaseEvent(event) self.timer.add(event.pos()) - def paintEvent(self, event): + def paintEvent(self, event) -> None: super().paintEvent(event) p = QPainter(self) @@ -102,11 +104,13 @@ def paintEvent(self, event): p.drawRect(self.rect()) p.setBrush(Qt.yellow) - p.drawRect(self.width() // 6, self.width() // 5, self.width() // 3, self.height() // 4) + p.drawRect( + self.width() // 6, self.width() // 5, self.width() // 3, self.height() // 4 + ) p.drawImage(0, 0, self.im) -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication(sys.argv) w = Widget() diff --git a/email/attach.py b/email/attach.py index 0a61ead27..15bec41de 100644 --- a/email/attach.py +++ b/email/attach.py @@ -3,7 +3,7 @@ from email.mime.image import MIMEImage import smtplib -__author__ = 'ipetrash' +__author__ = "ipetrash" """Пример отсылки письма, содержащего прикрепленные файлы, "самому себе".""" @@ -11,15 +11,15 @@ # http://www.tutorialspoint.com/python/python_sending_email.htm # https://docs.python.org/3.4/library/email-examples.html -if __name__ == '__main__': - mail_sender = 'USERNAME@DOMAIN' # Например: vasyapupkin@mail.ru - mail_passwd = 'PASSWORD' # Пароль к почте +if __name__ == "__main__": + mail_sender = "USERNAME@DOMAIN" # Например: vasyapupkin@mail.ru + mail_passwd = "PASSWORD" # Пароль к почте - smtp_server = 'YOUR.MAIL.SERVER' # Например: smtp.mail.ru + smtp_server = "YOUR.MAIL.SERVER" # Например: smtp.mail.ru port = 587 - mail_text = 'Example!\nFirst!\nSecond!\n\nРаз!\nДва!\nТри!' - mail_subject = 'Здарова чувак! Hello!!!' + mail_text = "Example!\nFirst!\nSecond!\n\nРаз!\nДва!\nТри!" + mail_subject = "Здарова чувак! Hello!!!" mail_from = mail_sender mail_to = [ mail_sender @@ -34,26 +34,26 @@ # Create a text/plain message msg = MIMEMultipart() - msg['From'] = mail_from - msg['To'] = ', '.join(mail_to) + msg["From"] = mail_from + msg["To"] = ", ".join(mail_to) # msg['Cc'] = ', '.join(mail_cc) # Получатели копии письма - msg['Subject'] = mail_subject + msg["Subject"] = mail_subject msg.attach(MIMEText(mail_text)) - with open('attach_files/im1.png', mode='rb') as f: + with open("attach_files/im1.png", mode="rb") as f: im = MIMEImage(f.read()) - im.add_header('content-disposition', 'attachment', filename='pict') + im.add_header("content-disposition", "attachment", filename="pict") msg.attach(im) - with open('attach_files/im2.jpg', mode='rb') as f: + with open("attach_files/im2.jpg", mode="rb") as f: im = MIMEImage(f.read()) - im.add_header('content-disposition', 'attachment', filename='im2.jpg') + im.add_header("content-disposition", "attachment", filename="im2.jpg") msg.attach(im) - with open('attach_files/hello.html', mode='rb') as f: - t = MIMEText(f.read(), _charset='utf-8') - t.add_header('content-disposition', 'attachment', filename='hello.html') + with open("attach_files/hello.html", mode="rb") as f: + t = MIMEText(f.read(), _charset="utf-8") + t.add_header("content-disposition", "attachment", filename="hello.html") msg.attach(t) try: @@ -63,7 +63,7 @@ s.login(mail_sender, mail_passwd) s.send_message(msg) - print('Email sent') + print("Email sent") except Exception as e: - print('Error sending mail: ' + str(e)) \ No newline at end of file + print("Error sending mail: " + str(e)) diff --git a/email/html.py b/email/html.py index d02a23034..59adafa5b 100644 --- a/email/html.py +++ b/email/html.py @@ -1,23 +1,26 @@ -from email.mime.multipart import MIMEMultipart -from email.mime.text import MIMEText -import smtplib +__author__ = "ipetrash" -__author__ = 'ipetrash' """Пример отсылки письма, содержащего обычный текст и html, "самому себе".""" +import smtplib + +from email.mime.multipart import MIMEMultipart +from email.mime.text import MIMEText + + # http://www.tutorialspoint.com/python/python_sending_email.htm # https://docs.python.org/3.4/library/email-examples.html -if __name__ == '__main__': - mail_sender = 'USERNAME@DOMAIN' # Например: vasyapupkin@mail.ru - mail_passwd = 'PASSWORD' # Пароль к почте +if __name__ == "__main__": + mail_sender = "USERNAME@DOMAIN" # Например: vasyapupkin@mail.ru + mail_passwd = "PASSWORD" # Пароль к почте - smtp_server = 'YOUR.MAIL.SERVER' # Например: smtp.mail.ru + smtp_server = "YOUR.MAIL.SERVER" # Например: smtp.mail.ru port = 587 - mail_subject = 'Здарова чувак! Hello!!!' + mail_subject = "Здарова чувак! Hello!!!" mail_from = mail_sender mail_to = [ mail_sender @@ -32,10 +35,10 @@ # Create a text/plain message msg = MIMEMultipart() - msg['From'] = mail_from - msg['To'] = ', '.join(mail_to) + msg["From"] = mail_from + msg["To"] = ", ".join(mail_to) # msg['Cc'] = ', '.join(mail_cc) # Получатели копии письма - msg['Subject'] = mail_subject + msg["Subject"] = mail_subject # Create the body of the message (a plain-text and an HTML version). text = "Hi!\nHow are you?\nHere is the link you wanted:\nhttps://www.python.org" @@ -52,7 +55,7 @@ """ msg.attach(MIMEText(text)) - msg.attach(MIMEText(html, _subtype='html')) + msg.attach(MIMEText(html, _subtype="html")) msg.attach(MIMEText(html)) try: @@ -62,7 +65,7 @@ s.login(mail_sender, mail_passwd) s.send_message(msg) - print('Email sent') + print("Email sent") except Exception as e: - print('Error sending mail: ' + str(e)) \ No newline at end of file + print("Error sending mail: " + str(e)) diff --git a/email/last_from.py b/email/last_from.py index e323b3fe5..a0978de4c 100644 --- a/email/last_from.py +++ b/email/last_from.py @@ -1,21 +1,27 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" +import email +import imaplib import sys import logging + +from datetime import date, timedelta + + logging.basicConfig( level=logging.DEBUG, stream=sys.stdout, - format='[%(asctime)s] %(filename)s[LINE:%(lineno)d] %(levelname)-8s %(message)s' + format="[%(asctime)s] %(filename)s[LINE:%(lineno)d] %(levelname)-8s %(message)s", ) -username = '' -password = '' -smtp_server = '' -from_email = '' +username = "" +password = "" +smtp_server = "" +from_email = "" def get_last_lunch_menu(): @@ -24,27 +30,24 @@ def get_last_lunch_menu(): """ - logging.debug('Check last email.') + logging.debug("Check last email.") - import imaplib connect = imaplib.IMAP4(smtp_server) connect.login(username, password) connect.select() # Если не ограничивать датой, соберет все письма и запрос будет дольше выполняться - from datetime import date, timedelta today = date.today() week_ago = today - timedelta(weeks=1) - since = week_ago.strftime('%d-%b-%Y') + since = week_ago.strftime("%d-%b-%Y") - logging.debug('Search emails from %s.', from_email) - typ, msgnums = connect.search(None, 'HEADER From', from_email, 'SINCE', since) - logging.debug('Search result: %s.', msgnums[0].split()) + logging.debug("Search emails from %s.", from_email) + typ, msgnums = connect.search(None, "HEADER From", from_email, "SINCE", since) + logging.debug("Search result: %s.", msgnums[0].split()) last_id = msgnums[0].split()[-1] - typ, data = connect.fetch(last_id, '(RFC822)') + typ, data = connect.fetch(last_id, "(RFC822)") - import email msg = email.message_from_bytes(data[0][1]) connect.close() @@ -53,6 +56,6 @@ def get_last_lunch_menu(): return msg -if __name__ == '__main__': +if __name__ == "__main__": msg = get_last_lunch_menu() print(msg) diff --git a/email/simple.py b/email/simple.py index 9baadce6d..40875db3d 100644 --- a/email/simple.py +++ b/email/simple.py @@ -2,112 +2,45 @@ # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """Пример отсылки простого письма самому себе.""" -import smtplib -from email.mime.text import MIMEText - - # http://www.tutorialspoint.com/python/python_sending_email.htm # https://docs.python.org/3.4/library/email-examples.html -# if __name__ == '__main__': -# mail_sender = 'USERNAME@DOMAIN' # Например: vasyapupkin@mail.ru -# mail_passwd = 'PASSWORD' # Пароль к почте -# -# smtp_server = 'YOUR.MAIL.SERVER' # Например: smtp.mail.ru -# port = 587 -# -# mail_text = 'Example!\nFirst!\nSecond!\n\nРаз!\nДва!\nТри!' -# mail_subject = 'Здарова чувак! Hello!!!' -# mail_from = mail_sender -# mail_to = [ -# mail_sender -# # , '*****@mail.com', -# # ... -# ] -# # mail_cc = [ -# # # '*****@mail.com', -# # # '*****@gmail.com', -# # ... -# # ] -# -# # Create a text/plain message -# msg = MIMEText(mail_text) -# msg['From'] = mail_from -# msg['To'] = ', '.join(mail_to) -# # msg['Cc'] = ', '.join(mail_cc) # Получатели копии письма -# msg['Subject'] = mail_subject -# -# try: -# # Send the message on SMTP server. -# with smtplib.SMTP(smtp_server, port) as s: -# s.starttls() -# s.login(mail_sender, mail_passwd) -# s.send_message(msg) -# -# print('Email sent') -# -# except Exception as e: -# print('Error sending mail: ' + str(e)) - - -# import smtplib -# from email.MIMEMultipart import MIMEMultipart -# from email.MIMEText import MIMEText -# -# msg = MIMEMultipart() -# msg['From'] = 'me@gmail.com' -# msg['To'] = 'you@gmail.com' -# msg['Subject'] = 'simple email in python' -# message = 'here is the email' -# msg.attach(MIMEText(message)) -# -# mailserver = smtplib.SMTP('smtp.gmail.com',587) -# # identify ourselves to smtp gmail client -# mailserver.ehlo() -# # secure our email with tls encryption -# mailserver.starttls() -# # re-identify ourselves as an encrypted connection -# mailserver.ehlo() -# mailserver.login('me@gmail.com', 'mypassword') -# -# mailserver.sendmail('me@gmail.com','you@gmail.com',msg.as_string()) -# -# mailserver.quit() - - -SMTPserver = 'smtp.att.yahoo.com' -sender = 'me@my_email_domain.net' -destination = ['recipient@her_email_domain.com'] + +import sys + +# this invokes the secure SMTP protocol (port 465, uses SSL) +from smtplib import SMTP_SSL as SMTP + +from email.mime.text import MIMEText + + +SMTPserver = "smtp.att.yahoo.com" +sender = "me@my_email_domain.net" +destination = ["recipient@her_email_domain.com"] USERNAME = "USER_NAME_FOR_INTERNET_SERVICE_PROVIDER" PASSWORD = "PASSWORD_INTERNET_SERVICE_PROVIDER" # typical values for text_subtype are plain, html, xml -text_subtype = 'plain' +text_subtype = "plain" -content="""\ +content = """\ Test message """ -subject="Sent from Python" - -import sys - -from smtplib import SMTP_SSL as SMTP # this invokes the secure SMTP protocol (port 465, uses SSL) -# from smtplib import SMTP # use this for standard SMTP protocol (port 25, no encryption) -from email.mime.text import MIMEText +subject = "Sent from Python" try: msg = MIMEText(content, text_subtype) - msg['Subject']= subject - msg['From'] = sender # some SMTP servers will do this automatically, not all + msg["Subject"] = subject + msg["From"] = sender # some SMTP servers will do this automatically, not all conn = SMTP(SMTPserver) conn.set_debuglevel(False) @@ -118,4 +51,4 @@ conn.close() except Exception as e: - sys.exit( "mail failed; %s" % str(e) ) # give a error message + sys.exit("mail failed; %s" % str(e)) # give a error message diff --git a/email_validator__examples/hello_world.py b/email_validator__examples/hello_world.py index 47498932b..68212391a 100644 --- a/email_validator__examples/hello_world.py +++ b/email_validator__examples/hello_world.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install email-validator @@ -28,7 +28,7 @@ except EmailNotValidError as e: # Email is not valid. # The exception message is human-readable. - print(f'{email!r}, error: {e!r}') + print(f"{email!r}, error: {e!r}") # 'my+address@mydomain.tld', error: EmailUndeliverableError('The domain name mydomain.tld does not exist.') print(email) diff --git a/emulating input().py b/emulating input().py index ab2757eb9..471560e04 100644 --- a/emulating input().py +++ b/emulating input().py @@ -1,17 +1,17 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from io import StringIO -buffer = StringIO( - "dfdfdfdf\n12\n34", - newline='\n' -) + + +buffer = StringIO("dfdfdfdf\n12\n34", newline="\n") + def input(*args, **kwargs): - return next(buffer).rstrip('\n') + return next(buffer).rstrip("\n") text = input() diff --git a/enum__examples.py b/enum__examples.py index ef2717063..2c9cbae63 100644 --- a/enum__examples.py +++ b/enum__examples.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # https://docs.python.org/3.5/library/enum.html @@ -22,11 +22,12 @@ class Animal(Enum): print() print(Animal) # -print(list(Animal)) # [, , , ] +print(list(Animal)) +# [, , , ] print() for x in Animal: - print('{}: "{}" = {}'.format(x, x.name, x.value)) + print(f'{x}: "{x.name}" = {x.value}') print() @@ -50,23 +51,23 @@ class Animal2(IntEnum): # https://docs.python.org/3.5/library/enum.html#planet class Planet(Enum): - MERCURY = (3.303e+23, 2.4397e6) - VENUS = (4.869e+24, 6.0518e6) - EARTH = (5.976e+24, 6.37814e6) - MARS = (6.421e+23, 3.3972e6) - JUPITER = (1.9e+27, 7.1492e7) - SATURN = (5.688e+26, 6.0268e7) - URANUS = (8.686e+25, 2.5559e7) - NEPTUNE = (1.024e+26, 2.4746e7) - - def __init__(self, mass, radius): + MERCURY = (3.303e23, 2.4397e6) + VENUS = (4.869e24, 6.0518e6) + EARTH = (5.976e24, 6.37814e6) + MARS = (6.421e23, 3.3972e6) + JUPITER = (1.9e27, 7.1492e7) + SATURN = (5.688e26, 6.0268e7) + URANUS = (8.686e25, 2.5559e7) + NEPTUNE = (1.024e26, 2.4746e7) + + def __init__(self, mass, radius) -> None: self.mass = mass # in kilograms self.radius = radius # in meters @property def surface_gravity(self): # universal gravitational constant (m3 kg-1 s-2) - G = 6.67300E-11 + G = 6.67300e-11 return G * self.mass / (self.radius * self.radius) @@ -81,40 +82,41 @@ class StrEnum(str, Enum): @unique class Color(StrEnum): - RED = 'red' - GREEN = 'green' - BLUE = 'blue' + RED = "red" + GREEN = "green" + BLUE = "blue" -print(Color.RED == 'red') # True -print(Color.GREEN == 'green') # True -print(Color.GREEN == 'red') # False +print(Color.RED == "red") # True +print(Color.GREEN == "green") # True +print(Color.GREEN == "red") # False print() print(Color.RED in Color) # True print("red" in Color) # False print("red" in Color) # False print() -print('Color is ' + Color.RED) # Color is red -print('Color is ' + Color.RED + Color.GREEN) # Color is redgreen -print('Colors: ' + ', '.join(Color)) # Colors: red, green, blue +print("Color is " + Color.RED) # Color is red +print("Color is " + Color.RED + Color.GREEN) # Color is redgreen +print("Colors: " + ", ".join(Color)) # Colors: red, green, blue print() data = [ { - 'name': 'car', - 'color': Color.RED, + "name": "car", + "color": Color.RED, }, { - 'name': 'dog', - 'color': Color.BLUE, + "name": "dog", + "color": Color.BLUE, }, ] print(data) -print(Color('red')) # Color.red -print(Color('blue')) # Color.blue +print(Color("red")) # Color.red +print(Color("blue")) # Color.blue # print(Color('yellow')) # ValueError: 'yellow' is not a valid Color print() import json + print(json.dumps(data, indent=4)) diff --git a/enum__examples/hello_world__IntEnum__auto_value.py b/enum__examples/hello_world__IntEnum__auto_value.py index bd185ee51..4b2d3ca71 100644 --- a/enum__examples/hello_world__IntEnum__auto_value.py +++ b/enum__examples/hello_world__IntEnum__auto_value.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from enum import IntEnum, auto @@ -14,21 +14,21 @@ class Direction(IntEnum): RIGHT = auto() -print(Direction) # -print(Direction.UP) # Direction.UP -print(Direction.DOWN) # Direction.DOWN +print(Direction) # +print(Direction.UP) # Direction.UP +print(Direction.DOWN) # Direction.DOWN print() print(Direction.UP.value) # 1 -print(int(Direction.UP)) # 1 +print(int(Direction.UP)) # 1 print() -print(Direction.UP == 1) # True -print(Direction.UP == 2) # False +print(Direction.UP == 1) # True +print(Direction.UP == 2) # False print() direction = Direction.UP -print(direction == Direction.UP) # True +print(direction == Direction.UP) # True print(direction == Direction.DOWN) # False print() @@ -36,5 +36,8 @@ class Direction(IntEnum): print(Direction(3)) # Direction.LEFT print() -print(list(Direction)) # [, , , ] -print([x for x in Direction]) # [, , , ] +print(list(Direction)) +# [, , , ] + +print([x for x in Direction]) +# [, , , ] diff --git a/enum__examples/hello_world__auto_value.py b/enum__examples/hello_world__auto_value.py index ac03e56df..9d61e07c5 100644 --- a/enum__examples/hello_world__auto_value.py +++ b/enum__examples/hello_world__auto_value.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from enum import Enum, auto @@ -14,18 +14,18 @@ class Direction(Enum): RIGHT = auto() -print(Direction) # -print(Direction.UP) # Direction.UP -print(Direction.DOWN) # Direction.DOWN +print(Direction) # +print(Direction.UP) # Direction.UP +print(Direction.DOWN) # Direction.DOWN print() print(Direction.UP.value) # 1 -print(Direction.UP == 1) # False -print(Direction.UP == 2) # False +print(Direction.UP == 1) # False +print(Direction.UP == 2) # False print() direction = Direction.UP -print(direction == Direction.UP) # True +print(direction == Direction.UP) # True print(direction == Direction.DOWN) # False print() @@ -33,5 +33,8 @@ class Direction(Enum): print(Direction(3)) # Direction.LEFT print() -print(list(Direction)) # [, , , ] -print([x for x in Direction]) # [, , , ] +print(list(Direction)) +# [, , , ] + +print([x for x in Direction]) +# [, , , ] diff --git a/enum__examples/random_value.py b/enum__examples/random_value.py index b11f1129a..d9df1a41b 100644 --- a/enum__examples/random_value.py +++ b/enum__examples/random_value.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from enum import Enum, auto @@ -17,7 +17,7 @@ class MoveEnum(Enum): for _ in range(5): button = choice(list(MoveEnum)) - print(f'{button:<14} {button.name:<5} {button.value}') + print(f"{button:<14} {button.name:<5} {button.value}") print() print(choices(list(MoveEnum), k=5)) diff --git a/enum__examples/value_as_title.py b/enum__examples/value_as_title.py index ab3e18fe1..a93d989da 100644 --- a/enum__examples/value_as_title.py +++ b/enum__examples/value_as_title.py @@ -1,10 +1,11 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from enum import Enum, auto +from random import choice class KeyboardEnum(Enum): @@ -19,12 +20,11 @@ def title(self): return self.name.replace("_", " ") -print(KeyboardEnum.GET_ALL) # KeyboardEnum.GET_ALL +print(KeyboardEnum.GET_ALL) # KeyboardEnum.GET_ALL print(KeyboardEnum.GET_ALL.title()) # GET ALL print() -from random import choice button = choice(list(KeyboardEnum)) print(f'{button}: "{button.title()}"') -print(KeyboardEnum.SEND_ALL) \ No newline at end of file +print(KeyboardEnum.SEND_ALL) diff --git a/eval_expr_total_time.py b/eval_expr_total_time.py new file mode 100644 index 000000000..10fa6bdc0 --- /dev/null +++ b/eval_expr_total_time.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import re +from seconds_to_str import seconds_to_str + + +PATTERN_TIME: re.Pattern = re.compile(r"(\d\d:\d\d:\d\d)") +PATTERN_EXPR_WITH_TIME: re.Pattern = re.compile( + f"^{PATTERN_TIME.pattern}(?: *[+-] *{PATTERN_TIME.pattern})*$" +) +PATTERN_COMMENT: re.Pattern = re.compile("#.+$") +PATTERN_IGNORE_CHARS: re.Pattern = re.compile(r"[^\d+-:]+") + + +def get_seconds(hh_mm_ss: str) -> int: + hh, mm, ss = map(int, hh_mm_ss.split(":")) + return hh * 3600 + mm * 60 + ss + + +def preprocess_expr_with_time(text: str) -> str: + return PATTERN_TIME.sub(lambda m: str(get_seconds(m[1])), text) + + +def preprocess_text(text: str) -> str: + lines: list[str] = [] + for line in text.splitlines(): + line = PATTERN_COMMENT.sub("", line) + line = PATTERN_IGNORE_CHARS.sub("", line) + lines.append(line) + + return "".join(lines) + + +def eval_expr_with_time(text: str) -> str: + text: str = preprocess_text(text) + if not PATTERN_EXPR_WITH_TIME.match(text): + raise Exception(f"Expression {text!r} not valid!") + + text: str = preprocess_expr_with_time(text) + total_seconds: int = eval(text) + return seconds_to_str(total_seconds) + + +if __name__ == "__main__": + text = """ + 08:53:11 - 07:15:00 + + 08:56:12 + """ + result: str = eval_expr_with_time(text) + print(result) + assert result == "10:34:23" + + text = """ + # This is comment + 08:53:11 - 07:15:00 + # Day 2 + + 08:56:12 # Comment 2 + """ + result: str = eval_expr_with_time(text) + print(result) + assert result == "10:34:23" + + assert get_seconds("00:00:01") == 1 + assert get_seconds("00:01:01") == 61 + assert get_seconds("01:01:01") == 3661 + + assert preprocess_expr_with_time("00:00:01") == "1" + assert ( + preprocess_expr_with_time("00:00:01 + 00:01:01 + 01:01:01") == "1 + 61 + 3661" + ) + + assert eval_expr_with_time("08:53:11") == "08:53:11" + assert eval_expr_with_time("00:00:01 + 00:01:01 + 01:01:01") == "01:02:03" diff --git a/eval_expr_with_time.py b/eval_expr_with_time.py deleted file mode 100644 index 314c9f6a0..000000000 --- a/eval_expr_with_time.py +++ /dev/null @@ -1,47 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -__author__ = 'ipetrash' - - -import re -from seconds_to_str import seconds_to_str - - -PATTERN_TIME = re.compile(r'(\d\d:\d\d:\d\d)') -PATTERN_EXPR_WITH_TIME = re.compile(f'^{PATTERN_TIME.pattern}(?: [+-] {PATTERN_TIME.pattern})*$') - - -def get_seconds(hh_mm_ss: str) -> int: - hh, mm, ss = map(int, hh_mm_ss.split(':')) - return hh * 3600 + mm * 60 + ss - - -def preprocess_expr_with_time(text: str) -> str: - return PATTERN_TIME.sub(lambda m: str(get_seconds(m[1])), text) - - -def eval_expr_with_time(text: str) -> str: - if not PATTERN_EXPR_WITH_TIME.match(text): - raise Exception(f'Expression {text!r} not valid!') - - text = preprocess_expr_with_time(text) - total_seconds = eval(text) - return seconds_to_str(total_seconds) - - -if __name__ == '__main__': - text = '08:53:11 - 07:15:00 + 08:56:12' - - print(eval_expr_with_time(text)) - # 10:34:23 - - assert get_seconds('00:00:01') == 1 - assert get_seconds('00:01:01') == 61 - assert get_seconds('01:01:01') == 3661 - - assert preprocess_expr_with_time('00:00:01') == '1' - assert preprocess_expr_with_time('00:00:01 + 00:01:01 + 01:01:01') == '1 + 61 + 3661' - - assert eval_expr_with_time('08:53:11') == '08:53:11' - assert eval_expr_with_time('00:00:01 + 00:01:01 + 01:01:01') == '01:02:03' diff --git a/exchange_rates/banki_ru.py b/exchange_rates/banki_ru.py index f28f1076e..350442793 100644 --- a/exchange_rates/banki_ru.py +++ b/exchange_rates/banki_ru.py @@ -1,32 +1,39 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" + + +import requests def exchange_rate(currency_id, timestamp=None): if timestamp is None: from datetime import datetime + timestamp = int(datetime.today().timestamp()) data = { - 'currency_id': currency_id, - 'date': timestamp + "currency_id": currency_id, + "date": timestampб } headers = { - 'X-Requested-With': 'XMLHttpRequest', - 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:45.0) Gecko/20100101 Firefox/45.0' + "X-Requested-With": "XMLHttpRequest", + "User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:45.0) Gecko/20100101 Firefox/45.0", } - import requests - rs = requests.post('http://www.banki.ru/products/currency/ajax/quotations/value/cbr/', json=data, headers=headers) - return rs.json()['value'] + rs = requests.post( + "http://www.banki.ru/products/currency/ajax/quotations/value/cbr/", + json=data, + headers=headers, + ) + return rs.json()["value"] -if __name__ == '__main__': +if __name__ == "__main__": # 840 -- USD - print('USD:', exchange_rate(840)) + print("USD:", exchange_rate(840)) # 978 -- EUR - print('EUR:', exchange_rate(978)) + print("EUR:", exchange_rate(978)) diff --git a/exchange_rates/cbr_ru.py b/exchange_rates/cbr_ru.py index e58c4075b..5ec019b5a 100644 --- a/exchange_rates/cbr_ru.py +++ b/exchange_rates/cbr_ru.py @@ -1,7 +1,13 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" + + +from datetime import date +from urllib.request import urlopen + +from lxml import etree def exchange_rate(currency, date_req=None): @@ -15,19 +21,16 @@ def exchange_rate(currency, date_req=None): # Example: # http://www.cbr.ru/scripts/XML_daily.asp # http://www.cbr.ru/scripts/XML_daily.asp?date_req=21.10.2016 - from datetime import date if date_req is None: date_req = date.today() if isinstance(date_req, date): - date_req = date_req.strftime('%d.%m.%Y') + date_req = date_req.strftime("%d.%m.%Y") - url = 'http://www.cbr.ru/scripts/XML_daily.asp?date_req=' + date_req + url = "http://www.cbr.ru/scripts/XML_daily.asp?date_req=" + date_req # url = 'http://www.cbr.ru/scripts/XML_daily.asp' - from urllib.request import urlopen with urlopen(url) as f: - from lxml import etree root = etree.XML(f.read()) # @@ -41,33 +44,33 @@ def exchange_rate(currency, date_req=None): # ... for valute in root: - ccy = valute.xpath('child::CharCode/text()')[0] - value = valute.xpath('child::Value/text()')[0] + ccy = valute.xpath("child::CharCode/text()")[0] + value = valute.xpath("child::Value/text()")[0] if currency == ccy: - return float(value.replace(',', '.')), root.attrib['Date'] + return float(value.replace(",", ".")), root.attrib["Date"] return None, None -if __name__ == '__main__': - print('USD:', exchange_rate('USD')) - print('EUR:', exchange_rate('EUR')) +if __name__ == "__main__": + print("USD:", exchange_rate("USD")) + print("EUR:", exchange_rate("EUR")) from datetime import date, timedelta date_req = date.today() - value, rate_date = exchange_rate('USD', date_req) - print('{}: USD: {}'.format(rate_date, value)) + value, rate_date = exchange_rate("USD", date_req) + print(f"{rate_date}: USD: {value}") date_req = date.today() - timedelta(days=1) - value, rate_date = exchange_rate('USD', date_req) - print('{}: USD: {}'.format(rate_date, value)) + value, rate_date = exchange_rate("USD", date_req) + print(f"{rate_date}: USD: {value}") date_req = date.today() + timedelta(days=1) - value, rate_date = exchange_rate('USD', date_req) - print('{}: USD: {}'.format(rate_date, value)) + value, rate_date = exchange_rate("USD", date_req) + print(f"{rate_date}: USD: {value}") date_req = date.today() + timedelta(days=2) - value, rate_date = exchange_rate('USD', date_req) - print('{}: USD: {}'.format(rate_date, value)) + value, rate_date = exchange_rate("USD", date_req) + print(f"{rate_date}: USD: {value}") diff --git a/exchange_rates/cbr_ru_parse.py b/exchange_rates/cbr_ru_parse.py index 08aa971ef..f2c2b3b64 100644 --- a/exchange_rates/cbr_ru_parse.py +++ b/exchange_rates/cbr_ru_parse.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """Парсер курса доллара и евро за текущую дату от сайта центробанка России.""" @@ -14,12 +14,12 @@ from robobrowser import RoboBrowser -date_req = date.today().strftime('%d.%m.%Y') -url = 'http://www.cbr.ru/scripts/XML_daily.asp?date_req=' + date_req +date_req = date.today().strftime("%d.%m.%Y") +url = "http://www.cbr.ru/scripts/XML_daily.asp?date_req=" + date_req browser = RoboBrowser( - user_agent='Mozilla/5.0 (Windows NT 6.1; WOW64; rv:45.0) Gecko/20100101 Firefox/45.0', - parser='html.parser' + user_agent="Mozilla/5.0 (Windows NT 6.1; WOW64; rv:45.0) Gecko/20100101 Firefox/45.0", + parser="html.parser", ) browser.open(url) rs = browser.response @@ -28,9 +28,9 @@ print(rs.status_code, rs.reason) sys.exit() -for valute_el in browser.select('Valute'): - char_code = valute_el.select_one('CharCode').get_text(strip=True) - value = valute_el.select_one('Value').get_text(strip=True) +for valute_el in browser.select("Valute"): + char_code = valute_el.select_one("CharCode").get_text(strip=True) + value = valute_el.select_one("Value").get_text(strip=True) - if char_code in ['USD', 'EUR']: + if char_code in ["USD", "EUR"]: print(char_code, value) diff --git a/exchange_rates/yahoo.py b/exchange_rates/yahoo.py index a32446aa2..2861f0900 100644 --- a/exchange_rates/yahoo.py +++ b/exchange_rates/yahoo.py @@ -1,21 +1,21 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """Получение курса доллара и евро от yahoo.""" -# TODO: непонятно за какую дату находит -if __name__ == '__main__': - url = 'https://query.yahooapis.com/v1/public/yql?q=select+*+from+yahoo.finance.xchange+where+pair+=+%22USDRUB,EURRUB%22&format=json&env=store%3A%2F%2Fdatatables.org%2Falltableswithkeys&callback=' +import requests + - import requests - rs = requests.get(url) +# TODO: непонятно за какую дату находит +url = "https://query.yahooapis.com/v1/public/yql?q=select+*+from+yahoo.finance.xchange+where+pair+=+%22USDRUB,EURRUB%22&format=json&env=store%3A%2F%2Fdatatables.org%2Falltableswithkeys&callback=" +rs = requests.get(url) - text = 'Курс:' - for rate in rs.json()['query']['results']['rate']: - text += '\n' + rate['Name'].split('/')[0] + ' ' + rate['Rate'] +text = "Курс:" +for rate in rs.json()["query"]["results"]["rate"]: + text += "\n" + rate["Name"].split("/")[0] + " " + rate["Rate"] - print(text) +print(text) diff --git a/exit_handler.py b/exit_handler.py index 7d92419f8..b4a31d9a0 100644 --- a/exit_handler.py +++ b/exit_handler.py @@ -1,16 +1,18 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import atexit from timeit import default_timer as timer + + start_time = timer() -def exit_handler(): - print('Execution time: {:.3f} secs.'.format(timer() - start_time)) +def exit_handler() -> None: + print(f"Execution time: {timer() - start_time:.3f} secs.") atexit.register(exit_handler) @@ -18,9 +20,9 @@ def exit_handler(): # OR with decorator: @atexit.register -def exit_handler(): - print('Execution time: {:.3f} secs.'.format(timer() - start_time)) +def exit_handler() -> None: + print(f"Execution time: {timer() - start_time:.3f} secs.") -number = int(input('Input number: ')) -print('My super sum:', sum(range(number ** 2))) +number = int(input("Input number: ")) +print("My super sum:", sum(range(number**2))) diff --git a/explore__windows.py b/explore__windows.py index d0ff58b97..818614c91 100644 --- a/explore__windows.py +++ b/explore__windows.py @@ -1,27 +1,27 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -from pathlib import Path import subprocess -from typing import Union + +from pathlib import Path -def explore(path: Union[str, Path], select=True): +def explore(path: str | Path, select=True) -> None: path = Path(path).resolve() if path.is_dir() or path.is_file(): args = ["explorer"] if select: - args.append('/select,') + args.append("/select,") args.append(str(path)) subprocess.run(args, shell=True) -if __name__ == '__main__': +if __name__ == "__main__": current_dir = Path(__file__).resolve().parent # Open parent dir and select diff --git a/extract__one_file_exe__pyinstaller/_source_test_file.py b/extract__one_file_exe__pyinstaller/_source_test_file.py index 437b01bc5..0c8d714e5 100644 --- a/extract__one_file_exe__pyinstaller/_source_test_file.py +++ b/extract__one_file_exe__pyinstaller/_source_test_file.py @@ -2,11 +2,12 @@ # Python bytecode 3.7 (3394) # Decompiled from: Python 3.7.3 (default, Apr 24 2019, 15:29:51) [MSC v.1915 64 bit (AMD64)] # Embedded file name: extract__one_file_exe__pyinstaller\_test_file.py -__author__ = 'ipetrash' +__author__ = "ipetrash" + def say(): - print('Hello World!') + print("Hello World!") -if __name__ == '__main__': - say() \ No newline at end of file +if __name__ == "__main__": + say() diff --git a/extract__one_file_exe__pyinstaller/_test_file.py b/extract__one_file_exe__pyinstaller/_test_file.py index e846ca7f8..b9c0207b7 100644 --- a/extract__one_file_exe__pyinstaller/_test_file.py +++ b/extract__one_file_exe__pyinstaller/_test_file.py @@ -1,13 +1,13 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # Prints def say(): - print('Hello World!') + print("Hello World!") -if __name__ == '__main__': +if __name__ == "__main__": say() diff --git a/extract__one_file_exe__pyinstaller/main.py b/extract__one_file_exe__pyinstaller/main.py index 472296960..bea0ddc2e 100644 --- a/extract__one_file_exe__pyinstaller/main.py +++ b/extract__one_file_exe__pyinstaller/main.py @@ -1,14 +1,15 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -import subprocess +import os import shutil +import subprocess import sys + from pathlib import Path -import os from urllib.request import urlretrieve @@ -16,44 +17,41 @@ # SOURCE: https://github.com/rocky/python-uncompyle6 -print('START: INSTALL SCRIPTS') -subprocess.call([ - 'pip', 'install', 'uncompyle6' -]) +print("START: INSTALL SCRIPTS") +subprocess.call(["pip", "install", "uncompyle6"]) urlretrieve( - 'https://raw.githubusercontent.com/extremecoders-re/pyinstxtractor/master/pyinstxtractor.py', - 'pyinstxtractor.py' + "https://raw.githubusercontent.com/extremecoders-re/pyinstxtractor/master/pyinstxtractor.py", + "pyinstxtractor.py", ) -print('FINISH: INSTALL SCRIPTS') +print("FINISH: INSTALL SCRIPTS") -print('\n' + '-' * 100 + '\n') +print("\n" + "-" * 100 + "\n") -print('START: BUILD EXE') -subprocess.call([ - "pyinstaller", '--clean', "--onefile", '_test_file.py' -]) +print("START: BUILD EXE") +subprocess.call(["pyinstaller", "--clean", "--onefile", "_test_file.py"]) -shutil.rmtree('build') -os.remove('_test_file.spec') -print('FINISH: BUILD EXE') +shutil.rmtree("build") +os.remove("_test_file.spec") +print("FINISH: BUILD EXE") -print('\n' + '-' * 100 + '\n') +print("\n" + "-" * 100 + "\n") -print('START: UNPACK EXE') -subprocess.call([ - sys.executable, 'pyinstxtractor.py', 'dist/_test_file.exe' -]) -print('FINISH: UNPACK EXE') +print("START: UNPACK EXE") +subprocess.call([sys.executable, "pyinstxtractor.py", "dist/_test_file.exe"]) +print("FINISH: UNPACK EXE") -print('\n' + '-' * 100 + '\n') +print("\n" + "-" * 100 + "\n") -print('START: CONVERT PYC TO PY') -source_test_file = Path('_source_test_file.py') +print("START: CONVERT PYC TO PY") +source_test_file = Path("_source_test_file.py") subprocess.call([ - 'uncompyle6', '-o', str(source_test_file), '_test_file.exe_extracted/_test_file.pyc' + "uncompyle6", + "-o", + str(source_test_file), + "_test_file.exe_extracted/_test_file.pyc", ]) -print('FINISH: CONVERT PYC TO PY') +print("FINISH: CONVERT PYC TO PY") print() print(f"Content {source_test_file.name}:") -print(source_test_file.read_text('utf-8')) +print(source_test_file.read_text("utf-8")) diff --git a/extract_from_doc.qt.io/main.py b/extract_from_doc.qt.io/main.py index 1d33eff9f..77beb4572 100644 --- a/extract_from_doc.qt.io/main.py +++ b/extract_from_doc.qt.io/main.py @@ -1,47 +1,45 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """Скрипт для скачивания примеров как проекты: сохранение в папки, в файлы""" -def get_path_and_content(url): - import requests - rs = requests.get(url) +from urllib.parse import urljoin + +import requests +from bs4 import BeautifulSoup - from bs4 import BeautifulSoup - root = BeautifulSoup(rs.content, 'lxml') - path = root.select_one('.subtitle').text.strip() - content = root.select_one('.descr').text.strip() +def get_path_and_content(url: str) -> tuple[str, str]: + rs = requests.get(url) + root = BeautifulSoup(rs.content, "lxml") + + path = root.select_one(".subtitle").text.strip() + content = root.select_one(".descr").text.strip() return path, content -def get_content(url): +def get_content(url: str) -> str: _, content = get_path_and_content(url) return content -def get_list_urls_files(url): - import requests +def get_list_urls_files(url: str) -> list[tuple[str, str]]: rs = requests.get(url) + root = BeautifulSoup(rs.content, "lxml") - from bs4 import BeautifulSoup - root = BeautifulSoup(rs.content, 'lxml') + urls_files_list = [] - urls_files_list = list() - - for p in root.select('p'): - if p.text.strip() != 'Files:': + for p in root.select("p"): + if p.text.strip() != "Files:": continue - ul = p.find_next_sibling('ul') - for a in ul.select('li > a'): - from urllib.parse import urljoin - abs_url = urljoin(url, a['href']) - + ul = p.find_next_sibling("ul") + for a in ul.select("li > a"): + abs_url = urljoin(url, a["href"]) file_name = a.text.strip() urls_files_list.append((abs_url, file_name)) @@ -49,26 +47,27 @@ def get_list_urls_files(url): return urls_files_list -if __name__ == '__main__': - url = 'http://doc.qt.io/qt-5/qtwebengine-webenginewidgets-simplebrowser-simplebrowser-pro.html' +if __name__ == "__main__": + import os + + url = "http://doc.qt.io/qt-5/qtwebengine-webenginewidgets-simplebrowser-simplebrowser-pro.html" path, content = get_path_and_content(url) - print(path, content.encode('utf-8')) + print(path, content.encode("utf-8")) content = get_content(url) - print(content.encode('utf-8')) + print(content.encode("utf-8")) print() - url = 'http://doc.qt.io/qt-5/qtwebengine-webenginewidgets-simplebrowser-example.html' + url = "http://doc.qt.io/qt-5/qtwebengine-webenginewidgets-simplebrowser-example.html" urls_files_list = get_list_urls_files(url) - print('Files: {}'.format(len(urls_files_list))) + print(f"Files: {len(urls_files_list)}") for i, (url, path) in enumerate(urls_files_list, 1): - print('{}. "{}": {}'.format(i, path, url)) + print(f'{i}. "{path}": {url}') - import os dirs = os.path.dirname(path) os.makedirs(dirs, exist_ok=True) - with open(path, 'w', encoding='utf-8') as f: + with open(path, "w", encoding="utf-8") as f: content = get_content(url) f.write(content) diff --git a/f-strings__formatted string literals__PEP 498/example_from__python_docs.py b/f-strings__formatted string literals__PEP 498/example_from__python_docs.py index 538af82db..e320daade 100644 --- a/f-strings__formatted string literals__PEP 498/example_from__python_docs.py +++ b/f-strings__formatted string literals__PEP 498/example_from__python_docs.py @@ -1,25 +1,28 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # # SOURCE: https://docs.python.org/3/reference/lexical_analysis.html#formatted-string-literals # -print('# Some examples of formatted string literals:') +import decimal +from datetime import datetime + + +print("# Some examples of formatted string literals:") name = "Fred" print(f"He said his name is {name!r}.") print(f"He said his name is {repr(name)}.") # repr() is equivalent to !r width = 10 precision = 4 -import decimal + value = decimal.Decimal("12.34567") print(f"result: {value:{width}.{precision}}") # nested fields -from datetime import datetime today = datetime(year=2017, month=1, day=27) print(f"{today:%b %d, %Y}") # using date format specifier @@ -27,28 +30,31 @@ print(f"{number:#0x}") # using integer format specifier print() -print('# A consequence of sharing the same syntax as regular string literals is that characters in the replacement ' - 'fields must not conflict with the quoting used in the outer formatted string literal:') +print( + "# A consequence of sharing the same syntax as regular string literals is that characters in the replacement " + "fields must not conflict with the quoting used in the outer formatted string literal:" +) -a = {'x': 'abd'} +a = {"x": "abd"} # print(f"abc {a["x"]} def") # error: outer string literal ended prematurely -print(f"abc {a['x']} def") # workaround: use different quoting +print(f"abc {a['x']} def") # workaround: use different quoting # print('# Backslashes are not allowed in format expressions and will raise an error:') # f"newline: {ord('\n')}" # raises SyntaxError print() -print('# To include a value in which a backslash escape is required, create a temporary variable.') -newline = ord('\n') +print( + "# To include a value in which a backslash escape is required, create a temporary variable." +) +newline = ord("\n") print(f"newline: {newline}") # # Formatted string literals cannot be used as docstrings, even if they do not include expressions. -def foo(): - f"Not a docstring" - -print(foo.__doc__ is None) +def foo() -> None: + f"Not a docstring" +print(foo.__doc__ is None) diff --git a/f-strings__formatted string literals__PEP 498/example_from__shultais.education.py b/f-strings__formatted string literals__PEP 498/example_from__shultais.education.py index 9e9fc6723..af3b00ca7 100644 --- a/f-strings__formatted string literals__PEP 498/example_from__shultais.education.py +++ b/f-strings__formatted string literals__PEP 498/example_from__shultais.education.py @@ -1,54 +1,60 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://shultais.education/blog/python-f-strings +from datetime import datetime as dt +from math import pi + + name = "Дмитрий" age = 25 print(f"Меня зовут {name} Мне {age} лет.") print() -print('# f-строки также поддерживают расширенное форматирование чисел:') -from math import pi +print("# f-строки также поддерживают расширенное форматирование чисел:") print(f"Значение числа pi: {pi:.2f}") print() -print('# С помощью f-строк можно форматировать дату без вызова метода strftime():') -from datetime import datetime as dt +print("# С помощью f-строк можно форматировать дату без вызова метода strftime():") + print(f"Текущее время {dt.now():%d.%m.%Y %H:%M}") now = dt.now() print(f"Текущее время {now:%d.%m.%Y %H:%M}") print() -print('# Они поддерживают базовые арифметические операции. Да, прямо в строках:') +print("# Они поддерживают базовые арифметические операции. Да, прямо в строках:") x = 10 y = 5 print(f"{x} x {y} / 2 = {x * y / 2}") print() -print('# Позволяют обращаться к значениям списков по индексу:') +print("# Позволяют обращаться к значениям списков по индексу:") planets = ["Меркурий", "Венера", "Земля", "Марс"] print(f"Мы живим не планете {planets[2]}") print() -print('# А также к элементам словаря по ключу:') +print("# А также к элементам словаря по ключу:") planet = {"name": "Земля", "radius": 6378000} print(f"Планета {planet['name']}. Радиус {planet['radius']/1000} км.") print() -print('# Причем вы можете использовать как строковые, так и числовые ключи. Точно также как в обычном Python коде:') -digits = {0: 'ноль', 'one': 'один'} +print( + "# Причем вы можете использовать как строковые, так и числовые ключи. " + "Точно также как в обычном Python коде:" +) +digits = {0: "ноль", "one": "один"} print(f"0 - {digits[0]}, 1 - {digits['one']}") print() -print('# Вы можете вызывать в f-строках методы объектов:') +print("# Вы можете вызывать в f-строках методы объектов:") name = "Дмитрий" print(f"Имя: {name.upper()}") print() -print('# А также вызывать функции:') +print("# А также вызывать функции:") print(f"13 / 3 = {round(13/3)}") diff --git a/f-strings__formatted string literals__PEP 498/my_example.py b/f-strings__formatted string literals__PEP 498/my_example.py index 2d3feafdd..b8e6168e4 100644 --- a/f-strings__formatted string literals__PEP 498/my_example.py +++ b/f-strings__formatted string literals__PEP 498/my_example.py @@ -1,7 +1,10 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" + + +import re print(f"My cool string is called {__author__}.") @@ -10,66 +13,66 @@ name = __author__ print(f"My cool string is called {name}.") print(f"My cool string is called {name.upper()}.") -print(f"My cool string is called {''.join(c.lower() if i % 2 else c.upper() for i, c in enumerate(__author__))}.") +print( + f"My cool string is called {''.join(c.lower() if i % 2 else c.upper() for i, c in enumerate(__author__))}." +) print(f"My cool string is called {name.replace('etr', '123')}.") print() print() a = 1 -f = f'{a + 1}' +f = f"{a + 1}" print(f"fff{f + f'{f}' + f'{a}'}") # fff221 print() def strange_1(text): - return ''.join(c.lower() if i % 2 else c.upper() for i, c in enumerate(text)) + return "".join(c.lower() if i % 2 else c.upper() for i, c in enumerate(text)) -print('Use function:') + +print("Use function:") print(f"My cool string is called {strange_1(name)}.") print() - -import re -print('Use regexp:') +print("Use regexp:") print(f"My cool string is called {re.sub(r'[a, e]', '0', name)}.") print() # Use class class Foo: - def __init__(self, text=''): + def __init__(self, text="") -> None: self.text = text def strange_2(self, text): - import re - return re.sub(r'[aeo]', ' ', text) + return re.sub(r"[aeo]", " ", text) @staticmethod - def strange_3(text): - return '"{}"'.format(text) + def strange_3(text) -> str: + return f'"{text}"' - def __format__(self, format_spec): + def __format__(self, format_spec) -> str: format_spec = format_spec.strip() if not format_spec: return self.__str__() - if format_spec == 'upper :)': + if format_spec == "upper :)": return self.__str__().upper() - if format_spec == 'revert': + if format_spec == "revert": return self.__str__()[::-1] return self.__str__() - def __str__(self): + def __str__(self) -> str: return f'' - def __repr__(self): + def __repr__(self) -> str: return f'' -print('Use class:') +print("Use class:") print(f"My cool string is called {Foo().strange_2(name)}.") print(f"My cool string is called {Foo.strange_3(name)}.") f = Foo(name) @@ -78,22 +81,24 @@ def __repr__(self): print(f"My cool string is called {f!r}.") print() -print('Use class __format__:') +print("Use class __format__:") print(f"My cool string is called {f:upper :)}.") print(f"My cool string is called {f: upper :)}.") print(f"My cool string is called {f:revert}.") print() -print('Loop:') +print("Loop:") for i in range(1, 10): - print(f' {i}: {i * i}') + print(f" {i}: {i * i}") print() -print('Lambda:') +print("Lambda:") print(f"My cool string is called {(lambda x: x.upper())(name)}.") print() -print('Raw and f-strings may be combined. For example, they could be used to build up regular expressions:') -header = 'Subject' -print(fr'{header}:\s+') +print( + "Raw and f-strings may be combined. For example, they could be used to build up regular expressions:" +) +header = "Subject" +print(rf"{header}:\s+") diff --git a/fable3_backup_save/fable3_backup_save.py b/fable3_backup_save/fable3_backup_save.py deleted file mode 100644 index 3192a615d..000000000 --- a/fable3_backup_save/fable3_backup_save.py +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -__author__ = 'ipetrash' - - -# Backups - - -if __name__ == '__main__': - dir_path = r'C:\Users\ipetrash\Saved Games\Lionhead Studios\Fable 3\1000100010001000' - - from datetime import datetime as dt - import os - - save_name = os.path.basename(dir_path) - - backup_file_name = '{}_{}.backup.zip'.format(save_name, dt.today().strftime('%Y%m%d_%H%M%S')) - backup_full_file_name = os.path.join(os.path.dirname(dir_path), backup_file_name) - - import zipfile - - with zipfile.ZipFile(backup_full_file_name, mode="w") as f: - for file in os.listdir(dir_path): - f.write(os.path.join(dir_path, file), file) diff --git a/fastapi__examples/README.md b/fastapi__examples/README.md new file mode 100644 index 000000000..948d174e1 --- /dev/null +++ b/fastapi__examples/README.md @@ -0,0 +1,8 @@ +# Example + +http://127.0.0.1:7777/docs +http://127.0.0.1:7777/redoc + +``` +python -m uvicorn main:app --reload --port=7777 +``` diff --git a/fastapi__examples/blog_from_stepic/.gitignore b/fastapi__examples/blog_from_stepic/.gitignore new file mode 100644 index 000000000..87811cb1f --- /dev/null +++ b/fastapi__examples/blog_from_stepic/.gitignore @@ -0,0 +1,8 @@ +venv +*__pycache__* +.DS_Store +.idea +*.db +.bak +.dat +.dir \ No newline at end of file diff --git a/fastapi__examples/blog_from_stepic/README.md b/fastapi__examples/blog_from_stepic/README.md new file mode 100644 index 000000000..34c1a27a1 --- /dev/null +++ b/fastapi__examples/blog_from_stepic/README.md @@ -0,0 +1,4 @@ +https://stepik.org/lesson/1186984/step/7?unit=1222202 + +http://127.0.0.1:8000/docs +http://127.0.0.1:8000/redoc diff --git a/fastapi__examples/blog_from_stepic/requirements.txt b/fastapi__examples/blog_from_stepic/requirements.txt new file mode 100644 index 000000000..3af9cb384 --- /dev/null +++ b/fastapi__examples/blog_from_stepic/requirements.txt @@ -0,0 +1,2 @@ +fastapi==0.111.0 +uvicorn[standard]==0.30.1 diff --git a/fastapi__examples/blog_from_stepic/run.bat b/fastapi__examples/blog_from_stepic/run.bat new file mode 100644 index 000000000..98e4066f8 --- /dev/null +++ b/fastapi__examples/blog_from_stepic/run.bat @@ -0,0 +1 @@ +uvicorn blog.main:app --reload --app-dir src/ \ No newline at end of file diff --git a/games/tetris/config.py b/fastapi__examples/blog_from_stepic/src/blog/__init__.py similarity index 55% rename from games/tetris/config.py rename to fastapi__examples/blog_from_stepic/src/blog/__init__.py index c525fd49c..f25732790 100644 --- a/games/tetris/config.py +++ b/fastapi__examples/blog_from_stepic/src/blog/__init__.py @@ -1,7 +1,4 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' - - -DEBUG = False \ No newline at end of file +__author__ = "ipetrash" diff --git a/fastapi__examples/blog_from_stepic/src/blog/domains.py b/fastapi__examples/blog_from_stepic/src/blog/domains.py new file mode 100644 index 000000000..0822e86af --- /dev/null +++ b/fastapi__examples/blog_from_stepic/src/blog/domains.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from dataclasses import dataclass + + +@dataclass +class User: + """Обычный пользователь""" + id: str + + +@dataclass +class Admin(User): + """Пользователь, наделенный правами администратора""" + username: str + password: str + + +@dataclass +class Article: + """Сущность статьи""" + id: str + title: str + content: str diff --git a/fastapi__examples/blog_from_stepic/src/blog/main.py b/fastapi__examples/blog_from_stepic/src/blog/main.py new file mode 100644 index 000000000..46bd9e226 --- /dev/null +++ b/fastapi__examples/blog_from_stepic/src/blog/main.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from fastapi import FastAPI +from blog.resources import router + + +def get_app(): + app = FastAPI() + + app.include_router(router) # <- вот тут мы зарегистрировали роутер + + return app + + +app = get_app() diff --git a/fastapi__examples/blog_from_stepic/src/blog/repositories.py b/fastapi__examples/blog_from_stepic/src/blog/repositories.py new file mode 100644 index 000000000..c2926d033 --- /dev/null +++ b/fastapi__examples/blog_from_stepic/src/blog/repositories.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import shelve +from abc import ABC, abstractmethod + +from blog.domains import Admin, Article, User + + +class UsersRepository(ABC): + """ + Абстрактный репозиторий для пользователей. + От него нужно наследоваться в случае, когда нужно сделать другое хранилище, старое переписывать не нужно. + """ + @abstractmethod + def get_users( + self, username: str | None = None, password: str | None = None + ) -> list[User]: + pass + + +class MemoryUsersRepository(UsersRepository): + """ + Реализация пользовательского хранилища в оперативной памяти. + Пользователи инициализируются во время инициализации репозитория + """ + def __init__(self) -> None: + self.users = [ + Admin( + id="29ae7ebf-4445-42f2-9548-a3a54f095220", # это uuid4 – уникальный идентификатор пользователя + username="admin", + password="Admin_4321!", + ) + ] + + def get_users( + self, username: str | None = None, password: str | None = None + ) -> list[User]: + """ + :param username: фильтр по логину + :param password: фильтр по паролю + :return: отфильтрованные пользователи + """ + filtered_users = [] # тут собираются отфильтрованные пользователи + for user in self.users: # перебираем всех пользователей и осталвяем только тех, кто прошел фильтры + if username is not None and user.username != username: + continue + if password is not None and user.password != password: + continue + filtered_users.append(user) + return filtered_users + + +class ArticlesRepository(ABC): + """ + Абстрактный репозиторий для статей. + Он содержит методы, которые нужно реализовать в случае если захочется сделать новую реализацию репозитория. + Принцип такой же как и у пользователей. + """ + @abstractmethod + def get_articles(self) -> list[Article]: + pass + + @abstractmethod + def create_article(self, article: Article): + pass + + +class ShelveArticlesRepository(ArticlesRepository): + def __init__(self) -> None: + self.db_name = "articles" + + def get_articles(self) -> list[Article]: + with shelve.open(self.db_name) as db: + return list(db.values()) + + def create_article(self, article: Article) -> None: + with shelve.open(self.db_name) as db: + db[article.id] = article diff --git a/fastapi__examples/blog_from_stepic/src/blog/resources.py b/fastapi__examples/blog_from_stepic/src/blog/resources.py new file mode 100644 index 000000000..d46096528 --- /dev/null +++ b/fastapi__examples/blog_from_stepic/src/blog/resources.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from fastapi import APIRouter, status, HTTPException +from fastapi.responses import HTMLResponse + +from blog.domains import Admin +from blog.schemas import ( + GetArticlesModel, + CreateArticleModel, + LoginModel, + GetArticleModel, + ErrorModel, +) +from blog import services +from blog.repositories import ShelveArticlesRepository, MemoryUsersRepository + + +router = APIRouter() # это роутер, он нужен для FastAPI, чтобы определять эндпоинты + + +@router.get("/", response_class=HTMLResponse) +def index() -> str: + return """ + +
+ + + """ + + +@router.get("/articles", response_model=GetArticlesModel) +def get_articles() -> GetArticlesModel: + # во всех представлениях всегда происходит одно и то же: + # 1. получили данные + # 2. вызвали сервисный метод и получили из него результат + # 3. вернули результат клиенту в виде ответа + articles = services.get_articles(articles_repository=ShelveArticlesRepository()) + return GetArticlesModel( + items=[ + GetArticleModel(id=article.id, title=article.title, content=article.content) + for article in articles + ] + ) + + +@router.post( + "/articles", + response_model=GetArticleModel, + # 201 статус код потому что мы создаем объект – стандарт HTTP + status_code=status.HTTP_201_CREATED, + # Это нужно для сваггера. Мы перечисляем ответы эндпоинта, чтобы получить четкую документацию. + responses={201: {"model": GetArticleModel}, 401: {"model": ErrorModel}, 403: {"model": ErrorModel}}, +) +def create_article( + article: CreateArticleModel, +# credentials – тело с логином и паролем. Обычно аутентификация выглядит сложнее, но для нашего случая пойдет и так. + credentials: LoginModel, +): + current_user = services.login( + username=credentials.username, + password=credentials.password, + users_repository=MemoryUsersRepository(), + ) + + # Это аутентификация + if not current_user: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, detail="Unauthorized user" + ) + # а это авторизация + if not isinstance(current_user, Admin): + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, detail="Forbidden resource" + ) + + article = services.create_article( + title=article.title, + content=article.content, + articles_repository=ShelveArticlesRepository(), + ) + + return GetArticleModel(id=article.id, title=article.title, content=article.content) diff --git a/fastapi__examples/blog_from_stepic/src/blog/schemas.py b/fastapi__examples/blog_from_stepic/src/blog/schemas.py new file mode 100644 index 000000000..f3cf72f94 --- /dev/null +++ b/fastapi__examples/blog_from_stepic/src/blog/schemas.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from pydantic import BaseModel + + +class GetArticleModel(BaseModel): + id: str + title: str + content: str + + +class GetArticlesModel(BaseModel): + items: list[GetArticleModel] + + +class CreateArticleModel(BaseModel): + title: str + content: str + + +class LoginModel(BaseModel): + username: str + password: str + + +class ErrorModel(BaseModel): + detail: str diff --git a/fastapi__examples/blog_from_stepic/src/blog/services.py b/fastapi__examples/blog_from_stepic/src/blog/services.py new file mode 100644 index 000000000..7e285ed62 --- /dev/null +++ b/fastapi__examples/blog_from_stepic/src/blog/services.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from uuid import uuid4 + +from blog.domains import Article, User +from blog.repositories import ArticlesRepository, UsersRepository + + +def get_articles(articles_repository: ArticlesRepository) -> list[Article]: + return articles_repository.get_articles() + + +def create_article( + title: str, content: str, articles_repository: ArticlesRepository +) -> Article: + article = Article(id=str(uuid4()), title=title, content=content) + articles_repository.create_article(article=article) + return article + + +def login( + username: str, password: str, users_repository: UsersRepository +) -> User | None: + users = users_repository.get_users(username=username, password=password) + if users: + return users[0] diff --git a/fastapi__examples/blog_from_stepic/src/tests/__init__.py b/fastapi__examples/blog_from_stepic/src/tests/__init__.py new file mode 100644 index 000000000..f25732790 --- /dev/null +++ b/fastapi__examples/blog_from_stepic/src/tests/__init__.py @@ -0,0 +1,4 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" diff --git a/fastapi__examples/logging/config.py b/fastapi__examples/logging/config.py new file mode 100644 index 000000000..408959e9b --- /dev/null +++ b/fastapi__examples/logging/config.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from typing import Any + + +# SOURCE: https://github.com/encode/uvicorn/blob/d79f285184404694c77f7ca649858e7488270cf7/uvicorn/config.py#L66 +# Added fmt="[%(asctime)s] " +LOGGING_CONFIG: dict[str, Any] = { + "version": 1, + "disable_existing_loggers": False, + "formatters": { + "default": { + "()": "uvicorn.logging.DefaultFormatter", + "fmt": "[%(asctime)s] %(levelprefix)s %(message)s", + "use_colors": None, + }, + "access": { + "()": "uvicorn.logging.AccessFormatter", + "fmt": '[%(asctime)s] %(levelprefix)s %(client_addr)s - "%(request_line)s" %(status_code)s', # noqa: E501 + }, + }, + "handlers": { + "default": { + "formatter": "default", + "class": "logging.StreamHandler", + "stream": "ext://sys.stderr", + }, + "access": { + "formatter": "access", + "class": "logging.StreamHandler", + "stream": "ext://sys.stdout", + }, + }, + "loggers": { + "uvicorn": {"handlers": ["default"], "level": "INFO", "propagate": False}, + "uvicorn.error": {"level": "INFO"}, + "uvicorn.access": {"handlers": ["access"], "level": "INFO", "propagate": False}, + }, +} + + +if __name__ == "__main__": + import yaml + yaml.dump( + LOGGING_CONFIG, + open("logging_config.yml", mode="w", encoding="utf-8"), + sort_keys=False, + ) diff --git a/fastapi__examples/logging/logging_config.yml b/fastapi__examples/logging/logging_config.yml new file mode 100644 index 000000000..a4fcd4d2d --- /dev/null +++ b/fastapi__examples/logging/logging_config.yml @@ -0,0 +1,32 @@ +version: 1 +disable_existing_loggers: false +formatters: + default: + (): uvicorn.logging.DefaultFormatter + fmt: '[%(asctime)s] %(levelprefix)s %(message)s' + use_colors: null + access: + (): uvicorn.logging.AccessFormatter + fmt: '[%(asctime)s] %(levelprefix)s %(client_addr)s - "%(request_line)s" %(status_code)s' +handlers: + default: + formatter: default + class: logging.StreamHandler + stream: ext://sys.stderr + access: + formatter: access + class: logging.StreamHandler + stream: ext://sys.stdout +loggers: + uvicorn: + handlers: + - default + level: INFO + propagate: false + uvicorn.error: + level: INFO + uvicorn.access: + handlers: + - access + level: INFO + propagate: false diff --git a/fastapi__examples/logging/main.py b/fastapi__examples/logging/main.py new file mode 100644 index 000000000..0f55b572b --- /dev/null +++ b/fastapi__examples/logging/main.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from fastapi import FastAPI +from config import LOGGING_CONFIG + + +app = FastAPI() + + +@app.get("/") +def index(): + return {"text": "Hello World!"} + + +if __name__ == "__main__": + from pathlib import Path + import uvicorn + + uvicorn.run( + app=f"{Path(__file__).stem}:app", + host="127.0.0.1", + port=8000, + log_config=LOGGING_CONFIG, + reload=True, + ) diff --git a/fastapi__examples/logging/run_with_log_config.bat b/fastapi__examples/logging/run_with_log_config.bat new file mode 100644 index 000000000..9c340920d --- /dev/null +++ b/fastapi__examples/logging/run_with_log_config.bat @@ -0,0 +1 @@ +uvicorn main:app --reload --port=7777 --log-config=logging_config.yml \ No newline at end of file diff --git a/fastapi__examples/market_from_stepic/.gitignore b/fastapi__examples/market_from_stepic/.gitignore new file mode 100644 index 000000000..466b5ad6c --- /dev/null +++ b/fastapi__examples/market_from_stepic/.gitignore @@ -0,0 +1,12 @@ +venv +*__pycache__* +.DS_Store +.idea +*.db +.bak +.dat +.dir + +SECRET_KEY.txt +database/ +database-test/ diff --git a/fastapi__examples/market_from_stepic/README.md b/fastapi__examples/market_from_stepic/README.md new file mode 100644 index 000000000..46460a6e9 --- /dev/null +++ b/fastapi__examples/market_from_stepic/README.md @@ -0,0 +1,18 @@ +https://stepik.org/lesson/1186984/step/8?unit=1222202 + +# Полезные ссылки + +http://127.0.0.1:7777/docs +http://127.0.0.1:7777/redoc + +# Авторизация + +Токен Bearer + +Используется POST запрос на "/api/v1/token". + +Пример: [src/examples/auth_to_api.py](./src/examples/auth_to_api.py) + +# База данных + +Как и [прошлый учебный проект](../blog_from_stepic) использует shelve - ~~для прикола~~ в образовательных целях. diff --git a/fastapi__examples/market_from_stepic/requirements.txt b/fastapi__examples/market_from_stepic/requirements.txt new file mode 100644 index 000000000..72cc557f6 --- /dev/null +++ b/fastapi__examples/market_from_stepic/requirements.txt @@ -0,0 +1,5 @@ +fastapi==0.111.0 +uvicorn[standard]==0.30.1 +PyJWT==2.13.0 +passlib==1.7.4 +bcrypt==4.0.1 \ No newline at end of file diff --git a/fastapi__examples/market_from_stepic/run.bat b/fastapi__examples/market_from_stepic/run.bat new file mode 100644 index 000000000..1cf083ea8 --- /dev/null +++ b/fastapi__examples/market_from_stepic/run.bat @@ -0,0 +1 @@ +uvicorn market.main:app --reload --app-dir=src/ --port=7777 \ No newline at end of file diff --git a/fastapi__examples/market_from_stepic/run_python12.bat b/fastapi__examples/market_from_stepic/run_python12.bat new file mode 100644 index 000000000..a48ffed0e --- /dev/null +++ b/fastapi__examples/market_from_stepic/run_python12.bat @@ -0,0 +1 @@ +C:\Users\ipetrash\AppData\Local\Programs\Python\Python312\python.exe -m uvicorn market.main:app --reload --app-dir=src/ --port=7777 \ No newline at end of file diff --git a/fastapi__examples/market_from_stepic/src/examples/auth_to_api.py b/fastapi__examples/market_from_stepic/src/examples/auth_to_api.py new file mode 100644 index 000000000..7e0b646c3 --- /dev/null +++ b/fastapi__examples/market_from_stepic/src/examples/auth_to_api.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import json +import urllib.request +import urllib.parse + + +URL = "http://127.0.0.1:7777" + + +def get_token(username: str, password: str) -> str: + login_data = { + "username": username, + "password": password, + } + + req = urllib.request.Request( + f"{URL}/api/v1/token", + method="POST", + data=urllib.parse.urlencode(login_data).encode("utf-8"), + ) + with urllib.request.urlopen(req) as rs: + rs_data = json.loads(rs.read().decode("utf-8")) + return rs_data["token"] + + +def get_users(token: str) -> dict[str, dict]: + req = urllib.request.Request( + f"{URL}/api/v1/users", + method="GET", + ) + req.add_header("Authorization", f"Bearer {token}") + + with urllib.request.urlopen(req) as rs: + return json.loads(rs.read().decode("utf-8")) + + +if __name__ == "__main__": + token = get_token(username="admin", password="Admin_4321!") + print(token) + # eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyOWFlN2ViZi00NDQ1LTQyZjItOTU0OC1hM2E1NGYwOTUyMjAiLCJyb2xlIjoiYWRtaW4iLCJleHAiOjE3MjE5MTg0MTh9.rTETSqUw0hRWJQFrBcHo9NiHwhfeUqZ0brDMuPp70xw + + print(get_users(token)) + # {'items': [{'id': '29ae7ebf-4445-42f2-9548-a3a54f095220', 'role': 'admin', 'username': 'admin'}]} diff --git a/fastapi__examples/market_from_stepic/src/market/__init__.py b/fastapi__examples/market_from_stepic/src/market/__init__.py new file mode 100644 index 000000000..f25732790 --- /dev/null +++ b/fastapi__examples/market_from_stepic/src/market/__init__.py @@ -0,0 +1,4 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" diff --git a/fastapi__examples/market_from_stepic/src/market/auth.py b/fastapi__examples/market_from_stepic/src/market/auth.py new file mode 100644 index 000000000..f38fc4da2 --- /dev/null +++ b/fastapi__examples/market_from_stepic/src/market/auth.py @@ -0,0 +1,130 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from dataclasses import dataclass, asdict +from datetime import datetime, timedelta, timezone +from typing import Any, Annotated + +import jwt +from fastapi import Depends, HTTPException, status +from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer + +from market import models +from market import services +from market.config import SECRET_KEY, ALGORITHM, ACCESS_TOKEN_EXPIRE_MINUTES + + +optional_security = HTTPBearer(auto_error=False) + + +not_authenticated_exception = HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Not authenticated", + headers={"WWW-Authenticate": "Bearer"}, +) +credentials_exception = HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Could not validate credentials", + headers={"WWW-Authenticate": "Bearer"}, +) +signature_has_expired_exception = HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Signature has expired", + headers={"WWW-Authenticate": "Bearer"}, +) + + +@dataclass +class TokenPayload: + sub: str + role: models.UserRoleEnum + exp: datetime | None = None + + +def create_access_token( + token: TokenPayload, + expires_delta: timedelta = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES), +) -> str: + data: dict[str, Any] = asdict(token) + data["exp"] = datetime.now(timezone.utc) + expires_delta + return jwt.encode(data, SECRET_KEY, algorithm=ALGORITHM) + + +def parse_access_token(token_data: str) -> TokenPayload: + try: + payload: dict[str, Any] = jwt.decode( + token_data, SECRET_KEY, algorithms=[ALGORITHM] + ) + return TokenPayload(**payload) + + except jwt.exceptions.ExpiredSignatureError: + raise signature_has_expired_exception + + except Exception: + raise credentials_exception + + +def get_current_user_or_none( + credentials: HTTPAuthorizationCredentials | None = Depends(optional_security), +) -> models.User | None: + if not credentials: + return + + token = credentials.credentials + token_data = parse_access_token(token) + + user: models.User = services.get_user(token_data.sub) + if not user: + raise credentials_exception + + # NOTE: Если было понижение в роли? :D + if token_data.role != user.role: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="Role in the token and in the database does not match", + ) + + return user + + +def get_current_user( + current_user: Annotated[models.User | None, Depends(get_current_user_or_none)], +) -> models.User: + if not current_user: + raise not_authenticated_exception + + return current_user + + +def get_current_user_admin( + current_user: Annotated[models.User, Depends(get_current_user)], +): + if current_user.role != models.UserRoleEnum.ADMIN: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="Allowed only for admin", + ) + return current_user + + +def get_current_user_manager_or_admin( + current_user: Annotated[models.User, Depends(get_current_user)], +): + if current_user.role not in [models.UserRoleEnum.MANAGER, models.UserRoleEnum.ADMIN]: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="Allowed only for admin and manager", + ) + return current_user + + +if __name__ == "__main__": + # TODO: в тесты + token_data = create_access_token( + TokenPayload(sub="dfsdfsdfsdfsdf", role=models.UserRoleEnum.ADMIN) + ) + print(token_data) + print(parse_access_token(token_data)) diff --git a/fastapi__examples/market_from_stepic/src/market/config.py b/fastapi__examples/market_from_stepic/src/market/config.py new file mode 100644 index 000000000..6478e4ee6 --- /dev/null +++ b/fastapi__examples/market_from_stepic/src/market/config.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import os + +from pathlib import Path + + +DIR: Path = Path(__file__).resolve().parent + +DB_DIR_NAME: Path = DIR / "database" +DB_DIR_NAME.mkdir(parents=True, exist_ok=True) + +DB_FILE_NAME: Path = DB_DIR_NAME / "db.shelve" + +DB_TEST_DIR_NAME: Path = DIR / "database-test" +DB_TEST_DIR_NAME.mkdir(parents=True, exist_ok=True) + +DB_TEST_FILE_NAME: Path = DB_TEST_DIR_NAME / "db.shelve" + +SECRET_KEY_FILE_NAME = DIR / "SECRET_KEY.txt" +SECRET_KEY = ( + os.environ.get("SECRET_KEY") + or SECRET_KEY_FILE_NAME.read_text("utf-8").strip() +) +if not SECRET_KEY: + raise Exception("SECRET_KEY must be set in the SECRET_KEY.txt file or in an environment variable") + +ALGORITHM: str = "HS256" +ACCESS_TOKEN_EXPIRE_MINUTES: int = 60 * 24 * 30 # 30 days diff --git a/fastapi__examples/market_from_stepic/src/market/db.py b/fastapi__examples/market_from_stepic/src/market/db.py new file mode 100644 index 000000000..8a164a2ef --- /dev/null +++ b/fastapi__examples/market_from_stepic/src/market/db.py @@ -0,0 +1,509 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import functools +import threading +import shelve + +from datetime import datetime +from pathlib import Path +from typing import Any +from uuid import uuid4 + +from market import models +from market.config import DB_FILE_NAME +from market.security import get_password_hash + + +class DbException(Exception): + pass + + +class NotFoundException(DbException): + pass + + +class InvalidException(DbException): + pass + + +class InvalidOrderStatusException(InvalidException): + def __init__(self, prev_status: models.StatusOrderEnum, new_status: models.StatusOrderEnum) -> None: + super().__init__(f"Unable to change order status {prev_status.value!r} to {new_status.value!r}") + + +class DB: + KEY_USERS: str = "users" + KEY_PRODUCTS: str = "products" + KEY_SHOPPING_CARTS: str = "shopping_carts" + KEY_ORDERS: str = "orders" + KEY_INDEXES: str = "[indexes]" + + _mutex = threading.RLock() + + def session(*decorator_args, **decorator_kwargs): + def actual_decorator(func): + @functools.wraps(func) + def wrapped(self, *args, **kwargs): + with self._mutex: + has_db: bool = self.db is not None + try: + if not has_db: + self.db = shelve.open(self.file_name, writeback=True) + return func(self, *args, **kwargs) + finally: + if not has_db and self.db is not None: + self.db.close() + self.db = None + return wrapped + return actual_decorator + + def lock(*decorator_args, **decorator_kwargs): + def actual_decorator(func): + @functools.wraps(func) + def wrapped(self, *args, **kwargs): + with self._mutex: + return func(self, *args, **kwargs) + return wrapped + return actual_decorator + + @session() + def get_value(self, name: str, default: Any = None) -> Any: + if not name: + return dict(self.db) + + if name not in self.db: + return default + return self.db.get(name) + + @session() + def set_value(self, name: str, value: Any) -> None: + self.db[name] = value + + def __init__(self, file_name: Path | str = DB_FILE_NAME) -> None: + self.file_name: str = str(file_name) + self.db: shelve.Shelf | None = None + + self._do_init_db_objects() + + def _generate_id(self) -> str: + return str(uuid4()) + + @lock() + def rebuild_indexes(self, clear: bool = True) -> None: + indexes: dict[str, dict[str, str]] = self.get_value(self.KEY_INDEXES, default=dict()) + + if self.KEY_USERS not in indexes: + indexes[self.KEY_USERS] = dict() + + if self.KEY_PRODUCTS not in indexes: + indexes[self.KEY_PRODUCTS] = dict() + + if clear: + indexes.clear() + + indexes[self.KEY_USERS] = {obj.username: obj.id for obj in self.get_users()} + indexes[self.KEY_PRODUCTS] = {obj.name: obj.id for obj in self.get_products()} + + self.set_value(self.KEY_INDEXES, indexes) + + @lock() + def add_index(self, table: str, key: str, id: str) -> None: + indexes: dict[str, dict[str, str]] = self.get_value(self.KEY_INDEXES) + indexes[table][key] = id + + self.set_value(self.KEY_INDEXES, indexes) + + @lock() + def remove_index(self, table: str, key: str) -> None: + indexes: dict[str, dict[str, str]] = self.get_value(self.KEY_INDEXES) + indexes[table].pop(key) + + self.set_value(self.KEY_INDEXES, indexes) + + @lock() + def get_id_from_index(self, table: str, key: str) -> str | None: + indexes: dict[str, dict[str, str]] = self.get_value(self.KEY_INDEXES) + return indexes[table].get(key) + + @lock() + def _do_init_db_objects(self) -> None: + self.rebuild_indexes(clear=False) + + if self.KEY_USERS not in self.get_value(""): + self.set_value(self.KEY_USERS, dict()) + + if self.KEY_PRODUCTS not in self.get_value(""): + self.set_value(self.KEY_PRODUCTS, dict()) + + if self.KEY_SHOPPING_CARTS not in self.get_value(""): + self.set_value(self.KEY_SHOPPING_CARTS, dict()) + + if self.KEY_ORDERS not in self.get_value(""): + self.set_value(self.KEY_ORDERS, dict()) + + if not self.get_value(self.KEY_USERS): + self.create_user( + role=models.UserRoleEnum.ADMIN, + username="admin", + password="Admin_4321!", + id="29ae7ebf-4445-42f2-9548-a3a54f095220", + ) + + if not self.get_value(self.KEY_PRODUCTS): + self.create_product( + name="Coca Cola 1л.", + price_minor=8000, + description="Газированный напиток", + ) + self.create_product( + name="Coca Cola 2л.", + price_minor=13500, + description="Газированный напиток", + ) + self.create_product( + name="Pepsi 1л.", + price_minor=8000, + description="Газированный напиток", + ) + self.create_product( + name="Сникерс", + price_minor=4000, + description="Шоколадный батончик", + ) + + @lock() + def get_users( + self, + username: str | None = None, + ) -> list[models.UserInDb]: + """ + :param username: фильтр по логину + + :return: отфильтрованные пользователи + """ + + filtered_users = [] # Тут собираются отфильтрованные пользователи + + # Перебираем всех пользователей и оставляем только тех, кто прошел фильтры + for user in self.get_value(self.KEY_USERS).values(): + if username is not None and user.username != username: + continue + filtered_users.append(user) + + return filtered_users + + @lock() + def get_user(self, id: str, check_exists: bool = False) -> models.UserInDb | None: + obj = self.get_value(self.KEY_USERS).get(id) + if obj is None and check_exists: + raise NotFoundException(f"User #{id} not found!") + return obj + + @lock() + def get_user_by_username( + self, + username: str, + check_exists: bool = False, + ) -> models.UserInDb | None: + obj_id: str | None = self.get_id_from_index(self.KEY_USERS, username) + return self.get_user( + id=obj_id, + check_exists=check_exists, + ) + + @lock() + def create_user( + self, + role: models.UserRoleEnum, + username: str, + password: str, + id: str | None = None, + ) -> models.UserInDb: + obj_id: str | None = self.get_id_from_index(self.KEY_USERS, username) + if obj_id: + raise DbException(f"Cannot create user {username!r} - this nickname is taken") + + obj = models.UserInDb( + id=id if id else self._generate_id(), + role=role, + username=username, + hashed_password=get_password_hash(password), + ) + self.add_index(self.KEY_USERS, username, obj.id) + + users = self.get_value(self.KEY_USERS) + users[obj.id] = obj + self.set_value(self.KEY_USERS, users) + return obj + + @lock() + def create_product( + self, + name: str, + price_minor: int, + description: str, + ) -> models.Product: + obj_id: str | None = self.get_id_from_index(self.KEY_PRODUCTS, name) + if obj_id: + raise DbException(f"Cannot create product {name!r} - this name is taken") + + obj = models.Product( + id=self._generate_id(), + name=name, + price_minor=price_minor, + description=description, + ) + self.add_index(self.KEY_PRODUCTS, name, obj.id) + + products = self.get_value(self.KEY_PRODUCTS) + products[obj.id] = obj + self.set_value(self.KEY_PRODUCTS, products) + return obj + + @lock() + def update_product( + self, + id: str, + name: str | None = None, + price_minor: int | None = None, + description: str | None = None, + ) -> None: + product = self.get_product(id, check_exists=True) + + if name is not None: + product.name = name + + if price_minor is not None: + product.price_minor = price_minor + + if description is not None: + product.description = description + + products = self.get_value(self.KEY_PRODUCTS) + products[id] = product + + self.set_value(self.KEY_PRODUCTS, products) + + @lock() + def get_products(self) -> list[models.Product]: + return list(self.get_value(self.KEY_PRODUCTS).values()) + + @lock() + def get_product(self, id: str, check_exists: bool = False) -> models.Product | None: + obj = self.get_value(self.KEY_PRODUCTS).get(id) + if obj is None and check_exists: + raise NotFoundException(f"Product #{id} not found!") + return obj + + @lock() + def create_shopping_cart(self, product_ids: list[str]) -> models.ShoppingCart: + obj = models.ShoppingCart( + id=self._generate_id(), + product_ids=product_ids, + ) + shopping_carts = self.get_value(self.KEY_SHOPPING_CARTS) + shopping_carts[obj.id] = obj + self.set_value(self.KEY_SHOPPING_CARTS, shopping_carts) + return obj + + @lock() + def delete_shopping_cart(self, shopping_cart_id: str) -> None: + # Проверка наличия + self.get_shopping_cart(shopping_cart_id, check_exists=True) + + shopping_carts: dict = self.get_value(self.KEY_SHOPPING_CARTS) + shopping_carts.pop(shopping_cart_id) + + self.set_value(self.KEY_SHOPPING_CARTS, shopping_carts) + + @lock() + def update_shopping_cart( + self, + shopping_cart_id: str, + product_ids: list[str], + ) -> None: + shopping_cart: models.ShoppingCart = self.get_shopping_cart( + shopping_cart_id, check_exists=True + ) + shopping_cart.product_ids = product_ids + + shopping_carts = self.get_value(self.KEY_SHOPPING_CARTS) + shopping_carts[shopping_cart_id] = shopping_cart + + self.set_value(self.KEY_SHOPPING_CARTS, shopping_carts) + + @lock() + def get_shopping_carts(self) -> list[models.ShoppingCart]: + return list(self.get_value(self.KEY_SHOPPING_CARTS).values()) + + @lock() + def get_shopping_cart( + self, + id: str, + check_exists: bool = False, + ) -> models.ShoppingCart | None: + obj = self.get_value(self.KEY_SHOPPING_CARTS).get(id) + if obj is None and check_exists: + raise NotFoundException(f"Shopping cart #{id} not found!") + return obj + + @lock() + def add_product_in_shopping_cart( + self, + shopping_cart_id: str, + product_id: str, + ) -> None: + shopping_cart: models.ShoppingCart = self.get_shopping_cart( + shopping_cart_id, check_exists=True + ) + + # Проверка наличия + self.get_product(product_id, check_exists=True) + + shopping_cart.product_ids.append(product_id) + self.update_shopping_cart(shopping_cart_id, shopping_cart.product_ids) + + @lock() + def remove_product_from_shopping_cart( + self, + shopping_cart_id: str, + product_id: str, + ) -> None: + shopping_cart: models.ShoppingCart = self.get_shopping_cart( + shopping_cart_id, check_exists=True + ) + + # Проверка наличия + self.get_product(product_id, check_exists=True) + + if product_id in shopping_cart.product_ids: + shopping_cart.product_ids.remove(product_id) + + self.update_shopping_cart(shopping_cart_id, shopping_cart.product_ids) + + @lock() + def create_order( + self, + email: str, + shopping_cart_id: str, + ) -> models.Order: + # Проверка наличия + self.get_shopping_cart(shopping_cart_id, check_exists=True) + + obj = models.Order( + id=self._generate_id(), + email=email, + shopping_cart_id=shopping_cart_id, + ) + orders = self.get_value(self.KEY_ORDERS) + orders[obj.id] = obj + self.set_value(self.KEY_ORDERS, orders) + return obj + + @lock() + def update_order( + self, + id: str, + email: str | None = None, + shopping_cart_id: str | None = None, + status: models.StatusOrderEnum | None = None, + cancel_reason: str | None = None, + ): + order = self.get_order(id, check_exists=True) + + complete_statuses = (models.StatusOrderEnum.FINISHED, models.StatusOrderEnum.CANCELED) + if order.status in complete_statuses: + raise DbException(f"It is forbidden to update an order with status {order.status.value!r}") + + if email is not None: + order.email = email + + if shopping_cart_id is not None: + # Проверка наличия + self.get_shopping_cart(shopping_cart_id, check_exists=True) + + order.shopping_cart_id = shopping_cart_id + + if status is not None and status != order.status: + invalid_status_exception = InvalidOrderStatusException(order.status, status) + + match order.status: + case models.StatusOrderEnum.CREATED: + pass + case models.StatusOrderEnum.IN_PROCESSED: + # Если текущий статус "в процессе", то следующий может быть или отмена, или завершение + if status not in complete_statuses: + raise invalid_status_exception + case models.StatusOrderEnum.CANCELED: + raise invalid_status_exception + case models.StatusOrderEnum.FINISHED: + raise invalid_status_exception + case _: + raise InvalidException(f"Unsupported status {status.value!r}!") + + order.status = status + + if status in complete_statuses: + order.closed_date = datetime.now() + + if cancel_reason is not None: + order.cancel_reason = cancel_reason + + orders = self.get_value(self.KEY_ORDERS) + orders[id] = order + + self.set_value(self.KEY_ORDERS, orders) + + @lock() + def get_orders(self) -> list[models.Order]: + return list(self.get_value(self.KEY_ORDERS).values()) + + @lock() + def get_order( + self, + id: str, + check_exists: bool = False, + ) -> models.Order | None: + obj = self.get_value(self.KEY_ORDERS).get(id) + if obj is None and check_exists: + raise NotFoundException(f"Order #{id} not found!") + return obj + + +db = DB() + + +if __name__ == "__main__": + # db.rebuild_indexes() + print(db.get_value("")) + + # # TODO: В тесты + # from market.config import DB_TEST_FILE_NAME + # db_test = DB(file_name=DB_TEST_FILE_NAME) + # + # value = db_test.get_value("counter", default=1) + # print(f"Counter: {value}") + # + # def inc_counter(): + # value = db_test.get_value("counter", default=1) + # db_test.set_value("counter", value + 1) + # + # inc_counter() + + # TODO: не рабочий вариант, нужно использовать методы самого DB + # current_value = db_test.get_value("counter", default=1) + # + # max_workers = 5 + # number = 50 + # expected_value = current_value + number + # + # import concurrent.futures + # with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor: + # futures = [executor.submit(inc_counter) for _ in range(number)] + # concurrent.futures.wait(futures) + # + # print(expected_value, db_test.get_value("counter", default=1)) diff --git a/fastapi__examples/market_from_stepic/src/market/main.py b/fastapi__examples/market_from_stepic/src/market/main.py new file mode 100644 index 000000000..2e1ecb5a4 --- /dev/null +++ b/fastapi__examples/market_from_stepic/src/market/main.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from fastapi import FastAPI, Request, status +from fastapi.responses import HTMLResponse, JSONResponse + +from market.db import DbException, NotFoundException +from market.resources import router + + +app = FastAPI() + + +@app.exception_handler(DbException) +async def unicorn_exception_handler(_: Request, exc: DbException): + status_code = ( + status.HTTP_404_NOT_FOUND + if isinstance(exc, NotFoundException) + else status.HTTP_400_BAD_REQUEST + ) + return JSONResponse( + status_code=status_code, + content={"detail": str(exc)}, + ) + + +@app.get("/", response_class=HTMLResponse) +def index() -> str: + return """\ + + + + + + + + +
+ + + + + """ + + +app.include_router(router, prefix="/api/v1") diff --git a/fastapi__examples/market_from_stepic/src/market/models.py b/fastapi__examples/market_from_stepic/src/market/models.py new file mode 100644 index 000000000..5eb290742 --- /dev/null +++ b/fastapi__examples/market_from_stepic/src/market/models.py @@ -0,0 +1,183 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import enum + +from dataclasses import dataclass, field, fields +from datetime import datetime +from typing import Any + + +# TODO: аннотации +def create_from(cls, other): + data: dict[str, Any] = dict.fromkeys( + f.name for f in fields(cls) + ) + for f in fields(other): + name = f.name + if name in data: + data[name] = getattr(other, name) + + return cls(**data) + + +class UserRoleEnum(enum.StrEnum): + MANAGER = enum.auto() + ADMIN = enum.auto() + + +class StatusOrderEnum(enum.StrEnum): + CREATED = enum.auto() + IN_PROCESSED = enum.auto() + FINISHED = enum.auto() + CANCELED = enum.auto() + + +@dataclass +class IdBasedObj: + id: str + + +@dataclass +class User(IdBasedObj): + role: UserRoleEnum + username: str = None + + +@dataclass +class UserInDb(User): + hashed_password: str = None + + +@dataclass +class LoginResponse: + token: str + user: User + + +@dataclass +class CreateUser: + username: str + password: str + role: UserRoleEnum + + +@dataclass +class Users: + items: list[User] + + +@dataclass +class CreateProduct: + """Товар""" + + name: str + price_minor: int # Копейки + description: str + + +@dataclass +class Product(CreateProduct, IdBasedObj): + pass + + +@dataclass +class UpdateProduct: + """Товар""" + + name: str | None = None + price_minor: int | None = None # Копейки + description: str | None = None + + +@dataclass +class Product(IdBasedObj): + """Товар""" + + name: str + price_minor: int # Копейки + description: str + + +@dataclass +class Products: + items: list[Product] + + +@dataclass +class ProductsBasedObj: + product_ids: list[str] = field(default_factory=list) + + +@dataclass +class CreateShoppingCart(ProductsBasedObj): + """Корзина с товарами""" + + name: str = "" + + +@dataclass +class ShoppingCart(ProductsBasedObj, IdBasedObj): + """Корзина с товарами""" + + +@dataclass +class ShoppingCarts: + items: list[ShoppingCart] + + +@dataclass +class BaseOrder: + """Заказ""" + + email: str + shopping_cart_id: str + + +@dataclass +class Order(BaseOrder, IdBasedObj): + """Заказ""" + + status: StatusOrderEnum = StatusOrderEnum.CREATED + created_date: datetime = datetime.now() + cancel_reason: str | None = None + closed_date: datetime | None = None + + +@dataclass +class UpdateOrder: + """Заказ""" + + email: str | None = None + shopping_cart_id: str | None = None + status: StatusOrderEnum | None = None + cancel_reason: str | None = None + + +@dataclass +class SubmitOrder: + """Заказ""" + + status: StatusOrderEnum + + +@dataclass +class Orders: + items: list[Order] + + +# TODO: в тесты +# user1 = UserInDb("123", UserRoleEnum.ADMIN, "dfsf", "dddd") +# print(user1) +# user2 = User("123", UserRoleEnum.ADMIN, "dfsf") +# print(user2) +# # print(User(**user1)) +# print() +# +# print(*fields(user1), sep="\n") +# print(fields(User)) +# +# print(create_from(User, user1)) diff --git a/fastapi__examples/market_from_stepic/src/market/resources.py b/fastapi__examples/market_from_stepic/src/market/resources.py new file mode 100644 index 000000000..cb3467622 --- /dev/null +++ b/fastapi__examples/market_from_stepic/src/market/resources.py @@ -0,0 +1,242 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from typing import Annotated + +from fastapi import APIRouter, status, Depends, HTTPException +from fastapi.security import OAuth2PasswordRequestForm + +from market import auth +from market import models +from market import services +from market.security import verify_password + + +router = APIRouter() + + +@router.post("/token") +def login_for_access_token( + credentials: Annotated[OAuth2PasswordRequestForm, Depends()], +) -> models.LoginResponse: + exception_400 = HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Incorrect username or password", + ) + + if not credentials.username or not credentials.password: + raise exception_400 + + user: models.UserInDb = services.get_user_by_username(credentials.username) + if verify_password(credentials.password, user.hashed_password): + # Generate a JWT token + access_token = auth.create_access_token( + token=auth.TokenPayload(user.id, user.role), + ) + + # Return the access token and user details + return models.LoginResponse( + token=access_token, + user=models.User( + id=user.id, + username=user.username, + role=user.role, + ), + ) + + raise exception_400 + + +@router.get("/users/me/") +def read_users_me( + current_user: Annotated[models.User, Depends(auth.get_current_user)], +) -> models.User: + return current_user + + +@router.get("/users") +def get_users( + _: Annotated[models.Users, Depends(auth.get_current_user_admin)], +) -> models.Users: + return services.get_users() + + +@router.get("/user/{id}") +def get_user(id: str) -> models.User: + return services.get_user(id) + + +@router.post( + "/users", + status_code=status.HTTP_201_CREATED, +) +def create_user( + user: models.CreateUser, + _: Annotated[models.User, Depends(auth.get_current_user_admin)], +) -> models.IdBasedObj: + return services.create_user( + role=user.role, + username=user.username, + password=user.password, + ) + + +@router.get("/products") +def get_products() -> models.Products: + return services.get_products() + + +@router.get("/product/{id}") +def get_product(id: str) -> models.Product: + return services.get_product(id) + + +@router.patch("/product/{id}") +def update_product( + id: str, + other: models.UpdateProduct, + current_user: Annotated[models.User, Depends(auth.get_current_user_manager_or_admin)], +) -> models.Product: + if current_user.role == models.UserRoleEnum.MANAGER: + # У менеджера нет прав на переименование продукта + if other.name is not None: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="No rights to edit the name field", + ) + + services.update_product( + id=id, + name=other.name, + price_minor=other.price_minor, + description=other.description, + ) + + return services.get_product(id) + + +@router.post( + "/products", + status_code=status.HTTP_201_CREATED, +) +def create_product( + product: models.CreateProduct, + _: Annotated[models.User, Depends(auth.get_current_user_admin)], +) -> models.IdBasedObj: + return services.create_product( + name=product.name, + price_minor=product.price_minor, + description=product.description, + ) + + +@router.get("/shopping-carts") +def get_shopping_carts( + _: Annotated[models.User, Depends(auth.get_current_user_manager_or_admin)], +) -> models.ShoppingCarts: + return services.get_shopping_carts() + + +@router.get("/shopping-cart/{id}") +def get_shopping_cart(id: str) -> models.ShoppingCart: + return services.get_shopping_cart(id) + + +@router.post("/shopping-cart/{id}/products") +def add_product_in_shopping_cart(id: str, add_to: models.ProductsBasedObj) -> models.ShoppingCart: + for product_id in add_to.product_ids: + services.add_product_in_shopping_cart( + shopping_cart_id=id, product_id=product_id + ) + + return services.get_shopping_cart(id) + + +@router.delete("/shopping-cart/{id}/products") +def remove_product_from_shopping_cart(id: str, remove_from: models.ProductsBasedObj) -> models.ShoppingCart: + for product_id in remove_from.product_ids: + services.remove_product_from_shopping_cart( + shopping_cart_id=id, product_id=product_id + ) + + return services.get_shopping_cart(id) + + +@router.post( + "/shopping-carts", + status_code=status.HTTP_201_CREATED, +) +def create_shopping_cart( + shopping_cart: models.CreateShoppingCart, +) -> models.IdBasedObj: + return services.create_shopping_cart( + product_ids=shopping_cart.product_ids, + ) + + +@router.delete("/shopping-cart/{id}", status_code=status.HTTP_204_NO_CONTENT) +def delete_shopping_cart(id: str) -> None: + services.delete_shopping_cart(id) + + +@router.get("/orders") +def get_orders( + _: Annotated[models.User, Depends(auth.get_current_user_manager_or_admin)], +) -> models.Orders: + return services.get_orders() + + +@router.get("/order/{id}") +def get_order(id: str) -> models.Order: + return services.get_order(id) + + +@router.post( + "/orders", + status_code=status.HTTP_201_CREATED, +) +def create_order( + order: models.BaseOrder, +) -> models.IdBasedObj: + return services.create_order( + email=order.email, + shopping_cart_id=order.shopping_cart_id, + ) + + +@router.patch("/order/{id}") +def update_order( + id: str, + other: models.UpdateOrder, + current_user: Annotated[models.User | None, Depends(auth.get_current_user_or_none)] = None, +) -> models.Order: + services.update_order( + id=id, + email=other.email, + shopping_cart_id=other.shopping_cart_id, + status=other.status, + cancel_reason=other.cancel_reason, + context_user=current_user, + ) + + return services.get_order(id) + + +@router.post("/order/{id}/submit") +def submit_order( + id: str, + other: models.SubmitOrder, + current_user: Annotated[ + models.User | None, Depends(auth.get_current_user_or_none) + ] = None, +) -> models.Order: + services.submit_order( + id=id, + status=other.status, + context_user=current_user, + ) + + return services.get_order(id) diff --git a/fastapi__examples/market_from_stepic/src/market/security.py b/fastapi__examples/market_from_stepic/src/market/security.py new file mode 100644 index 000000000..2f2689b2b --- /dev/null +++ b/fastapi__examples/market_from_stepic/src/market/security.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from passlib.context import CryptContext + + +pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") + + +def verify_password(plain_password, hashed_password): + return pwd_context.verify(plain_password, hashed_password) + + +def get_password_hash(password): + return pwd_context.hash(password) diff --git a/fastapi__examples/market_from_stepic/src/market/services.py b/fastapi__examples/market_from_stepic/src/market/services.py new file mode 100644 index 000000000..e0f838b71 --- /dev/null +++ b/fastapi__examples/market_from_stepic/src/market/services.py @@ -0,0 +1,186 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import fastapi + +from market import models +from market.db import db + + +def get_users() -> models.Users: + return models.Users( + items=[models.create_from(models.User, user) for user in db.get_users()] + ) + + +def get_user(id: str) -> models.User: + return models.create_from( + models.User, + db.get_user(id, check_exists=True), + ) + + +def get_user_by_username(username: str) -> models.UserInDb: + return db.get_user_by_username(username, check_exists=True) + + +def create_user( + role: models.UserRoleEnum, + username: str, + password: str, +) -> models.IdBasedObj: + user = db.create_user( + role=role, + username=username, + password=password, + ) + return models.IdBasedObj(id=user.id) + + +def get_products() -> models.Products: + return models.Products(items=db.get_products()) + + +def get_product(id: str) -> models.Product: + return db.get_product(id, check_exists=True) + + +def create_product( + name: str, + price_minor: int, # Копейки + description: str, +) -> models.IdBasedObj: + product = db.create_product( + name=name, + price_minor=price_minor, + description=description, + ) + return models.IdBasedObj(id=product.id) + + +def update_product( + id: str, + name: str | None = None, + price_minor: int | None = None, # Копейки + description: str | None = None, +) -> None: + db.update_product( + id=id, + name=name, + price_minor=price_minor, + description=description, + ) + + +def create_shopping_cart(product_ids: list[str] = None) -> models.IdBasedObj: + if product_ids is None: + product_ids = [] + + shopping_cart = db.create_shopping_cart( + product_ids=product_ids, + ) + return models.IdBasedObj(id=shopping_cart.id) + + +def delete_shopping_cart(shopping_cart_id: str) -> None: + db.delete_shopping_cart(shopping_cart_id) + + +def add_product_in_shopping_cart( + shopping_cart_id: str, + product_id: str, +) -> None: + db.add_product_in_shopping_cart( + shopping_cart_id=shopping_cart_id, + product_id=product_id, + ) + + +def remove_product_from_shopping_cart( + shopping_cart_id: str, + product_id: str, +) -> None: + db.remove_product_from_shopping_cart( + shopping_cart_id=shopping_cart_id, + product_id=product_id, + ) + + +def get_shopping_carts() -> models.ShoppingCarts: + return models.ShoppingCarts(items=db.get_shopping_carts()) + + +def get_shopping_cart(id: str) -> models.ShoppingCart: + return db.get_shopping_cart(id, check_exists=True) + + +def get_orders() -> models.Orders: + return models.Orders(items=db.get_orders()) + + +def get_order(id: str) -> models.Order: + return db.get_order(id, check_exists=True) + + +def create_order( + email: str, + shopping_cart_id: str, +) -> models.IdBasedObj: + obj = db.create_order( + email=email, + shopping_cart_id=shopping_cart_id, + ) + return models.IdBasedObj(id=obj.id) + + +def update_order( + id: str, + email: str | None = None, + shopping_cart_id: str | None = None, + status: models.StatusOrderEnum | None = None, + cancel_reason: str | None = None, + context_user: models.User | None = None, +) -> None: + # Клиент не может сам запускать выполнение заказа или завершать его + if context_user is None and status in (models.StatusOrderEnum.IN_PROCESSED, models.StatusOrderEnum.FINISHED): + raise fastapi.HTTPException( + status_code=fastapi.status.HTTP_403_FORBIDDEN, + detail=f"No rights to set status {status.value!r}", + ) + + cancel_reason: str | None = cancel_reason + + # Если причина отмены не задана и статус отмена + if cancel_reason is None and status == models.StatusOrderEnum.CANCELED: + user_role: models.UserRoleEnum | None = context_user.role if context_user else None + match user_role: + case models.UserRoleEnum.ADMIN: + cancel_reason = f"Canceled by admin {context_user.username!r}" + case models.UserRoleEnum.MANAGER: + cancel_reason = f"Canceled by manager {context_user.username!r}" + case _: + cancel_reason = "Canceled by user" + + db.update_order( + id=id, + email=email, + shopping_cart_id=shopping_cart_id, + status=status, + cancel_reason=cancel_reason, + ) + + +def submit_order( + id: str, + status: models.StatusOrderEnum, + context_user: models.User | None = None, +) -> None: + update_order( + id=id, + status=status, + context_user=context_user, + ) + diff --git a/fastapi__examples/market_from_stepic/src/tests/__init__.py b/fastapi__examples/market_from_stepic/src/tests/__init__.py new file mode 100644 index 000000000..f25732790 --- /dev/null +++ b/fastapi__examples/market_from_stepic/src/tests/__init__.py @@ -0,0 +1,4 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" diff --git a/fastapi__examples/running-programmatically.py b/fastapi__examples/running-programmatically.py new file mode 100644 index 000000000..6d558312a --- /dev/null +++ b/fastapi__examples/running-programmatically.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from fastapi import FastAPI + + +app = FastAPI() + + +@app.get("/") +def index(): + return {"text": "Hello World!"} + + +if __name__ == "__main__": + from pathlib import Path + import uvicorn + + uvicorn.run( + app=f"{Path(__file__).stem}:app", + host="127.0.0.1", + port=8000, + reload=True, + ) diff --git a/fastapi__examples/show_my_ip.py b/fastapi__examples/show_my_ip.py new file mode 100644 index 000000000..90434e702 --- /dev/null +++ b/fastapi__examples/show_my_ip.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from dataclasses import dataclass +from fastapi import FastAPI, Request + + +@dataclass +class Ip: + host: str + + +app = FastAPI() + + +@app.get("/") +def index(request: Request) -> str: + return request.client.host + + +@app.get("/json") +def index(request: Request) -> Ip: + return Ip( + host=request.client.host, + ) + + +if __name__ == "__main__": + from pathlib import Path + import uvicorn + + uvicorn.run( + app=f"{Path(__file__).stem}:app", + host="127.0.0.1", + port=8000, + reload=True, + ) diff --git a/fb2__parsing/common.py b/fb2__parsing/common.py index 14df1fae1..dba5d3a0a 100644 --- a/fb2__parsing/common.py +++ b/fb2__parsing/common.py @@ -1,34 +1,13 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -import os -import sys - -from typing import Optional -from pathlib import Path - -sys.path.append(str(Path(__file__).resolve().parent.parent)) -from human_byte_size import sizeof_fmt - - -def get_file_name_from_binary(binary_id: str, binary_content_type: str) -> str: - fmt = os.path.splitext(binary_id)[-1].lower() - - # Если формат файла есть, хорошо - if fmt in ['.png', '.jpg', '.jpeg']: - return binary_id - - content_type = binary_content_type.split('/')[-1] - return binary_id + '.' + {'jpeg': 'jpg', 'png': 'png'}[content_type] - - -def get_attribute_value_by_local_name(node, attr_name: str) -> Optional[str]: +def get_attribute_value_by_local_name(node, attr_name: str) -> str | None: for name, value in node.attrs.items(): # Получаем имя атрибута - name = name.split(':')[-1] + name = name.split(":")[-1] if name == attr_name: return value diff --git a/fb2__parsing/extract_cover_page_image.py b/fb2__parsing/extract_cover_page_image.py index 7c2b060c4..0b05eed5e 100644 --- a/fb2__parsing/extract_cover_page_image.py +++ b/fb2__parsing/extract_cover_page_image.py @@ -1,51 +1,50 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -from bs4 import BeautifulSoup import base64 - +from bs4 import BeautifulSoup from common import get_attribute_value_by_local_name def get_cover_page_image(root) -> (bytes, str): - cover_page_image = root.select_one('coverpage > image') + cover_page_image = root.select_one("coverpage > image") # Вытаскиваем значение атрибута href - id_image = get_attribute_value_by_local_name(cover_page_image, 'href') + id_image = get_attribute_value_by_local_name(cover_page_image, "href") # "#cover.jpg" -> "cover.jpg" id_image = id_image[1:] # Получится, например, такой css-селектор: "[id='cover.jpg']" - binary = root.select_one("[id='{}']".format(id_image)) + binary = root.select_one(f"[id='{id_image}']") # image/jpeg -> jpeg - content_type = binary.attrs['content-type'].split('/')[-1] + content_type = binary.attrs["content-type"].split("/")[-1] # Содержимое тега будет извлечено и представлено в виде байтов - data = binary.text.encode('utf-8') + data = binary.text.encode("utf-8") - return base64.b64decode(data), {'jpeg': 'jpg', 'png': 'png'}[content_type] + return base64.b64decode(data), {"jpeg": "jpg", "png": "png"}[content_type] -if __name__ == '__main__': +if __name__ == "__main__": import glob import os - output_dir = 'output' + output_dir = "output" os.makedirs(output_dir, exist_ok=True) - for fb2_file_name in glob.glob('input/*.fb2'): - with open(fb2_file_name, encoding='utf-8') as f: - root = BeautifulSoup(f, 'html.parser') + for fb2_file_name in glob.glob("input/*.fb2"): + with open(fb2_file_name, encoding="utf-8") as f: + root = BeautifulSoup(f, "html.parser") img_data, fmt = get_cover_page_image(root) - file_name = os.path.basename(fb2_file_name) + '.' + fmt + file_name = os.path.basename(fb2_file_name) + "." + fmt file_name = os.path.join(output_dir, file_name) - with open(file_name, 'wb') as f: + with open(file_name, "wb") as f: f.write(img_data) diff --git a/fb2__parsing/extract_pictures_from_fb2/common.py b/fb2__parsing/extract_pictures_from_fb2/common.py new file mode 100644 index 000000000..64394198d --- /dev/null +++ b/fb2__parsing/extract_pictures_from_fb2/common.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import os + + +def get_file_name_from_binary(binary_id: str, binary_content_type: str) -> str: + fmt = os.path.splitext(binary_id)[-1].lower() + + # Если формат файла есть, хорошо + if fmt in [".png", ".jpg", ".jpeg"]: + return binary_id + + content_type = binary_content_type.split("/")[-1] + return binary_id + "." + {"jpeg": "jpg", "png": "png"}[content_type] diff --git a/fb2__parsing/extract_pictures_from_fb2/fb2_pictures__using_bs4.py b/fb2__parsing/extract_pictures_from_fb2/fb2_pictures__using_bs4.py index f02ef2eaf..c64aaff27 100644 --- a/fb2__parsing/extract_pictures_from_fb2/fb2_pictures__using_bs4.py +++ b/fb2__parsing/extract_pictures_from_fb2/fb2_pictures__using_bs4.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """Скрипт парсит файл формата fb2, вытаскивает из него картинки и сохраняет их в папке с таким же названием, @@ -11,32 +11,34 @@ import os import base64 import io +import traceback from bs4 import BeautifulSoup -from PIL import Image -import sys -sys.path.append('..') +# pip install humanize +from humanize import naturalsize as sizeof_fmt + +from PIL import Image -from common import sizeof_fmt, get_file_name_from_binary +from common import get_file_name_from_binary -def do(file_name, output_dir='output', debug=True): +def do(file_name, output_dir="output", debug=True) -> None: dir_fb2 = os.path.basename(file_name) dir_im = os.path.join(output_dir, dir_fb2) os.makedirs(dir_im, exist_ok=True) - debug and print(dir_im + ':') + debug and print(dir_im + ":") total_image_size = 0 - with open(file_name, 'rb') as fb2: - root = BeautifulSoup(fb2, 'html.parser') + with open(file_name, "rb") as fb2: + root = BeautifulSoup(fb2, "html.parser") binaries = root.select("binary") for i, binary in enumerate(binaries, 1): try: - im_id = binary.attrs['id'] - content_type = binary.attrs['content-type'] + im_id = binary.attrs["id"] + content_type = binary.attrs["content-type"] im_file_name = get_file_name_from_binary(im_id, content_type) im_file_name = os.path.join(dir_im, im_file_name) @@ -46,26 +48,25 @@ def do(file_name, output_dir='output', debug=True): count_bytes = len(im_data) total_image_size += count_bytes - with open(im_file_name, mode='wb') as f: + with open(im_file_name, mode="wb") as f: f.write(im_data) im = Image.open(io.BytesIO(im_data)) - debug and print(' {}. {} {} format={} size={}'.format( - i, im_id, sizeof_fmt(count_bytes), im.format, im.size - )) + debug and print( + f" {i}. {im_id} {sizeof_fmt(count_bytes)} format={im.format} size={im.size}" + ) except: - import traceback traceback.print_exc() file_size = os.path.getsize(file_name) debug and print() - debug and print('fb2 file size =', sizeof_fmt(file_size)) - debug and print('total image size = {} ({:.2f}%)'.format( - sizeof_fmt(total_image_size), total_image_size / file_size * 100 - )) + debug and print("fb2 file size =", sizeof_fmt(file_size)) + debug and print( + f"total image size = {sizeof_fmt(total_image_size)} ({total_image_size / file_size * 100:.2f}%)" + ) -if __name__ == '__main__': - fb2_file_name = '../input/Непутевый ученик в школе магии 1. Зачисление в школу (Часть 1).fb2' +if __name__ == "__main__": + fb2_file_name = "../input/Непутевый ученик в школе магии 1. Зачисление в школу (Часть 1).fb2" do(fb2_file_name) diff --git a/fb2__parsing/extract_pictures_from_fb2/fb2_pictures__using_lxml.py b/fb2__parsing/extract_pictures_from_fb2/fb2_pictures__using_lxml.py index 863016545..80f3eb0a4 100644 --- a/fb2__parsing/extract_pictures_from_fb2/fb2_pictures__using_lxml.py +++ b/fb2__parsing/extract_pictures_from_fb2/fb2_pictures__using_lxml.py @@ -1,42 +1,44 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """Скрипт парсит файл формата fb2, вытаскивает из него картинки и сохраняет их в папке с таким же названием, как файл fb2.""" -import os import base64 import io +import os +import traceback from lxml import etree -from PIL import Image -import sys -sys.path.append('..') +# pip install humanize +from humanize import naturalsize as sizeof_fmt + +from PIL import Image -from common import sizeof_fmt, get_file_name_from_binary +from common import get_file_name_from_binary -def do(file_name, output_dir='output', debug=True): +def do(file_name, output_dir="output", debug=True) -> None: dir_fb2 = os.path.basename(file_name) dir_im = os.path.join(output_dir, dir_fb2) os.makedirs(dir_im, exist_ok=True) - debug and print(dir_im + ':') + debug and print(dir_im + ":") total_image_size = 0 - with open(file_name, 'rb') as fb2: + with open(file_name, "rb") as fb2: tree = etree.XML(fb2.read()) - binaries = tree.xpath("//*[local-name()='binary']") + binaries = tree.xpath("./*[local-name()='binary']") for i, binary in enumerate(binaries, 1): try: - im_id = binary.attrib['id'] - content_type = binary.attrib['content-type'] + im_id = binary.attrib["id"] + content_type = binary.attrib["content-type"] im_file_name = get_file_name_from_binary(im_id, content_type) im_file_name = os.path.join(dir_im, im_file_name) @@ -46,26 +48,25 @@ def do(file_name, output_dir='output', debug=True): count_bytes = len(im_data) total_image_size += count_bytes - with open(im_file_name, mode='wb') as f: + with open(im_file_name, mode="wb") as f: f.write(im_data) im = Image.open(io.BytesIO(im_data)) - debug and print(' {}. {} {} format={} size={}'.format( - i, im_id, sizeof_fmt(count_bytes), im.format, im.size - )) + debug and print( + f" {i}. {im_id} {sizeof_fmt(count_bytes)} format={im.format} size={im.size}" + ) except: - import traceback traceback.print_exc() file_size = os.path.getsize(file_name) debug and print() - debug and print('fb2 file size =', sizeof_fmt(file_size)) - debug and print('total image size = {} ({:.2f}%)'.format( - sizeof_fmt(total_image_size), total_image_size / file_size * 100 - )) + debug and print("fb2 file size =", sizeof_fmt(file_size)) + debug and print( + f"total image size = {sizeof_fmt(total_image_size)} ({total_image_size / file_size * 100:.2f}%)" + ) -if __name__ == '__main__': - fb2_file_name = '../input/Непутевый ученик в школе магии 1. Зачисление в школу (Часть 1).fb2' +if __name__ == "__main__": + fb2_file_name = "../input/Непутевый ученик в школе магии 1. Зачисление в школу (Часть 1).fb2" do(fb2_file_name) diff --git a/fb2__parsing/extract_pictures_from_fb2/fb2_pictures__using_lxml_iterwalk.py b/fb2__parsing/extract_pictures_from_fb2/fb2_pictures__using_lxml_iterwalk.py new file mode 100644 index 000000000..c896e749b --- /dev/null +++ b/fb2__parsing/extract_pictures_from_fb2/fb2_pictures__using_lxml_iterwalk.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +"""Скрипт парсит файл формата fb2, вытаскивает из него картинки и сохраняет их в папке с таким же названием, +как файл fb2.""" + + +import base64 +import io +import os +import traceback + +from lxml import etree + +# pip install humanize +from humanize import naturalsize as sizeof_fmt + +from PIL import Image + +from common import get_file_name_from_binary + + +def do(file_name, output_dir="output", debug=True) -> None: + dir_fb2 = os.path.basename(file_name) + dir_im = os.path.join(output_dir, dir_fb2) + os.makedirs(dir_im, exist_ok=True) + debug and print(dir_im + ":") + + total_image_size = 0 + + with open(file_name, "rb") as fb2: + tree = etree.XML(fb2.read()) + + binaries = etree.iterwalk( + tree, + events=["end"], + tag="{http://www.gribuser.ru/xml/fictionbook/2.0}binary", + ) + for i, (_, binary) in enumerate(binaries, 1): + try: + im_id = binary.attrib["id"] + content_type = binary.attrib["content-type"] + + im_file_name = get_file_name_from_binary(im_id, content_type) + im_file_name = os.path.join(dir_im, im_file_name) + + im_data = base64.b64decode(binary.text.encode()) + + count_bytes = len(im_data) + total_image_size += count_bytes + + with open(im_file_name, mode="wb") as f: + f.write(im_data) + + im = Image.open(io.BytesIO(im_data)) + debug and print( + f" {i}. {im_id} {sizeof_fmt(count_bytes)} format={im.format} size={im.size}" + ) + + except: + traceback.print_exc() + + file_size = os.path.getsize(file_name) + debug and print() + debug and print("fb2 file size =", sizeof_fmt(file_size)) + debug and print( + f"total image size = {sizeof_fmt(total_image_size)} ({total_image_size / file_size * 100:.2f}%)" + ) + + +if __name__ == "__main__": + fb2_file_name = "../input/Непутевый ученик в школе магии 1. Зачисление в школу (Часть 1).fb2" + do(fb2_file_name) diff --git a/fb2__parsing/extract_pictures_from_fb2/fb2_pictures__using_re.py b/fb2__parsing/extract_pictures_from_fb2/fb2_pictures__using_re.py index 904f56016..480e171d1 100644 --- a/fb2__parsing/extract_pictures_from_fb2/fb2_pictures__using_re.py +++ b/fb2__parsing/extract_pictures_from_fb2/fb2_pictures__using_re.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """Скрипт парсит файл формата fb2, вытаскивает из него картинки и сохраняет их в папке с таким же названием, @@ -15,26 +15,30 @@ import io import os import re +import traceback -from PIL import Image +# pip install humanize +from humanize import naturalsize as sizeof_fmt -import sys -sys.path.append('..') +from PIL import Image -from common import sizeof_fmt, get_file_name_from_binary +from common import get_file_name_from_binary -def do(file_name, output_dir='output', debug=True): +def do(file_name, output_dir="output", debug=True) -> None: dir_fb2 = os.path.basename(file_name) dir_im = os.path.join(output_dir, dir_fb2) os.makedirs(dir_im, exist_ok=True) - debug and print(dir_im + ':') + debug and print(dir_im + ":") total_image_size = 0 - with open(file_name, encoding='utf8') as fb2: - pattern = re.compile('(.+?)', re.DOTALL) + with open(file_name, encoding="utf-8") as fb2: + pattern = re.compile( + '(.+?)', + re.DOTALL, + ) find_content_type = re.compile('content-type="(.+?)"') find_id = re.compile('id="(.+?)"') @@ -66,26 +70,25 @@ def do(file_name, output_dir='output', debug=True): count_bytes = len(im_data) total_image_size += count_bytes - with open(im_file_name, mode='wb') as f: + with open(im_file_name, mode="wb") as f: f.write(im_data) im = Image.open(io.BytesIO(im_data)) - debug and print(' {}. {} {} format={} size={}'.format( - i, im_id, sizeof_fmt(count_bytes), im.format, im.size - )) + debug and print( + f" {i}. {im_id} {sizeof_fmt(count_bytes)} format={im.format} size={im.size}" + ) except: - import traceback traceback.print_exc() file_size = os.path.getsize(file_name) debug and print() - debug and print('fb2 file size =', sizeof_fmt(file_size)) - debug and print('total image size = {} ({:.2f}%)'.format( - sizeof_fmt(total_image_size), total_image_size / file_size * 100 - )) + debug and print("fb2 file size =", sizeof_fmt(file_size)) + debug and print( + f"total image size = {sizeof_fmt(total_image_size)} ({total_image_size / file_size * 100:.2f}%)" + ) -if __name__ == '__main__': - fb2_file_name = '../input/Непутевый ученик в школе магии 1. Зачисление в школу (Часть 1).fb2' +if __name__ == "__main__": + fb2_file_name = "../input/Непутевый ученик в школе магии 1. Зачисление в школу (Часть 1).fb2" do(fb2_file_name) diff --git a/fb2__parsing/extract_pictures_from_fb2/fb2_pictures__using_str_find.py b/fb2__parsing/extract_pictures_from_fb2/fb2_pictures__using_str_find.py new file mode 100644 index 000000000..cbe234673 --- /dev/null +++ b/fb2__parsing/extract_pictures_from_fb2/fb2_pictures__using_str_find.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +""" +Скрипт парсит файл формата fb2, вытаскивает из него картинки и сохраняет их в папке с таким же названием, как файл fb2. +""" + + +import base64 +import io +import os +import traceback + +from typing import Iterator + +# pip install humanize +from humanize import naturalsize as sizeof_fmt + +from PIL import Image + +from common import get_file_name_from_binary + + +def find_inner(text: str, start_str: str, end_str: str) -> tuple[str | None, int]: + idx_start = text.find(start_str) + if idx_start == -1: + return None, -1 + + idx_end = text.find(end_str, idx_start + len(start_str)) + if idx_end == -1: + return None, -1 + + return ( + text[idx_start + len(start_str) : idx_end], + idx_end + len(end_str) + ) + + +def iter_blocks(text: str, start_str: str, end_str: str) -> Iterator[str]: + block, idx_next_start = find_inner(text, start_str, end_str) + if not block: + return + yield block + + while block: + text = text[idx_next_start:] + block, idx_next_start = find_inner(text, start_str, end_str) + if not block: + break + + yield block + + +def do(file_name, output_dir="output", debug=True) -> None: + dir_fb2 = os.path.basename(file_name) + dir_im = os.path.join(output_dir, dir_fb2) + os.makedirs(dir_im, exist_ok=True) + debug and print(dir_im + ":") + + total_image_size = 0 + + with open(file_name, encoding="utf-8") as fb2: + for i, binary in enumerate( + iter_blocks(fb2.read(), ""), + start=1, + ): + try: + binary_header, idx_next_start = find_inner(binary, "", ">") + + im_id, _ = find_inner(binary_header, 'id="', '"') + content_type, _ = find_inner(binary_header, 'content-type="', '"') + + im_base64 = binary[idx_next_start:] + + im_file_name = get_file_name_from_binary(im_id, content_type) + im_file_name = os.path.join(dir_im, im_file_name) + + im_data = base64.b64decode(im_base64.encode()) + + count_bytes = len(im_data) + total_image_size += count_bytes + + with open(im_file_name, mode="wb") as f: + f.write(im_data) + + im = Image.open(io.BytesIO(im_data)) + debug and print( + f" {i}. {im_id} {sizeof_fmt(count_bytes)} format={im.format} size={im.size}" + ) + except: + traceback.print_exc() + + file_size = os.path.getsize(file_name) + debug and print() + debug and print("fb2 file size =", sizeof_fmt(file_size)) + debug and print( + f"total image size = {sizeof_fmt(total_image_size)} ({total_image_size / file_size * 100:.2f}%)" + ) + + +if __name__ == "__main__": + fb2_file_name = "../input/Непутевый ученик в школе магии 1. Зачисление в школу (Часть 1).fb2" + do(fb2_file_name) diff --git a/fb2__parsing/extract_pictures_from_fb2/fb2_pictures__using_xml_etree.py b/fb2__parsing/extract_pictures_from_fb2/fb2_pictures__using_xml_etree.py index 0e9e1fbf7..867a8b221 100644 --- a/fb2__parsing/extract_pictures_from_fb2/fb2_pictures__using_xml_etree.py +++ b/fb2__parsing/extract_pictures_from_fb2/fb2_pictures__using_xml_etree.py @@ -1,31 +1,33 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """Скрипт парсит файл формата fb2, вытаскивает из него картинки и сохраняет их в папке с таким же названием, как файл fb2.""" -import os import base64 import io +import os +import traceback from xml.etree import ElementTree as ET -from PIL import Image -import sys -sys.path.append('..') +# pip install humanize +from humanize import naturalsize as sizeof_fmt + +from PIL import Image -from common import sizeof_fmt, get_file_name_from_binary +from common import get_file_name_from_binary -def do(file_name, output_dir='output', debug=True): +def do(file_name, output_dir="output", debug=True) -> None: dir_fb2 = os.path.basename(file_name) dir_im = os.path.join(output_dir, dir_fb2) os.makedirs(dir_im, exist_ok=True) - debug and print(dir_im + ':') + debug and print(dir_im + ":") total_image_size = 0 number = 1 @@ -36,14 +38,14 @@ def do(file_name, output_dir='output', debug=True): for child in root: tag = child.tag if "}" in tag: - tag = tag[tag.index('}') + 1:] + tag = tag[tag.index("}") + 1 :] - if tag != 'binary': + if tag != "binary": continue try: - im_id = child.attrib['id'] - content_type = child.attrib['content-type'] + im_id = child.attrib["id"] + content_type = child.attrib["content-type"] im_file_name = get_file_name_from_binary(im_id, content_type) im_file_name = os.path.join(dir_im, im_file_name) @@ -53,28 +55,27 @@ def do(file_name, output_dir='output', debug=True): count_bytes = len(im_data) total_image_size += count_bytes - with open(im_file_name, mode='wb') as f: + with open(im_file_name, mode="wb") as f: f.write(im_data) im = Image.open(io.BytesIO(im_data)) - debug and print(' {}. {} {} format={} size={}'.format( - number, im_id, sizeof_fmt(count_bytes), im.format, im.size - )) + debug and print( + f" {number}. {im_id} {sizeof_fmt(count_bytes)} format={im.format} size={im.size}" + ) number += 1 except: - import traceback traceback.print_exc() file_size = os.path.getsize(file_name) debug and print() - debug and print('fb2 file size =', sizeof_fmt(file_size)) - debug and print('total image size = {} ({:.2f}%)'.format( - sizeof_fmt(total_image_size), total_image_size / file_size * 100 - )) + debug and print("fb2 file size =", sizeof_fmt(file_size)) + debug and print( + f"total image size = {sizeof_fmt(total_image_size)} ({total_image_size / file_size * 100:.2f}%)" + ) -if __name__ == '__main__': - fb2_file_name = '../input/Непутевый ученик в школе магии 1. Зачисление в школу (Часть 1).fb2' +if __name__ == "__main__": + fb2_file_name = "../input/Непутевый ученик в школе магии 1. Зачисление в школу (Часть 1).fb2" do(fb2_file_name) diff --git a/fb2__parsing/extract_pictures_from_fb2/fb2_pictures__using_xml_etree_xpath.py b/fb2__parsing/extract_pictures_from_fb2/fb2_pictures__using_xml_etree_xpath.py new file mode 100644 index 000000000..ae0c4e5a1 --- /dev/null +++ b/fb2__parsing/extract_pictures_from_fb2/fb2_pictures__using_xml_etree_xpath.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +"""Скрипт парсит файл формата fb2, вытаскивает из него картинки и сохраняет их в папке с таким же названием, +как файл fb2.""" + + +import base64 +import io +import os +import traceback + +from xml.etree import ElementTree as ET + +# pip install humanize +from humanize import naturalsize as sizeof_fmt + +from PIL import Image + +from common import get_file_name_from_binary + + +def do(file_name, output_dir="output", debug=True) -> None: + dir_fb2 = os.path.basename(file_name) + dir_im = os.path.join(output_dir, dir_fb2) + os.makedirs(dir_im, exist_ok=True) + debug and print(dir_im + ":") + + total_image_size = 0 + number = 1 + + tree = ET.parse(file_name) + root = tree.getroot() + + for child in root.iterfind("./{http://www.gribuser.ru/xml/fictionbook/2.0}binary"): + try: + im_id = child.attrib["id"] + content_type = child.attrib["content-type"] + + im_file_name = get_file_name_from_binary(im_id, content_type) + im_file_name = os.path.join(dir_im, im_file_name) + + im_data = base64.b64decode(child.text.encode()) + + count_bytes = len(im_data) + total_image_size += count_bytes + + with open(im_file_name, mode="wb") as f: + f.write(im_data) + + im = Image.open(io.BytesIO(im_data)) + debug and print( + f" {number}. {im_id} {sizeof_fmt(count_bytes)} format={im.format} size={im.size}" + ) + + number += 1 + + except: + traceback.print_exc() + + file_size = os.path.getsize(file_name) + debug and print() + debug and print("fb2 file size =", sizeof_fmt(file_size)) + debug and print( + f"total image size = {sizeof_fmt(total_image_size)} ({total_image_size / file_size * 100:.2f}%)" + ) + + +if __name__ == "__main__": + fb2_file_name = "../input/Непутевый ученик в школе магии 1. Зачисление в школу (Часть 1).fb2" + do(fb2_file_name) diff --git a/fb2__parsing/extract_pictures_from_fb2/fb2_pictures__using_xml_expat.py b/fb2__parsing/extract_pictures_from_fb2/fb2_pictures__using_xml_expat.py index aa4252c44..11740ce17 100644 --- a/fb2__parsing/extract_pictures_from_fb2/fb2_pictures__using_xml_expat.py +++ b/fb2__parsing/extract_pictures_from_fb2/fb2_pictures__using_xml_expat.py @@ -1,60 +1,61 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """Скрипт парсит файл формата fb2, вытаскивает из него картинки и сохраняет их в папке с таким же названием, как файл fb2.""" -import os import base64 import io - +import os +import traceback import xml.parsers.expat -from PIL import Image -import sys -sys.path.append('..') +# pip install humanize +from humanize import naturalsize as sizeof_fmt -from common import sizeof_fmt, get_file_name_from_binary +from PIL import Image + +from common import get_file_name_from_binary -def do(file_name, output_dir='output', debug=True): +def do(file_name, output_dir="output", debug=True) -> None: dir_fb2 = os.path.basename(file_name) dir_im = os.path.join(output_dir, dir_fb2) os.makedirs(dir_im, exist_ok=True) - debug and print(dir_im + ':') + debug and print(dir_im + ":") PARSE_DATA = { - 'last_start_tag': None, - 'last_tag_attrs': None, - 'last_tag_data': '', - 'total_image_size': 0, - 'number': 1, + "last_start_tag": None, + "last_tag_attrs": None, + "last_tag_data": "", + "total_image_size": 0, + "number": 1, } - def on_start_element(name, attrs): - PARSE_DATA['last_start_tag'] = name - PARSE_DATA['last_tag_attrs'] = attrs - PARSE_DATA['last_tag_data'] = '' + def on_start_element(name, attrs) -> None: + PARSE_DATA["last_start_tag"] = name + PARSE_DATA["last_tag_attrs"] = attrs + PARSE_DATA["last_tag_data"] = "" - def on_char_data(data): - if PARSE_DATA['last_start_tag'] != 'binary': + def on_char_data(data) -> None: + if PARSE_DATA["last_start_tag"] != "binary": return - PARSE_DATA['last_tag_data'] += data + PARSE_DATA["last_tag_data"] += data - def on_end_element(name): - if name != 'binary': + def on_end_element(name) -> None: + if name != "binary": return - data = PARSE_DATA['last_tag_data'] + data = PARSE_DATA["last_tag_data"] try: - im_id = PARSE_DATA['last_tag_attrs']['id'] - content_type = PARSE_DATA['last_tag_attrs']['content-type'] + im_id = PARSE_DATA["last_tag_attrs"]["id"] + content_type = PARSE_DATA["last_tag_attrs"]["content-type"] im_file_name = get_file_name_from_binary(im_id, content_type) im_file_name = os.path.join(dir_im, im_file_name) @@ -62,20 +63,25 @@ def on_end_element(name): im_data = base64.b64decode(data.encode()) count_bytes = len(im_data) - PARSE_DATA['total_image_size'] += count_bytes + PARSE_DATA["total_image_size"] += count_bytes - with open(im_file_name, mode='wb') as f: + with open(im_file_name, mode="wb") as f: f.write(im_data) im = Image.open(io.BytesIO(im_data)) - debug and print(' {}. {} {} format={} size={}'.format( - PARSE_DATA['number'], im_id, sizeof_fmt(count_bytes), im.format, im.size - )) - - PARSE_DATA['number'] += 1 + debug and print( + " {}. {} {} format={} size={}".format( + PARSE_DATA["number"], + im_id, + sizeof_fmt(count_bytes), + im.format, + im.size, + ) + ) + + PARSE_DATA["number"] += 1 except: - import traceback traceback.print_exc() p = xml.parsers.expat.ParserCreate() @@ -83,17 +89,20 @@ def on_end_element(name): p.CharacterDataHandler = on_char_data p.EndElementHandler = on_end_element - with open(file_name, 'rb') as fb2: + with open(file_name, "rb") as fb2: p.Parse(fb2.read(), 1) file_size = os.path.getsize(file_name) debug and print() - debug and print('fb2 file size =', sizeof_fmt(file_size)) - debug and print('total image size = {} ({:.2f}%)'.format( - sizeof_fmt(PARSE_DATA['total_image_size']), PARSE_DATA['total_image_size'] / file_size * 100 - )) + debug and print("fb2 file size =", sizeof_fmt(file_size)) + debug and print( + "total image size = {} ({:.2f}%)".format( + sizeof_fmt(PARSE_DATA["total_image_size"]), + PARSE_DATA["total_image_size"] / file_size * 100, + ) + ) -if __name__ == '__main__': - fb2_file_name = '../input/Непутевый ученик в школе магии 1. Зачисление в школу (Часть 1).fb2' +if __name__ == "__main__": + fb2_file_name = "../input/Непутевый ученик в школе магии 1. Зачисление в школу (Часть 1).fb2" do(fb2_file_name) diff --git a/fb2__parsing/extract_pictures_from_fb2/fb2_pictures__using_xml_sax.py b/fb2__parsing/extract_pictures_from_fb2/fb2_pictures__using_xml_sax.py index 50cfdd592..9877c212b 100644 --- a/fb2__parsing/extract_pictures_from_fb2/fb2_pictures__using_xml_sax.py +++ b/fb2__parsing/extract_pictures_from_fb2/fb2_pictures__using_xml_sax.py @@ -1,63 +1,64 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """Скрипт парсит файл формата fb2, вытаскивает из него картинки и сохраняет их в папке с таким же названием, как файл fb2.""" -import os import base64 import io - +import os +import traceback import xml.sax -from PIL import Image -import sys -sys.path.append('..') +# pip install humanize +from humanize import naturalsize as sizeof_fmt -from common import sizeof_fmt, get_file_name_from_binary +from PIL import Image +from common import get_file_name_from_binary -def do(file_name, output_dir='output', debug=True): + +def do(file_name, output_dir="output", debug=True) -> None: dir_fb2 = os.path.basename(file_name) dir_im = os.path.join(output_dir, dir_fb2) os.makedirs(dir_im, exist_ok=True) - debug and print(dir_im + ':') + debug and print(dir_im + ":") # Analog: fb2_pictures__using_xml_expat.py PARSE_DATA = { - 'last_start_tag': None, - 'last_tag_attrs': None, - 'last_tag_data': '', - 'total_image_size': 0, - 'number': 1, + "last_start_tag": None, + "last_tag_attrs": None, + "last_tag_data": "", + "total_image_size": 0, + "number": 1, } class BinaryHandler(xml.sax.ContentHandler): - def startElement(self, name, attrs): - PARSE_DATA['last_start_tag'] = name - PARSE_DATA['last_tag_attrs'] = attrs - PARSE_DATA['last_tag_data'] = '' + def startElement(self, name, attrs) -> None: + PARSE_DATA["last_start_tag"] = name + PARSE_DATA["last_tag_attrs"] = attrs + PARSE_DATA["last_tag_data"] = "" - def characters(self, content): - if PARSE_DATA['last_start_tag'] != 'binary': + def characters(self, content) -> None: + if PARSE_DATA["last_start_tag"] != "binary": return - PARSE_DATA['last_tag_data'] += content + PARSE_DATA["last_tag_data"] += content - def endElement(self, name): - if name != 'binary': + def endElement(self, name) -> None: + if name != "binary": return - data = PARSE_DATA['last_tag_data'] + data = PARSE_DATA["last_tag_data"] try: - im_id = PARSE_DATA['last_tag_attrs']['id'] - content_type = PARSE_DATA['last_tag_attrs']['content-type'] + im_id = PARSE_DATA["last_tag_attrs"]["id"] + content_type = PARSE_DATA["last_tag_attrs"]["content-type"] im_file_name = get_file_name_from_binary(im_id, content_type) im_file_name = os.path.join(dir_im, im_file_name) @@ -65,20 +66,25 @@ def endElement(self, name): im_data = base64.b64decode(data.encode()) count_bytes = len(im_data) - PARSE_DATA['total_image_size'] += count_bytes + PARSE_DATA["total_image_size"] += count_bytes - with open(im_file_name, mode='wb') as f: + with open(im_file_name, mode="wb") as f: f.write(im_data) im = Image.open(io.BytesIO(im_data)) - debug and print(' {}. {} {} format={} size={}'.format( - PARSE_DATA['number'], im_id, sizeof_fmt(count_bytes), im.format, im.size - )) - - PARSE_DATA['number'] += 1 + debug and print( + " {}. {} {} format={} size={}".format( + PARSE_DATA["number"], + im_id, + sizeof_fmt(count_bytes), + im.format, + im.size, + ) + ) + + PARSE_DATA["number"] += 1 except: - import traceback traceback.print_exc() parser = xml.sax.make_parser() @@ -87,12 +93,17 @@ def endElement(self, name): file_size = os.path.getsize(file_name) debug and print() - debug and print('fb2 file size =', sizeof_fmt(file_size)) - debug and print('total image size = {} ({:.2f}%)'.format( - sizeof_fmt(PARSE_DATA['total_image_size']), PARSE_DATA['total_image_size'] / file_size * 100 - )) - - -if __name__ == '__main__': - fb2_file_name = '../input/Непутевый ученик в школе магии 1. Зачисление в школу (Часть 1).fb2' + debug and print("fb2 file size =", sizeof_fmt(file_size)) + debug and print( + "total image size = {} ({:.2f}%)".format( + sizeof_fmt(PARSE_DATA["total_image_size"]), + PARSE_DATA["total_image_size"] / file_size * 100, + ) + ) + + +if __name__ == "__main__": + fb2_file_name = ( + "../input/Непутевый ученик в школе магии 1. Зачисление в школу (Часть 1).fb2" + ) do(fb2_file_name) diff --git a/fb2__parsing/extract_pictures_from_fb2/time_test.py b/fb2__parsing/extract_pictures_from_fb2/time_test.py index 6c81453d4..8f1040d7b 100644 --- a/fb2__parsing/extract_pictures_from_fb2/time_test.py +++ b/fb2__parsing/extract_pictures_from_fb2/time_test.py @@ -1,32 +1,47 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" +from timeit import timeit + from fb2_pictures__using_lxml import do as do_lxml +from fb2_pictures__using_lxml_iterwalk import do as do_lxml_iterwalk from fb2_pictures__using_bs4 import do as do_bs4 from fb2_pictures__using_xml_expat import do as do_xml_expat from fb2_pictures__using_xml_etree import do as do_xml_etree +from fb2_pictures__using_xml_etree_xpath import do as do_xml_etree_xpath from fb2_pictures__using_xml_sax import do as do_xml_sax from fb2_pictures__using_re import do as do_using_re +from fb2_pictures__using_str_find import do as do_using_str_find -file_name = '../input/Непутевый ученик в школе магии 1. Зачисление в школу (Часть 1).fb2' -count = 10 - -from timeit import timeit +file_name = "../input/Непутевый ученик в школе магии 1. Зачисление в школу (Часть 1).fb2" +count = 20 runs = [ - ('LXML', 'do_lxml'), - ('XML EXPAT', 'do_xml_expat'), - ('XML.ETREE', 'do_xml_etree'), - ('XML SAX', 'do_xml_sax'), - ('REGEXP', 'do_using_re'), - ('BS4', 'do_bs4'), + # ("LXML", "do_lxml"), + # ("LXML iterwalk", "do_lxml_iterwalk"), + # ("XML EXPAT", "do_xml_expat"), + # ("XML.ETREE", "do_xml_etree"), + # ("XML.ETREE xpath", "do_xml_etree_xpath"), + # ("XML SAX", "do_xml_sax"), + ("REGEXP", "do_using_re"), + ("STR FIND", "do_using_str_find"), + ("BS4", "do_bs4"), ] -runs_format = '{:%s} | {:.3f} secs' % (max(len(x[0]) for x in runs),) +runs_format = "{:%s} | {:.3f} secs" % (max(len(x[0]) for x in runs),) for name, stmt in runs: - timing = timeit(stmt + '(file_name, debug=False)', globals=globals(), number=count) + timing = timeit(stmt + "(file_name, debug=False)", globals=globals(), number=count) print(runs_format.format(name, timing)) +""" +LXML | 0.733 secs +XML EXPAT | 0.867 secs +XML.ETREE | 0.755 secs +XML SAX | 1.014 secs +REGEXP | 2.466 secs +STR FIND | 1.210 secs +BS4 | 2.166 secs +""" diff --git a/fb2__parsing/get_annotation.py b/fb2__parsing/get_annotation.py index b736071c3..5a7e3ebb5 100644 --- a/fb2__parsing/get_annotation.py +++ b/fb2__parsing/get_annotation.py @@ -1,31 +1,31 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from bs4 import BeautifulSoup def get_annotation(root) -> str: - annotation_node = root.select_one('description > title-info > annotation') + annotation_node = root.select_one("description > title-info > annotation") if not annotation_node: - return '' + return "" return annotation_node.text.strip() -if __name__ == '__main__': +if __name__ == "__main__": import glob - for fb2_file_name in glob.glob('input/*.fb2'): + for fb2_file_name in glob.glob("input/*.fb2"): print(fb2_file_name) - with open(fb2_file_name, encoding='utf-8') as f: - root = BeautifulSoup(f, 'html.parser') + with open(fb2_file_name, encoding="utf-8") as f: + root = BeautifulSoup(f, "html.parser") annotation = get_annotation(root) print(repr(annotation)) print(annotation) - print('\n') + print("\n") diff --git a/fb2__parsing/get_authors.py b/fb2__parsing/get_authors.py index 561925e8f..c6df42034 100644 --- a/fb2__parsing/get_authors.py +++ b/fb2__parsing/get_authors.py @@ -1,27 +1,26 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from bs4 import BeautifulSoup -import typing -def get_authors(root) -> typing.List[str]: +def get_authors(root) -> list[str]: # Пример тега: # # Сато # Цутому # - authors_nodes = root.select('description > title-info > author') + authors_nodes = root.select("description > title-info > author") authors = [] for author in authors_nodes: - first_name = author.select_one('first-name') - last_name = author.select_one('last-name') - middle_name = author.select_one('middle-name') + first_name = author.select_one("first-name") + last_name = author.select_one("last-name") + middle_name = author.select_one("middle-name") name = [] @@ -35,19 +34,19 @@ def get_authors(root) -> typing.List[str]: name.append(middle_name.text) # ['Виталий', 'Зыков'] -> 'Виталий Зыков' - name = ' '.join(name) + name = " ".join(name) authors.append(name) return authors -if __name__ == '__main__': +if __name__ == "__main__": import glob - for fb2_file_name in glob.glob('input/*.fb2'): - with open(fb2_file_name, encoding='utf-8') as f: - root = BeautifulSoup(f, 'html.parser') + for fb2_file_name in glob.glob("input/*.fb2"): + with open(fb2_file_name, encoding="utf-8") as f: + root = BeautifulSoup(f, "html.parser") authors = get_authors(root) print(authors) diff --git a/fb2__parsing/get_notes.py b/fb2__parsing/get_notes.py index 46ee63865..282ab4132 100644 --- a/fb2__parsing/get_notes.py +++ b/fb2__parsing/get_notes.py @@ -1,23 +1,21 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from bs4 import BeautifulSoup -from typing import List, Tuple - from common import get_attribute_value_by_local_name -def get_note_links(root) -> List[Tuple['href', 'text']]: +def get_note_links(root) -> list[tuple[str, str]]: # в стиле Роберта Адама [1] note_link_list = root.select('a[type="note"]') items = [] for link in note_link_list: - href = get_attribute_value_by_local_name(link, 'href') + href = get_attribute_value_by_local_name(link, "href") text = link.text.strip() items.append((href, text)) @@ -25,7 +23,7 @@ def get_note_links(root) -> List[Tuple['href', 'text']]: return items -def get_notes(root) -> List[Tuple['id', 'title', 'text']]: +def get_notes(root) -> list[tuple[str, str, str]]: # Пример тега: # # @@ -42,7 +40,7 @@ def get_notes(root) -> List[Tuple['id', 'title', 'text']]: items = [] for note in notes_list: - note_id = note.attrs['id'] + note_id = note.attrs["id"] title = note.title.text.strip() @@ -57,19 +55,19 @@ def get_notes(root) -> List[Tuple['id', 'title', 'text']]: return items -if __name__ == '__main__': +if __name__ == "__main__": import glob - for fb2_file_name in glob.glob('input/*.fb2'): - with open(fb2_file_name, encoding='utf-8') as f: - root = BeautifulSoup(f, 'html.parser') + for fb2_file_name in glob.glob("input/*.fb2"): + with open(fb2_file_name, encoding="utf-8") as f: + root = BeautifulSoup(f, "html.parser") print(fb2_file_name) note_links = get_note_links(root) - print('note_links:', note_links) + print("note_links:", note_links) notes = get_notes(root) - print('notes:', notes) + print("notes:", notes) - print('\n') + print("\n") diff --git a/fb2__parsing/get_sections.py b/fb2__parsing/get_sections.py index 94c0a6c2c..fdf63a009 100644 --- a/fb2__parsing/get_sections.py +++ b/fb2__parsing/get_sections.py @@ -1,38 +1,36 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from bs4 import BeautifulSoup -import typing -from collections import OrderedDict -def get_sections_as_dict(root) -> typing.Dict[str, dict]: +def get_sections_as_dict(root) -> dict[str, dict]: # Рекурсивная функция поиска <section> - def _find_sections(root, root_dict: dict): - for section in root.find_all('section', recursive=False): + def _find_sections(root, root_dict: dict) -> None: + for section in root.find_all("section", recursive=False): title = section.title.text.strip() - children = OrderedDict() + children = dict() root_dict[title] = children _find_sections(section, children) # Первое <body> должно быть с содержанием, описываемое <section> - body = root.select_one('body') - section_by_children = OrderedDict() + body = root.select_one("body") + section_by_children = dict() _find_sections(body, section_by_children) return section_by_children -def get_sections_as_list(root) -> typing.List[typing.Tuple[str, list]]: +def get_sections_as_list(root) -> list[tuple[str, list]]: # Рекурсивная функция поиска <section> - def _find_sections(root, children_list: list): - for section in root.find_all('section', recursive=False): + def _find_sections(root, children_list: list) -> None: + for section in root.find_all("section", recursive=False): title = section.title.text.strip() children = [] @@ -41,7 +39,7 @@ def _find_sections(root, children_list: list): _find_sections(section, children) # Первое <body> должно быть с содержанием, описываемое <section> - body = root.select_one('body') + body = root.select_one("body") root_list = [] _find_sections(body, root_list) @@ -49,25 +47,24 @@ def _find_sections(root, children_list: list): return root_list -if __name__ == '__main__': +if __name__ == "__main__": import glob import json - def _print_sections(root: dict, level=1): + def _print_sections(root: dict, level=1) -> None: for title, children in root.items(): - text = '{}{}'.format(' ' * (level - 1), title.replace('\n', '. ')) + text = "{}{}".format(" " * (level - 1), title.replace("\n", ". ")) if children: - text += ':' + text += ":" print(text) _print_sections(children, level + 1) - - for fb2_file_name in glob.glob('input/*.fb2'): + for fb2_file_name in glob.glob("input/*.fb2"): print(fb2_file_name) - with open(fb2_file_name, encoding='utf-8') as f: - root = BeautifulSoup(f, 'html.parser') + with open(fb2_file_name, encoding="utf-8") as f: + root = BeautifulSoup(f, "html.parser") sections_d = get_sections_as_dict(root) print(sections_d) @@ -78,7 +75,7 @@ def _print_sections(root: dict, level=1): print(json.dumps(sections_l, indent=4, ensure_ascii=False)) print() - print('Содержание:') + print("Содержание:") _print_sections(sections_d, level=2) - print('\n') + print("\n") diff --git a/fb2__parsing/print_section_by_text_number_length.py b/fb2__parsing/print_section_by_text_number_length.py index 7207026e8..54a691d8e 100644 --- a/fb2__parsing/print_section_by_text_number_length.py +++ b/fb2__parsing/print_section_by_text_number_length.py @@ -1,20 +1,18 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -from bs4 import BeautifulSoup -from typing import Dict, Tuple -from collections import OrderedDict +from bs4 import BeautifulSoup, Tag -def get_sections_as_dict(root) -> Tuple[Dict[str, dict], Dict[str, 'Section']]: +def get_sections_as_dict(root) -> tuple[dict[str, dict], dict[str, Tag]]: # Рекурсивная функция поиска <section> - def _find_sections(root, root_dict: dict, title_by_section: dict): - for section in root.find_all('section', recursive=False): + def _find_sections(root, root_dict: dict, title_by_section: dict) -> None: + for section in root.find_all("section", recursive=False): title = section.title.text.strip() - children = OrderedDict() + children = dict() root_dict[title] = children title_by_section[title] = section @@ -22,23 +20,26 @@ def _find_sections(root, root_dict: dict, title_by_section: dict): _find_sections(section, children, title_by_section) # Первое <body> должно быть с содержанием, описываемое <section> - body = root.select_one('body') - section_by_children = OrderedDict() - title_by_section = OrderedDict() + body = root.select_one("body") + section_by_children = dict() + title_by_section = dict() _find_sections(body, section_by_children, title_by_section) return section_by_children, title_by_section -def get_section_by_text(section_by_children: Dict[str, dict], title_by_section: Dict[str, 'Section']) -> Dict[str, str]: - def _find_sections(section_by_text, section_by_children): +def get_section_by_text( + section_by_children: dict[str, dict], + title_by_section: dict[str, Tag], +) -> dict[str, str]: + def _find_sections(section_by_text, section_by_children) -> None: for title, children in section_by_children.items(): if children: _find_sections(section_by_text, children) continue - text = title.replace('\n', '. ') + text = title.replace("\n", ". ") section = title_by_section[title] # Удаление <title>. Тег title относится к section, точнее, оно явлется его названием, поэтому @@ -55,27 +56,32 @@ def _find_sections(section_by_text, section_by_children): return section_by_text -if __name__ == '__main__': +if __name__ == "__main__": import glob - def _print_sections(root: dict, section_by_text: dict, number_length_book_text, level=1): - def _find_section_lines(root, level, lines: list): + def _print_sections( + root: dict, + section_by_text: dict, + number_length_book_text, + level=1 + ) -> None: + def _find_section_lines(root, level, lines: list) -> None: for title, children in root.items(): - text = '{}{}'.format(' ' * (level - 1), title.replace('\n', '. ')) + text = "{}{}".format(" " * (level - 1), title.replace("\n", ". ")) if children: - text += ':' + text += ":" if title in section_by_text: len_num = len(section_by_text[title]) lines.append(( text, - '{} символов'.format(len_num), - '{:.2%}'.format(len_num / number_length_book_text) + f"{len_num} символов", + f"{len_num / number_length_book_text:.2%}", )) else: - lines.append((text, '', '')) + lines.append((text, "", "")) _find_section_lines(children, level + 1, lines) @@ -87,7 +93,7 @@ def _find_section_lines(root, level, lines: list): max_len_columns = [max(map(len, map(str, col))) for col in zip(*lines)] # Создание строки форматирования: [30, 14, 5] -> "{:<30} | {:<14} | {:<5}" - my_table_format = ' | '.join('{:<%s}' % max_len for max_len in max_len_columns) + my_table_format = " | ".join("{:<%s}" % max_len for max_len in max_len_columns) # Ручное формирование: "{:30} | {:14} | {}" # my_table_format = '{:%s} | {:%s} | {}' % (max(len(x[0]) for x in lines), max(len(x[1]) for x in lines)) @@ -95,18 +101,18 @@ def _find_section_lines(root, level, lines: list): for line in lines: print(my_table_format.format(*line)) - for fb2_file_name in glob.glob('input/*.fb2'): + for fb2_file_name in glob.glob("input/*.fb2"): print(fb2_file_name) - with open(fb2_file_name, encoding='utf-8') as f: - root = BeautifulSoup(f, 'html.parser') + with open(fb2_file_name, encoding="utf-8") as f: + root = BeautifulSoup(f, "html.parser") section_by_children, title_by_section = get_sections_as_dict(root) section_by_text = get_section_by_text(section_by_children, title_by_section) number_length_book_text = sum(len(text) for text in section_by_text.values()) - print('Всего символов в книге:', number_length_book_text) + print("Всего символов в книге:", number_length_book_text) _print_sections(section_by_children, section_by_text, number_length_book_text) - print('\n') + print("\n") diff --git a/ffmpeg_examples/many_files_with_subprocess.py b/ffmpeg_examples/many_files_with_subprocess.py index 85a93e947..85704c652 100644 --- a/ffmpeg_examples/many_files_with_subprocess.py +++ b/ffmpeg_examples/many_files_with_subprocess.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """Запуск нескольких процессов ffmpeg и ожидание пока все они завершатся.""" @@ -9,17 +9,23 @@ # Чтобы убить все процессы ffmpeg в винде: Taskkill /F /IM ffmpeg.exe +import os +import time + +from subprocess import Popen, DEVNULL + + videos = [ - 'Горит от чатика - Dark Souls #1 176x144.3gp', - 'Горит от чатика - Dark Souls #1 320x180.3gp', - 'Горит от чатика - Dark Souls #1 640x360.mp4', - 'Горит от чатика - Dark Souls #1 640x360.webm', - 'Горит от чатика - Dark Souls #1 1280x720.mp4', + "Горит от чатика - Dark Souls #1 176x144.3gp", + "Горит от чатика - Dark Souls #1 320x180.3gp", + "Горит от чатика - Dark Souls #1 640x360.mp4", + "Горит от чатика - Dark Souls #1 640x360.webm", + "Горит от чатика - Dark Souls #1 1280x720.mp4", ] -DIRECTORY = 'extracted_images' -import os +DIRECTORY = "extracted_images" + if not os.path.exists(DIRECTORY): os.mkdir(DIRECTORY) @@ -28,11 +34,12 @@ process_list = list() for file_name in videos: - for ext in ['jpg', 'png']: - command = COMMAND_PATTERN.format(directory=DIRECTORY, file_name=file_name, ext=ext) + for ext in ["jpg", "png"]: + command = COMMAND_PATTERN.format( + directory=DIRECTORY, file_name=file_name, ext=ext + ) print(command) - from subprocess import Popen, DEVNULL process = Popen(command, stderr=DEVNULL, stdout=DEVNULL) process_list.append(process) @@ -49,5 +56,4 @@ print("Все процессы завершились") break - import time time.sleep(5) diff --git a/ffmpeg_examples/text_overlay_on_video/text_overlay_on_video.py b/ffmpeg_examples/text_overlay_on_video/text_overlay_on_video.py index 8494d9630..30198dfe0 100644 --- a/ffmpeg_examples/text_overlay_on_video/text_overlay_on_video.py +++ b/ffmpeg_examples/text_overlay_on_video/text_overlay_on_video.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """ @@ -13,45 +13,45 @@ # http://ffmpeg.org/ffmpeg-filters.html#drawtext-1 +import os +from subprocess import Popen, PIPE + + command_pattern = ( - '''ffmpeg -i {in} ''' - '''-vf drawtext="fontfile={font}: text='{text}': ''' - '''fontcolor={text_color}: fontsize={fontsize}: x={x}: y={y}" ''' - '''-vb 20M {out} ''' + """ffmpeg -i {in} """ + """-vf drawtext="fontfile={font}: text='{text}': """ + """fontcolor={text_color}: fontsize={fontsize}: x={x}: y={y}" """ + """-vb 20M {out} """ ) params = { - 'in': 'BH_Logo.wmv', - 'out': 'BH_Logo_new.wmv', - - 'text': 'http://ffmpeg.org/ffmpeg-filters.html#drawtext-1', - 'font': 'FreeSerif.ttf', - 'fontsize': 30, - 'text_color': 'white', - + "in": "BH_Logo.wmv", + "out": "BH_Logo_new.wmv", + "text": "http://ffmpeg.org/ffmpeg-filters.html#drawtext-1", + "font": "FreeSerif.ttf", + "fontsize": 30, + "text_color": "white", # Рисование в правом нижнем угле с небольшим отступом от границ - 'x': 'w - text_w - 5', - 'y': 'h - text_h - 5', + "x": "w - text_w - 5", + "y": "h - text_h - 5", } # Экранирование ":" (двоеточие), т.к. оно является символом разделение параметров # команды фильтра -params['text'] = params['text'].replace(':', '\\:') +params["text"] = params["text"].replace(":", "\\:") command = command_pattern.format(**params) print(command) -import os -if os.path.exists(params['out']): - os.remove(params['out']) +if os.path.exists(params["out"]): + os.remove(params["out"]) -from subprocess import Popen, PIPE with Popen(command, stdout=PIPE, stderr=PIPE, universal_newlines=True) as process: out, err = process.communicate() print(out) - print('-' * 25) + print("-" * 25) print(err) -print('-' * 25) -print('{} size: {}'.format(params['in'], os.path.getsize(params['in']))) -print('{} size: {}'.format(params['out'], os.path.getsize(params['out']))) +print("-" * 25) +print("{} size: {}".format(params["in"], os.path.getsize(params["in"]))) +print("{} size: {}".format(params["out"], os.path.getsize(params["out"]))) diff --git a/file/append.py b/file/append.py index c9632be8e..76c047a82 100644 --- a/file/append.py +++ b/file/append.py @@ -1,19 +1,23 @@ -__author__ = 'ipetrash' +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" # https://docs.python.org/3.4/tutorial/inputoutput.html#reading-and-writing-files # http://pythonworld.ru/tipy-dannyx-v-python/fajly-rabota-s-fajlami.html -if __name__ == '__main__': - from datetime import datetime - # Открыть файл в режиме добавления записей - with open('foo.txt', mode='a') as f: - now_time = datetime.now().time().strftime('%H:%M:%S') - f.write(now_time + '\n') +from datetime import datetime + + +# Открыть файл в режиме добавления записей +with open("foo.txt", mode="a", encoding="utf-8") as f: + now_time = datetime.now().time().strftime("%H:%M:%S") + f.write(now_time + "\n") - # Открыть файл в режиме добавления записей - with open('foo.txt', mode='a') as f: - f.write('!!!' + '\n') - f.write('!!' + '\n') - f.write('!' + '\n') \ No newline at end of file +# Открыть файл в режиме добавления записей +with open("foo.txt", mode="a", encoding="utf-8") as f: + f.write("!!!" + "\n") + f.write("!!" + "\n") + f.write("!" + "\n") diff --git a/file/foo.py b/file/foo.py deleted file mode 100644 index 694a37004..000000000 --- a/file/foo.py +++ /dev/null @@ -1,7 +0,0 @@ -__author__ = 'ipetrash' - - -# TODO: больше примеров -# https://docs.python.org/3.4/tutorial/inputoutput.html#reading-and-writing-files -# http://pythonworld.ru/tipy-dannyx-v-python/fajly-rabota-s-fajlami.html - diff --git a/file/read.py b/file/read.py index 4d68e4f24..61ac67c1d 100644 --- a/file/read.py +++ b/file/read.py @@ -1,16 +1,20 @@ -__author__ = 'ipetrash' +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + # https://docs.python.org/3.4/tutorial/inputoutput.html#reading-and-writing-files # http://pythonworld.ru/tipy-dannyx-v-python/fajly-rabota-s-fajlami.html -if __name__ == '__main__': - # Открыть файл в режиме чтения - with open('foo.txt', mode='r') as f: - print(f.read()) - print() +# Открыть файл в режиме чтения +with open("foo.txt", mode="r", encoding="utf-8") as f: + print(f.read()) + +print() - # Открыть файл в режиме чтения и построчно считать файл - with open('foo.txt', mode='r') as f: - for r in f: - print(r, end='') \ No newline at end of file +# Открыть файл в режиме чтения и построчно считать файл +with open("foo.txt", mode="r", encoding="utf-8") as f: + for line in f: + print(line, end="") diff --git a/file/seek.py b/file/seek.py index f10150acb..c74bcbb19 100644 --- a/file/seek.py +++ b/file/seek.py @@ -1,15 +1,15 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -with open('input__seek.txt') as f: +with open("input__seek.txt", encoding="utf-8") as f: for i in range(3): - print('{}.'.format(i)) + print(f"{i}.") for line in f: - print(line.strip(), end=' ') + print(line.strip(), end=" ") - print('\n') + print("\n") f.seek(0) diff --git a/file/write.py b/file/write.py index 1a4bf890b..5c4fb7898 100644 --- a/file/write.py +++ b/file/write.py @@ -1,13 +1,16 @@ -__author__ = 'ipetrash' +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" # https://docs.python.org/3.4/tutorial/inputoutput.html#reading-and-writing-files # http://pythonworld.ru/tipy-dannyx-v-python/fajly-rabota-s-fajlami.html -if __name__ == '__main__': - # Открыть файл в режиме записи - with open('foo.txt', mode='w') as f: - f.write('123\n') - f.write('one two\n') - f.write('one two\n') - f.write('раз два\n') \ No newline at end of file + +# Открыть файл в режиме записи +with open("foo.txt", mode="w", encoding="utf-8") as f: + f.write("123\n") + f.write("one two\n") + f.write("one two\n") + f.write("раз два\n") diff --git a/file_tree_maker.py b/file_tree_maker.py index d0d2124e6..67fbab1cd 100644 --- a/file_tree_maker.py +++ b/file_tree_maker.py @@ -2,18 +2,18 @@ # -*- coding: utf-8 -*- -__author__ = 'legendmohe' +__author__ = "legendmohe" # CODE: http://stackoverflow.com/a/32656429/5909792 -import os import argparse +import os -class FileTreeMaker(object): - def _recurse(self, parent_path, file_list, prefix, output_buf, level): +class FileTreeMaker: + def _recurse(self, parent_path, file_list, prefix, output_buf, level) -> None: if len(file_list) == 0 or (self.max_level != -1 and self.max_level <= level): return else: @@ -33,7 +33,13 @@ def _recurse(self, parent_path, file_list, prefix, output_buf, level): tmp_prefix = prefix + "┃ " else: tmp_prefix = prefix + " " - self._recurse(full_path, os.listdir(full_path), tmp_prefix, output_buf, level + 1) + self._recurse( + full_path, + os.listdir(full_path), + tmp_prefix, + output_buf, + level + 1, + ) elif os.path.isfile(full_path): output_buf.append("%s%s%s" % (prefix, idc, sub_path)) @@ -52,7 +58,7 @@ def make(self, args): output_str = "\n".join(buf) if len(args.output) != 0: - with open(args.output, 'w') as of: + with open(args.output, "w") as of: of.write(output_str) return output_str @@ -61,8 +67,12 @@ def make(self, args): parser = argparse.ArgumentParser() parser.add_argument("-r", "--root", help="root of file tree", default=".") parser.add_argument("-o", "--output", help="output file name", default="") - parser.add_argument("-xf", "--exclude_folder", nargs='*', help="exclude folder", default=[]) - parser.add_argument("-xn", "--exclude_name", nargs='*', help="exclude name", default=[]) + parser.add_argument( + "-xf", "--exclude_folder", nargs="*", help="exclude folder", default=[] + ) + parser.add_argument( + "-xn", "--exclude_name", nargs="*", help="exclude name", default=[] + ) parser.add_argument("-m", "--max_level", help="max level", type=int, default=-1) args = parser.parse_args() print(FileTreeMaker().make(args)) diff --git a/find_groups_of_repeating_sequences__using_itertools_groupby.py b/find_groups_of_repeating_sequences__using_itertools_groupby.py index 4eb4d072d..8cae19e6a 100644 --- a/find_groups_of_repeating_sequences__using_itertools_groupby.py +++ b/find_groups_of_repeating_sequences__using_itertools_groupby.py @@ -1,17 +1,17 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from itertools import groupby def get_groups_seqs(text): - return [''.join(v) for _, v in groupby(text)] + return ["".join(v) for _, v in groupby(text)] -if __name__ == '__main__': - text = 'hhhrrrraaavvvvvvv' +if __name__ == "__main__": + text = "hhhrrrraaavvvvvvv" items = get_groups_seqs(text) print(items) # ['hhh', 'rrrr', 'aaa', 'vvvvvvv'] diff --git a/find_groups_of_repeating_sequences__using_re.py b/find_groups_of_repeating_sequences__using_re.py index 6e52cdbcf..ccb6dc6a6 100644 --- a/find_groups_of_repeating_sequences__using_re.py +++ b/find_groups_of_repeating_sequences__using_re.py @@ -1,17 +1,17 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import re def get_groups_seqs(text): - return [m.group() for m in re.finditer(r'(.)\1+', text)] + return [m.group() for m in re.finditer(r"(.)\1+", text)] -if __name__ == '__main__': - text = 'hhhrrrraaavvvvvvv' +if __name__ == "__main__": + text = "hhhrrrraaavvvvvvv" items = get_groups_seqs(text) print(items) # ['hhh', 'rrrr', 'aaa', 'vvvvvvv'] diff --git a/find_most_common_substrings_in_a_string.py b/find_most_common_substrings_in_a_string.py index b55210788..161d66a0e 100644 --- a/find_most_common_substrings_in_a_string.py +++ b/find_most_common_substrings_in_a_string.py @@ -1,18 +1,20 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -text = 'ACGTTGCATGTCGCATGATGCATGAGAGCT' +from collections import defaultdict + + +text = "ACGTTGCATGTCGCATGATGCATGAGAGCT" -from collections import defaultdict accumulator = defaultdict(int) for length in range(1, len(text) + 1): for start in range(len(text) - length): - sub_text = text[start: start + length] + sub_text = text[start : start + length] accumulator[sub_text] += 1 print(accumulator) diff --git a/find_most_common_substrings_in_a_string__with_fixed_subtring_length.py b/find_most_common_substrings_in_a_string__with_fixed_subtring_length.py index c674d08aa..e16a7220f 100644 --- a/find_most_common_substrings_in_a_string__with_fixed_subtring_length.py +++ b/find_most_common_substrings_in_a_string__with_fixed_subtring_length.py @@ -1,16 +1,16 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -text = 'ACGTTGCATGTCGCATGATGCATGAGAGCT' -key_len = 4 +from collections import defaultdict, Counter -from collections import defaultdict, Counter -accumulator = Counter(text[i: i + key_len] for i in range(len(text) - key_len + 1)) +text = "ACGTTGCATGTCGCATGATGCATGAGAGCT" +key_len = 4 +accumulator = Counter(text[i : i + key_len] for i in range(len(text) - key_len + 1)) print(accumulator) # {'A': 7, 'C': 6, 'G': 9, 'T': 7, max_items = defaultdict(list) diff --git a/find_most_common_substrings_in_a_string__with_minimal_substring_length.py b/find_most_common_substrings_in_a_string__with_minimal_substring_length.py index b239fd9d0..f9745b3ab 100644 --- a/find_most_common_substrings_in_a_string__with_minimal_substring_length.py +++ b/find_most_common_substrings_in_a_string__with_minimal_substring_length.py @@ -1,19 +1,20 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -text = 'ACGTTGCATGTCGCATGATGCATGAGAGCT' -min_len = 4 +from collections import defaultdict -from collections import defaultdict +text = "ACGTTGCATGTCGCATGATGCATGAGAGCT" +min_len = 4 + accumulator = defaultdict(int) for length in range(1, len(text) + 1): for start in range(len(text) - length): - sub_text = text[start: start + length] + sub_text = text[start : start + length] accumulator[sub_text] += 1 print(accumulator) diff --git a/firefox/api.py b/firefox/api.py index 27b2dc722..5e509a032 100644 --- a/firefox/api.py +++ b/firefox/api.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import logging @@ -9,60 +9,59 @@ from collections import Counter from pathlib import Path -from typing import List from jsonlz4_mozLz4 import mozlz4a def get_sessionstore(file_name_session: Path) -> dict: - with open(file_name_session, 'rb') as f: + with open(file_name_session, "rb") as f: return mozlz4a.loads_json(f) -def get_tab_urls_from_sessionstore(sessionstore: dict) -> List[str]: +def get_tab_urls_from_sessionstore(sessionstore: dict) -> list[str]: urls = [] - for w in sessionstore['windows']: - for t in w['tabs']: - i = t['index'] - 1 - tab_url = t['entries'][i]['url'] + for w in sessionstore["windows"]: + for t in w["tabs"]: + i = t["index"] - 1 + tab_url = t["entries"][i]["url"] urls.append(tab_url) return urls -def get_tab_urls(file_name_session: Path) -> List[str]: +def get_tab_urls(file_name_session: Path) -> list[str]: json_data = get_sessionstore(file_name_session) return get_tab_urls_from_sessionstore(json_data) def close_tabs( - file_name_session: Path, - urls: List[str], - log: logging.Logger, -): + file_name_session: Path, + urls: list[str], + log: logging.Logger, +) -> None: modified = False json_data = get_sessionstore(file_name_session) - for w in json_data['windows']: - for t in reversed(w['tabs']): - i = t['index'] - 1 - tab_url = t['entries'][i]['url'] + for w in json_data["windows"]: + for t in reversed(w["tabs"]): + i = t["index"] - 1 + tab_url = t["entries"][i]["url"] if tab_url in urls: - w['tabs'].remove(t) + w["tabs"].remove(t) - log.info(f'Removing tab: {tab_url}') + log.info(f"Removing tab: {tab_url}") modified = True if modified: - with open(file_name_session, 'wb') as f: + with open(file_name_session, "wb") as f: mozlz4a.dumps_json(f, json_data) def close_duplicate_tabs( - file_name_session: Path, - log: logging.Logger, -): + file_name_session: Path, + log: logging.Logger, +) -> None: json_data = get_sessionstore(file_name_session) urls = get_tab_urls_from_sessionstore(json_data) @@ -72,43 +71,43 @@ def close_duplicate_tabs( if count > 1 } - for w in json_data['windows']: - for t in reversed(w['tabs']): - i = t['index'] - 1 - tab_url = t['entries'][i]['url'] + for w in json_data["windows"]: + for t in reversed(w["tabs"]): + i = t["index"] - 1 + tab_url = t["entries"][i]["url"] if need_to_delete.get(tab_url, 0) > 0: - w['tabs'].remove(t) + w["tabs"].remove(t) need_to_delete[tab_url] -= 1 - log.info(f'Removed duplicate: {tab_url}') + log.info(f"Removed duplicate: {tab_url}") if need_to_delete: - with open(file_name_session, 'wb') as f: + with open(file_name_session, "wb") as f: mozlz4a.dumps_json(f, json_data) def get_bookmark_urls( file_name_places: Path -) -> List[str]: +) -> list[str]: connect = sqlite3.connect(file_name_places) - sql = 'SELECT p.url FROM moz_bookmarks b, moz_places p WHERE p.id = b.fk' + sql = "SELECT p.url FROM moz_bookmarks b, moz_places p WHERE p.id = b.fk" return [place_url for place_url, in connect.execute(sql)] def close_bookmarks( - file_name_places: Path, - urls: List[str], - log: logging.Logger, -): + file_name_places: Path, + urls: list[str], + log: logging.Logger, +) -> None: with sqlite3.connect(file_name_places) as connect: for url in urls: - sql = 'SELECT id FROM moz_bookmarks WHERE fk = (SELECT id FROM moz_places WHERE url = ?)' + sql = "SELECT id FROM moz_bookmarks WHERE fk = (SELECT id FROM moz_places WHERE url = ?)" result = connect.execute(sql, [url]).fetchone() if not result: continue log.info(f"Deleted bookmark for url: {url}") - sql = 'DELETE FROM moz_bookmarks WHERE id = ?' + sql = "DELETE FROM moz_bookmarks WHERE id = ?" bookmark_id = result[0] connect.execute(sql, [bookmark_id]) diff --git a/firefox/common.py b/firefox/common.py index 4dae7f62d..84f3b8578 100644 --- a/firefox/common.py +++ b/firefox/common.py @@ -1,41 +1,43 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import logging import sys - +from logging.handlers import RotatingFileHandler from pathlib import Path DIR = Path(__file__).resolve().parent -DIR_LOGS = DIR / 'logs' +DIR_LOGS = DIR / "logs" def get_logger(file_name: str, dir_name=DIR_LOGS): dir_name = Path(dir_name).resolve() dir_name.mkdir(parents=True, exist_ok=True) - file_name = dir_name / (Path(file_name).resolve().name + '.log') + file_name = dir_name / (Path(file_name).resolve().name + ".log") - log = logging.getLogger(__name__) + log = logging.getLogger(__file__) log.setLevel(logging.DEBUG) - formatter = logging.Formatter('[%(asctime)s] %(filename)s[LINE:%(lineno)d] %(levelname)-8s %(message)s') + formatter = logging.Formatter( + "[%(asctime)s] %(filename)s[LINE:%(lineno)d] %(levelname)-8s %(message)s" + ) - fh = logging.FileHandler(file_name, encoding='utf-8') + fh = RotatingFileHandler( + file_name, maxBytes=10_000_000, backupCount=5, encoding="utf-8" + ) fh.setLevel(logging.DEBUG) + fh.setFormatter(formatter) + log.addHandler(fh) ch = logging.StreamHandler(stream=sys.stdout) ch.setLevel(logging.DEBUG) - - fh.setFormatter(formatter) ch.setFormatter(formatter) - - log.addHandler(fh) log.addHandler(ch) return log diff --git a/firefox/delete_tabs_and_bookmarks/close_tabs_with_bookmarks.py b/firefox/delete_tabs_and_bookmarks/close_tabs_with_bookmarks.py index 21dd89d13..d7f951e3c 100644 --- a/firefox/delete_tabs_and_bookmarks/close_tabs_with_bookmarks.py +++ b/firefox/delete_tabs_and_bookmarks/close_tabs_with_bookmarks.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import sys @@ -15,16 +15,16 @@ from config import file_name_places, file_name_session -log = get_logger(__file__, DIR / 'logs') +log = get_logger(__file__, DIR / "logs") -log.info('Start') +log.info("Start") tab_urls = api.get_tab_urls(file_name_session) -log.info(f'Tab urls: {len(tab_urls)}') +log.info(f"Tab urls: {len(tab_urls)}") bookmark_urls = api.get_bookmark_urls(file_name_places) -log.info(f'Bookmark urls: {len(bookmark_urls)}') +log.info(f"Bookmark urls: {len(bookmark_urls)}") api.close_tabs(file_name_session, bookmark_urls, log) -log.info('Finish') +log.info("Finish") diff --git a/firefox/delete_tabs_and_bookmarks/config.py b/firefox/delete_tabs_and_bookmarks/config.py index 7674b7e1e..7abc59fe2 100644 --- a/firefox/delete_tabs_and_bookmarks/config.py +++ b/firefox/delete_tabs_and_bookmarks/config.py @@ -1,18 +1,18 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import os.path from pathlib import Path -profile_name = 'ef941a74.dev-edition-default' +profile_name = "ef941a74.dev-edition-default" # Example: C:\Users\<user>\AppData\Roaming\Mozilla\Firefox\Profiles\<profile>\places.sqlite -dir_profiles = Path(os.path.expandvars(r'%AppData%\Mozilla\Firefox\Profiles')) +dir_profiles = Path(os.path.expandvars(r"%AppData%\Mozilla\Firefox\Profiles")) dir_profile = dir_profiles / profile_name -file_name_places = dir_profile / 'places.sqlite' -file_name_session = dir_profile / 'sessionstore.jsonlz4' +file_name_places = dir_profile / "places.sqlite" +file_name_session = dir_profile / "sessionstore.jsonlz4" diff --git a/firefox/delete_tabs_and_bookmarks/delete_duplicate_tabs.py b/firefox/delete_tabs_and_bookmarks/delete_duplicate_tabs.py index fa3122a7d..595a5efc9 100644 --- a/firefox/delete_tabs_and_bookmarks/delete_duplicate_tabs.py +++ b/firefox/delete_tabs_and_bookmarks/delete_duplicate_tabs.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import sys @@ -15,9 +15,9 @@ from config import file_name_session -log = get_logger(__file__, DIR / 'logs') +log = get_logger(__file__, DIR / "logs") -log.info('Start') +log.info("Start") api.close_duplicate_tabs(file_name_session, log) -log.info('Finish') +log.info("Finish") diff --git a/firefox/delete_tabs_and_bookmarks/delete_tabs_and_bookmarks_with_404.py b/firefox/delete_tabs_and_bookmarks/delete_tabs_and_bookmarks_with_404.py index 4a7647315..e1c597acf 100644 --- a/firefox/delete_tabs_and_bookmarks/delete_tabs_and_bookmarks_with_404.py +++ b/firefox/delete_tabs_and_bookmarks/delete_tabs_and_bookmarks_with_404.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import logging @@ -20,40 +20,40 @@ from config import file_name_places, file_name_session -log = get_logger(__file__, DIR / 'logs') +log = get_logger(__file__, DIR / "logs") def is_404( - url: str, - log: logging.Logger, + url: str, + log: logging.Logger, ) -> bool: while True: try: rs = requests.get(url) return rs.status_code == 404 except Exception as e: - log.error(f'Error {str(e)!r} on {url}') + log.error(f"Error {str(e)!r} on {url}") return False # Skip url finally: time.sleep(2) -log.info('Start') +log.info("Start") tab_urls = api.get_tab_urls(file_name_session) -log.info(f'Tab urls: {len(tab_urls)}') +log.info(f"Tab urls: {len(tab_urls)}") bookmark_urls = api.get_bookmark_urls(file_name_places) -log.info(f'Bookmark urls: {len(bookmark_urls)}') +log.info(f"Bookmark urls: {len(bookmark_urls)}") urls = set(tab_urls + bookmark_urls) count = len(urls) -log.info(f'Urls to processing: {count}') +log.info(f"Urls to processing: {count}") for i, url in enumerate(urls, 1): # Every 10% if i % (count // 10) == 0: - log.info(f'Processed {i / count:.0%}') + log.info(f"Processed {i / count:.0%}") if not is_404(url, log): continue @@ -61,4 +61,4 @@ def is_404( api.close_tabs(file_name_session, [url], log) api.close_bookmarks(file_name_places, [url], log) -log.info('Finish') +log.info("Finish") diff --git a/firefox/delete_tabs_and_bookmarks/show_stats.py b/firefox/delete_tabs_and_bookmarks/show_stats.py new file mode 100644 index 000000000..4e6e7d404 --- /dev/null +++ b/firefox/delete_tabs_and_bookmarks/show_stats.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import sys +from pathlib import Path + +DIR = Path(__file__).resolve().parent +sys.path.append(str(DIR.parent)) + +import api +from config import file_name_places, file_name_session + + +tab_urls = api.get_tab_urls(file_name_session) +print(f"Tab urls: {len(tab_urls)}") + +bookmark_urls = api.get_bookmark_urls(file_name_places) +print(f"Bookmark urls: {len(bookmark_urls)}") \ No newline at end of file diff --git a/firefox/jsonlz4_mozLz4/mozlz4a.py b/firefox/jsonlz4_mozLz4/mozlz4a.py index 9f5c9906f..bbee46ea8 100644 --- a/firefox/jsonlz4_mozLz4/mozlz4a.py +++ b/firefox/jsonlz4_mozLz4/mozlz4a.py @@ -79,8 +79,8 @@ def loads_json(file_obj: BinaryIO) -> dict: return json.loads(data) -def dumps_json(file_obj: BinaryIO, json_data: dict): - data = json.dumps(json_data).encode('utf-8') +def dumps_json(file_obj: BinaryIO, json_data: dict) -> None: + data = json.dumps(json_data).encode("utf-8") compressed = compress_data(data) file_obj.write(compressed) @@ -88,33 +88,37 @@ def dumps_json(file_obj: BinaryIO, json_data: dict): if __name__ == "__main__": from argparse import ArgumentParser + argparser = ArgumentParser(description="MozLz4a compression/decompression utility") argparser.add_argument( - "-d", "--decompress", "--uncompress", + "-d", + "--decompress", + "--uncompress", action="store_true", - help="Decompress the input file instead of compressing it." - ) - argparser.add_argument( - "in_file", - help="Path to input file." - ) - argparser.add_argument( - "out_file", - help="Path to output file." + help="Decompress the input file instead of compressing it.", ) + argparser.add_argument("in_file", help="Path to input file.") + argparser.add_argument("out_file", help="Path to output file.") parsed_args = argparser.parse_args() try: in_file = open(parsed_args.in_file, "rb") except IOError as e: - print("Could not open input file `%s' for reading: %s" % (parsed_args.in_file, e), file=sys.stderr) + print( + "Could not open input file `%s' for reading: %s" % (parsed_args.in_file, e), + file=sys.stderr, + ) sys.exit(2) - + try: out_file = open(parsed_args.out_file, "wb") except IOError as e: - print("Could not open output file `%s' for writing: %s" % (parsed_args.out_file, e), file=sys.stderr) + print( + "Could not open output file `%s' for writing: %s" + % (parsed_args.out_file, e), + file=sys.stderr, + ) sys.exit(3) try: @@ -123,13 +127,19 @@ def dumps_json(file_obj: BinaryIO, json_data: dict): else: data = compress(in_file) except Exception as e: - print("Could not compress/decompress file `%s': %s" % (parsed_args.in_file, e), file=sys.stderr) + print( + "Could not compress/decompress file `%s': %s" % (parsed_args.in_file, e), + file=sys.stderr, + ) sys.exit(4) try: out_file.write(data) except IOError as e: - print("Could not write to output file `%s': %s" % (parsed_args.out_file, e), file=sys.stderr) + print( + "Could not write to output file `%s': %s" % (parsed_args.out_file, e), + file=sys.stderr, + ) sys.exit(5) finally: out_file.close() diff --git a/firefox/jsonlz4_mozLz4/test/test.py b/firefox/jsonlz4_mozLz4/test/test.py index 57286dc27..2179a9864 100644 --- a/firefox/jsonlz4_mozLz4/test/test.py +++ b/firefox/jsonlz4_mozLz4/test/test.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import io @@ -18,16 +18,16 @@ import mozlz4a -FILE_TEST = DIR / 'recovery.jsonlz4' +FILE_TEST = DIR / "recovery.jsonlz4" class TestAll(unittest.TestCase): - def test_1_exists_file(self): + def test_1_exists_file(self) -> None: self.assertTrue(FILE_TEST.exists()) self.assertTrue(FILE_TEST.read_bytes()) - def test_decompress_compress(self): - with open(FILE_TEST, 'rb') as f: + def test_decompress_compress(self) -> None: + with open(FILE_TEST, "rb") as f: expected_data = mozlz4a.decompress(f) bytes_io = io.BytesIO(expected_data) @@ -38,16 +38,16 @@ def test_decompress_compress(self): self.assertEqual(expected_data, data) - def test_compress_decompress_data(self): - expected_data = str(uuid.uuid4()).encode('utf-8') + def test_compress_decompress_data(self) -> None: + expected_data = str(uuid.uuid4()).encode("utf-8") compressed_data = mozlz4a.compress_data(expected_data) data = mozlz4a.decompress_data(compressed_data) self.assertEqual(expected_data, data) - def test_json(self): - with open(FILE_TEST, 'rb') as f: + def test_json(self) -> None: + with open(FILE_TEST, "rb") as f: expected_json_data = mozlz4a.loads_json(f) bytes_io = io.BytesIO() @@ -59,5 +59,5 @@ def test_json(self): self.assertEqual(expected_json_data, json_data) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/firefox/parse_profile__places.sqlite/dump_dns-shop_products.py b/firefox/parse_profile__places.sqlite/dump_dns-shop_products.py index f356eb262..66c0e6ad5 100644 --- a/firefox/parse_profile__places.sqlite/dump_dns-shop_products.py +++ b/firefox/parse_profile__places.sqlite/dump_dns-shop_products.py @@ -1,25 +1,23 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import json import re import sqlite3 -from collections import OrderedDict - def process_title(title: str) -> str: - if title.startswith('Купить '): - title = title[len('Купить '):] + if title.startswith("Купить "): + title = title[len("Купить ") :] - match = re.search('(.+?) – купить в интернет магазине DNS', title) + match = re.search("(.+?) – купить в интернет магазине DNS", title) if match: title = match.group(1) - match = re.search('(.+?) в интернет магазине DNS', title) + match = re.search("(.+?) в интернет магазине DNS", title) if match: title = match.group(1) @@ -29,35 +27,34 @@ def process_title(title: str) -> str: tracked_products = [] # C:\Users\<user>\AppData\Roaming\Mozilla\Firefox\Profiles\<profile>\places.sqlite -with sqlite3.connect('places.sqlite') as connect: +with sqlite3.connect("places.sqlite") as connect: sql = "select url, title from moz_places where title is not null and url like '%//www.dns-shop.ru/product/%/%/'" for url, title in connect.execute(sql): if not title: continue - if url.startswith('http://'): - url = 'https://' + url[len('http://'):] + if url.startswith("http://"): + url = "https://" + url[len("http://") :] - if not url.startswith('https://www.dns-shop.ru/product/'): + if not url.startswith("https://www.dns-shop.ru/product/"): continue - if not re.fullmatch(r'https://www\.dns-shop\.ru/product/\w+/[^/]+/', url): + if not re.fullmatch(r"https://www\.dns-shop\.ru/product/\w+/[^/]+/", url): continue title = process_title(title) print(url, title) - tracked_products.append( - OrderedDict([ - ('title', title), - ('url', url), - ]) - ) + tracked_products.append({ + "title": title, + "url": url, + }) -tracked_products.sort(key=lambda x: x['title']) +tracked_products.sort(key=lambda x: x["title"]) json.dump( tracked_products, - open('tracked_products.json', 'w', encoding='utf-8'), - indent=4, ensure_ascii=False + open("tracked_products.json", "w", encoding="utf-8"), + indent=4, + ensure_ascii=False, ) diff --git a/flask-paginate__example/app.py b/flask-paginate__example/app.py index 0ac4ef4a5..57ed5fd7c 100644 --- a/flask-paginate__example/app.py +++ b/flask-paginate__example/app.py @@ -1,131 +1,143 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -from __future__ import unicode_literals + import sqlite3 + +import click + from flask import Flask, render_template, g, current_app from flask_paginate import Pagination, get_page_args -import click + click.disable_unicode_literals_warning = True app = Flask(__name__) -app.config.from_pyfile('app.cfg') +app.config.from_pyfile("app.cfg") @app.before_request -def before_request(): - g.conn = sqlite3.connect('test.db') +def before_request() -> None: + g.conn = sqlite3.connect("test.db") g.conn.row_factory = sqlite3.Row g.cur = g.conn.cursor() @app.teardown_request -def teardown(error): - if hasattr(g, 'conn'): +def teardown(error) -> None: + if hasattr(g, "conn"): g.conn.close() -@app.route('/') +@app.route("/") def index(): - g.cur.execute('select count(*) from users') + g.cur.execute("select count(*) from users") total = g.cur.fetchone()[0] page, per_page, offset = get_page_args() - sql = 'select name from users order by name limit {}, {}'\ - .format(offset, per_page) + sql = f"select name from users order by name limit {offset}, {per_page}" g.cur.execute(sql) users = g.cur.fetchall() - pagination = get_pagination(page=page, - per_page=per_page, - total=total, - record_name='users', - format_total=True, - format_number=True, - ) - return render_template('index.html', users=users, - page=page, - per_page=per_page, - pagination=pagination, - ) - - -@app.route('/users/', defaults={'page': 1}) -@app.route('/users', defaults={'page': 1}) -@app.route('/users/page/<int:page>/') -@app.route('/users/page/<int:page>') + pagination = get_pagination( + page=page, + per_page=per_page, + total=total, + record_name="users", + format_total=True, + format_number=True, + ) + return render_template( + "index.html", + users=users, + page=page, + per_page=per_page, + pagination=pagination, + ) + + +@app.route("/users/", defaults={"page": 1}) +@app.route("/users", defaults={"page": 1}) +@app.route("/users/page/<int:page>/") +@app.route("/users/page/<int:page>") def users(page): - g.cur.execute('select count(*) from users') + g.cur.execute("select count(*) from users") total = g.cur.fetchone()[0] page, per_page, offset = get_page_args() - sql = 'select name from users order by name limit {}, {}'\ - .format(offset, per_page) + sql = f"select name from users order by name limit {offset}, {per_page}" g.cur.execute(sql) users = g.cur.fetchall() - pagination = get_pagination(page=page, - per_page=per_page, - total=total, - record_name='users', - format_total=True, - format_number=True, - ) - return render_template('index.html', users=users, - page=page, - per_page=per_page, - pagination=pagination, - active_url='users-page-url', - ) - - -@app.route('/search/<name>/') -@app.route('/search/<name>') + pagination = get_pagination( + page=page, + per_page=per_page, + total=total, + record_name="users", + format_total=True, + format_number=True, + ) + return render_template( + "index.html", + users=users, + page=page, + per_page=per_page, + pagination=pagination, + active_url="users-page-url", + ) + + +@app.route("/search/<name>/") +@app.route("/search/<name>") def search(name): """The function is used to test multi values url.""" - sql = 'select count(*) from users where name like ?' - args = ('%{}%'.format(name), ) + sql = "select count(*) from users where name like ?" + args = (f"%{name}%",) g.cur.execute(sql, args) total = g.cur.fetchone()[0] page, per_page, offset = get_page_args() - sql = 'select * from users where name like ? limit {}, {}' + sql = "select * from users where name like ? limit {}, {}" g.cur.execute(sql.format(offset, per_page), args) users = g.cur.fetchall() - pagination = get_pagination(page=page, - per_page=per_page, - total=total, - record_name='users', - ) - return render_template('index.html', users=users, - page=page, - per_page=per_page, - pagination=pagination, - ) + pagination = get_pagination( + page=page, + per_page=per_page, + total=total, + record_name="users", + ) + return render_template( + "index.html", + users=users, + page=page, + per_page=per_page, + pagination=pagination, + ) def get_css_framework(): - return current_app.config.get('CSS_FRAMEWORK', 'bootstrap3') + return current_app.config.get("CSS_FRAMEWORK", "bootstrap3") def get_link_size(): - return current_app.config.get('LINK_SIZE', 'sm') + return current_app.config.get("LINK_SIZE", "sm") def show_single_page_or_not(): - return current_app.config.get('SHOW_SINGLE_PAGE', False) + return current_app.config.get("SHOW_SINGLE_PAGE", False) def get_pagination(**kwargs): - kwargs.setdefault('record_name', 'records') - return Pagination(css_framework=get_css_framework(), - link_size=get_link_size(), - show_single_page=show_single_page_or_not(), - **kwargs - ) + kwargs.setdefault("record_name", "records") + return Pagination( + css_framework=get_css_framework(), + link_size=get_link_size(), + show_single_page=show_single_page_or_not(), + **kwargs + ) @click.command() -@click.option('--port', '-p', default=5000, help='listening port') -def run(port): +@click.option("--port", "-p", default=5000, help="listening port") +def run(port) -> None: app.run(debug=True, port=port) -if __name__ == '__main__': + +if __name__ == "__main__": run() diff --git a/flask-paginate__example/sql.py b/flask-paginate__example/sql.py index 3a8a055bc..21fc60f4b 100644 --- a/flask-paginate__example/sql.py +++ b/flask-paginate__example/sql.py @@ -1,44 +1,45 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -from __future__ import unicode_literals import sqlite3 import click + click.disable_unicode_literals_warning = True -sql = '''create table if not exists users( + +sql = """create table if not exists users( id integer primary key autoincrement, name varchar(30) ) -''' +""" @click.group() -def cli(): +def cli() -> None: pass -@cli.command(short_help='initialize database and tables') -def init_db(): - conn = sqlite3.connect('test.db') +@cli.command(short_help="initialize database and tables") +def init_db() -> None: + conn = sqlite3.connect("test.db") cur = conn.cursor() cur.execute(sql) conn.commit() conn.close() -@cli.command(short_help='fill records to database') -@click.option('--total', '-t', default=300, help='fill data for example') -def fill_data(total): - conn = sqlite3.connect('test.db') +@cli.command(short_help="fill records to database") +@click.option("--total", "-t", default=300, help="fill data for example") +def fill_data(total) -> None: + conn = sqlite3.connect("test.db") cur = conn.cursor() for i in range(total): - cur.execute('insert into users (name) values (?)', ['name' + str(i)]) + cur.execute("insert into users (name) values (?)", ["name" + str(i)]) conn.commit() conn.close() -if __name__ == '__main__': +if __name__ == "__main__": cli() diff --git a/flask__jquery_easyui__examples/datagrid__games/main.py b/flask__jquery_easyui__examples/datagrid__games/main.py index 3a45e7ed8..09bace2da 100644 --- a/flask__jquery_easyui__examples/datagrid__games/main.py +++ b/flask__jquery_easyui__examples/datagrid__games/main.py @@ -1,26 +1,27 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: http://www.jeasyui.com/tutorial/index.php # SOURCE: http://www.jeasyui.com/tutorial/app/crud.php -from flask import Flask, render_template, jsonify, request -app = Flask(__name__, static_folder='../static') - import logging -logging.basicConfig(level=logging.DEBUG) +import sqlite3 -import datetime as DT +from datetime import datetime -import sqlite3 +from flask import Flask, render_template, jsonify, request + + +app = Flask(__name__, static_folder="../static") +logging.basicConfig(level=logging.DEBUG) def create_connect(fields_as_dict=False): - connect = sqlite3.connect('games.sqlite') + connect = sqlite3.connect("games.sqlite") if fields_as_dict: connect.row_factory = sqlite3.Row @@ -33,27 +34,27 @@ def index(): return render_template("index.html") -@app.route("/get_games", methods=['POST', 'GET']) +@app.route("/get_games", methods=["POST", "GET"]) def get_games(): with create_connect(fields_as_dict=True) as connect: - sql = ''' + sql = """ SELECT id, name, price, append_date FROM game ORDER BY name - ''' + """ items = list(map(dict, connect.execute(sql).fetchall())) return jsonify(items) -@app.route("/save_game", methods=['POST', 'GET']) +@app.route("/save_game", methods=["POST", "GET"]) def save_game(): try: - name = request.form['name'] - price = request.form['price'] + name = request.form["name"] + price = request.form["price"] # 2017-06-03 21:21:17 - append_date = DT.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + append_date = datetime.now().strftime("%Y-%m-%d %H:%M:%S") with create_connect() as connect: sql = "INSERT INTO Game (name, price, append_date) VALUES (?,?,?)" @@ -61,9 +62,9 @@ def save_game(): connect.execute(sql, (name, price, append_date)) except Exception as e: - return jsonify({'errorMsg': f'Some errors occured: "{e}"'}) + return jsonify({"errorMsg": f'Some errors occured: "{e}"'}) - return jsonify({'success': True}) + return jsonify({"success": True}) # return jsonify({ # 'id': rs.lastrowid, # 'name': name, @@ -72,42 +73,42 @@ def save_game(): # }) -@app.route("/update_game", methods=['POST', 'GET']) +@app.route("/update_game", methods=["POST", "GET"]) def update_game(): try: - id_ = request.args['id'] - name = request.form['name'] - price = request.form['price'] + id_ = request.args["id"] + name = request.form["name"] + price = request.form["price"] with create_connect() as connect: sql = "UPDATE Game SET name = ?, price = ? WHERE id = ?" connect.execute(sql, (name, price, id_)) except Exception as e: - return jsonify({'errorMsg': f'Some errors occured: "{e}"'}) + return jsonify({"errorMsg": f'Some errors occured: "{e}"'}) - return jsonify({'success': True}) + return jsonify({"success": True}) -@app.route("/delete_game", methods=['POST', 'GET']) +@app.route("/delete_game", methods=["POST", "GET"]) def delete_game(): try: - id_ = request.form['id'] + id_ = request.form["id"] with create_connect() as connect: sql = "DELETE FROM Game WHERE id = ?" connect.execute(sql, (id_,)) except Exception as e: - return jsonify({'errorMsg': f'Some errors occured: "{e}"'}) + return jsonify({"errorMsg": f'Some errors occured: "{e}"'}) - return jsonify({'success': True}) + return jsonify({"success": True}) # TODO: диалог редактирования кнопку Ок показывает активной только если данные изменены -if __name__ == '__main__': +if __name__ == "__main__": # app.debug = True # Localhost diff --git a/flask__jquery_easyui__examples/datalist__basic__and__group/main.py b/flask__jquery_easyui__examples/datalist__basic__and__group/main.py index 65302bad3..8b1d6098d 100644 --- a/flask__jquery_easyui__examples/datalist__basic__and__group/main.py +++ b/flask__jquery_easyui__examples/datalist__basic__and__group/main.py @@ -1,23 +1,25 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: http://www.jeasyui.com/demo/main/index.php?plugin=DataList&theme=default # SOURCE: http://www.jeasyui.com/documentation/datalist.php +import logging from flask import Flask, render_template_string, jsonify -app = Flask(__name__, static_folder='../static') -import logging + +app = Flask(__name__, static_folder="../static") logging.basicConfig(level=logging.DEBUG) @app.route("/") def index(): - return render_template_string("""\ + return render_template_string( + """\ <html> <head> <meta content='text/html; charset=UTF-8' http-equiv='Content-Type'/> @@ -144,7 +146,8 @@ def index(): </script> </body> </html> - """) + """ + ) @app.route("/datalist_data.json") @@ -162,11 +165,11 @@ def get_datalist_data(): {"text": "Logitech Gaming Keyboard G110", "group": "Keyboard"}, {"text": "Nikon COOLPIX L26 16.1 MP", "group": "Camera"}, {"text": "Canon PowerShot A1300", "group": "Camera"}, - {"text": "Canon PowerShot A2300", "group": "Camera"} + {"text": "Canon PowerShot A2300", "group": "Camera"}, ]) -if __name__ == '__main__': +if __name__ == "__main__": app.debug = True # Localhost diff --git a/flask__jquery_easyui__examples/layout__datagrid__games__two_horizontal/main.py b/flask__jquery_easyui__examples/layout__datagrid__games__two_horizontal/main.py index a741cab1c..78a7f63c3 100644 --- a/flask__jquery_easyui__examples/layout__datagrid__games__two_horizontal/main.py +++ b/flask__jquery_easyui__examples/layout__datagrid__games__two_horizontal/main.py @@ -1,24 +1,25 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: http://www.jeasyui.com/tutorial/index.php # SOURCE: http://www.jeasyui.com/tutorial/app/crud.php +import logging +import sqlite3 + from flask import Flask, render_template, jsonify, request -app = Flask(__name__, static_folder='../static') -import logging -logging.basicConfig(level=logging.DEBUG) -import sqlite3 +app = Flask(__name__, static_folder="../static") +logging.basicConfig(level=logging.DEBUG) def create_connect(fields_as_dict=False): - connect = sqlite3.connect('../datagrid__games/games.sqlite') + connect = sqlite3.connect("../datagrid__games/games.sqlite") if fields_as_dict: connect.row_factory = sqlite3.Row @@ -31,23 +32,23 @@ def index(): return render_template("index.html") -@app.route("/get_games", methods=['POST', 'GET']) +@app.route("/get_games", methods=["POST", "GET"]) def get_games(): - kind = request.args['kind'] + kind = request.args["kind"] with create_connect(fields_as_dict=True) as connect: - sql = ''' + sql = """ SELECT id, name, price, append_date FROM game WHERE kind = ? ORDER BY name - ''' + """ items = list(map(dict, connect.execute(sql, (kind,)).fetchall())) return jsonify(items) -if __name__ == '__main__': +if __name__ == "__main__": # app.debug = True # Localhost diff --git a/flask__jquery_easyui__examples/set_locale/main.py b/flask__jquery_easyui__examples/set_locale/main.py index b16182e3a..64f9f7ff3 100644 --- a/flask__jquery_easyui__examples/set_locale/main.py +++ b/flask__jquery_easyui__examples/set_locale/main.py @@ -1,23 +1,25 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: http://www.jeasyui.com/tutorial/index.php # SOURCE: http://www.jeasyui.com/tutorial/app/crud.php +import logging from flask import Flask, render_template_string -app = Flask(__name__, static_folder='../static') -import logging + +app = Flask(__name__, static_folder="../static") logging.basicConfig(level=logging.DEBUG) @app.route("/") def index(): - return render_template_string("""\ + return render_template_string( + """\ <html> <head> <meta content='text/html; charset=UTF-8' http-equiv='Content-Type'/> @@ -45,10 +47,11 @@ def index(): <div class="easyui-calendar" style="width:250px;height:250px;"></div> </body> </html> - """) + """ + ) -if __name__ == '__main__': +if __name__ == "__main__": # app.debug = True # Localhost diff --git a/flask__jquery_easyui__examples/set_theme/main.py b/flask__jquery_easyui__examples/set_theme/main.py index a6b5661e9..df878dac3 100644 --- a/flask__jquery_easyui__examples/set_theme/main.py +++ b/flask__jquery_easyui__examples/set_theme/main.py @@ -1,22 +1,24 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: http://www.jeasyui.com/tutorial/ +import logging from flask import Flask, render_template_string, request, jsonify -app = Flask(__name__, static_folder='../static') -import logging + +app = Flask(__name__, static_folder="../static") logging.basicConfig(level=logging.DEBUG) @app.route("/") def index(): - return render_template_string("""\ + return render_template_string( + """\ <html> <head> <meta content='text/html; charset=UTF-8' http-equiv='Content-Type'/> @@ -52,21 +54,25 @@ def index(): </iframe> </body> </html> - """) + """ + ) @app.route("/get_html") def get_html(): - theme = request.args.get('theme', 'default') + theme = request.args.get("theme", "default") - return render_template_string("""\ + return render_template_string( + """\ <html> <head> <meta content='text/html; charset=UTF-8' http-equiv='Content-Type'/> <title>set_theme

{{ theme }}

- + @@ -97,26 +103,111 @@ def get_html(): - """, theme=theme) + """, + theme=theme, + ) @app.route("/datagrid_data1.json") def get_datalist_data(): - return jsonify({"total": 28, "rows": [ - {"productid": "FI-SW-01", "productname": "Koi", "unitcost": "10.00", "status": "P", "listprice": "36.50", "attr1": "Large", "itemid": "EST-1"}, - {"productid": "K9-DL-01", "productname": "Dalmation", "unitcost": "12.00", "status": "P", "listprice": "18.50", "attr1": "Spotted Adult Female", "itemid": "EST-10"}, - {"productid": "RP-SN-01", "productname": "Rattlesnake", "unitcost": "12.00", "status": "P", "listprice": "38.50", "attr1": "Venomless", "itemid": "EST-11"}, - {"productid": "RP-SN-01", "productname": "Rattlesnake", "unitcost": "12.00", "status": "P", "listprice": "26.50", "attr1": "Rattleless", "itemid": "EST-12"}, - {"productid": "RP-LI-02", "productname": "Iguana", "unitcost": "12.00", "status": "P", "listprice": "35.50", "attr1": "Green Adult", "itemid": "EST-13"}, - {"productid": "FL-DSH-01", "productname": "Manx", "unitcost": "12.00", "status": "P", "listprice": "158.50", "attr1": "Tailless", "itemid": "EST-14"}, - {"productid": "FL-DSH-01", "productname": "Manx", "unitcost": "12.00", "status": "P", "listprice": "83.50", "attr1": "With tail", "itemid": "EST-15"}, - {"productid": "FL-DLH-02", "productname": "Persian", "unitcost": "12.00", "status": "P", "listprice": "23.50", "attr1": "Adult Female", "itemid": "EST-16"}, - {"productid": "FL-DLH-02", "productname": "Persian", "unitcost": "12.00", "status": "P", "listprice": "89.50", "attr1": "Adult Male", "itemid": "EST-17"}, - {"productid": "AV-CB-01", "productname": "Amazon Parrot", "unitcost": "92.00", "status": "P", "listprice": "63.50", "attr1": "Adult Male", "itemid": "EST-18"} - ]}) - - -if __name__ == '__main__': + return jsonify({ + "total": 28, + "rows": [ + { + "productid": "FI-SW-01", + "productname": "Koi", + "unitcost": "10.00", + "status": "P", + "listprice": "36.50", + "attr1": "Large", + "itemid": "EST-1", + }, + { + "productid": "K9-DL-01", + "productname": "Dalmation", + "unitcost": "12.00", + "status": "P", + "listprice": "18.50", + "attr1": "Spotted Adult Female", + "itemid": "EST-10", + }, + { + "productid": "RP-SN-01", + "productname": "Rattlesnake", + "unitcost": "12.00", + "status": "P", + "listprice": "38.50", + "attr1": "Venomless", + "itemid": "EST-11", + }, + { + "productid": "RP-SN-01", + "productname": "Rattlesnake", + "unitcost": "12.00", + "status": "P", + "listprice": "26.50", + "attr1": "Rattleless", + "itemid": "EST-12", + }, + { + "productid": "RP-LI-02", + "productname": "Iguana", + "unitcost": "12.00", + "status": "P", + "listprice": "35.50", + "attr1": "Green Adult", + "itemid": "EST-13", + }, + { + "productid": "FL-DSH-01", + "productname": "Manx", + "unitcost": "12.00", + "status": "P", + "listprice": "158.50", + "attr1": "Tailless", + "itemid": "EST-14", + }, + { + "productid": "FL-DSH-01", + "productname": "Manx", + "unitcost": "12.00", + "status": "P", + "listprice": "83.50", + "attr1": "With tail", + "itemid": "EST-15", + }, + { + "productid": "FL-DLH-02", + "productname": "Persian", + "unitcost": "12.00", + "status": "P", + "listprice": "23.50", + "attr1": "Adult Female", + "itemid": "EST-16", + }, + { + "productid": "FL-DLH-02", + "productname": "Persian", + "unitcost": "12.00", + "status": "P", + "listprice": "89.50", + "attr1": "Adult Male", + "itemid": "EST-17", + }, + { + "productid": "AV-CB-01", + "productname": "Amazon Parrot", + "unitcost": "92.00", + "status": "P", + "listprice": "63.50", + "attr1": "Adult Male", + "itemid": "EST-18", + }, + ], + }) + + +if __name__ == "__main__": app.debug = True # Localhost diff --git a/flask__webservers/3_ways_to_get_table/ajax__json/main.py b/flask__webservers/3_ways_to_get_table/ajax__json/main.py index 5a5b15d82..aeb481969 100644 --- a/flask__webservers/3_ways_to_get_table/ajax__json/main.py +++ b/flask__webservers/3_ways_to_get_table/ajax__json/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import logging @@ -10,7 +10,7 @@ from flask import Flask, render_template_string, jsonify # Для импорта common.py -sys.path.append('..') +sys.path.append("..") from common import generate_table @@ -21,7 +21,8 @@ @app.route("/") def index(): - return render_template_string("""\ + return render_template_string( + """\ @@ -95,7 +96,8 @@ def index(): - """) + """ + ) @app.route("/get_table") @@ -105,7 +107,7 @@ def get_table(): return jsonify(items) -if __name__ == '__main__': +if __name__ == "__main__": app.debug = True # Localhost diff --git a/flask__webservers/3_ways_to_get_table/common.py b/flask__webservers/3_ways_to_get_table/common.py index 745c7f49e..879f6dd02 100644 --- a/flask__webservers/3_ways_to_get_table/common.py +++ b/flask__webservers/3_ways_to_get_table/common.py @@ -1,13 +1,10 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -from typing import List - - -def generate_table(size=20) -> List[List[str]]: +def generate_table(size=20) -> list[list[str]]: items = [[str(i) for i in range(size + 1)]] for i in range(1, size + 1): @@ -18,13 +15,13 @@ def generate_table(size=20) -> List[List[str]]: items.append(row) - items[0][0] = '' + items[0][0] = "" return items -if __name__ == '__main__': +if __name__ == "__main__": print(generate_table(5)) print(generate_table(2)) - assert generate_table(2) == [['', '1', '2'], ['1', '1', '2'], ['2', '2', '4']] + assert generate_table(2) == [["", "1", "2"], ["1", "1", "2"], ["2", "2", "4"]] diff --git a/flask__webservers/3_ways_to_get_table/html__script/main.py b/flask__webservers/3_ways_to_get_table/html__script/main.py index bc6739cc9..ade936069 100644 --- a/flask__webservers/3_ways_to_get_table/html__script/main.py +++ b/flask__webservers/3_ways_to_get_table/html__script/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import json @@ -11,7 +11,7 @@ from flask import Flask, render_template_string # Для импорта common.py -sys.path.append('..') +sys.path.append("..") from common import generate_table @@ -25,7 +25,8 @@ def index(): items = generate_table(10) items = json.dumps(items, ensure_ascii=False) - return render_template_string("""\ + return render_template_string( + """\ @@ -84,10 +85,12 @@ def index(): - """, items=items) + """, + items=items, + ) -if __name__ == '__main__': +if __name__ == "__main__": app.debug = True # Localhost diff --git a/flask__webservers/3_ways_to_get_table/html__tag__table/main.py b/flask__webservers/3_ways_to_get_table/html__tag__table/main.py index 503070cc2..2a572ff07 100644 --- a/flask__webservers/3_ways_to_get_table/html__tag__table/main.py +++ b/flask__webservers/3_ways_to_get_table/html__tag__table/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import logging @@ -10,7 +10,7 @@ from flask import Flask, render_template_string # Для импорта common.py -sys.path.append('..') +sys.path.append("..") from common import generate_table @@ -23,7 +23,8 @@ def index(): items = generate_table(10) - return render_template_string("""\ + return render_template_string( + """\ @@ -70,10 +71,12 @@ def index(): - """, items=items) + """, + items=items, + ) -if __name__ == '__main__': +if __name__ == "__main__": app.debug = True # Localhost diff --git a/flask__webservers/ajax_and_jquery/main.py b/flask__webservers/ajax_and_jquery/main.py index c460506fe..544ec2e8b 100644 --- a/flask__webservers/ajax_and_jquery/main.py +++ b/flask__webservers/ajax_and_jquery/main.py @@ -1,19 +1,23 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" +import logging +from datetime import datetime + from flask import Flask, render_template_string, request, jsonify -app = Flask(__name__) -import logging + +app = Flask(__name__) logging.basicConfig(level=logging.DEBUG) @app.route("/") def index(): - return render_template_string('''\ + return render_template_string( + """\ @@ -78,10 +82,11 @@ def index():

-''') +""" + ) -@app.route("/post_method", methods=['POST']) +@app.route("/post_method", methods=["POST"]) def post_method(): data = request.get_json() print(data) @@ -91,7 +96,6 @@ def post_method(): @app.route("/get_method") def get_method(): - from datetime import datetime data = datetime.today() print(data) diff --git a/flask__webservers/ajax_recursive_ul_from_json/main.py b/flask__webservers/ajax_recursive_ul_from_json/main.py index 0d353a2f1..4d29c35a1 100644 --- a/flask__webservers/ajax_recursive_ul_from_json/main.py +++ b/flask__webservers/ajax_recursive_ul_from_json/main.py @@ -1,11 +1,10 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import logging - from flask import Flask, jsonify, render_template @@ -36,15 +35,15 @@ "children": [ { "title": "Потомок Потомка 1", - "url": "http://domain.com/url2/child1/child2/" + "url": "http://domain.com/url2/child1/child2/", }, { "title": "Потомок Потомка 2", - "url": "http://domain.com/url2/child1/child3/" - } - ] - } - ] + "url": "http://domain.com/url2/child1/child3/", + }, + ], + }, + ], }, { "title": "Родитель 3", @@ -53,9 +52,9 @@ ] -@app.route('/') +@app.route("/") def index(): - return render_template('index.html') + return render_template("index.html") @app.route("/json/") @@ -63,7 +62,7 @@ def get_json(): return jsonify(DATA) -if __name__ == '__main__': +if __name__ == "__main__": app.debug = True # Localhost diff --git a/flask__webservers/bootstrap_4__examples/clickable_table/main.py b/flask__webservers/bootstrap_4__examples/clickable_table/main.py index 008429e7d..3b76e8f9e 100644 --- a/flask__webservers/bootstrap_4__examples/clickable_table/main.py +++ b/flask__webservers/bootstrap_4__examples/clickable_table/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/twbs/bootstrap @@ -13,17 +13,17 @@ from flask import Flask, render_template -app = Flask(__name__, static_folder='../_static') +app = Flask(__name__, static_folder="../_static") logging.basicConfig(level=logging.DEBUG) @app.route("/") def index(): - return render_template('index.html') + return render_template("index.html") -if __name__ == '__main__': +if __name__ == "__main__": app.debug = True # Localhost diff --git a/flask__webservers/bootstrap_4__examples/grid__layout/main.py b/flask__webservers/bootstrap_4__examples/grid__layout/main.py index f7dc24930..1fb24211a 100644 --- a/flask__webservers/bootstrap_4__examples/grid__layout/main.py +++ b/flask__webservers/bootstrap_4__examples/grid__layout/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/twbs/bootstrap @@ -13,17 +13,17 @@ from flask import Flask, render_template -app = Flask(__name__, static_folder='../_static') +app = Flask(__name__, static_folder="../_static") logging.basicConfig(level=logging.DEBUG) @app.route("/") def index(): - return render_template('index.html') + return render_template("index.html") -if __name__ == '__main__': +if __name__ == "__main__": app.debug = True # Localhost diff --git a/flask__webservers/bootstrap_4__examples/shadow/main.py b/flask__webservers/bootstrap_4__examples/shadow/main.py index 4d0bb2ea9..9304065bd 100644 --- a/flask__webservers/bootstrap_4__examples/shadow/main.py +++ b/flask__webservers/bootstrap_4__examples/shadow/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/twbs/bootstrap @@ -13,17 +13,17 @@ from flask import Flask, render_template -app = Flask(__name__, static_folder='../_static') +app = Flask(__name__, static_folder="../_static") logging.basicConfig(level=logging.DEBUG) @app.route("/") def index(): - return render_template('index.html') + return render_template("index.html") -if __name__ == '__main__': +if __name__ == "__main__": app.debug = True # Localhost diff --git a/flask__webservers/bootstrap_4__examples/table/main.py b/flask__webservers/bootstrap_4__examples/table/main.py index 008429e7d..3b76e8f9e 100644 --- a/flask__webservers/bootstrap_4__examples/table/main.py +++ b/flask__webservers/bootstrap_4__examples/table/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/twbs/bootstrap @@ -13,17 +13,17 @@ from flask import Flask, render_template -app = Flask(__name__, static_folder='../_static') +app = Flask(__name__, static_folder="../_static") logging.basicConfig(level=logging.DEBUG) @app.route("/") def index(): - return render_template('index.html') + return render_template("index.html") -if __name__ == '__main__': +if __name__ == "__main__": app.debug = True # Localhost diff --git a/flask__webservers/bootstrap_4__examples/tabs/main.py b/flask__webservers/bootstrap_4__examples/tabs/main.py index 385f08b12..554e3bb78 100644 --- a/flask__webservers/bootstrap_4__examples/tabs/main.py +++ b/flask__webservers/bootstrap_4__examples/tabs/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/twbs/bootstrap @@ -13,17 +13,17 @@ from flask import Flask, render_template -app = Flask(__name__, static_folder='../_static') +app = Flask(__name__, static_folder="../_static") logging.basicConfig(level=logging.DEBUG) @app.route("/") def index(): - return render_template('index.html') + return render_template("index.html") -if __name__ == '__main__': +if __name__ == "__main__": app.debug = True # Localhost diff --git a/flask__webservers/bootstrap_4__examples/tags__badges__game_genres_example/main.py b/flask__webservers/bootstrap_4__examples/tags__badges__game_genres_example/main.py index 3bccd06b2..214eecaf7 100644 --- a/flask__webservers/bootstrap_4__examples/tags__badges__game_genres_example/main.py +++ b/flask__webservers/bootstrap_4__examples/tags__badges__game_genres_example/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/twbs/bootstrap @@ -15,7 +15,7 @@ from flask import Flask, render_template -app = Flask(__name__, static_folder='../_static') +app = Flask(__name__, static_folder="../_static") logging.basicConfig(level=logging.DEBUG) @@ -80,10 +80,10 @@ def index(): ] columns = ["NAME", "URL", "GENRES"] - return render_template('index.html', columns=columns, items=items) + return render_template("index.html", columns=columns, items=items) -if __name__ == '__main__': +if __name__ == "__main__": app.debug = True # Localhost diff --git a/flask__webservers/bootstrap_4__toggle_switch__examples/main.py b/flask__webservers/bootstrap_4__toggle_switch__examples/main.py index 7db9c9ee2..ed308867d 100644 --- a/flask__webservers/bootstrap_4__toggle_switch__examples/main.py +++ b/flask__webservers/bootstrap_4__toggle_switch__examples/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/twbs/bootstrap @@ -20,10 +20,10 @@ @app.route("/") def index(): - return render_template('index.html') + return render_template("index.html") -if __name__ == '__main__': +if __name__ == "__main__": app.debug = True # Localhost diff --git a/flask__webservers/caching_response.py b/flask__webservers/caching_response.py new file mode 100644 index 000000000..ac2b6128e --- /dev/null +++ b/flask__webservers/caching_response.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from datetime import datetime + +# pip install flask==2.3.3 +from flask import Flask, render_template_string + +# pip install flask-caching=2.0.2 +from flask_caching import Cache + + +config = { + "DEBUG": True, # some Flask specific configs + "CACHE_TYPE": "SimpleCache", # Flask-Caching related configs + "CACHE_DEFAULT_TIMEOUT": 300, +} + +app = Flask(__name__) + +# tell Flask to use the above defined config +app.config.from_mapping(config) +cache = Cache(app) + + +@app.route("/") +@cache.cached(timeout=50) +def index(): + # SOURCE: https://stackoverflow.com/a/55050637/5909792 + return render_template_string( + """ + + + + + +
+

{{ text }}

+
+ + + """, + text=str(datetime.now()), + ) + + +if __name__ == "__main__": + # Localhost + # port=0 -- random free port + # app.run(port=0) + app.run(port=50000) + + # # Public IP + # app.run(host='0.0.0.0') diff --git a/flask__webservers/chart_js/radar_chart__DarkSouls__builds_stats/main.py b/flask__webservers/chart_js/radar_chart__DarkSouls__builds_stats/main.py index bb091f14a..4a32996f5 100644 --- a/flask__webservers/chart_js/radar_chart__DarkSouls__builds_stats/main.py +++ b/flask__webservers/chart_js/radar_chart__DarkSouls__builds_stats/main.py @@ -1,54 +1,63 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import logging from flask import Flask, render_template -app = Flask(__name__, static_folder='../static') +app = Flask(__name__, static_folder="../static") logging.basicConfig(level=logging.DEBUG) -title = 'chart_js/radar_chart__DarkSouls__builds_stats' -labels = ['Vitality', 'Attunement', 'Endurance', 'Strength', 'Dexterity', 'Resistance', 'Intelligence', 'Faith'] +title = "chart_js/radar_chart__DarkSouls__builds_stats" +labels = [ + "Vitality", + "Attunement", + "Endurance", + "Strength", + "Dexterity", + "Resistance", + "Intelligence", + "Faith", +] items = [ { "name": "Cult Cleric Regen", "url": "https://darksouls.wiki.fextralife.com/Cult+Cleric+Regen", "color": "rgb(255, 99, 132)", "stats": [95, 12, 30, 16, 13, 10, 9, 16], - "hidden": 'true', + "hidden": "true", }, { "name": "Holy Dragon Knightess", "url": "https://darksouls.wiki.fextralife.com/Holy+Dragon+Knightess", "color": "rgb(255, 159, 64)", "stats": [26, 14, 36, 16, 10, 12, 8, 60], - "hidden": 'false', + "hidden": "false", }, { "name": "Royal Tank", "url": "https://darksouls.wiki.fextralife.com/Royal+Tank", "color": "rgb(75, 192, 192)", "stats": [50, 8, 40, 46, 10, 11, 8, 10], - "hidden": 'false', + "hidden": "false", }, { "name": "Vergil Style (Wanderer Style)", "url": "https://darksouls.wiki.fextralife.com/Vergil+Style", "color": "rgb(54, 162, 235)", "stats": [31, 12, 40, 16, 40, 12, 24, 8], - "hidden": 'false', + "hidden": "false", }, { "name": "Vergil Style (Thief Style)", "url": "https://darksouls.wiki.fextralife.com/Vergil+Style", "color": "rgb(153, 102, 255)", "stats": [28, 12, 40, 16, 40, 10, 24, 11], - "hidden": 'false', + "hidden": "false", }, ] @@ -56,16 +65,14 @@ @app.route("/") def index(): return render_template( - 'index.html', + "index.html", title=title, - title_chart='DarkSouls builds stats', + title_chart="DarkSouls builds stats", labels=labels, items=items, ) -if __name__ == '__main__': +if __name__ == "__main__": app.debug = True - app.run( - port=5000 - ) + app.run(port=5000) diff --git a/flask__webservers/common.py b/flask__webservers/common.py deleted file mode 100644 index 7d2556fcd..000000000 --- a/flask__webservers/common.py +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -__author__ = 'ipetrash' - - -import sys -from pathlib import Path - - -DIR = Path(__file__).resolve().parent - -sys.path.append(str(DIR.parent)) - -from human_byte_size import sizeof_fmt diff --git a/flask__webservers/cookies/main.py b/flask__webservers/cookies/main.py index a472c803d..e6ddcd2d3 100644 --- a/flask__webservers/cookies/main.py +++ b/flask__webservers/cookies/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import logging @@ -17,7 +17,7 @@ def get_cookies(): return jsonify(request.cookies) -@app.route("/set-cookies", methods=['POST']) +@app.route("/set-cookies", methods=["POST"]) def set_cookies(): rs = make_response(jsonify(dict(ok=True))) @@ -27,7 +27,7 @@ def set_cookies(): return rs -if __name__ == '__main__': +if __name__ == "__main__": app.debug = True app.run( port=5001, diff --git a/flask__webservers/cookies/test.py b/flask__webservers/cookies/test.py index 138f970ac..5395d9602 100644 --- a/flask__webservers/cookies/test.py +++ b/flask__webservers/cookies/test.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import requests @@ -10,7 +10,7 @@ session = requests.Session() -rs = session.get('http://127.0.0.1:5001/get-cookies') +rs = session.get("http://127.0.0.1:5001/get-cookies") print(rs, rs.url) print(rs.headers) print(rs.cookies) @@ -24,7 +24,7 @@ print() -rs = session.post('http://127.0.0.1:5001/set-cookies', params=dict(a=123, b=3)) +rs = session.post("http://127.0.0.1:5001/set-cookies", params=dict(a=123, b=3)) print(rs, rs.url) print(rs.headers) print(rs.cookies) @@ -38,7 +38,7 @@ print() -rs = session.get('http://127.0.0.1:5001/get-cookies') +rs = session.get("http://127.0.0.1:5001/get-cookies") print(rs, rs.url) print(rs.headers) print(rs.cookies) diff --git a/flask__webservers/cors__hello_world.py b/flask__webservers/cors__hello_world.py new file mode 100644 index 000000000..f771cddde --- /dev/null +++ b/flask__webservers/cors__hello_world.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +# pip install flask==2.3.3 +from flask import Flask + +# pip install flask-cors==4.0.0 +from flask_cors import CORS + + +app = Flask(__name__) +CORS(app) + + +@app.route("/") +def index() -> str: + return "Hello World!" + + +if __name__ == "__main__": + app.debug = True + + # Localhost + # port=0 -- random free port + # app.run(port=0) + app.run(port=50000) + + # # Public IP + # app.run(host='0.0.0.0') diff --git a/flask__webservers/custom_error_page/main.py b/flask__webservers/custom_error_page/main.py new file mode 100644 index 000000000..64c67879b --- /dev/null +++ b/flask__webservers/custom_error_page/main.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +# SOURCE: https://flask.palletsprojects.com/en/3.0.x/errorhandling/ + + +import logging + +from flask import Flask, render_template +from werkzeug.exceptions import HTTPException + + +app = Flask(__name__) +logging.basicConfig(level=logging.DEBUG) + + +@app.errorhandler(404) +def page_not_found(e): + # note that we set the 404 status explicitly + return render_template('errors/404.html'), 404 + + +@app.errorhandler(Exception) +def handle_exception(e): + # pass through HTTP errors + if isinstance(e, HTTPException): + return e + + # now you're handling non-HTTP exceptions only + return render_template("errors/500.html", e=e), 500 + + +@app.route("/") +def index(): + return render_template('index.html') + + +@app.route("/500") +def do_500() -> None: + 1/0 + + +if __name__ == "__main__": + app.run(port=5000) diff --git a/flask__webservers/custom_error_page/static/images/404.jpg b/flask__webservers/custom_error_page/static/images/404.jpg new file mode 100644 index 000000000..76a486e4a Binary files /dev/null and b/flask__webservers/custom_error_page/static/images/404.jpg differ diff --git a/flask__webservers/custom_error_page/static/images/500.jpg b/flask__webservers/custom_error_page/static/images/500.jpg new file mode 100644 index 000000000..f9edf60a6 Binary files /dev/null and b/flask__webservers/custom_error_page/static/images/500.jpg differ diff --git a/flask__webservers/custom_error_page/templates/base.html b/flask__webservers/custom_error_page/templates/base.html new file mode 100644 index 000000000..67f2c8af6 --- /dev/null +++ b/flask__webservers/custom_error_page/templates/base.html @@ -0,0 +1,11 @@ + + + + + + {% block title %}{% endblock %} + + + {% block content %}{% endblock %} + + diff --git a/flask__webservers/custom_error_page/templates/errors/404.html b/flask__webservers/custom_error_page/templates/errors/404.html new file mode 100644 index 000000000..40823bc07 --- /dev/null +++ b/flask__webservers/custom_error_page/templates/errors/404.html @@ -0,0 +1,10 @@ +{% extends 'base.html' %} + +{% block title %}Page Not Found{% endblock %} + +{% block content %} +

Page Not Found

+

What you were looking for is just not there.

+ +

go somewhere nice

+{% endblock %} diff --git a/flask__webservers/custom_error_page/templates/errors/500.html b/flask__webservers/custom_error_page/templates/errors/500.html new file mode 100644 index 000000000..01bd13206 --- /dev/null +++ b/flask__webservers/custom_error_page/templates/errors/500.html @@ -0,0 +1,10 @@ +{% extends 'base.html' %} + +{% block title %}Internal Server Error{% endblock %} + +{% block content %} +

Internal Server Error

+ +

Something unexpected happened... Please try again later

+

Error: {{ e }}

+{% endblock %} diff --git a/flask__webservers/custom_error_page/templates/index.html b/flask__webservers/custom_error_page/templates/index.html new file mode 100644 index 000000000..01cc057a1 --- /dev/null +++ b/flask__webservers/custom_error_page/templates/index.html @@ -0,0 +1,9 @@ +{% extends 'base.html' %} + +{% block title %}Start page{% endblock %} + +{% block content %} +

Start page

+

Go 404

+

Go 500

+{% endblock %} diff --git a/flask__webservers/filter-sort-paginate-on-server/items.json b/flask__webservers/filter-sort-paginate-on-server/items.json new file mode 100644 index 000000000..bc8789bd8 --- /dev/null +++ b/flask__webservers/filter-sort-paginate-on-server/items.json @@ -0,0 +1,302 @@ +[ + { + "id": 1, + "name": "Preston", + "description": "Magna commodo deserunt culpa anim anim exercitation reprehenderit officia occaecat consectetur nisi est ut. Tempor minim aute nostrud sunt do labore cillum. Consequat dolore do pariatur pariatur fugiat nulla anim exercitation culpa nisi qui dolor dolore magna.\r\n", + "command": "Autograte-prestonherrera@autograte.com" + }, + { + "id": 2, + "name": "Fay", + "description": "Elit laboris quis sint magna labore magna ullamco anim excepteur. Duis incididunt excepteur ut labore aliquip do. Aute sint do excepteur aliquip mollit proident fugiat sint. Mollit labore ex esse consectetur incididunt commodo in. Occaecat commodo eiusmod ad enim eiusmod consectetur anim cupidatat do duis est elit. Nisi occaecat est dolor dolor officia anim irure excepteur magna aute mollit.\r\n", + "command": "Liquidoc-fayherrera@liquidoc.com" + }, + { + "id": 3, + "name": "Dena", + "description": "Deserunt consequat Lorem adipisicing duis minim. Irure pariatur occaecat voluptate consectetur pariatur sint commodo excepteur incididunt esse amet id. Elit commodo nulla enim aliqua mollit ullamco duis anim consequat. Culpa sit ex nostrud fugiat reprehenderit fugiat. Incididunt enim ullamco esse velit consectetur. Esse cillum anim ullamco occaecat commodo enim aute laboris.\r\n", + "command": "Lingoage-denaherrera@lingoage.com" + }, + { + "id": 4, + "name": "Jacobs", + "description": "Voluptate anim amet dolor deserunt nostrud cillum magna velit enim qui. Dolore magna aliquip nisi eu exercitation eu eu Lorem. Ea occaecat culpa sit qui cupidatat veniam. Exercitation minim minim anim ad nulla veniam et velit. Nostrud laborum labore culpa do ipsum et cupidatat. Cupidatat officia adipisicing sunt aliquip id nisi deserunt.\r\n", + "command": "Imant-jacobsherrera@imant.com" + }, + { + "id": 5, + "name": "Richards", + "description": "Et eu laboris aute ipsum. Cupidatat consectetur dolor sint elit. Et quis laborum deserunt deserunt do cillum ipsum proident sunt laboris enim veniam. Sint id fugiat eu quis est dolore ut minim duis. Labore voluptate nulla adipisicing culpa proident.\r\n", + "command": "Octocore-richardsherrera@octocore.com" + }, + { + "id": 6, + "name": "Connie", + "description": "Officia culpa culpa proident fugiat dolor esse irure incididunt officia occaecat mollit amet. Ea exercitation reprehenderit proident laboris sint aute eiusmod voluptate. Mollit cupidatat exercitation laboris nulla sunt non ex.\r\n", + "command": "Circum-connieherrera@circum.com" + }, + { + "id": 7, + "name": "Lilia", + "description": "Aliquip sunt non do et nisi incididunt minim et. Nisi culpa elit in exercitation velit dolor cupidatat veniam consectetur mollit aliqua culpa. Ea irure commodo et cillum id. Sint pariatur eu cupidatat occaecat tempor excepteur do eiusmod sint.\r\n", + "command": "Petigems-liliaherrera@petigems.com" + }, + { + "id": 8, + "name": "Serena", + "description": "Lorem aliqua ad ut ut. Minim dolore incididunt sit enim incididunt consectetur. Ad cillum non sit sunt elit quis reprehenderit qui deserunt. Exercitation voluptate consectetur ullamco Lorem ipsum ut pariatur ea velit anim ex elit. Cupidatat proident reprehenderit ad aute in anim anim veniam ullamco mollit qui.\r\n", + "command": "Slumberia-serenaherrera@slumberia.com" + }, + { + "id": 9, + "name": "Cherie", + "description": "Labore incididunt mollit exercitation deserunt nostrud sint qui eu. Quis laboris labore ipsum cupidatat ea. Laboris consectetur ea Lorem deserunt tempor reprehenderit aliquip ipsum nostrud ex nisi veniam.\r\n", + "command": "Futurize-cherieherrera@futurize.com" + }, + { + "id": 10, + "name": "Lindsey", + "description": "Excepteur sit pariatur cupidatat veniam ullamco quis magna eu voluptate labore eiusmod magna tempor. Sint do sit in cillum id et ullamco aliquip nostrud occaecat. Consequat amet in nostrud adipisicing magna laborum duis labore fugiat culpa ut laborum. Non aliqua ea ex non. Nostrud quis ullamco nisi Lorem reprehenderit proident culpa fugiat.\r\n", + "command": "Aquoavo-lindseyherrera@aquoavo.com" + }, + { + "id": 11, + "name": "Nicole", + "description": "In est magna laboris velit sint ea. Quis tempor deserunt quis voluptate in proident adipisicing dolor exercitation laborum. Sit consequat reprehenderit cillum ut non ex aute sint. Lorem ea eu fugiat adipisicing nostrud cillum duis dolore incididunt nostrud nisi veniam aliqua occaecat.\r\n", + "command": "Strozen-nicoleherrera@strozen.com" + }, + { + "id": 12, + "name": "Joyner", + "description": "Dolore enim sunt adipisicing et reprehenderit. Incididunt laborum elit commodo duis sint Lorem fugiat dolore in cupidatat sunt commodo. Adipisicing do officia eiusmod adipisicing amet eu reprehenderit non incididunt excepteur ipsum sit. Et duis irure pariatur tempor laborum ad minim id enim fugiat exercitation aute mollit eu. Sint velit ipsum sint pariatur dolor aliquip sint irure adipisicing eu. Deserunt non eu dolor mollit irure elit consectetur voluptate sint ut dolore cillum do ullamco. Magna amet cillum est culpa aliquip occaecat eiusmod non est laborum non officia aliqua non.\r\n", + "command": "Rubadub-joynerherrera@rubadub.com" + }, + { + "id": 13, + "name": "Isabel", + "description": "Ad culpa esse mollit quis laborum deserunt consequat ad aliqua qui irure reprehenderit est mollit. Irure mollit cillum nostrud non. Ullamco laboris deserunt est et. Irure consectetur pariatur laboris in sunt aliqua ex reprehenderit duis laboris aliquip deserunt laborum. Pariatur ex veniam nulla ipsum amet excepteur qui dolor commodo. Incididunt aliqua nostrud et dolore nisi cupidatat dolore eu minim deserunt aute velit. Duis occaecat et duis commodo ex magna officia pariatur consectetur exercitation irure ullamco proident consectetur.\r\n", + "command": "Assurity-isabelherrera@assurity.com" + }, + { + "id": 14, + "name": "Jayne", + "description": "Ad cupidatat adipisicing occaecat ea magna mollit ex esse nostrud velit officia. Aliqua commodo enim commodo adipisicing cillum est in quis. Mollit sint ea adipisicing enim ea labore et. Cillum aliquip dolore elit aute sit sint incididunt exercitation dolore voluptate ut. Aliqua nostrud consectetur minim reprehenderit eu aliquip elit nulla consectetur nisi reprehenderit ullamco. Nostrud consectetur sunt in nostrud nostrud consectetur officia aliqua esse Lorem.\r\n", + "command": "Playce-jayneherrera@playce.com" + }, + { + "id": 15, + "name": "Tonia", + "description": "Eiusmod ex elit ullamco exercitation consectetur enim aliquip id qui dolor. Occaecat id et sit aute deserunt duis eiusmod mollit labore adipisicing. Voluptate consectetur dolore deserunt elit Lorem. Reprehenderit ex esse proident anim sunt qui pariatur in sit culpa.\r\n", + "command": "Fossiel-toniaherrera@fossiel.com" + }, + { + "id": 16, + "name": "Teresa", + "description": "Duis nostrud fugiat in duis laboris non irure quis. Non tempor reprehenderit eu incididunt occaecat exercitation incididunt excepteur deserunt cillum nulla deserunt elit sunt. Minim occaecat amet nostrud dolore Lorem labore incididunt tempor proident. Fugiat reprehenderit culpa quis pariatur eiusmod eu do aute eu sunt.\r\n", + "command": "Zosis-teresaherrera@zosis.com" + }, + { + "id": 17, + "name": "Nelda", + "description": "Nulla labore eiusmod aute aute mollit laborum mollit aliquip. Non qui id incididunt enim qui minim cupidatat aute proident est in nostrud. Id consequat eiusmod officia ex veniam elit occaecat veniam commodo eiusmod ullamco tempor. Pariatur exercitation sint sint anim nulla. Deserunt dolore enim cupidatat exercitation mollit elit magna magna Lorem mollit.\r\n", + "command": "Uneeq-neldaherrera@uneeq.com" + }, + { + "id": 18, + "name": "Farley", + "description": "Qui sit veniam nostrud consequat deserunt fugiat laboris occaecat. Laboris nisi sit velit velit aliqua ea labore amet sunt ullamco cillum. Laborum Lorem exercitation laboris consequat exercitation occaecat cupidatat ex elit officia aliqua deserunt culpa ea.\r\n", + "command": "Shopabout-farleyherrera@shopabout.com" + }, + { + "id": 19, + "name": "Porter", + "description": "Ipsum anim pariatur eiusmod sint sunt labore. Reprehenderit adipisicing laborum occaecat anim labore laboris mollit eiusmod minim et. Nisi dolor in dolor est nostrud. Commodo qui laborum ipsum occaecat esse est. Veniam velit mollit aute qui.\r\n", + "command": "Artiq-porterherrera@artiq.com" + }, + { + "id": 20, + "name": "Teri", + "description": "Lorem aute nostrud ipsum excepteur ipsum Lorem nulla eiusmod do. Eiusmod tempor nostrud commodo enim consectetur qui ut irure excepteur do duis reprehenderit amet tempor. Et in laboris exercitation anim sunt voluptate est cillum ullamco mollit reprehenderit anim ut.\r\n", + "command": "Omnigog-teriherrera@omnigog.com" + }, + { + "id": 21, + "name": "Miranda", + "description": "Ut dolore fugiat ex anim minim amet magna commodo mollit dolor excepteur reprehenderit laboris dolor. Amet consectetur anim reprehenderit do. Consectetur eu sit mollit culpa elit irure. Id exercitation ea nostrud cillum nostrud fugiat fugiat pariatur nulla culpa labore. Pariatur exercitation minim laboris cillum esse ullamco ut occaecat est. Excepteur officia officia officia consectetur tempor esse do. Nisi sit eiusmod ex anim ullamco non labore culpa.\r\n", + "command": "Comverges-mirandaherrera@comverges.com" + }, + { + "id": 22, + "name": "Hutchinson", + "description": "Et cupidatat aliquip culpa qui. Labore in sunt ut deserunt irure exercitation amet cupidatat aliqua nostrud velit sit aliquip ea. Ad aute consequat eiusmod non irure voluptate incididunt officia ipsum aliqua anim occaecat irure. Officia quis consectetur ea pariatur incididunt voluptate occaecat. Velit exercitation id mollit minim proident nostrud mollit adipisicing sint dolore.\r\n", + "command": "Zolavo-hutchinsonherrera@zolavo.com" + }, + { + "id": 23, + "name": "Woods", + "description": "Sunt sint laborum enim irure officia. Occaecat dolor velit esse adipisicing reprehenderit culpa laboris aute deserunt laboris exercitation. Aliqua elit ipsum reprehenderit laboris eu ad. Dolore mollit ullamco aute dolore dolore mollit dolore cillum incididunt aliquip qui tempor. Veniam ut labore in magna ullamco reprehenderit proident labore ea fugiat Lorem exercitation. Esse anim sint duis id eiusmod non eiusmod Lorem et. Pariatur sit cupidatat aliquip id reprehenderit minim nisi enim ipsum dolor cillum.\r\n", + "command": "Magnina-woodsherrera@magnina.com" + }, + { + "id": 24, + "name": "Minnie", + "description": "Velit elit cupidatat culpa culpa in commodo labore excepteur eu. Irure et dolore nulla adipisicing. Magna consectetur nisi do est veniam fugiat.\r\n", + "command": "Grupoli-minnieherrera@grupoli.com" + }, + { + "id": 25, + "name": "Faulkner", + "description": "Sunt aliquip quis quis consequat mollit aliqua laboris qui elit id duis et. Cupidatat officia excepteur anim non deserunt velit. Deserunt voluptate ex velit officia sit dolore minim tempor et. Et in dolor laboris dolore. Elit exercitation aliqua amet sint ut et cillum tempor proident incididunt sit. Dolore elit nisi proident anim exercitation tempor quis. Enim Lorem in laboris ipsum proident pariatur consectetur.\r\n", + "command": "Plasmosis-faulknerherrera@plasmosis.com" + }, + { + "id": 26, + "name": "Maude", + "description": "Consectetur esse Lorem id officia reprehenderit quis. Labore magna nisi deserunt sint officia cillum. Incididunt aliqua veniam officia incididunt elit mollit aliqua qui reprehenderit cillum anim. Qui tempor eu ex anim tempor amet minim dolore elit ipsum Lorem quis cupidatat. Reprehenderit velit quis aliquip Lorem incididunt.\r\n", + "command": "Jamnation-maudeherrera@jamnation.com" + }, + { + "id": 27, + "name": "Roberson", + "description": "Mollit sint aliqua veniam velit consectetur consequat. Ex fugiat labore reprehenderit in cupidatat nostrud adipisicing. Do sit nulla do nisi voluptate nostrud nostrud magna laboris.\r\n", + "command": "Kyagoro-robersonherrera@kyagoro.ru" + }, + { + "id": 28, + "name": "Debra", + "description": "Ex sit laboris duis velit culpa cupidatat nisi consectetur reprehenderit quis. Amet ea dolore irure nulla proident deserunt sint sint adipisicing pariatur sint elit. Non commodo aute Lorem cupidatat sit labore excepteur velit nostrud ut ea. Pariatur non qui sunt mollit amet veniam deserunt ullamco laborum est sint. Et minim fugiat irure dolor aliqua ut proident amet adipisicing culpa minim.\r\n", + "command": "Magneato-debraherrera@magneato.ru" + }, + { + "id": 29, + "name": "Pacheco", + "description": "Pariatur excepteur proident anim cillum dolore consectetur ipsum deserunt cupidatat cillum pariatur sunt officia laborum. Dolor voluptate culpa et elit pariatur ea. Voluptate velit ex commodo sunt exercitation ea nostrud quis elit est velit amet. Laboris est magna incididunt non sit minim culpa anim non proident occaecat irure veniam non. Ut quis laboris aliquip do tempor dolore ipsum reprehenderit deserunt deserunt. Do labore consectetur eu quis excepteur nulla anim consectetur ut dolore elit.\r\n", + "command": "Keengen-pachecoherrera@keengen.com" + }, + { + "id": 30, + "name": "Wyatt", + "description": "Commodo ex sint et amet tempor veniam culpa pariatur Lorem proident ut sint. Commodo anim occaecat anim in minim occaecat velit. Anim id id ea ex aliqua deserunt minim duis tempor ullamco voluptate esse. Duis mollit esse minim excepteur et consequat. Laborum voluptate culpa deserunt anim et nulla enim qui anim laborum anim exercitation. In dolor elit culpa velit ullamco aute anim Lorem excepteur esse magna Lorem exercitation.\r\n", + "command": "Newcube-wyattherrera@newcube.com" + }, + { + "id": 31, + "name": "Laurel", + "description": "Laborum exercitation aliqua occaecat labore. In velit ea fugiat elit eu quis sint duis elit officia irure enim proident tempor. Minim do quis mollit elit. Ipsum tempor laborum exercitation mollit ut qui tempor laboris magna fugiat qui sit aliqua quis. Elit velit aliquip ipsum esse quis anim pariatur anim irure minim. Veniam laborum id commodo et mollit Lorem aliqua laborum ipsum magna. Occaecat amet fugiat proident in.\r\n", + "command": "Escenta-laurelherrera@escenta.org" + }, + { + "id": 32, + "name": "Hopper", + "description": "Do consequat magna in quis quis. Veniam esse tempor velit ex irure. Minim reprehenderit excepteur sint mollit sit. Incididunt nisi velit minim ad anim nisi incididunt mollit culpa proident.\r\n", + "command": "Portalis-hopperherrera@portalis.com" + }, + { + "id": 33, + "name": "Guzman", + "description": "Lorem in ipsum elit eiusmod. Nostrud officia veniam voluptate culpa anim eu ex qui duis do quis ea quis aliquip. Do dolore aliqua qui enim sunt do exercitation non non esse nostrud eu. Aliqua enim consectetur sunt minim non. Eu anim sunt ipsum id aliquip labore laboris exercitation. Commodo cupidatat exercitation occaecat ea anim sint pariatur incididunt velit minim eu.\r\n", + "command": "Jimbies-guzmanherrera@jimbies.ru" + }, + { + "id": 34, + "name": "Amber", + "description": "Nostrud reprehenderit culpa veniam eu ipsum et id nostrud. Deserunt do aliquip exercitation enim ullamco incididunt fugiat in nostrud exercitation anim. Dolor cillum nisi nostrud ipsum reprehenderit excepteur quis adipisicing mollit voluptate consequat ea occaecat. Minim mollit non in fugiat magna voluptate.\r\n", + "command": "Zaggles-amberherrera@zaggles.ru" + }, + { + "id": 35, + "name": "Ruthie", + "description": "Deserunt nostrud dolor labore laboris. Elit pariatur non cillum minim veniam. Nisi incididunt cillum in consectetur enim nulla ea dolore pariatur. Velit magna in ex exercitation voluptate eiusmod velit eiusmod nisi in elit nisi cillum. Non do labore aute eiusmod ut et nulla commodo.\r\n", + "command": "Boilicon-ruthieherrera@boilicon.org" + }, + { + "id": 36, + "name": "Billie", + "description": "Consectetur minim pariatur duis dolor cupidatat laboris occaecat nisi veniam reprehenderit laborum veniam et. Officia ullamco fugiat proident cupidatat fugiat ut culpa minim sint. Elit eiusmod id labore voluptate sit tempor ea cupidatat aute voluptate anim. Do laboris dolor elit duis nulla aliquip incididunt amet duis magna dolore esse. Eiusmod eiusmod nisi enim aute aliquip do ea laborum duis voluptate nostrud deserunt officia.\r\n", + "command": "Geekko-billieherrera@geekko.com" + }, + { + "id": 37, + "name": "Lindsay", + "description": "Ea laboris exercitation tempor amet mollit in ea excepteur ut excepteur quis. Sit ipsum ipsum ullamco aliquip anim cillum veniam. Do incididunt excepteur duis ullamco aute quis elit minim ipsum.\r\n", + "command": "Rocklogic-lindsayherrera@rocklogic.org" + }, + { + "id": 38, + "name": "Mercer", + "description": "Mollit labore aliqua tempor pariatur veniam duis enim laborum. Ipsum pariatur ut do ullamco velit ea nisi adipisicing ullamco est ea elit laboris pariatur. Ex consequat do deserunt pariatur mollit ut ipsum incididunt aute sit proident reprehenderit eu.\r\n", + "command": "Satiance-mercerherrera@satiance.org" + }, + { + "id": 39, + "name": "Benson", + "description": "Proident est aliqua eu nisi tempor ut veniam irure. Sunt sit ex enim elit consequat eu officia cillum occaecat esse. Lorem id magna et veniam et ullamco adipisicing nostrud irure Lorem. Velit proident occaecat eiusmod ullamco nisi commodo. Amet velit ex commodo qui id exercitation amet fugiat reprehenderit velit magna consequat quis. Laborum minim exercitation esse veniam ad do qui duis mollit adipisicing voluptate nostrud cupidatat cupidatat. Reprehenderit ipsum ex nostrud do ex sit excepteur mollit aute.\r\n", + "command": "Lyria-bensonherrera@lyria.com" + }, + { + "id": 40, + "name": "Melisa", + "description": "Mollit esse sunt ullamco excepteur cillum non cupidatat laborum consequat enim ea. Amet quis incididunt cupidatat incididunt excepteur voluptate sit. Culpa proident laboris occaecat fugiat labore id eu aute magna ea irure commodo laborum voluptate. Ut excepteur esse sint culpa reprehenderit sit. Officia tempor elit dolore ex sint culpa. Nostrud ipsum et dolore aliquip magna laboris aliquip commodo qui consequat in commodo amet. Velit proident non esse reprehenderit excepteur consectetur eu irure.\r\n", + "command": "Genmy-melisaherrera@genmy.com" + }, + { + "id": 41, + "name": "Amber", + "description": "Et tempor cupidatat consequat veniam minim labore quis duis incididunt quis anim. Ut incididunt fugiat pariatur ipsum occaecat dolore ea aute id nisi commodo. Amet eiusmod proident dolore Lorem. Ut consequat officia sint nostrud est in occaecat esse.\r\n", + "command": "Asimiline-ellenherrera@asimiline.org" + }, + { + "id": 42, + "name": "Jocelyn", + "description": "Est anim mollit aliqua qui est cillum. Est voluptate eu laboris ea adipisicing occaecat nostrud aliquip exercitation cillum deserunt sit cillum. Cillum sunt fugiat sit veniam dolore velit minim cillum occaecat ut ex aliqua ipsum.\r\n", + "command": "Insurety-jocelynherrera@insurety.org" + }, + { + "id": 43, + "name": "Mccullough", + "description": "Ullamco aliqua et id et minim. Est amet deserunt adipisicing cupidatat ipsum quis tempor. Irure deserunt amet aliquip officia id. Quis deserunt dolore aute quis aliqua irure voluptate ex qui ad. Sint fugiat do nisi sunt ad laborum officia reprehenderit. Non commodo velit amet enim mollit occaecat tempor quis eiusmod ipsum ex commodo sunt.\r\n", + "command": "Straloy-mcculloughherrera@straloy.com" + }, + { + "id": 44, + "name": "Lisa", + "description": "Nostrud fugiat in commodo ex tempor. Veniam excepteur mollit eiusmod ut labore quis sint qui nisi. Voluptate elit labore nostrud est pariatur do amet commodo consectetur.\r\n", + "command": "Flyboyz-lisaherrera@flyboyz.com" + }, + { + "id": 45, + "name": "Abigail", + "description": "Deserunt nisi irure ex culpa ut eu commodo tempor proident qui. Magna nostrud amet qui ipsum et ipsum. Est cillum ullamco voluptate labore laborum quis ut et consectetur reprehenderit sint. Sint nostrud fugiat consectetur mollit nisi excepteur veniam excepteur tempor adipisicing dolor amet. Proident commodo exercitation dolore reprehenderit quis quis in aliquip est veniam sit quis ipsum nisi. Laborum ut pariatur in enim cupidatat enim deserunt adipisicing irure ut tempor non consequat aute. Ea cupidatat excepteur sunt eiusmod.\r\n", + "command": "Overfork-abigailherrera@overfork.com" + }, + { + "id": 46, + "name": "Estrada", + "description": "Aute qui nisi adipisicing qui ad laborum sit duis deserunt sint in consequat. Dolor irure mollit eu nostrud officia. Sit amet irure ea nisi in mollit amet ad laborum ea nulla ex tempor. Voluptate labore est exercitation proident ullamco magna magna pariatur. Fugiat enim eu officia ut ullamco quis officia proident commodo. Laborum incididunt consectetur eiusmod nulla qui. Anim commodo eiusmod sunt fugiat ea amet ex et in pariatur mollit mollit.\r\n", + "command": "Typhonica-estradaherrera@typhonica.com" + }, + { + "id": 47, + "name": "Amber", + "description": "Aute quis cillum id nostrud labore duis aliquip duis commodo officia irure amet Lorem. Esse voluptate amet cillum adipisicing amet esse nulla deserunt. Aliquip eu quis pariatur reprehenderit et occaecat excepteur ut do consequat consequat reprehenderit et. Aliqua minim exercitation consectetur laboris enim minim ex voluptate quis. Laboris pariatur dolore eiusmod Lorem nulla tempor reprehenderit quis nisi enim aute. Non velit sit pariatur officia minim laboris minim irure sunt irure ea ex anim culpa.\r\n", + "command": "Vinch-susanaherrera@vinch.com" + }, + { + "id": 48, + "name": "Joseph", + "description": "Elit et exercitation et officia ad mollit consectetur id laborum consectetur adipisicing sint quis. Aliqua deserunt eiusmod voluptate ex. Ipsum nulla aliquip occaecat in minim do in adipisicing sunt in minim.\r\n", + "command": "Jumpstack-josephherrera@jumpstack.ru" + }, + { + "id": 49, + "name": "Johnnie", + "description": "Aliqua Lorem in ea ea duis. Consectetur ex laborum non nulla enim magna elit qui exercitation commodo consectetur. Consequat irure mollit veniam consectetur tempor enim aliquip amet nulla aliquip excepteur reprehenderit. Reprehenderit excepteur eu nulla cillum excepteur nulla velit consequat anim. Amet ut aute et ut aliqua. Consequat nostrud duis in eiusmod quis consectetur ad nostrud nostrud do exercitation occaecat dolore qui. Tempor sint ad do consequat.\r\n", + "command": "Biohab-johnnieherrera@biohab.ru" + }, + { + "id": 50, + "name": "Amber", + "description": "Aliquip aliqua nostrud veniam est. Laborum ut et nostrud proident qui proident. Esse quis amet in culpa sint tempor proident consectetur velit incididunt reprehenderit. Eu sint exercitation qui do ullamco.\r\n", + "command": "Luxuria-estesherrera@luxuria.ru" + } +] \ No newline at end of file diff --git a/flask__webservers/filter-sort-paginate-on-server/main.py b/flask__webservers/filter-sort-paginate-on-server/main.py new file mode 100644 index 000000000..314282a7a --- /dev/null +++ b/flask__webservers/filter-sort-paginate-on-server/main.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +# NOTE: https://datatables.net/manual/server-side + + +import json +import re +from functools import cmp_to_key +from pathlib import Path +from typing import Any + +from flask import Flask, render_template, jsonify, Response, request + +# pip install querystring-parser==1.2.4 +from querystring_parser import parser + + +def cmp(a: Any, b: Any) -> int: + return (a > b) - (a < b) + + +ITEMS = json.load(open("items.json", encoding="utf-8")) + + +app = Flask(__name__) + + +@app.route("/") +def index() -> str: + return render_template( + "index.html", + title=Path(__file__).resolve().parent.name, + ) + + +@app.route("/api/get_items") +def api_get_items() -> Response: + args: dict[str, Any] = parser.parse(request.query_string) + + search_value: str = args["search"]["value"] + has_search_regex: str = args["search"]["regex"] == "true" + + # TODO: Фильтрация может быть по отдельным столбцам в поле search + # { + # 0: {'data': 'id', 'name': 'id', 'searchable': 'true', 'orderable': 'true', 'search': {'value': '', 'regex': 'false'}}, + # 1: {'data': 'name', 'name': 'name', 'searchable': 'true', 'orderable': 'true', 'search': {'value': '', 'regex': 'false'}}, + # 2: {'data': 'description', 'name': 'description', 'searchable': 'true', 'orderable': 'true', 'search': {'value': '', 'regex': 'false'}}, + # 3: {'data': 'command', 'name': 'command', 'searchable': 'true', 'orderable': 'true', 'search': {'value': '', 'regex': 'false'}} + # } + # columns: dict[int, dict[str, str]] = args["columns"] + print("columns:", args["columns"]) + + filtered_items: list[dict[str, Any]] = [] + for item in ITEMS: + if search_value: + values: list[str] = [str(x).lower() for x in item.values()] + if not any( + ( + re.search(search_value, value) + if has_search_regex + else search_value.lower() in value + ) + for value in values + ): + continue + + filtered_items.append(item) + + number_of_filtered = len(filtered_items) + + order: dict[int, dict[str, str]] = args.get("order", dict()) + print("order:", order) + if order: + def cmp2(item1: dict[str, str], item2: dict[str, str]) -> int: + # NOTE: https://stackoverflow.com/a/62381089/5909792 + total_result: int | None = None + for order_column in order.values(): + name: str = order_column["name"] + result = cmp(item1[name], item2[name]) + if order_column["dir"] == "desc": + result = -result + + if total_result is None: + total_result = result + else: + total_result = total_result or result + + return total_result + + filtered_items.sort(key=cmp_to_key(cmp2)) + + start: int = int(args["start"]) + length: int = int(args["length"]) + if length > 0: + filtered_items = filtered_items[start : start + length] + + return jsonify( + { + "draw": int(args["draw"]), + "recordsTotal": len(ITEMS), + "recordsFiltered": number_of_filtered, + "data": filtered_items, + } + ) + + +if __name__ == "__main__": + app.run() diff --git a/flask__webservers/filter-sort-paginate-on-server/static/index.js b/flask__webservers/filter-sort-paginate-on-server/static/index.js new file mode 100644 index 000000000..498ec7edf --- /dev/null +++ b/flask__webservers/filter-sort-paginate-on-server/static/index.js @@ -0,0 +1,22 @@ +$(function() { + new DataTable('table', { + ajax: 'api/get_items', + rowId: 'id', + serverSide: true, + processing: true, + lengthMenu: [ + [5, 10, 25, 50, -1], + ["5 records", "10 records", "25 records", "50 records", "All records"] + ], + columns: [ + { name: 'id', data: 'id', title: 'Id', }, + { name: 'name', data: 'name', title: 'Name' }, + { name: 'description', data: 'description', title: 'Description', }, + { name: 'command', data: 'command', title: 'Command', } + ], + order: [ + // Сортировка по убыванию id + [0, "desc"], + ], + }); +}); diff --git a/flask__webservers/filter-sort-paginate-on-server/templates/index.html b/flask__webservers/filter-sort-paginate-on-server/templates/index.html new file mode 100644 index 000000000..911c791f4 --- /dev/null +++ b/flask__webservers/filter-sort-paginate-on-server/templates/index.html @@ -0,0 +1,18 @@ + + + + + + + {{ title }} + + + + + + +
+ + + + diff --git a/flask__webservers/flask_debugtoolbar__examples/app.py b/flask__webservers/flask_debugtoolbar__examples/app.py index 5e3790aa8..85a51fbea 100644 --- a/flask__webservers/flask_debugtoolbar__examples/app.py +++ b/flask__webservers/flask_debugtoolbar__examples/app.py @@ -6,47 +6,47 @@ app = Flask(__name__) -app.config['DEBUG_TB_INTERCEPT_REDIRECTS'] = True -#app.config['DEBUG_TB_PANELS'] = ( +app.config["DEBUG_TB_INTERCEPT_REDIRECTS"] = True +# app.config['DEBUG_TB_PANELS'] = ( # 'flask_debugtoolbar.panels.headers.HeaderDebugPanel', # 'flask_debugtoolbar.panels.logger.LoggingPanel', # 'flask_debugtoolbar.panels.timer.TimerDebugPanel', -#) -#app.config['DEBUG_TB_HOSTS'] = ('127.0.0.1', '::1' ) -app.config['SECRET_KEY'] = 'asd' +# ) +# app.config['DEBUG_TB_HOSTS'] = ('127.0.0.1', '::1' ) +app.config["SECRET_KEY"] = "asd" # TODO: This can be removed once flask_sqlalchemy 3.0 ships -app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False +app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False -app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///test.db' +app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///test.db" db = SQLAlchemy(app) class ExampleModel(db.Model): - __tablename__ = 'examples' + __tablename__ = "examples" value = db.Column(db.String(100), primary_key=True) @app.before_first_request -def setup(): +def setup() -> None: db.create_all() -@app.route('/') +@app.route("/") def index(): app.logger.info("Hello there") ExampleModel.query.get(1) - return render_template('index.html') + return render_template("index.html") -@app.route('/redirect') +@app.route("/redirect") def redirect_example(): - response = redirect(url_for('index')) - response.set_cookie('test_cookie', '1') + response = redirect(url_for("index")) + response.set_cookie("test_cookie", "1") return response -if __name__ == '__main__': +if __name__ == "__main__": app.debug = True toolbar = DebugToolbarExtension(app) @@ -54,9 +54,7 @@ def redirect_example(): # Localhost # port=0 -- random free port # app.run(port=0) - app.run( - port=5000 - ) + app.run(port=5000) # # Public IP # app.run(host='0.0.0.0') diff --git a/flask__webservers/flask_debugtoolbar__examples/hello_world.py b/flask__webservers/flask_debugtoolbar__examples/hello_world.py index ad793d1fa..d6846fb15 100644 --- a/flask__webservers/flask_debugtoolbar__examples/hello_world.py +++ b/flask__webservers/flask_debugtoolbar__examples/hello_world.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import logging @@ -9,19 +9,20 @@ from flask import Flask from flask_debugtoolbar import DebugToolbarExtension + app = Flask(__name__) # set a 'SECRET_KEY' to enable the Flask session cookies -app.config['SECRET_KEY'] = '' +app.config["SECRET_KEY"] = "" @app.route("/") -def index(): +def index() -> str: # NOTE: Need tab body: "Could not insert debug toolbar. tag not found in response." return "Hello World!" -if __name__ == '__main__': +if __name__ == "__main__": app.debug = True if app.debug: @@ -32,9 +33,7 @@ def index(): # Localhost # port=0 -- random free port # app.run(port=0) - app.run( - port=5000 - ) + app.run(port=5000) # # Public IP # app.run(host='0.0.0.0') diff --git a/flask__webservers/generate_qrcode/main.py b/flask__webservers/generate_qrcode/main.py index 9b8e78a03..01e022eb4 100644 --- a/flask__webservers/generate_qrcode/main.py +++ b/flask__webservers/generate_qrcode/main.py @@ -1,23 +1,31 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import hashlib import logging import os -from flask import Flask, request, jsonify, render_template_string, send_from_directory, url_for, redirect +from flask import ( + Flask, + request, + jsonify, + render_template_string, + send_from_directory, + url_for, + redirect, +) # pip install qrcode import qrcode -UPLOAD_FOLDER = 'images' +UPLOAD_FOLDER = "images" app = Flask(__name__) -app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER +app.config["UPLOAD_FOLDER"] = UPLOAD_FOLDER os.makedirs(UPLOAD_FOLDER, exist_ok=True) @@ -26,7 +34,8 @@ @app.route("/") def index(): - return render_template_string('''\ + return render_template_string( + """\ @@ -89,37 +98,41 @@ def index(): -''') +""" + ) -@app.route("/generate_qrcode", methods=['GET', 'POST']) +@app.route("/generate_qrcode", methods=["GET", "POST"]) def generate_qrcode(): - print('request.args:', request.args) - print('request.form:', request.form) + print("request.args:", request.args) + print("request.form:", request.form) print() - text = request.args.get('text', None) + text = request.args.get("text", None) if not text: - text = request.form.get('text') + text = request.form.get("text") - download = 'download' in request.args - is_redirect = 'redirect' in request.args - as_image = 'as_image' in request.args + download = "download" in request.args + is_redirect = "redirect" in request.args + as_image = "as_image" in request.args if not text: - return render_template_string("""\ + return render_template_string( + """\ Example:
{{ example_uri }}
{{ example_uri }}&download
{{ example_uri }}&redirect
{{ example_uri }}&as_image - """, example_uri='/generate_qrcode?text=Hello World!') + """, + example_uri="/generate_qrcode?text=Hello World!", + ) algo = hashlib.md5(text.encode()) hash_data = algo.hexdigest() - file_name = hash_data + '.png' - upload_folder = app.config['UPLOAD_FOLDER'] + file_name = hash_data + ".png" + upload_folder = app.config["UPLOAD_FOLDER"] abs_file_name = os.path.join(upload_folder, file_name) if not os.path.exists(abs_file_name): @@ -143,14 +156,16 @@ def generate_qrcode(): # Вернется json c ссылкой на картинку return jsonify({ # url_for составляет путь для функции images, которая возвращает картинку с сервера - 'file_name': uri, + "file_name": uri, }) -@app.route('/' + UPLOAD_FOLDER + '/') +@app.route("/" + UPLOAD_FOLDER + "/") def images(file_name): - download = 'download' in request.args - return send_from_directory(app.config['UPLOAD_FOLDER'], file_name, as_attachment=download) + download = "download" in request.args + return send_from_directory( + app.config["UPLOAD_FOLDER"], file_name, as_attachment=download + ) if __name__ == "__main__": diff --git a/flask__webservers/generate_table/main.py b/flask__webservers/generate_table/main.py index c6c693046..3c8f6c18c 100644 --- a/flask__webservers/generate_table/main.py +++ b/flask__webservers/generate_table/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import logging @@ -26,9 +26,10 @@ def index(): items.append(row) - items[0][0] = '' + items[0][0] = "" - return render_template_string("""\ + return render_template_string( + """\ @@ -74,10 +75,12 @@ def index(): - """, items=items) + """, + items=items, + ) -if __name__ == '__main__': +if __name__ == "__main__": app.debug = True # Localhost diff --git a/flask__webservers/get_URL__parameter_argument_query.py b/flask__webservers/get_URL__parameter_argument_query.py index f659b31eb..1145a004d 100644 --- a/flask__webservers/get_URL__parameter_argument_query.py +++ b/flask__webservers/get_URL__parameter_argument_query.py @@ -1,18 +1,19 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" +import logging from flask import Flask, request -app = Flask(__name__) -import logging + +app = Flask(__name__) logging.basicConfig(level=logging.DEBUG) @app.route("/") -def index(): +def index() -> str: return """
@@ -47,13 +48,13 @@ def print_args(): print(args) text = '' - text += '' - text += '
URL ARGUMENTS:' + text += "URL ARGUMENTS:" for k, v in args.items(): - text += f'
{k}{v}
' + text += f"{k}{v}" + text += "" return text -if __name__ == '__main__': +if __name__ == "__main__": app.run() diff --git a/flask__webservers/get_exif_info/main.py b/flask__webservers/get_exif_info/main.py index e0f8533af..702ecf392 100644 --- a/flask__webservers/get_exif_info/main.py +++ b/flask__webservers/get_exif_info/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import base64 @@ -17,17 +17,17 @@ def get_exif_tags(file_object_or_file_name, as_category=True): if type(file_object_or_file_name) == str: # Open image file for reading (binary mode) - file_object_or_file_name = open(file_object_or_file_name, mode='rb') + file_object_or_file_name = open(file_object_or_file_name, mode="rb") # Return Exif tags tags = exifread.process_file(file_object_or_file_name) tags_by_value = dict() if not tags: - print('Not tags') + print("Not tags") return tags_by_value - print('Tags ({}):'.format(len(tags))) + print(f"Tags ({len(tags)}):") for tag, value in tags.items(): # Process value @@ -36,9 +36,9 @@ def get_exif_tags(file_object_or_file_name, as_category=True): try: # If last 2 items equals [0, 0] if value.values[-2:] == [0, 0]: - value = bytes(value.values[:-2]).decode('utf-16') + value = bytes(value.values[:-2]).decode("utf-16") else: - value = bytes(value.values).decode('utf-16') + value = bytes(value.values).decode("utf-16") except: value = str(value.values) @@ -52,16 +52,16 @@ def get_exif_tags(file_object_or_file_name, as_category=True): if type(value) == bytes: value = base64.b64encode(value).decode() - print(' "{}": {}'.format(tag, value)) + print(f' "{tag}": {value}') if not as_category: tags_by_value[tag] = value else: # Fill categories_by_tag - if ' ' in tag: - ''.split() - category, sub_tag = tag.split(' ', maxsplit=1) + if " " in tag: + "".split() + category, sub_tag = tag.split(" ", maxsplit=1) if category not in tags_by_value: tags_by_value[category] = dict() @@ -81,9 +81,10 @@ def get_exif_tags(file_object_or_file_name, as_category=True): logging.basicConfig(level=logging.DEBUG) -@app.route('/') +@app.route("/") def index(): - return render_template_string('''\ + return render_template_string( + """\ @@ -215,23 +216,24 @@ def index(): -''') +""" + ) -@app.route("/get_exif", methods=['POST']) +@app.route("/get_exif", methods=["POST"]) def get_exif(): print(request.files) # check if the post request has the file part - if 'file' not in request.files: - return redirect('/') + if "file" not in request.files: + return redirect("/") - file = request.files['file'] + file = request.files["file"] categories_by_tag = get_exif_tags(file.stream) return jsonify(categories_by_tag) -if __name__ == '__main__': +if __name__ == "__main__": app.debug = True # Localhost diff --git a/flask__webservers/get_size_upload_file/main.py b/flask__webservers/get_size_upload_file/main.py index 40f2504db..761086f62 100644 --- a/flask__webservers/get_size_upload_file/main.py +++ b/flask__webservers/get_size_upload_file/main.py @@ -1,16 +1,15 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import logging -import sys from flask import Flask, request, redirect, render_template_string, jsonify -sys.path.append('..') -from common import sizeof_fmt +# pip install humanize +from humanize import naturalsize as sizeof_fmt app = Flask(__name__) @@ -18,9 +17,10 @@ logging.basicConfig(level=logging.DEBUG) -@app.route('/') +@app.route("/") def index(): - return render_template_string('''\ + return render_template_string( + """\ @@ -87,28 +87,29 @@ def index(): -''') +""" + ) -@app.route('/get_file_size', methods=['POST']) +@app.route("/get_file_size", methods=["POST"]) def get_file_size(): print(request.files) # check if the post request has the file part - if 'file' not in request.files: - return redirect('/') + if "file" not in request.files: + return redirect("/") length = 0 - file = request.files['file'] + file = request.files["file"] if file: data = file.stream.read() length = len(data) - return jsonify({'length': length, 'length_human': sizeof_fmt(length)}) + return jsonify({"length": length, "length_human": sizeof_fmt(length)}) -if __name__ == '__main__': +if __name__ == "__main__": app.debug = True # Localhost diff --git a/flask__webservers/get_size_upload_file_with_progress/main.py b/flask__webservers/get_size_upload_file_with_progress/main.py index 882d398c4..35ae0b78b 100644 --- a/flask__webservers/get_size_upload_file_with_progress/main.py +++ b/flask__webservers/get_size_upload_file_with_progress/main.py @@ -1,16 +1,15 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import logging -import sys from flask import Flask, request, redirect, render_template_string, jsonify -sys.path.append('..') -from common import sizeof_fmt +# pip install humanize +from humanize import naturalsize as sizeof_fmt app = Flask(__name__) @@ -18,9 +17,10 @@ logging.basicConfig(level=logging.DEBUG) -@app.route('/') +@app.route("/") def index(): - return render_template_string('''\ + return render_template_string( + """\ @@ -118,28 +118,29 @@ def index(): -''') +""" + ) -@app.route('/get_file_size', methods=['POST']) +@app.route("/get_file_size", methods=["POST"]) def get_file_size(): print(request.files) # check if the post request has the file part - if 'file' not in request.files: - return redirect('/') + if "file" not in request.files: + return redirect("/") length = 0 - file = request.files['file'] + file = request.files["file"] if file: data = file.stream.read() length = len(data) - return jsonify({'length': length, 'length_human': sizeof_fmt(length)}) + return jsonify({"length": length, "length_human": sizeof_fmt(length)}) -if __name__ == '__main__': +if __name__ == "__main__": app.debug = True # Localhost diff --git a/flask__webservers/get_upload_and_image_process/commands.py b/flask__webservers/get_upload_and_image_process/commands.py index c25dacbbf..ae059f2f0 100644 --- a/flask__webservers/get_upload_and_image_process/commands.py +++ b/flask__webservers/get_upload_and_image_process/commands.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PIL import Image, ImageOps, ImageFilter @@ -10,12 +10,12 @@ # SOURCE: https://github.com/gil9red/SimplePyScripts/blob/4f6d5b013d218cfe5b964af17a2540e2e49adca0/pil_example/invert/main.py def invert(image): - if image.mode == 'RGBA': + if image.mode == "RGBA": r, g, b, a = image.split() - rgb_image = Image.merge('RGB', (r, g, b)) + rgb_image = Image.merge("RGB", (r, g, b)) inverted_image = ImageOps.invert(rgb_image) r2, g2, b2 = inverted_image.split() - return Image.merge('RGBA', (r2, g2, b2, a)) + return Image.merge("RGBA", (r2, g2, b2, a)) else: return ImageOps.invert(image) @@ -34,8 +34,14 @@ def invert_gray(img): def pixelate(image, pixel_size=9, draw_margin=True): margin_color = (0, 0, 0) - image = image.resize((image.size[0] // pixel_size, image.size[1] // pixel_size), Image.NEAREST) - image = image.resize((image.size[0] * pixel_size, image.size[1] * pixel_size), Image.NEAREST) + image = image.resize( + (image.size[0] // pixel_size, image.size[1] // pixel_size), + Image.NEAREST + ) + image = image.resize( + (image.size[0] * pixel_size, image.size[1] * pixel_size), + Image.NEAREST + ) pixel = image.load() # Draw black margin between pixels @@ -43,15 +49,15 @@ def pixelate(image, pixel_size=9, draw_margin=True): for i in range(0, image.size[0], pixel_size): for j in range(0, image.size[1], pixel_size): for r in range(pixel_size): - pixel[i+r, j] = margin_color - pixel[i, j+r] = margin_color + pixel[i + r, j] = margin_color + pixel[i, j + r] = margin_color return image def jackal_jpg(img): data_io = io.BytesIO() - img.save(data_io, format='JPEG', quality=1) + img.save(data_io, format="JPEG", quality=1) return Image.open(data_io) diff --git a/flask__webservers/get_upload_and_image_process/main.py b/flask__webservers/get_upload_and_image_process/main.py index de1e025b6..6c7b48f64 100644 --- a/flask__webservers/get_upload_and_image_process/main.py +++ b/flask__webservers/get_upload_and_image_process/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import base64 @@ -18,28 +18,28 @@ COMMANDS = { - 'invert': invert, - 'gray': gray, - 'invert_gray': invert_gray, - 'pixelate': pixelate, - 'pixelate16': lambda img: pixelate(img, 16), - 'pixelate32': lambda img: pixelate(img, 32), - 'pixelate48': lambda img: pixelate(img, 48), - 'jackal_jpg': jackal_jpg, - 'thumbnail32': lambda img: thumbnail(img, (32, 32)), - 'thumbnail64': lambda img: thumbnail(img, (64, 64)), - 'thumbnail128': lambda img: thumbnail(img, (128, 129)), - 'blur': blur, - 'blur5': lambda img: blur(img, 5) + "invert": invert, + "gray": gray, + "invert_gray": invert_gray, + "pixelate": pixelate, + "pixelate16": lambda img: pixelate(img, 16), + "pixelate32": lambda img: pixelate(img, 32), + "pixelate48": lambda img: pixelate(img, 48), + "jackal_jpg": jackal_jpg, + "thumbnail32": lambda img: thumbnail(img, (32, 32)), + "thumbnail64": lambda img: thumbnail(img, (64, 64)), + "thumbnail128": lambda img: thumbnail(img, (128, 129)), + "blur": blur, + "blur5": lambda img: blur(img, 5), } # SOURCE: https://github.com/gil9red/SimplePyScripts/blob/master/img_to_base64_html/main.py -def img_to_base64_html(file_name__or__bytes__or__file_object): +def img_to_base64_html(file_name__or__bytes__or__file_object) -> str: arg = file_name__or__bytes__or__file_object if type(arg) == str: - with open(arg, mode='rb') as f: + with open(arg, mode="rb") as f: img_bytes = f.read() elif type(arg) == bytes: @@ -51,10 +51,10 @@ def img_to_base64_html(file_name__or__bytes__or__file_object): bytes_io = io.BytesIO(img_bytes) img = Image.open(bytes_io) - img_base64 = base64.b64encode(img_bytes).decode('utf-8') + img_base64 = base64.b64encode(img_bytes).decode("utf-8") # print(img_base64) - return 'data:image/{};base64,'.format(img.format.lower()) + img_base64 + return f"data:image/{img.format.lower()};base64,{img_base64}" app = Flask(__name__) @@ -64,27 +64,28 @@ def img_to_base64_html(file_name__or__bytes__or__file_object): # ensure that independent of the hash seed of the dictionary the return value will be consistent to not trash external # HTTP caches. You can override the default behavior by changing this variable. This is not recommended but might give # you a performance improvement on the cost of cacheability. -app.config['JSON_SORT_KEYS'] = False +app.config["JSON_SORT_KEYS"] = False logging.basicConfig(level=logging.DEBUG) -LAST_IMAGE = 'last_image.jpg' +LAST_IMAGE = "last_image.jpg" -def save_last_image(file_data): - with open(LAST_IMAGE, 'wb') as f: +def save_last_image(file_data) -> None: + with open(LAST_IMAGE, "wb") as f: f.write(file_data) -def load_last_image(): - with open(LAST_IMAGE, 'rb') as f: +def load_last_image() -> bytes: + with open(LAST_IMAGE, "rb") as f: return f.read() -@app.route('/') +@app.route("/") def index(): - return render_template_string('''\ + return render_template_string( + """\ @@ -279,70 +280,72 @@ def index(): -''', commands=COMMANDS) +""", + commands=COMMANDS, + ) -@app.route("/upload_file", methods=['POST']) +@app.route("/upload_file", methods=["POST"]) def upload_file(): print(request.files) # check if the post request has the file part - if 'file' not in request.files: - return redirect('/') + if "file" not in request.files: + return redirect("/") - file = request.files['file'] + file = request.files["file"] file_data = file.stream.read() save_last_image(file_data) return jsonify({ - 'img_original': img_to_base64_html(file_data), - 'img_process': None, + "img_original": img_to_base64_html(file_data), + "img_process": None, }) -@app.route("/upload_url", methods=['POST']) +@app.route("/upload_url", methods=["POST"]) def upload_url(): print(request.form) - if 'url' not in request.form: - return redirect('/') + if "url" not in request.form: + return redirect("/") - url = request.form['url'] + url = request.form["url"] file_data = requests.get(url).content save_last_image(file_data) return jsonify({ - 'img_original': img_to_base64_html(file_data), - 'img_process': None, + "img_original": img_to_base64_html(file_data), + "img_process": None, }) -@app.route("/image_process", methods=['POST']) +@app.route("/image_process", methods=["POST"]) def image_process(): print(request.form) - command = request.form['command'] + command = request.form["command"] img_original = load_last_image() data_io = io.BytesIO(img_original) - img = Image.open(data_io).convert('RGB') + img = Image.open(data_io).convert("RGB") # Получение и вызов функции result = COMMANDS[command](img) data_io = io.BytesIO() - result.save(data_io, 'JPEG') + result.save(data_io, "JPEG") img_process = data_io.getvalue() return jsonify({ - 'img_original': img_to_base64_html(img_original), - 'img_process': img_to_base64_html(img_process), + "img_original": img_to_base64_html(img_original), + "img_process": img_to_base64_html(img_process), }) -if __name__ == '__main__': +if __name__ == "__main__": app.debug = True # Localhost diff --git a/flask__webservers/get_upload_image_info/main.py b/flask__webservers/get_upload_image_info/main.py index 7897c4c38..b1a2edacb 100644 --- a/flask__webservers/get_upload_image_info/main.py +++ b/flask__webservers/get_upload_image_info/main.py @@ -1,14 +1,13 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import base64 import io import json import logging -import sys # pip install exifread import exifread @@ -16,17 +15,18 @@ import requests from flask import Flask, jsonify, render_template_string, redirect, request -from PIL import Image -sys.path.append('..') -from common import sizeof_fmt +# pip install humanize +from humanize import naturalsize as sizeof_fmt + +from PIL import Image # SOURCE: https://github.com/gil9red/SimplePyScripts/blob/master/print_exif/main.py def get_exif_tags(file_object_or_file_name, as_category=True): if type(file_object_or_file_name) == str: # Open image file for reading (binary mode) - file_object_or_file_name = open(file_object_or_file_name, mode='rb') + file_object_or_file_name = open(file_object_or_file_name, mode="rb") # Return Exif tags tags = exifread.process_file(file_object_or_file_name) @@ -45,9 +45,9 @@ def get_exif_tags(file_object_or_file_name, as_category=True): try: # If last 2 items equals [0, 0] if value.values[-2:] == [0, 0]: - value = bytes(value.values[:-2]).decode('utf-16') + value = bytes(value.values[:-2]).decode("utf-16") else: - value = bytes(value.values).decode('utf-16') + value = bytes(value.values).decode("utf-16") except: value = str(value.values) @@ -68,8 +68,8 @@ def get_exif_tags(file_object_or_file_name, as_category=True): else: # Fill categories_by_tag - if ' ' in tag: - category, sub_tag = tag.split(' ', maxsplit=1) + if " " in tag: + category, sub_tag = tag.split(" ", maxsplit=1) if category not in tags_by_value: tags_by_value[category] = dict() @@ -85,11 +85,11 @@ def get_exif_tags(file_object_or_file_name, as_category=True): # SOURCE: https://github.com/gil9red/SimplePyScripts/blob/master/img_to_base64_html/main.py -def img_to_base64_html(file_name__or__bytes__or__file_object): +def img_to_base64_html(file_name__or__bytes__or__file_object) -> str: arg = file_name__or__bytes__or__file_object if type(arg) == str: - with open(arg, mode='rb') as f: + with open(arg, mode="rb") as f: img_bytes = f.read() elif type(arg) == bytes: @@ -101,10 +101,10 @@ def img_to_base64_html(file_name__or__bytes__or__file_object): bytes_io = io.BytesIO(img_bytes) img = Image.open(bytes_io) - img_base64 = base64.b64encode(img_bytes).decode('utf-8') + img_base64 = base64.b64encode(img_bytes).decode("utf-8") # print(img_base64) - return 'data:image/{};base64,'.format(img.format.lower()) + img_base64 + return f"data:image/{img.format.lower()};base64,{img_base64}" # SOURCE: https://github.com/gil9red/SimplePyScripts/blob/84651cfefaee768851170ec4ba7d025bbaae622d/get_image_info/main.py#L84 @@ -114,7 +114,7 @@ def get_image_info(file_name__or__bytes__or__bytes_io, pretty_json_str=False): # File name if type_data == str: - with open(data, mode='rb') as f: + with open(data, mode="rb") as f: data = f.read() if type(data) == bytes: @@ -126,23 +126,30 @@ def get_image_info(file_name__or__bytes__or__bytes_io, pretty_json_str=False): img = Image.open(data) info = dict() - info['length'] = dict() - info['length']['value'] = length - info['length']['text'] = sizeof_fmt(length) - - info['format'] = img.format - info['mode'] = img.mode - info['channels'] = len(img.getbands()) - info['bit_color'] = { - '1': 1, 'L': 8, 'P': 8, 'RGB': 24, 'RGBA': 32, - 'CMYK': 32, 'YCbCr': 24, 'I': 32, 'F': 32 + info["length"] = dict() + info["length"]["value"] = length + info["length"]["text"] = sizeof_fmt(length) + + info["format"] = img.format + info["mode"] = img.mode + info["channels"] = len(img.getbands()) + info["bit_color"] = { + "1": 1, + "L": 8, + "P": 8, + "RGB": 24, + "RGBA": 32, + "CMYK": 32, + "YCbCr": 24, + "I": 32, + "F": 32, }[img.mode] - info['size'] = dict() - info['size']['width'] = img.width - info['size']['height'] = img.height + info["size"] = dict() + info["size"]["width"] = img.width + info["size"]["height"] = img.height - info['exif'] = exif + info["exif"] = exif if pretty_json_str: info = json.dumps(info, indent=4, ensure_ascii=False) @@ -157,14 +164,15 @@ def get_image_info(file_name__or__bytes__or__bytes_io, pretty_json_str=False): # ensure that independent of the hash seed of the dictionary the return value will be consistent to not trash external # HTTP caches. You can override the default behavior by changing this variable. This is not recommended but might give # you a performance improvement on the cost of cacheability. -app.config['JSON_SORT_KEYS'] = False +app.config["JSON_SORT_KEYS"] = False logging.basicConfig(level=logging.DEBUG) -@app.route('/') +@app.route("/") def index(): - return render_template_string('''\ + return render_template_string( + """\ @@ -356,43 +364,44 @@ def index(): -''') +""" + ) -@app.route("/get_info_from_file", methods=['POST']) +@app.route("/get_info_from_file", methods=["POST"]) def get_info_from_file(): print(request.files) # check if the post request has the file part - if 'file' not in request.files: - return redirect('/') + if "file" not in request.files: + return redirect("/") - file = request.files['file'] + file = request.files["file"] file_data = file.stream.read() info = get_image_info(file_data) - info['img_base64'] = img_to_base64_html(file_data) + info["img_base64"] = img_to_base64_html(file_data) return jsonify(info) -@app.route("/get_info_from_url", methods=['POST']) +@app.route("/get_info_from_url", methods=["POST"]) def get_info_from_url(): print(request.form) - if 'url' not in request.form: - return redirect('/') + if "url" not in request.form: + return redirect("/") - url = request.form['url'] + url = request.form["url"] file_data = requests.get(url).content info = get_image_info(file_data) - info['img_base64'] = img_to_base64_html(file_data) + info["img_base64"] = img_to_base64_html(file_data) return jsonify(info) -if __name__ == '__main__': +if __name__ == "__main__": app.debug = True # Localhost diff --git a/flask__webservers/hash__md5_sha1_etc/main.py b/flask__webservers/hash__md5_sha1_etc/main.py index b97386b77..f13aaa1ba 100644 --- a/flask__webservers/hash__md5_sha1_etc/main.py +++ b/flask__webservers/hash__md5_sha1_etc/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import hashlib @@ -15,14 +15,17 @@ app = Flask(__name__) -SUPPORTED_HASH_ALGOS = set(list(hashlib.algorithms_guaranteed) + ['md4']) -SUPPORTED_HASH_ALGOS = [algo for algo in SUPPORTED_HASH_ALGOS if not algo.lower().startswith('shake')] +SUPPORTED_HASH_ALGOS = set(list(hashlib.algorithms_guaranteed) + ["md4"]) +SUPPORTED_HASH_ALGOS = [ + algo for algo in SUPPORTED_HASH_ALGOS if not algo.lower().startswith("shake") +] SUPPORTED_HASH_ALGOS = sorted(SUPPORTED_HASH_ALGOS) @app.route("/") def index(): - return render_template_string('''\ + return render_template_string( + """\ @@ -131,46 +134,51 @@ def index(): -''', algos=SUPPORTED_HASH_ALGOS) +""", + algos=SUPPORTED_HASH_ALGOS, + ) -@app.route("/do_hash", methods=['GET', 'POST']) +@app.route("/do_hash", methods=["GET", "POST"]) def do_hash(): - print('request.args:', request.args) - print('request.form:', request.form) - print('request.files:', request.files) + print("request.args:", request.args) + print("request.form:", request.form) + print("request.files:", request.files) print() - text = request.args.get('text') + text = request.args.get("text") if not text: - text = request.form.get('text') + text = request.form.get("text") - file = request.files.get('file') + file = request.files.get("file") - algo = request.args.get('hash') + algo = request.args.get("hash") if not algo: - algo = request.form.get('hash') + algo = request.form.get("hash") if not ((text or file) and algo): - return render_template_string("""\ + return render_template_string( + """\ Example:
{{ example_uri }}&hash=md5
{{ example_uri }}&hash=sha1
{{ example_uri }}&hash=sha512 - """, example_uri='/do_hash?text=Hello World!') + """, + example_uri="/do_hash?text=Hello World!", + ) result = { - 'result': None, - 'hash': algo, - 'error': None, + "result": None, + "hash": algo, + "error": None, } if algo not in SUPPORTED_HASH_ALGOS: - result['error'] = f'Unsupported algorithm {algo!r}' + result["error"] = f"Unsupported algorithm {algo!r}" else: if file: digest = hashlib.new(algo) - for chunk in iter(lambda: file.stream.read(128 * digest.block_size), b''): + for chunk in iter(lambda: file.stream.read(128 * digest.block_size), b""): digest.update(chunk) else: @@ -178,7 +186,7 @@ def do_hash(): hex_digest = digest.hexdigest() - result['result'] = hex_digest + result["result"] = hex_digest # Вернется json c результатом хеширования return jsonify(result) diff --git a/flask__webservers/hello_world.py b/flask__webservers/hello_world.py index a6418ba30..90f9c84e3 100644 --- a/flask__webservers/hello_world.py +++ b/flask__webservers/hello_world.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import logging @@ -14,11 +14,11 @@ @app.route("/") -def index(): +def index() -> str: return "Hello World!" -if __name__ == '__main__': +if __name__ == "__main__": app.debug = True # Localhost diff --git a/flask__webservers/hello_world_with_compress.py b/flask__webservers/hello_world_with_compress.py new file mode 100644 index 000000000..4c7185f22 --- /dev/null +++ b/flask__webservers/hello_world_with_compress.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import logging + +from flask import Flask + +# pip install flask-compress +from flask_compress import Compress + + +app = Flask(__name__) +Compress(app) + +logging.basicConfig(level=logging.DEBUG) + + +@app.route("/") +def index(): + return "Hello World!" * 100 + + +if __name__ == "__main__": + app.debug = True + + # Localhost + # port=0 -- random free port + # app.run(port=0) + app.run(port=5001) + + # # Public IP + # app.run(host='0.0.0.0') diff --git a/flask__webservers/json_as_response.py b/flask__webservers/json_as_response.py index a521639e3..a744bf9e3 100644 --- a/flask__webservers/json_as_response.py +++ b/flask__webservers/json_as_response.py @@ -1,24 +1,24 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import logging - from flask import Flask, jsonify -app = Flask(__name__) + +app = Flask(__name__) logging.basicConfig(level=logging.DEBUG) DATA = { - 'text': "Hello World!", - 'ok': True, - 'items': [ - 'Hello', - 'World!', - ] + "text": "Hello World!", + "ok": True, + "items": [ + "Hello", + "World!", + ], } @@ -32,5 +32,5 @@ def index_v2(): return DATA -if __name__ == '__main__': +if __name__ == "__main__": app.run() diff --git a/flask__webservers/logging__examples/log-config.yaml b/flask__webservers/logging__examples/log-config.yaml new file mode 100644 index 000000000..b427173a0 --- /dev/null +++ b/flask__webservers/logging__examples/log-config.yaml @@ -0,0 +1,27 @@ +version: 1 + +formatters: + default: + format: "[%(asctime)s] %(filename)s[LINE:%(lineno)d] %(levelname)-8s %(message)s" + +handlers: + console: + class: "logging.StreamHandler" + formatter: "default" + stream: "ext://sys.stdout" + + web-server-file: + class: "logging.handlers.RotatingFileHandler" + formatter: "default" + filename: "main_new.log" + encoding: "utf-8" + backupCount: 5 + maxBytes: 10000000 + delay: true + +loggers: + web-server: &web-server + handlers: ["console", "web-server-file"] + level: "DEBUG" + + werkzeug: *web-server diff --git a/flask__webservers/logging__examples/main_new.py b/flask__webservers/logging__examples/main_new.py new file mode 100644 index 000000000..8000e0e63 --- /dev/null +++ b/flask__webservers/logging__examples/main_new.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import logging.config +from pathlib import Path +from typing import Any + +# pip install PyYAML +import yaml + +from flask import Flask + + +DIR: Path = Path(__file__).resolve().parent + +DIR_LOGS: Path = DIR / "logs" +DIR_LOGS.mkdir(parents=True, exist_ok=True) + +CONFIG_LOG_FILE_NAME: Path = DIR / "log-config.yaml" + + +LOGGING: dict[str, Any] = yaml.safe_load( + CONFIG_LOG_FILE_NAME.read_text("utf-8") +) +for handler in LOGGING["handlers"].values(): + try: + handler["filename"] = DIR_LOGS / handler["filename"] + except KeyError: + pass + +logging.config.dictConfig(LOGGING) + +log = logging.getLogger("web-server") + + +app = Flask(__name__) +app.logger = log + + +@app.route("/") +def index() -> str: + log.debug("call index") + return "Hello World!" + + +if __name__ == "__main__": + app.debug = True + + app.run(host="0.0.0.0", port=5000) diff --git a/flask__webservers/logging__examples/main_old.py b/flask__webservers/logging__examples/main_old.py new file mode 100644 index 000000000..e5bdab429 --- /dev/null +++ b/flask__webservers/logging__examples/main_old.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import logging +import sys +from logging.handlers import RotatingFileHandler +from pathlib import Path + +from flask import Flask + + +DIR_LOGS: Path = Path(__file__).resolve().parent / "logs" +DIR_LOGS.mkdir(parents=True, exist_ok=True) + + +app = Flask(__name__) + +formatter = logging.Formatter( + "[%(asctime)s] %(filename)s:%(lineno)d %(levelname)-8s %(message)s" +) + +file_handler = RotatingFileHandler( + DIR_LOGS / "main_old.log", maxBytes=10_000_000, backupCount=5, encoding="utf-8" +) +file_handler.setFormatter(formatter) + +stream_handler = logging.StreamHandler(stream=sys.stdout) +stream_handler.setFormatter(formatter) + +log: logging.Logger = app.logger +log.handlers.clear() +log.setLevel(logging.DEBUG) +log.addHandler(file_handler) +log.addHandler(stream_handler) + +log_werkzeug = logging.getLogger("werkzeug") +log_werkzeug.setLevel(logging.DEBUG) +log_werkzeug.addHandler(file_handler) +log_werkzeug.addHandler(stream_handler) + + +@app.route("/") +def index() -> str: + log.debug("call index") + return "Hello World!" + + +if __name__ == "__main__": + app.debug = True + + app.run(host="0.0.0.0", port=5000) diff --git a/flask__webservers/logging__remove_date_from_werkzeug_logs/log-config.yaml b/flask__webservers/logging__remove_date_from_werkzeug_logs/log-config.yaml new file mode 100644 index 000000000..90c089ccd --- /dev/null +++ b/flask__webservers/logging__remove_date_from_werkzeug_logs/log-config.yaml @@ -0,0 +1,30 @@ +version: 1 + +formatters: + default: + format: "[%(asctime)s] %(filename)s[LINE:%(lineno)d] %(levelname)-8s %(message)s" + +handlers: + console: + class: "logging.StreamHandler" + formatter: "default" + stream: "ext://sys.stdout" + + web-server-file: + class: "logging.handlers.RotatingFileHandler" + formatter: "default" + filename: "main_new.log" + encoding: "utf-8" + backupCount: 5 + maxBytes: 10000000 + delay: true + +filters: + filter_remove_date_from_werkzeug_logs: + (): utils.FilterRemoveDateFromWerkzeugLogs + +loggers: + werkzeug: + handlers: ["console", "web-server-file"] + level: "DEBUG" + filters: ["filter_remove_date_from_werkzeug_logs"] diff --git a/flask__webservers/logging__remove_date_from_werkzeug_logs/main_new.py b/flask__webservers/logging__remove_date_from_werkzeug_logs/main_new.py new file mode 100644 index 000000000..6c2e83aec --- /dev/null +++ b/flask__webservers/logging__remove_date_from_werkzeug_logs/main_new.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import logging.config +from pathlib import Path +from typing import Any + +# pip install PyYAML +import yaml + +from flask import Flask + + +DIR: Path = Path(__file__).resolve().parent + +DIR_LOGS: Path = DIR / "logs" +DIR_LOGS.mkdir(parents=True, exist_ok=True) + +CONFIG_LOG_FILE_NAME: Path = DIR / "log-config.yaml" + + +LOGGING: dict[str, Any] = yaml.safe_load( + CONFIG_LOG_FILE_NAME.read_text("utf-8") +) +for handler in LOGGING["handlers"].values(): + try: + handler["filename"] = DIR_LOGS / handler["filename"] + except KeyError: + pass + +logging.config.dictConfig(LOGGING) + +log = logging.getLogger("werkzeug") + + +app = Flask(__name__) +app.logger = log + + +@app.route("/") +def index() -> str: + log.debug("call index") + return "Hello World!" + + +if __name__ == "__main__": + app.debug = True + + app.run(host="0.0.0.0", port=5000) diff --git a/flask__webservers/logging__remove_date_from_werkzeug_logs/main_old.py b/flask__webservers/logging__remove_date_from_werkzeug_logs/main_old.py new file mode 100644 index 000000000..7b7c003bb --- /dev/null +++ b/flask__webservers/logging__remove_date_from_werkzeug_logs/main_old.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import logging +import sys + +from logging.handlers import RotatingFileHandler +from pathlib import Path + +from flask import Flask + +from utils import FilterRemoveDateFromWerkzeugLogs + + +DIR_LOGS: Path = Path(__file__).resolve().parent / "logs" +DIR_LOGS.mkdir(parents=True, exist_ok=True) + + +app = Flask(__name__) + +formatter = logging.Formatter( + "[%(asctime)s] %(filename)s:%(lineno)d %(levelname)-8s %(message)s" +) + +file_handler = RotatingFileHandler( + DIR_LOGS / "main_old.log", maxBytes=10_000_000, backupCount=5, encoding="utf-8" +) +file_handler.setFormatter(formatter) + +stream_handler = logging.StreamHandler(stream=sys.stdout) +stream_handler.setFormatter(formatter) + +log: logging.Logger = app.logger +log.handlers.clear() +log.setLevel(logging.DEBUG) +log.addHandler(file_handler) +log.addHandler(stream_handler) + +log_werkzeug = logging.getLogger("werkzeug") +log_werkzeug.setLevel(logging.DEBUG) +log_werkzeug.addHandler(file_handler) +log_werkzeug.addHandler(stream_handler) +log_werkzeug.addFilter(FilterRemoveDateFromWerkzeugLogs()) + + +@app.route("/") +def index() -> str: + log.debug("call index") + return "Hello World!" + + +if __name__ == "__main__": + app.debug = True + + app.run(host="0.0.0.0", port=5000) diff --git a/flask__webservers/logging__remove_date_from_werkzeug_logs/utils.py b/flask__webservers/logging__remove_date_from_werkzeug_logs/utils.py new file mode 100644 index 000000000..531a2deba --- /dev/null +++ b/flask__webservers/logging__remove_date_from_werkzeug_logs/utils.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import logging +import re + + +# NOTE: Fix https://github.com/pallets/werkzeug/blob/72b2e48e7d44927b1b7d6b2f940d0691230de893/src/werkzeug/serving.py#L425C38-L425C44 +class FilterRemoveDateFromWerkzeugLogs(logging.Filter): + # '192.168.0.102 - - [30/Jun/2024 01:14:03] "%s" %s %s' -> '192.168.0.102 - "%s" %s %s' + pattern: re.Pattern = re.compile(r' - - \[.+?] "') + + def filter(self, record: logging.LogRecord) -> bool: + record.msg = self.pattern.sub(' - "', record.msg) + return True diff --git a/flask__webservers/post_data.py b/flask__webservers/post_data.py index d2ece5c21..6d22577a5 100644 --- a/flask__webservers/post_data.py +++ b/flask__webservers/post_data.py @@ -1,18 +1,19 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" +import logging from flask import Flask, request -app = Flask(__name__) -import logging + +app = Flask(__name__) logging.basicConfig(level=logging.DEBUG) @app.route("/") -def index(): +def index() -> str: return """
@@ -38,13 +39,13 @@ def index(): """ -@app.route("/print_data", methods=['POST']) +@app.route("/print_data", methods=["POST"]) def print_data(): data = request.data - print('data:', data) + print("data:", data) return data -if __name__ == '__main__': +if __name__ == "__main__": app.run() diff --git a/flask__webservers/post_data__as_form.py b/flask__webservers/post_data__as_form.py index 2791a8c43..ab732dd71 100644 --- a/flask__webservers/post_data__as_form.py +++ b/flask__webservers/post_data__as_form.py @@ -1,18 +1,19 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -from flask import Flask, request, jsonify -app = Flask(__name__) - import logging +from flask import Flask, request + + +app = Flask(__name__) logging.basicConfig(level=logging.DEBUG) @app.route("/") -def index(): +def index() -> str: return """

@@ -46,16 +47,16 @@ def index(): """ -@app.route("/print_data", methods=['POST']) +@app.route("/print_data", methods=["POST"]) def print_data(): data = request.data - print('data:', data) + print("data:", data) form = request.form - print('form:', form) + print("form:", form) - return form['name'] + " " + form['surname'] + return form["name"] + " " + form["surname"] -if __name__ == '__main__': +if __name__ == "__main__": app.run() diff --git a/flask__webservers/post_data__as_json.py b/flask__webservers/post_data__as_json.py index 9666b60aa..3c44aa550 100644 --- a/flask__webservers/post_data__as_json.py +++ b/flask__webservers/post_data__as_json.py @@ -1,18 +1,19 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" +import logging from flask import Flask, request -app = Flask(__name__) -import logging + +app = Flask(__name__) logging.basicConfig(level=logging.DEBUG) @app.route("/") -def index(): +def index() -> str: return """
@@ -42,16 +43,16 @@ def index(): """ -@app.route("/print_data", methods=['POST']) +@app.route("/print_data", methods=["POST"]) def print_data(): data = request.data - print('data:', data) + print("data:", data) json = request.json - print('json:', json) + print("json:", json) return data -if __name__ == '__main__': +if __name__ == "__main__": app.run() diff --git a/flask__webservers/print_hex_post_data/main.py b/flask__webservers/print_hex_post_data/main.py index 0a571f9f3..e8b699b97 100644 --- a/flask__webservers/print_hex_post_data/main.py +++ b/flask__webservers/print_hex_post_data/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import binascii @@ -15,15 +15,15 @@ logging.basicConfig(level=logging.DEBUG) -@app.route("/", methods=['POST']) -def index(): +@app.route("/", methods=["POST"]) +def index() -> str: data = request.data print(binascii.hexlify(data), data) return "Ok" -if __name__ == '__main__': +if __name__ == "__main__": app.debug = True app.run(port=33333) diff --git a/flask__webservers/redirect_to_/main.py b/flask__webservers/redirect_to_/main.py index b315d5013..1e5e8dec1 100644 --- a/flask__webservers/redirect_to_/main.py +++ b/flask__webservers/redirect_to_/main.py @@ -1,40 +1,44 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" +import logging from flask import Flask, redirect, request, render_template_string -app = Flask(__name__) -import logging + +app = Flask(__name__) logging.basicConfig(level=logging.DEBUG) @app.route("/") def index(): - return render_template_string('''\ + return render_template_string( + """\
{{ example }}

- ''', example='/redirect?url=http://bash.im/') + """, + example="/redirect?url=http://bash.im/", + ) -@app.route('/redirect') +@app.route("/redirect") def redirect_to(): # From url arguments - url = request.args.get('url', None) + url = request.args.get("url", None) if not url: # From form arguments - url = request.form.get('url', '/') + url = request.form.get("url", "/") return redirect(url) -if __name__ == '__main__': +if __name__ == "__main__": # Localhost app.run(port=5001) diff --git a/flask__webservers/redirect_to_google/main.py b/flask__webservers/redirect_to_google/main.py index 7f7751ac2..93b33e734 100644 --- a/flask__webservers/redirect_to_google/main.py +++ b/flask__webservers/redirect_to_google/main.py @@ -1,22 +1,23 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" +import logging from flask import Flask, redirect -app = Flask(__name__) -import logging + +app = Flask(__name__) logging.basicConfig(level=logging.DEBUG) @app.route("/") def index(): - return redirect('https://google.ru') + return redirect("https://google.ru") -if __name__ == '__main__': +if __name__ == "__main__": # Localhost app.run(port=5001) diff --git a/flask__webservers/run_bat_from_ajax/main.py b/flask__webservers/run_bat_from_ajax/main.py index 146078588..9055ec144 100644 --- a/flask__webservers/run_bat_from_ajax/main.py +++ b/flask__webservers/run_bat_from_ajax/main.py @@ -1,19 +1,20 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import logging import os import subprocess + from pathlib import Path from flask import Flask, jsonify, render_template DIR = Path(__file__).resolve().parent -DIR_BAT_SCRIPTS = DIR / 'bat_scripts' +DIR_BAT_SCRIPTS = DIR / "bat_scripts" app = Flask(__name__) @@ -21,28 +22,31 @@ logging.basicConfig(level=logging.DEBUG) -@app.route('/') +@app.route("/") def index(): - return render_template('index.html', scripts=[f.name for f in DIR_BAT_SCRIPTS.glob('*.bat')]) + return render_template( + "index.html", + scripts=[f.name for f in DIR_BAT_SCRIPTS.glob("*.bat")], + ) -@app.route("/os_startfile/", methods=['POST']) +@app.route("/os_startfile/", methods=["POST"]) def on_os_startfile(script_name: str): os.startfile(DIR_BAT_SCRIPTS / script_name) - return jsonify({'ok': True}) + return jsonify({"ok": True}) -@app.route("/subprocess/", methods=['POST']) +@app.route("/subprocess/", methods=["POST"]) def on_subprocess(script_name: str): result = subprocess.check_output( DIR_BAT_SCRIPTS / script_name, universal_newlines=True, timeout=1 ) print(result) os.startfile(DIR_BAT_SCRIPTS / script_name) - return jsonify({'ok': True, 'result': result}) + return jsonify({"ok": True, "result": result}) -if __name__ == '__main__': +if __name__ == "__main__": app.debug = True # Localhost diff --git a/flask__webservers/run_with_random_port/main.py b/flask__webservers/run_with_random_port/main.py index 5e3244a97..f9a8d2d53 100644 --- a/flask__webservers/run_with_random_port/main.py +++ b/flask__webservers/run_with_random_port/main.py @@ -5,22 +5,23 @@ # SOURCE: https://stackoverflow.com/a/5089963/5909792 -from flask import Flask, request import socket +from flask import Flask, request + app = Flask(__name__) -@app.route('/') -def hello(): - return 'Hello, world! running on %s' % request.host +@app.route("/") +def hello() -> str: + return "Hello, world! running on %s" % request.host -if __name__ == '__main__': +if __name__ == "__main__": sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - sock.bind(('localhost', 0)) + sock.bind(("localhost", 0)) port = sock.getsockname()[1] - print('sock.getsockname:', sock.getsockname()) + print("sock.getsockname:", sock.getsockname()) sock.close() app.run(port=port) diff --git a/flask__webservers/server__handle_stdin__input.py b/flask__webservers/server__handle_stdin__input.py index 414428fb7..288ff43e8 100644 --- a/flask__webservers/server__handle_stdin__input.py +++ b/flask__webservers/server__handle_stdin__input.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import logging @@ -20,9 +20,9 @@ text = "Hello World!" -def go(): +def go() -> None: time.sleep(2) - print('\n') + print("\n") global text @@ -35,7 +35,7 @@ def index(): return text -if __name__ == '__main__': +if __name__ == "__main__": thread = Thread(target=go) thread.start() diff --git a/flask__webservers/server_with_additional_command_thread/main.py b/flask__webservers/server_with_additional_command_thread/main.py index 12d4ccac4..fcb917e94 100644 --- a/flask__webservers/server_with_additional_command_thread/main.py +++ b/flask__webservers/server_with_additional_command_thread/main.py @@ -1,13 +1,21 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" +import logging +import time + +from urllib.parse import urljoin + +import requests + +from bs4 import BeautifulSoup from flask import Flask, request, redirect -app = Flask(__name__) -import logging + +app = Flask(__name__) logging.basicConfig(level=logging.DEBUG) @@ -17,55 +25,50 @@ @app.route("/") def index(): - return redirect('/img_search?text=Котята') + return redirect("/img_search?text=Котята") @app.route("/img_search") def img_search(): - text = request.args.get('text') - print('text:', text) + text = request.args.get("text") + print("text:", text) global EXECUTE_COMMAND, COMMAND_TEXT COMMAND_TEXT = text EXECUTE_COMMAND = False - return 'text: ' + text - + return "text: " + text -def loop_command_function(): - import time +def loop_command_function() -> None: while True: global EXECUTE_COMMAND, COMMAND_TEXT if not EXECUTE_COMMAND and COMMAND_TEXT: print(COMMAND_TEXT) - url = 'http://yandex.ru/images/search?text=' + COMMAND_TEXT + url = "http://yandex.ru/images/search?text=" + COMMAND_TEXT - import requests rs = requests.get(url) print(rs) - from bs4 import BeautifulSoup - root = BeautifulSoup(rs.content, 'lxml') - - from urllib.parse import urljoin + root = BeautifulSoup(rs.content, "lxml") img_list = [] - for img in root.select('img.serp-item__thumb'): - url_img = urljoin(url, img['src']) + for img in root.select("img.serp-item__thumb"): + url_img = urljoin(url, img["src"]) img_list.append(url_img) - print('img_list[{}]: {}'.format(len(img_list), img_list)) + print(f"img_list[{len(img_list)}]: {img_list}") EXECUTE_COMMAND = True time.sleep(1) -if __name__ == '__main__': +if __name__ == "__main__": from threading import Thread + thread = Thread(target=loop_command_function) thread.start() diff --git a/flask__webservers/server_with_window_notification/main.py b/flask__webservers/server_with_window_notification/main.py index 866cb270f..c7a16d49f 100644 --- a/flask__webservers/server_with_window_notification/main.py +++ b/flask__webservers/server_with_window_notification/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import logging @@ -13,7 +13,9 @@ from flask import Flask, request, redirect ROOT = Path(__file__).resolve().parent.parent.parent -sys.path.append(str(ROOT / 'winapi__windows__ctypes/windows__toast_balloontip_notifications')) +sys.path.append( + str(ROOT / "winapi__windows__ctypes/windows__toast_balloontip_notifications") +) from run_notify import run_in_thread @@ -21,29 +23,29 @@ logging.basicConfig(level=logging.DEBUG) -def show(text): +def show(text) -> None: title = str(threading.current_thread()) run_in_thread(title, text, duration=20) @app.route("/") def index(): - return redirect('/show_notification?text=О, уведомление пришло!') + return redirect("/show_notification?text=О, уведомление пришло!") @app.route("/show_notification") -def show_notification(): - text = request.args.get('text') - print('text:', text) +def show_notification() -> str: + text = request.args.get("text") + print("text:", text) # Run function in new thread thread = threading.Thread(target=show, args=(text,)) thread.start() - return 'Ok' + return "Ok" -if __name__ == '__main__': +if __name__ == "__main__": # Localhost app.run( port=5000, diff --git a/flask__webservers/set_response_headers.py b/flask__webservers/set_response_headers.py index 5038d328e..edc84839f 100644 --- a/flask__webservers/set_response_headers.py +++ b/flask__webservers/set_response_headers.py @@ -1,23 +1,24 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" +import logging from flask import Flask, Response -app = Flask(__name__) -import logging + +app = Flask(__name__) logging.basicConfig(level=logging.DEBUG) @app.route("/") def index(): rs = Response("Hello World!") - rs.headers['User-Agent'] = "FooBar" + rs.headers["User-Agent"] = "FooBar" return rs -if __name__ == '__main__': +if __name__ == "__main__": app.run() diff --git a/flask__webservers/show_and_download_from_readmanga/main.py b/flask__webservers/show_and_download_from_readmanga/main.py index 8d8d0eb6d..7b8a2a63c 100644 --- a/flask__webservers/show_and_download_from_readmanga/main.py +++ b/flask__webservers/show_and_download_from_readmanga/main.py @@ -1,24 +1,24 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" +import logging +import os +import sys + from flask import Flask, render_template_string, request, redirect -app = Flask(__name__) -import logging + +app = Flask(__name__) logging.basicConfig(level=logging.DEBUG) # Добавление пути основной папки репозитория, чтобы импортировать модуль download_volume_readmanga -import os dir = os.path.dirname(__file__) dir = os.path.dirname(dir) dir = os.path.dirname(dir) - -import sys sys.path.append(dir) - from download_volume_readmanga import get_url_images, save_urls_to_zip @@ -27,7 +27,7 @@

Например: {0}

""" -DEFAULT_URL_MANGA = 'https://readmanga.live/one_punch_man__A1bc88e/vol1/1' +DEFAULT_URL_MANGA = "https://readmanga.live/one_punch_man__A1bc88e/vol1/1" # TODO: FIXED HTTP STATUS "402 Payment Required" on @@ -35,15 +35,16 @@ @app.route("/") def index(): if not request.args: - return NOT_ARGS_HTML.format(f'/?url={DEFAULT_URL_MANGA}') + return NOT_ARGS_HTML.format(f"/?url={DEFAULT_URL_MANGA}") - url = request.args.get('url') - print('Url manga:', url) + url = request.args.get("url") + print("Url manga:", url) images_urls = get_url_images(url) - print('Urls images:', images_urls) + print("Urls images:", images_urls) - return render_template_string('''\ + return render_template_string( + """\ Показать и скачать из readmanga @@ -61,32 +62,37 @@ def index(): - ''', url=url, images_urls=images_urls) + """, + url=url, + images_urls=images_urls, + ) @app.route("/export") def export(): if not request.args: - return NOT_ARGS_HTML.format(f'/export?{DEFAULT_URL_MANGA}') + return NOT_ARGS_HTML.format(f"/export?{DEFAULT_URL_MANGA}") - url = request.args.get('url') - print('Url manga:', url) + url = request.args.get("url") + print("Url manga:", url) - file_name = os.path.basename(url) + '.zip' - static_file_name = os.path.join('static', file_name) + file_name = os.path.basename(url) + ".zip" + static_file_name = os.path.join("static", file_name) # Если архива с главой нет, качаем ее if not os.path.exists(static_file_name): - print('Архива "{}" нет, качаем и создаем.'.format(static_file_name)) + print(f'Архива "{static_file_name}" нет, качаем и создаем.') images_urls = get_url_images(url) - print('Urls images:', images_urls) + print("Urls images:", images_urls) save_urls_to_zip(static_file_name, images_urls) - print('Сохранено в архиве:', file_name) + print("Сохранено в архиве:", file_name) # Перенаправляем к url с архивом - relative_url = render_template_string("{{ url_for('static', filename='%s') }}" % (file_name, )) + relative_url = render_template_string( + "{{ url_for('static', filename='%s') }}" % (file_name,) + ) return redirect(relative_url) diff --git a/flask__webservers/show_client_ip_API.py b/flask__webservers/show_client_ip_API.py index 182aac0c2..422c6a93a 100644 --- a/flask__webservers/show_client_ip_API.py +++ b/flask__webservers/show_client_ip_API.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import logging @@ -14,28 +14,25 @@ logging.basicConfig(level=logging.DEBUG) -@app.route('/') +@app.route("/") def index(): return request.remote_addr -@app.route('/json') +@app.route("/json") def get_json(): return { - 'ip': request.remote_addr, + "ip": request.remote_addr, } -@app.route('/xml') +@app.route("/xml") def get_xml(): - root = ET.Element('ip') + root = ET.Element("ip") root.text = request.remote_addr xml_bytes = ET.tostring(root, encoding="utf-8", xml_declaration=True) - return Response(xml_bytes, mimetype='text/xml') + return Response(xml_bytes, mimetype="text/xml") -if __name__ == '__main__': - app.run( - host='0.0.0.0', - port=5000 - ) +if __name__ == "__main__": + app.run(host="0.0.0.0", port=5000) diff --git a/flask__webservers/show_last_bash_quotes/main.py b/flask__webservers/show_last_bash_quotes/main.py index 3663c4fee..3c91e6018 100644 --- a/flask__webservers/show_last_bash_quotes/main.py +++ b/flask__webservers/show_last_bash_quotes/main.py @@ -1,37 +1,35 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" +import logging import re +import traceback + +import requests + +from bs4 import BeautifulSoup +from flask import Flask, render_template_string def today_quotes(soup): # Узнаем количество сегодняшних цитат - tag = soup.find(attrs=dict(id='stats')) + tag = soup.find(attrs=dict(id="stats")) - match = re.search('сегодня (\d+),', tag.text) + match = re.search(r"сегодня (\d+),", tag.text) return int(match.group(1)) -from flask import Flask, render_template_string app = Flask(__name__) - -import logging logging.basicConfig(level=logging.DEBUG) -import requests -from bs4 import BeautifulSoup - -import traceback - - @app.route("/") def index(): try: - rs = requests.get('http://bash.im') + rs = requests.get("http://bash.im") soup = BeautifulSoup(rs.text, "lxml") number = today_quotes(soup) @@ -48,7 +46,8 @@ def index(): print(i, text) quotes.append(text) - return render_template_string('''\ + return render_template_string( + """\ Новые за день цитаты bash.im @@ -68,11 +67,14 @@ def index(): {% endif %} - ''', number=number, item_list=quotes) + """, + number=number, + item_list=quotes, + ) except BaseException as e: - print('Error: {}\n\n{}'.format(e, traceback.format_exc())) - return 'Error: ' + str(e) + print(f"Error: {e}\n\n{traceback.format_exc()}") + return "Error: " + str(e) if __name__ == "__main__": diff --git a/flask__webservers/show_lunch_menu/main.py b/flask__webservers/show_lunch_menu/main.py index 8f5ce497a..cb2de5d3c 100644 --- a/flask__webservers/show_lunch_menu/main.py +++ b/flask__webservers/show_lunch_menu/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """Чтение из docx файла таблицы меню и отображении таблицы на веб странице. @@ -10,17 +10,18 @@ """ +import logging +import re + +from docx import Document from flask import Flask, render_template_string -app = Flask(__name__) -import logging + +app = Flask(__name__) logging.basicConfig(level=logging.DEBUG) # Регулярка для поиска последовательностей пробелов: от двух подряд и более -import re -multi_space_pattern = re.compile(r'[ ]{2,}') - -from docx import Document +multi_space_pattern = re.compile(r"[ ]{2,}") def get_rows_lunch_menu(): @@ -31,16 +32,19 @@ def get_rows_lunch_menu(): for table in document.tables: # Перебор начинается со второй строки, потому что, первая строка таблицы -- это строка "Обеденное меню" for row in table.rows[1:]: - name, weight, price = [multi_space_pattern.sub(' ', i.text.strip()) for i in row.cells] + name, weight, price = [ + multi_space_pattern.sub(" ", i.text.strip()) + for i in row.cells + ] if name == weight == price or (not weight or not price): name = name.title() logging.debug(name) - rows.append((name, )) + rows.append((name,)) continue rows.append((name, weight, price)) - logging.debug('{} {} {}'.format(name, weight, price)) + logging.debug(f"{name} {weight} {price}") # Таблицы в меню дублируются break @@ -52,7 +56,8 @@ def get_rows_lunch_menu(): def index(): rows = get_rows_lunch_menu() - return render_template_string('''\ + return render_template_string( + """\ Обеденное меню @@ -82,10 +87,12 @@ def index(): - ''', rows=rows) + """, + rows=rows, + ) -if __name__ == '__main__': +if __name__ == "__main__": # Localhost app.run(port=5001) diff --git a/flask__webservers/show_lunch_menu_from_email/config.py b/flask__webservers/show_lunch_menu_from_email/config.py index 747621f50..fb26fa83a 100644 --- a/flask__webservers/show_lunch_menu_from_email/config.py +++ b/flask__webservers/show_lunch_menu_from_email/config.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # Например: @mail.com diff --git a/flask__webservers/show_lunch_menu_from_email/main.py b/flask__webservers/show_lunch_menu_from_email/main.py index 4e8e8057b..6ebcd5323 100644 --- a/flask__webservers/show_lunch_menu_from_email/main.py +++ b/flask__webservers/show_lunch_menu_from_email/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """ @@ -14,21 +14,30 @@ """ +import email +import imaplib import os -import traceback +import re import sys +import traceback +from datetime import datetime, date + +# pip install python-docx +from docx import Document +from flask import Flask, render_template_string, request import config + + if config.debug: import logging logging.basicConfig(level=logging.DEBUG, stream=sys.stdout) def get_current_lunch_file_name(): - from datetime import date - filename = date.today().strftime('%d.%m.%y') + '.docx' - return os.path.join('lunch menu', filename) + filename = date.today().strftime("%d.%m.%y") + ".docx" + return os.path.join("lunch menu", filename) def save_attachment(msg): @@ -43,22 +52,22 @@ def save_attachment(msg): os.makedirs(os.path.dirname(file_name), exist_ok=True) for part in msg.walk(): - if part.get_content_maintype() == 'multipart': + if part.get_content_maintype() == "multipart": continue - if part.get('Content-Disposition') is None: + if part.get("Content-Disposition") is None: continue if not os.path.exists(file_name): - with open(file_name, 'wb') as fp: + with open(file_name, "wb") as fp: fp.write(part.get_payload(decode=True)) else: - logging.debug('Lunch menu file exist: %s', file_name) + logging.debug("Lunch menu file exist: %s", file_name) return file_name -def add_lunch_email_info(msg, file_name): +def add_lunch_email_info(msg, file_name) -> None: """ Функция для добавления в поле comments docx информации о письме. @@ -68,24 +77,20 @@ def add_lunch_email_info(msg, file_name): """ try: - import email - # Получаем заголовок письма - data, charset = email.header.decode_header(msg['Subject'])[0] - subject = data.decode(charset).strip().replace('\t', ' ') + data, charset = email.header.decode_header(msg["Subject"])[0] + subject = data.decode(charset).strip().replace("\t", " ") logging.debug('Subject: "%s".', subject) # Получаем дату получения (отправления??) письма - from datetime import datetime - date_tuple = email.utils.parsedate_tz(msg['Date']) + date_tuple = email.utils.parsedate_tz(msg["Date"]) email_date = datetime.fromtimestamp(email.utils.mktime_tz(date_tuple)) email_date = email_date.strftime(config.header_date_format) logging.debug("Date: %s.", email_date) # Открываем документ и переписываем поле comments, добавляя в него свою информацию - from docx import Document document = Document(file_name) - document.core_properties.comments = subject + '\t' + email_date + document.core_properties.comments = subject + "\t" + email_date document.save(file_name) except BaseException as e: @@ -103,26 +108,24 @@ def get_last_lunch_menu(): # Проверка на то, что меню уже скачано file_name = get_current_lunch_file_name() if os.path.exists(file_name): - logging.debug('Lunch menu file already exist: %s', file_name) + logging.debug("Lunch menu file already exist: %s", file_name) return file_name - logging.debug('Check last email.') + logging.debug("Check last email.") - import imaplib connect = imaplib.IMAP4(config.smtp_server) connect.login(config.username, config.password) connect.select() - logging.debug('Search emails from %s.', config.lunch_email) + logging.debug("Search emails from %s.", config.lunch_email) - typ, msgnums = connect.search(None, '(HEADER From {})'.format(config.lunch_email)) + typ, msgnums = connect.search(None, f"(HEADER From {config.lunch_email})") last_id = msgnums[0].split()[-1] - typ, data = connect.fetch(last_id, '(RFC822)') + typ, data = connect.fetch(last_id, "(RFC822)") - import email msg = email.message_from_bytes(data[0][1]) - logging.debug('Save lunch file name: %s.', file_name) + logging.debug("Save lunch file name: %s.", file_name) file_name = save_attachment(msg) add_lunch_email_info(msg, file_name) @@ -132,12 +135,10 @@ def get_last_lunch_menu(): return file_name -from flask import Flask, render_template_string, request app = Flask(__name__) # Регулярка для поиска последовательностей пробелов: от двух подряд и более -import re -multi_space_pattern = re.compile(r'[ ]{2,}') +multi_space_pattern = re.compile(r"[ ]{2,}") def get_info_lunch_menu(): @@ -147,14 +148,14 @@ def get_info_lunch_menu(): if file_name is None: return rows - logging.debug('Read lunch file name: %s.', file_name) - from docx import Document + logging.debug("Read lunch file name: %s.", file_name) + document = Document(file_name) - subject, date = '', '' + subject, date = "", "" comments = document.core_properties.comments if comments: - parts = comments.split('\t', 2) + parts = comments.split("\t", 2) if len(parts) == 2: subject, date = parts @@ -164,16 +165,18 @@ def get_info_lunch_menu(): for table in document.tables: # Перебор начинается со второй строки, потому что, первая строка таблицы -- это строка "Обеденное меню" for row in table.rows[1:]: - name, weight, price = [multi_space_pattern.sub(' ', i.text.strip()) for i in row.cells] + name, weight, price = [ + multi_space_pattern.sub(" ", i.text.strip()) for i in row.cells + ] if name == weight == price or (not weight or not price): name = name.title() logging.debug(name) - rows.append((name, )) + rows.append((name,)) continue rows.append((name, weight, price)) - logging.debug('{} {} {}'.format(name, weight, price)) + logging.debug(f"{name} {weight} {price}") # Таблицы в меню дублируются break @@ -183,16 +186,17 @@ def get_info_lunch_menu(): @app.route("/") def index(): - logging.debug('/index from %s.', request.remote_addr) + logging.debug("/index from %s.", request.remote_addr) try: rows, subject, date = get_info_lunch_menu() except BaseException as e: logging.error(e) logging.error(traceback.format_exc()) - return 'Error: "{}".'.format(e), 500 + return f'Error: "{e}".', 500 - return render_template_string('''\ + return render_template_string( + """\ {{ subject }} @@ -222,10 +226,14 @@ def index(): - ''', rows=rows, subject=subject, date=date) + """, + rows=rows, + subject=subject, + date=date, + ) -if __name__ == '__main__': +if __name__ == "__main__": # Localhost app.run(port=5001) diff --git a/flask__webservers/show_my_ip/main.py b/flask__webservers/show_my_ip/main.py index 991ea4a38..8ff891a59 100644 --- a/flask__webservers/show_my_ip/main.py +++ b/flask__webservers/show_my_ip/main.py @@ -1,24 +1,25 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" +import logging from flask import Flask, request -app = Flask(__name__) -import logging + +app = Flask(__name__) logging.basicConfig(level=logging.DEBUG) @app.route("/") -def index(): - return """Your IPv4 Address Is: {}""".format(request.remote_addr) +def index() -> str: + return f"""Your IPv4 Address Is: {request.remote_addr}""" -if __name__ == '__main__': +if __name__ == "__main__": # # Localhost # app.run(port=5001) # Public IP - app.run(host='0.0.0.0') + app.run(host="0.0.0.0") diff --git a/flask__webservers/show_random_abusive/main.py b/flask__webservers/show_random_abusive/main.py index 9c2410772..5947db507 100644 --- a/flask__webservers/show_random_abusive/main.py +++ b/flask__webservers/show_random_abusive/main.py @@ -1,40 +1,41 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" +import logging +import os +import sys + from flask import Flask, render_template_string, request, redirect -app = Flask(__name__) -import logging + +app = Flask(__name__) logging.basicConfig(level=logging.DEBUG) # Добавление пути основной папки репозитория, чтобы импортировать модуль random_abusive -import os dir = os.path.dirname(__file__) dir = os.path.dirname(dir) dir = os.path.dirname(dir) - -import sys sys.path.append(dir) - import random_abusive @app.route("/") def index(): if not request.args: - return redirect('/?number=10&chain=2') + return redirect("/?number=10&chain=2") - print('query_string:', request.query_string) - number = int(request.args['number']) - chain = int(request.args['chain']) + print("query_string:", request.query_string) + number = int(request.args["number"]) + chain = int(request.args["chain"]) words = random_abusive.get_words(number, chain) - print('words:', words) + print("words:", words) - return render_template_string('''\ + return render_template_string( + """\ Рандомные матерные слова @@ -44,7 +45,9 @@ def index(): - ''', words=words) + """, + words=words, + ) if __name__ == "__main__": diff --git a/flask__webservers/show_screenshot/main.py b/flask__webservers/show_screenshot/main.py index 58b8f0918..29f52c27d 100644 --- a/flask__webservers/show_screenshot/main.py +++ b/flask__webservers/show_screenshot/main.py @@ -1,30 +1,34 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -from flask import Flask, render_template_string -app = Flask(__name__) - import logging -logging.basicConfig(level=logging.DEBUG) + +from flask import Flask, render_template_string import pyscreenshot as ImageGrab +app = Flask(__name__) +logging.basicConfig(level=logging.DEBUG) + + @app.route("/") def index(): - im = ImageGrab.grab() - im.save('static/screenshot.png') + img = ImageGrab.grab() + img.save("static/screenshot.png") - return render_template_string('''\ + return render_template_string( + """\ Show screenshot

- ''') + """ + ) if __name__ == "__main__": diff --git a/flask__webservers/show_your_user_agent.py b/flask__webservers/show_your_user_agent.py index 96aa46a87..addb8a263 100644 --- a/flask__webservers/show_your_user_agent.py +++ b/flask__webservers/show_your_user_agent.py @@ -1,23 +1,24 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" +import logging from flask import Flask, request -app = Flask(__name__) -import logging + +app = Flask(__name__) logging.basicConfig(level=logging.DEBUG) @app.route("/") def index(): - user_agent = request.headers['User-Agent'] + user_agent = request.headers["User-Agent"] print(user_agent) return user_agent -if __name__ == '__main__': +if __name__ == "__main__": app.run() diff --git a/flask__webservers/simple_echo.py b/flask__webservers/simple_echo.py index e0a234438..5bd7bf429 100644 --- a/flask__webservers/simple_echo.py +++ b/flask__webservers/simple_echo.py @@ -1,25 +1,27 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from flask import Flask, request, Response + + app = Flask(__name__) -@app.route('/', methods=['GET', 'POST']) +@app.route("/", methods=["GET", "POST"]) def index(): - print('-' * 20) - print('Method:', request.method) - print('Args:', dict(request.args)) - print('Data:', request.data) - print('Form:', request.form) - print('Headers:', dict(request.headers)) - print('-' * 20) + print("-" * 20) + print("Method:", request.method) + print("Args:", dict(request.args)) + print("Data:", request.data) + print("Form:", request.form) + print("Headers:", dict(request.headers)) + print("-" * 20) return Response( response=request.data, - content_type=request.headers.get('Content-Type') + content_type=request.headers.get("Content-Type"), ) diff --git a/flask__webservers/simple_upload_server/main.py b/flask__webservers/simple_upload_server/main.py index 82d65ec3c..2f5faf9aa 100644 --- a/flask__webservers/simple_upload_server/main.py +++ b/flask__webservers/simple_upload_server/main.py @@ -1,24 +1,26 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from flask import Flask, request + + app = Flask(__name__) -@app.route("/", methods=['POST']) -def index(): - file = request.files['file'] +@app.route("/", methods=["POST"]) +def index() -> str: + file = request.files["file"] # file.save(file.filename) # OR: - file.save('upload_file.txt') + file.save("upload_file.txt") - return 'ok' + return "ok" -if __name__ == '__main__': +if __name__ == "__main__": app.run( port=6000, ) diff --git a/flask__webservers/simple_upload_server/test.py b/flask__webservers/simple_upload_server/test.py index 0bacceb12..f4584925a 100644 --- a/flask__webservers/simple_upload_server/test.py +++ b/flask__webservers/simple_upload_server/test.py @@ -1,10 +1,12 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # Отправка файла на сервер import requests -rs = requests.post('http://localhost:6000', files={'file': open('disk_info.txt', 'rb')}) + + +rs = requests.post("http://localhost:6000", files={"file": open("disk_info.txt", "rb")}) print(rs) diff --git a/flask__webservers/upload_and_download_files/main.py b/flask__webservers/upload_and_download_files/main.py index bab1d66b9..f92fabaf2 100644 --- a/flask__webservers/upload_and_download_files/main.py +++ b/flask__webservers/upload_and_download_files/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import os @@ -10,41 +10,41 @@ from werkzeug.utils import secure_filename -UPLOAD_FOLDER = 'uploads' +UPLOAD_FOLDER = "uploads" UPLOAD_FOLDER = os.path.abspath(UPLOAD_FOLDER) app = Flask(__name__) -app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER +app.config["UPLOAD_FOLDER"] = UPLOAD_FOLDER os.makedirs(UPLOAD_FOLDER, exist_ok=True) -@app.route('/', methods=['GET', 'POST']) +@app.route("/", methods=["GET", "POST"]) def upload_file(): - if request.method == 'POST': + if request.method == "POST": # check if the post request has the file part - if 'file' not in request.files: - flash('No file part') + if "file" not in request.files: + flash("No file part") return redirect(request.url) - file = request.files['file'] + file = request.files["file"] # if user does not select file, browser also # submit a empty part without filename - if file.filename == '': - flash('No selected file') + if file.filename == "": + flash("No selected file") return redirect(request.url) if file: # Функция secure_filename не дружит с не ascii-символами, поэтому # файлы русскими словами не называть filename = secure_filename(file.filename) - file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename)) + file.save(os.path.join(app.config["UPLOAD_FOLDER"], filename)) # Функция url_for('uploaded_file', filename=filename) возвращает строку вида: /uploads/ - return redirect(url_for('uploaded_file', filename=filename)) + return redirect(url_for("uploaded_file", filename=filename)) - return ''' + return """ Upload new File

Upload new File

@@ -52,17 +52,17 @@ def upload_file():

- ''' + """ # Пример обработчика, возвращающий файлы из папки app.config['UPLOAD_FOLDER'] для путей uploads и files. # т.е. не нужно давать специальное название, чтобы получить файл в flask # @app.route('/uploads/') -@app.route('/files/') +@app.route("/files/") def uploaded_file(filename): - return send_from_directory(app.config['UPLOAD_FOLDER'], filename) + return send_from_directory(app.config["UPLOAD_FOLDER"], filename) -if __name__ == '__main__': +if __name__ == "__main__": # Localhost app.run() diff --git a/flask__webservers/upload_and_download_files/test_download_from_script.py b/flask__webservers/upload_and_download_files/test_download_from_script.py index 5529ad71e..c52b6b141 100644 --- a/flask__webservers/upload_and_download_files/test_download_from_script.py +++ b/flask__webservers/upload_and_download_files/test_download_from_script.py @@ -1,34 +1,37 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import os -import sys -sys.path.append('..') -from common import sizeof_fmt +# pip install humanize +from humanize import naturalsize as sizeof_fmt -def progress(count, block_size, total_size): +def progress(count, block_size, total_size) -> None: percent = count * block_size * 100.0 / total_size - print(f"Download: {sizeof_fmt(count * block_size)}/{sizeof_fmt(total_size)}({percent:.1f}%)" + ' ' * 20, end='\r') + print( + f"Download: {sizeof_fmt(count * block_size)}/{sizeof_fmt(total_size)}({percent:.1f}%)" + + " " * 20, + end="\r", + ) -def create_test_file(): - file_name = 'uploads/bigfile' +def create_test_file() -> None: + file_name = "uploads/bigfile" if os.path.exists(file_name): return # Создание больших файлов для теста - with open(file_name, 'wb') as f: + with open(file_name, "wb") as f: for i in range(1024 * 1024 * 600): - f.write(b'0') + f.write(b"0") -if __name__ == '__main__': +if __name__ == "__main__": create_test_file() from urllib.request import urlretrieve - urlretrieve('http://127.0.0.1:5000/files/bigfile', "file", reporthook=progress) + urlretrieve("http://127.0.0.1:5000/files/bigfile", "file", reporthook=progress) diff --git a/flask__webservers/url_shortener/config.py b/flask__webservers/url_shortener/config.py new file mode 100644 index 000000000..f23e91138 --- /dev/null +++ b/flask__webservers/url_shortener/config.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from pathlib import Path + + +# Текущая папка, где находится скрипт +DIR = Path(__file__).resolve().parent + +# Создание папки для базы данных +DB_DIR_NAME = DIR / "database" +DB_DIR_NAME.mkdir(parents=True, exist_ok=True) + +# Путь к файлу базы данных +DB_FILE_NAME = str(DB_DIR_NAME / "database.sqlite") + +# Максимальная длина идентификаторов ссылок +LENGTH_URL_ID: int = 5 diff --git a/flask__webservers/url_shortener/db.py b/flask__webservers/url_shortener/db.py new file mode 100644 index 000000000..2b8c3dc08 --- /dev/null +++ b/flask__webservers/url_shortener/db.py @@ -0,0 +1,162 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import time + +from typing import Type, Optional, Iterable, TypeVar +from uuid import uuid4 + +# pip install peewee +from peewee import Model, TextField, ForeignKeyField, CharField +from playhouse.sqliteq import SqliteQueueDatabase + +from config import DB_FILE_NAME, LENGTH_URL_ID +from shorten import shorten + + +def generate_link_id(length: int = LENGTH_URL_ID) -> str: + # TODO: Возможна ситуация, когда length будет больше размера UUID + return uuid4().hex[:length] + + +class NotDefinedParameterException(Exception): + def __init__(self, parameter_name: str) -> None: + self.parameter_name = parameter_name + text = f'Parameter "{self.parameter_name}" must be defined!' + + super().__init__(text) + + +# This working with multithreading +# SOURCE: http://docs.peewee-orm.com/en/latest/peewee/playhouse.html#sqliteq +db = SqliteQueueDatabase( + DB_FILE_NAME, + pragmas={ + "foreign_keys": 1, + "journal_mode": "wal", # WAL-mode + "cache_size": -1024 * 64, # 64MB page-cache + }, + use_gevent=False, # Use the standard library "threading" module. + autostart=True, + queue_max_size=64, # Max. # of pending writes that can accumulate. + results_timeout=5.0, # Max. time to wait for query to be executed. +) + + +ChildModel = TypeVar("ChildModel", bound="BaseModel") + + +class BaseModel(Model): + """ + Базовая модель классов-таблиц + """ + + class Meta: + database = db + + def get_new(self) -> ChildModel: + return type(self).get(self._pk_expr()) + + @classmethod + def get_first(cls) -> ChildModel: + return cls.select().first() + + @classmethod + def get_last(cls) -> ChildModel: + return cls.select().order_by(cls.id.desc()).first() + + @classmethod + def get_inherited_models(cls) -> list[Type["BaseModel"]]: + return sorted(cls.__subclasses__(), key=lambda x: x.__name__) + + @classmethod + def print_count_of_tables(cls) -> None: + items = [] + for sub_cls in cls.get_inherited_models(): + name = sub_cls.__name__ + count = sub_cls.select().count() + items.append(f"{name}: {count}") + + print(", ".join(items)) + + @classmethod + def count(cls, filters: Iterable = None) -> int: + query = cls.select() + if filters: + query = query.filter(*filters) + return query.count() + + def __str__(self) -> str: + fields = [] + for k, field in self._meta.fields.items(): + v = getattr(self, k) + + if isinstance(field, (TextField, CharField)): + if v: + v = repr(shorten(v)) + + elif isinstance(field, ForeignKeyField): + k = f"{k}_id" + if v: + v = v.id + + fields.append(f"{k}={v}") + + return self.__class__.__name__ + "(" + ", ".join(fields) + ")" + + +class Link(BaseModel): + link_id = TextField(primary_key=True) + link_url = TextField(unique=True) + + @classmethod + def get_by_link_url(cls, link_url: str) -> Optional["Link"]: + if not link_url or not link_url.strip(): + raise NotDefinedParameterException(parameter_name="link_url") + + return cls.get_or_none(cls.link_url == link_url) + + @classmethod + def get_by_link_id(cls, link_id: str) -> Optional["Link"]: + if not link_id or not link_id.strip(): + raise NotDefinedParameterException(parameter_name="link_id") + + return cls.get_or_none(cls.link_id == link_id) + + @classmethod + def add(cls, link_url: str) -> "Link": + obj = cls.get_by_link_url(link_url) + if not obj: + # Убеждаемся, что link_id будет уникальным + while True: # TODO: Возможна ситуация, когда свободные закончатся + link_id = generate_link_id() + if not cls.get_by_link_id(link_id): + break + + obj = cls.create( + link_id=link_id, + link_url=link_url, + ) + + return obj + + +db.connect() +db.create_tables(BaseModel.get_inherited_models()) + +# Задержка в 50мс, чтобы дать время на запуск SqliteQueueDatabase и создание таблиц +# Т.к. в SqliteQueueDatabase запросы на чтение выполняются сразу, а на запись попадают в очередь +time.sleep(0.050) + +if __name__ == "__main__": + BaseModel.print_count_of_tables() + # Link: 1 + print() + + link = Link.add(link_url="https://example.com") + assert Link.select().count() + assert link == link.get_by_link_url(link_url=link.link_url) + assert link == link.get_by_link_id(link_id=link.link_id) diff --git a/flask__webservers/url_shortener/main.py b/flask__webservers/url_shortener/main.py new file mode 100644 index 000000000..a048af41f --- /dev/null +++ b/flask__webservers/url_shortener/main.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from pathlib import Path +from urllib.parse import urlparse, urljoin + +from flask import Flask, request, render_template, redirect, abort, jsonify + +import db + + +app = Flask(__name__) + + +def server_url() -> str: + parse = urlparse(request.url) + return f"{parse.scheme}://{parse.netloc}/" + + +@app.route("/", defaults={"link_id": ""}) +@app.route("/") +def index(link_id: str): + if link_id: + link = db.Link.get_by_link_id(link_id) + if not link: + abort(404) + + url = link.link_url + return redirect(url) + + return render_template( + "index.html", + title=Path(__file__).parent.resolve().stem, + ) + + +@app.route("/add", methods=["POST"]) +def add(): + result = { + "url": "", + } + if "url" in request.form: + link_url = request.form["url"] + link = db.Link.add(link_url) + result["url"] = urljoin(server_url(), link.link_id) + + return jsonify(result) + + +if __name__ == "__main__": + # # Localhost + # app.run(port=5001) + + # Public IP + app.run(host="0.0.0.0") diff --git a/flask__webservers/url_shortener/static/bootstrap-4.4.1/bootstrap.bundle.js b/flask__webservers/url_shortener/static/bootstrap-4.4.1/bootstrap.bundle.js new file mode 100644 index 000000000..534452210 --- /dev/null +++ b/flask__webservers/url_shortener/static/bootstrap-4.4.1/bootstrap.bundle.js @@ -0,0 +1,7134 @@ +/*! + * Bootstrap v4.4.1 (https://getbootstrap.com/) + * Copyright 2011-2019 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('jquery')) : + typeof define === 'function' && define.amd ? define(['exports', 'jquery'], factory) : + (global = global || self, factory(global.bootstrap = {}, global.jQuery)); +}(this, (function (exports, $) { 'use strict'; + + $ = $ && $.hasOwnProperty('default') ? $['default'] : $; + + function _defineProperties(target, props) { + for (var i = 0; i < props.length; i++) { + var descriptor = props[i]; + descriptor.enumerable = descriptor.enumerable || false; + descriptor.configurable = true; + if ("value" in descriptor) descriptor.writable = true; + Object.defineProperty(target, descriptor.key, descriptor); + } + } + + function _createClass(Constructor, protoProps, staticProps) { + if (protoProps) _defineProperties(Constructor.prototype, protoProps); + if (staticProps) _defineProperties(Constructor, staticProps); + return Constructor; + } + + function _defineProperty(obj, key, value) { + if (key in obj) { + Object.defineProperty(obj, key, { + value: value, + enumerable: true, + configurable: true, + writable: true + }); + } else { + obj[key] = value; + } + + return obj; + } + + function ownKeys(object, enumerableOnly) { + var keys = Object.keys(object); + + if (Object.getOwnPropertySymbols) { + var symbols = Object.getOwnPropertySymbols(object); + if (enumerableOnly) symbols = symbols.filter(function (sym) { + return Object.getOwnPropertyDescriptor(object, sym).enumerable; + }); + keys.push.apply(keys, symbols); + } + + return keys; + } + + function _objectSpread2(target) { + for (var i = 1; i < arguments.length; i++) { + var source = arguments[i] != null ? arguments[i] : {}; + + if (i % 2) { + ownKeys(Object(source), true).forEach(function (key) { + _defineProperty(target, key, source[key]); + }); + } else if (Object.getOwnPropertyDescriptors) { + Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); + } else { + ownKeys(Object(source)).forEach(function (key) { + Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); + }); + } + } + + return target; + } + + function _inheritsLoose(subClass, superClass) { + subClass.prototype = Object.create(superClass.prototype); + subClass.prototype.constructor = subClass; + subClass.__proto__ = superClass; + } + + /** + * -------------------------------------------------------------------------- + * Bootstrap (v4.4.1): util.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * -------------------------------------------------------------------------- + */ + /** + * ------------------------------------------------------------------------ + * Private TransitionEnd Helpers + * ------------------------------------------------------------------------ + */ + + var TRANSITION_END = 'transitionend'; + var MAX_UID = 1000000; + var MILLISECONDS_MULTIPLIER = 1000; // Shoutout AngusCroll (https://goo.gl/pxwQGp) + + function toType(obj) { + return {}.toString.call(obj).match(/\s([a-z]+)/i)[1].toLowerCase(); + } + + function getSpecialTransitionEndEvent() { + return { + bindType: TRANSITION_END, + delegateType: TRANSITION_END, + handle: function handle(event) { + if ($(event.target).is(this)) { + return event.handleObj.handler.apply(this, arguments); // eslint-disable-line prefer-rest-params + } + + return undefined; // eslint-disable-line no-undefined + } + }; + } + + function transitionEndEmulator(duration) { + var _this = this; + + var called = false; + $(this).one(Util.TRANSITION_END, function () { + called = true; + }); + setTimeout(function () { + if (!called) { + Util.triggerTransitionEnd(_this); + } + }, duration); + return this; + } + + function setTransitionEndSupport() { + $.fn.emulateTransitionEnd = transitionEndEmulator; + $.event.special[Util.TRANSITION_END] = getSpecialTransitionEndEvent(); + } + /** + * -------------------------------------------------------------------------- + * Public Util Api + * -------------------------------------------------------------------------- + */ + + + var Util = { + TRANSITION_END: 'bsTransitionEnd', + getUID: function getUID(prefix) { + do { + // eslint-disable-next-line no-bitwise + prefix += ~~(Math.random() * MAX_UID); // "~~" acts like a faster Math.floor() here + } while (document.getElementById(prefix)); + + return prefix; + }, + getSelectorFromElement: function getSelectorFromElement(element) { + var selector = element.getAttribute('data-target'); + + if (!selector || selector === '#') { + var hrefAttr = element.getAttribute('href'); + selector = hrefAttr && hrefAttr !== '#' ? hrefAttr.trim() : ''; + } + + try { + return document.querySelector(selector) ? selector : null; + } catch (err) { + return null; + } + }, + getTransitionDurationFromElement: function getTransitionDurationFromElement(element) { + if (!element) { + return 0; + } // Get transition-duration of the element + + + var transitionDuration = $(element).css('transition-duration'); + var transitionDelay = $(element).css('transition-delay'); + var floatTransitionDuration = parseFloat(transitionDuration); + var floatTransitionDelay = parseFloat(transitionDelay); // Return 0 if element or transition duration is not found + + if (!floatTransitionDuration && !floatTransitionDelay) { + return 0; + } // If multiple durations are defined, take the first + + + transitionDuration = transitionDuration.split(',')[0]; + transitionDelay = transitionDelay.split(',')[0]; + return (parseFloat(transitionDuration) + parseFloat(transitionDelay)) * MILLISECONDS_MULTIPLIER; + }, + reflow: function reflow(element) { + return element.offsetHeight; + }, + triggerTransitionEnd: function triggerTransitionEnd(element) { + $(element).trigger(TRANSITION_END); + }, + // TODO: Remove in v5 + supportsTransitionEnd: function supportsTransitionEnd() { + return Boolean(TRANSITION_END); + }, + isElement: function isElement(obj) { + return (obj[0] || obj).nodeType; + }, + typeCheckConfig: function typeCheckConfig(componentName, config, configTypes) { + for (var property in configTypes) { + if (Object.prototype.hasOwnProperty.call(configTypes, property)) { + var expectedTypes = configTypes[property]; + var value = config[property]; + var valueType = value && Util.isElement(value) ? 'element' : toType(value); + + if (!new RegExp(expectedTypes).test(valueType)) { + throw new Error(componentName.toUpperCase() + ": " + ("Option \"" + property + "\" provided type \"" + valueType + "\" ") + ("but expected type \"" + expectedTypes + "\".")); + } + } + } + }, + findShadowRoot: function findShadowRoot(element) { + if (!document.documentElement.attachShadow) { + return null; + } // Can find the shadow root otherwise it'll return the document + + + if (typeof element.getRootNode === 'function') { + var root = element.getRootNode(); + return root instanceof ShadowRoot ? root : null; + } + + if (element instanceof ShadowRoot) { + return element; + } // when we don't find a shadow root + + + if (!element.parentNode) { + return null; + } + + return Util.findShadowRoot(element.parentNode); + }, + jQueryDetection: function jQueryDetection() { + if (typeof $ === 'undefined') { + throw new TypeError('Bootstrap\'s JavaScript requires jQuery. jQuery must be included before Bootstrap\'s JavaScript.'); + } + + var version = $.fn.jquery.split(' ')[0].split('.'); + var minMajor = 1; + var ltMajor = 2; + var minMinor = 9; + var minPatch = 1; + var maxMajor = 4; + + if (version[0] < ltMajor && version[1] < minMinor || version[0] === minMajor && version[1] === minMinor && version[2] < minPatch || version[0] >= maxMajor) { + throw new Error('Bootstrap\'s JavaScript requires at least jQuery v1.9.1 but less than v4.0.0'); + } + } + }; + Util.jQueryDetection(); + setTransitionEndSupport(); + + /** + * ------------------------------------------------------------------------ + * Constants + * ------------------------------------------------------------------------ + */ + + var NAME = 'alert'; + var VERSION = '4.4.1'; + var DATA_KEY = 'bs.alert'; + var EVENT_KEY = "." + DATA_KEY; + var DATA_API_KEY = '.data-api'; + var JQUERY_NO_CONFLICT = $.fn[NAME]; + var Selector = { + DISMISS: '[data-dismiss="alert"]' + }; + var Event = { + CLOSE: "close" + EVENT_KEY, + CLOSED: "closed" + EVENT_KEY, + CLICK_DATA_API: "click" + EVENT_KEY + DATA_API_KEY + }; + var ClassName = { + ALERT: 'alert', + FADE: 'fade', + SHOW: 'show' + }; + /** + * ------------------------------------------------------------------------ + * Class Definition + * ------------------------------------------------------------------------ + */ + + var Alert = + /*#__PURE__*/ + function () { + function Alert(element) { + this._element = element; + } // Getters + + + var _proto = Alert.prototype; + + // Public + _proto.close = function close(element) { + var rootElement = this._element; + + if (element) { + rootElement = this._getRootElement(element); + } + + var customEvent = this._triggerCloseEvent(rootElement); + + if (customEvent.isDefaultPrevented()) { + return; + } + + this._removeElement(rootElement); + }; + + _proto.dispose = function dispose() { + $.removeData(this._element, DATA_KEY); + this._element = null; + } // Private + ; + + _proto._getRootElement = function _getRootElement(element) { + var selector = Util.getSelectorFromElement(element); + var parent = false; + + if (selector) { + parent = document.querySelector(selector); + } + + if (!parent) { + parent = $(element).closest("." + ClassName.ALERT)[0]; + } + + return parent; + }; + + _proto._triggerCloseEvent = function _triggerCloseEvent(element) { + var closeEvent = $.Event(Event.CLOSE); + $(element).trigger(closeEvent); + return closeEvent; + }; + + _proto._removeElement = function _removeElement(element) { + var _this = this; + + $(element).removeClass(ClassName.SHOW); + + if (!$(element).hasClass(ClassName.FADE)) { + this._destroyElement(element); + + return; + } + + var transitionDuration = Util.getTransitionDurationFromElement(element); + $(element).one(Util.TRANSITION_END, function (event) { + return _this._destroyElement(element, event); + }).emulateTransitionEnd(transitionDuration); + }; + + _proto._destroyElement = function _destroyElement(element) { + $(element).detach().trigger(Event.CLOSED).remove(); + } // Static + ; + + Alert._jQueryInterface = function _jQueryInterface(config) { + return this.each(function () { + var $element = $(this); + var data = $element.data(DATA_KEY); + + if (!data) { + data = new Alert(this); + $element.data(DATA_KEY, data); + } + + if (config === 'close') { + data[config](this); + } + }); + }; + + Alert._handleDismiss = function _handleDismiss(alertInstance) { + return function (event) { + if (event) { + event.preventDefault(); + } + + alertInstance.close(this); + }; + }; + + _createClass(Alert, null, [{ + key: "VERSION", + get: function get() { + return VERSION; + } + }]); + + return Alert; + }(); + /** + * ------------------------------------------------------------------------ + * Data Api implementation + * ------------------------------------------------------------------------ + */ + + + $(document).on(Event.CLICK_DATA_API, Selector.DISMISS, Alert._handleDismiss(new Alert())); + /** + * ------------------------------------------------------------------------ + * jQuery + * ------------------------------------------------------------------------ + */ + + $.fn[NAME] = Alert._jQueryInterface; + $.fn[NAME].Constructor = Alert; + + $.fn[NAME].noConflict = function () { + $.fn[NAME] = JQUERY_NO_CONFLICT; + return Alert._jQueryInterface; + }; + + /** + * ------------------------------------------------------------------------ + * Constants + * ------------------------------------------------------------------------ + */ + + var NAME$1 = 'button'; + var VERSION$1 = '4.4.1'; + var DATA_KEY$1 = 'bs.button'; + var EVENT_KEY$1 = "." + DATA_KEY$1; + var DATA_API_KEY$1 = '.data-api'; + var JQUERY_NO_CONFLICT$1 = $.fn[NAME$1]; + var ClassName$1 = { + ACTIVE: 'active', + BUTTON: 'btn', + FOCUS: 'focus' + }; + var Selector$1 = { + DATA_TOGGLE_CARROT: '[data-toggle^="button"]', + DATA_TOGGLES: '[data-toggle="buttons"]', + DATA_TOGGLE: '[data-toggle="button"]', + DATA_TOGGLES_BUTTONS: '[data-toggle="buttons"] .btn', + INPUT: 'input:not([type="hidden"])', + ACTIVE: '.active', + BUTTON: '.btn' + }; + var Event$1 = { + CLICK_DATA_API: "click" + EVENT_KEY$1 + DATA_API_KEY$1, + FOCUS_BLUR_DATA_API: "focus" + EVENT_KEY$1 + DATA_API_KEY$1 + " " + ("blur" + EVENT_KEY$1 + DATA_API_KEY$1), + LOAD_DATA_API: "load" + EVENT_KEY$1 + DATA_API_KEY$1 + }; + /** + * ------------------------------------------------------------------------ + * Class Definition + * ------------------------------------------------------------------------ + */ + + var Button = + /*#__PURE__*/ + function () { + function Button(element) { + this._element = element; + } // Getters + + + var _proto = Button.prototype; + + // Public + _proto.toggle = function toggle() { + var triggerChangeEvent = true; + var addAriaPressed = true; + var rootElement = $(this._element).closest(Selector$1.DATA_TOGGLES)[0]; + + if (rootElement) { + var input = this._element.querySelector(Selector$1.INPUT); + + if (input) { + if (input.type === 'radio') { + if (input.checked && this._element.classList.contains(ClassName$1.ACTIVE)) { + triggerChangeEvent = false; + } else { + var activeElement = rootElement.querySelector(Selector$1.ACTIVE); + + if (activeElement) { + $(activeElement).removeClass(ClassName$1.ACTIVE); + } + } + } else if (input.type === 'checkbox') { + if (this._element.tagName === 'LABEL' && input.checked === this._element.classList.contains(ClassName$1.ACTIVE)) { + triggerChangeEvent = false; + } + } else { + // if it's not a radio button or checkbox don't add a pointless/invalid checked property to the input + triggerChangeEvent = false; + } + + if (triggerChangeEvent) { + input.checked = !this._element.classList.contains(ClassName$1.ACTIVE); + $(input).trigger('change'); + } + + input.focus(); + addAriaPressed = false; + } + } + + if (!(this._element.hasAttribute('disabled') || this._element.classList.contains('disabled'))) { + if (addAriaPressed) { + this._element.setAttribute('aria-pressed', !this._element.classList.contains(ClassName$1.ACTIVE)); + } + + if (triggerChangeEvent) { + $(this._element).toggleClass(ClassName$1.ACTIVE); + } + } + }; + + _proto.dispose = function dispose() { + $.removeData(this._element, DATA_KEY$1); + this._element = null; + } // Static + ; + + Button._jQueryInterface = function _jQueryInterface(config) { + return this.each(function () { + var data = $(this).data(DATA_KEY$1); + + if (!data) { + data = new Button(this); + $(this).data(DATA_KEY$1, data); + } + + if (config === 'toggle') { + data[config](); + } + }); + }; + + _createClass(Button, null, [{ + key: "VERSION", + get: function get() { + return VERSION$1; + } + }]); + + return Button; + }(); + /** + * ------------------------------------------------------------------------ + * Data Api implementation + * ------------------------------------------------------------------------ + */ + + + $(document).on(Event$1.CLICK_DATA_API, Selector$1.DATA_TOGGLE_CARROT, function (event) { + var button = event.target; + + if (!$(button).hasClass(ClassName$1.BUTTON)) { + button = $(button).closest(Selector$1.BUTTON)[0]; + } + + if (!button || button.hasAttribute('disabled') || button.classList.contains('disabled')) { + event.preventDefault(); // work around Firefox bug #1540995 + } else { + var inputBtn = button.querySelector(Selector$1.INPUT); + + if (inputBtn && (inputBtn.hasAttribute('disabled') || inputBtn.classList.contains('disabled'))) { + event.preventDefault(); // work around Firefox bug #1540995 + + return; + } + + Button._jQueryInterface.call($(button), 'toggle'); + } + }).on(Event$1.FOCUS_BLUR_DATA_API, Selector$1.DATA_TOGGLE_CARROT, function (event) { + var button = $(event.target).closest(Selector$1.BUTTON)[0]; + $(button).toggleClass(ClassName$1.FOCUS, /^focus(in)?$/.test(event.type)); + }); + $(window).on(Event$1.LOAD_DATA_API, function () { + // ensure correct active class is set to match the controls' actual values/states + // find all checkboxes/readio buttons inside data-toggle groups + var buttons = [].slice.call(document.querySelectorAll(Selector$1.DATA_TOGGLES_BUTTONS)); + + for (var i = 0, len = buttons.length; i < len; i++) { + var button = buttons[i]; + var input = button.querySelector(Selector$1.INPUT); + + if (input.checked || input.hasAttribute('checked')) { + button.classList.add(ClassName$1.ACTIVE); + } else { + button.classList.remove(ClassName$1.ACTIVE); + } + } // find all button toggles + + + buttons = [].slice.call(document.querySelectorAll(Selector$1.DATA_TOGGLE)); + + for (var _i = 0, _len = buttons.length; _i < _len; _i++) { + var _button = buttons[_i]; + + if (_button.getAttribute('aria-pressed') === 'true') { + _button.classList.add(ClassName$1.ACTIVE); + } else { + _button.classList.remove(ClassName$1.ACTIVE); + } + } + }); + /** + * ------------------------------------------------------------------------ + * jQuery + * ------------------------------------------------------------------------ + */ + + $.fn[NAME$1] = Button._jQueryInterface; + $.fn[NAME$1].Constructor = Button; + + $.fn[NAME$1].noConflict = function () { + $.fn[NAME$1] = JQUERY_NO_CONFLICT$1; + return Button._jQueryInterface; + }; + + /** + * ------------------------------------------------------------------------ + * Constants + * ------------------------------------------------------------------------ + */ + + var NAME$2 = 'carousel'; + var VERSION$2 = '4.4.1'; + var DATA_KEY$2 = 'bs.carousel'; + var EVENT_KEY$2 = "." + DATA_KEY$2; + var DATA_API_KEY$2 = '.data-api'; + var JQUERY_NO_CONFLICT$2 = $.fn[NAME$2]; + var ARROW_LEFT_KEYCODE = 37; // KeyboardEvent.which value for left arrow key + + var ARROW_RIGHT_KEYCODE = 39; // KeyboardEvent.which value for right arrow key + + var TOUCHEVENT_COMPAT_WAIT = 500; // Time for mouse compat events to fire after touch + + var SWIPE_THRESHOLD = 40; + var Default = { + interval: 5000, + keyboard: true, + slide: false, + pause: 'hover', + wrap: true, + touch: true + }; + var DefaultType = { + interval: '(number|boolean)', + keyboard: 'boolean', + slide: '(boolean|string)', + pause: '(string|boolean)', + wrap: 'boolean', + touch: 'boolean' + }; + var Direction = { + NEXT: 'next', + PREV: 'prev', + LEFT: 'left', + RIGHT: 'right' + }; + var Event$2 = { + SLIDE: "slide" + EVENT_KEY$2, + SLID: "slid" + EVENT_KEY$2, + KEYDOWN: "keydown" + EVENT_KEY$2, + MOUSEENTER: "mouseenter" + EVENT_KEY$2, + MOUSELEAVE: "mouseleave" + EVENT_KEY$2, + TOUCHSTART: "touchstart" + EVENT_KEY$2, + TOUCHMOVE: "touchmove" + EVENT_KEY$2, + TOUCHEND: "touchend" + EVENT_KEY$2, + POINTERDOWN: "pointerdown" + EVENT_KEY$2, + POINTERUP: "pointerup" + EVENT_KEY$2, + DRAG_START: "dragstart" + EVENT_KEY$2, + LOAD_DATA_API: "load" + EVENT_KEY$2 + DATA_API_KEY$2, + CLICK_DATA_API: "click" + EVENT_KEY$2 + DATA_API_KEY$2 + }; + var ClassName$2 = { + CAROUSEL: 'carousel', + ACTIVE: 'active', + SLIDE: 'slide', + RIGHT: 'carousel-item-right', + LEFT: 'carousel-item-left', + NEXT: 'carousel-item-next', + PREV: 'carousel-item-prev', + ITEM: 'carousel-item', + POINTER_EVENT: 'pointer-event' + }; + var Selector$2 = { + ACTIVE: '.active', + ACTIVE_ITEM: '.active.carousel-item', + ITEM: '.carousel-item', + ITEM_IMG: '.carousel-item img', + NEXT_PREV: '.carousel-item-next, .carousel-item-prev', + INDICATORS: '.carousel-indicators', + DATA_SLIDE: '[data-slide], [data-slide-to]', + DATA_RIDE: '[data-ride="carousel"]' + }; + var PointerType = { + TOUCH: 'touch', + PEN: 'pen' + }; + /** + * ------------------------------------------------------------------------ + * Class Definition + * ------------------------------------------------------------------------ + */ + + var Carousel = + /*#__PURE__*/ + function () { + function Carousel(element, config) { + this._items = null; + this._interval = null; + this._activeElement = null; + this._isPaused = false; + this._isSliding = false; + this.touchTimeout = null; + this.touchStartX = 0; + this.touchDeltaX = 0; + this._config = this._getConfig(config); + this._element = element; + this._indicatorsElement = this._element.querySelector(Selector$2.INDICATORS); + this._touchSupported = 'ontouchstart' in document.documentElement || navigator.maxTouchPoints > 0; + this._pointerEvent = Boolean(window.PointerEvent || window.MSPointerEvent); + + this._addEventListeners(); + } // Getters + + + var _proto = Carousel.prototype; + + // Public + _proto.next = function next() { + if (!this._isSliding) { + this._slide(Direction.NEXT); + } + }; + + _proto.nextWhenVisible = function nextWhenVisible() { + // Don't call next when the page isn't visible + // or the carousel or its parent isn't visible + if (!document.hidden && $(this._element).is(':visible') && $(this._element).css('visibility') !== 'hidden') { + this.next(); + } + }; + + _proto.prev = function prev() { + if (!this._isSliding) { + this._slide(Direction.PREV); + } + }; + + _proto.pause = function pause(event) { + if (!event) { + this._isPaused = true; + } + + if (this._element.querySelector(Selector$2.NEXT_PREV)) { + Util.triggerTransitionEnd(this._element); + this.cycle(true); + } + + clearInterval(this._interval); + this._interval = null; + }; + + _proto.cycle = function cycle(event) { + if (!event) { + this._isPaused = false; + } + + if (this._interval) { + clearInterval(this._interval); + this._interval = null; + } + + if (this._config.interval && !this._isPaused) { + this._interval = setInterval((document.visibilityState ? this.nextWhenVisible : this.next).bind(this), this._config.interval); + } + }; + + _proto.to = function to(index) { + var _this = this; + + this._activeElement = this._element.querySelector(Selector$2.ACTIVE_ITEM); + + var activeIndex = this._getItemIndex(this._activeElement); + + if (index > this._items.length - 1 || index < 0) { + return; + } + + if (this._isSliding) { + $(this._element).one(Event$2.SLID, function () { + return _this.to(index); + }); + return; + } + + if (activeIndex === index) { + this.pause(); + this.cycle(); + return; + } + + var direction = index > activeIndex ? Direction.NEXT : Direction.PREV; + + this._slide(direction, this._items[index]); + }; + + _proto.dispose = function dispose() { + $(this._element).off(EVENT_KEY$2); + $.removeData(this._element, DATA_KEY$2); + this._items = null; + this._config = null; + this._element = null; + this._interval = null; + this._isPaused = null; + this._isSliding = null; + this._activeElement = null; + this._indicatorsElement = null; + } // Private + ; + + _proto._getConfig = function _getConfig(config) { + config = _objectSpread2({}, Default, {}, config); + Util.typeCheckConfig(NAME$2, config, DefaultType); + return config; + }; + + _proto._handleSwipe = function _handleSwipe() { + var absDeltax = Math.abs(this.touchDeltaX); + + if (absDeltax <= SWIPE_THRESHOLD) { + return; + } + + var direction = absDeltax / this.touchDeltaX; + this.touchDeltaX = 0; // swipe left + + if (direction > 0) { + this.prev(); + } // swipe right + + + if (direction < 0) { + this.next(); + } + }; + + _proto._addEventListeners = function _addEventListeners() { + var _this2 = this; + + if (this._config.keyboard) { + $(this._element).on(Event$2.KEYDOWN, function (event) { + return _this2._keydown(event); + }); + } + + if (this._config.pause === 'hover') { + $(this._element).on(Event$2.MOUSEENTER, function (event) { + return _this2.pause(event); + }).on(Event$2.MOUSELEAVE, function (event) { + return _this2.cycle(event); + }); + } + + if (this._config.touch) { + this._addTouchEventListeners(); + } + }; + + _proto._addTouchEventListeners = function _addTouchEventListeners() { + var _this3 = this; + + if (!this._touchSupported) { + return; + } + + var start = function start(event) { + if (_this3._pointerEvent && PointerType[event.originalEvent.pointerType.toUpperCase()]) { + _this3.touchStartX = event.originalEvent.clientX; + } else if (!_this3._pointerEvent) { + _this3.touchStartX = event.originalEvent.touches[0].clientX; + } + }; + + var move = function move(event) { + // ensure swiping with one touch and not pinching + if (event.originalEvent.touches && event.originalEvent.touches.length > 1) { + _this3.touchDeltaX = 0; + } else { + _this3.touchDeltaX = event.originalEvent.touches[0].clientX - _this3.touchStartX; + } + }; + + var end = function end(event) { + if (_this3._pointerEvent && PointerType[event.originalEvent.pointerType.toUpperCase()]) { + _this3.touchDeltaX = event.originalEvent.clientX - _this3.touchStartX; + } + + _this3._handleSwipe(); + + if (_this3._config.pause === 'hover') { + // If it's a touch-enabled device, mouseenter/leave are fired as + // part of the mouse compatibility events on first tap - the carousel + // would stop cycling until user tapped out of it; + // here, we listen for touchend, explicitly pause the carousel + // (as if it's the second time we tap on it, mouseenter compat event + // is NOT fired) and after a timeout (to allow for mouse compatibility + // events to fire) we explicitly restart cycling + _this3.pause(); + + if (_this3.touchTimeout) { + clearTimeout(_this3.touchTimeout); + } + + _this3.touchTimeout = setTimeout(function (event) { + return _this3.cycle(event); + }, TOUCHEVENT_COMPAT_WAIT + _this3._config.interval); + } + }; + + $(this._element.querySelectorAll(Selector$2.ITEM_IMG)).on(Event$2.DRAG_START, function (e) { + return e.preventDefault(); + }); + + if (this._pointerEvent) { + $(this._element).on(Event$2.POINTERDOWN, function (event) { + return start(event); + }); + $(this._element).on(Event$2.POINTERUP, function (event) { + return end(event); + }); + + this._element.classList.add(ClassName$2.POINTER_EVENT); + } else { + $(this._element).on(Event$2.TOUCHSTART, function (event) { + return start(event); + }); + $(this._element).on(Event$2.TOUCHMOVE, function (event) { + return move(event); + }); + $(this._element).on(Event$2.TOUCHEND, function (event) { + return end(event); + }); + } + }; + + _proto._keydown = function _keydown(event) { + if (/input|textarea/i.test(event.target.tagName)) { + return; + } + + switch (event.which) { + case ARROW_LEFT_KEYCODE: + event.preventDefault(); + this.prev(); + break; + + case ARROW_RIGHT_KEYCODE: + event.preventDefault(); + this.next(); + break; + } + }; + + _proto._getItemIndex = function _getItemIndex(element) { + this._items = element && element.parentNode ? [].slice.call(element.parentNode.querySelectorAll(Selector$2.ITEM)) : []; + return this._items.indexOf(element); + }; + + _proto._getItemByDirection = function _getItemByDirection(direction, activeElement) { + var isNextDirection = direction === Direction.NEXT; + var isPrevDirection = direction === Direction.PREV; + + var activeIndex = this._getItemIndex(activeElement); + + var lastItemIndex = this._items.length - 1; + var isGoingToWrap = isPrevDirection && activeIndex === 0 || isNextDirection && activeIndex === lastItemIndex; + + if (isGoingToWrap && !this._config.wrap) { + return activeElement; + } + + var delta = direction === Direction.PREV ? -1 : 1; + var itemIndex = (activeIndex + delta) % this._items.length; + return itemIndex === -1 ? this._items[this._items.length - 1] : this._items[itemIndex]; + }; + + _proto._triggerSlideEvent = function _triggerSlideEvent(relatedTarget, eventDirectionName) { + var targetIndex = this._getItemIndex(relatedTarget); + + var fromIndex = this._getItemIndex(this._element.querySelector(Selector$2.ACTIVE_ITEM)); + + var slideEvent = $.Event(Event$2.SLIDE, { + relatedTarget: relatedTarget, + direction: eventDirectionName, + from: fromIndex, + to: targetIndex + }); + $(this._element).trigger(slideEvent); + return slideEvent; + }; + + _proto._setActiveIndicatorElement = function _setActiveIndicatorElement(element) { + if (this._indicatorsElement) { + var indicators = [].slice.call(this._indicatorsElement.querySelectorAll(Selector$2.ACTIVE)); + $(indicators).removeClass(ClassName$2.ACTIVE); + + var nextIndicator = this._indicatorsElement.children[this._getItemIndex(element)]; + + if (nextIndicator) { + $(nextIndicator).addClass(ClassName$2.ACTIVE); + } + } + }; + + _proto._slide = function _slide(direction, element) { + var _this4 = this; + + var activeElement = this._element.querySelector(Selector$2.ACTIVE_ITEM); + + var activeElementIndex = this._getItemIndex(activeElement); + + var nextElement = element || activeElement && this._getItemByDirection(direction, activeElement); + + var nextElementIndex = this._getItemIndex(nextElement); + + var isCycling = Boolean(this._interval); + var directionalClassName; + var orderClassName; + var eventDirectionName; + + if (direction === Direction.NEXT) { + directionalClassName = ClassName$2.LEFT; + orderClassName = ClassName$2.NEXT; + eventDirectionName = Direction.LEFT; + } else { + directionalClassName = ClassName$2.RIGHT; + orderClassName = ClassName$2.PREV; + eventDirectionName = Direction.RIGHT; + } + + if (nextElement && $(nextElement).hasClass(ClassName$2.ACTIVE)) { + this._isSliding = false; + return; + } + + var slideEvent = this._triggerSlideEvent(nextElement, eventDirectionName); + + if (slideEvent.isDefaultPrevented()) { + return; + } + + if (!activeElement || !nextElement) { + // Some weirdness is happening, so we bail + return; + } + + this._isSliding = true; + + if (isCycling) { + this.pause(); + } + + this._setActiveIndicatorElement(nextElement); + + var slidEvent = $.Event(Event$2.SLID, { + relatedTarget: nextElement, + direction: eventDirectionName, + from: activeElementIndex, + to: nextElementIndex + }); + + if ($(this._element).hasClass(ClassName$2.SLIDE)) { + $(nextElement).addClass(orderClassName); + Util.reflow(nextElement); + $(activeElement).addClass(directionalClassName); + $(nextElement).addClass(directionalClassName); + var nextElementInterval = parseInt(nextElement.getAttribute('data-interval'), 10); + + if (nextElementInterval) { + this._config.defaultInterval = this._config.defaultInterval || this._config.interval; + this._config.interval = nextElementInterval; + } else { + this._config.interval = this._config.defaultInterval || this._config.interval; + } + + var transitionDuration = Util.getTransitionDurationFromElement(activeElement); + $(activeElement).one(Util.TRANSITION_END, function () { + $(nextElement).removeClass(directionalClassName + " " + orderClassName).addClass(ClassName$2.ACTIVE); + $(activeElement).removeClass(ClassName$2.ACTIVE + " " + orderClassName + " " + directionalClassName); + _this4._isSliding = false; + setTimeout(function () { + return $(_this4._element).trigger(slidEvent); + }, 0); + }).emulateTransitionEnd(transitionDuration); + } else { + $(activeElement).removeClass(ClassName$2.ACTIVE); + $(nextElement).addClass(ClassName$2.ACTIVE); + this._isSliding = false; + $(this._element).trigger(slidEvent); + } + + if (isCycling) { + this.cycle(); + } + } // Static + ; + + Carousel._jQueryInterface = function _jQueryInterface(config) { + return this.each(function () { + var data = $(this).data(DATA_KEY$2); + + var _config = _objectSpread2({}, Default, {}, $(this).data()); + + if (typeof config === 'object') { + _config = _objectSpread2({}, _config, {}, config); + } + + var action = typeof config === 'string' ? config : _config.slide; + + if (!data) { + data = new Carousel(this, _config); + $(this).data(DATA_KEY$2, data); + } + + if (typeof config === 'number') { + data.to(config); + } else if (typeof action === 'string') { + if (typeof data[action] === 'undefined') { + throw new TypeError("No method named \"" + action + "\""); + } + + data[action](); + } else if (_config.interval && _config.ride) { + data.pause(); + data.cycle(); + } + }); + }; + + Carousel._dataApiClickHandler = function _dataApiClickHandler(event) { + var selector = Util.getSelectorFromElement(this); + + if (!selector) { + return; + } + + var target = $(selector)[0]; + + if (!target || !$(target).hasClass(ClassName$2.CAROUSEL)) { + return; + } + + var config = _objectSpread2({}, $(target).data(), {}, $(this).data()); + + var slideIndex = this.getAttribute('data-slide-to'); + + if (slideIndex) { + config.interval = false; + } + + Carousel._jQueryInterface.call($(target), config); + + if (slideIndex) { + $(target).data(DATA_KEY$2).to(slideIndex); + } + + event.preventDefault(); + }; + + _createClass(Carousel, null, [{ + key: "VERSION", + get: function get() { + return VERSION$2; + } + }, { + key: "Default", + get: function get() { + return Default; + } + }]); + + return Carousel; + }(); + /** + * ------------------------------------------------------------------------ + * Data Api implementation + * ------------------------------------------------------------------------ + */ + + + $(document).on(Event$2.CLICK_DATA_API, Selector$2.DATA_SLIDE, Carousel._dataApiClickHandler); + $(window).on(Event$2.LOAD_DATA_API, function () { + var carousels = [].slice.call(document.querySelectorAll(Selector$2.DATA_RIDE)); + + for (var i = 0, len = carousels.length; i < len; i++) { + var $carousel = $(carousels[i]); + + Carousel._jQueryInterface.call($carousel, $carousel.data()); + } + }); + /** + * ------------------------------------------------------------------------ + * jQuery + * ------------------------------------------------------------------------ + */ + + $.fn[NAME$2] = Carousel._jQueryInterface; + $.fn[NAME$2].Constructor = Carousel; + + $.fn[NAME$2].noConflict = function () { + $.fn[NAME$2] = JQUERY_NO_CONFLICT$2; + return Carousel._jQueryInterface; + }; + + /** + * ------------------------------------------------------------------------ + * Constants + * ------------------------------------------------------------------------ + */ + + var NAME$3 = 'collapse'; + var VERSION$3 = '4.4.1'; + var DATA_KEY$3 = 'bs.collapse'; + var EVENT_KEY$3 = "." + DATA_KEY$3; + var DATA_API_KEY$3 = '.data-api'; + var JQUERY_NO_CONFLICT$3 = $.fn[NAME$3]; + var Default$1 = { + toggle: true, + parent: '' + }; + var DefaultType$1 = { + toggle: 'boolean', + parent: '(string|element)' + }; + var Event$3 = { + SHOW: "show" + EVENT_KEY$3, + SHOWN: "shown" + EVENT_KEY$3, + HIDE: "hide" + EVENT_KEY$3, + HIDDEN: "hidden" + EVENT_KEY$3, + CLICK_DATA_API: "click" + EVENT_KEY$3 + DATA_API_KEY$3 + }; + var ClassName$3 = { + SHOW: 'show', + COLLAPSE: 'collapse', + COLLAPSING: 'collapsing', + COLLAPSED: 'collapsed' + }; + var Dimension = { + WIDTH: 'width', + HEIGHT: 'height' + }; + var Selector$3 = { + ACTIVES: '.show, .collapsing', + DATA_TOGGLE: '[data-toggle="collapse"]' + }; + /** + * ------------------------------------------------------------------------ + * Class Definition + * ------------------------------------------------------------------------ + */ + + var Collapse = + /*#__PURE__*/ + function () { + function Collapse(element, config) { + this._isTransitioning = false; + this._element = element; + this._config = this._getConfig(config); + this._triggerArray = [].slice.call(document.querySelectorAll("[data-toggle=\"collapse\"][href=\"#" + element.id + "\"]," + ("[data-toggle=\"collapse\"][data-target=\"#" + element.id + "\"]"))); + var toggleList = [].slice.call(document.querySelectorAll(Selector$3.DATA_TOGGLE)); + + for (var i = 0, len = toggleList.length; i < len; i++) { + var elem = toggleList[i]; + var selector = Util.getSelectorFromElement(elem); + var filterElement = [].slice.call(document.querySelectorAll(selector)).filter(function (foundElem) { + return foundElem === element; + }); + + if (selector !== null && filterElement.length > 0) { + this._selector = selector; + + this._triggerArray.push(elem); + } + } + + this._parent = this._config.parent ? this._getParent() : null; + + if (!this._config.parent) { + this._addAriaAndCollapsedClass(this._element, this._triggerArray); + } + + if (this._config.toggle) { + this.toggle(); + } + } // Getters + + + var _proto = Collapse.prototype; + + // Public + _proto.toggle = function toggle() { + if ($(this._element).hasClass(ClassName$3.SHOW)) { + this.hide(); + } else { + this.show(); + } + }; + + _proto.show = function show() { + var _this = this; + + if (this._isTransitioning || $(this._element).hasClass(ClassName$3.SHOW)) { + return; + } + + var actives; + var activesData; + + if (this._parent) { + actives = [].slice.call(this._parent.querySelectorAll(Selector$3.ACTIVES)).filter(function (elem) { + if (typeof _this._config.parent === 'string') { + return elem.getAttribute('data-parent') === _this._config.parent; + } + + return elem.classList.contains(ClassName$3.COLLAPSE); + }); + + if (actives.length === 0) { + actives = null; + } + } + + if (actives) { + activesData = $(actives).not(this._selector).data(DATA_KEY$3); + + if (activesData && activesData._isTransitioning) { + return; + } + } + + var startEvent = $.Event(Event$3.SHOW); + $(this._element).trigger(startEvent); + + if (startEvent.isDefaultPrevented()) { + return; + } + + if (actives) { + Collapse._jQueryInterface.call($(actives).not(this._selector), 'hide'); + + if (!activesData) { + $(actives).data(DATA_KEY$3, null); + } + } + + var dimension = this._getDimension(); + + $(this._element).removeClass(ClassName$3.COLLAPSE).addClass(ClassName$3.COLLAPSING); + this._element.style[dimension] = 0; + + if (this._triggerArray.length) { + $(this._triggerArray).removeClass(ClassName$3.COLLAPSED).attr('aria-expanded', true); + } + + this.setTransitioning(true); + + var complete = function complete() { + $(_this._element).removeClass(ClassName$3.COLLAPSING).addClass(ClassName$3.COLLAPSE).addClass(ClassName$3.SHOW); + _this._element.style[dimension] = ''; + + _this.setTransitioning(false); + + $(_this._element).trigger(Event$3.SHOWN); + }; + + var capitalizedDimension = dimension[0].toUpperCase() + dimension.slice(1); + var scrollSize = "scroll" + capitalizedDimension; + var transitionDuration = Util.getTransitionDurationFromElement(this._element); + $(this._element).one(Util.TRANSITION_END, complete).emulateTransitionEnd(transitionDuration); + this._element.style[dimension] = this._element[scrollSize] + "px"; + }; + + _proto.hide = function hide() { + var _this2 = this; + + if (this._isTransitioning || !$(this._element).hasClass(ClassName$3.SHOW)) { + return; + } + + var startEvent = $.Event(Event$3.HIDE); + $(this._element).trigger(startEvent); + + if (startEvent.isDefaultPrevented()) { + return; + } + + var dimension = this._getDimension(); + + this._element.style[dimension] = this._element.getBoundingClientRect()[dimension] + "px"; + Util.reflow(this._element); + $(this._element).addClass(ClassName$3.COLLAPSING).removeClass(ClassName$3.COLLAPSE).removeClass(ClassName$3.SHOW); + var triggerArrayLength = this._triggerArray.length; + + if (triggerArrayLength > 0) { + for (var i = 0; i < triggerArrayLength; i++) { + var trigger = this._triggerArray[i]; + var selector = Util.getSelectorFromElement(trigger); + + if (selector !== null) { + var $elem = $([].slice.call(document.querySelectorAll(selector))); + + if (!$elem.hasClass(ClassName$3.SHOW)) { + $(trigger).addClass(ClassName$3.COLLAPSED).attr('aria-expanded', false); + } + } + } + } + + this.setTransitioning(true); + + var complete = function complete() { + _this2.setTransitioning(false); + + $(_this2._element).removeClass(ClassName$3.COLLAPSING).addClass(ClassName$3.COLLAPSE).trigger(Event$3.HIDDEN); + }; + + this._element.style[dimension] = ''; + var transitionDuration = Util.getTransitionDurationFromElement(this._element); + $(this._element).one(Util.TRANSITION_END, complete).emulateTransitionEnd(transitionDuration); + }; + + _proto.setTransitioning = function setTransitioning(isTransitioning) { + this._isTransitioning = isTransitioning; + }; + + _proto.dispose = function dispose() { + $.removeData(this._element, DATA_KEY$3); + this._config = null; + this._parent = null; + this._element = null; + this._triggerArray = null; + this._isTransitioning = null; + } // Private + ; + + _proto._getConfig = function _getConfig(config) { + config = _objectSpread2({}, Default$1, {}, config); + config.toggle = Boolean(config.toggle); // Coerce string values + + Util.typeCheckConfig(NAME$3, config, DefaultType$1); + return config; + }; + + _proto._getDimension = function _getDimension() { + var hasWidth = $(this._element).hasClass(Dimension.WIDTH); + return hasWidth ? Dimension.WIDTH : Dimension.HEIGHT; + }; + + _proto._getParent = function _getParent() { + var _this3 = this; + + var parent; + + if (Util.isElement(this._config.parent)) { + parent = this._config.parent; // It's a jQuery object + + if (typeof this._config.parent.jquery !== 'undefined') { + parent = this._config.parent[0]; + } + } else { + parent = document.querySelector(this._config.parent); + } + + var selector = "[data-toggle=\"collapse\"][data-parent=\"" + this._config.parent + "\"]"; + var children = [].slice.call(parent.querySelectorAll(selector)); + $(children).each(function (i, element) { + _this3._addAriaAndCollapsedClass(Collapse._getTargetFromElement(element), [element]); + }); + return parent; + }; + + _proto._addAriaAndCollapsedClass = function _addAriaAndCollapsedClass(element, triggerArray) { + var isOpen = $(element).hasClass(ClassName$3.SHOW); + + if (triggerArray.length) { + $(triggerArray).toggleClass(ClassName$3.COLLAPSED, !isOpen).attr('aria-expanded', isOpen); + } + } // Static + ; + + Collapse._getTargetFromElement = function _getTargetFromElement(element) { + var selector = Util.getSelectorFromElement(element); + return selector ? document.querySelector(selector) : null; + }; + + Collapse._jQueryInterface = function _jQueryInterface(config) { + return this.each(function () { + var $this = $(this); + var data = $this.data(DATA_KEY$3); + + var _config = _objectSpread2({}, Default$1, {}, $this.data(), {}, typeof config === 'object' && config ? config : {}); + + if (!data && _config.toggle && /show|hide/.test(config)) { + _config.toggle = false; + } + + if (!data) { + data = new Collapse(this, _config); + $this.data(DATA_KEY$3, data); + } + + if (typeof config === 'string') { + if (typeof data[config] === 'undefined') { + throw new TypeError("No method named \"" + config + "\""); + } + + data[config](); + } + }); + }; + + _createClass(Collapse, null, [{ + key: "VERSION", + get: function get() { + return VERSION$3; + } + }, { + key: "Default", + get: function get() { + return Default$1; + } + }]); + + return Collapse; + }(); + /** + * ------------------------------------------------------------------------ + * Data Api implementation + * ------------------------------------------------------------------------ + */ + + + $(document).on(Event$3.CLICK_DATA_API, Selector$3.DATA_TOGGLE, function (event) { + // preventDefault only for elements (which change the URL) not inside the collapsible element + if (event.currentTarget.tagName === 'A') { + event.preventDefault(); + } + + var $trigger = $(this); + var selector = Util.getSelectorFromElement(this); + var selectors = [].slice.call(document.querySelectorAll(selector)); + $(selectors).each(function () { + var $target = $(this); + var data = $target.data(DATA_KEY$3); + var config = data ? 'toggle' : $trigger.data(); + + Collapse._jQueryInterface.call($target, config); + }); + }); + /** + * ------------------------------------------------------------------------ + * jQuery + * ------------------------------------------------------------------------ + */ + + $.fn[NAME$3] = Collapse._jQueryInterface; + $.fn[NAME$3].Constructor = Collapse; + + $.fn[NAME$3].noConflict = function () { + $.fn[NAME$3] = JQUERY_NO_CONFLICT$3; + return Collapse._jQueryInterface; + }; + + /**! + * @fileOverview Kickass library to create and place poppers near their reference elements. + * @version 1.16.0 + * @license + * Copyright (c) 2016 Federico Zivolo and contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + var isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined' && typeof navigator !== 'undefined'; + + var timeoutDuration = function () { + var longerTimeoutBrowsers = ['Edge', 'Trident', 'Firefox']; + for (var i = 0; i < longerTimeoutBrowsers.length; i += 1) { + if (isBrowser && navigator.userAgent.indexOf(longerTimeoutBrowsers[i]) >= 0) { + return 1; + } + } + return 0; + }(); + + function microtaskDebounce(fn) { + var called = false; + return function () { + if (called) { + return; + } + called = true; + window.Promise.resolve().then(function () { + called = false; + fn(); + }); + }; + } + + function taskDebounce(fn) { + var scheduled = false; + return function () { + if (!scheduled) { + scheduled = true; + setTimeout(function () { + scheduled = false; + fn(); + }, timeoutDuration); + } + }; + } + + var supportsMicroTasks = isBrowser && window.Promise; + + /** + * Create a debounced version of a method, that's asynchronously deferred + * but called in the minimum time possible. + * + * @method + * @memberof Popper.Utils + * @argument {Function} fn + * @returns {Function} + */ + var debounce = supportsMicroTasks ? microtaskDebounce : taskDebounce; + + /** + * Check if the given variable is a function + * @method + * @memberof Popper.Utils + * @argument {Any} functionToCheck - variable to check + * @returns {Boolean} answer to: is a function? + */ + function isFunction(functionToCheck) { + var getType = {}; + return functionToCheck && getType.toString.call(functionToCheck) === '[object Function]'; + } + + /** + * Get CSS computed property of the given element + * @method + * @memberof Popper.Utils + * @argument {Eement} element + * @argument {String} property + */ + function getStyleComputedProperty(element, property) { + if (element.nodeType !== 1) { + return []; + } + // NOTE: 1 DOM access here + var window = element.ownerDocument.defaultView; + var css = window.getComputedStyle(element, null); + return property ? css[property] : css; + } + + /** + * Returns the parentNode or the host of the element + * @method + * @memberof Popper.Utils + * @argument {Element} element + * @returns {Element} parent + */ + function getParentNode(element) { + if (element.nodeName === 'HTML') { + return element; + } + return element.parentNode || element.host; + } + + /** + * Returns the scrolling parent of the given element + * @method + * @memberof Popper.Utils + * @argument {Element} element + * @returns {Element} scroll parent + */ + function getScrollParent(element) { + // Return body, `getScroll` will take care to get the correct `scrollTop` from it + if (!element) { + return document.body; + } + + switch (element.nodeName) { + case 'HTML': + case 'BODY': + return element.ownerDocument.body; + case '#document': + return element.body; + } + + // Firefox want us to check `-x` and `-y` variations as well + + var _getStyleComputedProp = getStyleComputedProperty(element), + overflow = _getStyleComputedProp.overflow, + overflowX = _getStyleComputedProp.overflowX, + overflowY = _getStyleComputedProp.overflowY; + + if (/(auto|scroll|overlay)/.test(overflow + overflowY + overflowX)) { + return element; + } + + return getScrollParent(getParentNode(element)); + } + + /** + * Returns the reference node of the reference object, or the reference object itself. + * @method + * @memberof Popper.Utils + * @param {Element|Object} reference - the reference element (the popper will be relative to this) + * @returns {Element} parent + */ + function getReferenceNode(reference) { + return reference && reference.referenceNode ? reference.referenceNode : reference; + } + + var isIE11 = isBrowser && !!(window.MSInputMethodContext && document.documentMode); + var isIE10 = isBrowser && /MSIE 10/.test(navigator.userAgent); + + /** + * Determines if the browser is Internet Explorer + * @method + * @memberof Popper.Utils + * @param {Number} version to check + * @returns {Boolean} isIE + */ + function isIE(version) { + if (version === 11) { + return isIE11; + } + if (version === 10) { + return isIE10; + } + return isIE11 || isIE10; + } + + /** + * Returns the offset parent of the given element + * @method + * @memberof Popper.Utils + * @argument {Element} element + * @returns {Element} offset parent + */ + function getOffsetParent(element) { + if (!element) { + return document.documentElement; + } + + var noOffsetParent = isIE(10) ? document.body : null; + + // NOTE: 1 DOM access here + var offsetParent = element.offsetParent || null; + // Skip hidden elements which don't have an offsetParent + while (offsetParent === noOffsetParent && element.nextElementSibling) { + offsetParent = (element = element.nextElementSibling).offsetParent; + } + + var nodeName = offsetParent && offsetParent.nodeName; + + if (!nodeName || nodeName === 'BODY' || nodeName === 'HTML') { + return element ? element.ownerDocument.documentElement : document.documentElement; + } + + // .offsetParent will return the closest TH, TD or TABLE in case + // no offsetParent is present, I hate this job... + if (['TH', 'TD', 'TABLE'].indexOf(offsetParent.nodeName) !== -1 && getStyleComputedProperty(offsetParent, 'position') === 'static') { + return getOffsetParent(offsetParent); + } + + return offsetParent; + } + + function isOffsetContainer(element) { + var nodeName = element.nodeName; + + if (nodeName === 'BODY') { + return false; + } + return nodeName === 'HTML' || getOffsetParent(element.firstElementChild) === element; + } + + /** + * Finds the root node (document, shadowDOM root) of the given element + * @method + * @memberof Popper.Utils + * @argument {Element} node + * @returns {Element} root node + */ + function getRoot(node) { + if (node.parentNode !== null) { + return getRoot(node.parentNode); + } + + return node; + } + + /** + * Finds the offset parent common to the two provided nodes + * @method + * @memberof Popper.Utils + * @argument {Element} element1 + * @argument {Element} element2 + * @returns {Element} common offset parent + */ + function findCommonOffsetParent(element1, element2) { + // This check is needed to avoid errors in case one of the elements isn't defined for any reason + if (!element1 || !element1.nodeType || !element2 || !element2.nodeType) { + return document.documentElement; + } + + // Here we make sure to give as "start" the element that comes first in the DOM + var order = element1.compareDocumentPosition(element2) & Node.DOCUMENT_POSITION_FOLLOWING; + var start = order ? element1 : element2; + var end = order ? element2 : element1; + + // Get common ancestor container + var range = document.createRange(); + range.setStart(start, 0); + range.setEnd(end, 0); + var commonAncestorContainer = range.commonAncestorContainer; + + // Both nodes are inside #document + + if (element1 !== commonAncestorContainer && element2 !== commonAncestorContainer || start.contains(end)) { + if (isOffsetContainer(commonAncestorContainer)) { + return commonAncestorContainer; + } + + return getOffsetParent(commonAncestorContainer); + } + + // one of the nodes is inside shadowDOM, find which one + var element1root = getRoot(element1); + if (element1root.host) { + return findCommonOffsetParent(element1root.host, element2); + } else { + return findCommonOffsetParent(element1, getRoot(element2).host); + } + } + + /** + * Gets the scroll value of the given element in the given side (top and left) + * @method + * @memberof Popper.Utils + * @argument {Element} element + * @argument {String} side `top` or `left` + * @returns {number} amount of scrolled pixels + */ + function getScroll(element) { + var side = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'top'; + + var upperSide = side === 'top' ? 'scrollTop' : 'scrollLeft'; + var nodeName = element.nodeName; + + if (nodeName === 'BODY' || nodeName === 'HTML') { + var html = element.ownerDocument.documentElement; + var scrollingElement = element.ownerDocument.scrollingElement || html; + return scrollingElement[upperSide]; + } + + return element[upperSide]; + } + + /* + * Sum or subtract the element scroll values (left and top) from a given rect object + * @method + * @memberof Popper.Utils + * @param {Object} rect - Rect object you want to change + * @param {HTMLElement} element - The element from the function reads the scroll values + * @param {Boolean} subtract - set to true if you want to subtract the scroll values + * @return {Object} rect - The modifier rect object + */ + function includeScroll(rect, element) { + var subtract = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; + + var scrollTop = getScroll(element, 'top'); + var scrollLeft = getScroll(element, 'left'); + var modifier = subtract ? -1 : 1; + rect.top += scrollTop * modifier; + rect.bottom += scrollTop * modifier; + rect.left += scrollLeft * modifier; + rect.right += scrollLeft * modifier; + return rect; + } + + /* + * Helper to detect borders of a given element + * @method + * @memberof Popper.Utils + * @param {CSSStyleDeclaration} styles + * Result of `getStyleComputedProperty` on the given element + * @param {String} axis - `x` or `y` + * @return {number} borders - The borders size of the given axis + */ + + function getBordersSize(styles, axis) { + var sideA = axis === 'x' ? 'Left' : 'Top'; + var sideB = sideA === 'Left' ? 'Right' : 'Bottom'; + + return parseFloat(styles['border' + sideA + 'Width'], 10) + parseFloat(styles['border' + sideB + 'Width'], 10); + } + + function getSize(axis, body, html, computedStyle) { + return Math.max(body['offset' + axis], body['scroll' + axis], html['client' + axis], html['offset' + axis], html['scroll' + axis], isIE(10) ? parseInt(html['offset' + axis]) + parseInt(computedStyle['margin' + (axis === 'Height' ? 'Top' : 'Left')]) + parseInt(computedStyle['margin' + (axis === 'Height' ? 'Bottom' : 'Right')]) : 0); + } + + function getWindowSizes(document) { + var body = document.body; + var html = document.documentElement; + var computedStyle = isIE(10) && getComputedStyle(html); + + return { + height: getSize('Height', body, html, computedStyle), + width: getSize('Width', body, html, computedStyle) + }; + } + + var classCallCheck = function (instance, Constructor) { + if (!(instance instanceof Constructor)) { + throw new TypeError("Cannot call a class as a function"); + } + }; + + var createClass = function () { + function defineProperties(target, props) { + for (var i = 0; i < props.length; i++) { + var descriptor = props[i]; + descriptor.enumerable = descriptor.enumerable || false; + descriptor.configurable = true; + if ("value" in descriptor) descriptor.writable = true; + Object.defineProperty(target, descriptor.key, descriptor); + } + } + + return function (Constructor, protoProps, staticProps) { + if (protoProps) defineProperties(Constructor.prototype, protoProps); + if (staticProps) defineProperties(Constructor, staticProps); + return Constructor; + }; + }(); + + + + + + var defineProperty = function (obj, key, value) { + if (key in obj) { + Object.defineProperty(obj, key, { + value: value, + enumerable: true, + configurable: true, + writable: true + }); + } else { + obj[key] = value; + } + + return obj; + }; + + var _extends = Object.assign || function (target) { + for (var i = 1; i < arguments.length; i++) { + var source = arguments[i]; + + for (var key in source) { + if (Object.prototype.hasOwnProperty.call(source, key)) { + target[key] = source[key]; + } + } + } + + return target; + }; + + /** + * Given element offsets, generate an output similar to getBoundingClientRect + * @method + * @memberof Popper.Utils + * @argument {Object} offsets + * @returns {Object} ClientRect like output + */ + function getClientRect(offsets) { + return _extends({}, offsets, { + right: offsets.left + offsets.width, + bottom: offsets.top + offsets.height + }); + } + + /** + * Get bounding client rect of given element + * @method + * @memberof Popper.Utils + * @param {HTMLElement} element + * @return {Object} client rect + */ + function getBoundingClientRect(element) { + var rect = {}; + + // IE10 10 FIX: Please, don't ask, the element isn't + // considered in DOM in some circumstances... + // This isn't reproducible in IE10 compatibility mode of IE11 + try { + if (isIE(10)) { + rect = element.getBoundingClientRect(); + var scrollTop = getScroll(element, 'top'); + var scrollLeft = getScroll(element, 'left'); + rect.top += scrollTop; + rect.left += scrollLeft; + rect.bottom += scrollTop; + rect.right += scrollLeft; + } else { + rect = element.getBoundingClientRect(); + } + } catch (e) {} + + var result = { + left: rect.left, + top: rect.top, + width: rect.right - rect.left, + height: rect.bottom - rect.top + }; + + // subtract scrollbar size from sizes + var sizes = element.nodeName === 'HTML' ? getWindowSizes(element.ownerDocument) : {}; + var width = sizes.width || element.clientWidth || result.width; + var height = sizes.height || element.clientHeight || result.height; + + var horizScrollbar = element.offsetWidth - width; + var vertScrollbar = element.offsetHeight - height; + + // if an hypothetical scrollbar is detected, we must be sure it's not a `border` + // we make this check conditional for performance reasons + if (horizScrollbar || vertScrollbar) { + var styles = getStyleComputedProperty(element); + horizScrollbar -= getBordersSize(styles, 'x'); + vertScrollbar -= getBordersSize(styles, 'y'); + + result.width -= horizScrollbar; + result.height -= vertScrollbar; + } + + return getClientRect(result); + } + + function getOffsetRectRelativeToArbitraryNode(children, parent) { + var fixedPosition = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; + + var isIE10 = isIE(10); + var isHTML = parent.nodeName === 'HTML'; + var childrenRect = getBoundingClientRect(children); + var parentRect = getBoundingClientRect(parent); + var scrollParent = getScrollParent(children); + + var styles = getStyleComputedProperty(parent); + var borderTopWidth = parseFloat(styles.borderTopWidth, 10); + var borderLeftWidth = parseFloat(styles.borderLeftWidth, 10); + + // In cases where the parent is fixed, we must ignore negative scroll in offset calc + if (fixedPosition && isHTML) { + parentRect.top = Math.max(parentRect.top, 0); + parentRect.left = Math.max(parentRect.left, 0); + } + var offsets = getClientRect({ + top: childrenRect.top - parentRect.top - borderTopWidth, + left: childrenRect.left - parentRect.left - borderLeftWidth, + width: childrenRect.width, + height: childrenRect.height + }); + offsets.marginTop = 0; + offsets.marginLeft = 0; + + // Subtract margins of documentElement in case it's being used as parent + // we do this only on HTML because it's the only element that behaves + // differently when margins are applied to it. The margins are included in + // the box of the documentElement, in the other cases not. + if (!isIE10 && isHTML) { + var marginTop = parseFloat(styles.marginTop, 10); + var marginLeft = parseFloat(styles.marginLeft, 10); + + offsets.top -= borderTopWidth - marginTop; + offsets.bottom -= borderTopWidth - marginTop; + offsets.left -= borderLeftWidth - marginLeft; + offsets.right -= borderLeftWidth - marginLeft; + + // Attach marginTop and marginLeft because in some circumstances we may need them + offsets.marginTop = marginTop; + offsets.marginLeft = marginLeft; + } + + if (isIE10 && !fixedPosition ? parent.contains(scrollParent) : parent === scrollParent && scrollParent.nodeName !== 'BODY') { + offsets = includeScroll(offsets, parent); + } + + return offsets; + } + + function getViewportOffsetRectRelativeToArtbitraryNode(element) { + var excludeScroll = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; + + var html = element.ownerDocument.documentElement; + var relativeOffset = getOffsetRectRelativeToArbitraryNode(element, html); + var width = Math.max(html.clientWidth, window.innerWidth || 0); + var height = Math.max(html.clientHeight, window.innerHeight || 0); + + var scrollTop = !excludeScroll ? getScroll(html) : 0; + var scrollLeft = !excludeScroll ? getScroll(html, 'left') : 0; + + var offset = { + top: scrollTop - relativeOffset.top + relativeOffset.marginTop, + left: scrollLeft - relativeOffset.left + relativeOffset.marginLeft, + width: width, + height: height + }; + + return getClientRect(offset); + } + + /** + * Check if the given element is fixed or is inside a fixed parent + * @method + * @memberof Popper.Utils + * @argument {Element} element + * @argument {Element} customContainer + * @returns {Boolean} answer to "isFixed?" + */ + function isFixed(element) { + var nodeName = element.nodeName; + if (nodeName === 'BODY' || nodeName === 'HTML') { + return false; + } + if (getStyleComputedProperty(element, 'position') === 'fixed') { + return true; + } + var parentNode = getParentNode(element); + if (!parentNode) { + return false; + } + return isFixed(parentNode); + } + + /** + * Finds the first parent of an element that has a transformed property defined + * @method + * @memberof Popper.Utils + * @argument {Element} element + * @returns {Element} first transformed parent or documentElement + */ + + function getFixedPositionOffsetParent(element) { + // This check is needed to avoid errors in case one of the elements isn't defined for any reason + if (!element || !element.parentElement || isIE()) { + return document.documentElement; + } + var el = element.parentElement; + while (el && getStyleComputedProperty(el, 'transform') === 'none') { + el = el.parentElement; + } + return el || document.documentElement; + } + + /** + * Computed the boundaries limits and return them + * @method + * @memberof Popper.Utils + * @param {HTMLElement} popper + * @param {HTMLElement} reference + * @param {number} padding + * @param {HTMLElement} boundariesElement - Element used to define the boundaries + * @param {Boolean} fixedPosition - Is in fixed position mode + * @returns {Object} Coordinates of the boundaries + */ + function getBoundaries(popper, reference, padding, boundariesElement) { + var fixedPosition = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false; + + // NOTE: 1 DOM access here + + var boundaries = { top: 0, left: 0 }; + var offsetParent = fixedPosition ? getFixedPositionOffsetParent(popper) : findCommonOffsetParent(popper, getReferenceNode(reference)); + + // Handle viewport case + if (boundariesElement === 'viewport') { + boundaries = getViewportOffsetRectRelativeToArtbitraryNode(offsetParent, fixedPosition); + } else { + // Handle other cases based on DOM element used as boundaries + var boundariesNode = void 0; + if (boundariesElement === 'scrollParent') { + boundariesNode = getScrollParent(getParentNode(reference)); + if (boundariesNode.nodeName === 'BODY') { + boundariesNode = popper.ownerDocument.documentElement; + } + } else if (boundariesElement === 'window') { + boundariesNode = popper.ownerDocument.documentElement; + } else { + boundariesNode = boundariesElement; + } + + var offsets = getOffsetRectRelativeToArbitraryNode(boundariesNode, offsetParent, fixedPosition); + + // In case of HTML, we need a different computation + if (boundariesNode.nodeName === 'HTML' && !isFixed(offsetParent)) { + var _getWindowSizes = getWindowSizes(popper.ownerDocument), + height = _getWindowSizes.height, + width = _getWindowSizes.width; + + boundaries.top += offsets.top - offsets.marginTop; + boundaries.bottom = height + offsets.top; + boundaries.left += offsets.left - offsets.marginLeft; + boundaries.right = width + offsets.left; + } else { + // for all the other DOM elements, this one is good + boundaries = offsets; + } + } + + // Add paddings + padding = padding || 0; + var isPaddingNumber = typeof padding === 'number'; + boundaries.left += isPaddingNumber ? padding : padding.left || 0; + boundaries.top += isPaddingNumber ? padding : padding.top || 0; + boundaries.right -= isPaddingNumber ? padding : padding.right || 0; + boundaries.bottom -= isPaddingNumber ? padding : padding.bottom || 0; + + return boundaries; + } + + function getArea(_ref) { + var width = _ref.width, + height = _ref.height; + + return width * height; + } + + /** + * Utility used to transform the `auto` placement to the placement with more + * available space. + * @method + * @memberof Popper.Utils + * @argument {Object} data - The data object generated by update method + * @argument {Object} options - Modifiers configuration and options + * @returns {Object} The data object, properly modified + */ + function computeAutoPlacement(placement, refRect, popper, reference, boundariesElement) { + var padding = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : 0; + + if (placement.indexOf('auto') === -1) { + return placement; + } + + var boundaries = getBoundaries(popper, reference, padding, boundariesElement); + + var rects = { + top: { + width: boundaries.width, + height: refRect.top - boundaries.top + }, + right: { + width: boundaries.right - refRect.right, + height: boundaries.height + }, + bottom: { + width: boundaries.width, + height: boundaries.bottom - refRect.bottom + }, + left: { + width: refRect.left - boundaries.left, + height: boundaries.height + } + }; + + var sortedAreas = Object.keys(rects).map(function (key) { + return _extends({ + key: key + }, rects[key], { + area: getArea(rects[key]) + }); + }).sort(function (a, b) { + return b.area - a.area; + }); + + var filteredAreas = sortedAreas.filter(function (_ref2) { + var width = _ref2.width, + height = _ref2.height; + return width >= popper.clientWidth && height >= popper.clientHeight; + }); + + var computedPlacement = filteredAreas.length > 0 ? filteredAreas[0].key : sortedAreas[0].key; + + var variation = placement.split('-')[1]; + + return computedPlacement + (variation ? '-' + variation : ''); + } + + /** + * Get offsets to the reference element + * @method + * @memberof Popper.Utils + * @param {Object} state + * @param {Element} popper - the popper element + * @param {Element} reference - the reference element (the popper will be relative to this) + * @param {Element} fixedPosition - is in fixed position mode + * @returns {Object} An object containing the offsets which will be applied to the popper + */ + function getReferenceOffsets(state, popper, reference) { + var fixedPosition = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null; + + var commonOffsetParent = fixedPosition ? getFixedPositionOffsetParent(popper) : findCommonOffsetParent(popper, getReferenceNode(reference)); + return getOffsetRectRelativeToArbitraryNode(reference, commonOffsetParent, fixedPosition); + } + + /** + * Get the outer sizes of the given element (offset size + margins) + * @method + * @memberof Popper.Utils + * @argument {Element} element + * @returns {Object} object containing width and height properties + */ + function getOuterSizes(element) { + var window = element.ownerDocument.defaultView; + var styles = window.getComputedStyle(element); + var x = parseFloat(styles.marginTop || 0) + parseFloat(styles.marginBottom || 0); + var y = parseFloat(styles.marginLeft || 0) + parseFloat(styles.marginRight || 0); + var result = { + width: element.offsetWidth + y, + height: element.offsetHeight + x + }; + return result; + } + + /** + * Get the opposite placement of the given one + * @method + * @memberof Popper.Utils + * @argument {String} placement + * @returns {String} flipped placement + */ + function getOppositePlacement(placement) { + var hash = { left: 'right', right: 'left', bottom: 'top', top: 'bottom' }; + return placement.replace(/left|right|bottom|top/g, function (matched) { + return hash[matched]; + }); + } + + /** + * Get offsets to the popper + * @method + * @memberof Popper.Utils + * @param {Object} position - CSS position the Popper will get applied + * @param {HTMLElement} popper - the popper element + * @param {Object} referenceOffsets - the reference offsets (the popper will be relative to this) + * @param {String} placement - one of the valid placement options + * @returns {Object} popperOffsets - An object containing the offsets which will be applied to the popper + */ + function getPopperOffsets(popper, referenceOffsets, placement) { + placement = placement.split('-')[0]; + + // Get popper node sizes + var popperRect = getOuterSizes(popper); + + // Add position, width and height to our offsets object + var popperOffsets = { + width: popperRect.width, + height: popperRect.height + }; + + // depending by the popper placement we have to compute its offsets slightly differently + var isHoriz = ['right', 'left'].indexOf(placement) !== -1; + var mainSide = isHoriz ? 'top' : 'left'; + var secondarySide = isHoriz ? 'left' : 'top'; + var measurement = isHoriz ? 'height' : 'width'; + var secondaryMeasurement = !isHoriz ? 'height' : 'width'; + + popperOffsets[mainSide] = referenceOffsets[mainSide] + referenceOffsets[measurement] / 2 - popperRect[measurement] / 2; + if (placement === secondarySide) { + popperOffsets[secondarySide] = referenceOffsets[secondarySide] - popperRect[secondaryMeasurement]; + } else { + popperOffsets[secondarySide] = referenceOffsets[getOppositePlacement(secondarySide)]; + } + + return popperOffsets; + } + + /** + * Mimics the `find` method of Array + * @method + * @memberof Popper.Utils + * @argument {Array} arr + * @argument prop + * @argument value + * @returns index or -1 + */ + function find(arr, check) { + // use native find if supported + if (Array.prototype.find) { + return arr.find(check); + } + + // use `filter` to obtain the same behavior of `find` + return arr.filter(check)[0]; + } + + /** + * Return the index of the matching object + * @method + * @memberof Popper.Utils + * @argument {Array} arr + * @argument prop + * @argument value + * @returns index or -1 + */ + function findIndex(arr, prop, value) { + // use native findIndex if supported + if (Array.prototype.findIndex) { + return arr.findIndex(function (cur) { + return cur[prop] === value; + }); + } + + // use `find` + `indexOf` if `findIndex` isn't supported + var match = find(arr, function (obj) { + return obj[prop] === value; + }); + return arr.indexOf(match); + } + + /** + * Loop trough the list of modifiers and run them in order, + * each of them will then edit the data object. + * @method + * @memberof Popper.Utils + * @param {dataObject} data + * @param {Array} modifiers + * @param {String} ends - Optional modifier name used as stopper + * @returns {dataObject} + */ + function runModifiers(modifiers, data, ends) { + var modifiersToRun = ends === undefined ? modifiers : modifiers.slice(0, findIndex(modifiers, 'name', ends)); + + modifiersToRun.forEach(function (modifier) { + if (modifier['function']) { + // eslint-disable-line dot-notation + console.warn('`modifier.function` is deprecated, use `modifier.fn`!'); + } + var fn = modifier['function'] || modifier.fn; // eslint-disable-line dot-notation + if (modifier.enabled && isFunction(fn)) { + // Add properties to offsets to make them a complete clientRect object + // we do this before each modifier to make sure the previous one doesn't + // mess with these values + data.offsets.popper = getClientRect(data.offsets.popper); + data.offsets.reference = getClientRect(data.offsets.reference); + + data = fn(data, modifier); + } + }); + + return data; + } + + /** + * Updates the position of the popper, computing the new offsets and applying + * the new style.
+ * Prefer `scheduleUpdate` over `update` because of performance reasons. + * @method + * @memberof Popper + */ + function update() { + // if popper is destroyed, don't perform any further update + if (this.state.isDestroyed) { + return; + } + + var data = { + instance: this, + styles: {}, + arrowStyles: {}, + attributes: {}, + flipped: false, + offsets: {} + }; + + // compute reference element offsets + data.offsets.reference = getReferenceOffsets(this.state, this.popper, this.reference, this.options.positionFixed); + + // compute auto placement, store placement inside the data object, + // modifiers will be able to edit `placement` if needed + // and refer to originalPlacement to know the original value + data.placement = computeAutoPlacement(this.options.placement, data.offsets.reference, this.popper, this.reference, this.options.modifiers.flip.boundariesElement, this.options.modifiers.flip.padding); + + // store the computed placement inside `originalPlacement` + data.originalPlacement = data.placement; + + data.positionFixed = this.options.positionFixed; + + // compute the popper offsets + data.offsets.popper = getPopperOffsets(this.popper, data.offsets.reference, data.placement); + + data.offsets.popper.position = this.options.positionFixed ? 'fixed' : 'absolute'; + + // run the modifiers + data = runModifiers(this.modifiers, data); + + // the first `update` will call `onCreate` callback + // the other ones will call `onUpdate` callback + if (!this.state.isCreated) { + this.state.isCreated = true; + this.options.onCreate(data); + } else { + this.options.onUpdate(data); + } + } + + /** + * Helper used to know if the given modifier is enabled. + * @method + * @memberof Popper.Utils + * @returns {Boolean} + */ + function isModifierEnabled(modifiers, modifierName) { + return modifiers.some(function (_ref) { + var name = _ref.name, + enabled = _ref.enabled; + return enabled && name === modifierName; + }); + } + + /** + * Get the prefixed supported property name + * @method + * @memberof Popper.Utils + * @argument {String} property (camelCase) + * @returns {String} prefixed property (camelCase or PascalCase, depending on the vendor prefix) + */ + function getSupportedPropertyName(property) { + var prefixes = [false, 'ms', 'Webkit', 'Moz', 'O']; + var upperProp = property.charAt(0).toUpperCase() + property.slice(1); + + for (var i = 0; i < prefixes.length; i++) { + var prefix = prefixes[i]; + var toCheck = prefix ? '' + prefix + upperProp : property; + if (typeof document.body.style[toCheck] !== 'undefined') { + return toCheck; + } + } + return null; + } + + /** + * Destroys the popper. + * @method + * @memberof Popper + */ + function destroy() { + this.state.isDestroyed = true; + + // touch DOM only if `applyStyle` modifier is enabled + if (isModifierEnabled(this.modifiers, 'applyStyle')) { + this.popper.removeAttribute('x-placement'); + this.popper.style.position = ''; + this.popper.style.top = ''; + this.popper.style.left = ''; + this.popper.style.right = ''; + this.popper.style.bottom = ''; + this.popper.style.willChange = ''; + this.popper.style[getSupportedPropertyName('transform')] = ''; + } + + this.disableEventListeners(); + + // remove the popper if user explicitly asked for the deletion on destroy + // do not use `remove` because IE11 doesn't support it + if (this.options.removeOnDestroy) { + this.popper.parentNode.removeChild(this.popper); + } + return this; + } + + /** + * Get the window associated with the element + * @argument {Element} element + * @returns {Window} + */ + function getWindow(element) { + var ownerDocument = element.ownerDocument; + return ownerDocument ? ownerDocument.defaultView : window; + } + + function attachToScrollParents(scrollParent, event, callback, scrollParents) { + var isBody = scrollParent.nodeName === 'BODY'; + var target = isBody ? scrollParent.ownerDocument.defaultView : scrollParent; + target.addEventListener(event, callback, { passive: true }); + + if (!isBody) { + attachToScrollParents(getScrollParent(target.parentNode), event, callback, scrollParents); + } + scrollParents.push(target); + } + + /** + * Setup needed event listeners used to update the popper position + * @method + * @memberof Popper.Utils + * @private + */ + function setupEventListeners(reference, options, state, updateBound) { + // Resize event listener on window + state.updateBound = updateBound; + getWindow(reference).addEventListener('resize', state.updateBound, { passive: true }); + + // Scroll event listener on scroll parents + var scrollElement = getScrollParent(reference); + attachToScrollParents(scrollElement, 'scroll', state.updateBound, state.scrollParents); + state.scrollElement = scrollElement; + state.eventsEnabled = true; + + return state; + } + + /** + * It will add resize/scroll events and start recalculating + * position of the popper element when they are triggered. + * @method + * @memberof Popper + */ + function enableEventListeners() { + if (!this.state.eventsEnabled) { + this.state = setupEventListeners(this.reference, this.options, this.state, this.scheduleUpdate); + } + } + + /** + * Remove event listeners used to update the popper position + * @method + * @memberof Popper.Utils + * @private + */ + function removeEventListeners(reference, state) { + // Remove resize event listener on window + getWindow(reference).removeEventListener('resize', state.updateBound); + + // Remove scroll event listener on scroll parents + state.scrollParents.forEach(function (target) { + target.removeEventListener('scroll', state.updateBound); + }); + + // Reset state + state.updateBound = null; + state.scrollParents = []; + state.scrollElement = null; + state.eventsEnabled = false; + return state; + } + + /** + * It will remove resize/scroll events and won't recalculate popper position + * when they are triggered. It also won't trigger `onUpdate` callback anymore, + * unless you call `update` method manually. + * @method + * @memberof Popper + */ + function disableEventListeners() { + if (this.state.eventsEnabled) { + cancelAnimationFrame(this.scheduleUpdate); + this.state = removeEventListeners(this.reference, this.state); + } + } + + /** + * Tells if a given input is a number + * @method + * @memberof Popper.Utils + * @param {*} input to check + * @return {Boolean} + */ + function isNumeric(n) { + return n !== '' && !isNaN(parseFloat(n)) && isFinite(n); + } + + /** + * Set the style to the given popper + * @method + * @memberof Popper.Utils + * @argument {Element} element - Element to apply the style to + * @argument {Object} styles + * Object with a list of properties and values which will be applied to the element + */ + function setStyles(element, styles) { + Object.keys(styles).forEach(function (prop) { + var unit = ''; + // add unit if the value is numeric and is one of the following + if (['width', 'height', 'top', 'right', 'bottom', 'left'].indexOf(prop) !== -1 && isNumeric(styles[prop])) { + unit = 'px'; + } + element.style[prop] = styles[prop] + unit; + }); + } + + /** + * Set the attributes to the given popper + * @method + * @memberof Popper.Utils + * @argument {Element} element - Element to apply the attributes to + * @argument {Object} styles + * Object with a list of properties and values which will be applied to the element + */ + function setAttributes(element, attributes) { + Object.keys(attributes).forEach(function (prop) { + var value = attributes[prop]; + if (value !== false) { + element.setAttribute(prop, attributes[prop]); + } else { + element.removeAttribute(prop); + } + }); + } + + /** + * @function + * @memberof Modifiers + * @argument {Object} data - The data object generated by `update` method + * @argument {Object} data.styles - List of style properties - values to apply to popper element + * @argument {Object} data.attributes - List of attribute properties - values to apply to popper element + * @argument {Object} options - Modifiers configuration and options + * @returns {Object} The same data object + */ + function applyStyle(data) { + // any property present in `data.styles` will be applied to the popper, + // in this way we can make the 3rd party modifiers add custom styles to it + // Be aware, modifiers could override the properties defined in the previous + // lines of this modifier! + setStyles(data.instance.popper, data.styles); + + // any property present in `data.attributes` will be applied to the popper, + // they will be set as HTML attributes of the element + setAttributes(data.instance.popper, data.attributes); + + // if arrowElement is defined and arrowStyles has some properties + if (data.arrowElement && Object.keys(data.arrowStyles).length) { + setStyles(data.arrowElement, data.arrowStyles); + } + + return data; + } + + /** + * Set the x-placement attribute before everything else because it could be used + * to add margins to the popper margins needs to be calculated to get the + * correct popper offsets. + * @method + * @memberof Popper.modifiers + * @param {HTMLElement} reference - The reference element used to position the popper + * @param {HTMLElement} popper - The HTML element used as popper + * @param {Object} options - Popper.js options + */ + function applyStyleOnLoad(reference, popper, options, modifierOptions, state) { + // compute reference element offsets + var referenceOffsets = getReferenceOffsets(state, popper, reference, options.positionFixed); + + // compute auto placement, store placement inside the data object, + // modifiers will be able to edit `placement` if needed + // and refer to originalPlacement to know the original value + var placement = computeAutoPlacement(options.placement, referenceOffsets, popper, reference, options.modifiers.flip.boundariesElement, options.modifiers.flip.padding); + + popper.setAttribute('x-placement', placement); + + // Apply `position` to popper before anything else because + // without the position applied we can't guarantee correct computations + setStyles(popper, { position: options.positionFixed ? 'fixed' : 'absolute' }); + + return options; + } + + /** + * @function + * @memberof Popper.Utils + * @argument {Object} data - The data object generated by `update` method + * @argument {Boolean} shouldRound - If the offsets should be rounded at all + * @returns {Object} The popper's position offsets rounded + * + * The tale of pixel-perfect positioning. It's still not 100% perfect, but as + * good as it can be within reason. + * Discussion here: https://github.com/FezVrasta/popper.js/pull/715 + * + * Low DPI screens cause a popper to be blurry if not using full pixels (Safari + * as well on High DPI screens). + * + * Firefox prefers no rounding for positioning and does not have blurriness on + * high DPI screens. + * + * Only horizontal placement and left/right values need to be considered. + */ + function getRoundedOffsets(data, shouldRound) { + var _data$offsets = data.offsets, + popper = _data$offsets.popper, + reference = _data$offsets.reference; + var round = Math.round, + floor = Math.floor; + + var noRound = function noRound(v) { + return v; + }; + + var referenceWidth = round(reference.width); + var popperWidth = round(popper.width); + + var isVertical = ['left', 'right'].indexOf(data.placement) !== -1; + var isVariation = data.placement.indexOf('-') !== -1; + var sameWidthParity = referenceWidth % 2 === popperWidth % 2; + var bothOddWidth = referenceWidth % 2 === 1 && popperWidth % 2 === 1; + + var horizontalToInteger = !shouldRound ? noRound : isVertical || isVariation || sameWidthParity ? round : floor; + var verticalToInteger = !shouldRound ? noRound : round; + + return { + left: horizontalToInteger(bothOddWidth && !isVariation && shouldRound ? popper.left - 1 : popper.left), + top: verticalToInteger(popper.top), + bottom: verticalToInteger(popper.bottom), + right: horizontalToInteger(popper.right) + }; + } + + var isFirefox = isBrowser && /Firefox/i.test(navigator.userAgent); + + /** + * @function + * @memberof Modifiers + * @argument {Object} data - The data object generated by `update` method + * @argument {Object} options - Modifiers configuration and options + * @returns {Object} The data object, properly modified + */ + function computeStyle(data, options) { + var x = options.x, + y = options.y; + var popper = data.offsets.popper; + + // Remove this legacy support in Popper.js v2 + + var legacyGpuAccelerationOption = find(data.instance.modifiers, function (modifier) { + return modifier.name === 'applyStyle'; + }).gpuAcceleration; + if (legacyGpuAccelerationOption !== undefined) { + console.warn('WARNING: `gpuAcceleration` option moved to `computeStyle` modifier and will not be supported in future versions of Popper.js!'); + } + var gpuAcceleration = legacyGpuAccelerationOption !== undefined ? legacyGpuAccelerationOption : options.gpuAcceleration; + + var offsetParent = getOffsetParent(data.instance.popper); + var offsetParentRect = getBoundingClientRect(offsetParent); + + // Styles + var styles = { + position: popper.position + }; + + var offsets = getRoundedOffsets(data, window.devicePixelRatio < 2 || !isFirefox); + + var sideA = x === 'bottom' ? 'top' : 'bottom'; + var sideB = y === 'right' ? 'left' : 'right'; + + // if gpuAcceleration is set to `true` and transform is supported, + // we use `translate3d` to apply the position to the popper we + // automatically use the supported prefixed version if needed + var prefixedProperty = getSupportedPropertyName('transform'); + + // now, let's make a step back and look at this code closely (wtf?) + // If the content of the popper grows once it's been positioned, it + // may happen that the popper gets misplaced because of the new content + // overflowing its reference element + // To avoid this problem, we provide two options (x and y), which allow + // the consumer to define the offset origin. + // If we position a popper on top of a reference element, we can set + // `x` to `top` to make the popper grow towards its top instead of + // its bottom. + var left = void 0, + top = void 0; + if (sideA === 'bottom') { + // when offsetParent is the positioning is relative to the bottom of the screen (excluding the scrollbar) + // and not the bottom of the html element + if (offsetParent.nodeName === 'HTML') { + top = -offsetParent.clientHeight + offsets.bottom; + } else { + top = -offsetParentRect.height + offsets.bottom; + } + } else { + top = offsets.top; + } + if (sideB === 'right') { + if (offsetParent.nodeName === 'HTML') { + left = -offsetParent.clientWidth + offsets.right; + } else { + left = -offsetParentRect.width + offsets.right; + } + } else { + left = offsets.left; + } + if (gpuAcceleration && prefixedProperty) { + styles[prefixedProperty] = 'translate3d(' + left + 'px, ' + top + 'px, 0)'; + styles[sideA] = 0; + styles[sideB] = 0; + styles.willChange = 'transform'; + } else { + // othwerise, we use the standard `top`, `left`, `bottom` and `right` properties + var invertTop = sideA === 'bottom' ? -1 : 1; + var invertLeft = sideB === 'right' ? -1 : 1; + styles[sideA] = top * invertTop; + styles[sideB] = left * invertLeft; + styles.willChange = sideA + ', ' + sideB; + } + + // Attributes + var attributes = { + 'x-placement': data.placement + }; + + // Update `data` attributes, styles and arrowStyles + data.attributes = _extends({}, attributes, data.attributes); + data.styles = _extends({}, styles, data.styles); + data.arrowStyles = _extends({}, data.offsets.arrow, data.arrowStyles); + + return data; + } + + /** + * Helper used to know if the given modifier depends from another one.
+ * It checks if the needed modifier is listed and enabled. + * @method + * @memberof Popper.Utils + * @param {Array} modifiers - list of modifiers + * @param {String} requestingName - name of requesting modifier + * @param {String} requestedName - name of requested modifier + * @returns {Boolean} + */ + function isModifierRequired(modifiers, requestingName, requestedName) { + var requesting = find(modifiers, function (_ref) { + var name = _ref.name; + return name === requestingName; + }); + + var isRequired = !!requesting && modifiers.some(function (modifier) { + return modifier.name === requestedName && modifier.enabled && modifier.order < requesting.order; + }); + + if (!isRequired) { + var _requesting = '`' + requestingName + '`'; + var requested = '`' + requestedName + '`'; + console.warn(requested + ' modifier is required by ' + _requesting + ' modifier in order to work, be sure to include it before ' + _requesting + '!'); + } + return isRequired; + } + + /** + * @function + * @memberof Modifiers + * @argument {Object} data - The data object generated by update method + * @argument {Object} options - Modifiers configuration and options + * @returns {Object} The data object, properly modified + */ + function arrow(data, options) { + var _data$offsets$arrow; + + // arrow depends on keepTogether in order to work + if (!isModifierRequired(data.instance.modifiers, 'arrow', 'keepTogether')) { + return data; + } + + var arrowElement = options.element; + + // if arrowElement is a string, suppose it's a CSS selector + if (typeof arrowElement === 'string') { + arrowElement = data.instance.popper.querySelector(arrowElement); + + // if arrowElement is not found, don't run the modifier + if (!arrowElement) { + return data; + } + } else { + // if the arrowElement isn't a query selector we must check that the + // provided DOM node is child of its popper node + if (!data.instance.popper.contains(arrowElement)) { + console.warn('WARNING: `arrow.element` must be child of its popper element!'); + return data; + } + } + + var placement = data.placement.split('-')[0]; + var _data$offsets = data.offsets, + popper = _data$offsets.popper, + reference = _data$offsets.reference; + + var isVertical = ['left', 'right'].indexOf(placement) !== -1; + + var len = isVertical ? 'height' : 'width'; + var sideCapitalized = isVertical ? 'Top' : 'Left'; + var side = sideCapitalized.toLowerCase(); + var altSide = isVertical ? 'left' : 'top'; + var opSide = isVertical ? 'bottom' : 'right'; + var arrowElementSize = getOuterSizes(arrowElement)[len]; + + // + // extends keepTogether behavior making sure the popper and its + // reference have enough pixels in conjunction + // + + // top/left side + if (reference[opSide] - arrowElementSize < popper[side]) { + data.offsets.popper[side] -= popper[side] - (reference[opSide] - arrowElementSize); + } + // bottom/right side + if (reference[side] + arrowElementSize > popper[opSide]) { + data.offsets.popper[side] += reference[side] + arrowElementSize - popper[opSide]; + } + data.offsets.popper = getClientRect(data.offsets.popper); + + // compute center of the popper + var center = reference[side] + reference[len] / 2 - arrowElementSize / 2; + + // Compute the sideValue using the updated popper offsets + // take popper margin in account because we don't have this info available + var css = getStyleComputedProperty(data.instance.popper); + var popperMarginSide = parseFloat(css['margin' + sideCapitalized], 10); + var popperBorderSide = parseFloat(css['border' + sideCapitalized + 'Width'], 10); + var sideValue = center - data.offsets.popper[side] - popperMarginSide - popperBorderSide; + + // prevent arrowElement from being placed not contiguously to its popper + sideValue = Math.max(Math.min(popper[len] - arrowElementSize, sideValue), 0); + + data.arrowElement = arrowElement; + data.offsets.arrow = (_data$offsets$arrow = {}, defineProperty(_data$offsets$arrow, side, Math.round(sideValue)), defineProperty(_data$offsets$arrow, altSide, ''), _data$offsets$arrow); + + return data; + } + + /** + * Get the opposite placement variation of the given one + * @method + * @memberof Popper.Utils + * @argument {String} placement variation + * @returns {String} flipped placement variation + */ + function getOppositeVariation(variation) { + if (variation === 'end') { + return 'start'; + } else if (variation === 'start') { + return 'end'; + } + return variation; + } + + /** + * List of accepted placements to use as values of the `placement` option.
+ * Valid placements are: + * - `auto` + * - `top` + * - `right` + * - `bottom` + * - `left` + * + * Each placement can have a variation from this list: + * - `-start` + * - `-end` + * + * Variations are interpreted easily if you think of them as the left to right + * written languages. Horizontally (`top` and `bottom`), `start` is left and `end` + * is right.
+ * Vertically (`left` and `right`), `start` is top and `end` is bottom. + * + * Some valid examples are: + * - `top-end` (on top of reference, right aligned) + * - `right-start` (on right of reference, top aligned) + * - `bottom` (on bottom, centered) + * - `auto-end` (on the side with more space available, alignment depends by placement) + * + * @static + * @type {Array} + * @enum {String} + * @readonly + * @method placements + * @memberof Popper + */ + var placements = ['auto-start', 'auto', 'auto-end', 'top-start', 'top', 'top-end', 'right-start', 'right', 'right-end', 'bottom-end', 'bottom', 'bottom-start', 'left-end', 'left', 'left-start']; + + // Get rid of `auto` `auto-start` and `auto-end` + var validPlacements = placements.slice(3); + + /** + * Given an initial placement, returns all the subsequent placements + * clockwise (or counter-clockwise). + * + * @method + * @memberof Popper.Utils + * @argument {String} placement - A valid placement (it accepts variations) + * @argument {Boolean} counter - Set to true to walk the placements counterclockwise + * @returns {Array} placements including their variations + */ + function clockwise(placement) { + var counter = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; + + var index = validPlacements.indexOf(placement); + var arr = validPlacements.slice(index + 1).concat(validPlacements.slice(0, index)); + return counter ? arr.reverse() : arr; + } + + var BEHAVIORS = { + FLIP: 'flip', + CLOCKWISE: 'clockwise', + COUNTERCLOCKWISE: 'counterclockwise' + }; + + /** + * @function + * @memberof Modifiers + * @argument {Object} data - The data object generated by update method + * @argument {Object} options - Modifiers configuration and options + * @returns {Object} The data object, properly modified + */ + function flip(data, options) { + // if `inner` modifier is enabled, we can't use the `flip` modifier + if (isModifierEnabled(data.instance.modifiers, 'inner')) { + return data; + } + + if (data.flipped && data.placement === data.originalPlacement) { + // seems like flip is trying to loop, probably there's not enough space on any of the flippable sides + return data; + } + + var boundaries = getBoundaries(data.instance.popper, data.instance.reference, options.padding, options.boundariesElement, data.positionFixed); + + var placement = data.placement.split('-')[0]; + var placementOpposite = getOppositePlacement(placement); + var variation = data.placement.split('-')[1] || ''; + + var flipOrder = []; + + switch (options.behavior) { + case BEHAVIORS.FLIP: + flipOrder = [placement, placementOpposite]; + break; + case BEHAVIORS.CLOCKWISE: + flipOrder = clockwise(placement); + break; + case BEHAVIORS.COUNTERCLOCKWISE: + flipOrder = clockwise(placement, true); + break; + default: + flipOrder = options.behavior; + } + + flipOrder.forEach(function (step, index) { + if (placement !== step || flipOrder.length === index + 1) { + return data; + } + + placement = data.placement.split('-')[0]; + placementOpposite = getOppositePlacement(placement); + + var popperOffsets = data.offsets.popper; + var refOffsets = data.offsets.reference; + + // using floor because the reference offsets may contain decimals we are not going to consider here + var floor = Math.floor; + var overlapsRef = placement === 'left' && floor(popperOffsets.right) > floor(refOffsets.left) || placement === 'right' && floor(popperOffsets.left) < floor(refOffsets.right) || placement === 'top' && floor(popperOffsets.bottom) > floor(refOffsets.top) || placement === 'bottom' && floor(popperOffsets.top) < floor(refOffsets.bottom); + + var overflowsLeft = floor(popperOffsets.left) < floor(boundaries.left); + var overflowsRight = floor(popperOffsets.right) > floor(boundaries.right); + var overflowsTop = floor(popperOffsets.top) < floor(boundaries.top); + var overflowsBottom = floor(popperOffsets.bottom) > floor(boundaries.bottom); + + var overflowsBoundaries = placement === 'left' && overflowsLeft || placement === 'right' && overflowsRight || placement === 'top' && overflowsTop || placement === 'bottom' && overflowsBottom; + + // flip the variation if required + var isVertical = ['top', 'bottom'].indexOf(placement) !== -1; + + // flips variation if reference element overflows boundaries + var flippedVariationByRef = !!options.flipVariations && (isVertical && variation === 'start' && overflowsLeft || isVertical && variation === 'end' && overflowsRight || !isVertical && variation === 'start' && overflowsTop || !isVertical && variation === 'end' && overflowsBottom); + + // flips variation if popper content overflows boundaries + var flippedVariationByContent = !!options.flipVariationsByContent && (isVertical && variation === 'start' && overflowsRight || isVertical && variation === 'end' && overflowsLeft || !isVertical && variation === 'start' && overflowsBottom || !isVertical && variation === 'end' && overflowsTop); + + var flippedVariation = flippedVariationByRef || flippedVariationByContent; + + if (overlapsRef || overflowsBoundaries || flippedVariation) { + // this boolean to detect any flip loop + data.flipped = true; + + if (overlapsRef || overflowsBoundaries) { + placement = flipOrder[index + 1]; + } + + if (flippedVariation) { + variation = getOppositeVariation(variation); + } + + data.placement = placement + (variation ? '-' + variation : ''); + + // this object contains `position`, we want to preserve it along with + // any additional property we may add in the future + data.offsets.popper = _extends({}, data.offsets.popper, getPopperOffsets(data.instance.popper, data.offsets.reference, data.placement)); + + data = runModifiers(data.instance.modifiers, data, 'flip'); + } + }); + return data; + } + + /** + * @function + * @memberof Modifiers + * @argument {Object} data - The data object generated by update method + * @argument {Object} options - Modifiers configuration and options + * @returns {Object} The data object, properly modified + */ + function keepTogether(data) { + var _data$offsets = data.offsets, + popper = _data$offsets.popper, + reference = _data$offsets.reference; + + var placement = data.placement.split('-')[0]; + var floor = Math.floor; + var isVertical = ['top', 'bottom'].indexOf(placement) !== -1; + var side = isVertical ? 'right' : 'bottom'; + var opSide = isVertical ? 'left' : 'top'; + var measurement = isVertical ? 'width' : 'height'; + + if (popper[side] < floor(reference[opSide])) { + data.offsets.popper[opSide] = floor(reference[opSide]) - popper[measurement]; + } + if (popper[opSide] > floor(reference[side])) { + data.offsets.popper[opSide] = floor(reference[side]); + } + + return data; + } + + /** + * Converts a string containing value + unit into a px value number + * @function + * @memberof {modifiers~offset} + * @private + * @argument {String} str - Value + unit string + * @argument {String} measurement - `height` or `width` + * @argument {Object} popperOffsets + * @argument {Object} referenceOffsets + * @returns {Number|String} + * Value in pixels, or original string if no values were extracted + */ + function toValue(str, measurement, popperOffsets, referenceOffsets) { + // separate value from unit + var split = str.match(/((?:\-|\+)?\d*\.?\d*)(.*)/); + var value = +split[1]; + var unit = split[2]; + + // If it's not a number it's an operator, I guess + if (!value) { + return str; + } + + if (unit.indexOf('%') === 0) { + var element = void 0; + switch (unit) { + case '%p': + element = popperOffsets; + break; + case '%': + case '%r': + default: + element = referenceOffsets; + } + + var rect = getClientRect(element); + return rect[measurement] / 100 * value; + } else if (unit === 'vh' || unit === 'vw') { + // if is a vh or vw, we calculate the size based on the viewport + var size = void 0; + if (unit === 'vh') { + size = Math.max(document.documentElement.clientHeight, window.innerHeight || 0); + } else { + size = Math.max(document.documentElement.clientWidth, window.innerWidth || 0); + } + return size / 100 * value; + } else { + // if is an explicit pixel unit, we get rid of the unit and keep the value + // if is an implicit unit, it's px, and we return just the value + return value; + } + } + + /** + * Parse an `offset` string to extrapolate `x` and `y` numeric offsets. + * @function + * @memberof {modifiers~offset} + * @private + * @argument {String} offset + * @argument {Object} popperOffsets + * @argument {Object} referenceOffsets + * @argument {String} basePlacement + * @returns {Array} a two cells array with x and y offsets in numbers + */ + function parseOffset(offset, popperOffsets, referenceOffsets, basePlacement) { + var offsets = [0, 0]; + + // Use height if placement is left or right and index is 0 otherwise use width + // in this way the first offset will use an axis and the second one + // will use the other one + var useHeight = ['right', 'left'].indexOf(basePlacement) !== -1; + + // Split the offset string to obtain a list of values and operands + // The regex addresses values with the plus or minus sign in front (+10, -20, etc) + var fragments = offset.split(/(\+|\-)/).map(function (frag) { + return frag.trim(); + }); + + // Detect if the offset string contains a pair of values or a single one + // they could be separated by comma or space + var divider = fragments.indexOf(find(fragments, function (frag) { + return frag.search(/,|\s/) !== -1; + })); + + if (fragments[divider] && fragments[divider].indexOf(',') === -1) { + console.warn('Offsets separated by white space(s) are deprecated, use a comma (,) instead.'); + } + + // If divider is found, we divide the list of values and operands to divide + // them by ofset X and Y. + var splitRegex = /\s*,\s*|\s+/; + var ops = divider !== -1 ? [fragments.slice(0, divider).concat([fragments[divider].split(splitRegex)[0]]), [fragments[divider].split(splitRegex)[1]].concat(fragments.slice(divider + 1))] : [fragments]; + + // Convert the values with units to absolute pixels to allow our computations + ops = ops.map(function (op, index) { + // Most of the units rely on the orientation of the popper + var measurement = (index === 1 ? !useHeight : useHeight) ? 'height' : 'width'; + var mergeWithPrevious = false; + return op + // This aggregates any `+` or `-` sign that aren't considered operators + // e.g.: 10 + +5 => [10, +, +5] + .reduce(function (a, b) { + if (a[a.length - 1] === '' && ['+', '-'].indexOf(b) !== -1) { + a[a.length - 1] = b; + mergeWithPrevious = true; + return a; + } else if (mergeWithPrevious) { + a[a.length - 1] += b; + mergeWithPrevious = false; + return a; + } else { + return a.concat(b); + } + }, []) + // Here we convert the string values into number values (in px) + .map(function (str) { + return toValue(str, measurement, popperOffsets, referenceOffsets); + }); + }); + + // Loop trough the offsets arrays and execute the operations + ops.forEach(function (op, index) { + op.forEach(function (frag, index2) { + if (isNumeric(frag)) { + offsets[index] += frag * (op[index2 - 1] === '-' ? -1 : 1); + } + }); + }); + return offsets; + } + + /** + * @function + * @memberof Modifiers + * @argument {Object} data - The data object generated by update method + * @argument {Object} options - Modifiers configuration and options + * @argument {Number|String} options.offset=0 + * The offset value as described in the modifier description + * @returns {Object} The data object, properly modified + */ + function offset(data, _ref) { + var offset = _ref.offset; + var placement = data.placement, + _data$offsets = data.offsets, + popper = _data$offsets.popper, + reference = _data$offsets.reference; + + var basePlacement = placement.split('-')[0]; + + var offsets = void 0; + if (isNumeric(+offset)) { + offsets = [+offset, 0]; + } else { + offsets = parseOffset(offset, popper, reference, basePlacement); + } + + if (basePlacement === 'left') { + popper.top += offsets[0]; + popper.left -= offsets[1]; + } else if (basePlacement === 'right') { + popper.top += offsets[0]; + popper.left += offsets[1]; + } else if (basePlacement === 'top') { + popper.left += offsets[0]; + popper.top -= offsets[1]; + } else if (basePlacement === 'bottom') { + popper.left += offsets[0]; + popper.top += offsets[1]; + } + + data.popper = popper; + return data; + } + + /** + * @function + * @memberof Modifiers + * @argument {Object} data - The data object generated by `update` method + * @argument {Object} options - Modifiers configuration and options + * @returns {Object} The data object, properly modified + */ + function preventOverflow(data, options) { + var boundariesElement = options.boundariesElement || getOffsetParent(data.instance.popper); + + // If offsetParent is the reference element, we really want to + // go one step up and use the next offsetParent as reference to + // avoid to make this modifier completely useless and look like broken + if (data.instance.reference === boundariesElement) { + boundariesElement = getOffsetParent(boundariesElement); + } + + // NOTE: DOM access here + // resets the popper's position so that the document size can be calculated excluding + // the size of the popper element itself + var transformProp = getSupportedPropertyName('transform'); + var popperStyles = data.instance.popper.style; // assignment to help minification + var top = popperStyles.top, + left = popperStyles.left, + transform = popperStyles[transformProp]; + + popperStyles.top = ''; + popperStyles.left = ''; + popperStyles[transformProp] = ''; + + var boundaries = getBoundaries(data.instance.popper, data.instance.reference, options.padding, boundariesElement, data.positionFixed); + + // NOTE: DOM access here + // restores the original style properties after the offsets have been computed + popperStyles.top = top; + popperStyles.left = left; + popperStyles[transformProp] = transform; + + options.boundaries = boundaries; + + var order = options.priority; + var popper = data.offsets.popper; + + var check = { + primary: function primary(placement) { + var value = popper[placement]; + if (popper[placement] < boundaries[placement] && !options.escapeWithReference) { + value = Math.max(popper[placement], boundaries[placement]); + } + return defineProperty({}, placement, value); + }, + secondary: function secondary(placement) { + var mainSide = placement === 'right' ? 'left' : 'top'; + var value = popper[mainSide]; + if (popper[placement] > boundaries[placement] && !options.escapeWithReference) { + value = Math.min(popper[mainSide], boundaries[placement] - (placement === 'right' ? popper.width : popper.height)); + } + return defineProperty({}, mainSide, value); + } + }; + + order.forEach(function (placement) { + var side = ['left', 'top'].indexOf(placement) !== -1 ? 'primary' : 'secondary'; + popper = _extends({}, popper, check[side](placement)); + }); + + data.offsets.popper = popper; + + return data; + } + + /** + * @function + * @memberof Modifiers + * @argument {Object} data - The data object generated by `update` method + * @argument {Object} options - Modifiers configuration and options + * @returns {Object} The data object, properly modified + */ + function shift(data) { + var placement = data.placement; + var basePlacement = placement.split('-')[0]; + var shiftvariation = placement.split('-')[1]; + + // if shift shiftvariation is specified, run the modifier + if (shiftvariation) { + var _data$offsets = data.offsets, + reference = _data$offsets.reference, + popper = _data$offsets.popper; + + var isVertical = ['bottom', 'top'].indexOf(basePlacement) !== -1; + var side = isVertical ? 'left' : 'top'; + var measurement = isVertical ? 'width' : 'height'; + + var shiftOffsets = { + start: defineProperty({}, side, reference[side]), + end: defineProperty({}, side, reference[side] + reference[measurement] - popper[measurement]) + }; + + data.offsets.popper = _extends({}, popper, shiftOffsets[shiftvariation]); + } + + return data; + } + + /** + * @function + * @memberof Modifiers + * @argument {Object} data - The data object generated by update method + * @argument {Object} options - Modifiers configuration and options + * @returns {Object} The data object, properly modified + */ + function hide(data) { + if (!isModifierRequired(data.instance.modifiers, 'hide', 'preventOverflow')) { + return data; + } + + var refRect = data.offsets.reference; + var bound = find(data.instance.modifiers, function (modifier) { + return modifier.name === 'preventOverflow'; + }).boundaries; + + if (refRect.bottom < bound.top || refRect.left > bound.right || refRect.top > bound.bottom || refRect.right < bound.left) { + // Avoid unnecessary DOM access if visibility hasn't changed + if (data.hide === true) { + return data; + } + + data.hide = true; + data.attributes['x-out-of-boundaries'] = ''; + } else { + // Avoid unnecessary DOM access if visibility hasn't changed + if (data.hide === false) { + return data; + } + + data.hide = false; + data.attributes['x-out-of-boundaries'] = false; + } + + return data; + } + + /** + * @function + * @memberof Modifiers + * @argument {Object} data - The data object generated by `update` method + * @argument {Object} options - Modifiers configuration and options + * @returns {Object} The data object, properly modified + */ + function inner(data) { + var placement = data.placement; + var basePlacement = placement.split('-')[0]; + var _data$offsets = data.offsets, + popper = _data$offsets.popper, + reference = _data$offsets.reference; + + var isHoriz = ['left', 'right'].indexOf(basePlacement) !== -1; + + var subtractLength = ['top', 'left'].indexOf(basePlacement) === -1; + + popper[isHoriz ? 'left' : 'top'] = reference[basePlacement] - (subtractLength ? popper[isHoriz ? 'width' : 'height'] : 0); + + data.placement = getOppositePlacement(placement); + data.offsets.popper = getClientRect(popper); + + return data; + } + + /** + * Modifier function, each modifier can have a function of this type assigned + * to its `fn` property.
+ * These functions will be called on each update, this means that you must + * make sure they are performant enough to avoid performance bottlenecks. + * + * @function ModifierFn + * @argument {dataObject} data - The data object generated by `update` method + * @argument {Object} options - Modifiers configuration and options + * @returns {dataObject} The data object, properly modified + */ + + /** + * Modifiers are plugins used to alter the behavior of your poppers.
+ * Popper.js uses a set of 9 modifiers to provide all the basic functionalities + * needed by the library. + * + * Usually you don't want to override the `order`, `fn` and `onLoad` props. + * All the other properties are configurations that could be tweaked. + * @namespace modifiers + */ + var modifiers = { + /** + * Modifier used to shift the popper on the start or end of its reference + * element.
+ * It will read the variation of the `placement` property.
+ * It can be one either `-end` or `-start`. + * @memberof modifiers + * @inner + */ + shift: { + /** @prop {number} order=100 - Index used to define the order of execution */ + order: 100, + /** @prop {Boolean} enabled=true - Whether the modifier is enabled or not */ + enabled: true, + /** @prop {ModifierFn} */ + fn: shift + }, + + /** + * The `offset` modifier can shift your popper on both its axis. + * + * It accepts the following units: + * - `px` or unit-less, interpreted as pixels + * - `%` or `%r`, percentage relative to the length of the reference element + * - `%p`, percentage relative to the length of the popper element + * - `vw`, CSS viewport width unit + * - `vh`, CSS viewport height unit + * + * For length is intended the main axis relative to the placement of the popper.
+ * This means that if the placement is `top` or `bottom`, the length will be the + * `width`. In case of `left` or `right`, it will be the `height`. + * + * You can provide a single value (as `Number` or `String`), or a pair of values + * as `String` divided by a comma or one (or more) white spaces.
+ * The latter is a deprecated method because it leads to confusion and will be + * removed in v2.
+ * Additionally, it accepts additions and subtractions between different units. + * Note that multiplications and divisions aren't supported. + * + * Valid examples are: + * ``` + * 10 + * '10%' + * '10, 10' + * '10%, 10' + * '10 + 10%' + * '10 - 5vh + 3%' + * '-10px + 5vh, 5px - 6%' + * ``` + * > **NB**: If you desire to apply offsets to your poppers in a way that may make them overlap + * > with their reference element, unfortunately, you will have to disable the `flip` modifier. + * > You can read more on this at this [issue](https://github.com/FezVrasta/popper.js/issues/373). + * + * @memberof modifiers + * @inner + */ + offset: { + /** @prop {number} order=200 - Index used to define the order of execution */ + order: 200, + /** @prop {Boolean} enabled=true - Whether the modifier is enabled or not */ + enabled: true, + /** @prop {ModifierFn} */ + fn: offset, + /** @prop {Number|String} offset=0 + * The offset value as described in the modifier description + */ + offset: 0 + }, + + /** + * Modifier used to prevent the popper from being positioned outside the boundary. + * + * A scenario exists where the reference itself is not within the boundaries.
+ * We can say it has "escaped the boundaries" — or just "escaped".
+ * In this case we need to decide whether the popper should either: + * + * - detach from the reference and remain "trapped" in the boundaries, or + * - if it should ignore the boundary and "escape with its reference" + * + * When `escapeWithReference` is set to`true` and reference is completely + * outside its boundaries, the popper will overflow (or completely leave) + * the boundaries in order to remain attached to the edge of the reference. + * + * @memberof modifiers + * @inner + */ + preventOverflow: { + /** @prop {number} order=300 - Index used to define the order of execution */ + order: 300, + /** @prop {Boolean} enabled=true - Whether the modifier is enabled or not */ + enabled: true, + /** @prop {ModifierFn} */ + fn: preventOverflow, + /** + * @prop {Array} [priority=['left','right','top','bottom']] + * Popper will try to prevent overflow following these priorities by default, + * then, it could overflow on the left and on top of the `boundariesElement` + */ + priority: ['left', 'right', 'top', 'bottom'], + /** + * @prop {number} padding=5 + * Amount of pixel used to define a minimum distance between the boundaries + * and the popper. This makes sure the popper always has a little padding + * between the edges of its container + */ + padding: 5, + /** + * @prop {String|HTMLElement} boundariesElement='scrollParent' + * Boundaries used by the modifier. Can be `scrollParent`, `window`, + * `viewport` or any DOM element. + */ + boundariesElement: 'scrollParent' + }, + + /** + * Modifier used to make sure the reference and its popper stay near each other + * without leaving any gap between the two. Especially useful when the arrow is + * enabled and you want to ensure that it points to its reference element. + * It cares only about the first axis. You can still have poppers with margin + * between the popper and its reference element. + * @memberof modifiers + * @inner + */ + keepTogether: { + /** @prop {number} order=400 - Index used to define the order of execution */ + order: 400, + /** @prop {Boolean} enabled=true - Whether the modifier is enabled or not */ + enabled: true, + /** @prop {ModifierFn} */ + fn: keepTogether + }, + + /** + * This modifier is used to move the `arrowElement` of the popper to make + * sure it is positioned between the reference element and its popper element. + * It will read the outer size of the `arrowElement` node to detect how many + * pixels of conjunction are needed. + * + * It has no effect if no `arrowElement` is provided. + * @memberof modifiers + * @inner + */ + arrow: { + /** @prop {number} order=500 - Index used to define the order of execution */ + order: 500, + /** @prop {Boolean} enabled=true - Whether the modifier is enabled or not */ + enabled: true, + /** @prop {ModifierFn} */ + fn: arrow, + /** @prop {String|HTMLElement} element='[x-arrow]' - Selector or node used as arrow */ + element: '[x-arrow]' + }, + + /** + * Modifier used to flip the popper's placement when it starts to overlap its + * reference element. + * + * Requires the `preventOverflow` modifier before it in order to work. + * + * **NOTE:** this modifier will interrupt the current update cycle and will + * restart it if it detects the need to flip the placement. + * @memberof modifiers + * @inner + */ + flip: { + /** @prop {number} order=600 - Index used to define the order of execution */ + order: 600, + /** @prop {Boolean} enabled=true - Whether the modifier is enabled or not */ + enabled: true, + /** @prop {ModifierFn} */ + fn: flip, + /** + * @prop {String|Array} behavior='flip' + * The behavior used to change the popper's placement. It can be one of + * `flip`, `clockwise`, `counterclockwise` or an array with a list of valid + * placements (with optional variations) + */ + behavior: 'flip', + /** + * @prop {number} padding=5 + * The popper will flip if it hits the edges of the `boundariesElement` + */ + padding: 5, + /** + * @prop {String|HTMLElement} boundariesElement='viewport' + * The element which will define the boundaries of the popper position. + * The popper will never be placed outside of the defined boundaries + * (except if `keepTogether` is enabled) + */ + boundariesElement: 'viewport', + /** + * @prop {Boolean} flipVariations=false + * The popper will switch placement variation between `-start` and `-end` when + * the reference element overlaps its boundaries. + * + * The original placement should have a set variation. + */ + flipVariations: false, + /** + * @prop {Boolean} flipVariationsByContent=false + * The popper will switch placement variation between `-start` and `-end` when + * the popper element overlaps its reference boundaries. + * + * The original placement should have a set variation. + */ + flipVariationsByContent: false + }, + + /** + * Modifier used to make the popper flow toward the inner of the reference element. + * By default, when this modifier is disabled, the popper will be placed outside + * the reference element. + * @memberof modifiers + * @inner + */ + inner: { + /** @prop {number} order=700 - Index used to define the order of execution */ + order: 700, + /** @prop {Boolean} enabled=false - Whether the modifier is enabled or not */ + enabled: false, + /** @prop {ModifierFn} */ + fn: inner + }, + + /** + * Modifier used to hide the popper when its reference element is outside of the + * popper boundaries. It will set a `x-out-of-boundaries` attribute which can + * be used to hide with a CSS selector the popper when its reference is + * out of boundaries. + * + * Requires the `preventOverflow` modifier before it in order to work. + * @memberof modifiers + * @inner + */ + hide: { + /** @prop {number} order=800 - Index used to define the order of execution */ + order: 800, + /** @prop {Boolean} enabled=true - Whether the modifier is enabled or not */ + enabled: true, + /** @prop {ModifierFn} */ + fn: hide + }, + + /** + * Computes the style that will be applied to the popper element to gets + * properly positioned. + * + * Note that this modifier will not touch the DOM, it just prepares the styles + * so that `applyStyle` modifier can apply it. This separation is useful + * in case you need to replace `applyStyle` with a custom implementation. + * + * This modifier has `850` as `order` value to maintain backward compatibility + * with previous versions of Popper.js. Expect the modifiers ordering method + * to change in future major versions of the library. + * + * @memberof modifiers + * @inner + */ + computeStyle: { + /** @prop {number} order=850 - Index used to define the order of execution */ + order: 850, + /** @prop {Boolean} enabled=true - Whether the modifier is enabled or not */ + enabled: true, + /** @prop {ModifierFn} */ + fn: computeStyle, + /** + * @prop {Boolean} gpuAcceleration=true + * If true, it uses the CSS 3D transformation to position the popper. + * Otherwise, it will use the `top` and `left` properties + */ + gpuAcceleration: true, + /** + * @prop {string} [x='bottom'] + * Where to anchor the X axis (`bottom` or `top`). AKA X offset origin. + * Change this if your popper should grow in a direction different from `bottom` + */ + x: 'bottom', + /** + * @prop {string} [x='left'] + * Where to anchor the Y axis (`left` or `right`). AKA Y offset origin. + * Change this if your popper should grow in a direction different from `right` + */ + y: 'right' + }, + + /** + * Applies the computed styles to the popper element. + * + * All the DOM manipulations are limited to this modifier. This is useful in case + * you want to integrate Popper.js inside a framework or view library and you + * want to delegate all the DOM manipulations to it. + * + * Note that if you disable this modifier, you must make sure the popper element + * has its position set to `absolute` before Popper.js can do its work! + * + * Just disable this modifier and define your own to achieve the desired effect. + * + * @memberof modifiers + * @inner + */ + applyStyle: { + /** @prop {number} order=900 - Index used to define the order of execution */ + order: 900, + /** @prop {Boolean} enabled=true - Whether the modifier is enabled or not */ + enabled: true, + /** @prop {ModifierFn} */ + fn: applyStyle, + /** @prop {Function} */ + onLoad: applyStyleOnLoad, + /** + * @deprecated since version 1.10.0, the property moved to `computeStyle` modifier + * @prop {Boolean} gpuAcceleration=true + * If true, it uses the CSS 3D transformation to position the popper. + * Otherwise, it will use the `top` and `left` properties + */ + gpuAcceleration: undefined + } + }; + + /** + * The `dataObject` is an object containing all the information used by Popper.js. + * This object is passed to modifiers and to the `onCreate` and `onUpdate` callbacks. + * @name dataObject + * @property {Object} data.instance The Popper.js instance + * @property {String} data.placement Placement applied to popper + * @property {String} data.originalPlacement Placement originally defined on init + * @property {Boolean} data.flipped True if popper has been flipped by flip modifier + * @property {Boolean} data.hide True if the reference element is out of boundaries, useful to know when to hide the popper + * @property {HTMLElement} data.arrowElement Node used as arrow by arrow modifier + * @property {Object} data.styles Any CSS property defined here will be applied to the popper. It expects the JavaScript nomenclature (eg. `marginBottom`) + * @property {Object} data.arrowStyles Any CSS property defined here will be applied to the popper arrow. It expects the JavaScript nomenclature (eg. `marginBottom`) + * @property {Object} data.boundaries Offsets of the popper boundaries + * @property {Object} data.offsets The measurements of popper, reference and arrow elements + * @property {Object} data.offsets.popper `top`, `left`, `width`, `height` values + * @property {Object} data.offsets.reference `top`, `left`, `width`, `height` values + * @property {Object} data.offsets.arrow] `top` and `left` offsets, only one of them will be different from 0 + */ + + /** + * Default options provided to Popper.js constructor.
+ * These can be overridden using the `options` argument of Popper.js.
+ * To override an option, simply pass an object with the same + * structure of the `options` object, as the 3rd argument. For example: + * ``` + * new Popper(ref, pop, { + * modifiers: { + * preventOverflow: { enabled: false } + * } + * }) + * ``` + * @type {Object} + * @static + * @memberof Popper + */ + var Defaults = { + /** + * Popper's placement. + * @prop {Popper.placements} placement='bottom' + */ + placement: 'bottom', + + /** + * Set this to true if you want popper to position it self in 'fixed' mode + * @prop {Boolean} positionFixed=false + */ + positionFixed: false, + + /** + * Whether events (resize, scroll) are initially enabled. + * @prop {Boolean} eventsEnabled=true + */ + eventsEnabled: true, + + /** + * Set to true if you want to automatically remove the popper when + * you call the `destroy` method. + * @prop {Boolean} removeOnDestroy=false + */ + removeOnDestroy: false, + + /** + * Callback called when the popper is created.
+ * By default, it is set to no-op.
+ * Access Popper.js instance with `data.instance`. + * @prop {onCreate} + */ + onCreate: function onCreate() {}, + + /** + * Callback called when the popper is updated. This callback is not called + * on the initialization/creation of the popper, but only on subsequent + * updates.
+ * By default, it is set to no-op.
+ * Access Popper.js instance with `data.instance`. + * @prop {onUpdate} + */ + onUpdate: function onUpdate() {}, + + /** + * List of modifiers used to modify the offsets before they are applied to the popper. + * They provide most of the functionalities of Popper.js. + * @prop {modifiers} + */ + modifiers: modifiers + }; + + /** + * @callback onCreate + * @param {dataObject} data + */ + + /** + * @callback onUpdate + * @param {dataObject} data + */ + + // Utils + // Methods + var Popper = function () { + /** + * Creates a new Popper.js instance. + * @class Popper + * @param {Element|referenceObject} reference - The reference element used to position the popper + * @param {Element} popper - The HTML / XML element used as the popper + * @param {Object} options - Your custom options to override the ones defined in [Defaults](#defaults) + * @return {Object} instance - The generated Popper.js instance + */ + function Popper(reference, popper) { + var _this = this; + + var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; + classCallCheck(this, Popper); + + this.scheduleUpdate = function () { + return requestAnimationFrame(_this.update); + }; + + // make update() debounced, so that it only runs at most once-per-tick + this.update = debounce(this.update.bind(this)); + + // with {} we create a new object with the options inside it + this.options = _extends({}, Popper.Defaults, options); + + // init state + this.state = { + isDestroyed: false, + isCreated: false, + scrollParents: [] + }; + + // get reference and popper elements (allow jQuery wrappers) + this.reference = reference && reference.jquery ? reference[0] : reference; + this.popper = popper && popper.jquery ? popper[0] : popper; + + // Deep merge modifiers options + this.options.modifiers = {}; + Object.keys(_extends({}, Popper.Defaults.modifiers, options.modifiers)).forEach(function (name) { + _this.options.modifiers[name] = _extends({}, Popper.Defaults.modifiers[name] || {}, options.modifiers ? options.modifiers[name] : {}); + }); + + // Refactoring modifiers' list (Object => Array) + this.modifiers = Object.keys(this.options.modifiers).map(function (name) { + return _extends({ + name: name + }, _this.options.modifiers[name]); + }) + // sort the modifiers by order + .sort(function (a, b) { + return a.order - b.order; + }); + + // modifiers have the ability to execute arbitrary code when Popper.js get inited + // such code is executed in the same order of its modifier + // they could add new properties to their options configuration + // BE AWARE: don't add options to `options.modifiers.name` but to `modifierOptions`! + this.modifiers.forEach(function (modifierOptions) { + if (modifierOptions.enabled && isFunction(modifierOptions.onLoad)) { + modifierOptions.onLoad(_this.reference, _this.popper, _this.options, modifierOptions, _this.state); + } + }); + + // fire the first update to position the popper in the right place + this.update(); + + var eventsEnabled = this.options.eventsEnabled; + if (eventsEnabled) { + // setup event listeners, they will take care of update the position in specific situations + this.enableEventListeners(); + } + + this.state.eventsEnabled = eventsEnabled; + } + + // We can't use class properties because they don't get listed in the + // class prototype and break stuff like Sinon stubs + + + createClass(Popper, [{ + key: 'update', + value: function update$$1() { + return update.call(this); + } + }, { + key: 'destroy', + value: function destroy$$1() { + return destroy.call(this); + } + }, { + key: 'enableEventListeners', + value: function enableEventListeners$$1() { + return enableEventListeners.call(this); + } + }, { + key: 'disableEventListeners', + value: function disableEventListeners$$1() { + return disableEventListeners.call(this); + } + + /** + * Schedules an update. It will run on the next UI update available. + * @method scheduleUpdate + * @memberof Popper + */ + + + /** + * Collection of utilities useful when writing custom modifiers. + * Starting from version 1.7, this method is available only if you + * include `popper-utils.js` before `popper.js`. + * + * **DEPRECATION**: This way to access PopperUtils is deprecated + * and will be removed in v2! Use the PopperUtils module directly instead. + * Due to the high instability of the methods contained in Utils, we can't + * guarantee them to follow semver. Use them at your own risk! + * @static + * @private + * @type {Object} + * @deprecated since version 1.8 + * @member Utils + * @memberof Popper + */ + + }]); + return Popper; + }(); + + /** + * The `referenceObject` is an object that provides an interface compatible with Popper.js + * and lets you use it as replacement of a real DOM node.
+ * You can use this method to position a popper relatively to a set of coordinates + * in case you don't have a DOM node to use as reference. + * + * ``` + * new Popper(referenceObject, popperNode); + * ``` + * + * NB: This feature isn't supported in Internet Explorer 10. + * @name referenceObject + * @property {Function} data.getBoundingClientRect + * A function that returns a set of coordinates compatible with the native `getBoundingClientRect` method. + * @property {number} data.clientWidth + * An ES6 getter that will return the width of the virtual reference element. + * @property {number} data.clientHeight + * An ES6 getter that will return the height of the virtual reference element. + */ + + + Popper.Utils = (typeof window !== 'undefined' ? window : global).PopperUtils; + Popper.placements = placements; + Popper.Defaults = Defaults; + + /** + * ------------------------------------------------------------------------ + * Constants + * ------------------------------------------------------------------------ + */ + + var NAME$4 = 'dropdown'; + var VERSION$4 = '4.4.1'; + var DATA_KEY$4 = 'bs.dropdown'; + var EVENT_KEY$4 = "." + DATA_KEY$4; + var DATA_API_KEY$4 = '.data-api'; + var JQUERY_NO_CONFLICT$4 = $.fn[NAME$4]; + var ESCAPE_KEYCODE = 27; // KeyboardEvent.which value for Escape (Esc) key + + var SPACE_KEYCODE = 32; // KeyboardEvent.which value for space key + + var TAB_KEYCODE = 9; // KeyboardEvent.which value for tab key + + var ARROW_UP_KEYCODE = 38; // KeyboardEvent.which value for up arrow key + + var ARROW_DOWN_KEYCODE = 40; // KeyboardEvent.which value for down arrow key + + var RIGHT_MOUSE_BUTTON_WHICH = 3; // MouseEvent.which value for the right button (assuming a right-handed mouse) + + var REGEXP_KEYDOWN = new RegExp(ARROW_UP_KEYCODE + "|" + ARROW_DOWN_KEYCODE + "|" + ESCAPE_KEYCODE); + var Event$4 = { + HIDE: "hide" + EVENT_KEY$4, + HIDDEN: "hidden" + EVENT_KEY$4, + SHOW: "show" + EVENT_KEY$4, + SHOWN: "shown" + EVENT_KEY$4, + CLICK: "click" + EVENT_KEY$4, + CLICK_DATA_API: "click" + EVENT_KEY$4 + DATA_API_KEY$4, + KEYDOWN_DATA_API: "keydown" + EVENT_KEY$4 + DATA_API_KEY$4, + KEYUP_DATA_API: "keyup" + EVENT_KEY$4 + DATA_API_KEY$4 + }; + var ClassName$4 = { + DISABLED: 'disabled', + SHOW: 'show', + DROPUP: 'dropup', + DROPRIGHT: 'dropright', + DROPLEFT: 'dropleft', + MENURIGHT: 'dropdown-menu-right', + MENULEFT: 'dropdown-menu-left', + POSITION_STATIC: 'position-static' + }; + var Selector$4 = { + DATA_TOGGLE: '[data-toggle="dropdown"]', + FORM_CHILD: '.dropdown form', + MENU: '.dropdown-menu', + NAVBAR_NAV: '.navbar-nav', + VISIBLE_ITEMS: '.dropdown-menu .dropdown-item:not(.disabled):not(:disabled)' + }; + var AttachmentMap = { + TOP: 'top-start', + TOPEND: 'top-end', + BOTTOM: 'bottom-start', + BOTTOMEND: 'bottom-end', + RIGHT: 'right-start', + RIGHTEND: 'right-end', + LEFT: 'left-start', + LEFTEND: 'left-end' + }; + var Default$2 = { + offset: 0, + flip: true, + boundary: 'scrollParent', + reference: 'toggle', + display: 'dynamic', + popperConfig: null + }; + var DefaultType$2 = { + offset: '(number|string|function)', + flip: 'boolean', + boundary: '(string|element)', + reference: '(string|element)', + display: 'string', + popperConfig: '(null|object)' + }; + /** + * ------------------------------------------------------------------------ + * Class Definition + * ------------------------------------------------------------------------ + */ + + var Dropdown = + /*#__PURE__*/ + function () { + function Dropdown(element, config) { + this._element = element; + this._popper = null; + this._config = this._getConfig(config); + this._menu = this._getMenuElement(); + this._inNavbar = this._detectNavbar(); + + this._addEventListeners(); + } // Getters + + + var _proto = Dropdown.prototype; + + // Public + _proto.toggle = function toggle() { + if (this._element.disabled || $(this._element).hasClass(ClassName$4.DISABLED)) { + return; + } + + var isActive = $(this._menu).hasClass(ClassName$4.SHOW); + + Dropdown._clearMenus(); + + if (isActive) { + return; + } + + this.show(true); + }; + + _proto.show = function show(usePopper) { + if (usePopper === void 0) { + usePopper = false; + } + + if (this._element.disabled || $(this._element).hasClass(ClassName$4.DISABLED) || $(this._menu).hasClass(ClassName$4.SHOW)) { + return; + } + + var relatedTarget = { + relatedTarget: this._element + }; + var showEvent = $.Event(Event$4.SHOW, relatedTarget); + + var parent = Dropdown._getParentFromElement(this._element); + + $(parent).trigger(showEvent); + + if (showEvent.isDefaultPrevented()) { + return; + } // Disable totally Popper.js for Dropdown in Navbar + + + if (!this._inNavbar && usePopper) { + /** + * Check for Popper dependency + * Popper - https://popper.js.org + */ + if (typeof Popper === 'undefined') { + throw new TypeError('Bootstrap\'s dropdowns require Popper.js (https://popper.js.org/)'); + } + + var referenceElement = this._element; + + if (this._config.reference === 'parent') { + referenceElement = parent; + } else if (Util.isElement(this._config.reference)) { + referenceElement = this._config.reference; // Check if it's jQuery element + + if (typeof this._config.reference.jquery !== 'undefined') { + referenceElement = this._config.reference[0]; + } + } // If boundary is not `scrollParent`, then set position to `static` + // to allow the menu to "escape" the scroll parent's boundaries + // https://github.com/twbs/bootstrap/issues/24251 + + + if (this._config.boundary !== 'scrollParent') { + $(parent).addClass(ClassName$4.POSITION_STATIC); + } + + this._popper = new Popper(referenceElement, this._menu, this._getPopperConfig()); + } // If this is a touch-enabled device we add extra + // empty mouseover listeners to the body's immediate children; + // only needed because of broken event delegation on iOS + // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html + + + if ('ontouchstart' in document.documentElement && $(parent).closest(Selector$4.NAVBAR_NAV).length === 0) { + $(document.body).children().on('mouseover', null, $.noop); + } + + this._element.focus(); + + this._element.setAttribute('aria-expanded', true); + + $(this._menu).toggleClass(ClassName$4.SHOW); + $(parent).toggleClass(ClassName$4.SHOW).trigger($.Event(Event$4.SHOWN, relatedTarget)); + }; + + _proto.hide = function hide() { + if (this._element.disabled || $(this._element).hasClass(ClassName$4.DISABLED) || !$(this._menu).hasClass(ClassName$4.SHOW)) { + return; + } + + var relatedTarget = { + relatedTarget: this._element + }; + var hideEvent = $.Event(Event$4.HIDE, relatedTarget); + + var parent = Dropdown._getParentFromElement(this._element); + + $(parent).trigger(hideEvent); + + if (hideEvent.isDefaultPrevented()) { + return; + } + + if (this._popper) { + this._popper.destroy(); + } + + $(this._menu).toggleClass(ClassName$4.SHOW); + $(parent).toggleClass(ClassName$4.SHOW).trigger($.Event(Event$4.HIDDEN, relatedTarget)); + }; + + _proto.dispose = function dispose() { + $.removeData(this._element, DATA_KEY$4); + $(this._element).off(EVENT_KEY$4); + this._element = null; + this._menu = null; + + if (this._popper !== null) { + this._popper.destroy(); + + this._popper = null; + } + }; + + _proto.update = function update() { + this._inNavbar = this._detectNavbar(); + + if (this._popper !== null) { + this._popper.scheduleUpdate(); + } + } // Private + ; + + _proto._addEventListeners = function _addEventListeners() { + var _this = this; + + $(this._element).on(Event$4.CLICK, function (event) { + event.preventDefault(); + event.stopPropagation(); + + _this.toggle(); + }); + }; + + _proto._getConfig = function _getConfig(config) { + config = _objectSpread2({}, this.constructor.Default, {}, $(this._element).data(), {}, config); + Util.typeCheckConfig(NAME$4, config, this.constructor.DefaultType); + return config; + }; + + _proto._getMenuElement = function _getMenuElement() { + if (!this._menu) { + var parent = Dropdown._getParentFromElement(this._element); + + if (parent) { + this._menu = parent.querySelector(Selector$4.MENU); + } + } + + return this._menu; + }; + + _proto._getPlacement = function _getPlacement() { + var $parentDropdown = $(this._element.parentNode); + var placement = AttachmentMap.BOTTOM; // Handle dropup + + if ($parentDropdown.hasClass(ClassName$4.DROPUP)) { + placement = AttachmentMap.TOP; + + if ($(this._menu).hasClass(ClassName$4.MENURIGHT)) { + placement = AttachmentMap.TOPEND; + } + } else if ($parentDropdown.hasClass(ClassName$4.DROPRIGHT)) { + placement = AttachmentMap.RIGHT; + } else if ($parentDropdown.hasClass(ClassName$4.DROPLEFT)) { + placement = AttachmentMap.LEFT; + } else if ($(this._menu).hasClass(ClassName$4.MENURIGHT)) { + placement = AttachmentMap.BOTTOMEND; + } + + return placement; + }; + + _proto._detectNavbar = function _detectNavbar() { + return $(this._element).closest('.navbar').length > 0; + }; + + _proto._getOffset = function _getOffset() { + var _this2 = this; + + var offset = {}; + + if (typeof this._config.offset === 'function') { + offset.fn = function (data) { + data.offsets = _objectSpread2({}, data.offsets, {}, _this2._config.offset(data.offsets, _this2._element) || {}); + return data; + }; + } else { + offset.offset = this._config.offset; + } + + return offset; + }; + + _proto._getPopperConfig = function _getPopperConfig() { + var popperConfig = { + placement: this._getPlacement(), + modifiers: { + offset: this._getOffset(), + flip: { + enabled: this._config.flip + }, + preventOverflow: { + boundariesElement: this._config.boundary + } + } + }; // Disable Popper.js if we have a static display + + if (this._config.display === 'static') { + popperConfig.modifiers.applyStyle = { + enabled: false + }; + } + + return _objectSpread2({}, popperConfig, {}, this._config.popperConfig); + } // Static + ; + + Dropdown._jQueryInterface = function _jQueryInterface(config) { + return this.each(function () { + var data = $(this).data(DATA_KEY$4); + + var _config = typeof config === 'object' ? config : null; + + if (!data) { + data = new Dropdown(this, _config); + $(this).data(DATA_KEY$4, data); + } + + if (typeof config === 'string') { + if (typeof data[config] === 'undefined') { + throw new TypeError("No method named \"" + config + "\""); + } + + data[config](); + } + }); + }; + + Dropdown._clearMenus = function _clearMenus(event) { + if (event && (event.which === RIGHT_MOUSE_BUTTON_WHICH || event.type === 'keyup' && event.which !== TAB_KEYCODE)) { + return; + } + + var toggles = [].slice.call(document.querySelectorAll(Selector$4.DATA_TOGGLE)); + + for (var i = 0, len = toggles.length; i < len; i++) { + var parent = Dropdown._getParentFromElement(toggles[i]); + + var context = $(toggles[i]).data(DATA_KEY$4); + var relatedTarget = { + relatedTarget: toggles[i] + }; + + if (event && event.type === 'click') { + relatedTarget.clickEvent = event; + } + + if (!context) { + continue; + } + + var dropdownMenu = context._menu; + + if (!$(parent).hasClass(ClassName$4.SHOW)) { + continue; + } + + if (event && (event.type === 'click' && /input|textarea/i.test(event.target.tagName) || event.type === 'keyup' && event.which === TAB_KEYCODE) && $.contains(parent, event.target)) { + continue; + } + + var hideEvent = $.Event(Event$4.HIDE, relatedTarget); + $(parent).trigger(hideEvent); + + if (hideEvent.isDefaultPrevented()) { + continue; + } // If this is a touch-enabled device we remove the extra + // empty mouseover listeners we added for iOS support + + + if ('ontouchstart' in document.documentElement) { + $(document.body).children().off('mouseover', null, $.noop); + } + + toggles[i].setAttribute('aria-expanded', 'false'); + + if (context._popper) { + context._popper.destroy(); + } + + $(dropdownMenu).removeClass(ClassName$4.SHOW); + $(parent).removeClass(ClassName$4.SHOW).trigger($.Event(Event$4.HIDDEN, relatedTarget)); + } + }; + + Dropdown._getParentFromElement = function _getParentFromElement(element) { + var parent; + var selector = Util.getSelectorFromElement(element); + + if (selector) { + parent = document.querySelector(selector); + } + + return parent || element.parentNode; + } // eslint-disable-next-line complexity + ; + + Dropdown._dataApiKeydownHandler = function _dataApiKeydownHandler(event) { + // If not input/textarea: + // - And not a key in REGEXP_KEYDOWN => not a dropdown command + // If input/textarea: + // - If space key => not a dropdown command + // - If key is other than escape + // - If key is not up or down => not a dropdown command + // - If trigger inside the menu => not a dropdown command + if (/input|textarea/i.test(event.target.tagName) ? event.which === SPACE_KEYCODE || event.which !== ESCAPE_KEYCODE && (event.which !== ARROW_DOWN_KEYCODE && event.which !== ARROW_UP_KEYCODE || $(event.target).closest(Selector$4.MENU).length) : !REGEXP_KEYDOWN.test(event.which)) { + return; + } + + event.preventDefault(); + event.stopPropagation(); + + if (this.disabled || $(this).hasClass(ClassName$4.DISABLED)) { + return; + } + + var parent = Dropdown._getParentFromElement(this); + + var isActive = $(parent).hasClass(ClassName$4.SHOW); + + if (!isActive && event.which === ESCAPE_KEYCODE) { + return; + } + + if (!isActive || isActive && (event.which === ESCAPE_KEYCODE || event.which === SPACE_KEYCODE)) { + if (event.which === ESCAPE_KEYCODE) { + var toggle = parent.querySelector(Selector$4.DATA_TOGGLE); + $(toggle).trigger('focus'); + } + + $(this).trigger('click'); + return; + } + + var items = [].slice.call(parent.querySelectorAll(Selector$4.VISIBLE_ITEMS)).filter(function (item) { + return $(item).is(':visible'); + }); + + if (items.length === 0) { + return; + } + + var index = items.indexOf(event.target); + + if (event.which === ARROW_UP_KEYCODE && index > 0) { + // Up + index--; + } + + if (event.which === ARROW_DOWN_KEYCODE && index < items.length - 1) { + // Down + index++; + } + + if (index < 0) { + index = 0; + } + + items[index].focus(); + }; + + _createClass(Dropdown, null, [{ + key: "VERSION", + get: function get() { + return VERSION$4; + } + }, { + key: "Default", + get: function get() { + return Default$2; + } + }, { + key: "DefaultType", + get: function get() { + return DefaultType$2; + } + }]); + + return Dropdown; + }(); + /** + * ------------------------------------------------------------------------ + * Data Api implementation + * ------------------------------------------------------------------------ + */ + + + $(document).on(Event$4.KEYDOWN_DATA_API, Selector$4.DATA_TOGGLE, Dropdown._dataApiKeydownHandler).on(Event$4.KEYDOWN_DATA_API, Selector$4.MENU, Dropdown._dataApiKeydownHandler).on(Event$4.CLICK_DATA_API + " " + Event$4.KEYUP_DATA_API, Dropdown._clearMenus).on(Event$4.CLICK_DATA_API, Selector$4.DATA_TOGGLE, function (event) { + event.preventDefault(); + event.stopPropagation(); + + Dropdown._jQueryInterface.call($(this), 'toggle'); + }).on(Event$4.CLICK_DATA_API, Selector$4.FORM_CHILD, function (e) { + e.stopPropagation(); + }); + /** + * ------------------------------------------------------------------------ + * jQuery + * ------------------------------------------------------------------------ + */ + + $.fn[NAME$4] = Dropdown._jQueryInterface; + $.fn[NAME$4].Constructor = Dropdown; + + $.fn[NAME$4].noConflict = function () { + $.fn[NAME$4] = JQUERY_NO_CONFLICT$4; + return Dropdown._jQueryInterface; + }; + + /** + * ------------------------------------------------------------------------ + * Constants + * ------------------------------------------------------------------------ + */ + + var NAME$5 = 'modal'; + var VERSION$5 = '4.4.1'; + var DATA_KEY$5 = 'bs.modal'; + var EVENT_KEY$5 = "." + DATA_KEY$5; + var DATA_API_KEY$5 = '.data-api'; + var JQUERY_NO_CONFLICT$5 = $.fn[NAME$5]; + var ESCAPE_KEYCODE$1 = 27; // KeyboardEvent.which value for Escape (Esc) key + + var Default$3 = { + backdrop: true, + keyboard: true, + focus: true, + show: true + }; + var DefaultType$3 = { + backdrop: '(boolean|string)', + keyboard: 'boolean', + focus: 'boolean', + show: 'boolean' + }; + var Event$5 = { + HIDE: "hide" + EVENT_KEY$5, + HIDE_PREVENTED: "hidePrevented" + EVENT_KEY$5, + HIDDEN: "hidden" + EVENT_KEY$5, + SHOW: "show" + EVENT_KEY$5, + SHOWN: "shown" + EVENT_KEY$5, + FOCUSIN: "focusin" + EVENT_KEY$5, + RESIZE: "resize" + EVENT_KEY$5, + CLICK_DISMISS: "click.dismiss" + EVENT_KEY$5, + KEYDOWN_DISMISS: "keydown.dismiss" + EVENT_KEY$5, + MOUSEUP_DISMISS: "mouseup.dismiss" + EVENT_KEY$5, + MOUSEDOWN_DISMISS: "mousedown.dismiss" + EVENT_KEY$5, + CLICK_DATA_API: "click" + EVENT_KEY$5 + DATA_API_KEY$5 + }; + var ClassName$5 = { + SCROLLABLE: 'modal-dialog-scrollable', + SCROLLBAR_MEASURER: 'modal-scrollbar-measure', + BACKDROP: 'modal-backdrop', + OPEN: 'modal-open', + FADE: 'fade', + SHOW: 'show', + STATIC: 'modal-static' + }; + var Selector$5 = { + DIALOG: '.modal-dialog', + MODAL_BODY: '.modal-body', + DATA_TOGGLE: '[data-toggle="modal"]', + DATA_DISMISS: '[data-dismiss="modal"]', + FIXED_CONTENT: '.fixed-top, .fixed-bottom, .is-fixed, .sticky-top', + STICKY_CONTENT: '.sticky-top' + }; + /** + * ------------------------------------------------------------------------ + * Class Definition + * ------------------------------------------------------------------------ + */ + + var Modal = + /*#__PURE__*/ + function () { + function Modal(element, config) { + this._config = this._getConfig(config); + this._element = element; + this._dialog = element.querySelector(Selector$5.DIALOG); + this._backdrop = null; + this._isShown = false; + this._isBodyOverflowing = false; + this._ignoreBackdropClick = false; + this._isTransitioning = false; + this._scrollbarWidth = 0; + } // Getters + + + var _proto = Modal.prototype; + + // Public + _proto.toggle = function toggle(relatedTarget) { + return this._isShown ? this.hide() : this.show(relatedTarget); + }; + + _proto.show = function show(relatedTarget) { + var _this = this; + + if (this._isShown || this._isTransitioning) { + return; + } + + if ($(this._element).hasClass(ClassName$5.FADE)) { + this._isTransitioning = true; + } + + var showEvent = $.Event(Event$5.SHOW, { + relatedTarget: relatedTarget + }); + $(this._element).trigger(showEvent); + + if (this._isShown || showEvent.isDefaultPrevented()) { + return; + } + + this._isShown = true; + + this._checkScrollbar(); + + this._setScrollbar(); + + this._adjustDialog(); + + this._setEscapeEvent(); + + this._setResizeEvent(); + + $(this._element).on(Event$5.CLICK_DISMISS, Selector$5.DATA_DISMISS, function (event) { + return _this.hide(event); + }); + $(this._dialog).on(Event$5.MOUSEDOWN_DISMISS, function () { + $(_this._element).one(Event$5.MOUSEUP_DISMISS, function (event) { + if ($(event.target).is(_this._element)) { + _this._ignoreBackdropClick = true; + } + }); + }); + + this._showBackdrop(function () { + return _this._showElement(relatedTarget); + }); + }; + + _proto.hide = function hide(event) { + var _this2 = this; + + if (event) { + event.preventDefault(); + } + + if (!this._isShown || this._isTransitioning) { + return; + } + + var hideEvent = $.Event(Event$5.HIDE); + $(this._element).trigger(hideEvent); + + if (!this._isShown || hideEvent.isDefaultPrevented()) { + return; + } + + this._isShown = false; + var transition = $(this._element).hasClass(ClassName$5.FADE); + + if (transition) { + this._isTransitioning = true; + } + + this._setEscapeEvent(); + + this._setResizeEvent(); + + $(document).off(Event$5.FOCUSIN); + $(this._element).removeClass(ClassName$5.SHOW); + $(this._element).off(Event$5.CLICK_DISMISS); + $(this._dialog).off(Event$5.MOUSEDOWN_DISMISS); + + if (transition) { + var transitionDuration = Util.getTransitionDurationFromElement(this._element); + $(this._element).one(Util.TRANSITION_END, function (event) { + return _this2._hideModal(event); + }).emulateTransitionEnd(transitionDuration); + } else { + this._hideModal(); + } + }; + + _proto.dispose = function dispose() { + [window, this._element, this._dialog].forEach(function (htmlElement) { + return $(htmlElement).off(EVENT_KEY$5); + }); + /** + * `document` has 2 events `Event.FOCUSIN` and `Event.CLICK_DATA_API` + * Do not move `document` in `htmlElements` array + * It will remove `Event.CLICK_DATA_API` event that should remain + */ + + $(document).off(Event$5.FOCUSIN); + $.removeData(this._element, DATA_KEY$5); + this._config = null; + this._element = null; + this._dialog = null; + this._backdrop = null; + this._isShown = null; + this._isBodyOverflowing = null; + this._ignoreBackdropClick = null; + this._isTransitioning = null; + this._scrollbarWidth = null; + }; + + _proto.handleUpdate = function handleUpdate() { + this._adjustDialog(); + } // Private + ; + + _proto._getConfig = function _getConfig(config) { + config = _objectSpread2({}, Default$3, {}, config); + Util.typeCheckConfig(NAME$5, config, DefaultType$3); + return config; + }; + + _proto._triggerBackdropTransition = function _triggerBackdropTransition() { + var _this3 = this; + + if (this._config.backdrop === 'static') { + var hideEventPrevented = $.Event(Event$5.HIDE_PREVENTED); + $(this._element).trigger(hideEventPrevented); + + if (hideEventPrevented.defaultPrevented) { + return; + } + + this._element.classList.add(ClassName$5.STATIC); + + var modalTransitionDuration = Util.getTransitionDurationFromElement(this._element); + $(this._element).one(Util.TRANSITION_END, function () { + _this3._element.classList.remove(ClassName$5.STATIC); + }).emulateTransitionEnd(modalTransitionDuration); + + this._element.focus(); + } else { + this.hide(); + } + }; + + _proto._showElement = function _showElement(relatedTarget) { + var _this4 = this; + + var transition = $(this._element).hasClass(ClassName$5.FADE); + var modalBody = this._dialog ? this._dialog.querySelector(Selector$5.MODAL_BODY) : null; + + if (!this._element.parentNode || this._element.parentNode.nodeType !== Node.ELEMENT_NODE) { + // Don't move modal's DOM position + document.body.appendChild(this._element); + } + + this._element.style.display = 'block'; + + this._element.removeAttribute('aria-hidden'); + + this._element.setAttribute('aria-modal', true); + + if ($(this._dialog).hasClass(ClassName$5.SCROLLABLE) && modalBody) { + modalBody.scrollTop = 0; + } else { + this._element.scrollTop = 0; + } + + if (transition) { + Util.reflow(this._element); + } + + $(this._element).addClass(ClassName$5.SHOW); + + if (this._config.focus) { + this._enforceFocus(); + } + + var shownEvent = $.Event(Event$5.SHOWN, { + relatedTarget: relatedTarget + }); + + var transitionComplete = function transitionComplete() { + if (_this4._config.focus) { + _this4._element.focus(); + } + + _this4._isTransitioning = false; + $(_this4._element).trigger(shownEvent); + }; + + if (transition) { + var transitionDuration = Util.getTransitionDurationFromElement(this._dialog); + $(this._dialog).one(Util.TRANSITION_END, transitionComplete).emulateTransitionEnd(transitionDuration); + } else { + transitionComplete(); + } + }; + + _proto._enforceFocus = function _enforceFocus() { + var _this5 = this; + + $(document).off(Event$5.FOCUSIN) // Guard against infinite focus loop + .on(Event$5.FOCUSIN, function (event) { + if (document !== event.target && _this5._element !== event.target && $(_this5._element).has(event.target).length === 0) { + _this5._element.focus(); + } + }); + }; + + _proto._setEscapeEvent = function _setEscapeEvent() { + var _this6 = this; + + if (this._isShown && this._config.keyboard) { + $(this._element).on(Event$5.KEYDOWN_DISMISS, function (event) { + if (event.which === ESCAPE_KEYCODE$1) { + _this6._triggerBackdropTransition(); + } + }); + } else if (!this._isShown) { + $(this._element).off(Event$5.KEYDOWN_DISMISS); + } + }; + + _proto._setResizeEvent = function _setResizeEvent() { + var _this7 = this; + + if (this._isShown) { + $(window).on(Event$5.RESIZE, function (event) { + return _this7.handleUpdate(event); + }); + } else { + $(window).off(Event$5.RESIZE); + } + }; + + _proto._hideModal = function _hideModal() { + var _this8 = this; + + this._element.style.display = 'none'; + + this._element.setAttribute('aria-hidden', true); + + this._element.removeAttribute('aria-modal'); + + this._isTransitioning = false; + + this._showBackdrop(function () { + $(document.body).removeClass(ClassName$5.OPEN); + + _this8._resetAdjustments(); + + _this8._resetScrollbar(); + + $(_this8._element).trigger(Event$5.HIDDEN); + }); + }; + + _proto._removeBackdrop = function _removeBackdrop() { + if (this._backdrop) { + $(this._backdrop).remove(); + this._backdrop = null; + } + }; + + _proto._showBackdrop = function _showBackdrop(callback) { + var _this9 = this; + + var animate = $(this._element).hasClass(ClassName$5.FADE) ? ClassName$5.FADE : ''; + + if (this._isShown && this._config.backdrop) { + this._backdrop = document.createElement('div'); + this._backdrop.className = ClassName$5.BACKDROP; + + if (animate) { + this._backdrop.classList.add(animate); + } + + $(this._backdrop).appendTo(document.body); + $(this._element).on(Event$5.CLICK_DISMISS, function (event) { + if (_this9._ignoreBackdropClick) { + _this9._ignoreBackdropClick = false; + return; + } + + if (event.target !== event.currentTarget) { + return; + } + + _this9._triggerBackdropTransition(); + }); + + if (animate) { + Util.reflow(this._backdrop); + } + + $(this._backdrop).addClass(ClassName$5.SHOW); + + if (!callback) { + return; + } + + if (!animate) { + callback(); + return; + } + + var backdropTransitionDuration = Util.getTransitionDurationFromElement(this._backdrop); + $(this._backdrop).one(Util.TRANSITION_END, callback).emulateTransitionEnd(backdropTransitionDuration); + } else if (!this._isShown && this._backdrop) { + $(this._backdrop).removeClass(ClassName$5.SHOW); + + var callbackRemove = function callbackRemove() { + _this9._removeBackdrop(); + + if (callback) { + callback(); + } + }; + + if ($(this._element).hasClass(ClassName$5.FADE)) { + var _backdropTransitionDuration = Util.getTransitionDurationFromElement(this._backdrop); + + $(this._backdrop).one(Util.TRANSITION_END, callbackRemove).emulateTransitionEnd(_backdropTransitionDuration); + } else { + callbackRemove(); + } + } else if (callback) { + callback(); + } + } // ---------------------------------------------------------------------- + // the following methods are used to handle overflowing modals + // todo (fat): these should probably be refactored out of modal.js + // ---------------------------------------------------------------------- + ; + + _proto._adjustDialog = function _adjustDialog() { + var isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight; + + if (!this._isBodyOverflowing && isModalOverflowing) { + this._element.style.paddingLeft = this._scrollbarWidth + "px"; + } + + if (this._isBodyOverflowing && !isModalOverflowing) { + this._element.style.paddingRight = this._scrollbarWidth + "px"; + } + }; + + _proto._resetAdjustments = function _resetAdjustments() { + this._element.style.paddingLeft = ''; + this._element.style.paddingRight = ''; + }; + + _proto._checkScrollbar = function _checkScrollbar() { + var rect = document.body.getBoundingClientRect(); + this._isBodyOverflowing = rect.left + rect.right < window.innerWidth; + this._scrollbarWidth = this._getScrollbarWidth(); + }; + + _proto._setScrollbar = function _setScrollbar() { + var _this10 = this; + + if (this._isBodyOverflowing) { + // Note: DOMNode.style.paddingRight returns the actual value or '' if not set + // while $(DOMNode).css('padding-right') returns the calculated value or 0 if not set + var fixedContent = [].slice.call(document.querySelectorAll(Selector$5.FIXED_CONTENT)); + var stickyContent = [].slice.call(document.querySelectorAll(Selector$5.STICKY_CONTENT)); // Adjust fixed content padding + + $(fixedContent).each(function (index, element) { + var actualPadding = element.style.paddingRight; + var calculatedPadding = $(element).css('padding-right'); + $(element).data('padding-right', actualPadding).css('padding-right', parseFloat(calculatedPadding) + _this10._scrollbarWidth + "px"); + }); // Adjust sticky content margin + + $(stickyContent).each(function (index, element) { + var actualMargin = element.style.marginRight; + var calculatedMargin = $(element).css('margin-right'); + $(element).data('margin-right', actualMargin).css('margin-right', parseFloat(calculatedMargin) - _this10._scrollbarWidth + "px"); + }); // Adjust body padding + + var actualPadding = document.body.style.paddingRight; + var calculatedPadding = $(document.body).css('padding-right'); + $(document.body).data('padding-right', actualPadding).css('padding-right', parseFloat(calculatedPadding) + this._scrollbarWidth + "px"); + } + + $(document.body).addClass(ClassName$5.OPEN); + }; + + _proto._resetScrollbar = function _resetScrollbar() { + // Restore fixed content padding + var fixedContent = [].slice.call(document.querySelectorAll(Selector$5.FIXED_CONTENT)); + $(fixedContent).each(function (index, element) { + var padding = $(element).data('padding-right'); + $(element).removeData('padding-right'); + element.style.paddingRight = padding ? padding : ''; + }); // Restore sticky content + + var elements = [].slice.call(document.querySelectorAll("" + Selector$5.STICKY_CONTENT)); + $(elements).each(function (index, element) { + var margin = $(element).data('margin-right'); + + if (typeof margin !== 'undefined') { + $(element).css('margin-right', margin).removeData('margin-right'); + } + }); // Restore body padding + + var padding = $(document.body).data('padding-right'); + $(document.body).removeData('padding-right'); + document.body.style.paddingRight = padding ? padding : ''; + }; + + _proto._getScrollbarWidth = function _getScrollbarWidth() { + // thx d.walsh + var scrollDiv = document.createElement('div'); + scrollDiv.className = ClassName$5.SCROLLBAR_MEASURER; + document.body.appendChild(scrollDiv); + var scrollbarWidth = scrollDiv.getBoundingClientRect().width - scrollDiv.clientWidth; + document.body.removeChild(scrollDiv); + return scrollbarWidth; + } // Static + ; + + Modal._jQueryInterface = function _jQueryInterface(config, relatedTarget) { + return this.each(function () { + var data = $(this).data(DATA_KEY$5); + + var _config = _objectSpread2({}, Default$3, {}, $(this).data(), {}, typeof config === 'object' && config ? config : {}); + + if (!data) { + data = new Modal(this, _config); + $(this).data(DATA_KEY$5, data); + } + + if (typeof config === 'string') { + if (typeof data[config] === 'undefined') { + throw new TypeError("No method named \"" + config + "\""); + } + + data[config](relatedTarget); + } else if (_config.show) { + data.show(relatedTarget); + } + }); + }; + + _createClass(Modal, null, [{ + key: "VERSION", + get: function get() { + return VERSION$5; + } + }, { + key: "Default", + get: function get() { + return Default$3; + } + }]); + + return Modal; + }(); + /** + * ------------------------------------------------------------------------ + * Data Api implementation + * ------------------------------------------------------------------------ + */ + + + $(document).on(Event$5.CLICK_DATA_API, Selector$5.DATA_TOGGLE, function (event) { + var _this11 = this; + + var target; + var selector = Util.getSelectorFromElement(this); + + if (selector) { + target = document.querySelector(selector); + } + + var config = $(target).data(DATA_KEY$5) ? 'toggle' : _objectSpread2({}, $(target).data(), {}, $(this).data()); + + if (this.tagName === 'A' || this.tagName === 'AREA') { + event.preventDefault(); + } + + var $target = $(target).one(Event$5.SHOW, function (showEvent) { + if (showEvent.isDefaultPrevented()) { + // Only register focus restorer if modal will actually get shown + return; + } + + $target.one(Event$5.HIDDEN, function () { + if ($(_this11).is(':visible')) { + _this11.focus(); + } + }); + }); + + Modal._jQueryInterface.call($(target), config, this); + }); + /** + * ------------------------------------------------------------------------ + * jQuery + * ------------------------------------------------------------------------ + */ + + $.fn[NAME$5] = Modal._jQueryInterface; + $.fn[NAME$5].Constructor = Modal; + + $.fn[NAME$5].noConflict = function () { + $.fn[NAME$5] = JQUERY_NO_CONFLICT$5; + return Modal._jQueryInterface; + }; + + /** + * -------------------------------------------------------------------------- + * Bootstrap (v4.4.1): tools/sanitizer.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * -------------------------------------------------------------------------- + */ + var uriAttrs = ['background', 'cite', 'href', 'itemtype', 'longdesc', 'poster', 'src', 'xlink:href']; + var ARIA_ATTRIBUTE_PATTERN = /^aria-[\w-]*$/i; + var DefaultWhitelist = { + // Global attributes allowed on any supplied element below. + '*': ['class', 'dir', 'id', 'lang', 'role', ARIA_ATTRIBUTE_PATTERN], + a: ['target', 'href', 'title', 'rel'], + area: [], + b: [], + br: [], + col: [], + code: [], + div: [], + em: [], + hr: [], + h1: [], + h2: [], + h3: [], + h4: [], + h5: [], + h6: [], + i: [], + img: ['src', 'alt', 'title', 'width', 'height'], + li: [], + ol: [], + p: [], + pre: [], + s: [], + small: [], + span: [], + sub: [], + sup: [], + strong: [], + u: [], + ul: [] + }; + /** + * A pattern that recognizes a commonly useful subset of URLs that are safe. + * + * Shoutout to Angular 7 https://github.com/angular/angular/blob/7.2.4/packages/core/src/sanitization/url_sanitizer.ts + */ + + var SAFE_URL_PATTERN = /^(?:(?:https?|mailto|ftp|tel|file):|[^&:/?#]*(?:[/?#]|$))/gi; + /** + * A pattern that matches safe data URLs. Only matches image, video and audio types. + * + * Shoutout to Angular 7 https://github.com/angular/angular/blob/7.2.4/packages/core/src/sanitization/url_sanitizer.ts + */ + + var DATA_URL_PATTERN = /^data:(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm)|audio\/(?:mp3|oga|ogg|opus));base64,[a-z0-9+/]+=*$/i; + + function allowedAttribute(attr, allowedAttributeList) { + var attrName = attr.nodeName.toLowerCase(); + + if (allowedAttributeList.indexOf(attrName) !== -1) { + if (uriAttrs.indexOf(attrName) !== -1) { + return Boolean(attr.nodeValue.match(SAFE_URL_PATTERN) || attr.nodeValue.match(DATA_URL_PATTERN)); + } + + return true; + } + + var regExp = allowedAttributeList.filter(function (attrRegex) { + return attrRegex instanceof RegExp; + }); // Check if a regular expression validates the attribute. + + for (var i = 0, l = regExp.length; i < l; i++) { + if (attrName.match(regExp[i])) { + return true; + } + } + + return false; + } + + function sanitizeHtml(unsafeHtml, whiteList, sanitizeFn) { + if (unsafeHtml.length === 0) { + return unsafeHtml; + } + + if (sanitizeFn && typeof sanitizeFn === 'function') { + return sanitizeFn(unsafeHtml); + } + + var domParser = new window.DOMParser(); + var createdDocument = domParser.parseFromString(unsafeHtml, 'text/html'); + var whitelistKeys = Object.keys(whiteList); + var elements = [].slice.call(createdDocument.body.querySelectorAll('*')); + + var _loop = function _loop(i, len) { + var el = elements[i]; + var elName = el.nodeName.toLowerCase(); + + if (whitelistKeys.indexOf(el.nodeName.toLowerCase()) === -1) { + el.parentNode.removeChild(el); + return "continue"; + } + + var attributeList = [].slice.call(el.attributes); + var whitelistedAttributes = [].concat(whiteList['*'] || [], whiteList[elName] || []); + attributeList.forEach(function (attr) { + if (!allowedAttribute(attr, whitelistedAttributes)) { + el.removeAttribute(attr.nodeName); + } + }); + }; + + for (var i = 0, len = elements.length; i < len; i++) { + var _ret = _loop(i); + + if (_ret === "continue") continue; + } + + return createdDocument.body.innerHTML; + } + + /** + * ------------------------------------------------------------------------ + * Constants + * ------------------------------------------------------------------------ + */ + + var NAME$6 = 'tooltip'; + var VERSION$6 = '4.4.1'; + var DATA_KEY$6 = 'bs.tooltip'; + var EVENT_KEY$6 = "." + DATA_KEY$6; + var JQUERY_NO_CONFLICT$6 = $.fn[NAME$6]; + var CLASS_PREFIX = 'bs-tooltip'; + var BSCLS_PREFIX_REGEX = new RegExp("(^|\\s)" + CLASS_PREFIX + "\\S+", 'g'); + var DISALLOWED_ATTRIBUTES = ['sanitize', 'whiteList', 'sanitizeFn']; + var DefaultType$4 = { + animation: 'boolean', + template: 'string', + title: '(string|element|function)', + trigger: 'string', + delay: '(number|object)', + html: 'boolean', + selector: '(string|boolean)', + placement: '(string|function)', + offset: '(number|string|function)', + container: '(string|element|boolean)', + fallbackPlacement: '(string|array)', + boundary: '(string|element)', + sanitize: 'boolean', + sanitizeFn: '(null|function)', + whiteList: 'object', + popperConfig: '(null|object)' + }; + var AttachmentMap$1 = { + AUTO: 'auto', + TOP: 'top', + RIGHT: 'right', + BOTTOM: 'bottom', + LEFT: 'left' + }; + var Default$4 = { + animation: true, + template: '', + trigger: 'hover focus', + title: '', + delay: 0, + html: false, + selector: false, + placement: 'top', + offset: 0, + container: false, + fallbackPlacement: 'flip', + boundary: 'scrollParent', + sanitize: true, + sanitizeFn: null, + whiteList: DefaultWhitelist, + popperConfig: null + }; + var HoverState = { + SHOW: 'show', + OUT: 'out' + }; + var Event$6 = { + HIDE: "hide" + EVENT_KEY$6, + HIDDEN: "hidden" + EVENT_KEY$6, + SHOW: "show" + EVENT_KEY$6, + SHOWN: "shown" + EVENT_KEY$6, + INSERTED: "inserted" + EVENT_KEY$6, + CLICK: "click" + EVENT_KEY$6, + FOCUSIN: "focusin" + EVENT_KEY$6, + FOCUSOUT: "focusout" + EVENT_KEY$6, + MOUSEENTER: "mouseenter" + EVENT_KEY$6, + MOUSELEAVE: "mouseleave" + EVENT_KEY$6 + }; + var ClassName$6 = { + FADE: 'fade', + SHOW: 'show' + }; + var Selector$6 = { + TOOLTIP: '.tooltip', + TOOLTIP_INNER: '.tooltip-inner', + ARROW: '.arrow' + }; + var Trigger = { + HOVER: 'hover', + FOCUS: 'focus', + CLICK: 'click', + MANUAL: 'manual' + }; + /** + * ------------------------------------------------------------------------ + * Class Definition + * ------------------------------------------------------------------------ + */ + + var Tooltip = + /*#__PURE__*/ + function () { + function Tooltip(element, config) { + if (typeof Popper === 'undefined') { + throw new TypeError('Bootstrap\'s tooltips require Popper.js (https://popper.js.org/)'); + } // private + + + this._isEnabled = true; + this._timeout = 0; + this._hoverState = ''; + this._activeTrigger = {}; + this._popper = null; // Protected + + this.element = element; + this.config = this._getConfig(config); + this.tip = null; + + this._setListeners(); + } // Getters + + + var _proto = Tooltip.prototype; + + // Public + _proto.enable = function enable() { + this._isEnabled = true; + }; + + _proto.disable = function disable() { + this._isEnabled = false; + }; + + _proto.toggleEnabled = function toggleEnabled() { + this._isEnabled = !this._isEnabled; + }; + + _proto.toggle = function toggle(event) { + if (!this._isEnabled) { + return; + } + + if (event) { + var dataKey = this.constructor.DATA_KEY; + var context = $(event.currentTarget).data(dataKey); + + if (!context) { + context = new this.constructor(event.currentTarget, this._getDelegateConfig()); + $(event.currentTarget).data(dataKey, context); + } + + context._activeTrigger.click = !context._activeTrigger.click; + + if (context._isWithActiveTrigger()) { + context._enter(null, context); + } else { + context._leave(null, context); + } + } else { + if ($(this.getTipElement()).hasClass(ClassName$6.SHOW)) { + this._leave(null, this); + + return; + } + + this._enter(null, this); + } + }; + + _proto.dispose = function dispose() { + clearTimeout(this._timeout); + $.removeData(this.element, this.constructor.DATA_KEY); + $(this.element).off(this.constructor.EVENT_KEY); + $(this.element).closest('.modal').off('hide.bs.modal', this._hideModalHandler); + + if (this.tip) { + $(this.tip).remove(); + } + + this._isEnabled = null; + this._timeout = null; + this._hoverState = null; + this._activeTrigger = null; + + if (this._popper) { + this._popper.destroy(); + } + + this._popper = null; + this.element = null; + this.config = null; + this.tip = null; + }; + + _proto.show = function show() { + var _this = this; + + if ($(this.element).css('display') === 'none') { + throw new Error('Please use show on visible elements'); + } + + var showEvent = $.Event(this.constructor.Event.SHOW); + + if (this.isWithContent() && this._isEnabled) { + $(this.element).trigger(showEvent); + var shadowRoot = Util.findShadowRoot(this.element); + var isInTheDom = $.contains(shadowRoot !== null ? shadowRoot : this.element.ownerDocument.documentElement, this.element); + + if (showEvent.isDefaultPrevented() || !isInTheDom) { + return; + } + + var tip = this.getTipElement(); + var tipId = Util.getUID(this.constructor.NAME); + tip.setAttribute('id', tipId); + this.element.setAttribute('aria-describedby', tipId); + this.setContent(); + + if (this.config.animation) { + $(tip).addClass(ClassName$6.FADE); + } + + var placement = typeof this.config.placement === 'function' ? this.config.placement.call(this, tip, this.element) : this.config.placement; + + var attachment = this._getAttachment(placement); + + this.addAttachmentClass(attachment); + + var container = this._getContainer(); + + $(tip).data(this.constructor.DATA_KEY, this); + + if (!$.contains(this.element.ownerDocument.documentElement, this.tip)) { + $(tip).appendTo(container); + } + + $(this.element).trigger(this.constructor.Event.INSERTED); + this._popper = new Popper(this.element, tip, this._getPopperConfig(attachment)); + $(tip).addClass(ClassName$6.SHOW); // If this is a touch-enabled device we add extra + // empty mouseover listeners to the body's immediate children; + // only needed because of broken event delegation on iOS + // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html + + if ('ontouchstart' in document.documentElement) { + $(document.body).children().on('mouseover', null, $.noop); + } + + var complete = function complete() { + if (_this.config.animation) { + _this._fixTransition(); + } + + var prevHoverState = _this._hoverState; + _this._hoverState = null; + $(_this.element).trigger(_this.constructor.Event.SHOWN); + + if (prevHoverState === HoverState.OUT) { + _this._leave(null, _this); + } + }; + + if ($(this.tip).hasClass(ClassName$6.FADE)) { + var transitionDuration = Util.getTransitionDurationFromElement(this.tip); + $(this.tip).one(Util.TRANSITION_END, complete).emulateTransitionEnd(transitionDuration); + } else { + complete(); + } + } + }; + + _proto.hide = function hide(callback) { + var _this2 = this; + + var tip = this.getTipElement(); + var hideEvent = $.Event(this.constructor.Event.HIDE); + + var complete = function complete() { + if (_this2._hoverState !== HoverState.SHOW && tip.parentNode) { + tip.parentNode.removeChild(tip); + } + + _this2._cleanTipClass(); + + _this2.element.removeAttribute('aria-describedby'); + + $(_this2.element).trigger(_this2.constructor.Event.HIDDEN); + + if (_this2._popper !== null) { + _this2._popper.destroy(); + } + + if (callback) { + callback(); + } + }; + + $(this.element).trigger(hideEvent); + + if (hideEvent.isDefaultPrevented()) { + return; + } + + $(tip).removeClass(ClassName$6.SHOW); // If this is a touch-enabled device we remove the extra + // empty mouseover listeners we added for iOS support + + if ('ontouchstart' in document.documentElement) { + $(document.body).children().off('mouseover', null, $.noop); + } + + this._activeTrigger[Trigger.CLICK] = false; + this._activeTrigger[Trigger.FOCUS] = false; + this._activeTrigger[Trigger.HOVER] = false; + + if ($(this.tip).hasClass(ClassName$6.FADE)) { + var transitionDuration = Util.getTransitionDurationFromElement(tip); + $(tip).one(Util.TRANSITION_END, complete).emulateTransitionEnd(transitionDuration); + } else { + complete(); + } + + this._hoverState = ''; + }; + + _proto.update = function update() { + if (this._popper !== null) { + this._popper.scheduleUpdate(); + } + } // Protected + ; + + _proto.isWithContent = function isWithContent() { + return Boolean(this.getTitle()); + }; + + _proto.addAttachmentClass = function addAttachmentClass(attachment) { + $(this.getTipElement()).addClass(CLASS_PREFIX + "-" + attachment); + }; + + _proto.getTipElement = function getTipElement() { + this.tip = this.tip || $(this.config.template)[0]; + return this.tip; + }; + + _proto.setContent = function setContent() { + var tip = this.getTipElement(); + this.setElementContent($(tip.querySelectorAll(Selector$6.TOOLTIP_INNER)), this.getTitle()); + $(tip).removeClass(ClassName$6.FADE + " " + ClassName$6.SHOW); + }; + + _proto.setElementContent = function setElementContent($element, content) { + if (typeof content === 'object' && (content.nodeType || content.jquery)) { + // Content is a DOM node or a jQuery + if (this.config.html) { + if (!$(content).parent().is($element)) { + $element.empty().append(content); + } + } else { + $element.text($(content).text()); + } + + return; + } + + if (this.config.html) { + if (this.config.sanitize) { + content = sanitizeHtml(content, this.config.whiteList, this.config.sanitizeFn); + } + + $element.html(content); + } else { + $element.text(content); + } + }; + + _proto.getTitle = function getTitle() { + var title = this.element.getAttribute('data-original-title'); + + if (!title) { + title = typeof this.config.title === 'function' ? this.config.title.call(this.element) : this.config.title; + } + + return title; + } // Private + ; + + _proto._getPopperConfig = function _getPopperConfig(attachment) { + var _this3 = this; + + var defaultBsConfig = { + placement: attachment, + modifiers: { + offset: this._getOffset(), + flip: { + behavior: this.config.fallbackPlacement + }, + arrow: { + element: Selector$6.ARROW + }, + preventOverflow: { + boundariesElement: this.config.boundary + } + }, + onCreate: function onCreate(data) { + if (data.originalPlacement !== data.placement) { + _this3._handlePopperPlacementChange(data); + } + }, + onUpdate: function onUpdate(data) { + return _this3._handlePopperPlacementChange(data); + } + }; + return _objectSpread2({}, defaultBsConfig, {}, this.config.popperConfig); + }; + + _proto._getOffset = function _getOffset() { + var _this4 = this; + + var offset = {}; + + if (typeof this.config.offset === 'function') { + offset.fn = function (data) { + data.offsets = _objectSpread2({}, data.offsets, {}, _this4.config.offset(data.offsets, _this4.element) || {}); + return data; + }; + } else { + offset.offset = this.config.offset; + } + + return offset; + }; + + _proto._getContainer = function _getContainer() { + if (this.config.container === false) { + return document.body; + } + + if (Util.isElement(this.config.container)) { + return $(this.config.container); + } + + return $(document).find(this.config.container); + }; + + _proto._getAttachment = function _getAttachment(placement) { + return AttachmentMap$1[placement.toUpperCase()]; + }; + + _proto._setListeners = function _setListeners() { + var _this5 = this; + + var triggers = this.config.trigger.split(' '); + triggers.forEach(function (trigger) { + if (trigger === 'click') { + $(_this5.element).on(_this5.constructor.Event.CLICK, _this5.config.selector, function (event) { + return _this5.toggle(event); + }); + } else if (trigger !== Trigger.MANUAL) { + var eventIn = trigger === Trigger.HOVER ? _this5.constructor.Event.MOUSEENTER : _this5.constructor.Event.FOCUSIN; + var eventOut = trigger === Trigger.HOVER ? _this5.constructor.Event.MOUSELEAVE : _this5.constructor.Event.FOCUSOUT; + $(_this5.element).on(eventIn, _this5.config.selector, function (event) { + return _this5._enter(event); + }).on(eventOut, _this5.config.selector, function (event) { + return _this5._leave(event); + }); + } + }); + + this._hideModalHandler = function () { + if (_this5.element) { + _this5.hide(); + } + }; + + $(this.element).closest('.modal').on('hide.bs.modal', this._hideModalHandler); + + if (this.config.selector) { + this.config = _objectSpread2({}, this.config, { + trigger: 'manual', + selector: '' + }); + } else { + this._fixTitle(); + } + }; + + _proto._fixTitle = function _fixTitle() { + var titleType = typeof this.element.getAttribute('data-original-title'); + + if (this.element.getAttribute('title') || titleType !== 'string') { + this.element.setAttribute('data-original-title', this.element.getAttribute('title') || ''); + this.element.setAttribute('title', ''); + } + }; + + _proto._enter = function _enter(event, context) { + var dataKey = this.constructor.DATA_KEY; + context = context || $(event.currentTarget).data(dataKey); + + if (!context) { + context = new this.constructor(event.currentTarget, this._getDelegateConfig()); + $(event.currentTarget).data(dataKey, context); + } + + if (event) { + context._activeTrigger[event.type === 'focusin' ? Trigger.FOCUS : Trigger.HOVER] = true; + } + + if ($(context.getTipElement()).hasClass(ClassName$6.SHOW) || context._hoverState === HoverState.SHOW) { + context._hoverState = HoverState.SHOW; + return; + } + + clearTimeout(context._timeout); + context._hoverState = HoverState.SHOW; + + if (!context.config.delay || !context.config.delay.show) { + context.show(); + return; + } + + context._timeout = setTimeout(function () { + if (context._hoverState === HoverState.SHOW) { + context.show(); + } + }, context.config.delay.show); + }; + + _proto._leave = function _leave(event, context) { + var dataKey = this.constructor.DATA_KEY; + context = context || $(event.currentTarget).data(dataKey); + + if (!context) { + context = new this.constructor(event.currentTarget, this._getDelegateConfig()); + $(event.currentTarget).data(dataKey, context); + } + + if (event) { + context._activeTrigger[event.type === 'focusout' ? Trigger.FOCUS : Trigger.HOVER] = false; + } + + if (context._isWithActiveTrigger()) { + return; + } + + clearTimeout(context._timeout); + context._hoverState = HoverState.OUT; + + if (!context.config.delay || !context.config.delay.hide) { + context.hide(); + return; + } + + context._timeout = setTimeout(function () { + if (context._hoverState === HoverState.OUT) { + context.hide(); + } + }, context.config.delay.hide); + }; + + _proto._isWithActiveTrigger = function _isWithActiveTrigger() { + for (var trigger in this._activeTrigger) { + if (this._activeTrigger[trigger]) { + return true; + } + } + + return false; + }; + + _proto._getConfig = function _getConfig(config) { + var dataAttributes = $(this.element).data(); + Object.keys(dataAttributes).forEach(function (dataAttr) { + if (DISALLOWED_ATTRIBUTES.indexOf(dataAttr) !== -1) { + delete dataAttributes[dataAttr]; + } + }); + config = _objectSpread2({}, this.constructor.Default, {}, dataAttributes, {}, typeof config === 'object' && config ? config : {}); + + if (typeof config.delay === 'number') { + config.delay = { + show: config.delay, + hide: config.delay + }; + } + + if (typeof config.title === 'number') { + config.title = config.title.toString(); + } + + if (typeof config.content === 'number') { + config.content = config.content.toString(); + } + + Util.typeCheckConfig(NAME$6, config, this.constructor.DefaultType); + + if (config.sanitize) { + config.template = sanitizeHtml(config.template, config.whiteList, config.sanitizeFn); + } + + return config; + }; + + _proto._getDelegateConfig = function _getDelegateConfig() { + var config = {}; + + if (this.config) { + for (var key in this.config) { + if (this.constructor.Default[key] !== this.config[key]) { + config[key] = this.config[key]; + } + } + } + + return config; + }; + + _proto._cleanTipClass = function _cleanTipClass() { + var $tip = $(this.getTipElement()); + var tabClass = $tip.attr('class').match(BSCLS_PREFIX_REGEX); + + if (tabClass !== null && tabClass.length) { + $tip.removeClass(tabClass.join('')); + } + }; + + _proto._handlePopperPlacementChange = function _handlePopperPlacementChange(popperData) { + var popperInstance = popperData.instance; + this.tip = popperInstance.popper; + + this._cleanTipClass(); + + this.addAttachmentClass(this._getAttachment(popperData.placement)); + }; + + _proto._fixTransition = function _fixTransition() { + var tip = this.getTipElement(); + var initConfigAnimation = this.config.animation; + + if (tip.getAttribute('x-placement') !== null) { + return; + } + + $(tip).removeClass(ClassName$6.FADE); + this.config.animation = false; + this.hide(); + this.show(); + this.config.animation = initConfigAnimation; + } // Static + ; + + Tooltip._jQueryInterface = function _jQueryInterface(config) { + return this.each(function () { + var data = $(this).data(DATA_KEY$6); + + var _config = typeof config === 'object' && config; + + if (!data && /dispose|hide/.test(config)) { + return; + } + + if (!data) { + data = new Tooltip(this, _config); + $(this).data(DATA_KEY$6, data); + } + + if (typeof config === 'string') { + if (typeof data[config] === 'undefined') { + throw new TypeError("No method named \"" + config + "\""); + } + + data[config](); + } + }); + }; + + _createClass(Tooltip, null, [{ + key: "VERSION", + get: function get() { + return VERSION$6; + } + }, { + key: "Default", + get: function get() { + return Default$4; + } + }, { + key: "NAME", + get: function get() { + return NAME$6; + } + }, { + key: "DATA_KEY", + get: function get() { + return DATA_KEY$6; + } + }, { + key: "Event", + get: function get() { + return Event$6; + } + }, { + key: "EVENT_KEY", + get: function get() { + return EVENT_KEY$6; + } + }, { + key: "DefaultType", + get: function get() { + return DefaultType$4; + } + }]); + + return Tooltip; + }(); + /** + * ------------------------------------------------------------------------ + * jQuery + * ------------------------------------------------------------------------ + */ + + + $.fn[NAME$6] = Tooltip._jQueryInterface; + $.fn[NAME$6].Constructor = Tooltip; + + $.fn[NAME$6].noConflict = function () { + $.fn[NAME$6] = JQUERY_NO_CONFLICT$6; + return Tooltip._jQueryInterface; + }; + + /** + * ------------------------------------------------------------------------ + * Constants + * ------------------------------------------------------------------------ + */ + + var NAME$7 = 'popover'; + var VERSION$7 = '4.4.1'; + var DATA_KEY$7 = 'bs.popover'; + var EVENT_KEY$7 = "." + DATA_KEY$7; + var JQUERY_NO_CONFLICT$7 = $.fn[NAME$7]; + var CLASS_PREFIX$1 = 'bs-popover'; + var BSCLS_PREFIX_REGEX$1 = new RegExp("(^|\\s)" + CLASS_PREFIX$1 + "\\S+", 'g'); + + var Default$5 = _objectSpread2({}, Tooltip.Default, { + placement: 'right', + trigger: 'click', + content: '', + template: '' + }); + + var DefaultType$5 = _objectSpread2({}, Tooltip.DefaultType, { + content: '(string|element|function)' + }); + + var ClassName$7 = { + FADE: 'fade', + SHOW: 'show' + }; + var Selector$7 = { + TITLE: '.popover-header', + CONTENT: '.popover-body' + }; + var Event$7 = { + HIDE: "hide" + EVENT_KEY$7, + HIDDEN: "hidden" + EVENT_KEY$7, + SHOW: "show" + EVENT_KEY$7, + SHOWN: "shown" + EVENT_KEY$7, + INSERTED: "inserted" + EVENT_KEY$7, + CLICK: "click" + EVENT_KEY$7, + FOCUSIN: "focusin" + EVENT_KEY$7, + FOCUSOUT: "focusout" + EVENT_KEY$7, + MOUSEENTER: "mouseenter" + EVENT_KEY$7, + MOUSELEAVE: "mouseleave" + EVENT_KEY$7 + }; + /** + * ------------------------------------------------------------------------ + * Class Definition + * ------------------------------------------------------------------------ + */ + + var Popover = + /*#__PURE__*/ + function (_Tooltip) { + _inheritsLoose(Popover, _Tooltip); + + function Popover() { + return _Tooltip.apply(this, arguments) || this; + } + + var _proto = Popover.prototype; + + // Overrides + _proto.isWithContent = function isWithContent() { + return this.getTitle() || this._getContent(); + }; + + _proto.addAttachmentClass = function addAttachmentClass(attachment) { + $(this.getTipElement()).addClass(CLASS_PREFIX$1 + "-" + attachment); + }; + + _proto.getTipElement = function getTipElement() { + this.tip = this.tip || $(this.config.template)[0]; + return this.tip; + }; + + _proto.setContent = function setContent() { + var $tip = $(this.getTipElement()); // We use append for html objects to maintain js events + + this.setElementContent($tip.find(Selector$7.TITLE), this.getTitle()); + + var content = this._getContent(); + + if (typeof content === 'function') { + content = content.call(this.element); + } + + this.setElementContent($tip.find(Selector$7.CONTENT), content); + $tip.removeClass(ClassName$7.FADE + " " + ClassName$7.SHOW); + } // Private + ; + + _proto._getContent = function _getContent() { + return this.element.getAttribute('data-content') || this.config.content; + }; + + _proto._cleanTipClass = function _cleanTipClass() { + var $tip = $(this.getTipElement()); + var tabClass = $tip.attr('class').match(BSCLS_PREFIX_REGEX$1); + + if (tabClass !== null && tabClass.length > 0) { + $tip.removeClass(tabClass.join('')); + } + } // Static + ; + + Popover._jQueryInterface = function _jQueryInterface(config) { + return this.each(function () { + var data = $(this).data(DATA_KEY$7); + + var _config = typeof config === 'object' ? config : null; + + if (!data && /dispose|hide/.test(config)) { + return; + } + + if (!data) { + data = new Popover(this, _config); + $(this).data(DATA_KEY$7, data); + } + + if (typeof config === 'string') { + if (typeof data[config] === 'undefined') { + throw new TypeError("No method named \"" + config + "\""); + } + + data[config](); + } + }); + }; + + _createClass(Popover, null, [{ + key: "VERSION", + // Getters + get: function get() { + return VERSION$7; + } + }, { + key: "Default", + get: function get() { + return Default$5; + } + }, { + key: "NAME", + get: function get() { + return NAME$7; + } + }, { + key: "DATA_KEY", + get: function get() { + return DATA_KEY$7; + } + }, { + key: "Event", + get: function get() { + return Event$7; + } + }, { + key: "EVENT_KEY", + get: function get() { + return EVENT_KEY$7; + } + }, { + key: "DefaultType", + get: function get() { + return DefaultType$5; + } + }]); + + return Popover; + }(Tooltip); + /** + * ------------------------------------------------------------------------ + * jQuery + * ------------------------------------------------------------------------ + */ + + + $.fn[NAME$7] = Popover._jQueryInterface; + $.fn[NAME$7].Constructor = Popover; + + $.fn[NAME$7].noConflict = function () { + $.fn[NAME$7] = JQUERY_NO_CONFLICT$7; + return Popover._jQueryInterface; + }; + + /** + * ------------------------------------------------------------------------ + * Constants + * ------------------------------------------------------------------------ + */ + + var NAME$8 = 'scrollspy'; + var VERSION$8 = '4.4.1'; + var DATA_KEY$8 = 'bs.scrollspy'; + var EVENT_KEY$8 = "." + DATA_KEY$8; + var DATA_API_KEY$6 = '.data-api'; + var JQUERY_NO_CONFLICT$8 = $.fn[NAME$8]; + var Default$6 = { + offset: 10, + method: 'auto', + target: '' + }; + var DefaultType$6 = { + offset: 'number', + method: 'string', + target: '(string|element)' + }; + var Event$8 = { + ACTIVATE: "activate" + EVENT_KEY$8, + SCROLL: "scroll" + EVENT_KEY$8, + LOAD_DATA_API: "load" + EVENT_KEY$8 + DATA_API_KEY$6 + }; + var ClassName$8 = { + DROPDOWN_ITEM: 'dropdown-item', + DROPDOWN_MENU: 'dropdown-menu', + ACTIVE: 'active' + }; + var Selector$8 = { + DATA_SPY: '[data-spy="scroll"]', + ACTIVE: '.active', + NAV_LIST_GROUP: '.nav, .list-group', + NAV_LINKS: '.nav-link', + NAV_ITEMS: '.nav-item', + LIST_ITEMS: '.list-group-item', + DROPDOWN: '.dropdown', + DROPDOWN_ITEMS: '.dropdown-item', + DROPDOWN_TOGGLE: '.dropdown-toggle' + }; + var OffsetMethod = { + OFFSET: 'offset', + POSITION: 'position' + }; + /** + * ------------------------------------------------------------------------ + * Class Definition + * ------------------------------------------------------------------------ + */ + + var ScrollSpy = + /*#__PURE__*/ + function () { + function ScrollSpy(element, config) { + var _this = this; + + this._element = element; + this._scrollElement = element.tagName === 'BODY' ? window : element; + this._config = this._getConfig(config); + this._selector = this._config.target + " " + Selector$8.NAV_LINKS + "," + (this._config.target + " " + Selector$8.LIST_ITEMS + ",") + (this._config.target + " " + Selector$8.DROPDOWN_ITEMS); + this._offsets = []; + this._targets = []; + this._activeTarget = null; + this._scrollHeight = 0; + $(this._scrollElement).on(Event$8.SCROLL, function (event) { + return _this._process(event); + }); + this.refresh(); + + this._process(); + } // Getters + + + var _proto = ScrollSpy.prototype; + + // Public + _proto.refresh = function refresh() { + var _this2 = this; + + var autoMethod = this._scrollElement === this._scrollElement.window ? OffsetMethod.OFFSET : OffsetMethod.POSITION; + var offsetMethod = this._config.method === 'auto' ? autoMethod : this._config.method; + var offsetBase = offsetMethod === OffsetMethod.POSITION ? this._getScrollTop() : 0; + this._offsets = []; + this._targets = []; + this._scrollHeight = this._getScrollHeight(); + var targets = [].slice.call(document.querySelectorAll(this._selector)); + targets.map(function (element) { + var target; + var targetSelector = Util.getSelectorFromElement(element); + + if (targetSelector) { + target = document.querySelector(targetSelector); + } + + if (target) { + var targetBCR = target.getBoundingClientRect(); + + if (targetBCR.width || targetBCR.height) { + // TODO (fat): remove sketch reliance on jQuery position/offset + return [$(target)[offsetMethod]().top + offsetBase, targetSelector]; + } + } + + return null; + }).filter(function (item) { + return item; + }).sort(function (a, b) { + return a[0] - b[0]; + }).forEach(function (item) { + _this2._offsets.push(item[0]); + + _this2._targets.push(item[1]); + }); + }; + + _proto.dispose = function dispose() { + $.removeData(this._element, DATA_KEY$8); + $(this._scrollElement).off(EVENT_KEY$8); + this._element = null; + this._scrollElement = null; + this._config = null; + this._selector = null; + this._offsets = null; + this._targets = null; + this._activeTarget = null; + this._scrollHeight = null; + } // Private + ; + + _proto._getConfig = function _getConfig(config) { + config = _objectSpread2({}, Default$6, {}, typeof config === 'object' && config ? config : {}); + + if (typeof config.target !== 'string') { + var id = $(config.target).attr('id'); + + if (!id) { + id = Util.getUID(NAME$8); + $(config.target).attr('id', id); + } + + config.target = "#" + id; + } + + Util.typeCheckConfig(NAME$8, config, DefaultType$6); + return config; + }; + + _proto._getScrollTop = function _getScrollTop() { + return this._scrollElement === window ? this._scrollElement.pageYOffset : this._scrollElement.scrollTop; + }; + + _proto._getScrollHeight = function _getScrollHeight() { + return this._scrollElement.scrollHeight || Math.max(document.body.scrollHeight, document.documentElement.scrollHeight); + }; + + _proto._getOffsetHeight = function _getOffsetHeight() { + return this._scrollElement === window ? window.innerHeight : this._scrollElement.getBoundingClientRect().height; + }; + + _proto._process = function _process() { + var scrollTop = this._getScrollTop() + this._config.offset; + + var scrollHeight = this._getScrollHeight(); + + var maxScroll = this._config.offset + scrollHeight - this._getOffsetHeight(); + + if (this._scrollHeight !== scrollHeight) { + this.refresh(); + } + + if (scrollTop >= maxScroll) { + var target = this._targets[this._targets.length - 1]; + + if (this._activeTarget !== target) { + this._activate(target); + } + + return; + } + + if (this._activeTarget && scrollTop < this._offsets[0] && this._offsets[0] > 0) { + this._activeTarget = null; + + this._clear(); + + return; + } + + var offsetLength = this._offsets.length; + + for (var i = offsetLength; i--;) { + var isActiveTarget = this._activeTarget !== this._targets[i] && scrollTop >= this._offsets[i] && (typeof this._offsets[i + 1] === 'undefined' || scrollTop < this._offsets[i + 1]); + + if (isActiveTarget) { + this._activate(this._targets[i]); + } + } + }; + + _proto._activate = function _activate(target) { + this._activeTarget = target; + + this._clear(); + + var queries = this._selector.split(',').map(function (selector) { + return selector + "[data-target=\"" + target + "\"]," + selector + "[href=\"" + target + "\"]"; + }); + + var $link = $([].slice.call(document.querySelectorAll(queries.join(',')))); + + if ($link.hasClass(ClassName$8.DROPDOWN_ITEM)) { + $link.closest(Selector$8.DROPDOWN).find(Selector$8.DROPDOWN_TOGGLE).addClass(ClassName$8.ACTIVE); + $link.addClass(ClassName$8.ACTIVE); + } else { + // Set triggered link as active + $link.addClass(ClassName$8.ACTIVE); // Set triggered links parents as active + // With both
+
+
+ + + + + diff --git a/flask__webservers/users/main_with_db.py b/flask__webservers/users/main_with_db.py new file mode 100644 index 000000000..7f8fb28af --- /dev/null +++ b/flask__webservers/users/main_with_db.py @@ -0,0 +1,216 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +# SOURCE: https://www.geeksforgeeks.org/how-to-add-authentication-to-your-app-with-flask-login/ + + +import os + +from typing import Optional + +# pip install flask==3.0.0 +import flask + +# pip install flask-login==0.6.2 +import flask_login + +# pip install flask-sqlalchemy==3.1.1 +# TODO: Bug fix for flask-login==0.6.2 - flask-sqlalchemy installed/updated Werkzeug +# pip install Werkzeug==2.3.7 +from flask_sqlalchemy import SQLAlchemy + + +# TODO: Убрать дублирование кода в шаблонах +# TODO: Не хранить пароль в чистом виде +# TODO: Сделать вариант примера с peewee + + +app = flask.Flask(__name__) +app.secret_key = "super secret string" # Change this! +app.config["SQLALCHEMY_DATABASE_URI"] = os.environ.get("DATABASE_URI", "sqlite:///db.sqlite") + +db = SQLAlchemy() + +login_manager = flask_login.LoginManager() +login_manager.init_app(app) + + +class User(db.Model, flask_login.UserMixin): + id = db.Column(db.Integer, primary_key=True) + username = db.Column(db.String(250), unique=True, nullable=False) + password = db.Column(db.String(250), nullable=False) + + @classmethod + def get_by(cls, username: str) -> Optional["User"]: + return cls.query.filter_by( + username=username + ).first() + + +db.init_app(app) + +with app.app_context(): + db.create_all() + + +@login_manager.user_loader +def loader_user(user_id: int): + return db.session.get(User, user_id) + + +@app.route("/") +def index(): + return flask.render_template_string(""" + + + + + + + Home + + + + + {% if current_user.is_authenticated %} +

You are logged as {{ current_user.username }}

+ {% else %} +

You are not logged in

+ {% endif %} + + + """) + + +@app.route('/register', methods=["GET", "POST"]) +def register(): + if flask.request.method == "POST": + username = flask.request.form.get("username") + if User.get_by(username): + return "There is already a user with this nickname" + + password = flask.request.form.get("password") + + user = User( + username=username, + password=password, + ) + db.session.add(user) + db.session.commit() + + return flask.redirect(flask.url_for("login")) + + return flask.render_template_string(""" + + + + + + + Sign Up + + + + +

Create an account

+
+ + + + + +
+ + + """) + + +@app.route("/login", methods=["GET", "POST"]) +def login(): + if flask.request.method == "POST": + username = flask.request.form.get("username") + password = flask.request.form.get("password") + + user = User.get_by(username) + if user and user.password == password: + flask_login.login_user(user) + return flask.redirect(flask.url_for("index")) + + return "Bad login" + + return flask.render_template_string(""" + + + + + + + Login + + + + +

Login to your account

+
+ + + + + +
+ + + """) + + +@app.route("/logout") +def logout(): + flask_login.logout_user() + return flask.redirect(flask.url_for("index")) + + +@app.route("/protected") +@flask_login.login_required +def protected() -> str: + return f"Logged in as: {flask_login.current_user.username}" + + +if __name__ == "__main__": + app.debug = True + app.run(port=10101) diff --git a/flask__webservers/users/main_without_db.py b/flask__webservers/users/main_without_db.py new file mode 100644 index 000000000..927caa1fd --- /dev/null +++ b/flask__webservers/users/main_without_db.py @@ -0,0 +1,140 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import os + +# pip install flask==3.0.0 +import flask + +# pip install flask-login==0.6.2 +import flask_login + + +LOGIN = os.environ.get("ADMIN_LOGIN", "foo@bar.example") +PASSWORD = os.environ.get("ADMIN_PASSWORD", "secret") + +# Our mock database. +USERS = { + LOGIN: { + "password": PASSWORD, + }, +} + + +app = flask.Flask(__name__) +app.secret_key = "super secret string" # Change this! + +login_manager = flask_login.LoginManager() +login_manager.init_app(app) + + +class User(flask_login.UserMixin): + @classmethod + def create(cls, login: str) -> "User": + user = User() + user.id = login + return user + + +# Если авторизован +@login_manager.user_loader +def user_loader(email): + if email not in USERS: + return + + return User.create(email) + + +# Если не авторизован +@login_manager.request_loader +def request_loader(request): + email = request.form.get("email") + if email not in USERS: + return + + return User.create(email) + + +@app.route("/") +def index(): + return flask.render_template_string( + """ + + + + + + + Home + + + + + {% if current_user.is_authenticated %} +

You are logged as {{ current_user.id }}

+ Protected + {% else %} +

You are not logged in

+ {% endif %} + + + """ + ) + + +@app.route("/login", methods=["GET", "POST"]) +def login(): + if flask.request.method == "GET": + return """ +
+ + + +
+ """ + + email = flask.request.form["email"] + if email in USERS and flask.request.form["password"] == USERS[email]["password"]: + user = User.create(email) + flask_login.login_user(user) + return flask.redirect(flask.url_for("index")) + + return "Bad login" + + +@app.route("/protected") +@flask_login.login_required +def protected() -> str: + return f"Logged in as: {flask_login.current_user.id}" + + +@app.route("/logout") +@flask_login.login_required +def logout(): + flask_login.logout_user() + return flask.redirect(flask.url_for("index")) + + +@login_manager.unauthorized_handler +def unauthorized_handler(): + return "Unauthorized", 401 + + +if __name__ == "__main__": + app.debug = True + app.run(port=10101) diff --git a/flask__websocket/ajax_vs_websocket/_static/socket.io.js b/flask__websocket/ajax_vs_websocket/_static/socket.io.js deleted file mode 100644 index d3e395011..000000000 --- a/flask__websocket/ajax_vs_websocket/_static/socket.io.js +++ /dev/null @@ -1,9 +0,0 @@ -/*! - * Socket.IO v2.2.0 - * (c) 2014-2018 Guillermo Rauch - * Released under the MIT License. - */ -!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.io=e():t.io=e()}(this,function(){return function(t){function e(r){if(n[r])return n[r].exports;var o=n[r]={exports:{},id:r,loaded:!1};return t[r].call(o.exports,o,o.exports,e),o.loaded=!0,o.exports}var n={};return e.m=t,e.c=n,e.p="",e(0)}([function(t,e,n){"use strict";function r(t,e){"object"===("undefined"==typeof t?"undefined":o(t))&&(e=t,t=void 0),e=e||{};var n,r=i(t),s=r.source,u=r.id,h=r.path,f=p[u]&&h in p[u].nsps,l=e.forceNew||e["force new connection"]||!1===e.multiplex||f;return l?(c("ignoring socket cache for %s",s),n=a(s,e)):(p[u]||(c("new io instance for %s",s),p[u]=a(s,e)),n=p[u]),r.query&&!e.query&&(e.query=r.query),n.socket(r.path,e)}var o="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},i=n(1),s=n(7),a=n(12),c=n(3)("socket.io-client");t.exports=e=r;var p=e.managers={};e.protocol=s.protocol,e.connect=r,e.Manager=n(12),e.Socket=n(36)},function(t,e,n){"use strict";function r(t,e){var n=t;e=e||"undefined"!=typeof location&&location,null==t&&(t=e.protocol+"//"+e.host),"string"==typeof t&&("/"===t.charAt(0)&&(t="/"===t.charAt(1)?e.protocol+t:e.host+t),/^(https?|wss?):\/\//.test(t)||(i("protocol-less url %s",t),t="undefined"!=typeof e?e.protocol+"//"+t:"https://"+t),i("parse %s",t),n=o(t)),n.port||(/^(http|ws)$/.test(n.protocol)?n.port="80":/^(http|ws)s$/.test(n.protocol)&&(n.port="443")),n.path=n.path||"/";var r=n.host.indexOf(":")!==-1,s=r?"["+n.host+"]":n.host;return n.id=n.protocol+"://"+s+":"+n.port,n.href=n.protocol+"://"+s+(e&&e.port===n.port?"":":"+n.port),n}var o=n(2),i=n(3)("socket.io-client:url");t.exports=r},function(t,e){var n=/^(?:(?![^:@]+:[^:@\/]*@)(http|https|ws|wss):\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?((?:[a-f0-9]{0,4}:){2,7}[a-f0-9]{0,4}|[^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/,r=["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"];t.exports=function(t){var e=t,o=t.indexOf("["),i=t.indexOf("]");o!=-1&&i!=-1&&(t=t.substring(0,o)+t.substring(o,i).replace(/:/g,";")+t.substring(i,t.length));for(var s=n.exec(t||""),a={},c=14;c--;)a[r[c]]=s[c]||"";return o!=-1&&i!=-1&&(a.source=e,a.host=a.host.substring(1,a.host.length-1).replace(/;/g,":"),a.authority=a.authority.replace("[","").replace("]","").replace(/;/g,":"),a.ipv6uri=!0),a}},function(t,e,n){(function(r){function o(){return!("undefined"==typeof window||!window.process||"renderer"!==window.process.type)||("undefined"==typeof navigator||!navigator.userAgent||!navigator.userAgent.toLowerCase().match(/(edge|trident)\/(\d+)/))&&("undefined"!=typeof document&&document.documentElement&&document.documentElement.style&&document.documentElement.style.WebkitAppearance||"undefined"!=typeof window&&window.console&&(window.console.firebug||window.console.exception&&window.console.table)||"undefined"!=typeof navigator&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/)&&parseInt(RegExp.$1,10)>=31||"undefined"!=typeof navigator&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/))}function i(t){var n=this.useColors;if(t[0]=(n?"%c":"")+this.namespace+(n?" %c":" ")+t[0]+(n?"%c ":" ")+"+"+e.humanize(this.diff),n){var r="color: "+this.color;t.splice(1,0,r,"color: inherit");var o=0,i=0;t[0].replace(/%[a-zA-Z%]/g,function(t){"%%"!==t&&(o++,"%c"===t&&(i=o))}),t.splice(i,0,r)}}function s(){return"object"==typeof console&&console.log&&Function.prototype.apply.call(console.log,console,arguments)}function a(t){try{null==t?e.storage.removeItem("debug"):e.storage.debug=t}catch(n){}}function c(){var t;try{t=e.storage.debug}catch(n){}return!t&&"undefined"!=typeof r&&"env"in r&&(t=r.env.DEBUG),t}function p(){try{return window.localStorage}catch(t){}}e=t.exports=n(5),e.log=s,e.formatArgs=i,e.save=a,e.load=c,e.useColors=o,e.storage="undefined"!=typeof chrome&&"undefined"!=typeof chrome.storage?chrome.storage.local:p(),e.colors=["#0000CC","#0000FF","#0033CC","#0033FF","#0066CC","#0066FF","#0099CC","#0099FF","#00CC00","#00CC33","#00CC66","#00CC99","#00CCCC","#00CCFF","#3300CC","#3300FF","#3333CC","#3333FF","#3366CC","#3366FF","#3399CC","#3399FF","#33CC00","#33CC33","#33CC66","#33CC99","#33CCCC","#33CCFF","#6600CC","#6600FF","#6633CC","#6633FF","#66CC00","#66CC33","#9900CC","#9900FF","#9933CC","#9933FF","#99CC00","#99CC33","#CC0000","#CC0033","#CC0066","#CC0099","#CC00CC","#CC00FF","#CC3300","#CC3333","#CC3366","#CC3399","#CC33CC","#CC33FF","#CC6600","#CC6633","#CC9900","#CC9933","#CCCC00","#CCCC33","#FF0000","#FF0033","#FF0066","#FF0099","#FF00CC","#FF00FF","#FF3300","#FF3333","#FF3366","#FF3399","#FF33CC","#FF33FF","#FF6600","#FF6633","#FF9900","#FF9933","#FFCC00","#FFCC33"],e.formatters.j=function(t){try{return JSON.stringify(t)}catch(e){return"[UnexpectedJSONParseError]: "+e.message}},e.enable(c())}).call(e,n(4))},function(t,e){function n(){throw new Error("setTimeout has not been defined")}function r(){throw new Error("clearTimeout has not been defined")}function o(t){if(u===setTimeout)return setTimeout(t,0);if((u===n||!u)&&setTimeout)return u=setTimeout,setTimeout(t,0);try{return u(t,0)}catch(e){try{return u.call(null,t,0)}catch(e){return u.call(this,t,0)}}}function i(t){if(h===clearTimeout)return clearTimeout(t);if((h===r||!h)&&clearTimeout)return h=clearTimeout,clearTimeout(t);try{return h(t)}catch(e){try{return h.call(null,t)}catch(e){return h.call(this,t)}}}function s(){y&&l&&(y=!1,l.length?d=l.concat(d):m=-1,d.length&&a())}function a(){if(!y){var t=o(s);y=!0;for(var e=d.length;e;){for(l=d,d=[];++m1)for(var n=1;n100)){var e=/^((?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|years?|yrs?|y)?$/i.exec(t);if(e){var n=parseFloat(e[1]),r=(e[2]||"ms").toLowerCase();switch(r){case"years":case"year":case"yrs":case"yr":case"y":return n*u;case"days":case"day":case"d":return n*p;case"hours":case"hour":case"hrs":case"hr":case"h":return n*c;case"minutes":case"minute":case"mins":case"min":case"m":return n*a;case"seconds":case"second":case"secs":case"sec":case"s":return n*s;case"milliseconds":case"millisecond":case"msecs":case"msec":case"ms":return n;default:return}}}}function r(t){return t>=p?Math.round(t/p)+"d":t>=c?Math.round(t/c)+"h":t>=a?Math.round(t/a)+"m":t>=s?Math.round(t/s)+"s":t+"ms"}function o(t){return i(t,p,"day")||i(t,c,"hour")||i(t,a,"minute")||i(t,s,"second")||t+" ms"}function i(t,e,n){if(!(t0)return n(t);if("number"===i&&isNaN(t)===!1)return e["long"]?o(t):r(t);throw new Error("val is not a non-empty string or a valid number. val="+JSON.stringify(t))}},function(t,e,n){function r(){}function o(t){var n=""+t.type;if(e.BINARY_EVENT!==t.type&&e.BINARY_ACK!==t.type||(n+=t.attachments+"-"),t.nsp&&"/"!==t.nsp&&(n+=t.nsp+","),null!=t.id&&(n+=t.id),null!=t.data){var r=i(t.data);if(r===!1)return g;n+=r}return f("encoded %j as %s",t,n),n}function i(t){try{return JSON.stringify(t)}catch(e){return!1}}function s(t,e){function n(t){var n=d.deconstructPacket(t),r=o(n.packet),i=n.buffers;i.unshift(r),e(i)}d.removeBlobs(t,n)}function a(){this.reconstructor=null}function c(t){var n=0,r={type:Number(t.charAt(0))};if(null==e.types[r.type])return h("unknown packet type "+r.type);if(e.BINARY_EVENT===r.type||e.BINARY_ACK===r.type){for(var o="";"-"!==t.charAt(++n)&&(o+=t.charAt(n),n!=t.length););if(o!=Number(o)||"-"!==t.charAt(n))throw new Error("Illegal attachments");r.attachments=Number(o)}if("/"===t.charAt(n+1))for(r.nsp="";++n;){var i=t.charAt(n);if(","===i)break;if(r.nsp+=i,n===t.length)break}else r.nsp="/";var s=t.charAt(n+1);if(""!==s&&Number(s)==s){for(r.id="";++n;){var i=t.charAt(n);if(null==i||Number(i)!=i){--n;break}if(r.id+=t.charAt(n),n===t.length)break}r.id=Number(r.id)}if(t.charAt(++n)){var a=p(t.substr(n)),c=a!==!1&&(r.type===e.ERROR||y(a));if(!c)return h("invalid payload");r.data=a}return f("decoded %s as %j",t,r),r}function p(t){try{return JSON.parse(t)}catch(e){return!1}}function u(t){this.reconPack=t,this.buffers=[]}function h(t){return{type:e.ERROR,data:"parser error: "+t}}var f=n(3)("socket.io-parser"),l=n(8),d=n(9),y=n(10),m=n(11);e.protocol=4,e.types=["CONNECT","DISCONNECT","EVENT","ACK","ERROR","BINARY_EVENT","BINARY_ACK"],e.CONNECT=0,e.DISCONNECT=1,e.EVENT=2,e.ACK=3,e.ERROR=4,e.BINARY_EVENT=5,e.BINARY_ACK=6,e.Encoder=r,e.Decoder=a;var g=e.ERROR+'"encode error"';r.prototype.encode=function(t,n){if(f("encoding packet %j",t),e.BINARY_EVENT===t.type||e.BINARY_ACK===t.type)s(t,n);else{var r=o(t);n([r])}},l(a.prototype),a.prototype.add=function(t){var n;if("string"==typeof t)n=c(t),e.BINARY_EVENT===n.type||e.BINARY_ACK===n.type?(this.reconstructor=new u(n),0===this.reconstructor.reconPack.attachments&&this.emit("decoded",n)):this.emit("decoded",n);else{if(!m(t)&&!t.base64)throw new Error("Unknown type: "+t);if(!this.reconstructor)throw new Error("got binary data when not reconstructing a packet");n=this.reconstructor.takeBinaryData(t),n&&(this.reconstructor=null,this.emit("decoded",n))}},a.prototype.destroy=function(){this.reconstructor&&this.reconstructor.finishedReconstruction()},u.prototype.takeBinaryData=function(t){if(this.buffers.push(t),this.buffers.length===this.reconPack.attachments){var e=d.reconstructPacket(this.reconPack,this.buffers);return this.finishedReconstruction(),e}return null},u.prototype.finishedReconstruction=function(){this.reconPack=null,this.buffers=[]}},function(t,e,n){function r(t){if(t)return o(t)}function o(t){for(var e in r.prototype)t[e]=r.prototype[e];return t}t.exports=r,r.prototype.on=r.prototype.addEventListener=function(t,e){return this._callbacks=this._callbacks||{},(this._callbacks["$"+t]=this._callbacks["$"+t]||[]).push(e),this},r.prototype.once=function(t,e){function n(){this.off(t,n),e.apply(this,arguments)}return n.fn=e,this.on(t,n),this},r.prototype.off=r.prototype.removeListener=r.prototype.removeAllListeners=r.prototype.removeEventListener=function(t,e){if(this._callbacks=this._callbacks||{},0==arguments.length)return this._callbacks={},this;var n=this._callbacks["$"+t];if(!n)return this;if(1==arguments.length)return delete this._callbacks["$"+t],this;for(var r,o=0;o0&&!this.encoding){var t=this.packetBuffer.shift();this.packet(t)}},r.prototype.cleanup=function(){h("cleanup");for(var t=this.subs.length,e=0;e=this._reconnectionAttempts)h("reconnect failed"),this.backoff.reset(),this.emitAll("reconnect_failed"),this.reconnecting=!1;else{var e=this.backoff.duration();h("will wait %dms before reconnect attempt",e),this.reconnecting=!0;var n=setTimeout(function(){t.skipReconnect||(h("attempting reconnect"),t.emitAll("reconnect_attempt",t.backoff.attempts),t.emitAll("reconnecting",t.backoff.attempts),t.skipReconnect||t.open(function(e){e?(h("reconnect attempt error"),t.reconnecting=!1,t.reconnect(),t.emitAll("reconnect_error",e.data)):(h("reconnect success"),t.onreconnect())}))},e);this.subs.push({destroy:function(){clearTimeout(n)}})}},r.prototype.onreconnect=function(){var t=this.backoff.attempts;this.reconnecting=!1,this.backoff.reset(),this.updateSocketIds(),this.emitAll("reconnect",t)}},function(t,e,n){t.exports=n(14),t.exports.parser=n(21)},function(t,e,n){function r(t,e){return this instanceof r?(e=e||{},t&&"object"==typeof t&&(e=t,t=null),t?(t=u(t),e.hostname=t.host,e.secure="https"===t.protocol||"wss"===t.protocol,e.port=t.port,t.query&&(e.query=t.query)):e.host&&(e.hostname=u(e.host).host),this.secure=null!=e.secure?e.secure:"undefined"!=typeof location&&"https:"===location.protocol,e.hostname&&!e.port&&(e.port=this.secure?"443":"80"),this.agent=e.agent||!1,this.hostname=e.hostname||("undefined"!=typeof location?location.hostname:"localhost"),this.port=e.port||("undefined"!=typeof location&&location.port?location.port:this.secure?443:80),this.query=e.query||{},"string"==typeof this.query&&(this.query=h.decode(this.query)),this.upgrade=!1!==e.upgrade,this.path=(e.path||"/engine.io").replace(/\/$/,"")+"/",this.forceJSONP=!!e.forceJSONP,this.jsonp=!1!==e.jsonp,this.forceBase64=!!e.forceBase64,this.enablesXDR=!!e.enablesXDR,this.timestampParam=e.timestampParam||"t",this.timestampRequests=e.timestampRequests,this.transports=e.transports||["polling","websocket"],this.transportOptions=e.transportOptions||{},this.readyState="",this.writeBuffer=[],this.prevBufferLen=0,this.policyPort=e.policyPort||843,this.rememberUpgrade=e.rememberUpgrade||!1,this.binaryType=null,this.onlyBinaryUpgrades=e.onlyBinaryUpgrades,this.perMessageDeflate=!1!==e.perMessageDeflate&&(e.perMessageDeflate||{}),!0===this.perMessageDeflate&&(this.perMessageDeflate={}),this.perMessageDeflate&&null==this.perMessageDeflate.threshold&&(this.perMessageDeflate.threshold=1024),this.pfx=e.pfx||null,this.key=e.key||null,this.passphrase=e.passphrase||null,this.cert=e.cert||null,this.ca=e.ca||null,this.ciphers=e.ciphers||null,this.rejectUnauthorized=void 0===e.rejectUnauthorized||e.rejectUnauthorized,this.forceNode=!!e.forceNode,this.isReactNative="undefined"!=typeof navigator&&"string"==typeof navigator.product&&"reactnative"===navigator.product.toLowerCase(),("undefined"==typeof self||this.isReactNative)&&(e.extraHeaders&&Object.keys(e.extraHeaders).length>0&&(this.extraHeaders=e.extraHeaders),e.localAddress&&(this.localAddress=e.localAddress)),this.id=null,this.upgrades=null,this.pingInterval=null,this.pingTimeout=null,this.pingIntervalTimer=null,this.pingTimeoutTimer=null,void this.open()):new r(t,e)}function o(t){var e={};for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n]);return e}var i=n(15),s=n(8),a=n(3)("engine.io-client:socket"),c=n(35),p=n(21),u=n(2),h=n(29);t.exports=r,r.priorWebsocketSuccess=!1,s(r.prototype),r.protocol=p.protocol,r.Socket=r,r.Transport=n(20),r.transports=n(15),r.parser=n(21),r.prototype.createTransport=function(t){a('creating transport "%s"',t);var e=o(this.query);e.EIO=p.protocol,e.transport=t;var n=this.transportOptions[t]||{};this.id&&(e.sid=this.id);var r=new i[t]({query:e,socket:this,agent:n.agent||this.agent,hostname:n.hostname||this.hostname,port:n.port||this.port,secure:n.secure||this.secure,path:n.path||this.path,forceJSONP:n.forceJSONP||this.forceJSONP,jsonp:n.jsonp||this.jsonp,forceBase64:n.forceBase64||this.forceBase64,enablesXDR:n.enablesXDR||this.enablesXDR,timestampRequests:n.timestampRequests||this.timestampRequests,timestampParam:n.timestampParam||this.timestampParam,policyPort:n.policyPort||this.policyPort,pfx:n.pfx||this.pfx,key:n.key||this.key,passphrase:n.passphrase||this.passphrase,cert:n.cert||this.cert,ca:n.ca||this.ca,ciphers:n.ciphers||this.ciphers,rejectUnauthorized:n.rejectUnauthorized||this.rejectUnauthorized,perMessageDeflate:n.perMessageDeflate||this.perMessageDeflate,extraHeaders:n.extraHeaders||this.extraHeaders,forceNode:n.forceNode||this.forceNode,localAddress:n.localAddress||this.localAddress,requestTimeout:n.requestTimeout||this.requestTimeout,protocols:n.protocols||void 0,isReactNative:this.isReactNative});return r},r.prototype.open=function(){var t;if(this.rememberUpgrade&&r.priorWebsocketSuccess&&this.transports.indexOf("websocket")!==-1)t="websocket";else{if(0===this.transports.length){var e=this;return void setTimeout(function(){e.emit("error","No transports available")},0)}t=this.transports[0]}this.readyState="opening";try{t=this.createTransport(t)}catch(n){return this.transports.shift(),void this.open()}t.open(),this.setTransport(t)},r.prototype.setTransport=function(t){a("setting transport %s",t.name);var e=this;this.transport&&(a("clearing existing transport %s",this.transport.name),this.transport.removeAllListeners()),this.transport=t,t.on("drain",function(){e.onDrain()}).on("packet",function(t){e.onPacket(t)}).on("error",function(t){e.onError(t)}).on("close",function(){e.onClose("transport close")})},r.prototype.probe=function(t){function e(){if(f.onlyBinaryUpgrades){var e=!this.supportsBinary&&f.transport.supportsBinary;h=h||e}h||(a('probe transport "%s" opened',t),u.send([{type:"ping",data:"probe"}]),u.once("packet",function(e){if(!h)if("pong"===e.type&&"probe"===e.data){if(a('probe transport "%s" pong',t),f.upgrading=!0,f.emit("upgrading",u),!u)return;r.priorWebsocketSuccess="websocket"===u.name,a('pausing current transport "%s"',f.transport.name),f.transport.pause(function(){h||"closed"!==f.readyState&&(a("changing transport and sending upgrade packet"),p(),f.setTransport(u),u.send([{type:"upgrade"}]),f.emit("upgrade",u),u=null,f.upgrading=!1,f.flush())})}else{a('probe transport "%s" failed',t);var n=new Error("probe error");n.transport=u.name,f.emit("upgradeError",n)}}))}function n(){h||(h=!0,p(),u.close(),u=null)}function o(e){var r=new Error("probe error: "+e);r.transport=u.name,n(),a('probe transport "%s" failed because of error: %s',t,e),f.emit("upgradeError",r)}function i(){o("transport closed")}function s(){o("socket closed")}function c(t){u&&t.name!==u.name&&(a('"%s" works - aborting "%s"',t.name,u.name),n())}function p(){u.removeListener("open",e),u.removeListener("error",o),u.removeListener("close",i),f.removeListener("close",s),f.removeListener("upgrading",c)}a('probing transport "%s"',t);var u=this.createTransport(t,{probe:1}),h=!1,f=this;r.priorWebsocketSuccess=!1,u.once("open",e),u.once("error",o),u.once("close",i),this.once("close",s),this.once("upgrading",c),u.open()},r.prototype.onOpen=function(){if(a("socket open"),this.readyState="open",r.priorWebsocketSuccess="websocket"===this.transport.name,this.emit("open"),this.flush(),"open"===this.readyState&&this.upgrade&&this.transport.pause){a("starting upgrade probes");for(var t=0,e=this.upgrades.length;t1?{type:b[o],data:t.substring(1)}:{type:b[o]}:w}var i=new Uint8Array(t),o=i[0],s=f(t,1);return k&&"blob"===n&&(s=new k([s])),{type:b[o],data:s}},e.decodeBase64Packet=function(t,e){var n=b[t.charAt(0)];if(!p)return{type:n,data:{base64:!0,data:t.substr(1)}};var r=p.decode(t.substr(1));return"blob"===e&&k&&(r=new k([r])),{type:n,data:r}},e.encodePayload=function(t,n,r){function o(t){return t.length+":"+t}function i(t,r){e.encodePacket(t,!!s&&n,!1,function(t){r(null,o(t))})}"function"==typeof n&&(r=n,n=null);var s=h(t);return n&&s?k&&!g?e.encodePayloadAsBlob(t,r):e.encodePayloadAsArrayBuffer(t,r):t.length?void c(t,i,function(t,e){return r(e.join(""))}):r("0:")},e.decodePayload=function(t,n,r){if("string"!=typeof t)return e.decodePayloadAsBinary(t,n,r);"function"==typeof n&&(r=n,n=null);var o;if(""===t)return r(w,0,1);for(var i,s,a="",c=0,p=t.length;c0;){for(var s=new Uint8Array(o),a=0===s[0],c="",p=1;255!==s[p];p++){if(c.length>310)return r(w,0,1);c+=s[p]}o=f(o,2+c.length),c=parseInt(c);var u=f(o,0,c);if(a)try{u=String.fromCharCode.apply(null,new Uint8Array(u))}catch(h){var l=new Uint8Array(u);u="";for(var p=0;pr&&(n=r),e>=r||e>=n||0===r)return new ArrayBuffer(0);for(var o=new Uint8Array(t),i=new Uint8Array(n-e),s=e,a=0;s=55296&&e<=56319&&o65535&&(e-=65536,o+=d(e>>>10&1023|55296),e=56320|1023&e),o+=d(e);return o}function o(t,e){if(t>=55296&&t<=57343){if(e)throw Error("Lone surrogate U+"+t.toString(16).toUpperCase()+" is not a scalar value");return!1}return!0}function i(t,e){return d(t>>e&63|128)}function s(t,e){if(0==(4294967168&t))return d(t);var n="";return 0==(4294965248&t)?n=d(t>>6&31|192):0==(4294901760&t)?(o(t,e)||(t=65533),n=d(t>>12&15|224),n+=i(t,6)):0==(4292870144&t)&&(n=d(t>>18&7|240),n+=i(t,12),n+=i(t,6)),n+=d(63&t|128)}function a(t,e){e=e||{};for(var r,o=!1!==e.strict,i=n(t),a=i.length,c=-1,p="";++c=f)throw Error("Invalid byte index");var t=255&h[l];if(l++,128==(192&t))return 63&t;throw Error("Invalid continuation byte")}function p(t){var e,n,r,i,s;if(l>f)throw Error("Invalid byte index");if(l==f)return!1;if(e=255&h[l],l++,0==(128&e))return e;if(192==(224&e)){if(n=c(),s=(31&e)<<6|n,s>=128)return s;throw Error("Invalid continuation byte")}if(224==(240&e)){if(n=c(),r=c(),s=(15&e)<<12|n<<6|r,s>=2048)return o(s,t)?s:65533;throw Error("Invalid continuation byte")}if(240==(248&e)&&(n=c(),r=c(),i=c(),s=(7&e)<<18|n<<12|r<<6|i,s>=65536&&s<=1114111))return s;throw Error("Invalid UTF-8 detected")}function u(t,e){e=e||{};var o=!1!==e.strict;h=n(t),f=h.length,l=0;for(var i,s=[];(i=p(o))!==!1;)s.push(i);return r(s)}/*! https://mths.be/utf8js v2.1.2 by @mathias */ -var h,f,l,d=String.fromCharCode;t.exports={version:"2.1.2",encode:a,decode:u}},function(t,e){!function(){"use strict";for(var t="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",n=new Uint8Array(256),r=0;r>2],i+=t[(3&r[n])<<4|r[n+1]>>4],i+=t[(15&r[n+1])<<2|r[n+2]>>6],i+=t[63&r[n+2]];return o%3===2?i=i.substring(0,i.length-1)+"=":o%3===1&&(i=i.substring(0,i.length-2)+"=="),i},e.decode=function(t){var e,r,o,i,s,a=.75*t.length,c=t.length,p=0;"="===t[t.length-1]&&(a--,"="===t[t.length-2]&&a--);var u=new ArrayBuffer(a),h=new Uint8Array(u);for(e=0;e>4,h[p++]=(15&o)<<4|i>>2,h[p++]=(3&i)<<6|63&s;return u}}()},function(t,e){function n(t){return t.map(function(t){if(t.buffer instanceof ArrayBuffer){var e=t.buffer;if(t.byteLength!==e.byteLength){var n=new Uint8Array(t.byteLength);n.set(new Uint8Array(e,t.byteOffset,t.byteLength)),e=n.buffer}return e}return t})}function r(t,e){e=e||{};var r=new i;return n(t).forEach(function(t){r.append(t)}),e.type?r.getBlob(e.type):r.getBlob()}function o(t,e){return new Blob(n(t),e||{})}var i="undefined"!=typeof i?i:"undefined"!=typeof WebKitBlobBuilder?WebKitBlobBuilder:"undefined"!=typeof MSBlobBuilder?MSBlobBuilder:"undefined"!=typeof MozBlobBuilder&&MozBlobBuilder,s=function(){try{var t=new Blob(["hi"]);return 2===t.size}catch(e){return!1}}(),a=s&&function(){try{var t=new Blob([new Uint8Array([1,2])]);return 2===t.size}catch(e){return!1}}(),c=i&&i.prototype.append&&i.prototype.getBlob;"undefined"!=typeof Blob&&(r.prototype=Blob.prototype,o.prototype=Blob.prototype),t.exports=function(){return s?a?Blob:o:c?r:void 0}()},function(t,e){e.encode=function(t){var e="";for(var n in t)t.hasOwnProperty(n)&&(e.length&&(e+="&"),e+=encodeURIComponent(n)+"="+encodeURIComponent(t[n]));return e},e.decode=function(t){for(var e={},n=t.split("&"),r=0,o=n.length;r0);return e}function r(t){var e=0;for(u=0;u';i=document.createElement(e)}catch(t){i=document.createElement("iframe"),i.name=o.iframeId,i.src="javascript:0"}i.id=o.iframeId,o.form.appendChild(i),o.iframe=i}var o=this;if(!this.form){var i,s=document.createElement("form"),a=document.createElement("textarea"),c=this.iframeId="eio_iframe_"+this.index;s.className="socketio",s.style.position="absolute",s.style.top="-1000px",s.style.left="-1000px",s.target=c,s.method="POST",s.setAttribute("accept-charset","utf-8"),a.name="d",s.appendChild(a),document.body.appendChild(s),this.form=s,this.area=a}this.form.action=this.uri(),r(),t=t.replace(u,"\\\n"),this.area.value=t.replace(p,"\\n");try{this.form.submit()}catch(h){}this.iframe.attachEvent?this.iframe.onreadystatechange=function(){"complete"===o.iframe.readyState&&n()}:this.iframe.onload=n}}).call(e,function(){return this}())},function(t,e,n){function r(t){var e=t&&t.forceBase64;e&&(this.supportsBinary=!1),this.perMessageDeflate=t.perMessageDeflate,this.usingBrowserWebSocket=o&&!t.forceNode,this.protocols=t.protocols,this.usingBrowserWebSocket||(l=i),s.call(this,t)}var o,i,s=n(20),a=n(21),c=n(29),p=n(30),u=n(31),h=n(3)("engine.io-client:websocket");if("undefined"==typeof self)try{i=n(34)}catch(f){}else o=self.WebSocket||self.MozWebSocket;var l=o||i;t.exports=r,p(r,s),r.prototype.name="websocket",r.prototype.supportsBinary=!0,r.prototype.doOpen=function(){if(this.check()){var t=this.uri(),e=this.protocols,n={agent:this.agent,perMessageDeflate:this.perMessageDeflate};n.pfx=this.pfx,n.key=this.key,n.passphrase=this.passphrase,n.cert=this.cert,n.ca=this.ca,n.ciphers=this.ciphers,n.rejectUnauthorized=this.rejectUnauthorized,this.extraHeaders&&(n.headers=this.extraHeaders),this.localAddress&&(n.localAddress=this.localAddress);try{this.ws=this.usingBrowserWebSocket&&!this.isReactNative?e?new l(t,e):new l(t):new l(t,e,n)}catch(r){return this.emit("error",r)}void 0===this.ws.binaryType&&(this.supportsBinary=!1),this.ws.supports&&this.ws.supports.binary?(this.supportsBinary=!0,this.ws.binaryType="nodebuffer"):this.ws.binaryType="arraybuffer",this.addEventListeners()}},r.prototype.addEventListeners=function(){var t=this;this.ws.onopen=function(){t.onOpen()},this.ws.onclose=function(){t.onClose()},this.ws.onmessage=function(e){t.onData(e.data)},this.ws.onerror=function(e){t.onError("websocket error",e)}},r.prototype.write=function(t){function e(){n.emit("flush"),setTimeout(function(){n.writable=!0,n.emit("drain")},0)}var n=this;this.writable=!1;for(var r=t.length,o=0,i=r;o0&&t.jitter<=1?t.jitter:0,this.attempts=0}t.exports=n,n.prototype.duration=function(){var t=this.ms*Math.pow(this.factor,this.attempts++);if(this.jitter){var e=Math.random(),n=Math.floor(e*this.jitter*t);t=0==(1&Math.floor(10*e))?t-n:t+n}return 0|Math.min(t,this.max)},n.prototype.reset=function(){this.attempts=0},n.prototype.setMin=function(t){this.ms=t},n.prototype.setMax=function(t){this.max=t},n.prototype.setJitter=function(t){this.jitter=t}}])}); -//# sourceMappingURL=socket.io.js.map \ No newline at end of file diff --git a/flask__websocket/ajax_vs_websocket/_static/socket.io.min.js b/flask__websocket/ajax_vs_websocket/_static/socket.io.min.js new file mode 100644 index 000000000..f39af5c95 --- /dev/null +++ b/flask__websocket/ajax_vs_websocket/_static/socket.io.min.js @@ -0,0 +1,7 @@ +/*! + * Socket.IO v4.7.2 + * (c) 2014-2023 Guillermo Rauch + * Released under the MIT License. + */ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).io=e()}(this,(function(){"use strict";function t(e){return t="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},t(e)}function e(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function n(t,e){for(var n=0;nt.length)&&(e=t.length);for(var n=0,r=new Array(e);n=t.length?{done:!0}:{done:!1,value:t[r++]}},e:function(t){throw t},f:i}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var o,s=!0,a=!1;return{s:function(){n=n.call(t)},n:function(){var t=n.next();return s=t.done,t},e:function(t){a=!0,o=t},f:function(){try{s||null==n.return||n.return()}finally{if(a)throw o}}}}var v=Object.create(null);v.open="0",v.close="1",v.ping="2",v.pong="3",v.message="4",v.upgrade="5",v.noop="6";var g=Object.create(null);Object.keys(v).forEach((function(t){g[v[t]]=t}));var m,b={type:"error",data:"parser error"},k="function"==typeof Blob||"undefined"!=typeof Blob&&"[object BlobConstructor]"===Object.prototype.toString.call(Blob),w="function"==typeof ArrayBuffer,_=function(t){return"function"==typeof ArrayBuffer.isView?ArrayBuffer.isView(t):t&&t.buffer instanceof ArrayBuffer},A=function(t,e,n){var r=t.type,i=t.data;return k&&i instanceof Blob?e?n(i):O(i,n):w&&(i instanceof ArrayBuffer||_(i))?e?n(i):O(new Blob([i]),n):n(v[r]+(i||""))},O=function(t,e){var n=new FileReader;return n.onload=function(){var t=n.result.split(",")[1];e("b"+(t||""))},n.readAsDataURL(t)};function E(t){return t instanceof Uint8Array?t:t instanceof ArrayBuffer?new Uint8Array(t):new Uint8Array(t.buffer,t.byteOffset,t.byteLength)}for(var T="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",R="undefined"==typeof Uint8Array?[]:new Uint8Array(256),C=0;C<64;C++)R[T.charCodeAt(C)]=C;var B,S="function"==typeof ArrayBuffer,N=function(t,e){if("string"!=typeof t)return{type:"message",data:x(t,e)};var n=t.charAt(0);return"b"===n?{type:"message",data:L(t.substring(1),e)}:g[n]?t.length>1?{type:g[n],data:t.substring(1)}:{type:g[n]}:b},L=function(t,e){if(S){var n=function(t){var e,n,r,i,o,s=.75*t.length,a=t.length,u=0;"="===t[t.length-1]&&(s--,"="===t[t.length-2]&&s--);var c=new ArrayBuffer(s),h=new Uint8Array(c);for(e=0;e>4,h[u++]=(15&r)<<4|i>>2,h[u++]=(3&i)<<6|63&o;return c}(t);return x(n,e)}return{base64:!0,data:t}},x=function(t,e){return"blob"===e?t instanceof Blob?t:new Blob([t]):t instanceof ArrayBuffer?t:t.buffer},P=String.fromCharCode(30);function q(){return new TransformStream({transform:function(t,e){!function(t,e){k&&t.data instanceof Blob?t.data.arrayBuffer().then(E).then(e):w&&(t.data instanceof ArrayBuffer||_(t.data))?e(E(t.data)):A(t,!1,(function(t){m||(m=new TextEncoder),e(m.encode(t))}))}(t,(function(n){var r,i=n.length;if(i<126)r=new Uint8Array(1),new DataView(r.buffer).setUint8(0,i);else if(i<65536){r=new Uint8Array(3);var o=new DataView(r.buffer);o.setUint8(0,126),o.setUint16(1,i)}else{r=new Uint8Array(9);var s=new DataView(r.buffer);s.setUint8(0,127),s.setBigUint64(1,BigInt(i))}t.data&&"string"!=typeof t.data&&(r[0]|=128),e.enqueue(r),e.enqueue(n)}))}})}function j(t){return t.reduce((function(t,e){return t+e.length}),0)}function D(t,e){if(t[0].length===e)return t.shift();for(var n=new Uint8Array(e),r=0,i=0;i1?e-1:0),r=1;r1&&void 0!==arguments[1]?arguments[1]:{};return t+"://"+this._hostname()+this._port()+this.opts.path+this._query(e)}},{key:"_hostname",value:function(){var t=this.opts.hostname;return-1===t.indexOf(":")?t:"["+t+"]"}},{key:"_port",value:function(){return this.opts.port&&(this.opts.secure&&Number(443!==this.opts.port)||!this.opts.secure&&80!==Number(this.opts.port))?":"+this.opts.port:""}},{key:"_query",value:function(t){var e=function(t){var e="";for(var n in t)t.hasOwnProperty(n)&&(e.length&&(e+="&"),e+=encodeURIComponent(n)+"="+encodeURIComponent(t[n]));return e}(t);return e.length?"?"+e:""}}]),i}(U),z="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_".split(""),J=64,$={},Q=0,X=0;function G(t){var e="";do{e=z[t%J]+e,t=Math.floor(t/J)}while(t>0);return e}function Z(){var t=G(+new Date);return t!==K?(Q=0,K=t):t+"."+G(Q++)}for(;X0&&void 0!==arguments[0]?arguments[0]:{};return i(t,{xd:this.xd,cookieJar:this.cookieJar},this.opts),new st(this.uri(),t)}},{key:"doWrite",value:function(t,e){var n=this,r=this.request({method:"POST",data:t});r.on("success",e),r.on("error",(function(t,e){n.onError("xhr post error",t,e)}))}},{key:"doPoll",value:function(){var t=this,e=this.request();e.on("data",this.onData.bind(this)),e.on("error",(function(e,n){t.onError("xhr poll error",e,n)})),this.pollXhr=e}}]),s}(W),st=function(t){o(i,t);var n=l(i);function i(t,r){var o;return e(this,i),H(f(o=n.call(this)),r),o.opts=r,o.method=r.method||"GET",o.uri=t,o.data=void 0!==r.data?r.data:null,o.create(),o}return r(i,[{key:"create",value:function(){var t,e=this,n=F(this.opts,"agent","pfx","key","passphrase","cert","ca","ciphers","rejectUnauthorized","autoUnref");n.xdomain=!!this.opts.xd;var r=this.xhr=new nt(n);try{r.open(this.method,this.uri,!0);try{if(this.opts.extraHeaders)for(var o in r.setDisableHeaderCheck&&r.setDisableHeaderCheck(!0),this.opts.extraHeaders)this.opts.extraHeaders.hasOwnProperty(o)&&r.setRequestHeader(o,this.opts.extraHeaders[o])}catch(t){}if("POST"===this.method)try{r.setRequestHeader("Content-type","text/plain;charset=UTF-8")}catch(t){}try{r.setRequestHeader("Accept","*/*")}catch(t){}null===(t=this.opts.cookieJar)||void 0===t||t.addCookies(r),"withCredentials"in r&&(r.withCredentials=this.opts.withCredentials),this.opts.requestTimeout&&(r.timeout=this.opts.requestTimeout),r.onreadystatechange=function(){var t;3===r.readyState&&(null===(t=e.opts.cookieJar)||void 0===t||t.parseCookies(r)),4===r.readyState&&(200===r.status||1223===r.status?e.onLoad():e.setTimeoutFn((function(){e.onError("number"==typeof r.status?r.status:0)}),0))},r.send(this.data)}catch(t){return void this.setTimeoutFn((function(){e.onError(t)}),0)}"undefined"!=typeof document&&(this.index=i.requestsCount++,i.requests[this.index]=this)}},{key:"onError",value:function(t){this.emitReserved("error",t,this.xhr),this.cleanup(!0)}},{key:"cleanup",value:function(t){if(void 0!==this.xhr&&null!==this.xhr){if(this.xhr.onreadystatechange=rt,t)try{this.xhr.abort()}catch(t){}"undefined"!=typeof document&&delete i.requests[this.index],this.xhr=null}}},{key:"onLoad",value:function(){var t=this.xhr.responseText;null!==t&&(this.emitReserved("data",t),this.emitReserved("success"),this.cleanup())}},{key:"abort",value:function(){this.cleanup()}}]),i}(U);if(st.requestsCount=0,st.requests={},"undefined"!=typeof document)if("function"==typeof attachEvent)attachEvent("onunload",at);else if("function"==typeof addEventListener){addEventListener("onpagehide"in I?"pagehide":"unload",at,!1)}function at(){for(var t in st.requests)st.requests.hasOwnProperty(t)&&st.requests[t].abort()}var ut="function"==typeof Promise&&"function"==typeof Promise.resolve?function(t){return Promise.resolve().then(t)}:function(t,e){return e(t,0)},ct=I.WebSocket||I.MozWebSocket,ht="undefined"!=typeof navigator&&"string"==typeof navigator.product&&"reactnative"===navigator.product.toLowerCase(),ft=function(t){o(i,t);var n=l(i);function i(t){var r;return e(this,i),(r=n.call(this,t)).supportsBinary=!t.forceBase64,r}return r(i,[{key:"name",get:function(){return"websocket"}},{key:"doOpen",value:function(){if(this.check()){var t=this.uri(),e=this.opts.protocols,n=ht?{}:F(this.opts,"agent","perMessageDeflate","pfx","key","passphrase","cert","ca","ciphers","rejectUnauthorized","localAddress","protocolVersion","origin","maxPayload","family","checkServerIdentity");this.opts.extraHeaders&&(n.headers=this.opts.extraHeaders);try{this.ws=ht?new ct(t,e,n):e?new ct(t,e):new ct(t)}catch(t){return this.emitReserved("error",t)}this.ws.binaryType=this.socket.binaryType,this.addEventListeners()}}},{key:"addEventListeners",value:function(){var t=this;this.ws.onopen=function(){t.opts.autoUnref&&t.ws._socket.unref(),t.onOpen()},this.ws.onclose=function(e){return t.onClose({description:"websocket connection closed",context:e})},this.ws.onmessage=function(e){return t.onData(e.data)},this.ws.onerror=function(e){return t.onError("websocket error",e)}}},{key:"write",value:function(t){var e=this;this.writable=!1;for(var n=function(){var n=t[r],i=r===t.length-1;A(n,e.supportsBinary,(function(t){try{e.ws.send(t)}catch(t){}i&&ut((function(){e.writable=!0,e.emitReserved("drain")}),e.setTimeoutFn)}))},r=0;rMath.pow(2,21)-1){a.enqueue(b);break}i=l*Math.pow(2,32)+f.getUint32(4),r=3}else{if(j(n)t){a.enqueue(b);break}}}})}(Number.MAX_SAFE_INTEGER,t.socket.binaryType),r=e.readable.pipeThrough(n).getReader(),i=q();i.readable.pipeTo(e.writable),t.writer=i.writable.getWriter();!function e(){r.read().then((function(n){var r=n.done,i=n.value;r||(t.onPacket(i),e())})).catch((function(t){}))}();var o={type:"open"};t.query.sid&&(o.data='{"sid":"'.concat(t.query.sid,'"}')),t.writer.write(o).then((function(){return t.onOpen()}))}))})))}},{key:"write",value:function(t){var e=this;this.writable=!1;for(var n=function(){var n=t[r],i=r===t.length-1;e.writer.write(n).then((function(){i&&ut((function(){e.writable=!0,e.emitReserved("drain")}),e.setTimeoutFn)}))},r=0;r1&&void 0!==arguments[1]?arguments[1]:{};return e(this,a),(r=s.call(this)).binaryType="arraybuffer",r.writeBuffer=[],n&&"object"===t(n)&&(o=n,n=null),n?(n=vt(n),o.hostname=n.host,o.secure="https"===n.protocol||"wss"===n.protocol,o.port=n.port,n.query&&(o.query=n.query)):o.host&&(o.hostname=vt(o.host).host),H(f(r),o),r.secure=null!=o.secure?o.secure:"undefined"!=typeof location&&"https:"===location.protocol,o.hostname&&!o.port&&(o.port=r.secure?"443":"80"),r.hostname=o.hostname||("undefined"!=typeof location?location.hostname:"localhost"),r.port=o.port||("undefined"!=typeof location&&location.port?location.port:r.secure?"443":"80"),r.transports=o.transports||["polling","websocket","webtransport"],r.writeBuffer=[],r.prevBufferLen=0,r.opts=i({path:"/engine.io",agent:!1,withCredentials:!1,upgrade:!0,timestampParam:"t",rememberUpgrade:!1,addTrailingSlash:!0,rejectUnauthorized:!0,perMessageDeflate:{threshold:1024},transportOptions:{},closeOnBeforeunload:!1},o),r.opts.path=r.opts.path.replace(/\/$/,"")+(r.opts.addTrailingSlash?"/":""),"string"==typeof r.opts.query&&(r.opts.query=function(t){for(var e={},n=t.split("&"),r=0,i=n.length;r1))return this.writeBuffer;for(var t,e=1,n=0;n=57344?n+=3:(r++,n+=4);return n}(t):Math.ceil(1.33*(t.byteLength||t.size))),n>0&&e>this.maxPayload)return this.writeBuffer.slice(0,n);e+=2}return this.writeBuffer}},{key:"write",value:function(t,e,n){return this.sendPacket("message",t,e,n),this}},{key:"send",value:function(t,e,n){return this.sendPacket("message",t,e,n),this}},{key:"sendPacket",value:function(t,e,n,r){if("function"==typeof e&&(r=e,e=void 0),"function"==typeof n&&(r=n,n=null),"closing"!==this.readyState&&"closed"!==this.readyState){(n=n||{}).compress=!1!==n.compress;var i={type:t,data:e,options:n};this.emitReserved("packetCreate",i),this.writeBuffer.push(i),r&&this.once("flush",r),this.flush()}}},{key:"close",value:function(){var t=this,e=function(){t.onClose("forced close"),t.transport.close()},n=function n(){t.off("upgrade",n),t.off("upgradeError",n),e()},r=function(){t.once("upgrade",n),t.once("upgradeError",n)};return"opening"!==this.readyState&&"open"!==this.readyState||(this.readyState="closing",this.writeBuffer.length?this.once("drain",(function(){t.upgrading?r():e()})):this.upgrading?r():e()),this}},{key:"onError",value:function(t){a.priorWebsocketSuccess=!1,this.emitReserved("error",t),this.onClose("transport error",t)}},{key:"onClose",value:function(t,e){"opening"!==this.readyState&&"open"!==this.readyState&&"closing"!==this.readyState||(this.clearTimeoutFn(this.pingTimeoutTimer),this.transport.removeAllListeners("close"),this.transport.close(),this.transport.removeAllListeners(),"function"==typeof removeEventListener&&(removeEventListener("beforeunload",this.beforeunloadEventListener,!1),removeEventListener("offline",this.offlineEventListener,!1)),this.readyState="closed",this.id=null,this.emitReserved("close",t,e),this.writeBuffer=[],this.prevBufferLen=0)}},{key:"filterUpgrades",value:function(t){for(var e=[],n=0,r=t.length;n=0&&e.num1?e-1:0),r=1;r1?n-1:0),i=1;in._opts.retries&&(n._queue.shift(),e&&e(t));else if(n._queue.shift(),e){for(var i=arguments.length,o=new Array(i>1?i-1:0),s=1;s0&&void 0!==arguments[0]&&arguments[0];if(this.connected&&0!==this._queue.length){var e=this._queue[0];e.pending&&!t||(e.pending=!0,e.tryCount++,this.flags=e.flags,this.emit.apply(this,e.args))}}},{key:"packet",value:function(t){t.nsp=this.nsp,this.io._packet(t)}},{key:"onopen",value:function(){var t=this;"function"==typeof this.auth?this.auth((function(e){t._sendConnectPacket(e)})):this._sendConnectPacket(this.auth)}},{key:"_sendConnectPacket",value:function(t){this.packet({type:Bt.CONNECT,data:this._pid?i({pid:this._pid,offset:this._lastOffset},t):t})}},{key:"onerror",value:function(t){this.connected||this.emitReserved("connect_error",t)}},{key:"onclose",value:function(t,e){this.connected=!1,delete this.id,this.emitReserved("disconnect",t,e)}},{key:"onpacket",value:function(t){if(t.nsp===this.nsp)switch(t.type){case Bt.CONNECT:t.data&&t.data.sid?this.onconnect(t.data.sid,t.data.pid):this.emitReserved("connect_error",new Error("It seems you are trying to reach a Socket.IO server in v2.x with a v3.x client, but they are not compatible (more information here: https://socket.io/docs/v3/migrating-from-2-x-to-3-0/)"));break;case Bt.EVENT:case Bt.BINARY_EVENT:this.onevent(t);break;case Bt.ACK:case Bt.BINARY_ACK:this.onack(t);break;case Bt.DISCONNECT:this.ondisconnect();break;case Bt.CONNECT_ERROR:this.destroy();var e=new Error(t.data.message);e.data=t.data.data,this.emitReserved("connect_error",e)}}},{key:"onevent",value:function(t){var e=t.data||[];null!=t.id&&e.push(this.ack(t.id)),this.connected?this.emitEvent(e):this.receiveBuffer.push(Object.freeze(e))}},{key:"emitEvent",value:function(t){if(this._anyListeners&&this._anyListeners.length){var e,n=y(this._anyListeners.slice());try{for(n.s();!(e=n.n()).done;){e.value.apply(this,t)}}catch(t){n.e(t)}finally{n.f()}}p(s(a.prototype),"emit",this).apply(this,t),this._pid&&t.length&&"string"==typeof t[t.length-1]&&(this._lastOffset=t[t.length-1])}},{key:"ack",value:function(t){var e=this,n=!1;return function(){if(!n){n=!0;for(var r=arguments.length,i=new Array(r),o=0;o0&&t.jitter<=1?t.jitter:0,this.attempts=0}It.prototype.duration=function(){var t=this.ms*Math.pow(this.factor,this.attempts++);if(this.jitter){var e=Math.random(),n=Math.floor(e*this.jitter*t);t=0==(1&Math.floor(10*e))?t-n:t+n}return 0|Math.min(t,this.max)},It.prototype.reset=function(){this.attempts=0},It.prototype.setMin=function(t){this.ms=t},It.prototype.setMax=function(t){this.max=t},It.prototype.setJitter=function(t){this.jitter=t};var Ft=function(n){o(s,n);var i=l(s);function s(n,r){var o,a;e(this,s),(o=i.call(this)).nsps={},o.subs=[],n&&"object"===t(n)&&(r=n,n=void 0),(r=r||{}).path=r.path||"/socket.io",o.opts=r,H(f(o),r),o.reconnection(!1!==r.reconnection),o.reconnectionAttempts(r.reconnectionAttempts||1/0),o.reconnectionDelay(r.reconnectionDelay||1e3),o.reconnectionDelayMax(r.reconnectionDelayMax||5e3),o.randomizationFactor(null!==(a=r.randomizationFactor)&&void 0!==a?a:.5),o.backoff=new It({min:o.reconnectionDelay(),max:o.reconnectionDelayMax(),jitter:o.randomizationFactor()}),o.timeout(null==r.timeout?2e4:r.timeout),o._readyState="closed",o.uri=n;var u=r.parser||qt;return o.encoder=new u.Encoder,o.decoder=new u.Decoder,o._autoConnect=!1!==r.autoConnect,o._autoConnect&&o.open(),o}return r(s,[{key:"reconnection",value:function(t){return arguments.length?(this._reconnection=!!t,this):this._reconnection}},{key:"reconnectionAttempts",value:function(t){return void 0===t?this._reconnectionAttempts:(this._reconnectionAttempts=t,this)}},{key:"reconnectionDelay",value:function(t){var e;return void 0===t?this._reconnectionDelay:(this._reconnectionDelay=t,null===(e=this.backoff)||void 0===e||e.setMin(t),this)}},{key:"randomizationFactor",value:function(t){var e;return void 0===t?this._randomizationFactor:(this._randomizationFactor=t,null===(e=this.backoff)||void 0===e||e.setJitter(t),this)}},{key:"reconnectionDelayMax",value:function(t){var e;return void 0===t?this._reconnectionDelayMax:(this._reconnectionDelayMax=t,null===(e=this.backoff)||void 0===e||e.setMax(t),this)}},{key:"timeout",value:function(t){return arguments.length?(this._timeout=t,this):this._timeout}},{key:"maybeReconnectOnOpen",value:function(){!this._reconnecting&&this._reconnection&&0===this.backoff.attempts&&this.reconnect()}},{key:"open",value:function(t){var e=this;if(~this._readyState.indexOf("open"))return this;this.engine=new gt(this.uri,this.opts);var n=this.engine,r=this;this._readyState="opening",this.skipReconnect=!1;var i=jt(n,"open",(function(){r.onopen(),t&&t()})),o=function(n){e.cleanup(),e._readyState="closed",e.emitReserved("error",n),t?t(n):e.maybeReconnectOnOpen()},s=jt(n,"error",o);if(!1!==this._timeout){var a=this._timeout,u=this.setTimeoutFn((function(){i(),o(new Error("timeout")),n.close()}),a);this.opts.autoUnref&&u.unref(),this.subs.push((function(){e.clearTimeoutFn(u)}))}return this.subs.push(i),this.subs.push(s),this}},{key:"connect",value:function(t){return this.open(t)}},{key:"onopen",value:function(){this.cleanup(),this._readyState="open",this.emitReserved("open");var t=this.engine;this.subs.push(jt(t,"ping",this.onping.bind(this)),jt(t,"data",this.ondata.bind(this)),jt(t,"error",this.onerror.bind(this)),jt(t,"close",this.onclose.bind(this)),jt(this.decoder,"decoded",this.ondecoded.bind(this)))}},{key:"onping",value:function(){this.emitReserved("ping")}},{key:"ondata",value:function(t){try{this.decoder.add(t)}catch(t){this.onclose("parse error",t)}}},{key:"ondecoded",value:function(t){var e=this;ut((function(){e.emitReserved("packet",t)}),this.setTimeoutFn)}},{key:"onerror",value:function(t){this.emitReserved("error",t)}},{key:"socket",value:function(t,e){var n=this.nsps[t];return n?this._autoConnect&&!n.active&&n.connect():(n=new Ut(this,t,e),this.nsps[t]=n),n}},{key:"_destroy",value:function(t){for(var e=0,n=Object.keys(this.nsps);e=this._reconnectionAttempts)this.backoff.reset(),this.emitReserved("reconnect_failed"),this._reconnecting=!1;else{var n=this.backoff.duration();this._reconnecting=!0;var r=this.setTimeoutFn((function(){e.skipReconnect||(t.emitReserved("reconnect_attempt",e.backoff.attempts),e.skipReconnect||e.open((function(n){n?(e._reconnecting=!1,e.reconnect(),t.emitReserved("reconnect_error",n)):e.onreconnect()})))}),n);this.opts.autoUnref&&r.unref(),this.subs.push((function(){t.clearTimeoutFn(r)}))}}},{key:"onreconnect",value:function(){var t=this.backoff.attempts;this._reconnecting=!1,this.backoff.reset(),this.emitReserved("reconnect",t)}}]),s}(U),Mt={};function Vt(e,n){"object"===t(e)&&(n=e,e=void 0);var r,i=function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"",n=arguments.length>2?arguments[2]:void 0,r=t;n=n||"undefined"!=typeof location&&location,null==t&&(t=n.protocol+"//"+n.host),"string"==typeof t&&("/"===t.charAt(0)&&(t="/"===t.charAt(1)?n.protocol+t:n.host+t),/^(https?|wss?):\/\//.test(t)||(t=void 0!==n?n.protocol+"//"+t:"https://"+t),r=vt(t)),r.port||(/^(http|ws)$/.test(r.protocol)?r.port="80":/^(http|ws)s$/.test(r.protocol)&&(r.port="443")),r.path=r.path||"/";var i=-1!==r.host.indexOf(":")?"["+r.host+"]":r.host;return r.id=r.protocol+"://"+i+":"+r.port+e,r.href=r.protocol+"://"+i+(n&&n.port===r.port?"":":"+r.port),r}(e,(n=n||{}).path||"/socket.io"),o=i.source,s=i.id,a=i.path,u=Mt[s]&&a in Mt[s].nsps;return n.forceNew||n["force new connection"]||!1===n.multiplex||u?r=new Ft(o,n):(Mt[s]||(Mt[s]=new Ft(o,n)),r=Mt[s]),i.query&&!n.query&&(n.query=i.queryKey),r.socket(i.path,n)}return i(Vt,{Manager:Ft,Socket:Ut,io:Vt,connect:Vt}),Vt})); +//# sourceMappingURL=socket.io.min.js.map diff --git a/flask__websocket/ajax_vs_websocket/_static/socket.io.min.js.map b/flask__websocket/ajax_vs_websocket/_static/socket.io.min.js.map new file mode 100644 index 000000000..6a27b095e --- /dev/null +++ b/flask__websocket/ajax_vs_websocket/_static/socket.io.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"socket.io.min.js","sources":["../node_modules/engine.io-client/node_modules/engine.io-parser/build/esm/commons.js","../node_modules/engine.io-client/node_modules/engine.io-parser/build/esm/encodePacket.browser.js","../node_modules/engine.io-client/node_modules/engine.io-parser/build/esm/contrib/base64-arraybuffer.js","../node_modules/engine.io-client/node_modules/engine.io-parser/build/esm/index.js","../node_modules/engine.io-client/node_modules/engine.io-parser/build/esm/decodePacket.browser.js","../node_modules/@socket.io/component-emitter/index.mjs","../node_modules/engine.io-client/build/esm/globalThis.browser.js","../node_modules/engine.io-client/build/esm/util.js","../node_modules/engine.io-client/build/esm/transport.js","../node_modules/engine.io-client/build/esm/contrib/yeast.js","../node_modules/engine.io-client/build/esm/contrib/parseqs.js","../node_modules/engine.io-client/build/esm/contrib/has-cors.js","../node_modules/engine.io-client/build/esm/transports/xmlhttprequest.browser.js","../node_modules/engine.io-client/build/esm/transports/polling.js","../node_modules/engine.io-client/build/esm/transports/websocket-constructor.browser.js","../node_modules/engine.io-client/build/esm/transports/websocket.js","../node_modules/engine.io-client/build/esm/transports/webtransport.js","../node_modules/engine.io-client/build/esm/transports/index.js","../node_modules/engine.io-client/build/esm/contrib/parseuri.js","../node_modules/engine.io-client/build/esm/socket.js","../node_modules/engine.io-client/build/esm/index.js","../node_modules/socket.io-parser/build/esm/is-binary.js","../node_modules/socket.io-parser/build/esm/binary.js","../node_modules/socket.io-parser/build/esm/index.js","../build/esm/on.js","../build/esm/socket.js","../build/esm/contrib/backo2.js","../build/esm/manager.js","../build/esm/index.js","../build/esm/url.js"],"sourcesContent":["const PACKET_TYPES = Object.create(null); // no Map = no polyfill\nPACKET_TYPES[\"open\"] = \"0\";\nPACKET_TYPES[\"close\"] = \"1\";\nPACKET_TYPES[\"ping\"] = \"2\";\nPACKET_TYPES[\"pong\"] = \"3\";\nPACKET_TYPES[\"message\"] = \"4\";\nPACKET_TYPES[\"upgrade\"] = \"5\";\nPACKET_TYPES[\"noop\"] = \"6\";\nconst PACKET_TYPES_REVERSE = Object.create(null);\nObject.keys(PACKET_TYPES).forEach(key => {\n PACKET_TYPES_REVERSE[PACKET_TYPES[key]] = key;\n});\nconst ERROR_PACKET = { type: \"error\", data: \"parser error\" };\nexport { PACKET_TYPES, PACKET_TYPES_REVERSE, ERROR_PACKET };\n","import { PACKET_TYPES } from \"./commons.js\";\nconst withNativeBlob = typeof Blob === \"function\" ||\n (typeof Blob !== \"undefined\" &&\n Object.prototype.toString.call(Blob) === \"[object BlobConstructor]\");\nconst withNativeArrayBuffer = typeof ArrayBuffer === \"function\";\n// ArrayBuffer.isView method is not defined in IE10\nconst isView = obj => {\n return typeof ArrayBuffer.isView === \"function\"\n ? ArrayBuffer.isView(obj)\n : obj && obj.buffer instanceof ArrayBuffer;\n};\nconst encodePacket = ({ type, data }, supportsBinary, callback) => {\n if (withNativeBlob && data instanceof Blob) {\n if (supportsBinary) {\n return callback(data);\n }\n else {\n return encodeBlobAsBase64(data, callback);\n }\n }\n else if (withNativeArrayBuffer &&\n (data instanceof ArrayBuffer || isView(data))) {\n if (supportsBinary) {\n return callback(data);\n }\n else {\n return encodeBlobAsBase64(new Blob([data]), callback);\n }\n }\n // plain string\n return callback(PACKET_TYPES[type] + (data || \"\"));\n};\nconst encodeBlobAsBase64 = (data, callback) => {\n const fileReader = new FileReader();\n fileReader.onload = function () {\n const content = fileReader.result.split(\",\")[1];\n callback(\"b\" + (content || \"\"));\n };\n return fileReader.readAsDataURL(data);\n};\nfunction toArray(data) {\n if (data instanceof Uint8Array) {\n return data;\n }\n else if (data instanceof ArrayBuffer) {\n return new Uint8Array(data);\n }\n else {\n return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);\n }\n}\nlet TEXT_ENCODER;\nexport function encodePacketToBinary(packet, callback) {\n if (withNativeBlob && packet.data instanceof Blob) {\n return packet.data\n .arrayBuffer()\n .then(toArray)\n .then(callback);\n }\n else if (withNativeArrayBuffer &&\n (packet.data instanceof ArrayBuffer || isView(packet.data))) {\n return callback(toArray(packet.data));\n }\n encodePacket(packet, false, encoded => {\n if (!TEXT_ENCODER) {\n TEXT_ENCODER = new TextEncoder();\n }\n callback(TEXT_ENCODER.encode(encoded));\n });\n}\nexport { encodePacket };\n","// imported from https://github.com/socketio/base64-arraybuffer\nconst chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';\n// Use a lookup table to find the index.\nconst lookup = typeof Uint8Array === 'undefined' ? [] : new Uint8Array(256);\nfor (let i = 0; i < chars.length; i++) {\n lookup[chars.charCodeAt(i)] = i;\n}\nexport const encode = (arraybuffer) => {\n let bytes = new Uint8Array(arraybuffer), i, len = bytes.length, base64 = '';\n for (i = 0; i < len; i += 3) {\n base64 += chars[bytes[i] >> 2];\n base64 += chars[((bytes[i] & 3) << 4) | (bytes[i + 1] >> 4)];\n base64 += chars[((bytes[i + 1] & 15) << 2) | (bytes[i + 2] >> 6)];\n base64 += chars[bytes[i + 2] & 63];\n }\n if (len % 3 === 2) {\n base64 = base64.substring(0, base64.length - 1) + '=';\n }\n else if (len % 3 === 1) {\n base64 = base64.substring(0, base64.length - 2) + '==';\n }\n return base64;\n};\nexport const decode = (base64) => {\n let bufferLength = base64.length * 0.75, len = base64.length, i, p = 0, encoded1, encoded2, encoded3, encoded4;\n if (base64[base64.length - 1] === '=') {\n bufferLength--;\n if (base64[base64.length - 2] === '=') {\n bufferLength--;\n }\n }\n const arraybuffer = new ArrayBuffer(bufferLength), bytes = new Uint8Array(arraybuffer);\n for (i = 0; i < len; i += 4) {\n encoded1 = lookup[base64.charCodeAt(i)];\n encoded2 = lookup[base64.charCodeAt(i + 1)];\n encoded3 = lookup[base64.charCodeAt(i + 2)];\n encoded4 = lookup[base64.charCodeAt(i + 3)];\n bytes[p++] = (encoded1 << 2) | (encoded2 >> 4);\n bytes[p++] = ((encoded2 & 15) << 4) | (encoded3 >> 2);\n bytes[p++] = ((encoded3 & 3) << 6) | (encoded4 & 63);\n }\n return arraybuffer;\n};\n","import { encodePacket, encodePacketToBinary } from \"./encodePacket.js\";\nimport { decodePacket } from \"./decodePacket.js\";\nimport { ERROR_PACKET } from \"./commons.js\";\nconst SEPARATOR = String.fromCharCode(30); // see https://en.wikipedia.org/wiki/Delimiter#ASCII_delimited_text\nconst encodePayload = (packets, callback) => {\n // some packets may be added to the array while encoding, so the initial length must be saved\n const length = packets.length;\n const encodedPackets = new Array(length);\n let count = 0;\n packets.forEach((packet, i) => {\n // force base64 encoding for binary packets\n encodePacket(packet, false, encodedPacket => {\n encodedPackets[i] = encodedPacket;\n if (++count === length) {\n callback(encodedPackets.join(SEPARATOR));\n }\n });\n });\n};\nconst decodePayload = (encodedPayload, binaryType) => {\n const encodedPackets = encodedPayload.split(SEPARATOR);\n const packets = [];\n for (let i = 0; i < encodedPackets.length; i++) {\n const decodedPacket = decodePacket(encodedPackets[i], binaryType);\n packets.push(decodedPacket);\n if (decodedPacket.type === \"error\") {\n break;\n }\n }\n return packets;\n};\nexport function createPacketEncoderStream() {\n return new TransformStream({\n transform(packet, controller) {\n encodePacketToBinary(packet, encodedPacket => {\n const payloadLength = encodedPacket.length;\n let header;\n // inspired by the WebSocket format: https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_servers#decoding_payload_length\n if (payloadLength < 126) {\n header = new Uint8Array(1);\n new DataView(header.buffer).setUint8(0, payloadLength);\n }\n else if (payloadLength < 65536) {\n header = new Uint8Array(3);\n const view = new DataView(header.buffer);\n view.setUint8(0, 126);\n view.setUint16(1, payloadLength);\n }\n else {\n header = new Uint8Array(9);\n const view = new DataView(header.buffer);\n view.setUint8(0, 127);\n view.setBigUint64(1, BigInt(payloadLength));\n }\n // first bit indicates whether the payload is plain text (0) or binary (1)\n if (packet.data && typeof packet.data !== \"string\") {\n header[0] |= 0x80;\n }\n controller.enqueue(header);\n controller.enqueue(encodedPacket);\n });\n }\n });\n}\nlet TEXT_DECODER;\nfunction totalLength(chunks) {\n return chunks.reduce((acc, chunk) => acc + chunk.length, 0);\n}\nfunction concatChunks(chunks, size) {\n if (chunks[0].length === size) {\n return chunks.shift();\n }\n const buffer = new Uint8Array(size);\n let j = 0;\n for (let i = 0; i < size; i++) {\n buffer[i] = chunks[0][j++];\n if (j === chunks[0].length) {\n chunks.shift();\n j = 0;\n }\n }\n if (chunks.length && j < chunks[0].length) {\n chunks[0] = chunks[0].slice(j);\n }\n return buffer;\n}\nexport function createPacketDecoderStream(maxPayload, binaryType) {\n if (!TEXT_DECODER) {\n TEXT_DECODER = new TextDecoder();\n }\n const chunks = [];\n let state = 0 /* READ_HEADER */;\n let expectedLength = -1;\n let isBinary = false;\n return new TransformStream({\n transform(chunk, controller) {\n chunks.push(chunk);\n while (true) {\n if (state === 0 /* READ_HEADER */) {\n if (totalLength(chunks) < 1) {\n break;\n }\n const header = concatChunks(chunks, 1);\n isBinary = (header[0] & 0x80) === 0x80;\n expectedLength = header[0] & 0x7f;\n if (expectedLength < 126) {\n state = 3 /* READ_PAYLOAD */;\n }\n else if (expectedLength === 126) {\n state = 1 /* READ_EXTENDED_LENGTH_16 */;\n }\n else {\n state = 2 /* READ_EXTENDED_LENGTH_64 */;\n }\n }\n else if (state === 1 /* READ_EXTENDED_LENGTH_16 */) {\n if (totalLength(chunks) < 2) {\n break;\n }\n const headerArray = concatChunks(chunks, 2);\n expectedLength = new DataView(headerArray.buffer, headerArray.byteOffset, headerArray.length).getUint16(0);\n state = 3 /* READ_PAYLOAD */;\n }\n else if (state === 2 /* READ_EXTENDED_LENGTH_64 */) {\n if (totalLength(chunks) < 8) {\n break;\n }\n const headerArray = concatChunks(chunks, 8);\n const view = new DataView(headerArray.buffer, headerArray.byteOffset, headerArray.length);\n const n = view.getUint32(0);\n if (n > Math.pow(2, 53 - 32) - 1) {\n // the maximum safe integer in JavaScript is 2^53 - 1\n controller.enqueue(ERROR_PACKET);\n break;\n }\n expectedLength = n * Math.pow(2, 32) + view.getUint32(4);\n state = 3 /* READ_PAYLOAD */;\n }\n else {\n if (totalLength(chunks) < expectedLength) {\n break;\n }\n const data = concatChunks(chunks, expectedLength);\n controller.enqueue(decodePacket(isBinary ? data : TEXT_DECODER.decode(data), binaryType));\n state = 0 /* READ_HEADER */;\n }\n if (expectedLength === 0 || expectedLength > maxPayload) {\n controller.enqueue(ERROR_PACKET);\n break;\n }\n }\n }\n });\n}\nexport const protocol = 4;\nexport { encodePacket, encodePayload, decodePacket, decodePayload };\n","import { ERROR_PACKET, PACKET_TYPES_REVERSE } from \"./commons.js\";\nimport { decode } from \"./contrib/base64-arraybuffer.js\";\nconst withNativeArrayBuffer = typeof ArrayBuffer === \"function\";\nexport const decodePacket = (encodedPacket, binaryType) => {\n if (typeof encodedPacket !== \"string\") {\n return {\n type: \"message\",\n data: mapBinary(encodedPacket, binaryType)\n };\n }\n const type = encodedPacket.charAt(0);\n if (type === \"b\") {\n return {\n type: \"message\",\n data: decodeBase64Packet(encodedPacket.substring(1), binaryType)\n };\n }\n const packetType = PACKET_TYPES_REVERSE[type];\n if (!packetType) {\n return ERROR_PACKET;\n }\n return encodedPacket.length > 1\n ? {\n type: PACKET_TYPES_REVERSE[type],\n data: encodedPacket.substring(1)\n }\n : {\n type: PACKET_TYPES_REVERSE[type]\n };\n};\nconst decodeBase64Packet = (data, binaryType) => {\n if (withNativeArrayBuffer) {\n const decoded = decode(data);\n return mapBinary(decoded, binaryType);\n }\n else {\n return { base64: true, data }; // fallback for old browsers\n }\n};\nconst mapBinary = (data, binaryType) => {\n switch (binaryType) {\n case \"blob\":\n if (data instanceof Blob) {\n // from WebSocket + binaryType \"blob\"\n return data;\n }\n else {\n // from HTTP long-polling or WebTransport\n return new Blob([data]);\n }\n case \"arraybuffer\":\n default:\n if (data instanceof ArrayBuffer) {\n // from HTTP long-polling (base64) or WebSocket + binaryType \"arraybuffer\"\n return data;\n }\n else {\n // from WebTransport (Uint8Array)\n return data.buffer;\n }\n }\n};\n","/**\n * Initialize a new `Emitter`.\n *\n * @api public\n */\n\nexport function Emitter(obj) {\n if (obj) return mixin(obj);\n}\n\n/**\n * Mixin the emitter properties.\n *\n * @param {Object} obj\n * @return {Object}\n * @api private\n */\n\nfunction mixin(obj) {\n for (var key in Emitter.prototype) {\n obj[key] = Emitter.prototype[key];\n }\n return obj;\n}\n\n/**\n * Listen on the given `event` with `fn`.\n *\n * @param {String} event\n * @param {Function} fn\n * @return {Emitter}\n * @api public\n */\n\nEmitter.prototype.on =\nEmitter.prototype.addEventListener = function(event, fn){\n this._callbacks = this._callbacks || {};\n (this._callbacks['$' + event] = this._callbacks['$' + event] || [])\n .push(fn);\n return this;\n};\n\n/**\n * Adds an `event` listener that will be invoked a single\n * time then automatically removed.\n *\n * @param {String} event\n * @param {Function} fn\n * @return {Emitter}\n * @api public\n */\n\nEmitter.prototype.once = function(event, fn){\n function on() {\n this.off(event, on);\n fn.apply(this, arguments);\n }\n\n on.fn = fn;\n this.on(event, on);\n return this;\n};\n\n/**\n * Remove the given callback for `event` or all\n * registered callbacks.\n *\n * @param {String} event\n * @param {Function} fn\n * @return {Emitter}\n * @api public\n */\n\nEmitter.prototype.off =\nEmitter.prototype.removeListener =\nEmitter.prototype.removeAllListeners =\nEmitter.prototype.removeEventListener = function(event, fn){\n this._callbacks = this._callbacks || {};\n\n // all\n if (0 == arguments.length) {\n this._callbacks = {};\n return this;\n }\n\n // specific event\n var callbacks = this._callbacks['$' + event];\n if (!callbacks) return this;\n\n // remove all handlers\n if (1 == arguments.length) {\n delete this._callbacks['$' + event];\n return this;\n }\n\n // remove specific handler\n var cb;\n for (var i = 0; i < callbacks.length; i++) {\n cb = callbacks[i];\n if (cb === fn || cb.fn === fn) {\n callbacks.splice(i, 1);\n break;\n }\n }\n\n // Remove event specific arrays for event types that no\n // one is subscribed for to avoid memory leak.\n if (callbacks.length === 0) {\n delete this._callbacks['$' + event];\n }\n\n return this;\n};\n\n/**\n * Emit `event` with the given args.\n *\n * @param {String} event\n * @param {Mixed} ...\n * @return {Emitter}\n */\n\nEmitter.prototype.emit = function(event){\n this._callbacks = this._callbacks || {};\n\n var args = new Array(arguments.length - 1)\n , callbacks = this._callbacks['$' + event];\n\n for (var i = 1; i < arguments.length; i++) {\n args[i - 1] = arguments[i];\n }\n\n if (callbacks) {\n callbacks = callbacks.slice(0);\n for (var i = 0, len = callbacks.length; i < len; ++i) {\n callbacks[i].apply(this, args);\n }\n }\n\n return this;\n};\n\n// alias used for reserved events (protected method)\nEmitter.prototype.emitReserved = Emitter.prototype.emit;\n\n/**\n * Return array of callbacks for `event`.\n *\n * @param {String} event\n * @return {Array}\n * @api public\n */\n\nEmitter.prototype.listeners = function(event){\n this._callbacks = this._callbacks || {};\n return this._callbacks['$' + event] || [];\n};\n\n/**\n * Check if this emitter has `event` handlers.\n *\n * @param {String} event\n * @return {Boolean}\n * @api public\n */\n\nEmitter.prototype.hasListeners = function(event){\n return !! this.listeners(event).length;\n};\n","export const globalThisShim = (() => {\n if (typeof self !== \"undefined\") {\n return self;\n }\n else if (typeof window !== \"undefined\") {\n return window;\n }\n else {\n return Function(\"return this\")();\n }\n})();\n","import { globalThisShim as globalThis } from \"./globalThis.js\";\nexport function pick(obj, ...attr) {\n return attr.reduce((acc, k) => {\n if (obj.hasOwnProperty(k)) {\n acc[k] = obj[k];\n }\n return acc;\n }, {});\n}\n// Keep a reference to the real timeout functions so they can be used when overridden\nconst NATIVE_SET_TIMEOUT = globalThis.setTimeout;\nconst NATIVE_CLEAR_TIMEOUT = globalThis.clearTimeout;\nexport function installTimerFunctions(obj, opts) {\n if (opts.useNativeTimers) {\n obj.setTimeoutFn = NATIVE_SET_TIMEOUT.bind(globalThis);\n obj.clearTimeoutFn = NATIVE_CLEAR_TIMEOUT.bind(globalThis);\n }\n else {\n obj.setTimeoutFn = globalThis.setTimeout.bind(globalThis);\n obj.clearTimeoutFn = globalThis.clearTimeout.bind(globalThis);\n }\n}\n// base64 encoded buffers are about 33% bigger (https://en.wikipedia.org/wiki/Base64)\nconst BASE64_OVERHEAD = 1.33;\n// we could also have used `new Blob([obj]).size`, but it isn't supported in IE9\nexport function byteLength(obj) {\n if (typeof obj === \"string\") {\n return utf8Length(obj);\n }\n // arraybuffer or blob\n return Math.ceil((obj.byteLength || obj.size) * BASE64_OVERHEAD);\n}\nfunction utf8Length(str) {\n let c = 0, length = 0;\n for (let i = 0, l = str.length; i < l; i++) {\n c = str.charCodeAt(i);\n if (c < 0x80) {\n length += 1;\n }\n else if (c < 0x800) {\n length += 2;\n }\n else if (c < 0xd800 || c >= 0xe000) {\n length += 3;\n }\n else {\n i++;\n length += 4;\n }\n }\n return length;\n}\n","import { decodePacket } from \"engine.io-parser\";\nimport { Emitter } from \"@socket.io/component-emitter\";\nimport { installTimerFunctions } from \"./util.js\";\nimport { encode } from \"./contrib/parseqs.js\";\nclass TransportError extends Error {\n constructor(reason, description, context) {\n super(reason);\n this.description = description;\n this.context = context;\n this.type = \"TransportError\";\n }\n}\nexport class Transport extends Emitter {\n /**\n * Transport abstract constructor.\n *\n * @param {Object} opts - options\n * @protected\n */\n constructor(opts) {\n super();\n this.writable = false;\n installTimerFunctions(this, opts);\n this.opts = opts;\n this.query = opts.query;\n this.socket = opts.socket;\n }\n /**\n * Emits an error.\n *\n * @param {String} reason\n * @param description\n * @param context - the error context\n * @return {Transport} for chaining\n * @protected\n */\n onError(reason, description, context) {\n super.emitReserved(\"error\", new TransportError(reason, description, context));\n return this;\n }\n /**\n * Opens the transport.\n */\n open() {\n this.readyState = \"opening\";\n this.doOpen();\n return this;\n }\n /**\n * Closes the transport.\n */\n close() {\n if (this.readyState === \"opening\" || this.readyState === \"open\") {\n this.doClose();\n this.onClose();\n }\n return this;\n }\n /**\n * Sends multiple packets.\n *\n * @param {Array} packets\n */\n send(packets) {\n if (this.readyState === \"open\") {\n this.write(packets);\n }\n else {\n // this might happen if the transport was silently closed in the beforeunload event handler\n }\n }\n /**\n * Called upon open\n *\n * @protected\n */\n onOpen() {\n this.readyState = \"open\";\n this.writable = true;\n super.emitReserved(\"open\");\n }\n /**\n * Called with data.\n *\n * @param {String} data\n * @protected\n */\n onData(data) {\n const packet = decodePacket(data, this.socket.binaryType);\n this.onPacket(packet);\n }\n /**\n * Called with a decoded packet.\n *\n * @protected\n */\n onPacket(packet) {\n super.emitReserved(\"packet\", packet);\n }\n /**\n * Called upon close.\n *\n * @protected\n */\n onClose(details) {\n this.readyState = \"closed\";\n super.emitReserved(\"close\", details);\n }\n /**\n * Pauses the transport, in order not to lose packets during an upgrade.\n *\n * @param onPause\n */\n pause(onPause) { }\n createUri(schema, query = {}) {\n return (schema +\n \"://\" +\n this._hostname() +\n this._port() +\n this.opts.path +\n this._query(query));\n }\n _hostname() {\n const hostname = this.opts.hostname;\n return hostname.indexOf(\":\") === -1 ? hostname : \"[\" + hostname + \"]\";\n }\n _port() {\n if (this.opts.port &&\n ((this.opts.secure && Number(this.opts.port !== 443)) ||\n (!this.opts.secure && Number(this.opts.port) !== 80))) {\n return \":\" + this.opts.port;\n }\n else {\n return \"\";\n }\n }\n _query(query) {\n const encodedQuery = encode(query);\n return encodedQuery.length ? \"?\" + encodedQuery : \"\";\n }\n}\n","// imported from https://github.com/unshiftio/yeast\n'use strict';\nconst alphabet = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_'.split(''), length = 64, map = {};\nlet seed = 0, i = 0, prev;\n/**\n * Return a string representing the specified number.\n *\n * @param {Number} num The number to convert.\n * @returns {String} The string representation of the number.\n * @api public\n */\nexport function encode(num) {\n let encoded = '';\n do {\n encoded = alphabet[num % length] + encoded;\n num = Math.floor(num / length);\n } while (num > 0);\n return encoded;\n}\n/**\n * Return the integer value specified by the given string.\n *\n * @param {String} str The string to convert.\n * @returns {Number} The integer value represented by the string.\n * @api public\n */\nexport function decode(str) {\n let decoded = 0;\n for (i = 0; i < str.length; i++) {\n decoded = decoded * length + map[str.charAt(i)];\n }\n return decoded;\n}\n/**\n * Yeast: A tiny growing id generator.\n *\n * @returns {String} A unique id.\n * @api public\n */\nexport function yeast() {\n const now = encode(+new Date());\n if (now !== prev)\n return seed = 0, prev = now;\n return now + '.' + encode(seed++);\n}\n//\n// Map each character to its index.\n//\nfor (; i < length; i++)\n map[alphabet[i]] = i;\n","// imported from https://github.com/galkn/querystring\n/**\n * Compiles a querystring\n * Returns string representation of the object\n *\n * @param {Object}\n * @api private\n */\nexport function encode(obj) {\n let str = '';\n for (let i in obj) {\n if (obj.hasOwnProperty(i)) {\n if (str.length)\n str += '&';\n str += encodeURIComponent(i) + '=' + encodeURIComponent(obj[i]);\n }\n }\n return str;\n}\n/**\n * Parses a simple querystring into an object\n *\n * @param {String} qs\n * @api private\n */\nexport function decode(qs) {\n let qry = {};\n let pairs = qs.split('&');\n for (let i = 0, l = pairs.length; i < l; i++) {\n let pair = pairs[i].split('=');\n qry[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1]);\n }\n return qry;\n}\n","// imported from https://github.com/component/has-cors\nlet value = false;\ntry {\n value = typeof XMLHttpRequest !== 'undefined' &&\n 'withCredentials' in new XMLHttpRequest();\n}\ncatch (err) {\n // if XMLHttp support is disabled in IE then it will throw\n // when trying to create\n}\nexport const hasCORS = value;\n","// browser shim for xmlhttprequest module\nimport { hasCORS } from \"../contrib/has-cors.js\";\nimport { globalThisShim as globalThis } from \"../globalThis.js\";\nexport function XHR(opts) {\n const xdomain = opts.xdomain;\n // XMLHttpRequest can be disabled on IE\n try {\n if (\"undefined\" !== typeof XMLHttpRequest && (!xdomain || hasCORS)) {\n return new XMLHttpRequest();\n }\n }\n catch (e) { }\n if (!xdomain) {\n try {\n return new globalThis[[\"Active\"].concat(\"Object\").join(\"X\")](\"Microsoft.XMLHTTP\");\n }\n catch (e) { }\n }\n}\nexport function createCookieJar() { }\n","import { Transport } from \"../transport.js\";\nimport { yeast } from \"../contrib/yeast.js\";\nimport { encodePayload, decodePayload } from \"engine.io-parser\";\nimport { createCookieJar, XHR as XMLHttpRequest, } from \"./xmlhttprequest.js\";\nimport { Emitter } from \"@socket.io/component-emitter\";\nimport { installTimerFunctions, pick } from \"../util.js\";\nimport { globalThisShim as globalThis } from \"../globalThis.js\";\nfunction empty() { }\nconst hasXHR2 = (function () {\n const xhr = new XMLHttpRequest({\n xdomain: false,\n });\n return null != xhr.responseType;\n})();\nexport class Polling extends Transport {\n /**\n * XHR Polling constructor.\n *\n * @param {Object} opts\n * @package\n */\n constructor(opts) {\n super(opts);\n this.polling = false;\n if (typeof location !== \"undefined\") {\n const isSSL = \"https:\" === location.protocol;\n let port = location.port;\n // some user agents have empty `location.port`\n if (!port) {\n port = isSSL ? \"443\" : \"80\";\n }\n this.xd =\n (typeof location !== \"undefined\" &&\n opts.hostname !== location.hostname) ||\n port !== opts.port;\n }\n /**\n * XHR supports binary\n */\n const forceBase64 = opts && opts.forceBase64;\n this.supportsBinary = hasXHR2 && !forceBase64;\n if (this.opts.withCredentials) {\n this.cookieJar = createCookieJar();\n }\n }\n get name() {\n return \"polling\";\n }\n /**\n * Opens the socket (triggers polling). We write a PING message to determine\n * when the transport is open.\n *\n * @protected\n */\n doOpen() {\n this.poll();\n }\n /**\n * Pauses polling.\n *\n * @param {Function} onPause - callback upon buffers are flushed and transport is paused\n * @package\n */\n pause(onPause) {\n this.readyState = \"pausing\";\n const pause = () => {\n this.readyState = \"paused\";\n onPause();\n };\n if (this.polling || !this.writable) {\n let total = 0;\n if (this.polling) {\n total++;\n this.once(\"pollComplete\", function () {\n --total || pause();\n });\n }\n if (!this.writable) {\n total++;\n this.once(\"drain\", function () {\n --total || pause();\n });\n }\n }\n else {\n pause();\n }\n }\n /**\n * Starts polling cycle.\n *\n * @private\n */\n poll() {\n this.polling = true;\n this.doPoll();\n this.emitReserved(\"poll\");\n }\n /**\n * Overloads onData to detect payloads.\n *\n * @protected\n */\n onData(data) {\n const callback = (packet) => {\n // if its the first message we consider the transport open\n if (\"opening\" === this.readyState && packet.type === \"open\") {\n this.onOpen();\n }\n // if its a close packet, we close the ongoing requests\n if (\"close\" === packet.type) {\n this.onClose({ description: \"transport closed by the server\" });\n return false;\n }\n // otherwise bypass onData and handle the message\n this.onPacket(packet);\n };\n // decode payload\n decodePayload(data, this.socket.binaryType).forEach(callback);\n // if an event did not trigger closing\n if (\"closed\" !== this.readyState) {\n // if we got data we're not polling\n this.polling = false;\n this.emitReserved(\"pollComplete\");\n if (\"open\" === this.readyState) {\n this.poll();\n }\n else {\n }\n }\n }\n /**\n * For polling, send a close packet.\n *\n * @protected\n */\n doClose() {\n const close = () => {\n this.write([{ type: \"close\" }]);\n };\n if (\"open\" === this.readyState) {\n close();\n }\n else {\n // in case we're trying to close while\n // handshaking is in progress (GH-164)\n this.once(\"open\", close);\n }\n }\n /**\n * Writes a packets payload.\n *\n * @param {Array} packets - data packets\n * @protected\n */\n write(packets) {\n this.writable = false;\n encodePayload(packets, (data) => {\n this.doWrite(data, () => {\n this.writable = true;\n this.emitReserved(\"drain\");\n });\n });\n }\n /**\n * Generates uri for connection.\n *\n * @private\n */\n uri() {\n const schema = this.opts.secure ? \"https\" : \"http\";\n const query = this.query || {};\n // cache busting is forced\n if (false !== this.opts.timestampRequests) {\n query[this.opts.timestampParam] = yeast();\n }\n if (!this.supportsBinary && !query.sid) {\n query.b64 = 1;\n }\n return this.createUri(schema, query);\n }\n /**\n * Creates a request.\n *\n * @param {String} method\n * @private\n */\n request(opts = {}) {\n Object.assign(opts, { xd: this.xd, cookieJar: this.cookieJar }, this.opts);\n return new Request(this.uri(), opts);\n }\n /**\n * Sends data.\n *\n * @param {String} data to send.\n * @param {Function} called upon flush.\n * @private\n */\n doWrite(data, fn) {\n const req = this.request({\n method: \"POST\",\n data: data,\n });\n req.on(\"success\", fn);\n req.on(\"error\", (xhrStatus, context) => {\n this.onError(\"xhr post error\", xhrStatus, context);\n });\n }\n /**\n * Starts a poll cycle.\n *\n * @private\n */\n doPoll() {\n const req = this.request();\n req.on(\"data\", this.onData.bind(this));\n req.on(\"error\", (xhrStatus, context) => {\n this.onError(\"xhr poll error\", xhrStatus, context);\n });\n this.pollXhr = req;\n }\n}\nexport class Request extends Emitter {\n /**\n * Request constructor\n *\n * @param {Object} options\n * @package\n */\n constructor(uri, opts) {\n super();\n installTimerFunctions(this, opts);\n this.opts = opts;\n this.method = opts.method || \"GET\";\n this.uri = uri;\n this.data = undefined !== opts.data ? opts.data : null;\n this.create();\n }\n /**\n * Creates the XHR object and sends the request.\n *\n * @private\n */\n create() {\n var _a;\n const opts = pick(this.opts, \"agent\", \"pfx\", \"key\", \"passphrase\", \"cert\", \"ca\", \"ciphers\", \"rejectUnauthorized\", \"autoUnref\");\n opts.xdomain = !!this.opts.xd;\n const xhr = (this.xhr = new XMLHttpRequest(opts));\n try {\n xhr.open(this.method, this.uri, true);\n try {\n if (this.opts.extraHeaders) {\n xhr.setDisableHeaderCheck && xhr.setDisableHeaderCheck(true);\n for (let i in this.opts.extraHeaders) {\n if (this.opts.extraHeaders.hasOwnProperty(i)) {\n xhr.setRequestHeader(i, this.opts.extraHeaders[i]);\n }\n }\n }\n }\n catch (e) { }\n if (\"POST\" === this.method) {\n try {\n xhr.setRequestHeader(\"Content-type\", \"text/plain;charset=UTF-8\");\n }\n catch (e) { }\n }\n try {\n xhr.setRequestHeader(\"Accept\", \"*/*\");\n }\n catch (e) { }\n (_a = this.opts.cookieJar) === null || _a === void 0 ? void 0 : _a.addCookies(xhr);\n // ie6 check\n if (\"withCredentials\" in xhr) {\n xhr.withCredentials = this.opts.withCredentials;\n }\n if (this.opts.requestTimeout) {\n xhr.timeout = this.opts.requestTimeout;\n }\n xhr.onreadystatechange = () => {\n var _a;\n if (xhr.readyState === 3) {\n (_a = this.opts.cookieJar) === null || _a === void 0 ? void 0 : _a.parseCookies(xhr);\n }\n if (4 !== xhr.readyState)\n return;\n if (200 === xhr.status || 1223 === xhr.status) {\n this.onLoad();\n }\n else {\n // make sure the `error` event handler that's user-set\n // does not throw in the same tick and gets caught here\n this.setTimeoutFn(() => {\n this.onError(typeof xhr.status === \"number\" ? xhr.status : 0);\n }, 0);\n }\n };\n xhr.send(this.data);\n }\n catch (e) {\n // Need to defer since .create() is called directly from the constructor\n // and thus the 'error' event can only be only bound *after* this exception\n // occurs. Therefore, also, we cannot throw here at all.\n this.setTimeoutFn(() => {\n this.onError(e);\n }, 0);\n return;\n }\n if (typeof document !== \"undefined\") {\n this.index = Request.requestsCount++;\n Request.requests[this.index] = this;\n }\n }\n /**\n * Called upon error.\n *\n * @private\n */\n onError(err) {\n this.emitReserved(\"error\", err, this.xhr);\n this.cleanup(true);\n }\n /**\n * Cleans up house.\n *\n * @private\n */\n cleanup(fromError) {\n if (\"undefined\" === typeof this.xhr || null === this.xhr) {\n return;\n }\n this.xhr.onreadystatechange = empty;\n if (fromError) {\n try {\n this.xhr.abort();\n }\n catch (e) { }\n }\n if (typeof document !== \"undefined\") {\n delete Request.requests[this.index];\n }\n this.xhr = null;\n }\n /**\n * Called upon load.\n *\n * @private\n */\n onLoad() {\n const data = this.xhr.responseText;\n if (data !== null) {\n this.emitReserved(\"data\", data);\n this.emitReserved(\"success\");\n this.cleanup();\n }\n }\n /**\n * Aborts the request.\n *\n * @package\n */\n abort() {\n this.cleanup();\n }\n}\nRequest.requestsCount = 0;\nRequest.requests = {};\n/**\n * Aborts pending requests when unloading the window. This is needed to prevent\n * memory leaks (e.g. when using IE) and to ensure that no spurious error is\n * emitted.\n */\nif (typeof document !== \"undefined\") {\n // @ts-ignore\n if (typeof attachEvent === \"function\") {\n // @ts-ignore\n attachEvent(\"onunload\", unloadHandler);\n }\n else if (typeof addEventListener === \"function\") {\n const terminationEvent = \"onpagehide\" in globalThis ? \"pagehide\" : \"unload\";\n addEventListener(terminationEvent, unloadHandler, false);\n }\n}\nfunction unloadHandler() {\n for (let i in Request.requests) {\n if (Request.requests.hasOwnProperty(i)) {\n Request.requests[i].abort();\n }\n }\n}\n","import { globalThisShim as globalThis } from \"../globalThis.js\";\nexport const nextTick = (() => {\n const isPromiseAvailable = typeof Promise === \"function\" && typeof Promise.resolve === \"function\";\n if (isPromiseAvailable) {\n return (cb) => Promise.resolve().then(cb);\n }\n else {\n return (cb, setTimeoutFn) => setTimeoutFn(cb, 0);\n }\n})();\nexport const WebSocket = globalThis.WebSocket || globalThis.MozWebSocket;\nexport const usingBrowserWebSocket = true;\nexport const defaultBinaryType = \"arraybuffer\";\n","import { Transport } from \"../transport.js\";\nimport { yeast } from \"../contrib/yeast.js\";\nimport { pick } from \"../util.js\";\nimport { nextTick, usingBrowserWebSocket, WebSocket, } from \"./websocket-constructor.js\";\nimport { encodePacket } from \"engine.io-parser\";\n// detect ReactNative environment\nconst isReactNative = typeof navigator !== \"undefined\" &&\n typeof navigator.product === \"string\" &&\n navigator.product.toLowerCase() === \"reactnative\";\nexport class WS extends Transport {\n /**\n * WebSocket transport constructor.\n *\n * @param {Object} opts - connection options\n * @protected\n */\n constructor(opts) {\n super(opts);\n this.supportsBinary = !opts.forceBase64;\n }\n get name() {\n return \"websocket\";\n }\n doOpen() {\n if (!this.check()) {\n // let probe timeout\n return;\n }\n const uri = this.uri();\n const protocols = this.opts.protocols;\n // React Native only supports the 'headers' option, and will print a warning if anything else is passed\n const opts = isReactNative\n ? {}\n : pick(this.opts, \"agent\", \"perMessageDeflate\", \"pfx\", \"key\", \"passphrase\", \"cert\", \"ca\", \"ciphers\", \"rejectUnauthorized\", \"localAddress\", \"protocolVersion\", \"origin\", \"maxPayload\", \"family\", \"checkServerIdentity\");\n if (this.opts.extraHeaders) {\n opts.headers = this.opts.extraHeaders;\n }\n try {\n this.ws =\n usingBrowserWebSocket && !isReactNative\n ? protocols\n ? new WebSocket(uri, protocols)\n : new WebSocket(uri)\n : new WebSocket(uri, protocols, opts);\n }\n catch (err) {\n return this.emitReserved(\"error\", err);\n }\n this.ws.binaryType = this.socket.binaryType;\n this.addEventListeners();\n }\n /**\n * Adds event listeners to the socket\n *\n * @private\n */\n addEventListeners() {\n this.ws.onopen = () => {\n if (this.opts.autoUnref) {\n this.ws._socket.unref();\n }\n this.onOpen();\n };\n this.ws.onclose = (closeEvent) => this.onClose({\n description: \"websocket connection closed\",\n context: closeEvent,\n });\n this.ws.onmessage = (ev) => this.onData(ev.data);\n this.ws.onerror = (e) => this.onError(\"websocket error\", e);\n }\n write(packets) {\n this.writable = false;\n // encodePacket efficient as it uses WS framing\n // no need for encodePayload\n for (let i = 0; i < packets.length; i++) {\n const packet = packets[i];\n const lastPacket = i === packets.length - 1;\n encodePacket(packet, this.supportsBinary, (data) => {\n // always create a new object (GH-437)\n const opts = {};\n if (!usingBrowserWebSocket) {\n if (packet.options) {\n opts.compress = packet.options.compress;\n }\n if (this.opts.perMessageDeflate) {\n const len = \n // @ts-ignore\n \"string\" === typeof data ? Buffer.byteLength(data) : data.length;\n if (len < this.opts.perMessageDeflate.threshold) {\n opts.compress = false;\n }\n }\n }\n // Sometimes the websocket has already been closed but the browser didn't\n // have a chance of informing us about it yet, in that case send will\n // throw an error\n try {\n if (usingBrowserWebSocket) {\n // TypeError is thrown when passing the second argument on Safari\n this.ws.send(data);\n }\n else {\n this.ws.send(data, opts);\n }\n }\n catch (e) {\n }\n if (lastPacket) {\n // fake drain\n // defer to next tick to allow Socket to clear writeBuffer\n nextTick(() => {\n this.writable = true;\n this.emitReserved(\"drain\");\n }, this.setTimeoutFn);\n }\n });\n }\n }\n doClose() {\n if (typeof this.ws !== \"undefined\") {\n this.ws.close();\n this.ws = null;\n }\n }\n /**\n * Generates uri for connection.\n *\n * @private\n */\n uri() {\n const schema = this.opts.secure ? \"wss\" : \"ws\";\n const query = this.query || {};\n // append timestamp to URI\n if (this.opts.timestampRequests) {\n query[this.opts.timestampParam] = yeast();\n }\n // communicate binary support capabilities\n if (!this.supportsBinary) {\n query.b64 = 1;\n }\n return this.createUri(schema, query);\n }\n /**\n * Feature detection for WebSocket.\n *\n * @return {Boolean} whether this transport is available.\n * @private\n */\n check() {\n return !!WebSocket;\n }\n}\n","import { Transport } from \"../transport.js\";\nimport { nextTick } from \"./websocket-constructor.js\";\nimport { createPacketDecoderStream, createPacketEncoderStream, } from \"engine.io-parser\";\nexport class WT extends Transport {\n get name() {\n return \"webtransport\";\n }\n doOpen() {\n // @ts-ignore\n if (typeof WebTransport !== \"function\") {\n return;\n }\n // @ts-ignore\n this.transport = new WebTransport(this.createUri(\"https\"), this.opts.transportOptions[this.name]);\n this.transport.closed\n .then(() => {\n this.onClose();\n })\n .catch((err) => {\n this.onError(\"webtransport error\", err);\n });\n // note: we could have used async/await, but that would require some additional polyfills\n this.transport.ready.then(() => {\n this.transport.createBidirectionalStream().then((stream) => {\n const decoderStream = createPacketDecoderStream(Number.MAX_SAFE_INTEGER, this.socket.binaryType);\n const reader = stream.readable.pipeThrough(decoderStream).getReader();\n const encoderStream = createPacketEncoderStream();\n encoderStream.readable.pipeTo(stream.writable);\n this.writer = encoderStream.writable.getWriter();\n const read = () => {\n reader\n .read()\n .then(({ done, value }) => {\n if (done) {\n return;\n }\n this.onPacket(value);\n read();\n })\n .catch((err) => {\n });\n };\n read();\n const packet = { type: \"open\" };\n if (this.query.sid) {\n packet.data = `{\"sid\":\"${this.query.sid}\"}`;\n }\n this.writer.write(packet).then(() => this.onOpen());\n });\n });\n }\n write(packets) {\n this.writable = false;\n for (let i = 0; i < packets.length; i++) {\n const packet = packets[i];\n const lastPacket = i === packets.length - 1;\n this.writer.write(packet).then(() => {\n if (lastPacket) {\n nextTick(() => {\n this.writable = true;\n this.emitReserved(\"drain\");\n }, this.setTimeoutFn);\n }\n });\n }\n }\n doClose() {\n var _a;\n (_a = this.transport) === null || _a === void 0 ? void 0 : _a.close();\n }\n}\n","import { Polling } from \"./polling.js\";\nimport { WS } from \"./websocket.js\";\nimport { WT } from \"./webtransport.js\";\nexport const transports = {\n websocket: WS,\n webtransport: WT,\n polling: Polling,\n};\n","// imported from https://github.com/galkn/parseuri\n/**\n * Parses a URI\n *\n * Note: we could also have used the built-in URL object, but it isn't supported on all platforms.\n *\n * See:\n * - https://developer.mozilla.org/en-US/docs/Web/API/URL\n * - https://caniuse.com/url\n * - https://www.rfc-editor.org/rfc/rfc3986#appendix-B\n *\n * History of the parse() method:\n * - first commit: https://github.com/socketio/socket.io-client/commit/4ee1d5d94b3906a9c052b459f1a818b15f38f91c\n * - export into its own module: https://github.com/socketio/engine.io-client/commit/de2c561e4564efeb78f1bdb1ba39ef81b2822cb3\n * - reimport: https://github.com/socketio/engine.io-client/commit/df32277c3f6d622eec5ed09f493cae3f3391d242\n *\n * @author Steven Levithan (MIT license)\n * @api private\n */\nconst re = /^(?:(?![^:@\\/?#]+:[^:@\\/]*@)(http|https|ws|wss):\\/\\/)?((?:(([^:@\\/?#]*)(?::([^:@\\/?#]*))?)?@)?((?:[a-f0-9]{0,4}:){2,7}[a-f0-9]{0,4}|[^:\\/?#]*)(?::(\\d*))?)(((\\/(?:[^?#](?![^?#\\/]*\\.[^?#\\/.]+(?:[?#]|$)))*\\/?)?([^?#\\/]*))(?:\\?([^#]*))?(?:#(.*))?)/;\nconst parts = [\n 'source', 'protocol', 'authority', 'userInfo', 'user', 'password', 'host', 'port', 'relative', 'path', 'directory', 'file', 'query', 'anchor'\n];\nexport function parse(str) {\n const src = str, b = str.indexOf('['), e = str.indexOf(']');\n if (b != -1 && e != -1) {\n str = str.substring(0, b) + str.substring(b, e).replace(/:/g, ';') + str.substring(e, str.length);\n }\n let m = re.exec(str || ''), uri = {}, i = 14;\n while (i--) {\n uri[parts[i]] = m[i] || '';\n }\n if (b != -1 && e != -1) {\n uri.source = src;\n uri.host = uri.host.substring(1, uri.host.length - 1).replace(/;/g, ':');\n uri.authority = uri.authority.replace('[', '').replace(']', '').replace(/;/g, ':');\n uri.ipv6uri = true;\n }\n uri.pathNames = pathNames(uri, uri['path']);\n uri.queryKey = queryKey(uri, uri['query']);\n return uri;\n}\nfunction pathNames(obj, path) {\n const regx = /\\/{2,9}/g, names = path.replace(regx, \"/\").split(\"/\");\n if (path.slice(0, 1) == '/' || path.length === 0) {\n names.splice(0, 1);\n }\n if (path.slice(-1) == '/') {\n names.splice(names.length - 1, 1);\n }\n return names;\n}\nfunction queryKey(uri, query) {\n const data = {};\n query.replace(/(?:^|&)([^&=]*)=?([^&]*)/g, function ($0, $1, $2) {\n if ($1) {\n data[$1] = $2;\n }\n });\n return data;\n}\n","import { transports } from \"./transports/index.js\";\nimport { installTimerFunctions, byteLength } from \"./util.js\";\nimport { decode } from \"./contrib/parseqs.js\";\nimport { parse } from \"./contrib/parseuri.js\";\nimport { Emitter } from \"@socket.io/component-emitter\";\nimport { protocol } from \"engine.io-parser\";\nimport { defaultBinaryType } from \"./transports/websocket-constructor.js\";\nexport class Socket extends Emitter {\n /**\n * Socket constructor.\n *\n * @param {String|Object} uri - uri or options\n * @param {Object} opts - options\n */\n constructor(uri, opts = {}) {\n super();\n this.binaryType = defaultBinaryType;\n this.writeBuffer = [];\n if (uri && \"object\" === typeof uri) {\n opts = uri;\n uri = null;\n }\n if (uri) {\n uri = parse(uri);\n opts.hostname = uri.host;\n opts.secure = uri.protocol === \"https\" || uri.protocol === \"wss\";\n opts.port = uri.port;\n if (uri.query)\n opts.query = uri.query;\n }\n else if (opts.host) {\n opts.hostname = parse(opts.host).host;\n }\n installTimerFunctions(this, opts);\n this.secure =\n null != opts.secure\n ? opts.secure\n : typeof location !== \"undefined\" && \"https:\" === location.protocol;\n if (opts.hostname && !opts.port) {\n // if no port is specified manually, use the protocol default\n opts.port = this.secure ? \"443\" : \"80\";\n }\n this.hostname =\n opts.hostname ||\n (typeof location !== \"undefined\" ? location.hostname : \"localhost\");\n this.port =\n opts.port ||\n (typeof location !== \"undefined\" && location.port\n ? location.port\n : this.secure\n ? \"443\"\n : \"80\");\n this.transports = opts.transports || [\n \"polling\",\n \"websocket\",\n \"webtransport\",\n ];\n this.writeBuffer = [];\n this.prevBufferLen = 0;\n this.opts = Object.assign({\n path: \"/engine.io\",\n agent: false,\n withCredentials: false,\n upgrade: true,\n timestampParam: \"t\",\n rememberUpgrade: false,\n addTrailingSlash: true,\n rejectUnauthorized: true,\n perMessageDeflate: {\n threshold: 1024,\n },\n transportOptions: {},\n closeOnBeforeunload: false,\n }, opts);\n this.opts.path =\n this.opts.path.replace(/\\/$/, \"\") +\n (this.opts.addTrailingSlash ? \"/\" : \"\");\n if (typeof this.opts.query === \"string\") {\n this.opts.query = decode(this.opts.query);\n }\n // set on handshake\n this.id = null;\n this.upgrades = null;\n this.pingInterval = null;\n this.pingTimeout = null;\n // set on heartbeat\n this.pingTimeoutTimer = null;\n if (typeof addEventListener === \"function\") {\n if (this.opts.closeOnBeforeunload) {\n // Firefox closes the connection when the \"beforeunload\" event is emitted but not Chrome. This event listener\n // ensures every browser behaves the same (no \"disconnect\" event at the Socket.IO level when the page is\n // closed/reloaded)\n this.beforeunloadEventListener = () => {\n if (this.transport) {\n // silently close the transport\n this.transport.removeAllListeners();\n this.transport.close();\n }\n };\n addEventListener(\"beforeunload\", this.beforeunloadEventListener, false);\n }\n if (this.hostname !== \"localhost\") {\n this.offlineEventListener = () => {\n this.onClose(\"transport close\", {\n description: \"network connection lost\",\n });\n };\n addEventListener(\"offline\", this.offlineEventListener, false);\n }\n }\n this.open();\n }\n /**\n * Creates transport of the given type.\n *\n * @param {String} name - transport name\n * @return {Transport}\n * @private\n */\n createTransport(name) {\n const query = Object.assign({}, this.opts.query);\n // append engine.io protocol identifier\n query.EIO = protocol;\n // transport name\n query.transport = name;\n // session id if we already have one\n if (this.id)\n query.sid = this.id;\n const opts = Object.assign({}, this.opts, {\n query,\n socket: this,\n hostname: this.hostname,\n secure: this.secure,\n port: this.port,\n }, this.opts.transportOptions[name]);\n return new transports[name](opts);\n }\n /**\n * Initializes transport to use and starts probe.\n *\n * @private\n */\n open() {\n let transport;\n if (this.opts.rememberUpgrade &&\n Socket.priorWebsocketSuccess &&\n this.transports.indexOf(\"websocket\") !== -1) {\n transport = \"websocket\";\n }\n else if (0 === this.transports.length) {\n // Emit error on next tick so it can be listened to\n this.setTimeoutFn(() => {\n this.emitReserved(\"error\", \"No transports available\");\n }, 0);\n return;\n }\n else {\n transport = this.transports[0];\n }\n this.readyState = \"opening\";\n // Retry with the next transport if the transport is disabled (jsonp: false)\n try {\n transport = this.createTransport(transport);\n }\n catch (e) {\n this.transports.shift();\n this.open();\n return;\n }\n transport.open();\n this.setTransport(transport);\n }\n /**\n * Sets the current transport. Disables the existing one (if any).\n *\n * @private\n */\n setTransport(transport) {\n if (this.transport) {\n this.transport.removeAllListeners();\n }\n // set up transport\n this.transport = transport;\n // set up transport listeners\n transport\n .on(\"drain\", this.onDrain.bind(this))\n .on(\"packet\", this.onPacket.bind(this))\n .on(\"error\", this.onError.bind(this))\n .on(\"close\", (reason) => this.onClose(\"transport close\", reason));\n }\n /**\n * Probes a transport.\n *\n * @param {String} name - transport name\n * @private\n */\n probe(name) {\n let transport = this.createTransport(name);\n let failed = false;\n Socket.priorWebsocketSuccess = false;\n const onTransportOpen = () => {\n if (failed)\n return;\n transport.send([{ type: \"ping\", data: \"probe\" }]);\n transport.once(\"packet\", (msg) => {\n if (failed)\n return;\n if (\"pong\" === msg.type && \"probe\" === msg.data) {\n this.upgrading = true;\n this.emitReserved(\"upgrading\", transport);\n if (!transport)\n return;\n Socket.priorWebsocketSuccess = \"websocket\" === transport.name;\n this.transport.pause(() => {\n if (failed)\n return;\n if (\"closed\" === this.readyState)\n return;\n cleanup();\n this.setTransport(transport);\n transport.send([{ type: \"upgrade\" }]);\n this.emitReserved(\"upgrade\", transport);\n transport = null;\n this.upgrading = false;\n this.flush();\n });\n }\n else {\n const err = new Error(\"probe error\");\n // @ts-ignore\n err.transport = transport.name;\n this.emitReserved(\"upgradeError\", err);\n }\n });\n };\n function freezeTransport() {\n if (failed)\n return;\n // Any callback called by transport should be ignored since now\n failed = true;\n cleanup();\n transport.close();\n transport = null;\n }\n // Handle any error that happens while probing\n const onerror = (err) => {\n const error = new Error(\"probe error: \" + err);\n // @ts-ignore\n error.transport = transport.name;\n freezeTransport();\n this.emitReserved(\"upgradeError\", error);\n };\n function onTransportClose() {\n onerror(\"transport closed\");\n }\n // When the socket is closed while we're probing\n function onclose() {\n onerror(\"socket closed\");\n }\n // When the socket is upgraded while we're probing\n function onupgrade(to) {\n if (transport && to.name !== transport.name) {\n freezeTransport();\n }\n }\n // Remove all listeners on the transport and on self\n const cleanup = () => {\n transport.removeListener(\"open\", onTransportOpen);\n transport.removeListener(\"error\", onerror);\n transport.removeListener(\"close\", onTransportClose);\n this.off(\"close\", onclose);\n this.off(\"upgrading\", onupgrade);\n };\n transport.once(\"open\", onTransportOpen);\n transport.once(\"error\", onerror);\n transport.once(\"close\", onTransportClose);\n this.once(\"close\", onclose);\n this.once(\"upgrading\", onupgrade);\n if (this.upgrades.indexOf(\"webtransport\") !== -1 &&\n name !== \"webtransport\") {\n // favor WebTransport\n this.setTimeoutFn(() => {\n if (!failed) {\n transport.open();\n }\n }, 200);\n }\n else {\n transport.open();\n }\n }\n /**\n * Called when connection is deemed open.\n *\n * @private\n */\n onOpen() {\n this.readyState = \"open\";\n Socket.priorWebsocketSuccess = \"websocket\" === this.transport.name;\n this.emitReserved(\"open\");\n this.flush();\n // we check for `readyState` in case an `open`\n // listener already closed the socket\n if (\"open\" === this.readyState && this.opts.upgrade) {\n let i = 0;\n const l = this.upgrades.length;\n for (; i < l; i++) {\n this.probe(this.upgrades[i]);\n }\n }\n }\n /**\n * Handles a packet.\n *\n * @private\n */\n onPacket(packet) {\n if (\"opening\" === this.readyState ||\n \"open\" === this.readyState ||\n \"closing\" === this.readyState) {\n this.emitReserved(\"packet\", packet);\n // Socket is live - any packet counts\n this.emitReserved(\"heartbeat\");\n this.resetPingTimeout();\n switch (packet.type) {\n case \"open\":\n this.onHandshake(JSON.parse(packet.data));\n break;\n case \"ping\":\n this.sendPacket(\"pong\");\n this.emitReserved(\"ping\");\n this.emitReserved(\"pong\");\n break;\n case \"error\":\n const err = new Error(\"server error\");\n // @ts-ignore\n err.code = packet.data;\n this.onError(err);\n break;\n case \"message\":\n this.emitReserved(\"data\", packet.data);\n this.emitReserved(\"message\", packet.data);\n break;\n }\n }\n else {\n }\n }\n /**\n * Called upon handshake completion.\n *\n * @param {Object} data - handshake obj\n * @private\n */\n onHandshake(data) {\n this.emitReserved(\"handshake\", data);\n this.id = data.sid;\n this.transport.query.sid = data.sid;\n this.upgrades = this.filterUpgrades(data.upgrades);\n this.pingInterval = data.pingInterval;\n this.pingTimeout = data.pingTimeout;\n this.maxPayload = data.maxPayload;\n this.onOpen();\n // In case open handler closes socket\n if (\"closed\" === this.readyState)\n return;\n this.resetPingTimeout();\n }\n /**\n * Sets and resets ping timeout timer based on server pings.\n *\n * @private\n */\n resetPingTimeout() {\n this.clearTimeoutFn(this.pingTimeoutTimer);\n this.pingTimeoutTimer = this.setTimeoutFn(() => {\n this.onClose(\"ping timeout\");\n }, this.pingInterval + this.pingTimeout);\n if (this.opts.autoUnref) {\n this.pingTimeoutTimer.unref();\n }\n }\n /**\n * Called on `drain` event\n *\n * @private\n */\n onDrain() {\n this.writeBuffer.splice(0, this.prevBufferLen);\n // setting prevBufferLen = 0 is very important\n // for example, when upgrading, upgrade packet is sent over,\n // and a nonzero prevBufferLen could cause problems on `drain`\n this.prevBufferLen = 0;\n if (0 === this.writeBuffer.length) {\n this.emitReserved(\"drain\");\n }\n else {\n this.flush();\n }\n }\n /**\n * Flush write buffers.\n *\n * @private\n */\n flush() {\n if (\"closed\" !== this.readyState &&\n this.transport.writable &&\n !this.upgrading &&\n this.writeBuffer.length) {\n const packets = this.getWritablePackets();\n this.transport.send(packets);\n // keep track of current length of writeBuffer\n // splice writeBuffer and callbackBuffer on `drain`\n this.prevBufferLen = packets.length;\n this.emitReserved(\"flush\");\n }\n }\n /**\n * Ensure the encoded size of the writeBuffer is below the maxPayload value sent by the server (only for HTTP\n * long-polling)\n *\n * @private\n */\n getWritablePackets() {\n const shouldCheckPayloadSize = this.maxPayload &&\n this.transport.name === \"polling\" &&\n this.writeBuffer.length > 1;\n if (!shouldCheckPayloadSize) {\n return this.writeBuffer;\n }\n let payloadSize = 1; // first packet type\n for (let i = 0; i < this.writeBuffer.length; i++) {\n const data = this.writeBuffer[i].data;\n if (data) {\n payloadSize += byteLength(data);\n }\n if (i > 0 && payloadSize > this.maxPayload) {\n return this.writeBuffer.slice(0, i);\n }\n payloadSize += 2; // separator + packet type\n }\n return this.writeBuffer;\n }\n /**\n * Sends a message.\n *\n * @param {String} msg - message.\n * @param {Object} options.\n * @param {Function} callback function.\n * @return {Socket} for chaining.\n */\n write(msg, options, fn) {\n this.sendPacket(\"message\", msg, options, fn);\n return this;\n }\n send(msg, options, fn) {\n this.sendPacket(\"message\", msg, options, fn);\n return this;\n }\n /**\n * Sends a packet.\n *\n * @param {String} type: packet type.\n * @param {String} data.\n * @param {Object} options.\n * @param {Function} fn - callback function.\n * @private\n */\n sendPacket(type, data, options, fn) {\n if (\"function\" === typeof data) {\n fn = data;\n data = undefined;\n }\n if (\"function\" === typeof options) {\n fn = options;\n options = null;\n }\n if (\"closing\" === this.readyState || \"closed\" === this.readyState) {\n return;\n }\n options = options || {};\n options.compress = false !== options.compress;\n const packet = {\n type: type,\n data: data,\n options: options,\n };\n this.emitReserved(\"packetCreate\", packet);\n this.writeBuffer.push(packet);\n if (fn)\n this.once(\"flush\", fn);\n this.flush();\n }\n /**\n * Closes the connection.\n */\n close() {\n const close = () => {\n this.onClose(\"forced close\");\n this.transport.close();\n };\n const cleanupAndClose = () => {\n this.off(\"upgrade\", cleanupAndClose);\n this.off(\"upgradeError\", cleanupAndClose);\n close();\n };\n const waitForUpgrade = () => {\n // wait for upgrade to finish since we can't send packets while pausing a transport\n this.once(\"upgrade\", cleanupAndClose);\n this.once(\"upgradeError\", cleanupAndClose);\n };\n if (\"opening\" === this.readyState || \"open\" === this.readyState) {\n this.readyState = \"closing\";\n if (this.writeBuffer.length) {\n this.once(\"drain\", () => {\n if (this.upgrading) {\n waitForUpgrade();\n }\n else {\n close();\n }\n });\n }\n else if (this.upgrading) {\n waitForUpgrade();\n }\n else {\n close();\n }\n }\n return this;\n }\n /**\n * Called upon transport error\n *\n * @private\n */\n onError(err) {\n Socket.priorWebsocketSuccess = false;\n this.emitReserved(\"error\", err);\n this.onClose(\"transport error\", err);\n }\n /**\n * Called upon transport close.\n *\n * @private\n */\n onClose(reason, description) {\n if (\"opening\" === this.readyState ||\n \"open\" === this.readyState ||\n \"closing\" === this.readyState) {\n // clear timers\n this.clearTimeoutFn(this.pingTimeoutTimer);\n // stop event from firing again for transport\n this.transport.removeAllListeners(\"close\");\n // ensure transport won't stay open\n this.transport.close();\n // ignore further transport communication\n this.transport.removeAllListeners();\n if (typeof removeEventListener === \"function\") {\n removeEventListener(\"beforeunload\", this.beforeunloadEventListener, false);\n removeEventListener(\"offline\", this.offlineEventListener, false);\n }\n // set ready state\n this.readyState = \"closed\";\n // clear session id\n this.id = null;\n // emit close event\n this.emitReserved(\"close\", reason, description);\n // clean buffers after, so users can still\n // grab the buffers on `close` event\n this.writeBuffer = [];\n this.prevBufferLen = 0;\n }\n }\n /**\n * Filters upgrades, returning only those matching client transports.\n *\n * @param {Array} upgrades - server upgrades\n * @private\n */\n filterUpgrades(upgrades) {\n const filteredUpgrades = [];\n let i = 0;\n const j = upgrades.length;\n for (; i < j; i++) {\n if (~this.transports.indexOf(upgrades[i]))\n filteredUpgrades.push(upgrades[i]);\n }\n return filteredUpgrades;\n }\n}\nSocket.protocol = protocol;\n","import { Socket } from \"./socket.js\";\nexport { Socket };\nexport const protocol = Socket.protocol;\nexport { Transport } from \"./transport.js\";\nexport { transports } from \"./transports/index.js\";\nexport { installTimerFunctions } from \"./util.js\";\nexport { parse } from \"./contrib/parseuri.js\";\nexport { nextTick } from \"./transports/websocket-constructor.js\";\n","const withNativeArrayBuffer = typeof ArrayBuffer === \"function\";\nconst isView = (obj) => {\n return typeof ArrayBuffer.isView === \"function\"\n ? ArrayBuffer.isView(obj)\n : obj.buffer instanceof ArrayBuffer;\n};\nconst toString = Object.prototype.toString;\nconst withNativeBlob = typeof Blob === \"function\" ||\n (typeof Blob !== \"undefined\" &&\n toString.call(Blob) === \"[object BlobConstructor]\");\nconst withNativeFile = typeof File === \"function\" ||\n (typeof File !== \"undefined\" &&\n toString.call(File) === \"[object FileConstructor]\");\n/**\n * Returns true if obj is a Buffer, an ArrayBuffer, a Blob or a File.\n *\n * @private\n */\nexport function isBinary(obj) {\n return ((withNativeArrayBuffer && (obj instanceof ArrayBuffer || isView(obj))) ||\n (withNativeBlob && obj instanceof Blob) ||\n (withNativeFile && obj instanceof File));\n}\nexport function hasBinary(obj, toJSON) {\n if (!obj || typeof obj !== \"object\") {\n return false;\n }\n if (Array.isArray(obj)) {\n for (let i = 0, l = obj.length; i < l; i++) {\n if (hasBinary(obj[i])) {\n return true;\n }\n }\n return false;\n }\n if (isBinary(obj)) {\n return true;\n }\n if (obj.toJSON &&\n typeof obj.toJSON === \"function\" &&\n arguments.length === 1) {\n return hasBinary(obj.toJSON(), true);\n }\n for (const key in obj) {\n if (Object.prototype.hasOwnProperty.call(obj, key) && hasBinary(obj[key])) {\n return true;\n }\n }\n return false;\n}\n","import { isBinary } from \"./is-binary.js\";\n/**\n * Replaces every Buffer | ArrayBuffer | Blob | File in packet with a numbered placeholder.\n *\n * @param {Object} packet - socket.io event packet\n * @return {Object} with deconstructed packet and list of buffers\n * @public\n */\nexport function deconstructPacket(packet) {\n const buffers = [];\n const packetData = packet.data;\n const pack = packet;\n pack.data = _deconstructPacket(packetData, buffers);\n pack.attachments = buffers.length; // number of binary 'attachments'\n return { packet: pack, buffers: buffers };\n}\nfunction _deconstructPacket(data, buffers) {\n if (!data)\n return data;\n if (isBinary(data)) {\n const placeholder = { _placeholder: true, num: buffers.length };\n buffers.push(data);\n return placeholder;\n }\n else if (Array.isArray(data)) {\n const newData = new Array(data.length);\n for (let i = 0; i < data.length; i++) {\n newData[i] = _deconstructPacket(data[i], buffers);\n }\n return newData;\n }\n else if (typeof data === \"object\" && !(data instanceof Date)) {\n const newData = {};\n for (const key in data) {\n if (Object.prototype.hasOwnProperty.call(data, key)) {\n newData[key] = _deconstructPacket(data[key], buffers);\n }\n }\n return newData;\n }\n return data;\n}\n/**\n * Reconstructs a binary packet from its placeholder packet and buffers\n *\n * @param {Object} packet - event packet with placeholders\n * @param {Array} buffers - binary buffers to put in placeholder positions\n * @return {Object} reconstructed packet\n * @public\n */\nexport function reconstructPacket(packet, buffers) {\n packet.data = _reconstructPacket(packet.data, buffers);\n delete packet.attachments; // no longer useful\n return packet;\n}\nfunction _reconstructPacket(data, buffers) {\n if (!data)\n return data;\n if (data && data._placeholder === true) {\n const isIndexValid = typeof data.num === \"number\" &&\n data.num >= 0 &&\n data.num < buffers.length;\n if (isIndexValid) {\n return buffers[data.num]; // appropriate buffer (should be natural order anyway)\n }\n else {\n throw new Error(\"illegal attachments\");\n }\n }\n else if (Array.isArray(data)) {\n for (let i = 0; i < data.length; i++) {\n data[i] = _reconstructPacket(data[i], buffers);\n }\n }\n else if (typeof data === \"object\") {\n for (const key in data) {\n if (Object.prototype.hasOwnProperty.call(data, key)) {\n data[key] = _reconstructPacket(data[key], buffers);\n }\n }\n }\n return data;\n}\n","import { Emitter } from \"@socket.io/component-emitter\";\nimport { deconstructPacket, reconstructPacket } from \"./binary.js\";\nimport { isBinary, hasBinary } from \"./is-binary.js\";\n/**\n * These strings must not be used as event names, as they have a special meaning.\n */\nconst RESERVED_EVENTS = [\n \"connect\",\n \"connect_error\",\n \"disconnect\",\n \"disconnecting\",\n \"newListener\",\n \"removeListener\", // used by the Node.js EventEmitter\n];\n/**\n * Protocol version.\n *\n * @public\n */\nexport const protocol = 5;\nexport var PacketType;\n(function (PacketType) {\n PacketType[PacketType[\"CONNECT\"] = 0] = \"CONNECT\";\n PacketType[PacketType[\"DISCONNECT\"] = 1] = \"DISCONNECT\";\n PacketType[PacketType[\"EVENT\"] = 2] = \"EVENT\";\n PacketType[PacketType[\"ACK\"] = 3] = \"ACK\";\n PacketType[PacketType[\"CONNECT_ERROR\"] = 4] = \"CONNECT_ERROR\";\n PacketType[PacketType[\"BINARY_EVENT\"] = 5] = \"BINARY_EVENT\";\n PacketType[PacketType[\"BINARY_ACK\"] = 6] = \"BINARY_ACK\";\n})(PacketType || (PacketType = {}));\n/**\n * A socket.io Encoder instance\n */\nexport class Encoder {\n /**\n * Encoder constructor\n *\n * @param {function} replacer - custom replacer to pass down to JSON.parse\n */\n constructor(replacer) {\n this.replacer = replacer;\n }\n /**\n * Encode a packet as a single string if non-binary, or as a\n * buffer sequence, depending on packet type.\n *\n * @param {Object} obj - packet object\n */\n encode(obj) {\n if (obj.type === PacketType.EVENT || obj.type === PacketType.ACK) {\n if (hasBinary(obj)) {\n return this.encodeAsBinary({\n type: obj.type === PacketType.EVENT\n ? PacketType.BINARY_EVENT\n : PacketType.BINARY_ACK,\n nsp: obj.nsp,\n data: obj.data,\n id: obj.id,\n });\n }\n }\n return [this.encodeAsString(obj)];\n }\n /**\n * Encode packet as string.\n */\n encodeAsString(obj) {\n // first is type\n let str = \"\" + obj.type;\n // attachments if we have them\n if (obj.type === PacketType.BINARY_EVENT ||\n obj.type === PacketType.BINARY_ACK) {\n str += obj.attachments + \"-\";\n }\n // if we have a namespace other than `/`\n // we append it followed by a comma `,`\n if (obj.nsp && \"/\" !== obj.nsp) {\n str += obj.nsp + \",\";\n }\n // immediately followed by the id\n if (null != obj.id) {\n str += obj.id;\n }\n // json data\n if (null != obj.data) {\n str += JSON.stringify(obj.data, this.replacer);\n }\n return str;\n }\n /**\n * Encode packet as 'buffer sequence' by removing blobs, and\n * deconstructing packet into object with placeholders and\n * a list of buffers.\n */\n encodeAsBinary(obj) {\n const deconstruction = deconstructPacket(obj);\n const pack = this.encodeAsString(deconstruction.packet);\n const buffers = deconstruction.buffers;\n buffers.unshift(pack); // add packet info to beginning of data list\n return buffers; // write all the buffers\n }\n}\n// see https://stackoverflow.com/questions/8511281/check-if-a-value-is-an-object-in-javascript\nfunction isObject(value) {\n return Object.prototype.toString.call(value) === \"[object Object]\";\n}\n/**\n * A socket.io Decoder instance\n *\n * @return {Object} decoder\n */\nexport class Decoder extends Emitter {\n /**\n * Decoder constructor\n *\n * @param {function} reviver - custom reviver to pass down to JSON.stringify\n */\n constructor(reviver) {\n super();\n this.reviver = reviver;\n }\n /**\n * Decodes an encoded packet string into packet JSON.\n *\n * @param {String} obj - encoded packet\n */\n add(obj) {\n let packet;\n if (typeof obj === \"string\") {\n if (this.reconstructor) {\n throw new Error(\"got plaintext data when reconstructing a packet\");\n }\n packet = this.decodeString(obj);\n const isBinaryEvent = packet.type === PacketType.BINARY_EVENT;\n if (isBinaryEvent || packet.type === PacketType.BINARY_ACK) {\n packet.type = isBinaryEvent ? PacketType.EVENT : PacketType.ACK;\n // binary packet's json\n this.reconstructor = new BinaryReconstructor(packet);\n // no attachments, labeled binary but no binary data to follow\n if (packet.attachments === 0) {\n super.emitReserved(\"decoded\", packet);\n }\n }\n else {\n // non-binary full packet\n super.emitReserved(\"decoded\", packet);\n }\n }\n else if (isBinary(obj) || obj.base64) {\n // raw binary data\n if (!this.reconstructor) {\n throw new Error(\"got binary data when not reconstructing a packet\");\n }\n else {\n packet = this.reconstructor.takeBinaryData(obj);\n if (packet) {\n // received final buffer\n this.reconstructor = null;\n super.emitReserved(\"decoded\", packet);\n }\n }\n }\n else {\n throw new Error(\"Unknown type: \" + obj);\n }\n }\n /**\n * Decode a packet String (JSON data)\n *\n * @param {String} str\n * @return {Object} packet\n */\n decodeString(str) {\n let i = 0;\n // look up type\n const p = {\n type: Number(str.charAt(0)),\n };\n if (PacketType[p.type] === undefined) {\n throw new Error(\"unknown packet type \" + p.type);\n }\n // look up attachments if type binary\n if (p.type === PacketType.BINARY_EVENT ||\n p.type === PacketType.BINARY_ACK) {\n const start = i + 1;\n while (str.charAt(++i) !== \"-\" && i != str.length) { }\n const buf = str.substring(start, i);\n if (buf != Number(buf) || str.charAt(i) !== \"-\") {\n throw new Error(\"Illegal attachments\");\n }\n p.attachments = Number(buf);\n }\n // look up namespace (if any)\n if (\"/\" === str.charAt(i + 1)) {\n const start = i + 1;\n while (++i) {\n const c = str.charAt(i);\n if (\",\" === c)\n break;\n if (i === str.length)\n break;\n }\n p.nsp = str.substring(start, i);\n }\n else {\n p.nsp = \"/\";\n }\n // look up id\n const next = str.charAt(i + 1);\n if (\"\" !== next && Number(next) == next) {\n const start = i + 1;\n while (++i) {\n const c = str.charAt(i);\n if (null == c || Number(c) != c) {\n --i;\n break;\n }\n if (i === str.length)\n break;\n }\n p.id = Number(str.substring(start, i + 1));\n }\n // look up json data\n if (str.charAt(++i)) {\n const payload = this.tryParse(str.substr(i));\n if (Decoder.isPayloadValid(p.type, payload)) {\n p.data = payload;\n }\n else {\n throw new Error(\"invalid payload\");\n }\n }\n return p;\n }\n tryParse(str) {\n try {\n return JSON.parse(str, this.reviver);\n }\n catch (e) {\n return false;\n }\n }\n static isPayloadValid(type, payload) {\n switch (type) {\n case PacketType.CONNECT:\n return isObject(payload);\n case PacketType.DISCONNECT:\n return payload === undefined;\n case PacketType.CONNECT_ERROR:\n return typeof payload === \"string\" || isObject(payload);\n case PacketType.EVENT:\n case PacketType.BINARY_EVENT:\n return (Array.isArray(payload) &&\n (typeof payload[0] === \"number\" ||\n (typeof payload[0] === \"string\" &&\n RESERVED_EVENTS.indexOf(payload[0]) === -1)));\n case PacketType.ACK:\n case PacketType.BINARY_ACK:\n return Array.isArray(payload);\n }\n }\n /**\n * Deallocates a parser's resources\n */\n destroy() {\n if (this.reconstructor) {\n this.reconstructor.finishedReconstruction();\n this.reconstructor = null;\n }\n }\n}\n/**\n * A manager of a binary event's 'buffer sequence'. Should\n * be constructed whenever a packet of type BINARY_EVENT is\n * decoded.\n *\n * @param {Object} packet\n * @return {BinaryReconstructor} initialized reconstructor\n */\nclass BinaryReconstructor {\n constructor(packet) {\n this.packet = packet;\n this.buffers = [];\n this.reconPack = packet;\n }\n /**\n * Method to be called when binary data received from connection\n * after a BINARY_EVENT packet.\n *\n * @param {Buffer | ArrayBuffer} binData - the raw binary data received\n * @return {null | Object} returns null if more binary data is expected or\n * a reconstructed packet object if all buffers have been received.\n */\n takeBinaryData(binData) {\n this.buffers.push(binData);\n if (this.buffers.length === this.reconPack.attachments) {\n // done with buffer list\n const packet = reconstructPacket(this.reconPack, this.buffers);\n this.finishedReconstruction();\n return packet;\n }\n return null;\n }\n /**\n * Cleans up binary packet reconstruction variables.\n */\n finishedReconstruction() {\n this.reconPack = null;\n this.buffers = [];\n }\n}\n","export function on(obj, ev, fn) {\n obj.on(ev, fn);\n return function subDestroy() {\n obj.off(ev, fn);\n };\n}\n","import { PacketType } from \"socket.io-parser\";\nimport { on } from \"./on.js\";\nimport { Emitter, } from \"@socket.io/component-emitter\";\n/**\n * Internal events.\n * These events can't be emitted by the user.\n */\nconst RESERVED_EVENTS = Object.freeze({\n connect: 1,\n connect_error: 1,\n disconnect: 1,\n disconnecting: 1,\n // EventEmitter reserved events: https://nodejs.org/api/events.html#events_event_newlistener\n newListener: 1,\n removeListener: 1,\n});\n/**\n * A Socket is the fundamental class for interacting with the server.\n *\n * A Socket belongs to a certain Namespace (by default /) and uses an underlying {@link Manager} to communicate.\n *\n * @example\n * const socket = io();\n *\n * socket.on(\"connect\", () => {\n * console.log(\"connected\");\n * });\n *\n * // send an event to the server\n * socket.emit(\"foo\", \"bar\");\n *\n * socket.on(\"foobar\", () => {\n * // an event was received from the server\n * });\n *\n * // upon disconnection\n * socket.on(\"disconnect\", (reason) => {\n * console.log(`disconnected due to ${reason}`);\n * });\n */\nexport class Socket extends Emitter {\n /**\n * `Socket` constructor.\n */\n constructor(io, nsp, opts) {\n super();\n /**\n * Whether the socket is currently connected to the server.\n *\n * @example\n * const socket = io();\n *\n * socket.on(\"connect\", () => {\n * console.log(socket.connected); // true\n * });\n *\n * socket.on(\"disconnect\", () => {\n * console.log(socket.connected); // false\n * });\n */\n this.connected = false;\n /**\n * Whether the connection state was recovered after a temporary disconnection. In that case, any missed packets will\n * be transmitted by the server.\n */\n this.recovered = false;\n /**\n * Buffer for packets received before the CONNECT packet\n */\n this.receiveBuffer = [];\n /**\n * Buffer for packets that will be sent once the socket is connected\n */\n this.sendBuffer = [];\n /**\n * The queue of packets to be sent with retry in case of failure.\n *\n * Packets are sent one by one, each waiting for the server acknowledgement, in order to guarantee the delivery order.\n * @private\n */\n this._queue = [];\n /**\n * A sequence to generate the ID of the {@link QueuedPacket}.\n * @private\n */\n this._queueSeq = 0;\n this.ids = 0;\n this.acks = {};\n this.flags = {};\n this.io = io;\n this.nsp = nsp;\n if (opts && opts.auth) {\n this.auth = opts.auth;\n }\n this._opts = Object.assign({}, opts);\n if (this.io._autoConnect)\n this.open();\n }\n /**\n * Whether the socket is currently disconnected\n *\n * @example\n * const socket = io();\n *\n * socket.on(\"connect\", () => {\n * console.log(socket.disconnected); // false\n * });\n *\n * socket.on(\"disconnect\", () => {\n * console.log(socket.disconnected); // true\n * });\n */\n get disconnected() {\n return !this.connected;\n }\n /**\n * Subscribe to open, close and packet events\n *\n * @private\n */\n subEvents() {\n if (this.subs)\n return;\n const io = this.io;\n this.subs = [\n on(io, \"open\", this.onopen.bind(this)),\n on(io, \"packet\", this.onpacket.bind(this)),\n on(io, \"error\", this.onerror.bind(this)),\n on(io, \"close\", this.onclose.bind(this)),\n ];\n }\n /**\n * Whether the Socket will try to reconnect when its Manager connects or reconnects.\n *\n * @example\n * const socket = io();\n *\n * console.log(socket.active); // true\n *\n * socket.on(\"disconnect\", (reason) => {\n * if (reason === \"io server disconnect\") {\n * // the disconnection was initiated by the server, you need to manually reconnect\n * console.log(socket.active); // false\n * }\n * // else the socket will automatically try to reconnect\n * console.log(socket.active); // true\n * });\n */\n get active() {\n return !!this.subs;\n }\n /**\n * \"Opens\" the socket.\n *\n * @example\n * const socket = io({\n * autoConnect: false\n * });\n *\n * socket.connect();\n */\n connect() {\n if (this.connected)\n return this;\n this.subEvents();\n if (!this.io[\"_reconnecting\"])\n this.io.open(); // ensure open\n if (\"open\" === this.io._readyState)\n this.onopen();\n return this;\n }\n /**\n * Alias for {@link connect()}.\n */\n open() {\n return this.connect();\n }\n /**\n * Sends a `message` event.\n *\n * This method mimics the WebSocket.send() method.\n *\n * @see https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/send\n *\n * @example\n * socket.send(\"hello\");\n *\n * // this is equivalent to\n * socket.emit(\"message\", \"hello\");\n *\n * @return self\n */\n send(...args) {\n args.unshift(\"message\");\n this.emit.apply(this, args);\n return this;\n }\n /**\n * Override `emit`.\n * If the event is in `events`, it's emitted normally.\n *\n * @example\n * socket.emit(\"hello\", \"world\");\n *\n * // all serializable datastructures are supported (no need to call JSON.stringify)\n * socket.emit(\"hello\", 1, \"2\", { 3: [\"4\"], 5: Uint8Array.from([6]) });\n *\n * // with an acknowledgement from the server\n * socket.emit(\"hello\", \"world\", (val) => {\n * // ...\n * });\n *\n * @return self\n */\n emit(ev, ...args) {\n if (RESERVED_EVENTS.hasOwnProperty(ev)) {\n throw new Error('\"' + ev.toString() + '\" is a reserved event name');\n }\n args.unshift(ev);\n if (this._opts.retries && !this.flags.fromQueue && !this.flags.volatile) {\n this._addToQueue(args);\n return this;\n }\n const packet = {\n type: PacketType.EVENT,\n data: args,\n };\n packet.options = {};\n packet.options.compress = this.flags.compress !== false;\n // event ack callback\n if (\"function\" === typeof args[args.length - 1]) {\n const id = this.ids++;\n const ack = args.pop();\n this._registerAckCallback(id, ack);\n packet.id = id;\n }\n const isTransportWritable = this.io.engine &&\n this.io.engine.transport &&\n this.io.engine.transport.writable;\n const discardPacket = this.flags.volatile && (!isTransportWritable || !this.connected);\n if (discardPacket) {\n }\n else if (this.connected) {\n this.notifyOutgoingListeners(packet);\n this.packet(packet);\n }\n else {\n this.sendBuffer.push(packet);\n }\n this.flags = {};\n return this;\n }\n /**\n * @private\n */\n _registerAckCallback(id, ack) {\n var _a;\n const timeout = (_a = this.flags.timeout) !== null && _a !== void 0 ? _a : this._opts.ackTimeout;\n if (timeout === undefined) {\n this.acks[id] = ack;\n return;\n }\n // @ts-ignore\n const timer = this.io.setTimeoutFn(() => {\n delete this.acks[id];\n for (let i = 0; i < this.sendBuffer.length; i++) {\n if (this.sendBuffer[i].id === id) {\n this.sendBuffer.splice(i, 1);\n }\n }\n ack.call(this, new Error(\"operation has timed out\"));\n }, timeout);\n this.acks[id] = (...args) => {\n // @ts-ignore\n this.io.clearTimeoutFn(timer);\n ack.apply(this, [null, ...args]);\n };\n }\n /**\n * Emits an event and waits for an acknowledgement\n *\n * @example\n * // without timeout\n * const response = await socket.emitWithAck(\"hello\", \"world\");\n *\n * // with a specific timeout\n * try {\n * const response = await socket.timeout(1000).emitWithAck(\"hello\", \"world\");\n * } catch (err) {\n * // the server did not acknowledge the event in the given delay\n * }\n *\n * @return a Promise that will be fulfilled when the server acknowledges the event\n */\n emitWithAck(ev, ...args) {\n // the timeout flag is optional\n const withErr = this.flags.timeout !== undefined || this._opts.ackTimeout !== undefined;\n return new Promise((resolve, reject) => {\n args.push((arg1, arg2) => {\n if (withErr) {\n return arg1 ? reject(arg1) : resolve(arg2);\n }\n else {\n return resolve(arg1);\n }\n });\n this.emit(ev, ...args);\n });\n }\n /**\n * Add the packet to the queue.\n * @param args\n * @private\n */\n _addToQueue(args) {\n let ack;\n if (typeof args[args.length - 1] === \"function\") {\n ack = args.pop();\n }\n const packet = {\n id: this._queueSeq++,\n tryCount: 0,\n pending: false,\n args,\n flags: Object.assign({ fromQueue: true }, this.flags),\n };\n args.push((err, ...responseArgs) => {\n if (packet !== this._queue[0]) {\n // the packet has already been acknowledged\n return;\n }\n const hasError = err !== null;\n if (hasError) {\n if (packet.tryCount > this._opts.retries) {\n this._queue.shift();\n if (ack) {\n ack(err);\n }\n }\n }\n else {\n this._queue.shift();\n if (ack) {\n ack(null, ...responseArgs);\n }\n }\n packet.pending = false;\n return this._drainQueue();\n });\n this._queue.push(packet);\n this._drainQueue();\n }\n /**\n * Send the first packet of the queue, and wait for an acknowledgement from the server.\n * @param force - whether to resend a packet that has not been acknowledged yet\n *\n * @private\n */\n _drainQueue(force = false) {\n if (!this.connected || this._queue.length === 0) {\n return;\n }\n const packet = this._queue[0];\n if (packet.pending && !force) {\n return;\n }\n packet.pending = true;\n packet.tryCount++;\n this.flags = packet.flags;\n this.emit.apply(this, packet.args);\n }\n /**\n * Sends a packet.\n *\n * @param packet\n * @private\n */\n packet(packet) {\n packet.nsp = this.nsp;\n this.io._packet(packet);\n }\n /**\n * Called upon engine `open`.\n *\n * @private\n */\n onopen() {\n if (typeof this.auth == \"function\") {\n this.auth((data) => {\n this._sendConnectPacket(data);\n });\n }\n else {\n this._sendConnectPacket(this.auth);\n }\n }\n /**\n * Sends a CONNECT packet to initiate the Socket.IO session.\n *\n * @param data\n * @private\n */\n _sendConnectPacket(data) {\n this.packet({\n type: PacketType.CONNECT,\n data: this._pid\n ? Object.assign({ pid: this._pid, offset: this._lastOffset }, data)\n : data,\n });\n }\n /**\n * Called upon engine or manager `error`.\n *\n * @param err\n * @private\n */\n onerror(err) {\n if (!this.connected) {\n this.emitReserved(\"connect_error\", err);\n }\n }\n /**\n * Called upon engine `close`.\n *\n * @param reason\n * @param description\n * @private\n */\n onclose(reason, description) {\n this.connected = false;\n delete this.id;\n this.emitReserved(\"disconnect\", reason, description);\n }\n /**\n * Called with socket packet.\n *\n * @param packet\n * @private\n */\n onpacket(packet) {\n const sameNamespace = packet.nsp === this.nsp;\n if (!sameNamespace)\n return;\n switch (packet.type) {\n case PacketType.CONNECT:\n if (packet.data && packet.data.sid) {\n this.onconnect(packet.data.sid, packet.data.pid);\n }\n else {\n this.emitReserved(\"connect_error\", new Error(\"It seems you are trying to reach a Socket.IO server in v2.x with a v3.x client, but they are not compatible (more information here: https://socket.io/docs/v3/migrating-from-2-x-to-3-0/)\"));\n }\n break;\n case PacketType.EVENT:\n case PacketType.BINARY_EVENT:\n this.onevent(packet);\n break;\n case PacketType.ACK:\n case PacketType.BINARY_ACK:\n this.onack(packet);\n break;\n case PacketType.DISCONNECT:\n this.ondisconnect();\n break;\n case PacketType.CONNECT_ERROR:\n this.destroy();\n const err = new Error(packet.data.message);\n // @ts-ignore\n err.data = packet.data.data;\n this.emitReserved(\"connect_error\", err);\n break;\n }\n }\n /**\n * Called upon a server event.\n *\n * @param packet\n * @private\n */\n onevent(packet) {\n const args = packet.data || [];\n if (null != packet.id) {\n args.push(this.ack(packet.id));\n }\n if (this.connected) {\n this.emitEvent(args);\n }\n else {\n this.receiveBuffer.push(Object.freeze(args));\n }\n }\n emitEvent(args) {\n if (this._anyListeners && this._anyListeners.length) {\n const listeners = this._anyListeners.slice();\n for (const listener of listeners) {\n listener.apply(this, args);\n }\n }\n super.emit.apply(this, args);\n if (this._pid && args.length && typeof args[args.length - 1] === \"string\") {\n this._lastOffset = args[args.length - 1];\n }\n }\n /**\n * Produces an ack callback to emit with an event.\n *\n * @private\n */\n ack(id) {\n const self = this;\n let sent = false;\n return function (...args) {\n // prevent double callbacks\n if (sent)\n return;\n sent = true;\n self.packet({\n type: PacketType.ACK,\n id: id,\n data: args,\n });\n };\n }\n /**\n * Called upon a server acknowlegement.\n *\n * @param packet\n * @private\n */\n onack(packet) {\n const ack = this.acks[packet.id];\n if (\"function\" === typeof ack) {\n ack.apply(this, packet.data);\n delete this.acks[packet.id];\n }\n else {\n }\n }\n /**\n * Called upon server connect.\n *\n * @private\n */\n onconnect(id, pid) {\n this.id = id;\n this.recovered = pid && this._pid === pid;\n this._pid = pid; // defined only if connection state recovery is enabled\n this.connected = true;\n this.emitBuffered();\n this.emitReserved(\"connect\");\n this._drainQueue(true);\n }\n /**\n * Emit buffered events (received and emitted).\n *\n * @private\n */\n emitBuffered() {\n this.receiveBuffer.forEach((args) => this.emitEvent(args));\n this.receiveBuffer = [];\n this.sendBuffer.forEach((packet) => {\n this.notifyOutgoingListeners(packet);\n this.packet(packet);\n });\n this.sendBuffer = [];\n }\n /**\n * Called upon server disconnect.\n *\n * @private\n */\n ondisconnect() {\n this.destroy();\n this.onclose(\"io server disconnect\");\n }\n /**\n * Called upon forced client/server side disconnections,\n * this method ensures the manager stops tracking us and\n * that reconnections don't get triggered for this.\n *\n * @private\n */\n destroy() {\n if (this.subs) {\n // clean subscriptions to avoid reconnections\n this.subs.forEach((subDestroy) => subDestroy());\n this.subs = undefined;\n }\n this.io[\"_destroy\"](this);\n }\n /**\n * Disconnects the socket manually. In that case, the socket will not try to reconnect.\n *\n * If this is the last active Socket instance of the {@link Manager}, the low-level connection will be closed.\n *\n * @example\n * const socket = io();\n *\n * socket.on(\"disconnect\", (reason) => {\n * // console.log(reason); prints \"io client disconnect\"\n * });\n *\n * socket.disconnect();\n *\n * @return self\n */\n disconnect() {\n if (this.connected) {\n this.packet({ type: PacketType.DISCONNECT });\n }\n // remove socket from pool\n this.destroy();\n if (this.connected) {\n // fire events\n this.onclose(\"io client disconnect\");\n }\n return this;\n }\n /**\n * Alias for {@link disconnect()}.\n *\n * @return self\n */\n close() {\n return this.disconnect();\n }\n /**\n * Sets the compress flag.\n *\n * @example\n * socket.compress(false).emit(\"hello\");\n *\n * @param compress - if `true`, compresses the sending data\n * @return self\n */\n compress(compress) {\n this.flags.compress = compress;\n return this;\n }\n /**\n * Sets a modifier for a subsequent event emission that the event message will be dropped when this socket is not\n * ready to send messages.\n *\n * @example\n * socket.volatile.emit(\"hello\"); // the server may or may not receive it\n *\n * @returns self\n */\n get volatile() {\n this.flags.volatile = true;\n return this;\n }\n /**\n * Sets a modifier for a subsequent event emission that the callback will be called with an error when the\n * given number of milliseconds have elapsed without an acknowledgement from the server:\n *\n * @example\n * socket.timeout(5000).emit(\"my-event\", (err) => {\n * if (err) {\n * // the server did not acknowledge the event in the given delay\n * }\n * });\n *\n * @returns self\n */\n timeout(timeout) {\n this.flags.timeout = timeout;\n return this;\n }\n /**\n * Adds a listener that will be fired when any event is emitted. The event name is passed as the first argument to the\n * callback.\n *\n * @example\n * socket.onAny((event, ...args) => {\n * console.log(`got ${event}`);\n * });\n *\n * @param listener\n */\n onAny(listener) {\n this._anyListeners = this._anyListeners || [];\n this._anyListeners.push(listener);\n return this;\n }\n /**\n * Adds a listener that will be fired when any event is emitted. The event name is passed as the first argument to the\n * callback. The listener is added to the beginning of the listeners array.\n *\n * @example\n * socket.prependAny((event, ...args) => {\n * console.log(`got event ${event}`);\n * });\n *\n * @param listener\n */\n prependAny(listener) {\n this._anyListeners = this._anyListeners || [];\n this._anyListeners.unshift(listener);\n return this;\n }\n /**\n * Removes the listener that will be fired when any event is emitted.\n *\n * @example\n * const catchAllListener = (event, ...args) => {\n * console.log(`got event ${event}`);\n * }\n *\n * socket.onAny(catchAllListener);\n *\n * // remove a specific listener\n * socket.offAny(catchAllListener);\n *\n * // or remove all listeners\n * socket.offAny();\n *\n * @param listener\n */\n offAny(listener) {\n if (!this._anyListeners) {\n return this;\n }\n if (listener) {\n const listeners = this._anyListeners;\n for (let i = 0; i < listeners.length; i++) {\n if (listener === listeners[i]) {\n listeners.splice(i, 1);\n return this;\n }\n }\n }\n else {\n this._anyListeners = [];\n }\n return this;\n }\n /**\n * Returns an array of listeners that are listening for any event that is specified. This array can be manipulated,\n * e.g. to remove listeners.\n */\n listenersAny() {\n return this._anyListeners || [];\n }\n /**\n * Adds a listener that will be fired when any event is emitted. The event name is passed as the first argument to the\n * callback.\n *\n * Note: acknowledgements sent to the server are not included.\n *\n * @example\n * socket.onAnyOutgoing((event, ...args) => {\n * console.log(`sent event ${event}`);\n * });\n *\n * @param listener\n */\n onAnyOutgoing(listener) {\n this._anyOutgoingListeners = this._anyOutgoingListeners || [];\n this._anyOutgoingListeners.push(listener);\n return this;\n }\n /**\n * Adds a listener that will be fired when any event is emitted. The event name is passed as the first argument to the\n * callback. The listener is added to the beginning of the listeners array.\n *\n * Note: acknowledgements sent to the server are not included.\n *\n * @example\n * socket.prependAnyOutgoing((event, ...args) => {\n * console.log(`sent event ${event}`);\n * });\n *\n * @param listener\n */\n prependAnyOutgoing(listener) {\n this._anyOutgoingListeners = this._anyOutgoingListeners || [];\n this._anyOutgoingListeners.unshift(listener);\n return this;\n }\n /**\n * Removes the listener that will be fired when any event is emitted.\n *\n * @example\n * const catchAllListener = (event, ...args) => {\n * console.log(`sent event ${event}`);\n * }\n *\n * socket.onAnyOutgoing(catchAllListener);\n *\n * // remove a specific listener\n * socket.offAnyOutgoing(catchAllListener);\n *\n * // or remove all listeners\n * socket.offAnyOutgoing();\n *\n * @param [listener] - the catch-all listener (optional)\n */\n offAnyOutgoing(listener) {\n if (!this._anyOutgoingListeners) {\n return this;\n }\n if (listener) {\n const listeners = this._anyOutgoingListeners;\n for (let i = 0; i < listeners.length; i++) {\n if (listener === listeners[i]) {\n listeners.splice(i, 1);\n return this;\n }\n }\n }\n else {\n this._anyOutgoingListeners = [];\n }\n return this;\n }\n /**\n * Returns an array of listeners that are listening for any event that is specified. This array can be manipulated,\n * e.g. to remove listeners.\n */\n listenersAnyOutgoing() {\n return this._anyOutgoingListeners || [];\n }\n /**\n * Notify the listeners for each packet sent\n *\n * @param packet\n *\n * @private\n */\n notifyOutgoingListeners(packet) {\n if (this._anyOutgoingListeners && this._anyOutgoingListeners.length) {\n const listeners = this._anyOutgoingListeners.slice();\n for (const listener of listeners) {\n listener.apply(this, packet.data);\n }\n }\n }\n}\n","/**\n * Initialize backoff timer with `opts`.\n *\n * - `min` initial timeout in milliseconds [100]\n * - `max` max timeout [10000]\n * - `jitter` [0]\n * - `factor` [2]\n *\n * @param {Object} opts\n * @api public\n */\nexport function Backoff(opts) {\n opts = opts || {};\n this.ms = opts.min || 100;\n this.max = opts.max || 10000;\n this.factor = opts.factor || 2;\n this.jitter = opts.jitter > 0 && opts.jitter <= 1 ? opts.jitter : 0;\n this.attempts = 0;\n}\n/**\n * Return the backoff duration.\n *\n * @return {Number}\n * @api public\n */\nBackoff.prototype.duration = function () {\n var ms = this.ms * Math.pow(this.factor, this.attempts++);\n if (this.jitter) {\n var rand = Math.random();\n var deviation = Math.floor(rand * this.jitter * ms);\n ms = (Math.floor(rand * 10) & 1) == 0 ? ms - deviation : ms + deviation;\n }\n return Math.min(ms, this.max) | 0;\n};\n/**\n * Reset the number of attempts.\n *\n * @api public\n */\nBackoff.prototype.reset = function () {\n this.attempts = 0;\n};\n/**\n * Set the minimum duration\n *\n * @api public\n */\nBackoff.prototype.setMin = function (min) {\n this.ms = min;\n};\n/**\n * Set the maximum duration\n *\n * @api public\n */\nBackoff.prototype.setMax = function (max) {\n this.max = max;\n};\n/**\n * Set the jitter\n *\n * @api public\n */\nBackoff.prototype.setJitter = function (jitter) {\n this.jitter = jitter;\n};\n","import { Socket as Engine, installTimerFunctions, nextTick, } from \"engine.io-client\";\nimport { Socket } from \"./socket.js\";\nimport * as parser from \"socket.io-parser\";\nimport { on } from \"./on.js\";\nimport { Backoff } from \"./contrib/backo2.js\";\nimport { Emitter, } from \"@socket.io/component-emitter\";\nexport class Manager extends Emitter {\n constructor(uri, opts) {\n var _a;\n super();\n this.nsps = {};\n this.subs = [];\n if (uri && \"object\" === typeof uri) {\n opts = uri;\n uri = undefined;\n }\n opts = opts || {};\n opts.path = opts.path || \"/socket.io\";\n this.opts = opts;\n installTimerFunctions(this, opts);\n this.reconnection(opts.reconnection !== false);\n this.reconnectionAttempts(opts.reconnectionAttempts || Infinity);\n this.reconnectionDelay(opts.reconnectionDelay || 1000);\n this.reconnectionDelayMax(opts.reconnectionDelayMax || 5000);\n this.randomizationFactor((_a = opts.randomizationFactor) !== null && _a !== void 0 ? _a : 0.5);\n this.backoff = new Backoff({\n min: this.reconnectionDelay(),\n max: this.reconnectionDelayMax(),\n jitter: this.randomizationFactor(),\n });\n this.timeout(null == opts.timeout ? 20000 : opts.timeout);\n this._readyState = \"closed\";\n this.uri = uri;\n const _parser = opts.parser || parser;\n this.encoder = new _parser.Encoder();\n this.decoder = new _parser.Decoder();\n this._autoConnect = opts.autoConnect !== false;\n if (this._autoConnect)\n this.open();\n }\n reconnection(v) {\n if (!arguments.length)\n return this._reconnection;\n this._reconnection = !!v;\n return this;\n }\n reconnectionAttempts(v) {\n if (v === undefined)\n return this._reconnectionAttempts;\n this._reconnectionAttempts = v;\n return this;\n }\n reconnectionDelay(v) {\n var _a;\n if (v === undefined)\n return this._reconnectionDelay;\n this._reconnectionDelay = v;\n (_a = this.backoff) === null || _a === void 0 ? void 0 : _a.setMin(v);\n return this;\n }\n randomizationFactor(v) {\n var _a;\n if (v === undefined)\n return this._randomizationFactor;\n this._randomizationFactor = v;\n (_a = this.backoff) === null || _a === void 0 ? void 0 : _a.setJitter(v);\n return this;\n }\n reconnectionDelayMax(v) {\n var _a;\n if (v === undefined)\n return this._reconnectionDelayMax;\n this._reconnectionDelayMax = v;\n (_a = this.backoff) === null || _a === void 0 ? void 0 : _a.setMax(v);\n return this;\n }\n timeout(v) {\n if (!arguments.length)\n return this._timeout;\n this._timeout = v;\n return this;\n }\n /**\n * Starts trying to reconnect if reconnection is enabled and we have not\n * started reconnecting yet\n *\n * @private\n */\n maybeReconnectOnOpen() {\n // Only try to reconnect if it's the first time we're connecting\n if (!this._reconnecting &&\n this._reconnection &&\n this.backoff.attempts === 0) {\n // keeps reconnection from firing twice for the same reconnection loop\n this.reconnect();\n }\n }\n /**\n * Sets the current transport `socket`.\n *\n * @param {Function} fn - optional, callback\n * @return self\n * @public\n */\n open(fn) {\n if (~this._readyState.indexOf(\"open\"))\n return this;\n this.engine = new Engine(this.uri, this.opts);\n const socket = this.engine;\n const self = this;\n this._readyState = \"opening\";\n this.skipReconnect = false;\n // emit `open`\n const openSubDestroy = on(socket, \"open\", function () {\n self.onopen();\n fn && fn();\n });\n const onError = (err) => {\n this.cleanup();\n this._readyState = \"closed\";\n this.emitReserved(\"error\", err);\n if (fn) {\n fn(err);\n }\n else {\n // Only do this if there is no fn to handle the error\n this.maybeReconnectOnOpen();\n }\n };\n // emit `error`\n const errorSub = on(socket, \"error\", onError);\n if (false !== this._timeout) {\n const timeout = this._timeout;\n // set timer\n const timer = this.setTimeoutFn(() => {\n openSubDestroy();\n onError(new Error(\"timeout\"));\n socket.close();\n }, timeout);\n if (this.opts.autoUnref) {\n timer.unref();\n }\n this.subs.push(() => {\n this.clearTimeoutFn(timer);\n });\n }\n this.subs.push(openSubDestroy);\n this.subs.push(errorSub);\n return this;\n }\n /**\n * Alias for open()\n *\n * @return self\n * @public\n */\n connect(fn) {\n return this.open(fn);\n }\n /**\n * Called upon transport open.\n *\n * @private\n */\n onopen() {\n // clear old subs\n this.cleanup();\n // mark as open\n this._readyState = \"open\";\n this.emitReserved(\"open\");\n // add new subs\n const socket = this.engine;\n this.subs.push(on(socket, \"ping\", this.onping.bind(this)), on(socket, \"data\", this.ondata.bind(this)), on(socket, \"error\", this.onerror.bind(this)), on(socket, \"close\", this.onclose.bind(this)), on(this.decoder, \"decoded\", this.ondecoded.bind(this)));\n }\n /**\n * Called upon a ping.\n *\n * @private\n */\n onping() {\n this.emitReserved(\"ping\");\n }\n /**\n * Called with data.\n *\n * @private\n */\n ondata(data) {\n try {\n this.decoder.add(data);\n }\n catch (e) {\n this.onclose(\"parse error\", e);\n }\n }\n /**\n * Called when parser fully decodes a packet.\n *\n * @private\n */\n ondecoded(packet) {\n // the nextTick call prevents an exception in a user-provided event listener from triggering a disconnection due to a \"parse error\"\n nextTick(() => {\n this.emitReserved(\"packet\", packet);\n }, this.setTimeoutFn);\n }\n /**\n * Called upon socket error.\n *\n * @private\n */\n onerror(err) {\n this.emitReserved(\"error\", err);\n }\n /**\n * Creates a new socket for the given `nsp`.\n *\n * @return {Socket}\n * @public\n */\n socket(nsp, opts) {\n let socket = this.nsps[nsp];\n if (!socket) {\n socket = new Socket(this, nsp, opts);\n this.nsps[nsp] = socket;\n }\n else if (this._autoConnect && !socket.active) {\n socket.connect();\n }\n return socket;\n }\n /**\n * Called upon a socket close.\n *\n * @param socket\n * @private\n */\n _destroy(socket) {\n const nsps = Object.keys(this.nsps);\n for (const nsp of nsps) {\n const socket = this.nsps[nsp];\n if (socket.active) {\n return;\n }\n }\n this._close();\n }\n /**\n * Writes a packet.\n *\n * @param packet\n * @private\n */\n _packet(packet) {\n const encodedPackets = this.encoder.encode(packet);\n for (let i = 0; i < encodedPackets.length; i++) {\n this.engine.write(encodedPackets[i], packet.options);\n }\n }\n /**\n * Clean up transport subscriptions and packet buffer.\n *\n * @private\n */\n cleanup() {\n this.subs.forEach((subDestroy) => subDestroy());\n this.subs.length = 0;\n this.decoder.destroy();\n }\n /**\n * Close the current socket.\n *\n * @private\n */\n _close() {\n this.skipReconnect = true;\n this._reconnecting = false;\n this.onclose(\"forced close\");\n if (this.engine)\n this.engine.close();\n }\n /**\n * Alias for close()\n *\n * @private\n */\n disconnect() {\n return this._close();\n }\n /**\n * Called upon engine close.\n *\n * @private\n */\n onclose(reason, description) {\n this.cleanup();\n this.backoff.reset();\n this._readyState = \"closed\";\n this.emitReserved(\"close\", reason, description);\n if (this._reconnection && !this.skipReconnect) {\n this.reconnect();\n }\n }\n /**\n * Attempt a reconnection.\n *\n * @private\n */\n reconnect() {\n if (this._reconnecting || this.skipReconnect)\n return this;\n const self = this;\n if (this.backoff.attempts >= this._reconnectionAttempts) {\n this.backoff.reset();\n this.emitReserved(\"reconnect_failed\");\n this._reconnecting = false;\n }\n else {\n const delay = this.backoff.duration();\n this._reconnecting = true;\n const timer = this.setTimeoutFn(() => {\n if (self.skipReconnect)\n return;\n this.emitReserved(\"reconnect_attempt\", self.backoff.attempts);\n // check again for the case socket closed in above events\n if (self.skipReconnect)\n return;\n self.open((err) => {\n if (err) {\n self._reconnecting = false;\n self.reconnect();\n this.emitReserved(\"reconnect_error\", err);\n }\n else {\n self.onreconnect();\n }\n });\n }, delay);\n if (this.opts.autoUnref) {\n timer.unref();\n }\n this.subs.push(() => {\n this.clearTimeoutFn(timer);\n });\n }\n }\n /**\n * Called upon successful reconnect.\n *\n * @private\n */\n onreconnect() {\n const attempt = this.backoff.attempts;\n this._reconnecting = false;\n this.backoff.reset();\n this.emitReserved(\"reconnect\", attempt);\n }\n}\n","import { url } from \"./url.js\";\nimport { Manager } from \"./manager.js\";\nimport { Socket } from \"./socket.js\";\n/**\n * Managers cache.\n */\nconst cache = {};\nfunction lookup(uri, opts) {\n if (typeof uri === \"object\") {\n opts = uri;\n uri = undefined;\n }\n opts = opts || {};\n const parsed = url(uri, opts.path || \"/socket.io\");\n const source = parsed.source;\n const id = parsed.id;\n const path = parsed.path;\n const sameNamespace = cache[id] && path in cache[id][\"nsps\"];\n const newConnection = opts.forceNew ||\n opts[\"force new connection\"] ||\n false === opts.multiplex ||\n sameNamespace;\n let io;\n if (newConnection) {\n io = new Manager(source, opts);\n }\n else {\n if (!cache[id]) {\n cache[id] = new Manager(source, opts);\n }\n io = cache[id];\n }\n if (parsed.query && !opts.query) {\n opts.query = parsed.queryKey;\n }\n return io.socket(parsed.path, opts);\n}\n// so that \"lookup\" can be used both as a function (e.g. `io(...)`) and as a\n// namespace (e.g. `io.connect(...)`), for backward compatibility\nObject.assign(lookup, {\n Manager,\n Socket,\n io: lookup,\n connect: lookup,\n});\n/**\n * Protocol version.\n *\n * @public\n */\nexport { protocol } from \"socket.io-parser\";\n/**\n * Expose constructors for standalone build.\n *\n * @public\n */\nexport { Manager, Socket, lookup as io, lookup as connect, lookup as default, };\n","import { parse } from \"engine.io-client\";\n/**\n * URL parser.\n *\n * @param uri - url\n * @param path - the request path of the connection\n * @param loc - An object meant to mimic window.location.\n * Defaults to window.location.\n * @public\n */\nexport function url(uri, path = \"\", loc) {\n let obj = uri;\n // default to window.location\n loc = loc || (typeof location !== \"undefined\" && location);\n if (null == uri)\n uri = loc.protocol + \"//\" + loc.host;\n // relative path support\n if (typeof uri === \"string\") {\n if (\"/\" === uri.charAt(0)) {\n if (\"/\" === uri.charAt(1)) {\n uri = loc.protocol + uri;\n }\n else {\n uri = loc.host + uri;\n }\n }\n if (!/^(https?|wss?):\\/\\//.test(uri)) {\n if (\"undefined\" !== typeof loc) {\n uri = loc.protocol + \"//\" + uri;\n }\n else {\n uri = \"https://\" + uri;\n }\n }\n // parse\n obj = parse(uri);\n }\n // make sure we treat `localhost:80` and `localhost` equally\n if (!obj.port) {\n if (/^(http|ws)$/.test(obj.protocol)) {\n obj.port = \"80\";\n }\n else if (/^(http|ws)s$/.test(obj.protocol)) {\n obj.port = \"443\";\n }\n }\n obj.path = obj.path || \"/\";\n const ipv6 = obj.host.indexOf(\":\") !== -1;\n const host = ipv6 ? \"[\" + obj.host + \"]\" : obj.host;\n // define unique id\n obj.id = obj.protocol + \"://\" + host + \":\" + obj.port + path;\n // define href\n obj.href =\n obj.protocol +\n \"://\" +\n host +\n (loc && loc.port === obj.port ? \"\" : \":\" + obj.port);\n return obj;\n}\n"],"names":["PACKET_TYPES","Object","create","PACKET_TYPES_REVERSE","keys","forEach","key","TEXT_ENCODER","ERROR_PACKET","type","data","withNativeBlob","Blob","prototype","toString","call","withNativeArrayBuffer","ArrayBuffer","isView","obj","buffer","encodePacket","_ref","supportsBinary","callback","encodeBlobAsBase64","fileReader","FileReader","onload","content","result","split","readAsDataURL","toArray","Uint8Array","byteOffset","byteLength","chars","lookup","i","charCodeAt","TEXT_DECODER","decodePacket","encodedPacket","binaryType","mapBinary","charAt","decodeBase64Packet","substring","length","decoded","base64","encoded1","encoded2","encoded3","encoded4","bufferLength","len","p","arraybuffer","bytes","decode","SEPARATOR","String","fromCharCode","createPacketEncoderStream","TransformStream","transform","packet","controller","arrayBuffer","then","encoded","TextEncoder","encode","encodePacketToBinary","header","payloadLength","DataView","setUint8","view","setUint16","setBigUint64","BigInt","enqueue","totalLength","chunks","reduce","acc","chunk","concatChunks","size","shift","j","slice","Emitter","mixin","on","addEventListener","event","fn","this","_callbacks","push","once","off","apply","arguments","removeListener","removeAllListeners","removeEventListener","cb","callbacks","splice","emit","args","Array","emitReserved","listeners","hasListeners","globalThisShim","self","window","Function","pick","_len","attr","_key","k","hasOwnProperty","NATIVE_SET_TIMEOUT","globalThis","setTimeout","NATIVE_CLEAR_TIMEOUT","clearTimeout","installTimerFunctions","opts","useNativeTimers","setTimeoutFn","bind","clearTimeoutFn","prev","TransportError","_Error","_inherits","_super","_createSuper","reason","description","context","_this","_classCallCheck","_createClass","_wrapNativeSuper","Error","Transport","_Emitter","_super2","_this2","writable","_assertThisInitialized","query","socket","value","_get","_getPrototypeOf","readyState","doOpen","doClose","onClose","packets","write","onPacket","details","onPause","schema","undefined","_hostname","_port","path","_query","hostname","indexOf","port","secure","Number","encodedQuery","str","encodeURIComponent","alphabet","map","seed","num","Math","floor","yeast","now","Date","XMLHttpRequest","err","hasCORS","XHR","xdomain","e","concat","join","empty","hasXHR2","responseType","Polling","_Transport","polling","location","isSSL","protocol","xd","forceBase64","withCredentials","cookieJar","createCookieJar","get","poll","pause","total","doPoll","_this3","encodedPayload","encodedPackets","decodedPacket","decodePayload","onOpen","_this4","close","_this5","count","encodePayload","doWrite","timestampRequests","timestampParam","sid","b64","createUri","_extends","Request","uri","_this6","req","request","method","xhrStatus","onError","_this7","onData","pollXhr","_this8","_a","_this9","xhr","open","extraHeaders","setDisableHeaderCheck","setRequestHeader","addCookies","requestTimeout","timeout","onreadystatechange","parseCookies","status","onLoad","send","document","index","requestsCount","requests","cleanup","fromError","abort","responseText","attachEvent","unloadHandler","nextTick","Promise","resolve","WebSocket","MozWebSocket","isReactNative","navigator","product","toLowerCase","WS","check","protocols","headers","ws","addEventListeners","onopen","autoUnref","_socket","unref","onclose","closeEvent","onmessage","ev","onerror","_loop","lastPacket","WT","WebTransport","transport","transportOptions","name","closed","ready","createBidirectionalStream","stream","decoderStream","maxPayload","TextDecoder","state","expectedLength","isBinary","headerArray","getUint16","n","getUint32","pow","createPacketDecoderStream","MAX_SAFE_INTEGER","reader","readable","pipeThrough","getReader","encoderStream","pipeTo","writer","getWriter","read","done","transports","websocket","webtransport","re","parts","parse","src","b","replace","m","exec","source","host","authority","ipv6uri","pathNames","regx","names","queryKey","$0","$1","$2","Socket","writeBuffer","_typeof","prevBufferLen","agent","upgrade","rememberUpgrade","addTrailingSlash","rejectUnauthorized","perMessageDeflate","threshold","closeOnBeforeunload","qs","qry","pairs","l","pair","decodeURIComponent","id","upgrades","pingInterval","pingTimeout","pingTimeoutTimer","beforeunloadEventListener","offlineEventListener","EIO","priorWebsocketSuccess","createTransport","setTransport","onDrain","failed","onTransportOpen","msg","upgrading","flush","freezeTransport","error","onTransportClose","onupgrade","to","probe","resetPingTimeout","onHandshake","JSON","sendPacket","code","filterUpgrades","getWritablePackets","payloadSize","c","utf8Length","ceil","options","compress","cleanupAndClose","waitForUpgrade","filteredUpgrades","Socket$1","withNativeFile","File","hasBinary","toJSON","isArray","deconstructPacket","buffers","packetData","pack","_deconstructPacket","attachments","placeholder","_placeholder","newData","reconstructPacket","_reconstructPacket","PacketType","RESERVED_EVENTS","Encoder","replacer","EVENT","ACK","encodeAsString","encodeAsBinary","BINARY_EVENT","BINARY_ACK","nsp","stringify","deconstruction","unshift","isObject","Decoder","reviver","reconstructor","isBinaryEvent","decodeString","BinaryReconstructor","takeBinaryData","start","buf","next","payload","tryParse","substr","isPayloadValid","finishedReconstruction","CONNECT","DISCONNECT","CONNECT_ERROR","reconPack","binData","freeze","connect","connect_error","disconnect","disconnecting","newListener","io","connected","recovered","receiveBuffer","sendBuffer","_queue","_queueSeq","ids","acks","flags","auth","_opts","_autoConnect","subs","onpacket","subEvents","_readyState","_len2","_key2","retries","fromQueue","_addToQueue","ack","pop","_registerAckCallback","isTransportWritable","engine","notifyOutgoingListeners","ackTimeout","timer","_len3","_key3","_len4","_key4","withErr","reject","arg1","arg2","tryCount","pending","_len5","responseArgs","_key5","_drainQueue","force","_packet","_sendConnectPacket","_pid","pid","offset","_lastOffset","onconnect","onevent","onack","ondisconnect","destroy","message","emitEvent","_anyListeners","_step","_iterator","_createForOfIteratorHelper","s","f","sent","_len6","_key6","emitBuffered","subDestroy","listener","_anyOutgoingListeners","_step2","_iterator2","Backoff","ms","min","max","factor","jitter","attempts","duration","rand","random","deviation","reset","setMin","setMax","setJitter","Manager","nsps","reconnection","reconnectionAttempts","Infinity","reconnectionDelay","reconnectionDelayMax","randomizationFactor","backoff","_parser","parser","encoder","decoder","autoConnect","v","_reconnection","_reconnectionAttempts","_reconnectionDelay","_randomizationFactor","_reconnectionDelayMax","_timeout","_reconnecting","reconnect","Engine","skipReconnect","openSubDestroy","maybeReconnectOnOpen","errorSub","onping","ondata","ondecoded","add","active","_i","_nsps","_close","delay","onreconnect","attempt","cache","parsed","loc","test","href","url","sameNamespace","forceNew","multiplex"],"mappings":";;;;;6lJAAA,IAAMA,EAAeC,OAAOC,OAAO,MACnCF,EAAmB,KAAI,IACvBA,EAAoB,MAAI,IACxBA,EAAmB,KAAI,IACvBA,EAAmB,KAAI,IACvBA,EAAsB,QAAI,IAC1BA,EAAsB,QAAI,IAC1BA,EAAmB,KAAI,IACvB,IAAMG,EAAuBF,OAAOC,OAAO,MAC3CD,OAAOG,KAAKJ,GAAcK,SAAQ,SAAAC,GAC9BH,EAAqBH,EAAaM,IAAQA,CAC9C,IACA,ICuCIC,EDvCEC,EAAe,CAAEC,KAAM,QAASC,KAAM,gBCXtCC,EAAiC,mBAATC,MACT,oBAATA,MACqC,6BAAzCX,OAAOY,UAAUC,SAASC,KAAKH,MACjCI,EAA+C,mBAAhBC,YAE/BC,EAAS,SAAAC,GACX,MAAqC,mBAAvBF,YAAYC,OACpBD,YAAYC,OAAOC,GACnBA,GAAOA,EAAIC,kBAAkBH,WACvC,EACMI,EAAe,SAAHC,EAAoBC,EAAgBC,GAAa,IAA3Cf,EAAIa,EAAJb,KAAMC,EAAIY,EAAJZ,KAC1B,OAAIC,GAAkBD,aAAgBE,KAC9BW,EACOC,EAASd,GAGTe,EAAmBf,EAAMc,GAG/BR,IACJN,aAAgBO,aAAeC,EAAOR,IACnCa,EACOC,EAASd,GAGTe,EAAmB,IAAIb,KAAK,CAACF,IAAQc,GAI7CA,EAASxB,EAAaS,IAASC,GAAQ,IAClD,EACMe,EAAqB,SAACf,EAAMc,GAC9B,IAAME,EAAa,IAAIC,WAKvB,OAJAD,EAAWE,OAAS,WAChB,IAAMC,EAAUH,EAAWI,OAAOC,MAAM,KAAK,GAC7CP,EAAS,KAAOK,GAAW,MAExBH,EAAWM,cAActB,EACpC,EACA,SAASuB,EAAQvB,GACb,OAAIA,aAAgBwB,WACTxB,EAEFA,aAAgBO,YACd,IAAIiB,WAAWxB,GAGf,IAAIwB,WAAWxB,EAAKU,OAAQV,EAAKyB,WAAYzB,EAAK0B,WAEjE,CC9CA,IAHA,IAAMC,EAAQ,mEAERC,EAA+B,oBAAfJ,WAA6B,GAAK,IAAIA,WAAW,KAC9DK,EAAI,EAAGA,EAAIF,GAAcE,IAC9BD,EAAOD,EAAMG,WAAWD,IAAMA,EAkB3B,ICyCHE,EC9DEzB,EAA+C,mBAAhBC,YACxByB,EAAe,SAACC,EAAeC,GACxC,GAA6B,iBAAlBD,EACP,MAAO,CACHlC,KAAM,UACNC,KAAMmC,EAAUF,EAAeC,IAGvC,IAAMnC,EAAOkC,EAAcG,OAAO,GAClC,MAAa,MAATrC,EACO,CACHA,KAAM,UACNC,KAAMqC,EAAmBJ,EAAcK,UAAU,GAAIJ,IAG1CzC,EAAqBM,GAIjCkC,EAAcM,OAAS,EACxB,CACExC,KAAMN,EAAqBM,GAC3BC,KAAMiC,EAAcK,UAAU,IAEhC,CACEvC,KAAMN,EAAqBM,IARxBD,CAUf,EACMuC,EAAqB,SAACrC,EAAMkC,GAC9B,GAAI5B,EAAuB,CACvB,IAAMkC,EFTQ,SAACC,GACnB,IAA8DZ,EAAUa,EAAUC,EAAUC,EAAUC,EAAlGC,EAA+B,IAAhBL,EAAOF,OAAeQ,EAAMN,EAAOF,OAAWS,EAAI,EACnC,MAA9BP,EAAOA,EAAOF,OAAS,KACvBO,IACkC,MAA9BL,EAAOA,EAAOF,OAAS,IACvBO,KAGR,IAAMG,EAAc,IAAI1C,YAAYuC,GAAeI,EAAQ,IAAI1B,WAAWyB,GAC1E,IAAKpB,EAAI,EAAGA,EAAIkB,EAAKlB,GAAK,EACtBa,EAAWd,EAAOa,EAAOX,WAAWD,IACpCc,EAAWf,EAAOa,EAAOX,WAAWD,EAAI,IACxCe,EAAWhB,EAAOa,EAAOX,WAAWD,EAAI,IACxCgB,EAAWjB,EAAOa,EAAOX,WAAWD,EAAI,IACxCqB,EAAMF,KAAQN,GAAY,EAAMC,GAAY,EAC5CO,EAAMF,MAAoB,GAAXL,IAAkB,EAAMC,GAAY,EACnDM,EAAMF,MAAoB,EAAXJ,IAAiB,EAAiB,GAAXC,EAE1C,OAAOI,CACX,CEVwBE,CAAOnD,GACvB,OAAOmC,EAAUK,EAASN,EAC9B,CAEI,MAAO,CAAEO,QAAQ,EAAMzC,KAAAA,EAE/B,EACMmC,EAAY,SAACnC,EAAMkC,GACrB,MACS,SADDA,EAEIlC,aAAgBE,KAETF,EAIA,IAAIE,KAAK,CAACF,IAIjBA,aAAgBO,YAETP,EAIAA,EAAKU,MAG5B,ED1DM0C,EAAYC,OAAOC,aAAa,IA4B/B,SAASC,IACZ,OAAO,IAAIC,gBAAgB,CACvBC,UAASA,SAACC,EAAQC,IFmBnB,SAA8BD,EAAQ5C,GACrCb,GAAkByD,EAAO1D,gBAAgBE,KAClCwD,EAAO1D,KACT4D,cACAC,KAAKtC,GACLsC,KAAK/C,GAELR,IACJoD,EAAO1D,gBAAgBO,aAAeC,EAAOkD,EAAO1D,OAC9Cc,EAASS,EAAQmC,EAAO1D,OAEnCW,EAAa+C,GAAQ,GAAO,SAAAI,GACnBjE,IACDA,EAAe,IAAIkE,aAEvBjD,EAASjB,EAAamE,OAAOF,GACjC,GACJ,CEnCYG,CAAqBP,GAAQ,SAAAzB,GACzB,IACIiC,EADEC,EAAgBlC,EAAcM,OAGpC,GAAI4B,EAAgB,IAChBD,EAAS,IAAI1C,WAAW,GACxB,IAAI4C,SAASF,EAAOxD,QAAQ2D,SAAS,EAAGF,QAEvC,GAAIA,EAAgB,MAAO,CAC5BD,EAAS,IAAI1C,WAAW,GACxB,IAAM8C,EAAO,IAAIF,SAASF,EAAOxD,QACjC4D,EAAKD,SAAS,EAAG,KACjBC,EAAKC,UAAU,EAAGJ,EACtB,KACK,CACDD,EAAS,IAAI1C,WAAW,GACxB,IAAM8C,EAAO,IAAIF,SAASF,EAAOxD,QACjC4D,EAAKD,SAAS,EAAG,KACjBC,EAAKE,aAAa,EAAGC,OAAON,GAChC,CAEIT,EAAO1D,MAA+B,iBAAhB0D,EAAO1D,OAC7BkE,EAAO,IAAM,KAEjBP,EAAWe,QAAQR,GACnBP,EAAWe,QAAQzC,EACvB,GACJ,GAER,CAEA,SAAS0C,EAAYC,GACjB,OAAOA,EAAOC,QAAO,SAACC,EAAKC,GAAK,OAAKD,EAAMC,EAAMxC,MAAM,GAAE,EAC7D,CACA,SAASyC,EAAaJ,EAAQK,GAC1B,GAAIL,EAAO,GAAGrC,SAAW0C,EACrB,OAAOL,EAAOM,QAIlB,IAFA,IAAMxE,EAAS,IAAIc,WAAWyD,GAC1BE,EAAI,EACCtD,EAAI,EAAGA,EAAIoD,EAAMpD,IACtBnB,EAAOmB,GAAK+C,EAAO,GAAGO,KAClBA,IAAMP,EAAO,GAAGrC,SAChBqC,EAAOM,QACPC,EAAI,GAMZ,OAHIP,EAAOrC,QAAU4C,EAAIP,EAAO,GAAGrC,SAC/BqC,EAAO,GAAKA,EAAO,GAAGQ,MAAMD,IAEzBzE,CACX,CE/EO,SAAS2E,EAAQ5E,GACtB,GAAIA,EAAK,OAWX,SAAeA,GACb,IAAK,IAAIb,KAAOyF,EAAQlF,UACtBM,EAAIb,GAAOyF,EAAQlF,UAAUP,GAE/B,OAAOa,CACT,CAhBkB6E,CAAM7E,EACxB,CA0BA4E,EAAQlF,UAAUoF,GAClBF,EAAQlF,UAAUqF,iBAAmB,SAASC,EAAOC,GAInD,OAHAC,KAAKC,WAAaD,KAAKC,YAAc,CAAA,GACpCD,KAAKC,WAAW,IAAMH,GAASE,KAAKC,WAAW,IAAMH,IAAU,IAC7DI,KAAKH,GACDC,IACT,EAYAN,EAAQlF,UAAU2F,KAAO,SAASL,EAAOC,GACvC,SAASH,IACPI,KAAKI,IAAIN,EAAOF,GAChBG,EAAGM,MAAML,KAAMM,UACjB,CAIA,OAFAV,EAAGG,GAAKA,EACRC,KAAKJ,GAAGE,EAAOF,GACRI,IACT,EAYAN,EAAQlF,UAAU4F,IAClBV,EAAQlF,UAAU+F,eAClBb,EAAQlF,UAAUgG,mBAClBd,EAAQlF,UAAUiG,oBAAsB,SAASX,EAAOC,GAItD,GAHAC,KAAKC,WAAaD,KAAKC,YAAc,CAAA,EAGjC,GAAKK,UAAU1D,OAEjB,OADAoD,KAAKC,WAAa,GACXD,KAIT,IAUIU,EAVAC,EAAYX,KAAKC,WAAW,IAAMH,GACtC,IAAKa,EAAW,OAAOX,KAGvB,GAAI,GAAKM,UAAU1D,OAEjB,cADOoD,KAAKC,WAAW,IAAMH,GACtBE,KAKT,IAAK,IAAI9D,EAAI,EAAGA,EAAIyE,EAAU/D,OAAQV,IAEpC,IADAwE,EAAKC,EAAUzE,MACJ6D,GAAMW,EAAGX,KAAOA,EAAI,CAC7BY,EAAUC,OAAO1E,EAAG,GACpB,KACF,CASF,OAJyB,IAArByE,EAAU/D,eACLoD,KAAKC,WAAW,IAAMH,GAGxBE,IACT,EAUAN,EAAQlF,UAAUqG,KAAO,SAASf,GAChCE,KAAKC,WAAaD,KAAKC,YAAc,CAAA,EAKrC,IAHA,IAAIa,EAAO,IAAIC,MAAMT,UAAU1D,OAAS,GACpC+D,EAAYX,KAAKC,WAAW,IAAMH,GAE7B5D,EAAI,EAAGA,EAAIoE,UAAU1D,OAAQV,IACpC4E,EAAK5E,EAAI,GAAKoE,UAAUpE,GAG1B,GAAIyE,EAEG,CAAIzE,EAAI,EAAb,IAAK,IAAWkB,GADhBuD,EAAYA,EAAUlB,MAAM,IACI7C,OAAQV,EAAIkB,IAAOlB,EACjDyE,EAAUzE,GAAGmE,MAAML,KAAMc,EADKlE,CAKlC,OAAOoD,IACT,EAGAN,EAAQlF,UAAUwG,aAAetB,EAAQlF,UAAUqG,KAUnDnB,EAAQlF,UAAUyG,UAAY,SAASnB,GAErC,OADAE,KAAKC,WAAaD,KAAKC,YAAc,CAAA,EAC9BD,KAAKC,WAAW,IAAMH,IAAU,EACzC,EAUAJ,EAAQlF,UAAU0G,aAAe,SAASpB,GACxC,QAAUE,KAAKiB,UAAUnB,GAAOlD,MAClC,ECxKO,IAAMuE,EACW,oBAATC,KACAA,KAEgB,oBAAXC,OACLA,OAGAC,SAAS,cAATA,GCPR,SAASC,EAAKzG,GAAc,IAAA0G,IAAAA,EAAAlB,UAAA1D,OAAN6E,MAAIV,MAAAS,EAAAA,EAAAA,OAAAE,EAAA,EAAAA,EAAAF,EAAAE,IAAJD,EAAIC,EAAApB,GAAAA,UAAAoB,GAC7B,OAAOD,EAAKvC,QAAO,SAACC,EAAKwC,GAIrB,OAHI7G,EAAI8G,eAAeD,KACnBxC,EAAIwC,GAAK7G,EAAI6G,IAEVxC,CACV,GAAE,CAAE,EACT,CAEA,IAAM0C,EAAqBC,EAAWC,WAChCC,EAAuBF,EAAWG,aACjC,SAASC,EAAsBpH,EAAKqH,GACnCA,EAAKC,iBACLtH,EAAIuH,aAAeR,EAAmBS,KAAKR,GAC3ChH,EAAIyH,eAAiBP,EAAqBM,KAAKR,KAG/ChH,EAAIuH,aAAeP,EAAWC,WAAWO,KAAKR,GAC9ChH,EAAIyH,eAAiBT,EAAWG,aAAaK,KAAKR,GAE1D,CClB8C,ICAzBU,EDCfC,WAAcC,GAAAC,EAAAF,EAAAC,GAAA,IAAAE,EAAAC,EAAAJ,GAChB,SAAAA,EAAYK,EAAQC,EAAaC,GAAS,IAAAC,EAIT,OAJSC,OAAAT,IACtCQ,EAAAL,EAAAlI,UAAMoI,IACDC,YAAcA,EACnBE,EAAKD,QAAUA,EACfC,EAAK7I,KAAO,iBAAiB6I,CACjC,CAAC,OAAAE,EAAAV,EAAA,EAAAW,EANwBC,QAQhBC,WAASC,GAAAZ,EAAAW,EAAAC,GAAA,IAAAC,EAAAX,EAAAS,GAOlB,SAAAA,EAAYnB,GAAM,IAAAsB,EAMY,OANZP,OAAAI,IACdG,EAAAD,EAAA9I,KAAAsF,OACK0D,UAAW,EAChBxB,EAAqByB,EAAAF,GAAOtB,GAC5BsB,EAAKtB,KAAOA,EACZsB,EAAKG,MAAQzB,EAAKyB,MAClBH,EAAKI,OAAS1B,EAAK0B,OAAOJ,CAC9B,CAiHC,OAhHDN,EAAAG,EAAA,CAAA,CAAArJ,IAAA,UAAA6J,MASA,SAAQhB,EAAQC,EAAaC,GAEzB,OADAe,EAAAC,EAAAV,EAAA9I,gCAAAE,KAAAsF,KAAmB,QAAS,IAAIyC,EAAeK,EAAQC,EAAaC,IAC7DhD,IACX,GACA,CAAA/F,IAAA,OAAA6J,MAGA,WAGI,OAFA9D,KAAKiE,WAAa,UAClBjE,KAAKkE,SACElE,IACX,GACA,CAAA/F,IAAA,QAAA6J,MAGA,WAKI,MAJwB,YAApB9D,KAAKiE,YAAgD,SAApBjE,KAAKiE,aACtCjE,KAAKmE,UACLnE,KAAKoE,WAEFpE,IACX,GACA,CAAA/F,IAAA,OAAA6J,MAKA,SAAKO,GACuB,SAApBrE,KAAKiE,YACLjE,KAAKsE,MAAMD,EAKnB,GACA,CAAApK,IAAA,SAAA6J,MAKA,WACI9D,KAAKiE,WAAa,OAClBjE,KAAK0D,UAAW,EAChBK,EAAAC,EAAAV,EAAA9I,WAAA,eAAAwF,MAAAtF,KAAAsF,KAAmB,OACvB,GACA,CAAA/F,IAAA,SAAA6J,MAMA,SAAOzJ,GACH,IAAM0D,EAAS1B,EAAahC,EAAM2F,KAAK6D,OAAOtH,YAC9CyD,KAAKuE,SAASxG,EAClB,GACA,CAAA9D,IAAA,WAAA6J,MAKA,SAAS/F,GACLgG,EAAAC,EAAAV,EAAA9I,WAAA,eAAAwF,MAAAtF,KAAAsF,KAAmB,SAAUjC,EACjC,GACA,CAAA9D,IAAA,UAAA6J,MAKA,SAAQU,GACJxE,KAAKiE,WAAa,SAClBF,EAAAC,EAAAV,EAAA9I,WAAA,eAAAwF,MAAAtF,KAAAsF,KAAmB,QAASwE,EAChC,GACA,CAAAvK,IAAA,QAAA6J,MAKA,SAAMW,GAAW,GAAC,CAAAxK,IAAA,YAAA6J,MAClB,SAAUY,GAAoB,IAAZd,EAAKtD,UAAA1D,OAAA,QAAA+H,IAAArE,UAAA,GAAAA,UAAA,GAAG,CAAA,EACtB,OAAQoE,EACJ,MACA1E,KAAK4E,YACL5E,KAAK6E,QACL7E,KAAKmC,KAAK2C,KACV9E,KAAK+E,OAAOnB,EACpB,GAAC,CAAA3J,IAAA,YAAA6J,MACD,WACI,IAAMkB,EAAWhF,KAAKmC,KAAK6C,SAC3B,OAAkC,IAA3BA,EAASC,QAAQ,KAAcD,EAAW,IAAMA,EAAW,GACtE,GAAC,CAAA/K,IAAA,QAAA6J,MACD,WACI,OAAI9D,KAAKmC,KAAK+C,OACRlF,KAAKmC,KAAKgD,QAAUC,OAA0B,MAAnBpF,KAAKmC,KAAK+C,QACjClF,KAAKmC,KAAKgD,QAAqC,KAA3BC,OAAOpF,KAAKmC,KAAK+C,OACpC,IAAMlF,KAAKmC,KAAK+C,KAGhB,EAEf,GAAC,CAAAjL,IAAA,SAAA6J,MACD,SAAOF,GACH,IAAMyB,EEjIP,SAAgBvK,GACnB,IAAIwK,EAAM,GACV,IAAK,IAAIpJ,KAAKpB,EACNA,EAAI8G,eAAe1F,KACfoJ,EAAI1I,SACJ0I,GAAO,KACXA,GAAOC,mBAAmBrJ,GAAK,IAAMqJ,mBAAmBzK,EAAIoB,KAGpE,OAAOoJ,CACX,CFuH6BjH,CAAOuF,GAC5B,OAAOyB,EAAazI,OAAS,IAAMyI,EAAe,EACtD,KAAC/B,CAAA,EA/H0B5D,GCVzB8F,EAAW,mEAAmE9J,MAAM,IAAKkB,EAAS,GAAI6I,EAAM,CAAA,EAC9GC,EAAO,EAAGxJ,EAAI,EAQX,SAASmC,EAAOsH,GACnB,IAAIxH,EAAU,GACd,GACIA,EAAUqH,EAASG,EAAM/I,GAAUuB,EACnCwH,EAAMC,KAAKC,MAAMF,EAAM/I,SAClB+I,EAAM,GACf,OAAOxH,CACX,CAqBO,SAAS2H,IACZ,IAAMC,EAAM1H,GAAQ,IAAI2H,MACxB,OAAID,IAAQvD,GACDkD,EAAO,EAAGlD,EAAOuD,GACrBA,EAAM,IAAM1H,EAAOqH,IAC9B,CAIA,KAAOxJ,EAAIU,EAAQV,IACfuJ,EAAID,EAAStJ,IAAMA,EEhDvB,IAAI4H,IAAQ,EACZ,IACIA,GAAkC,oBAAnBmC,gBACX,oBAAqB,IAAIA,cACjC,CACA,MAAOC,GAEH,CAEG,IAAMC,GAAUrC,GCPhB,SAASsC,GAAIjE,GAChB,IAAMkE,EAAUlE,EAAKkE,QAErB,IACI,GAAI,oBAAuBJ,kBAAoBI,GAAWF,IACtD,OAAO,IAAIF,cAEnB,CACA,MAAOK,GAAK,CACZ,IAAKD,EACD,IACI,OAAO,IAAIvE,EAAW,CAAC,UAAUyE,OAAO,UAAUC,KAAK,OAAM,oBACjE,CACA,MAAOF,GAAK,CAEpB,CCXA,SAASG,KAAU,CACnB,IAAMC,GAIK,MAHK,IAAIT,GAAe,CAC3BI,SAAS,IAEMM,aAEVC,YAAOC,GAAAlE,EAAAiE,EAAAC,GAAA,IAAAjE,EAAAC,EAAA+D,GAOhB,SAAAA,EAAYzE,GAAM,IAAAc,EAGd,GAHcC,OAAA0D,IACd3D,EAAAL,EAAAlI,UAAMyH,IACD2E,SAAU,EACS,oBAAbC,SAA0B,CACjC,IAAMC,EAAQ,WAAaD,SAASE,SAChC/B,EAAO6B,SAAS7B,KAEfA,IACDA,EAAO8B,EAAQ,MAAQ,MAE3B/D,EAAKiE,GACoB,oBAAbH,UACJ5E,EAAK6C,WAAa+B,SAAS/B,UAC3BE,IAAS/C,EAAK+C,IAC1B,CAIA,IAAMiC,EAAchF,GAAQA,EAAKgF,YAIhC,OAHDlE,EAAK/H,eAAiBwL,KAAYS,EAC9BlE,EAAKd,KAAKiF,kBACVnE,EAAKoE,eAAYC,GACpBrE,CACL,CAgLC,OAhLAE,EAAAyD,EAAA,CAAA,CAAA3M,IAAA,OAAAsN,IACD,WACI,MAAO,SACX,GACA,CAAAtN,IAAA,SAAA6J,MAMA,WACI9D,KAAKwH,MACT,GACA,CAAAvN,IAAA,QAAA6J,MAMA,SAAMW,GAAS,IAAAhB,EAAAzD,KACXA,KAAKiE,WAAa,UAClB,IAAMwD,EAAQ,WACVhE,EAAKQ,WAAa,SAClBQ,KAEJ,GAAIzE,KAAK8G,UAAY9G,KAAK0D,SAAU,CAChC,IAAIgE,EAAQ,EACR1H,KAAK8G,UACLY,IACA1H,KAAKG,KAAK,gBAAgB,aACpBuH,GAASD,GACf,KAECzH,KAAK0D,WACNgE,IACA1H,KAAKG,KAAK,SAAS,aACbuH,GAASD,GACf,IAER,MAEIA,GAER,GACA,CAAAxN,IAAA,OAAA6J,MAKA,WACI9D,KAAK8G,SAAU,EACf9G,KAAK2H,SACL3H,KAAKgB,aAAa,OACtB,GACA,CAAA/G,IAAA,SAAA6J,MAKA,SAAOzJ,GAAM,IAAAuN,EAAA5H,MVpFK,SAAC6H,EAAgBtL,GAGnC,IAFA,IAAMuL,EAAiBD,EAAenM,MAAM+B,GACtC4G,EAAU,GACPnI,EAAI,EAAGA,EAAI4L,EAAelL,OAAQV,IAAK,CAC5C,IAAM6L,EAAgB1L,EAAayL,EAAe5L,GAAIK,GAEtD,GADA8H,EAAQnE,KAAK6H,GACc,UAAvBA,EAAc3N,KACd,KAER,CACA,OAAOiK,CACX,EUwFQ2D,CAAc3N,EAAM2F,KAAK6D,OAAOtH,YAAYvC,SAd3B,SAAC+D,GAMd,GAJI,YAAc6J,EAAK3D,YAA8B,SAAhBlG,EAAO3D,MACxCwN,EAAKK,SAGL,UAAYlK,EAAO3D,KAEnB,OADAwN,EAAKxD,QAAQ,CAAErB,YAAa,oCACrB,EAGX6E,EAAKrD,SAASxG,MAKd,WAAaiC,KAAKiE,aAElBjE,KAAK8G,SAAU,EACf9G,KAAKgB,aAAa,gBACd,SAAWhB,KAAKiE,YAChBjE,KAAKwH,OAKjB,GACA,CAAAvN,IAAA,UAAA6J,MAKA,WAAU,IAAAoE,EAAAlI,KACAmI,EAAQ,WACVD,EAAK5D,MAAM,CAAC,CAAElK,KAAM,YAEpB,SAAW4F,KAAKiE,WAChBkE,IAKAnI,KAAKG,KAAK,OAAQgI,EAE1B,GACA,CAAAlO,IAAA,QAAA6J,MAMA,SAAMO,GAAS,IAAA+D,EAAApI,KACXA,KAAK0D,UAAW,EVxJF,SAACW,EAASlJ,GAE5B,IAAMyB,EAASyH,EAAQzH,OACjBkL,EAAiB,IAAI/G,MAAMnE,GAC7ByL,EAAQ,EACZhE,EAAQrK,SAAQ,SAAC+D,EAAQ7B,GAErBlB,EAAa+C,GAAQ,GAAO,SAAAzB,GACxBwL,EAAe5L,GAAKI,IACd+L,IAAUzL,GACZzB,EAAS2M,EAAetB,KAAK/I,GAErC,GACJ,GACJ,CU2IQ6K,CAAcjE,GAAS,SAAChK,GACpB+N,EAAKG,QAAQlO,GAAM,WACf+N,EAAK1E,UAAW,EAChB0E,EAAKpH,aAAa,QACtB,GACJ,GACJ,GACA,CAAA/G,IAAA,MAAA6J,MAKA,WACI,IAAMY,EAAS1E,KAAKmC,KAAKgD,OAAS,QAAU,OACtCvB,EAAQ5D,KAAK4D,OAAS,GAQ5B,OANI,IAAU5D,KAAKmC,KAAKqG,oBACpB5E,EAAM5D,KAAKmC,KAAKsG,gBAAkB3C,KAEjC9F,KAAK9E,gBAAmB0I,EAAM8E,MAC/B9E,EAAM+E,IAAM,GAET3I,KAAK4I,UAAUlE,EAAQd,EAClC,GACA,CAAA3J,IAAA,UAAA6J,MAMA,WAAmB,IAAX3B,EAAI7B,UAAA1D,OAAA,QAAA+H,IAAArE,UAAA,GAAAA,UAAA,GAAG,CAAA,EAEX,OADAuI,EAAc1G,EAAM,CAAE+E,GAAIlH,KAAKkH,GAAIG,UAAWrH,KAAKqH,WAAarH,KAAKmC,MAC9D,IAAI2G,GAAQ9I,KAAK+I,MAAO5G,EACnC,GACA,CAAAlI,IAAA,UAAA6J,MAOA,SAAQzJ,EAAM0F,GAAI,IAAAiJ,EAAAhJ,KACRiJ,EAAMjJ,KAAKkJ,QAAQ,CACrBC,OAAQ,OACR9O,KAAMA,IAEV4O,EAAIrJ,GAAG,UAAWG,GAClBkJ,EAAIrJ,GAAG,SAAS,SAACwJ,EAAWpG,GACxBgG,EAAKK,QAAQ,iBAAkBD,EAAWpG,EAC9C,GACJ,GACA,CAAA/I,IAAA,SAAA6J,MAKA,WAAS,IAAAwF,EAAAtJ,KACCiJ,EAAMjJ,KAAKkJ,UACjBD,EAAIrJ,GAAG,OAAQI,KAAKuJ,OAAOjH,KAAKtC,OAChCiJ,EAAIrJ,GAAG,SAAS,SAACwJ,EAAWpG,GACxBsG,EAAKD,QAAQ,iBAAkBD,EAAWpG,EAC9C,IACAhD,KAAKwJ,QAAUP,CACnB,KAACrC,CAAA,EA9MwBtD,GAgNhBwF,YAAOvF,GAAAZ,EAAAmG,EAAAvF,GAAA,IAAAC,EAAAX,EAAAiG,GAOhB,SAAAA,EAAYC,EAAK5G,GAAM,IAAAsH,EAOL,OAPKvG,OAAA4F,GAEnB5G,EAAqByB,EADrB8F,EAAAjG,EAAA9I,KAAAsF,OAC4BmC,GAC5BsH,EAAKtH,KAAOA,EACZsH,EAAKN,OAAShH,EAAKgH,QAAU,MAC7BM,EAAKV,IAAMA,EACXU,EAAKpP,UAAOsK,IAAcxC,EAAK9H,KAAO8H,EAAK9H,KAAO,KAClDoP,EAAK5P,SAAS4P,CAClB,CA8HC,OA7HDtG,EAAA2F,EAAA,CAAA,CAAA7O,IAAA,SAAA6J,MAKA,WAAS,IACD4F,EADCC,EAAA3J,KAECmC,EAAOZ,EAAKvB,KAAKmC,KAAM,QAAS,MAAO,MAAO,aAAc,OAAQ,KAAM,UAAW,qBAAsB,aACjHA,EAAKkE,UAAYrG,KAAKmC,KAAK+E,GAC3B,IAAM0C,EAAO5J,KAAK4J,IAAM,IAAI3D,GAAe9D,GAC3C,IACIyH,EAAIC,KAAK7J,KAAKmJ,OAAQnJ,KAAK+I,KAAK,GAChC,IACI,GAAI/I,KAAKmC,KAAK2H,aAEV,IAAK,IAAI5N,KADT0N,EAAIG,uBAAyBH,EAAIG,uBAAsB,GACzC/J,KAAKmC,KAAK2H,aAChB9J,KAAKmC,KAAK2H,aAAalI,eAAe1F,IACtC0N,EAAII,iBAAiB9N,EAAG8D,KAAKmC,KAAK2H,aAAa5N,GAI/D,CACA,MAAOoK,GAAK,CACZ,GAAI,SAAWtG,KAAKmJ,OAChB,IACIS,EAAII,iBAAiB,eAAgB,2BACzC,CACA,MAAO1D,GAAK,CAEhB,IACIsD,EAAII,iBAAiB,SAAU,MACnC,CACA,MAAO1D,GAAK,CACmB,QAA9BoD,EAAK1J,KAAKmC,KAAKkF,iBAA8B,IAAPqC,GAAyBA,EAAGO,WAAWL,GAE1E,oBAAqBA,IACrBA,EAAIxC,gBAAkBpH,KAAKmC,KAAKiF,iBAEhCpH,KAAKmC,KAAK+H,iBACVN,EAAIO,QAAUnK,KAAKmC,KAAK+H,gBAE5BN,EAAIQ,mBAAqB,WACrB,IAAIV,EACmB,IAAnBE,EAAI3F,aAC2B,QAA9ByF,EAAKC,EAAKxH,KAAKkF,iBAA8B,IAAPqC,GAAyBA,EAAGW,aAAaT,IAEhF,IAAMA,EAAI3F,aAEV,MAAQ2F,EAAIU,QAAU,OAASV,EAAIU,OACnCX,EAAKY,SAKLZ,EAAKtH,cAAa,WACdsH,EAAKN,QAA8B,iBAAfO,EAAIU,OAAsBV,EAAIU,OAAS,EAC9D,GAAE,KAGXV,EAAIY,KAAKxK,KAAK3F,KACjB,CACD,MAAOiM,GAOH,YAHAtG,KAAKqC,cAAa,WACdsH,EAAKN,QAAQ/C,EAChB,GAAE,EAEP,CACwB,oBAAbmE,WACPzK,KAAK0K,MAAQ5B,EAAQ6B,gBACrB7B,EAAQ8B,SAAS5K,KAAK0K,OAAS1K,KAEvC,GACA,CAAA/F,IAAA,UAAA6J,MAKA,SAAQoC,GACJlG,KAAKgB,aAAa,QAASkF,EAAKlG,KAAK4J,KACrC5J,KAAK6K,SAAQ,EACjB,GACA,CAAA5Q,IAAA,UAAA6J,MAKA,SAAQgH,GACJ,QAAI,IAAuB9K,KAAK4J,KAAO,OAAS5J,KAAK4J,IAArD,CAIA,GADA5J,KAAK4J,IAAIQ,mBAAqB3D,GAC1BqE,EACA,IACI9K,KAAK4J,IAAImB,OACb,CACA,MAAOzE,GAAK,CAEQ,oBAAbmE,iBACA3B,EAAQ8B,SAAS5K,KAAK0K,OAEjC1K,KAAK4J,IAAM,IAXX,CAYJ,GACA,CAAA3P,IAAA,SAAA6J,MAKA,WACI,IAAMzJ,EAAO2F,KAAK4J,IAAIoB,aACT,OAAT3Q,IACA2F,KAAKgB,aAAa,OAAQ3G,GAC1B2F,KAAKgB,aAAa,WAClBhB,KAAK6K,UAEb,GACA,CAAA5Q,IAAA,QAAA6J,MAKA,WACI9D,KAAK6K,SACT,KAAC/B,CAAA,EA7IwBpJ,GAsJ7B,GAPAoJ,GAAQ6B,cAAgB,EACxB7B,GAAQ8B,SAAW,CAAA,EAMK,oBAAbH,SAEP,GAA2B,mBAAhBQ,YAEPA,YAAY,WAAYC,SAEvB,GAAgC,mBAArBrL,iBAAiC,CAE7CA,iBADyB,eAAgBiC,EAAa,WAAa,SAChCoJ,IAAe,EACtD,CAEJ,SAASA,KACL,IAAK,IAAIhP,KAAK4M,GAAQ8B,SACd9B,GAAQ8B,SAAShJ,eAAe1F,IAChC4M,GAAQ8B,SAAS1O,GAAG6O,OAGhC,CCpYO,IAAMI,GACqC,mBAAZC,SAAqD,mBAApBA,QAAQC,QAEhE,SAAC3K,GAAE,OAAK0K,QAAQC,UAAUnN,KAAKwC,EAAG,EAGlC,SAACA,EAAI2B,GAAY,OAAKA,EAAa3B,EAAI,EAAE,EAG3C4K,GAAYxJ,EAAWwJ,WAAaxJ,EAAWyJ,aCJtDC,GAAqC,oBAAdC,WACI,iBAAtBA,UAAUC,SACmB,gBAApCD,UAAUC,QAAQC,cACTC,YAAE/E,GAAAlE,EAAAiJ,EAAA/E,GAAA,IAAAjE,EAAAC,EAAA+I,GAOX,SAAAA,EAAYzJ,GAAM,IAAAc,EAE0B,OAF1BC,OAAA0I,IACd3I,EAAAL,EAAAlI,UAAMyH,IACDjH,gBAAkBiH,EAAKgF,YAAYlE,CAC5C,CAmIC,OAnIAE,EAAAyI,EAAA,CAAA,CAAA3R,IAAA,OAAAsN,IACD,WACI,MAAO,WACX,GAAC,CAAAtN,IAAA,SAAA6J,MACD,WACI,GAAK9D,KAAK6L,QAAV,CAIA,IAAM9C,EAAM/I,KAAK+I,MACX+C,EAAY9L,KAAKmC,KAAK2J,UAEtB3J,EAAOqJ,GACP,CAAA,EACAjK,EAAKvB,KAAKmC,KAAM,QAAS,oBAAqB,MAAO,MAAO,aAAc,OAAQ,KAAM,UAAW,qBAAsB,eAAgB,kBAAmB,SAAU,aAAc,SAAU,uBAChMnC,KAAKmC,KAAK2H,eACV3H,EAAK4J,QAAU/L,KAAKmC,KAAK2H,cAE7B,IACI9J,KAAKgM,GACyBR,GAIpB,IAAIF,GAAUvC,EAAK+C,EAAW3J,GAH9B2J,EACI,IAAIR,GAAUvC,EAAK+C,GACnB,IAAIR,GAAUvC,EAE/B,CACD,MAAO7C,GACH,OAAOlG,KAAKgB,aAAa,QAASkF,EACtC,CACAlG,KAAKgM,GAAGzP,WAAayD,KAAK6D,OAAOtH,WACjCyD,KAAKiM,mBAtBL,CAuBJ,GACA,CAAAhS,IAAA,oBAAA6J,MAKA,WAAoB,IAAAL,EAAAzD,KAChBA,KAAKgM,GAAGE,OAAS,WACTzI,EAAKtB,KAAKgK,WACV1I,EAAKuI,GAAGI,QAAQC,QAEpB5I,EAAKwE,UAETjI,KAAKgM,GAAGM,QAAU,SAACC,GAAU,OAAK9I,EAAKW,QAAQ,CAC3CrB,YAAa,8BACbC,QAASuJ,GACX,EACFvM,KAAKgM,GAAGQ,UAAY,SAACC,GAAE,OAAKhJ,EAAK8F,OAAOkD,EAAGpS,KAAK,EAChD2F,KAAKgM,GAAGU,QAAU,SAACpG,GAAC,OAAK7C,EAAK4F,QAAQ,kBAAmB/C,EAAE,CAC/D,GAAC,CAAArM,IAAA,QAAA6J,MACD,SAAMO,GAAS,IAAAuD,EAAA5H,KACXA,KAAK0D,UAAW,EAGhB,IADA,IAAAiJ,EAAAA,WAEI,IAAM5O,EAASsG,EAAQnI,GACjB0Q,EAAa1Q,IAAMmI,EAAQzH,OAAS,EAC1C5B,EAAa+C,EAAQ6J,EAAK1M,gBAAgB,SAACb,GAmBvC,IAGQuN,EAAKoE,GAAGxB,KAAKnQ,EAKrB,CACA,MAAOiM,GACP,CACIsG,GAGAzB,IAAS,WACLvD,EAAKlE,UAAW,EAChBkE,EAAK5G,aAAa,QACtB,GAAG4G,EAAKvF,aAEhB,KAzCKnG,EAAI,EAAGA,EAAImI,EAAQzH,OAAQV,IAAGyQ,GA2C3C,GAAC,CAAA1S,IAAA,UAAA6J,MACD,gBAC2B,IAAZ9D,KAAKgM,KACZhM,KAAKgM,GAAG7D,QACRnI,KAAKgM,GAAK,KAElB,GACA,CAAA/R,IAAA,MAAA6J,MAKA,WACI,IAAMY,EAAS1E,KAAKmC,KAAKgD,OAAS,MAAQ,KACpCvB,EAAQ5D,KAAK4D,OAAS,GAS5B,OAPI5D,KAAKmC,KAAKqG,oBACV5E,EAAM5D,KAAKmC,KAAKsG,gBAAkB3C,KAGjC9F,KAAK9E,iBACN0I,EAAM+E,IAAM,GAET3I,KAAK4I,UAAUlE,EAAQd,EAClC,GACA,CAAA3J,IAAA,QAAA6J,MAMA,WACI,QAASwH,EACb,KAACM,CAAA,EA7ImBtI,GCNXuJ,YAAEhG,GAAAlE,EAAAkK,EAAAhG,GAAA,IAAAjE,EAAAC,EAAAgK,GAAA,SAAAA,IAAA,OAAA3J,OAAA2J,GAAAjK,EAAAvC,MAAAL,KAAAM,UAAA,CAkEV,OAlEU6C,EAAA0J,EAAA,CAAA,CAAA5S,IAAA,OAAAsN,IACX,WACI,MAAO,cACX,GAAC,CAAAtN,IAAA,SAAA6J,MACD,WAAS,IAAAb,EAAAjD,KAEuB,mBAAjB8M,eAIX9M,KAAK+M,UAAY,IAAID,aAAa9M,KAAK4I,UAAU,SAAU5I,KAAKmC,KAAK6K,iBAAiBhN,KAAKiN,OAC3FjN,KAAK+M,UAAUG,OACVhP,MAAK,WACN+E,EAAKmB,SACT,IAAE,OACS,SAAC8B,GACRjD,EAAKoG,QAAQ,qBAAsBnD,EACvC,IAEAlG,KAAK+M,UAAUI,MAAMjP,MAAK,WACtB+E,EAAK8J,UAAUK,4BAA4BlP,MAAK,SAACmP,GAC7C,IAAMC,Eb8Df,SAAmCC,EAAYhR,GAC7CH,IACDA,EAAe,IAAIoR,aAEvB,IAAMvO,EAAS,GACXwO,EAAQ,EACRC,GAAkB,EAClBC,GAAW,EACf,OAAO,IAAI9P,gBAAgB,CACvBC,UAASA,SAACsB,EAAOpB,GAEb,IADAiB,EAAOiB,KAAKd,KACC,CACT,GAAc,IAAVqO,EAA+B,CAC/B,GAAIzO,EAAYC,GAAU,EACtB,MAEJ,IAAMV,EAASc,EAAaJ,EAAQ,GACpC0O,EAAkC,MAAV,IAAZpP,EAAO,IACnBmP,EAA6B,IAAZnP,EAAO,GAEpBkP,EADAC,EAAiB,IACT,EAEgB,MAAnBA,EACG,EAGA,CAEhB,MACK,GAAc,IAAVD,EAA2C,CAChD,GAAIzO,EAAYC,GAAU,EACtB,MAEJ,IAAM2O,EAAcvO,EAAaJ,EAAQ,GACzCyO,EAAiB,IAAIjP,SAASmP,EAAY7S,OAAQ6S,EAAY9R,WAAY8R,EAAYhR,QAAQiR,UAAU,GACxGJ,EAAQ,CACZ,MACK,GAAc,IAAVA,EAA2C,CAChD,GAAIzO,EAAYC,GAAU,EACtB,MAEJ,IAAM2O,EAAcvO,EAAaJ,EAAQ,GACnCN,EAAO,IAAIF,SAASmP,EAAY7S,OAAQ6S,EAAY9R,WAAY8R,EAAYhR,QAC5EkR,EAAInP,EAAKoP,UAAU,GACzB,GAAID,EAAIlI,KAAKoI,IAAI,EAAG,IAAW,EAAG,CAE9BhQ,EAAWe,QAAQ5E,GACnB,KACJ,CACAuT,EAAiBI,EAAIlI,KAAKoI,IAAI,EAAG,IAAMrP,EAAKoP,UAAU,GACtDN,EAAQ,CACZ,KACK,CACD,GAAIzO,EAAYC,GAAUyO,EACtB,MAEJ,IAAMrT,EAAOgF,EAAaJ,EAAQyO,GAClC1P,EAAWe,QAAQ1C,EAAasR,EAAWtT,EAAO+B,EAAaoB,OAAOnD,GAAOkC,IAC7EkR,EAAQ,CACZ,CACA,GAAuB,IAAnBC,GAAwBA,EAAiBH,EAAY,CACrDvP,EAAWe,QAAQ5E,GACnB,KACJ,CACJ,CACJ,GAER,CajIsC8T,CAA0B7I,OAAO8I,iBAAkBjL,EAAKY,OAAOtH,YAC/E4R,EAASd,EAAOe,SAASC,YAAYf,GAAegB,YACpDC,EAAgB3Q,IACtB2Q,EAAcH,SAASI,OAAOnB,EAAO3J,UACrCT,EAAKwL,OAASF,EAAc7K,SAASgL,aACxB,SAAPC,IACFR,EACKQ,OACAzQ,MAAK,SAAAjD,GAAqB,IAAlB2T,EAAI3T,EAAJ2T,KAAM9K,EAAK7I,EAAL6I,MACX8K,IAGJ3L,EAAKsB,SAAST,GACd6K,IACH,WACU,SAACzI,GACX,IAELyI,GACA,IAAM5Q,EAAS,CAAE3D,KAAM,QACnB6I,EAAKW,MAAM8E,MACX3K,EAAO1D,KAAI,WAAAkM,OAActD,EAAKW,MAAM8E,IAAO,OAE/CzF,EAAKwL,OAAOnK,MAAMvG,GAAQG,MAAK,WAAA,OAAM+E,EAAKgF,WAC9C,GACJ,IACJ,GAAC,CAAAhO,IAAA,QAAA6J,MACD,SAAMO,GAAS,IAAAZ,EAAAzD,KACXA,KAAK0D,UAAW,EAChB,IADsB,IAAAiJ,EAAAA,WAElB,IAAM5O,EAASsG,EAAQnI,GACjB0Q,EAAa1Q,IAAMmI,EAAQzH,OAAS,EAC1C6G,EAAKgL,OAAOnK,MAAMvG,GAAQG,MAAK,WACvB0O,GACAzB,IAAS,WACL1H,EAAKC,UAAW,EAChBD,EAAKzC,aAAa,QACtB,GAAGyC,EAAKpB,aAEhB,KAVKnG,EAAI,EAAGA,EAAImI,EAAQzH,OAAQV,IAAGyQ,GAY3C,GAAC,CAAA1S,IAAA,UAAA6J,MACD,WACI,IAAI4F,EACsB,QAAzBA,EAAK1J,KAAK+M,iBAA8B,IAAPrD,GAAyBA,EAAGvB,OAClE,KAAC0E,CAAA,EAlEmBvJ,GCAXuL,GAAa,CACtBC,UAAWlD,GACXmD,aAAclC,GACd/F,QAASF,ICaPoI,GAAK,sPACLC,GAAQ,CACV,SAAU,WAAY,YAAa,WAAY,OAAQ,WAAY,OAAQ,OAAQ,WAAY,OAAQ,YAAa,OAAQ,QAAS,UAElI,SAASC,GAAM5J,GAClB,IAAM6J,EAAM7J,EAAK8J,EAAI9J,EAAIL,QAAQ,KAAMqB,EAAIhB,EAAIL,QAAQ,MAC7C,GAANmK,IAAiB,GAAN9I,IACXhB,EAAMA,EAAI3I,UAAU,EAAGyS,GAAK9J,EAAI3I,UAAUyS,EAAG9I,GAAG+I,QAAQ,KAAM,KAAO/J,EAAI3I,UAAU2J,EAAGhB,EAAI1I,SAG9F,IADA,IAwBmBgH,EACbvJ,EAzBFiV,EAAIN,GAAGO,KAAKjK,GAAO,IAAKyD,EAAM,CAAE,EAAE7M,EAAI,GACnCA,KACH6M,EAAIkG,GAAM/S,IAAMoT,EAAEpT,IAAM,GAU5B,OARU,GAANkT,IAAiB,GAAN9I,IACXyC,EAAIyG,OAASL,EACbpG,EAAI0G,KAAO1G,EAAI0G,KAAK9S,UAAU,EAAGoM,EAAI0G,KAAK7S,OAAS,GAAGyS,QAAQ,KAAM,KACpEtG,EAAI2G,UAAY3G,EAAI2G,UAAUL,QAAQ,IAAK,IAAIA,QAAQ,IAAK,IAAIA,QAAQ,KAAM,KAC9EtG,EAAI4G,SAAU,GAElB5G,EAAI6G,UAIR,SAAmB9U,EAAKgK,GACpB,IAAM+K,EAAO,WAAYC,EAAQhL,EAAKuK,QAAQQ,EAAM,KAAKnU,MAAM,KACvC,KAApBoJ,EAAKrF,MAAM,EAAG,IAA6B,IAAhBqF,EAAKlI,QAChCkT,EAAMlP,OAAO,EAAG,GAEE,KAAlBkE,EAAKrF,OAAO,IACZqQ,EAAMlP,OAAOkP,EAAMlT,OAAS,EAAG,GAEnC,OAAOkT,CACX,CAboBF,CAAU7G,EAAKA,EAAU,MACzCA,EAAIgH,UAaenM,EAbUmF,EAAW,MAclC1O,EAAO,CAAA,EACbuJ,EAAMyL,QAAQ,6BAA6B,SAAUW,EAAIC,EAAIC,GACrDD,IACA5V,EAAK4V,GAAMC,EAEnB,IACO7V,GAnBA0O,CACX,CClCaoH,IAAAA,YAAM5M,GAAAZ,EAAAwN,EAAA5M,GAAA,IAAAX,EAAAC,EAAAsN,GAOf,SAAAA,EAAYpH,GAAgB,IAAA9F,EAAXd,EAAI7B,UAAA1D,OAAA,QAAA+H,IAAArE,UAAA,GAAAA,UAAA,GAAG,CAAA,EAgGR,OAhGU4C,OAAAiN,IACtBlN,EAAAL,EAAAlI,KAAAsF,OACKzD,WLJoB,cKKzB0G,EAAKmN,YAAc,GACfrH,GAAO,WAAQsH,EAAYtH,KAC3B5G,EAAO4G,EACPA,EAAM,MAENA,GACAA,EAAMmG,GAAMnG,GACZ5G,EAAK6C,SAAW+D,EAAI0G,KACpBtN,EAAKgD,OAA0B,UAAjB4D,EAAI9B,UAAyC,QAAjB8B,EAAI9B,SAC9C9E,EAAK+C,KAAO6D,EAAI7D,KACZ6D,EAAInF,QACJzB,EAAKyB,MAAQmF,EAAInF,QAEhBzB,EAAKsN,OACVtN,EAAK6C,SAAWkK,GAAM/M,EAAKsN,MAAMA,MAErCvN,EAAqByB,EAAAV,GAAOd,GAC5Bc,EAAKkC,OACD,MAAQhD,EAAKgD,OACPhD,EAAKgD,OACe,oBAAb4B,UAA4B,WAAaA,SAASE,SAC/D9E,EAAK6C,WAAa7C,EAAK+C,OAEvB/C,EAAK+C,KAAOjC,EAAKkC,OAAS,MAAQ,MAEtClC,EAAK+B,SACD7C,EAAK6C,WACoB,oBAAb+B,SAA2BA,SAAS/B,SAAW,aAC/D/B,EAAKiC,KACD/C,EAAK+C,OACoB,oBAAb6B,UAA4BA,SAAS7B,KACvC6B,SAAS7B,KACTjC,EAAKkC,OACD,MACA,MAClBlC,EAAK4L,WAAa1M,EAAK0M,YAAc,CACjC,UACA,YACA,gBAEJ5L,EAAKmN,YAAc,GACnBnN,EAAKqN,cAAgB,EACrBrN,EAAKd,KAAO0G,EAAc,CACtB/D,KAAM,aACNyL,OAAO,EACPnJ,iBAAiB,EACjBoJ,SAAS,EACT/H,eAAgB,IAChBgI,iBAAiB,EACjBC,kBAAkB,EAClBC,oBAAoB,EACpBC,kBAAmB,CACfC,UAAW,MAEf7D,iBAAkB,CAAE,EACpB8D,qBAAqB,GACtB3O,GACHc,EAAKd,KAAK2C,KACN7B,EAAKd,KAAK2C,KAAKuK,QAAQ,MAAO,KACzBpM,EAAKd,KAAKuO,iBAAmB,IAAM,IACb,iBAApBzN,EAAKd,KAAKyB,QACjBX,EAAKd,KAAKyB,MTrDf,SAAgBmN,GAGnB,IAFA,IAAIC,EAAM,CAAA,EACNC,EAAQF,EAAGrV,MAAM,KACZQ,EAAI,EAAGgV,EAAID,EAAMrU,OAAQV,EAAIgV,EAAGhV,IAAK,CAC1C,IAAIiV,EAAOF,EAAM/U,GAAGR,MAAM,KAC1BsV,EAAII,mBAAmBD,EAAK,KAAOC,mBAAmBD,EAAK,GAC/D,CACA,OAAOH,CACX,CS6C8BxT,CAAOyF,EAAKd,KAAKyB,QAGvCX,EAAKoO,GAAK,KACVpO,EAAKqO,SAAW,KAChBrO,EAAKsO,aAAe,KACpBtO,EAAKuO,YAAc,KAEnBvO,EAAKwO,iBAAmB,KACQ,mBAArB5R,mBACHoD,EAAKd,KAAK2O,sBAIV7N,EAAKyO,0BAA4B,WACzBzO,EAAK8J,YAEL9J,EAAK8J,UAAUvM,qBACfyC,EAAK8J,UAAU5E,UAGvBtI,iBAAiB,eAAgBoD,EAAKyO,2BAA2B,IAE/C,cAAlBzO,EAAK+B,WACL/B,EAAK0O,qBAAuB,WACxB1O,EAAKmB,QAAQ,kBAAmB,CAC5BrB,YAAa,6BAGrBlD,iBAAiB,UAAWoD,EAAK0O,sBAAsB,KAG/D1O,EAAK4G,OAAO5G,CAChB,CAgeC,OA/dDE,EAAAgN,EAAA,CAAA,CAAAlW,IAAA,kBAAA6J,MAOA,SAAgBmJ,GACZ,IAAMrJ,EAAQiF,EAAc,CAAA,EAAI7I,KAAKmC,KAAKyB,OAE1CA,EAAMgO,IhBgCU,EgB9BhBhO,EAAMmJ,UAAYE,EAEdjN,KAAKqR,KACLzN,EAAM8E,IAAM1I,KAAKqR,IACrB,IAAMlP,EAAO0G,EAAc,GAAI7I,KAAKmC,KAAM,CACtCyB,MAAAA,EACAC,OAAQ7D,KACRgF,SAAUhF,KAAKgF,SACfG,OAAQnF,KAAKmF,OACbD,KAAMlF,KAAKkF,MACZlF,KAAKmC,KAAK6K,iBAAiBC,IAC9B,OAAO,IAAI4B,GAAW5B,GAAM9K,EAChC,GACA,CAAAlI,IAAA,OAAA6J,MAKA,WAAO,IACCiJ,EADDtJ,EAAAzD,KAEH,GAAIA,KAAKmC,KAAKsO,iBACVN,EAAO0B,wBACmC,IAA1C7R,KAAK6O,WAAW5J,QAAQ,aACxB8H,EAAY,gBAEX,IAAI,IAAM/M,KAAK6O,WAAWjS,OAK3B,YAHAoD,KAAKqC,cAAa,WACdoB,EAAKzC,aAAa,QAAS,0BAC9B,GAAE,GAIH+L,EAAY/M,KAAK6O,WAAW,EAChC,CACA7O,KAAKiE,WAAa,UAElB,IACI8I,EAAY/M,KAAK8R,gBAAgB/E,EACpC,CACD,MAAOzG,GAGH,OAFAtG,KAAK6O,WAAWtP,aAChBS,KAAK6J,MAET,CACAkD,EAAUlD,OACV7J,KAAK+R,aAAahF,EACtB,GACA,CAAA9S,IAAA,eAAA6J,MAKA,SAAaiJ,GAAW,IAAAnF,EAAA5H,KAChBA,KAAK+M,WACL/M,KAAK+M,UAAUvM,qBAGnBR,KAAK+M,UAAYA,EAEjBA,EACKnN,GAAG,QAASI,KAAKgS,QAAQ1P,KAAKtC,OAC9BJ,GAAG,SAAUI,KAAKuE,SAASjC,KAAKtC,OAChCJ,GAAG,QAASI,KAAKqJ,QAAQ/G,KAAKtC,OAC9BJ,GAAG,SAAS,SAACkD,GAAM,OAAK8E,EAAKxD,QAAQ,kBAAmBtB,KACjE,GACA,CAAA7I,IAAA,QAAA6J,MAMA,SAAMmJ,GAAM,IAAA/E,EAAAlI,KACJ+M,EAAY/M,KAAK8R,gBAAgB7E,GACjCgF,GAAS,EACb9B,EAAO0B,uBAAwB,EAC/B,IAAMK,EAAkB,WAChBD,IAEJlF,EAAUvC,KAAK,CAAC,CAAEpQ,KAAM,OAAQC,KAAM,WACtC0S,EAAU5M,KAAK,UAAU,SAACgS,GACtB,IAAIF,EAEJ,GAAI,SAAWE,EAAI/X,MAAQ,UAAY+X,EAAI9X,KAAM,CAG7C,GAFA6N,EAAKkK,WAAY,EACjBlK,EAAKlH,aAAa,YAAa+L,IAC1BA,EACD,OACJoD,EAAO0B,sBAAwB,cAAgB9E,EAAUE,KACzD/E,EAAK6E,UAAUtF,OAAM,WACbwK,GAEA,WAAa/J,EAAKjE,aAEtB4G,IACA3C,EAAK6J,aAAahF,GAClBA,EAAUvC,KAAK,CAAC,CAAEpQ,KAAM,aACxB8N,EAAKlH,aAAa,UAAW+L,GAC7BA,EAAY,KACZ7E,EAAKkK,WAAY,EACjBlK,EAAKmK,QACT,GACJ,KACK,CACD,IAAMnM,EAAM,IAAI7C,MAAM,eAEtB6C,EAAI6G,UAAYA,EAAUE,KAC1B/E,EAAKlH,aAAa,eAAgBkF,EACtC,CACJ,MAEJ,SAASoM,IACDL,IAGJA,GAAS,EACTpH,IACAkC,EAAU5E,QACV4E,EAAY,KAChB,CAEA,IAAML,EAAU,SAACxG,GACb,IAAMqM,EAAQ,IAAIlP,MAAM,gBAAkB6C,GAE1CqM,EAAMxF,UAAYA,EAAUE,KAC5BqF,IACApK,EAAKlH,aAAa,eAAgBuR,IAEtC,SAASC,IACL9F,EAAQ,mBACZ,CAEA,SAASJ,IACLI,EAAQ,gBACZ,CAEA,SAAS+F,EAAUC,GACX3F,GAAa2F,EAAGzF,OAASF,EAAUE,MACnCqF,GAER,CAEA,IAAMzH,EAAU,WACZkC,EAAUxM,eAAe,OAAQ2R,GACjCnF,EAAUxM,eAAe,QAASmM,GAClCK,EAAUxM,eAAe,QAASiS,GAClCtK,EAAK9H,IAAI,QAASkM,GAClBpE,EAAK9H,IAAI,YAAaqS,IAE1B1F,EAAU5M,KAAK,OAAQ+R,GACvBnF,EAAU5M,KAAK,QAASuM,GACxBK,EAAU5M,KAAK,QAASqS,GACxBxS,KAAKG,KAAK,QAASmM,GACnBtM,KAAKG,KAAK,YAAasS,IACwB,IAA3CzS,KAAKsR,SAASrM,QAAQ,iBACb,iBAATgI,EAEAjN,KAAKqC,cAAa,WACT4P,GACDlF,EAAUlD,MAEjB,GAAE,KAGHkD,EAAUlD,MAElB,GACA,CAAA5P,IAAA,SAAA6J,MAKA,WAOI,GANA9D,KAAKiE,WAAa,OAClBkM,EAAO0B,sBAAwB,cAAgB7R,KAAK+M,UAAUE,KAC9DjN,KAAKgB,aAAa,QAClBhB,KAAKqS,QAGD,SAAWrS,KAAKiE,YAAcjE,KAAKmC,KAAKqO,QAGxC,IAFA,IAAItU,EAAI,EACFgV,EAAIlR,KAAKsR,SAAS1U,OACjBV,EAAIgV,EAAGhV,IACV8D,KAAK2S,MAAM3S,KAAKsR,SAASpV,GAGrC,GACA,CAAAjC,IAAA,WAAA6J,MAKA,SAAS/F,GACL,GAAI,YAAciC,KAAKiE,YACnB,SAAWjE,KAAKiE,YAChB,YAAcjE,KAAKiE,WAKnB,OAJAjE,KAAKgB,aAAa,SAAUjD,GAE5BiC,KAAKgB,aAAa,aAClBhB,KAAK4S,mBACG7U,EAAO3D,MACX,IAAK,OACD4F,KAAK6S,YAAYC,KAAK5D,MAAMnR,EAAO1D,OACnC,MACJ,IAAK,OACD2F,KAAK+S,WAAW,QAChB/S,KAAKgB,aAAa,QAClBhB,KAAKgB,aAAa,QAClB,MACJ,IAAK,QACD,IAAMkF,EAAM,IAAI7C,MAAM,gBAEtB6C,EAAI8M,KAAOjV,EAAO1D,KAClB2F,KAAKqJ,QAAQnD,GACb,MACJ,IAAK,UACDlG,KAAKgB,aAAa,OAAQjD,EAAO1D,MACjC2F,KAAKgB,aAAa,UAAWjD,EAAO1D,MAMpD,GACA,CAAAJ,IAAA,cAAA6J,MAMA,SAAYzJ,GACR2F,KAAKgB,aAAa,YAAa3G,GAC/B2F,KAAKqR,GAAKhX,EAAKqO,IACf1I,KAAK+M,UAAUnJ,MAAM8E,IAAMrO,EAAKqO,IAChC1I,KAAKsR,SAAWtR,KAAKiT,eAAe5Y,EAAKiX,UACzCtR,KAAKuR,aAAelX,EAAKkX,aACzBvR,KAAKwR,YAAcnX,EAAKmX,YACxBxR,KAAKuN,WAAalT,EAAKkT,WACvBvN,KAAKiI,SAED,WAAajI,KAAKiE,YAEtBjE,KAAK4S,kBACT,GACA,CAAA3Y,IAAA,mBAAA6J,MAKA,WAAmB,IAAAsE,EAAApI,KACfA,KAAKuC,eAAevC,KAAKyR,kBACzBzR,KAAKyR,iBAAmBzR,KAAKqC,cAAa,WACtC+F,EAAKhE,QAAQ,eAChB,GAAEpE,KAAKuR,aAAevR,KAAKwR,aACxBxR,KAAKmC,KAAKgK,WACVnM,KAAKyR,iBAAiBpF,OAE9B,GACA,CAAApS,IAAA,UAAA6J,MAKA,WACI9D,KAAKoQ,YAAYxP,OAAO,EAAGZ,KAAKsQ,eAIhCtQ,KAAKsQ,cAAgB,EACjB,IAAMtQ,KAAKoQ,YAAYxT,OACvBoD,KAAKgB,aAAa,SAGlBhB,KAAKqS,OAEb,GACA,CAAApY,IAAA,QAAA6J,MAKA,WACI,GAAI,WAAa9D,KAAKiE,YAClBjE,KAAK+M,UAAUrJ,WACd1D,KAAKoS,WACNpS,KAAKoQ,YAAYxT,OAAQ,CACzB,IAAMyH,EAAUrE,KAAKkT,qBACrBlT,KAAK+M,UAAUvC,KAAKnG,GAGpBrE,KAAKsQ,cAAgBjM,EAAQzH,OAC7BoD,KAAKgB,aAAa,QACtB,CACJ,GACA,CAAA/G,IAAA,qBAAA6J,MAMA,WAII,KAH+B9D,KAAKuN,YACR,YAAxBvN,KAAK+M,UAAUE,MACfjN,KAAKoQ,YAAYxT,OAAS,GAE1B,OAAOoD,KAAKoQ,YAGhB,IADA,IZtZmBtV,EYsZfqY,EAAc,EACTjX,EAAI,EAAGA,EAAI8D,KAAKoQ,YAAYxT,OAAQV,IAAK,CAC9C,IAAM7B,EAAO2F,KAAKoQ,YAAYlU,GAAG7B,KAIjC,GAHIA,IACA8Y,GZzZO,iBADIrY,EY0ZeT,GZnZ1C,SAAoBiL,GAEhB,IADA,IAAI8N,EAAI,EAAGxW,EAAS,EACXV,EAAI,EAAGgV,EAAI5L,EAAI1I,OAAQV,EAAIgV,EAAGhV,KACnCkX,EAAI9N,EAAInJ,WAAWD,IACX,IACJU,GAAU,EAELwW,EAAI,KACTxW,GAAU,EAELwW,EAAI,OAAUA,GAAK,MACxBxW,GAAU,GAGVV,IACAU,GAAU,GAGlB,OAAOA,CACX,CAxBeyW,CAAWvY,GAGf8K,KAAK0N,KAPQ,MAOFxY,EAAIiB,YAAcjB,EAAIwE,QYuZ5BpD,EAAI,GAAKiX,EAAcnT,KAAKuN,WAC5B,OAAOvN,KAAKoQ,YAAY3Q,MAAM,EAAGvD,GAErCiX,GAAe,CACnB,CACA,OAAOnT,KAAKoQ,WAChB,GACA,CAAAnW,IAAA,QAAA6J,MAQA,SAAMqO,EAAKoB,EAASxT,GAEhB,OADAC,KAAK+S,WAAW,UAAWZ,EAAKoB,EAASxT,GAClCC,IACX,GAAC,CAAA/F,IAAA,OAAA6J,MACD,SAAKqO,EAAKoB,EAASxT,GAEf,OADAC,KAAK+S,WAAW,UAAWZ,EAAKoB,EAASxT,GAClCC,IACX,GACA,CAAA/F,IAAA,aAAA6J,MASA,SAAW1J,EAAMC,EAAMkZ,EAASxT,GAS5B,GARI,mBAAsB1F,IACtB0F,EAAK1F,EACLA,OAAOsK,GAEP,mBAAsB4O,IACtBxT,EAAKwT,EACLA,EAAU,MAEV,YAAcvT,KAAKiE,YAAc,WAAajE,KAAKiE,WAAvD,EAGAsP,EAAUA,GAAW,IACbC,UAAW,IAAUD,EAAQC,SACrC,IAAMzV,EAAS,CACX3D,KAAMA,EACNC,KAAMA,EACNkZ,QAASA,GAEbvT,KAAKgB,aAAa,eAAgBjD,GAClCiC,KAAKoQ,YAAYlQ,KAAKnC,GAClBgC,GACAC,KAAKG,KAAK,QAASJ,GACvBC,KAAKqS,OAZL,CAaJ,GACA,CAAApY,IAAA,QAAA6J,MAGA,WAAQ,IAAAkF,EAAAhJ,KACEmI,EAAQ,WACVa,EAAK5E,QAAQ,gBACb4E,EAAK+D,UAAU5E,SAEbsL,EAAkB,SAAlBA,IACFzK,EAAK5I,IAAI,UAAWqT,GACpBzK,EAAK5I,IAAI,eAAgBqT,GACzBtL,KAEEuL,EAAiB,WAEnB1K,EAAK7I,KAAK,UAAWsT,GACrBzK,EAAK7I,KAAK,eAAgBsT,IAqB9B,MAnBI,YAAczT,KAAKiE,YAAc,SAAWjE,KAAKiE,aACjDjE,KAAKiE,WAAa,UACdjE,KAAKoQ,YAAYxT,OACjBoD,KAAKG,KAAK,SAAS,WACX6I,EAAKoJ,UACLsB,IAGAvL,GAER,IAEKnI,KAAKoS,UACVsB,IAGAvL,KAGDnI,IACX,GACA,CAAA/F,IAAA,UAAA6J,MAKA,SAAQoC,GACJiK,EAAO0B,uBAAwB,EAC/B7R,KAAKgB,aAAa,QAASkF,GAC3BlG,KAAKoE,QAAQ,kBAAmB8B,EACpC,GACA,CAAAjM,IAAA,UAAA6J,MAKA,SAAQhB,EAAQC,GACR,YAAc/C,KAAKiE,YACnB,SAAWjE,KAAKiE,YAChB,YAAcjE,KAAKiE,aAEnBjE,KAAKuC,eAAevC,KAAKyR,kBAEzBzR,KAAK+M,UAAUvM,mBAAmB,SAElCR,KAAK+M,UAAU5E,QAEfnI,KAAK+M,UAAUvM,qBACoB,mBAAxBC,sBACPA,oBAAoB,eAAgBT,KAAK0R,2BAA2B,GACpEjR,oBAAoB,UAAWT,KAAK2R,sBAAsB,IAG9D3R,KAAKiE,WAAa,SAElBjE,KAAKqR,GAAK,KAEVrR,KAAKgB,aAAa,QAAS8B,EAAQC,GAGnC/C,KAAKoQ,YAAc,GACnBpQ,KAAKsQ,cAAgB,EAE7B,GACA,CAAArW,IAAA,iBAAA6J,MAMA,SAAewN,GAIX,IAHA,IAAMqC,EAAmB,GACrBzX,EAAI,EACFsD,EAAI8R,EAAS1U,OACZV,EAAIsD,EAAGtD,KACL8D,KAAK6O,WAAW5J,QAAQqM,EAASpV,KAClCyX,EAAiBzT,KAAKoR,EAASpV,IAEvC,OAAOyX,CACX,KAACxD,CAAA,EAxkBuBzQ,GA0kBtBkU,GAAC3M,ShBvbiB,EiBxJAkJ,GAAOlJ,SCF/B,IAAMtM,GAA+C,mBAAhBC,YAC/BC,GAAS,SAACC,GACZ,MAAqC,mBAAvBF,YAAYC,OACpBD,YAAYC,OAAOC,GACnBA,EAAIC,kBAAkBH,WAChC,EACMH,GAAWb,OAAOY,UAAUC,SAC5BH,GAAiC,mBAATC,MACT,oBAATA,MACoB,6BAAxBE,GAASC,KAAKH,MAChBsZ,GAAiC,mBAATC,MACT,oBAATA,MACoB,6BAAxBrZ,GAASC,KAAKoZ,MAMf,SAASnG,GAAS7S,GACrB,OAASH,KAA0BG,aAAeF,aAAeC,GAAOC,KACnER,IAAkBQ,aAAeP,MACjCsZ,IAAkB/Y,aAAegZ,IAC1C,CACO,SAASC,GAAUjZ,EAAKkZ,GAC3B,IAAKlZ,GAAsB,WAAfuV,EAAOvV,GACf,OAAO,EAEX,GAAIiG,MAAMkT,QAAQnZ,GAAM,CACpB,IAAK,IAAIoB,EAAI,EAAGgV,EAAIpW,EAAI8B,OAAQV,EAAIgV,EAAGhV,IACnC,GAAI6X,GAAUjZ,EAAIoB,IACd,OAAO,EAGf,OAAO,CACX,CACA,GAAIyR,GAAS7S,GACT,OAAO,EAEX,GAAIA,EAAIkZ,QACkB,mBAAflZ,EAAIkZ,QACU,IAArB1T,UAAU1D,OACV,OAAOmX,GAAUjZ,EAAIkZ,UAAU,GAEnC,IAAK,IAAM/Z,KAAOa,EACd,GAAIlB,OAAOY,UAAUoH,eAAelH,KAAKI,EAAKb,IAAQ8Z,GAAUjZ,EAAIb,IAChE,OAAO,EAGf,OAAO,CACX,CCzCO,SAASia,GAAkBnW,GAC9B,IAAMoW,EAAU,GACVC,EAAarW,EAAO1D,KACpBga,EAAOtW,EAGb,OAFAsW,EAAKha,KAAOia,GAAmBF,EAAYD,GAC3CE,EAAKE,YAAcJ,EAAQvX,OACpB,CAAEmB,OAAQsW,EAAMF,QAASA,EACpC,CACA,SAASG,GAAmBja,EAAM8Z,GAC9B,IAAK9Z,EACD,OAAOA,EACX,GAAIsT,GAAStT,GAAO,CAChB,IAAMma,EAAc,CAAEC,cAAc,EAAM9O,IAAKwO,EAAQvX,QAEvD,OADAuX,EAAQjU,KAAK7F,GACNma,CACV,CACI,GAAIzT,MAAMkT,QAAQ5Z,GAAO,CAE1B,IADA,IAAMqa,EAAU,IAAI3T,MAAM1G,EAAKuC,QACtBV,EAAI,EAAGA,EAAI7B,EAAKuC,OAAQV,IAC7BwY,EAAQxY,GAAKoY,GAAmBja,EAAK6B,GAAIiY,GAE7C,OAAOO,CACX,CACK,GAAoB,WAAhBrE,EAAOhW,MAAuBA,aAAgB2L,MAAO,CAC1D,IAAM0O,EAAU,CAAA,EAChB,IAAK,IAAMza,KAAOI,EACVT,OAAOY,UAAUoH,eAAelH,KAAKL,EAAMJ,KAC3Cya,EAAQza,GAAOqa,GAAmBja,EAAKJ,GAAMka,IAGrD,OAAOO,CACX,CACA,OAAOra,CACX,CASO,SAASsa,GAAkB5W,EAAQoW,GAGtC,OAFApW,EAAO1D,KAAOua,GAAmB7W,EAAO1D,KAAM8Z,UACvCpW,EAAOwW,YACPxW,CACX,CACA,SAAS6W,GAAmBva,EAAM8Z,GAC9B,IAAK9Z,EACD,OAAOA,EACX,GAAIA,IAA8B,IAAtBA,EAAKoa,aAAuB,CAIpC,GAHyC,iBAAbpa,EAAKsL,KAC7BtL,EAAKsL,KAAO,GACZtL,EAAKsL,IAAMwO,EAAQvX,OAEnB,OAAOuX,EAAQ9Z,EAAKsL,KAGpB,MAAM,IAAItC,MAAM,sBAEvB,CACI,GAAItC,MAAMkT,QAAQ5Z,GACnB,IAAK,IAAI6B,EAAI,EAAGA,EAAI7B,EAAKuC,OAAQV,IAC7B7B,EAAK6B,GAAK0Y,GAAmBva,EAAK6B,GAAIiY,QAGzC,GAAoB,WAAhB9D,EAAOhW,GACZ,IAAK,IAAMJ,KAAOI,EACVT,OAAOY,UAAUoH,eAAelH,KAAKL,EAAMJ,KAC3CI,EAAKJ,GAAO2a,GAAmBva,EAAKJ,GAAMka,IAItD,OAAO9Z,CACX,CC5EA,IAcWwa,GAdLC,GAAkB,CACpB,UACA,gBACA,aACA,gBACA,cACA,mBASJ,SAAWD,GACPA,EAAWA,EAAoB,QAAI,GAAK,UACxCA,EAAWA,EAAuB,WAAI,GAAK,aAC3CA,EAAWA,EAAkB,MAAI,GAAK,QACtCA,EAAWA,EAAgB,IAAI,GAAK,MACpCA,EAAWA,EAA0B,cAAI,GAAK,gBAC9CA,EAAWA,EAAyB,aAAI,GAAK,eAC7CA,EAAWA,EAAuB,WAAI,GAAK,YAC9C,CARD,CAQGA,KAAeA,GAAa,CAAE,IAIjC,IAAaE,GAAO,WAMhB,SAAAA,EAAYC,GAAU9R,OAAA6R,GAClB/U,KAAKgV,SAAWA,CACpB,CA2DC,OA1DD7R,EAAA4R,EAAA,CAAA,CAAA9a,IAAA,SAAA6J,MAMA,SAAOhJ,GACH,OAAIA,EAAIV,OAASya,GAAWI,OAASna,EAAIV,OAASya,GAAWK,MACrDnB,GAAUjZ,GAWX,CAACkF,KAAKmV,eAAera,IAVbkF,KAAKoV,eAAe,CACvBhb,KAAMU,EAAIV,OAASya,GAAWI,MACxBJ,GAAWQ,aACXR,GAAWS,WACjBC,IAAKza,EAAIya,IACTlb,KAAMS,EAAIT,KACVgX,GAAIvW,EAAIuW,IAKxB,GACA,CAAApX,IAAA,iBAAA6J,MAGA,SAAehJ,GAEX,IAAIwK,EAAM,GAAKxK,EAAIV,KAmBnB,OAjBIU,EAAIV,OAASya,GAAWQ,cACxBva,EAAIV,OAASya,GAAWS,aACxBhQ,GAAOxK,EAAIyZ,YAAc,KAIzBzZ,EAAIya,KAAO,MAAQza,EAAIya,MACvBjQ,GAAOxK,EAAIya,IAAM,KAGjB,MAAQza,EAAIuW,KACZ/L,GAAOxK,EAAIuW,IAGX,MAAQvW,EAAIT,OACZiL,GAAOwN,KAAK0C,UAAU1a,EAAIT,KAAM2F,KAAKgV,WAElC1P,CACX,GACA,CAAArL,IAAA,iBAAA6J,MAKA,SAAehJ,GACX,IAAM2a,EAAiBvB,GAAkBpZ,GACnCuZ,EAAOrU,KAAKmV,eAAeM,EAAe1X,QAC1CoW,EAAUsB,EAAetB,QAE/B,OADAA,EAAQuB,QAAQrB,GACTF,CACX,KAACY,CAAA,CAnEe,GAsEpB,SAASY,GAAS7R,GACd,MAAiD,oBAA1ClK,OAAOY,UAAUC,SAASC,KAAKoJ,EAC1C,CAMa8R,IAAAA,YAAOrS,GAAAZ,EAAAiT,EAAArS,GAAA,IAAAX,EAAAC,EAAA+S,GAMhB,SAAAA,EAAYC,GAAS,IAAA5S,EAEM,OAFNC,OAAA0S,IACjB3S,EAAAL,EAAAlI,KAAAsF,OACK6V,QAAUA,EAAQ5S,CAC3B,CA4IC,OA3IDE,EAAAyS,EAAA,CAAA,CAAA3b,IAAA,MAAA6J,MAKA,SAAIhJ,GACA,IAAIiD,EACJ,GAAmB,iBAARjD,EAAkB,CACzB,GAAIkF,KAAK8V,cACL,MAAM,IAAIzS,MAAM,mDAGpB,IAAM0S,GADNhY,EAASiC,KAAKgW,aAAalb,IACEV,OAASya,GAAWQ,aAC7CU,GAAiBhY,EAAO3D,OAASya,GAAWS,YAC5CvX,EAAO3D,KAAO2b,EAAgBlB,GAAWI,MAAQJ,GAAWK,IAE5DlV,KAAK8V,cAAgB,IAAIG,GAAoBlY,GAElB,IAAvBA,EAAOwW,aACPxQ,EAAAC,EAAA4R,EAAApb,WAAA,eAAAwF,MAAAtF,KAAAsF,KAAmB,UAAWjC,IAKlCgG,EAAAC,EAAA4R,EAAApb,WAAA,eAAAwF,MAAAtF,KAAAsF,KAAmB,UAAWjC,EAErC,KACI,KAAI4P,GAAS7S,KAAQA,EAAIgC,OAe1B,MAAM,IAAIuG,MAAM,iBAAmBvI,GAbnC,IAAKkF,KAAK8V,cACN,MAAM,IAAIzS,MAAM,qDAGhBtF,EAASiC,KAAK8V,cAAcI,eAAepb,MAGvCkF,KAAK8V,cAAgB,KACrB/R,EAAAC,EAAA4R,EAAApb,WAAA,eAAAwF,MAAAtF,KAAAsF,KAAmB,UAAWjC,GAM1C,CACJ,GACA,CAAA9D,IAAA,eAAA6J,MAMA,SAAawB,GACT,IAAIpJ,EAAI,EAEFmB,EAAI,CACNjD,KAAMgL,OAAOE,EAAI7I,OAAO,KAE5B,QAA2BkI,IAAvBkQ,GAAWxX,EAAEjD,MACb,MAAM,IAAIiJ,MAAM,uBAAyBhG,EAAEjD,MAG/C,GAAIiD,EAAEjD,OAASya,GAAWQ,cACtBhY,EAAEjD,OAASya,GAAWS,WAAY,CAElC,IADA,IAAMa,EAAQja,EAAI,EACS,MAApBoJ,EAAI7I,SAASP,IAAcA,GAAKoJ,EAAI1I,SAC3C,IAAMwZ,EAAM9Q,EAAI3I,UAAUwZ,EAAOja,GACjC,GAAIka,GAAOhR,OAAOgR,IAA0B,MAAlB9Q,EAAI7I,OAAOP,GACjC,MAAM,IAAImH,MAAM,uBAEpBhG,EAAEkX,YAAcnP,OAAOgR,EAC3B,CAEA,GAAI,MAAQ9Q,EAAI7I,OAAOP,EAAI,GAAI,CAE3B,IADA,IAAMia,EAAQja,EAAI,IACTA,GAAG,CAER,GAAI,MADMoJ,EAAI7I,OAAOP,GAEjB,MACJ,GAAIA,IAAMoJ,EAAI1I,OACV,KACR,CACAS,EAAEkY,IAAMjQ,EAAI3I,UAAUwZ,EAAOja,EACjC,MAEImB,EAAEkY,IAAM,IAGZ,IAAMc,EAAO/Q,EAAI7I,OAAOP,EAAI,GAC5B,GAAI,KAAOma,GAAQjR,OAAOiR,IAASA,EAAM,CAErC,IADA,IAAMF,EAAQja,EAAI,IACTA,GAAG,CACR,IAAMkX,EAAI9N,EAAI7I,OAAOP,GACrB,GAAI,MAAQkX,GAAKhO,OAAOgO,IAAMA,EAAG,GAC3BlX,EACF,KACJ,CACA,GAAIA,IAAMoJ,EAAI1I,OACV,KACR,CACAS,EAAEgU,GAAKjM,OAAOE,EAAI3I,UAAUwZ,EAAOja,EAAI,GAC3C,CAEA,GAAIoJ,EAAI7I,SAASP,GAAI,CACjB,IAAMoa,EAAUtW,KAAKuW,SAASjR,EAAIkR,OAAOta,IACzC,IAAI0Z,EAAQa,eAAepZ,EAAEjD,KAAMkc,GAI/B,MAAM,IAAIjT,MAAM,mBAHhBhG,EAAEhD,KAAOic,CAKjB,CACA,OAAOjZ,CACX,GAAC,CAAApD,IAAA,WAAA6J,MACD,SAASwB,GACL,IACI,OAAOwN,KAAK5D,MAAM5J,EAAKtF,KAAK6V,QAC/B,CACD,MAAOvP,GACH,OAAO,CACX,CACJ,GAAC,CAAArM,IAAA,UAAA6J,MAuBD,WACQ9D,KAAK8V,gBACL9V,KAAK8V,cAAcY,yBACnB1W,KAAK8V,cAAgB,KAE7B,IAAC,CAAA,CAAA7b,IAAA,iBAAA6J,MA3BD,SAAsB1J,EAAMkc,GACxB,OAAQlc,GACJ,KAAKya,GAAW8B,QACZ,OAAOhB,GAASW,GACpB,KAAKzB,GAAW+B,WACZ,YAAmBjS,IAAZ2R,EACX,KAAKzB,GAAWgC,cACZ,MAA0B,iBAAZP,GAAwBX,GAASW,GACnD,KAAKzB,GAAWI,MAChB,KAAKJ,GAAWQ,aACZ,OAAQtU,MAAMkT,QAAQqC,KACK,iBAAfA,EAAQ,IACW,iBAAfA,EAAQ,KAC6B,IAAzCxB,GAAgB7P,QAAQqR,EAAQ,KAChD,KAAKzB,GAAWK,IAChB,KAAKL,GAAWS,WACZ,OAAOvU,MAAMkT,QAAQqC,GAEjC,KAACV,CAAA,EArJwBlW,GAwKvBuW,GAAmB,WACrB,SAAAA,EAAYlY,GAAQmF,OAAA+S,GAChBjW,KAAKjC,OAASA,EACdiC,KAAKmU,QAAU,GACfnU,KAAK8W,UAAY/Y,CACrB,CAyBC,OAxBDoF,EAAA8S,EAAA,CAAA,CAAAhc,IAAA,iBAAA6J,MAQA,SAAeiT,GAEX,GADA/W,KAAKmU,QAAQjU,KAAK6W,GACd/W,KAAKmU,QAAQvX,SAAWoD,KAAK8W,UAAUvC,YAAa,CAEpD,IAAMxW,EAAS4W,GAAkB3U,KAAK8W,UAAW9W,KAAKmU,SAEtD,OADAnU,KAAK0W,yBACE3Y,CACX,CACA,OAAO,IACX,GACA,CAAA9D,IAAA,yBAAA6J,MAGA,WACI9D,KAAK8W,UAAY,KACjB9W,KAAKmU,QAAU,EACnB,KAAC8B,CAAA,CA9BoB,6CApQD,sDCnBjB,SAASrW,GAAG9E,EAAK2R,EAAI1M,GAExB,OADAjF,EAAI8E,GAAG6M,EAAI1M,GACJ,WACHjF,EAAIsF,IAAIqM,EAAI1M,GAEpB,CCEA,IAAM+U,GAAkBlb,OAAOod,OAAO,CAClCC,QAAS,EACTC,cAAe,EACfC,WAAY,EACZC,cAAe,EAEfC,YAAa,EACb9W,eAAgB,IA0BP4P,YAAM5M,GAAAZ,EAAAwN,EAAA5M,GAAA,IAAAX,EAAAC,EAAAsN,GAIf,SAAAA,EAAYmH,EAAI/B,EAAKpT,GAAM,IAAAc,EAoDP,OApDOC,OAAAiN,IACvBlN,EAAAL,EAAAlI,KAAAsF,OAeKuX,WAAY,EAKjBtU,EAAKuU,WAAY,EAIjBvU,EAAKwU,cAAgB,GAIrBxU,EAAKyU,WAAa,GAOlBzU,EAAK0U,OAAS,GAKd1U,EAAK2U,UAAY,EACjB3U,EAAK4U,IAAM,EACX5U,EAAK6U,KAAO,GACZ7U,EAAK8U,MAAQ,GACb9U,EAAKqU,GAAKA,EACVrU,EAAKsS,IAAMA,EACPpT,GAAQA,EAAK6V,OACb/U,EAAK+U,KAAO7V,EAAK6V,MAErB/U,EAAKgV,MAAQpP,EAAc,CAAE,EAAE1G,GAC3Bc,EAAKqU,GAAGY,cACRjV,EAAK4G,OAAO5G,CACpB,CAmuBC,OAluBDE,EAAAgN,EAAA,CAAA,CAAAlW,IAAA,eAAAsN,IAcA,WACI,OAAQvH,KAAKuX,SACjB,GACA,CAAAtd,IAAA,YAAA6J,MAKA,WACI,IAAI9D,KAAKmY,KAAT,CAEA,IAAMb,EAAKtX,KAAKsX,GAChBtX,KAAKmY,KAAO,CACRvY,GAAG0X,EAAI,OAAQtX,KAAKkM,OAAO5J,KAAKtC,OAChCJ,GAAG0X,EAAI,SAAUtX,KAAKoY,SAAS9V,KAAKtC,OACpCJ,GAAG0X,EAAI,QAAStX,KAAK0M,QAAQpK,KAAKtC,OAClCJ,GAAG0X,EAAI,QAAStX,KAAKsM,QAAQhK,KAAKtC,OANlC,CAQR,GACA,CAAA/F,IAAA,SAAAsN,IAiBA,WACI,QAASvH,KAAKmY,IAClB,GACA,CAAAle,IAAA,UAAA6J,MAUA,WACI,OAAI9D,KAAKuX,YAETvX,KAAKqY,YACArY,KAAKsX,GAAkB,eACxBtX,KAAKsX,GAAGzN,OACR,SAAW7J,KAAKsX,GAAGgB,aACnBtY,KAAKkM,UALElM,IAOf,GACA,CAAA/F,IAAA,OAAA6J,MAGA,WACI,OAAO9D,KAAKiX,SAChB,GACA,CAAAhd,IAAA,OAAA6J,MAeA,WAAc,IAAA,IAAAtC,EAAAlB,UAAA1D,OAANkE,EAAIC,IAAAA,MAAAS,GAAAE,EAAA,EAAAA,EAAAF,EAAAE,IAAJZ,EAAIY,GAAApB,UAAAoB,GAGR,OAFAZ,EAAK4U,QAAQ,WACb1V,KAAKa,KAAKR,MAAML,KAAMc,GACfd,IACX,GACA,CAAA/F,IAAA,OAAA6J,MAiBA,SAAK2I,GACD,GAAIqI,GAAgBlT,eAAe6K,GAC/B,MAAM,IAAIpJ,MAAM,IAAMoJ,EAAGhS,WAAa,8BACzC,IAAA8d,IAAAA,EAAAjY,UAAA1D,OAHOkE,MAAIC,MAAAwX,EAAAA,EAAAA,OAAAC,EAAA,EAAAA,EAAAD,EAAAC,IAAJ1X,EAAI0X,EAAAlY,GAAAA,UAAAkY,GAKZ,GADA1X,EAAK4U,QAAQjJ,GACTzM,KAAKiY,MAAMQ,UAAYzY,KAAK+X,MAAMW,YAAc1Y,KAAK+X,eAErD,OADA/X,KAAK2Y,YAAY7X,GACVd,KAEX,IAAMjC,EAAS,CACX3D,KAAMya,GAAWI,MACjB5a,KAAMyG,EAEV/C,QAAiB,IAGjB,GAFAA,EAAOwV,QAAQC,UAAmC,IAAxBxT,KAAK+X,MAAMvE,SAEjC,mBAAsB1S,EAAKA,EAAKlE,OAAS,GAAI,CAC7C,IAAMyU,EAAKrR,KAAK6X,MACVe,EAAM9X,EAAK+X,MACjB7Y,KAAK8Y,qBAAqBzH,EAAIuH,GAC9B7a,EAAOsT,GAAKA,CAChB,CACA,IAAM0H,EAAsB/Y,KAAKsX,GAAG0B,QAChChZ,KAAKsX,GAAG0B,OAAOjM,WACf/M,KAAKsX,GAAG0B,OAAOjM,UAAUrJ,SAY7B,OAXsB1D,KAAK+X,MAAc,YAAMgB,IAAwB/Y,KAAKuX,aAGnEvX,KAAKuX,WACVvX,KAAKiZ,wBAAwBlb,GAC7BiC,KAAKjC,OAAOA,IAGZiC,KAAK0X,WAAWxX,KAAKnC,IAEzBiC,KAAK+X,MAAQ,GACN/X,IACX,GACA,CAAA/F,IAAA,uBAAA6J,MAGA,SAAqBuN,EAAIuH,GAAK,IACtBlP,EADsBjG,EAAAzD,KAEpBmK,EAAwC,QAA7BT,EAAK1J,KAAK+X,MAAM5N,eAA4B,IAAPT,EAAgBA,EAAK1J,KAAKiY,MAAMiB,WACtF,QAAgBvU,IAAZwF,EAAJ,CAKA,IAAMgP,EAAQnZ,KAAKsX,GAAGjV,cAAa,kBACxBoB,EAAKqU,KAAKzG,GACjB,IAAK,IAAInV,EAAI,EAAGA,EAAIuH,EAAKiU,WAAW9a,OAAQV,IACpCuH,EAAKiU,WAAWxb,GAAGmV,KAAOA,GAC1B5N,EAAKiU,WAAW9W,OAAO1E,EAAG,GAGlC0c,EAAIle,KAAK+I,EAAM,IAAIJ,MAAM,2BAC5B,GAAE8G,GACHnK,KAAK8X,KAAKzG,GAAM,WAEZ5N,EAAK6T,GAAG/U,eAAe4W,GAAO,IAAA,IAAAC,EAAA9Y,UAAA1D,OAFdkE,EAAIC,IAAAA,MAAAqY,GAAAC,EAAA,EAAAA,EAAAD,EAAAC,IAAJvY,EAAIuY,GAAA/Y,UAAA+Y,GAGpBT,EAAIvY,MAAMoD,EAAI,CAAG,MAAI8C,OAAKzF,IAd9B,MAFId,KAAK8X,KAAKzG,GAAMuH,CAkBxB,GACA,CAAA3e,IAAA,cAAA6J,MAgBA,SAAY2I,GAAa,IAAA,IAAA7E,EAAA5H,KAAAsZ,EAAAhZ,UAAA1D,OAANkE,MAAIC,MAAAuY,EAAAA,EAAAA,OAAAC,EAAA,EAAAA,EAAAD,EAAAC,IAAJzY,EAAIyY,EAAAjZ,GAAAA,UAAAiZ,GAEnB,IAAMC,OAAiC7U,IAAvB3E,KAAK+X,MAAM5N,cAAmDxF,IAA1B3E,KAAKiY,MAAMiB,WAC/D,OAAO,IAAI9N,SAAQ,SAACC,EAASoO,GACzB3Y,EAAKZ,MAAK,SAACwZ,EAAMC,GACb,OAAIH,EACOE,EAAOD,EAAOC,GAAQrO,EAAQsO,GAG9BtO,EAAQqO,EAEvB,IACA9R,EAAK/G,KAAIR,MAATuH,EAAU6E,CAAAA,GAAElG,OAAKzF,GACrB,GACJ,GACA,CAAA7G,IAAA,cAAA6J,MAKA,SAAYhD,GAAM,IACV8X,EADU1Q,EAAAlI,KAEuB,mBAA1Bc,EAAKA,EAAKlE,OAAS,KAC1Bgc,EAAM9X,EAAK+X,OAEf,IAAM9a,EAAS,CACXsT,GAAIrR,KAAK4X,YACTgC,SAAU,EACVC,SAAS,EACT/Y,KAAAA,EACAiX,MAAOlP,EAAc,CAAE6P,WAAW,GAAQ1Y,KAAK+X,QAEnDjX,EAAKZ,MAAK,SAACgG,GACP,GAAInI,IAAWmK,EAAKyP,OAAO,GAA3B,CAKA,GADyB,OAARzR,EAETnI,EAAO6b,SAAW1R,EAAK+P,MAAMQ,UAC7BvQ,EAAKyP,OAAOpY,QACRqZ,GACAA,EAAI1S,SAMZ,GADAgC,EAAKyP,OAAOpY,QACRqZ,EAAK,CAAA,IAAAkB,IAAAA,EAAAxZ,UAAA1D,OAhBEmd,MAAYhZ,MAAA+Y,EAAAA,EAAAA,OAAAE,EAAA,EAAAA,EAAAF,EAAAE,IAAZD,EAAYC,EAAA1Z,GAAAA,UAAA0Z,GAiBnBpB,EAAGvY,WAAC,EAAA,CAAA,MAAIkG,OAAKwT,GACjB,CAGJ,OADAhc,EAAO8b,SAAU,EACV3R,EAAK+R,aAjBZ,CAkBJ,IACAja,KAAK2X,OAAOzX,KAAKnC,GACjBiC,KAAKia,aACT,GACA,CAAAhgB,IAAA,cAAA6J,MAMA,WAA2B,IAAfoW,EAAK5Z,UAAA1D,OAAA,QAAA+H,IAAArE,UAAA,IAAAA,UAAA,GACb,GAAKN,KAAKuX,WAAoC,IAAvBvX,KAAK2X,OAAO/a,OAAnC,CAGA,IAAMmB,EAASiC,KAAK2X,OAAO,GACvB5Z,EAAO8b,UAAYK,IAGvBnc,EAAO8b,SAAU,EACjB9b,EAAO6b,WACP5Z,KAAK+X,MAAQha,EAAOga,MACpB/X,KAAKa,KAAKR,MAAML,KAAMjC,EAAO+C,MAR7B,CASJ,GACA,CAAA7G,IAAA,SAAA6J,MAMA,SAAO/F,GACHA,EAAOwX,IAAMvV,KAAKuV,IAClBvV,KAAKsX,GAAG6C,QAAQpc,EACpB,GACA,CAAA9D,IAAA,SAAA6J,MAKA,WAAS,IAAAsE,EAAApI,KACmB,mBAAbA,KAAKgY,KACZhY,KAAKgY,MAAK,SAAC3d,GACP+N,EAAKgS,mBAAmB/f,EAC5B,IAGA2F,KAAKoa,mBAAmBpa,KAAKgY,KAErC,GACA,CAAA/d,IAAA,qBAAA6J,MAMA,SAAmBzJ,GACf2F,KAAKjC,OAAO,CACR3D,KAAMya,GAAW8B,QACjBtc,KAAM2F,KAAKqa,KACLxR,EAAc,CAAEyR,IAAKta,KAAKqa,KAAME,OAAQva,KAAKwa,aAAengB,GAC5DA,GAEd,GACA,CAAAJ,IAAA,UAAA6J,MAMA,SAAQoC,GACClG,KAAKuX,WACNvX,KAAKgB,aAAa,gBAAiBkF,EAE3C,GACA,CAAAjM,IAAA,UAAA6J,MAOA,SAAQhB,EAAQC,GACZ/C,KAAKuX,WAAY,SACVvX,KAAKqR,GACZrR,KAAKgB,aAAa,aAAc8B,EAAQC,EAC5C,GACA,CAAA9I,IAAA,WAAA6J,MAMA,SAAS/F,GAEL,GADsBA,EAAOwX,MAAQvV,KAAKuV,IAG1C,OAAQxX,EAAO3D,MACX,KAAKya,GAAW8B,QACR5Y,EAAO1D,MAAQ0D,EAAO1D,KAAKqO,IAC3B1I,KAAKya,UAAU1c,EAAO1D,KAAKqO,IAAK3K,EAAO1D,KAAKigB,KAG5Cta,KAAKgB,aAAa,gBAAiB,IAAIqC,MAAM,8LAEjD,MACJ,KAAKwR,GAAWI,MAChB,KAAKJ,GAAWQ,aACZrV,KAAK0a,QAAQ3c,GACb,MACJ,KAAK8W,GAAWK,IAChB,KAAKL,GAAWS,WACZtV,KAAK2a,MAAM5c,GACX,MACJ,KAAK8W,GAAW+B,WACZ5W,KAAK4a,eACL,MACJ,KAAK/F,GAAWgC,cACZ7W,KAAK6a,UACL,IAAM3U,EAAM,IAAI7C,MAAMtF,EAAO1D,KAAKygB,SAElC5U,EAAI7L,KAAO0D,EAAO1D,KAAKA,KACvB2F,KAAKgB,aAAa,gBAAiBkF,GAG/C,GACA,CAAAjM,IAAA,UAAA6J,MAMA,SAAQ/F,GACJ,IAAM+C,EAAO/C,EAAO1D,MAAQ,GACxB,MAAQ0D,EAAOsT,IACfvQ,EAAKZ,KAAKF,KAAK4Y,IAAI7a,EAAOsT,KAE1BrR,KAAKuX,UACLvX,KAAK+a,UAAUja,GAGfd,KAAKyX,cAAcvX,KAAKtG,OAAOod,OAAOlW,GAE9C,GAAC,CAAA7G,IAAA,YAAA6J,MACD,SAAUhD,GACN,GAAId,KAAKgb,eAAiBhb,KAAKgb,cAAcpe,OAAQ,CACjD,IACgCqe,EADaC,EAAAC,EAA3Bnb,KAAKgb,cAAcvb,SACL,IAAhC,IAAAyb,EAAAE,MAAAH,EAAAC,EAAApN,KAAAc,MAAkC,CAAfqM,EAAAnX,MACNzD,MAAML,KAAMc,EACzB,CAAC,CAAA,MAAAoF,GAAAgV,EAAA5U,EAAAJ,EAAA,CAAA,QAAAgV,EAAAG,GAAA,CACL,CACAtX,EAAAC,EAAAmM,EAAA3V,WAAW6F,OAAAA,MAAAA,MAAML,KAAMc,GACnBd,KAAKqa,MAAQvZ,EAAKlE,QAA2C,iBAA1BkE,EAAKA,EAAKlE,OAAS,KACtDoD,KAAKwa,YAAc1Z,EAAKA,EAAKlE,OAAS,GAE9C,GACA,CAAA3C,IAAA,MAAA6J,MAKA,SAAIuN,GACA,IAAMjQ,EAAOpB,KACTsb,GAAO,EACX,OAAO,WAEH,IAAIA,EAAJ,CAEAA,GAAO,EAAK,IAAA,IAAAC,EAAAjb,UAAA1D,OAJIkE,EAAIC,IAAAA,MAAAwa,GAAAC,EAAA,EAAAA,EAAAD,EAAAC,IAAJ1a,EAAI0a,GAAAlb,UAAAkb,GAKpBpa,EAAKrD,OAAO,CACR3D,KAAMya,GAAWK,IACjB7D,GAAIA,EACJhX,KAAMyG,GALN,EAQZ,GACA,CAAA7G,IAAA,QAAA6J,MAMA,SAAM/F,GACF,IAAM6a,EAAM5Y,KAAK8X,KAAK/Z,EAAOsT,IACzB,mBAAsBuH,IACtBA,EAAIvY,MAAML,KAAMjC,EAAO1D,aAChB2F,KAAK8X,KAAK/Z,EAAOsT,IAIhC,GACA,CAAApX,IAAA,YAAA6J,MAKA,SAAUuN,EAAIiJ,GACVta,KAAKqR,GAAKA,EACVrR,KAAKwX,UAAY8C,GAAOta,KAAKqa,OAASC,EACtCta,KAAKqa,KAAOC,EACZta,KAAKuX,WAAY,EACjBvX,KAAKyb,eACLzb,KAAKgB,aAAa,WAClBhB,KAAKia,aAAY,EACrB,GACA,CAAAhgB,IAAA,eAAA6J,MAKA,WAAe,IAAAkF,EAAAhJ,KACXA,KAAKyX,cAAczd,SAAQ,SAAC8G,GAAI,OAAKkI,EAAK+R,UAAUja,MACpDd,KAAKyX,cAAgB,GACrBzX,KAAK0X,WAAW1d,SAAQ,SAAC+D,GACrBiL,EAAKiQ,wBAAwBlb,GAC7BiL,EAAKjL,OAAOA,EAChB,IACAiC,KAAK0X,WAAa,EACtB,GACA,CAAAzd,IAAA,eAAA6J,MAKA,WACI9D,KAAK6a,UACL7a,KAAKsM,QAAQ,uBACjB,GACA,CAAArS,IAAA,UAAA6J,MAOA,WACQ9D,KAAKmY,OAELnY,KAAKmY,KAAKne,SAAQ,SAAC0hB,GAAU,OAAKA,OAClC1b,KAAKmY,UAAOxT,GAEhB3E,KAAKsX,GAAa,SAAEtX,KACxB,GACA,CAAA/F,IAAA,aAAA6J,MAgBA,WAUI,OATI9D,KAAKuX,WACLvX,KAAKjC,OAAO,CAAE3D,KAAMya,GAAW+B,aAGnC5W,KAAK6a,UACD7a,KAAKuX,WAELvX,KAAKsM,QAAQ,wBAEVtM,IACX,GACA,CAAA/F,IAAA,QAAA6J,MAKA,WACI,OAAO9D,KAAKmX,YAChB,GACA,CAAAld,IAAA,WAAA6J,MASA,SAAS0P,GAEL,OADAxT,KAAK+X,MAAMvE,SAAWA,EACfxT,IACX,GACA,CAAA/F,IAAA,WAAAsN,IASA,WAEI,OADAvH,KAAK+X,MAAc,UAAG,EACf/X,IACX,GACA,CAAA/F,IAAA,UAAA6J,MAaA,SAAQqG,GAEJ,OADAnK,KAAK+X,MAAM5N,QAAUA,EACdnK,IACX,GACA,CAAA/F,IAAA,QAAA6J,MAWA,SAAM6X,GAGF,OAFA3b,KAAKgb,cAAgBhb,KAAKgb,eAAiB,GAC3Chb,KAAKgb,cAAc9a,KAAKyb,GACjB3b,IACX,GACA,CAAA/F,IAAA,aAAA6J,MAWA,SAAW6X,GAGP,OAFA3b,KAAKgb,cAAgBhb,KAAKgb,eAAiB,GAC3Chb,KAAKgb,cAActF,QAAQiG,GACpB3b,IACX,GACA,CAAA/F,IAAA,SAAA6J,MAkBA,SAAO6X,GACH,IAAK3b,KAAKgb,cACN,OAAOhb,KAEX,GAAI2b,GAEA,IADA,IAAM1a,EAAYjB,KAAKgb,cACd9e,EAAI,EAAGA,EAAI+E,EAAUrE,OAAQV,IAClC,GAAIyf,IAAa1a,EAAU/E,GAEvB,OADA+E,EAAUL,OAAO1E,EAAG,GACb8D,UAKfA,KAAKgb,cAAgB,GAEzB,OAAOhb,IACX,GACA,CAAA/F,IAAA,eAAA6J,MAIA,WACI,OAAO9D,KAAKgb,eAAiB,EACjC,GACA,CAAA/gB,IAAA,gBAAA6J,MAaA,SAAc6X,GAGV,OAFA3b,KAAK4b,sBAAwB5b,KAAK4b,uBAAyB,GAC3D5b,KAAK4b,sBAAsB1b,KAAKyb,GACzB3b,IACX,GACA,CAAA/F,IAAA,qBAAA6J,MAaA,SAAmB6X,GAGf,OAFA3b,KAAK4b,sBAAwB5b,KAAK4b,uBAAyB,GAC3D5b,KAAK4b,sBAAsBlG,QAAQiG,GAC5B3b,IACX,GACA,CAAA/F,IAAA,iBAAA6J,MAkBA,SAAe6X,GACX,IAAK3b,KAAK4b,sBACN,OAAO5b,KAEX,GAAI2b,GAEA,IADA,IAAM1a,EAAYjB,KAAK4b,sBACd1f,EAAI,EAAGA,EAAI+E,EAAUrE,OAAQV,IAClC,GAAIyf,IAAa1a,EAAU/E,GAEvB,OADA+E,EAAUL,OAAO1E,EAAG,GACb8D,UAKfA,KAAK4b,sBAAwB,GAEjC,OAAO5b,IACX,GACA,CAAA/F,IAAA,uBAAA6J,MAIA,WACI,OAAO9D,KAAK4b,uBAAyB,EACzC,GACA,CAAA3hB,IAAA,0BAAA6J,MAOA,SAAwB/F,GACpB,GAAIiC,KAAK4b,uBAAyB5b,KAAK4b,sBAAsBhf,OAAQ,CACjE,IACgCif,EADqBC,EAAAX,EAAnCnb,KAAK4b,sBAAsBnc,SACb,IAAhC,IAAAqc,EAAAV,MAAAS,EAAAC,EAAAhO,KAAAc,MAAkC,CAAfiN,EAAA/X,MACNzD,MAAML,KAAMjC,EAAO1D,KAChC,CAAC,CAAA,MAAA6L,GAAA4V,EAAAxV,EAAAJ,EAAA,CAAA,QAAA4V,EAAAT,GAAA,CACL,CACJ,KAAClL,CAAA,EA5xBuBzQ,GC7BrB,SAASqc,GAAQ5Z,GACpBA,EAAOA,GAAQ,GACfnC,KAAKgc,GAAK7Z,EAAK8Z,KAAO,IACtBjc,KAAKkc,IAAM/Z,EAAK+Z,KAAO,IACvBlc,KAAKmc,OAASha,EAAKga,QAAU,EAC7Bnc,KAAKoc,OAASja,EAAKia,OAAS,GAAKja,EAAKia,QAAU,EAAIja,EAAKia,OAAS,EAClEpc,KAAKqc,SAAW,CACpB,CAOAN,GAAQvhB,UAAU8hB,SAAW,WACzB,IAAIN,EAAKhc,KAAKgc,GAAKpW,KAAKoI,IAAIhO,KAAKmc,OAAQnc,KAAKqc,YAC9C,GAAIrc,KAAKoc,OAAQ,CACb,IAAIG,EAAO3W,KAAK4W,SACZC,EAAY7W,KAAKC,MAAM0W,EAAOvc,KAAKoc,OAASJ,GAChDA,EAAoC,IAAN,EAAxBpW,KAAKC,MAAa,GAAP0W,IAAuBP,EAAKS,EAAYT,EAAKS,CAClE,CACA,OAAgC,EAAzB7W,KAAKqW,IAAID,EAAIhc,KAAKkc,IAC7B,EAMAH,GAAQvhB,UAAUkiB,MAAQ,WACtB1c,KAAKqc,SAAW,CACpB,EAMAN,GAAQvhB,UAAUmiB,OAAS,SAAUV,GACjCjc,KAAKgc,GAAKC,CACd,EAMAF,GAAQvhB,UAAUoiB,OAAS,SAAUV,GACjClc,KAAKkc,IAAMA,CACf,EAMAH,GAAQvhB,UAAUqiB,UAAY,SAAUT,GACpCpc,KAAKoc,OAASA,CAClB,EC3DaU,IAAAA,YAAOvZ,GAAAZ,EAAAma,EAAAvZ,GAAA,IAAAX,EAAAC,EAAAia,GAChB,SAAAA,EAAY/T,EAAK5G,GAAM,IAAAc,EACfyG,EADexG,OAAA4Z,IAEnB7Z,EAAAL,EAAAlI,KAAAsF,OACK+c,KAAO,GACZ9Z,EAAKkV,KAAO,GACRpP,GAAO,WAAQsH,EAAYtH,KAC3B5G,EAAO4G,EACPA,OAAMpE,IAEVxC,EAAOA,GAAQ,IACV2C,KAAO3C,EAAK2C,MAAQ,aACzB7B,EAAKd,KAAOA,EACZD,EAAqByB,EAAAV,GAAOd,GAC5Bc,EAAK+Z,cAAmC,IAAtB7a,EAAK6a,cACvB/Z,EAAKga,qBAAqB9a,EAAK8a,sBAAwBC,KACvDja,EAAKka,kBAAkBhb,EAAKgb,mBAAqB,KACjDla,EAAKma,qBAAqBjb,EAAKib,sBAAwB,KACvDna,EAAKoa,oBAAwD,QAAnC3T,EAAKvH,EAAKkb,2BAAwC,IAAP3T,EAAgBA,EAAK,IAC1FzG,EAAKqa,QAAU,IAAIvB,GAAQ,CACvBE,IAAKhZ,EAAKka,oBACVjB,IAAKjZ,EAAKma,uBACVhB,OAAQnZ,EAAKoa,wBAEjBpa,EAAKkH,QAAQ,MAAQhI,EAAKgI,QAAU,IAAQhI,EAAKgI,SACjDlH,EAAKqV,YAAc,SACnBrV,EAAK8F,IAAMA,EACX,IAAMwU,EAAUpb,EAAKqb,QAAUA,GAKf,OAJhBva,EAAKwa,QAAU,IAAIF,EAAQxI,QAC3B9R,EAAKya,QAAU,IAAIH,EAAQ3H,QAC3B3S,EAAKiV,cAAoC,IAArB/V,EAAKwb,YACrB1a,EAAKiV,cACLjV,EAAK4G,OAAO5G,CACpB,CA6TC,OA7TAE,EAAA2Z,EAAA,CAAA,CAAA7iB,IAAA,eAAA6J,MACD,SAAa8Z,GACT,OAAKtd,UAAU1D,QAEfoD,KAAK6d,gBAAkBD,EAChB5d,MAFIA,KAAK6d,aAGpB,GAAC,CAAA5jB,IAAA,uBAAA6J,MACD,SAAqB8Z,GACjB,YAAUjZ,IAANiZ,EACO5d,KAAK8d,uBAChB9d,KAAK8d,sBAAwBF,EACtB5d,KACX,GAAC,CAAA/F,IAAA,oBAAA6J,MACD,SAAkB8Z,GACd,IAAIlU,EACJ,YAAU/E,IAANiZ,EACO5d,KAAK+d,oBAChB/d,KAAK+d,mBAAqBH,EACF,QAAvBlU,EAAK1J,KAAKsd,eAA4B,IAAP5T,GAAyBA,EAAGiT,OAAOiB,GAC5D5d,KACX,GAAC,CAAA/F,IAAA,sBAAA6J,MACD,SAAoB8Z,GAChB,IAAIlU,EACJ,YAAU/E,IAANiZ,EACO5d,KAAKge,sBAChBhe,KAAKge,qBAAuBJ,EACJ,QAAvBlU,EAAK1J,KAAKsd,eAA4B,IAAP5T,GAAyBA,EAAGmT,UAAUe,GAC/D5d,KACX,GAAC,CAAA/F,IAAA,uBAAA6J,MACD,SAAqB8Z,GACjB,IAAIlU,EACJ,YAAU/E,IAANiZ,EACO5d,KAAKie,uBAChBje,KAAKie,sBAAwBL,EACL,QAAvBlU,EAAK1J,KAAKsd,eAA4B,IAAP5T,GAAyBA,EAAGkT,OAAOgB,GAC5D5d,KACX,GAAC,CAAA/F,IAAA,UAAA6J,MACD,SAAQ8Z,GACJ,OAAKtd,UAAU1D,QAEfoD,KAAKke,SAAWN,EACT5d,MAFIA,KAAKke,QAGpB,GACA,CAAAjkB,IAAA,uBAAA6J,MAMA,YAES9D,KAAKme,eACNne,KAAK6d,eACqB,IAA1B7d,KAAKsd,QAAQjB,UAEbrc,KAAKoe,WAEb,GACA,CAAAnkB,IAAA,OAAA6J,MAOA,SAAK/D,GAAI,IAAA0D,EAAAzD,KACL,IAAKA,KAAKsY,YAAYrT,QAAQ,QAC1B,OAAOjF,KACXA,KAAKgZ,OAAS,IAAIqF,GAAOre,KAAK+I,IAAK/I,KAAKmC,MACxC,IAAM0B,EAAS7D,KAAKgZ,OACd5X,EAAOpB,KACbA,KAAKsY,YAAc,UACnBtY,KAAKse,eAAgB,EAErB,IAAMC,EAAiB3e,GAAGiE,EAAQ,QAAQ,WACtCzC,EAAK8K,SACLnM,GAAMA,GACV,IACMsJ,EAAU,SAACnD,GACbzC,EAAKoH,UACLpH,EAAK6U,YAAc,SACnB7U,EAAKzC,aAAa,QAASkF,GACvBnG,EACAA,EAAGmG,GAIHzC,EAAK+a,wBAIPC,EAAW7e,GAAGiE,EAAQ,QAASwF,GACrC,IAAI,IAAUrJ,KAAKke,SAAU,CACzB,IAAM/T,EAAUnK,KAAKke,SAEf/E,EAAQnZ,KAAKqC,cAAa,WAC5Bkc,IACAlV,EAAQ,IAAIhG,MAAM,YAClBQ,EAAOsE,OACV,GAAEgC,GACCnK,KAAKmC,KAAKgK,WACVgN,EAAM9M,QAEVrM,KAAKmY,KAAKjY,MAAK,WACXuD,EAAKlB,eAAe4W,EACxB,GACJ,CAGA,OAFAnZ,KAAKmY,KAAKjY,KAAKqe,GACfve,KAAKmY,KAAKjY,KAAKue,GACRze,IACX,GACA,CAAA/F,IAAA,UAAA6J,MAMA,SAAQ/D,GACJ,OAAOC,KAAK6J,KAAK9J,EACrB,GACA,CAAA9F,IAAA,SAAA6J,MAKA,WAEI9D,KAAK6K,UAEL7K,KAAKsY,YAAc,OACnBtY,KAAKgB,aAAa,QAElB,IAAM6C,EAAS7D,KAAKgZ,OACpBhZ,KAAKmY,KAAKjY,KAAKN,GAAGiE,EAAQ,OAAQ7D,KAAK0e,OAAOpc,KAAKtC,OAAQJ,GAAGiE,EAAQ,OAAQ7D,KAAK2e,OAAOrc,KAAKtC,OAAQJ,GAAGiE,EAAQ,QAAS7D,KAAK0M,QAAQpK,KAAKtC,OAAQJ,GAAGiE,EAAQ,QAAS7D,KAAKsM,QAAQhK,KAAKtC,OAAQJ,GAAGI,KAAK0d,QAAS,UAAW1d,KAAK4e,UAAUtc,KAAKtC,OACvP,GACA,CAAA/F,IAAA,SAAA6J,MAKA,WACI9D,KAAKgB,aAAa,OACtB,GACA,CAAA/G,IAAA,SAAA6J,MAKA,SAAOzJ,GACH,IACI2F,KAAK0d,QAAQmB,IAAIxkB,EACpB,CACD,MAAOiM,GACHtG,KAAKsM,QAAQ,cAAehG,EAChC,CACJ,GACA,CAAArM,IAAA,YAAA6J,MAKA,SAAU/F,GAAQ,IAAA6J,EAAA5H,KAEdmL,IAAS,WACLvD,EAAK5G,aAAa,SAAUjD,EAChC,GAAGiC,KAAKqC,aACZ,GACA,CAAApI,IAAA,UAAA6J,MAKA,SAAQoC,GACJlG,KAAKgB,aAAa,QAASkF,EAC/B,GACA,CAAAjM,IAAA,SAAA6J,MAMA,SAAOyR,EAAKpT,GACR,IAAI0B,EAAS7D,KAAK+c,KAAKxH,GAQvB,OAPK1R,EAII7D,KAAKkY,eAAiBrU,EAAOib,QAClCjb,EAAOoT,WAJPpT,EAAS,IAAIsM,GAAOnQ,KAAMuV,EAAKpT,GAC/BnC,KAAK+c,KAAKxH,GAAO1R,GAKdA,CACX,GACA,CAAA5J,IAAA,WAAA6J,MAMA,SAASD,GAEL,IADA,IACAkb,EAAA,EAAAC,EADaplB,OAAOG,KAAKiG,KAAK+c,MACRgC,EAAAC,EAAApiB,OAAAmiB,IAAE,CAAnB,IAAMxJ,EAAGyJ,EAAAD,GAEV,GADe/e,KAAK+c,KAAKxH,GACduJ,OACP,MAER,CACA9e,KAAKif,QACT,GACA,CAAAhlB,IAAA,UAAA6J,MAMA,SAAQ/F,GAEJ,IADA,IAAM+J,EAAiB9H,KAAKyd,QAAQpf,OAAON,GAClC7B,EAAI,EAAGA,EAAI4L,EAAelL,OAAQV,IACvC8D,KAAKgZ,OAAO1U,MAAMwD,EAAe5L,GAAI6B,EAAOwV,QAEpD,GACA,CAAAtZ,IAAA,UAAA6J,MAKA,WACI9D,KAAKmY,KAAKne,SAAQ,SAAC0hB,GAAU,OAAKA,OAClC1b,KAAKmY,KAAKvb,OAAS,EACnBoD,KAAK0d,QAAQ7C,SACjB,GACA,CAAA5gB,IAAA,SAAA6J,MAKA,WACI9D,KAAKse,eAAgB,EACrBte,KAAKme,eAAgB,EACrBne,KAAKsM,QAAQ,gBACTtM,KAAKgZ,QACLhZ,KAAKgZ,OAAO7Q,OACpB,GACA,CAAAlO,IAAA,aAAA6J,MAKA,WACI,OAAO9D,KAAKif,QAChB,GACA,CAAAhlB,IAAA,UAAA6J,MAKA,SAAQhB,EAAQC,GACZ/C,KAAK6K,UACL7K,KAAKsd,QAAQZ,QACb1c,KAAKsY,YAAc,SACnBtY,KAAKgB,aAAa,QAAS8B,EAAQC,GAC/B/C,KAAK6d,gBAAkB7d,KAAKse,eAC5Bte,KAAKoe,WAEb,GACA,CAAAnkB,IAAA,YAAA6J,MAKA,WAAY,IAAAoE,EAAAlI,KACR,GAAIA,KAAKme,eAAiBne,KAAKse,cAC3B,OAAOte,KACX,IAAMoB,EAAOpB,KACb,GAAIA,KAAKsd,QAAQjB,UAAYrc,KAAK8d,sBAC9B9d,KAAKsd,QAAQZ,QACb1c,KAAKgB,aAAa,oBAClBhB,KAAKme,eAAgB,MAEpB,CACD,IAAMe,EAAQlf,KAAKsd,QAAQhB,WAC3Btc,KAAKme,eAAgB,EACrB,IAAMhF,EAAQnZ,KAAKqC,cAAa,WACxBjB,EAAKkd,gBAETpW,EAAKlH,aAAa,oBAAqBI,EAAKkc,QAAQjB,UAEhDjb,EAAKkd,eAETld,EAAKyI,MAAK,SAAC3D,GACHA,GACA9E,EAAK+c,eAAgB,EACrB/c,EAAKgd,YACLlW,EAAKlH,aAAa,kBAAmBkF,IAGrC9E,EAAK+d,aAEb,IACH,GAAED,GACClf,KAAKmC,KAAKgK,WACVgN,EAAM9M,QAEVrM,KAAKmY,KAAKjY,MAAK,WACXgI,EAAK3F,eAAe4W,EACxB,GACJ,CACJ,GACA,CAAAlf,IAAA,cAAA6J,MAKA,WACI,IAAMsb,EAAUpf,KAAKsd,QAAQjB,SAC7Brc,KAAKme,eAAgB,EACrBne,KAAKsd,QAAQZ,QACb1c,KAAKgB,aAAa,YAAaoe,EACnC,KAACtC,CAAA,EA9VwBpd,GCAvB2f,GAAQ,CAAA,EACd,SAASpjB,GAAO8M,EAAK5G,GACE,WAAfkO,EAAOtH,KACP5G,EAAO4G,EACPA,OAAMpE,GAGV,IASI2S,EATEgI,ECHH,SAAavW,GAAqB,IAAhBjE,EAAIxE,UAAA1D,OAAA,QAAA+H,IAAArE,UAAA,GAAAA,UAAA,GAAG,GAAIif,EAAGjf,UAAA1D,OAAA0D,EAAAA,kBAAAqE,EAC/B7J,EAAMiO,EAEVwW,EAAMA,GAA4B,oBAAbxY,UAA4BA,SAC7C,MAAQgC,IACRA,EAAMwW,EAAItY,SAAW,KAAOsY,EAAI9P,MAEjB,iBAAR1G,IACH,MAAQA,EAAItM,OAAO,KAEfsM,EADA,MAAQA,EAAItM,OAAO,GACb8iB,EAAItY,SAAW8B,EAGfwW,EAAI9P,KAAO1G,GAGpB,sBAAsByW,KAAKzW,KAExBA,OADA,IAAuBwW,EACjBA,EAAItY,SAAW,KAAO8B,EAGtB,WAAaA,GAI3BjO,EAAMoU,GAAMnG,IAGXjO,EAAIoK,OACD,cAAcsa,KAAK1kB,EAAImM,UACvBnM,EAAIoK,KAAO,KAEN,eAAesa,KAAK1kB,EAAImM,YAC7BnM,EAAIoK,KAAO,QAGnBpK,EAAIgK,KAAOhK,EAAIgK,MAAQ,IACvB,IACM2K,GADkC,IAA3B3U,EAAI2U,KAAKxK,QAAQ,KACV,IAAMnK,EAAI2U,KAAO,IAAM3U,EAAI2U,KAS/C,OAPA3U,EAAIuW,GAAKvW,EAAImM,SAAW,MAAQwI,EAAO,IAAM3U,EAAIoK,KAAOJ,EAExDhK,EAAI2kB,KACA3kB,EAAImM,SACA,MACAwI,GACC8P,GAAOA,EAAIra,OAASpK,EAAIoK,KAAO,GAAK,IAAMpK,EAAIoK,MAChDpK,CACX,CD7CmB4kB,CAAI3W,GADnB5G,EAAOA,GAAQ,IACc2C,MAAQ,cAC/B0K,EAAS8P,EAAO9P,OAChB6B,EAAKiO,EAAOjO,GACZvM,EAAOwa,EAAOxa,KACd6a,EAAgBN,GAAMhO,IAAOvM,KAAQua,GAAMhO,GAAU,KAkB3D,OAjBsBlP,EAAKyd,UACvBzd,EAAK,0BACL,IAAUA,EAAK0d,WACfF,EAGArI,EAAK,IAAIwF,GAAQtN,EAAQrN,IAGpBkd,GAAMhO,KACPgO,GAAMhO,GAAM,IAAIyL,GAAQtN,EAAQrN,IAEpCmV,EAAK+H,GAAMhO,IAEXiO,EAAO1b,QAAUzB,EAAKyB,QACtBzB,EAAKyB,MAAQ0b,EAAOvP,UAEjBuH,EAAGzT,OAAOyb,EAAOxa,KAAM3C,EAClC,QAGA0G,EAAc5M,GAAQ,CAClB6gB,QAAAA,GACA3M,OAAAA,GACAmH,GAAIrb,GACJgb,QAAShb"} \ No newline at end of file diff --git a/flask__websocket/ajax_vs_websocket/ajax/main.py b/flask__websocket/ajax_vs_websocket/ajax/main.py index dd2539aba..d50cd8d0d 100644 --- a/flask__websocket/ajax_vs_websocket/ajax/main.py +++ b/flask__websocket/ajax_vs_websocket/ajax/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from timeit import default_timer @@ -9,50 +9,48 @@ from flask import Flask, render_template, request, jsonify -app = Flask(__name__, static_folder='../_static') -app.config['SECRET_KEY'] = 'secret!' + +app = Flask(__name__, static_folder="../_static") +app.config["SECRET_KEY"] = "secret!" lock = Lock() DATA = { - 'count': 0, - 'start_time': 0.0 + "count": 0, + "start_time": 0.0, } -@app.route('/') +@app.route("/") def index(): with lock: - DATA['count'] = 0 + DATA["count"] = 0 - return render_template('index.html') + return render_template("index.html") -@app.route("/post_method", methods=['POST']) +@app.route("/post_method", methods=["POST"]) def post_method(): data = request.get_json() # print(data) with lock: - if DATA['count'] == 0: - DATA['start_time'] = default_timer() + if DATA["count"] == 0: + DATA["start_time"] = default_timer() - DATA['count'] += 1 + DATA["count"] += 1 return jsonify({ - 'number': data['number'], - 'count': DATA['count'], - 'elapsed': round(default_timer() - DATA['start_time'], 3) + "number": data["number"], + "count": DATA["count"], + "elapsed": round(default_timer() - DATA["start_time"], 3), }) -if __name__ == '__main__': +if __name__ == "__main__": # Localhost - HOST = '127.0.0.1' + HOST = "127.0.0.1" PORT = 12000 - print(f'http://{HOST}:{PORT}') + print(f"http://{HOST}:{PORT}") app.debug = True - app.run( - host=HOST, - port=PORT - ) + app.run(host=HOST, port=PORT) diff --git a/flask__websocket/ajax_vs_websocket/websocket/main.py b/flask__websocket/ajax_vs_websocket/websocket/main.py index 4477254b2..f39e0d5b1 100644 --- a/flask__websocket/ajax_vs_websocket/websocket/main.py +++ b/flask__websocket/ajax_vs_websocket/websocket/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from timeit import default_timer @@ -11,6 +11,7 @@ from flask_socketio import SocketIO, emit from engineio.payload import Payload + Payload.max_decode_packets = 1000 @@ -19,54 +20,55 @@ # the best option based on installed packages. async_mode = None -app = Flask(__name__, static_folder='../_static') -app.config['SECRET_KEY'] = 'secret!' +app = Flask(__name__, static_folder="../_static") +app.config["SECRET_KEY"] = "secret!" socketio = SocketIO(app, async_mode=async_mode) lock = Lock() DATA = { - 'count': 0, - 'start_time': 0.0 + "count": 0, + "start_time": 0.0, } -@app.route('/') +@app.route("/") def index(): with lock: - DATA['count'] = 0 + DATA["count"] = 0 - return render_template('index.html', async_mode=socketio.async_mode) + return render_template("index.html", async_mode=socketio.async_mode) -@socketio.on('post_method', namespace='/test') -def post_method(message): +@socketio.on("post_method", namespace="/test") +def post_method(message) -> None: # print(message) with lock: - if DATA['count'] == 0: - DATA['start_time'] = default_timer() + if DATA["count"] == 0: + DATA["start_time"] = default_timer() - DATA['count'] += 1 + DATA["count"] += 1 emit( - 'my_response', + "my_response", { - 'number': message['number'], - 'count': DATA['count'], - 'elapsed': round(default_timer() - DATA['start_time'], 3) - } + "number": message["number"], + "count": DATA["count"], + "elapsed": round(default_timer() - DATA["start_time"], 3), + }, ) -if __name__ == '__main__': +if __name__ == "__main__": # Localhost - HOST = '127.0.0.1' + HOST = "127.0.0.1" PORT = 12001 - print(f'http://{HOST}:{PORT}') + print(f"http://{HOST}:{PORT}") app.debug = True socketio.run( app, host=HOST, port=PORT, + allow_unsafe_werkzeug=True, ) diff --git a/flask__websocket/ajax_vs_websocket/websocket/templates/index.html b/flask__websocket/ajax_vs_websocket/websocket/templates/index.html index eb2e246e0..dfa8e0d06 100644 --- a/flask__websocket/ajax_vs_websocket/websocket/templates/index.html +++ b/flask__websocket/ajax_vs_websocket/websocket/templates/index.html @@ -3,7 +3,7 @@ Ajax vs Websocket. Test Websocket - +

Ajax vs Websocket. Test Websocket

diff --git a/flask__websocket/commands__websocket__flask-socketio/main.py b/flask__websocket/commands__websocket__flask-socketio/main.py index 42dbbf1a3..4324325e4 100644 --- a/flask__websocket/commands__websocket__flask-socketio/main.py +++ b/flask__websocket/commands__websocket__flask-socketio/main.py @@ -1,11 +1,11 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -import datetime as DT import uuid +from datetime import datetime from flask import Flask, render_template, session, request from flask_socketio import SocketIO, emit @@ -17,25 +17,25 @@ async_mode = None app = Flask(__name__) -app.config['SECRET_KEY'] = 'secret!' +app.config["SECRET_KEY"] = "secret!" socketio = SocketIO(app, async_mode=async_mode) -@app.route('/') +@app.route("/") def index(): - return render_template('index.html', async_mode=socketio.async_mode) + return render_template("index.html", async_mode=socketio.async_mode) -@socketio.on('my_event', namespace='/test') -def test_message(message): - session['receive_count'] = session.get('receive_count', 0) + 1 +@socketio.on("my_event", namespace="/test") +def test_message(message) -> None: + session["receive_count"] = session.get("receive_count", 0) + 1 print(message) - data = message['data'] + data = message["data"] if data == "CURRENT_DATE_TIME": - response = DT.datetime.now().strftime('%Y-%m-%d_%H%M%S') + response = datetime.now().strftime("%Y-%m-%d_%H%M%S") elif data == "UUID": response = str(uuid.uuid4()) @@ -48,30 +48,30 @@ def test_message(message): response = data emit( - 'my_response', - {'data': response, 'count': session['receive_count']} + "my_response", + {"data": response, "count": session["receive_count"]}, ) -@socketio.on('my_ping', namespace='/test') -def ping_pong(): - emit('my_pong') +@socketio.on("my_ping", namespace="/test") +def ping_pong() -> None: + emit("my_pong") -@socketio.on('connect', namespace='/test') -def test_connect(): - emit('my_response', {'data': 'Connected'}) +@socketio.on("connect", namespace="/test") +def test_connect() -> None: + emit("my_response", {"data": "Connected"}) -@socketio.on('disconnect', namespace='/test') -def test_disconnect(): - print('Client disconnected', request.sid) +@socketio.on("disconnect", namespace="/test") +def test_disconnect() -> None: + print("Client disconnected", request.sid) -if __name__ == '__main__': - HOST = '127.0.0.1' +if __name__ == "__main__": + HOST = "127.0.0.1" PORT = 12000 - print(f'http://{HOST}:{PORT}') + print(f"http://{HOST}:{PORT}") socketio.run( app, diff --git a/flask__websocket/sending_all_from_while_in_thread/main.py b/flask__websocket/sending_all_from_while_in_thread/main.py new file mode 100644 index 000000000..fce7334c0 --- /dev/null +++ b/flask__websocket/sending_all_from_while_in_thread/main.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import time + +from datetime import datetime +from threading import Thread + +from flask import Flask, render_template, session, request + +# pip install flask-socketio==5.3.6 +from flask_socketio import SocketIO, emit + + +# Set this variable to "threading", "eventlet" or "gevent" to test the +# different async modes, or leave it set to None for the application to choose +# the best option based on installed packages. +async_mode = None + +app = Flask(__name__) +app.config["SECRET_KEY"] = "secret!" +socketio = SocketIO(app, async_mode=async_mode) + + +def send_all_cycled() -> None: + with app.app_context(): + i = 0 + while True: + i += 1 + emit( + "my_response_all", + {"data": f"#{i}. {datetime.now().isoformat()}"}, + broadcast=True, # Send all clients + namespace="/", + ) + time.sleep(1) + + +thread = Thread(target=send_all_cycled) +thread.daemon = True +thread.start() + + +@app.route("/") +def index(): + return render_template("index.html") + + +@socketio.on("my_event") +def test_message(message) -> None: + session["receive_count"] = session.get("receive_count", 0) + 1 + print(message) + + response = message["data"] + emit( + "my_response", + {"data": response, "count": session["receive_count"], "sid": request.sid}, + broadcast=True, # Send all clients + ) + + +@socketio.on("my_ping") +def ping_pong() -> None: + emit("my_pong") + + +@socketio.on("connect") +def test_connect() -> None: + print("Client connected", request.sid) + emit("my_response", {"data": f"Connected!", "count": 0, "sid": request.sid}) + + +@socketio.on("disconnect") +def test_disconnect() -> None: + print("Client disconnected", request.sid) + + +if __name__ == "__main__": + # HOST = '127.0.0.1' + # HOST = "0.0.0.0" + PORT = 12000 + # print(f"http://{HOST}:{PORT}") + + socketio.run( + app, + # host=HOST, + port=PORT, + allow_unsafe_werkzeug=True, + ) diff --git a/flask__websocket/sending_all_from_while_in_thread/static/index.js b/flask__websocket/sending_all_from_while_in_thread/static/index.js new file mode 100644 index 000000000..d94c761e9 --- /dev/null +++ b/flask__websocket/sending_all_from_while_in_thread/static/index.js @@ -0,0 +1,62 @@ +$(document).ready(function() { + // Connect to the Socket.IO server. + var socket = io(); + + // Event handler for new connections. + // The callback function is invoked when a connection with the + // server is established. + socket.on('connect', function() { + socket.emit('my_event', {data: "I'm connected!"}); + }); + + // Event handler for server sent data. + // The callback function is invoked whenever the server emits data + // to the client. The data is then displayed in the "Received" + // section of the page. + socket.on('my_response', function(msg, cb) { + console.log(msg); + $('#log').prepend( + '
' + $('
').text(`Received #${msg.count}: ${msg.data} from #${msg.sid}`).html() + ); + if (cb) + cb(); + }); + + socket.on('my_response_all', function(msg, cb) { + console.log(msg); + $('#server_data').text(msg.data); + if (cb) + cb(); + }); + + // Interval function that tests message latency by sending a "ping" + // message. The server then responds with a "pong" message and the + // round trip time is measured. + var ping_pong_times = []; + var start_time; + window.setInterval(function() { + start_time = (new Date).getTime(); + socket.emit('my_ping'); + }, 1000); + + // Handler for the "pong" message. When the pong is received, the + // time from the ping is stored, and the average of the last 30 + // samples is average and displayed. + socket.on('my_pong', function() { + var latency = (new Date).getTime() - start_time; + ping_pong_times.push(latency); + ping_pong_times = ping_pong_times.slice(-30); // keep last 30 samples + var sum = 0; + for (var i = 0; i < ping_pong_times.length; i++) + sum += ping_pong_times[i]; + $('#ping-pong').text(Math.round(10 * sum / ping_pong_times.length) / 10); + }); + + // Handlers for the different forms in the page. + // These accept data from the user and send it to the server in a + // variety of ways + $('form#emit').submit(function(event) { + socket.emit('my_event', {data: $('#emit_data').val()}); + return false; + }); +}); \ No newline at end of file diff --git a/flask__websocket/sending_all_from_while_in_thread/static/jquery.js b/flask__websocket/sending_all_from_while_in_thread/static/jquery.js new file mode 100644 index 000000000..d4b67f7e6 --- /dev/null +++ b/flask__websocket/sending_all_from_while_in_thread/static/jquery.js @@ -0,0 +1,10308 @@ +/*! + * jQuery JavaScript Library v1.11.1 + * http://jquery.com/ + * + * Includes Sizzle.js + * http://sizzlejs.com/ + * + * Copyright 2005, 2014 jQuery Foundation, Inc. and other contributors + * Released under the MIT license + * http://jquery.org/license + * + * Date: 2014-05-01T17:42Z + */ + +(function( global, factory ) { + + if ( typeof module === "object" && typeof module.exports === "object" ) { + // For CommonJS and CommonJS-like environments where a proper window is present, + // execute the factory and get jQuery + // For environments that do not inherently posses a window with a document + // (such as Node.js), expose a jQuery-making factory as module.exports + // This accentuates the need for the creation of a real window + // e.g. var jQuery = require("jquery")(window); + // See ticket #14549 for more info + module.exports = global.document ? + factory( global, true ) : + function( w ) { + if ( !w.document ) { + throw new Error( "jQuery requires a window with a document" ); + } + return factory( w ); + }; + } else { + factory( global ); + } + +// Pass this if window is not defined yet +}(typeof window !== "undefined" ? window : this, function( window, noGlobal ) { + +// Can't do this because several apps including ASP.NET trace +// the stack via arguments.caller.callee and Firefox dies if +// you try to trace through "use strict" call chains. (#13335) +// Support: Firefox 18+ +// + +var deletedIds = []; + +var slice = deletedIds.slice; + +var concat = deletedIds.concat; + +var push = deletedIds.push; + +var indexOf = deletedIds.indexOf; + +var class2type = {}; + +var toString = class2type.toString; + +var hasOwn = class2type.hasOwnProperty; + +var support = {}; + + + +var + version = "1.11.1", + + // Define a local copy of jQuery + jQuery = function( selector, context ) { + // The jQuery object is actually just the init constructor 'enhanced' + // Need init if jQuery is called (just allow error to be thrown if not included) + return new jQuery.fn.init( selector, context ); + }, + + // Support: Android<4.1, IE<9 + // Make sure we trim BOM and NBSP + rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, + + // Matches dashed string for camelizing + rmsPrefix = /^-ms-/, + rdashAlpha = /-([\da-z])/gi, + + // Used by jQuery.camelCase as callback to replace() + fcamelCase = function( all, letter ) { + return letter.toUpperCase(); + }; + +jQuery.fn = jQuery.prototype = { + // The current version of jQuery being used + jquery: version, + + constructor: jQuery, + + // Start with an empty selector + selector: "", + + // The default length of a jQuery object is 0 + length: 0, + + toArray: function() { + return slice.call( this ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + return num != null ? + + // Return just the one element from the set + ( num < 0 ? this[ num + this.length ] : this[ num ] ) : + + // Return all the elements in a clean array + slice.call( this ); + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems ) { + + // Build a new jQuery matched element set + var ret = jQuery.merge( this.constructor(), elems ); + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + ret.context = this.context; + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + // (You can seed the arguments with an array of args, but this is + // only used internally.) + each: function( callback, args ) { + return jQuery.each( this, callback, args ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map(this, function( elem, i ) { + return callback.call( elem, i, elem ); + })); + }, + + slice: function() { + return this.pushStack( slice.apply( this, arguments ) ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + eq: function( i ) { + var len = this.length, + j = +i + ( i < 0 ? len : 0 ); + return this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] ); + }, + + end: function() { + return this.prevObject || this.constructor(null); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: push, + sort: deletedIds.sort, + splice: deletedIds.splice +}; + +jQuery.extend = jQuery.fn.extend = function() { + var src, copyIsArray, copy, name, options, clone, + target = arguments[0] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + + // skip the boolean and the target + target = arguments[ i ] || {}; + i++; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !jQuery.isFunction(target) ) { + target = {}; + } + + // extend jQuery itself if only one argument is passed + if ( i === length ) { + target = this; + i--; + } + + for ( ; i < length; i++ ) { + // Only deal with non-null/undefined values + if ( (options = arguments[ i ]) != null ) { + // Extend the base object + for ( name in options ) { + src = target[ name ]; + copy = options[ name ]; + + // Prevent never-ending loop + if ( target === copy ) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) { + if ( copyIsArray ) { + copyIsArray = false; + clone = src && jQuery.isArray(src) ? src : []; + + } else { + clone = src && jQuery.isPlainObject(src) ? src : {}; + } + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend({ + // Unique for each copy of jQuery on the page + expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), + + // Assume jQuery is ready without the ready module + isReady: true, + + error: function( msg ) { + throw new Error( msg ); + }, + + noop: function() {}, + + // See test/unit/core.js for details concerning isFunction. + // Since version 1.3, DOM methods and functions like alert + // aren't supported. They return false on IE (#2968). + isFunction: function( obj ) { + return jQuery.type(obj) === "function"; + }, + + isArray: Array.isArray || function( obj ) { + return jQuery.type(obj) === "array"; + }, + + isWindow: function( obj ) { + /* jshint eqeqeq: false */ + return obj != null && obj == obj.window; + }, + + isNumeric: function( obj ) { + // parseFloat NaNs numeric-cast false positives (null|true|false|"") + // ...but misinterprets leading-number strings, particularly hex literals ("0x...") + // subtraction forces infinities to NaN + return !jQuery.isArray( obj ) && obj - parseFloat( obj ) >= 0; + }, + + isEmptyObject: function( obj ) { + var name; + for ( name in obj ) { + return false; + } + return true; + }, + + isPlainObject: function( obj ) { + var key; + + // Must be an Object. + // Because of IE, we also have to check the presence of the constructor property. + // Make sure that DOM nodes and window objects don't pass through, as well + if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) { + return false; + } + + try { + // Not own constructor property must be Object + if ( obj.constructor && + !hasOwn.call(obj, "constructor") && + !hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) { + return false; + } + } catch ( e ) { + // IE8,9 Will throw exceptions on certain host objects #9897 + return false; + } + + // Support: IE<9 + // Handle iteration over inherited properties before own properties. + if ( support.ownLast ) { + for ( key in obj ) { + return hasOwn.call( obj, key ); + } + } + + // Own properties are enumerated firstly, so to speed up, + // if last one is own, then all properties are own. + for ( key in obj ) {} + + return key === undefined || hasOwn.call( obj, key ); + }, + + type: function( obj ) { + if ( obj == null ) { + return obj + ""; + } + return typeof obj === "object" || typeof obj === "function" ? + class2type[ toString.call(obj) ] || "object" : + typeof obj; + }, + + // Evaluates a script in a global context + // Workarounds based on findings by Jim Driscoll + // http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context + globalEval: function( data ) { + if ( data && jQuery.trim( data ) ) { + // We use execScript on Internet Explorer + // We use an anonymous function so that context is window + // rather than jQuery in Firefox + ( window.execScript || function( data ) { + window[ "eval" ].call( window, data ); + } )( data ); + } + }, + + // Convert dashed to camelCase; used by the css and data modules + // Microsoft forgot to hump their vendor prefix (#9572) + camelCase: function( string ) { + return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); + }, + + nodeName: function( elem, name ) { + return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); + }, + + // args is for internal usage only + each: function( obj, callback, args ) { + var value, + i = 0, + length = obj.length, + isArray = isArraylike( obj ); + + if ( args ) { + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback.apply( obj[ i ], args ); + + if ( value === false ) { + break; + } + } + } else { + for ( i in obj ) { + value = callback.apply( obj[ i ], args ); + + if ( value === false ) { + break; + } + } + } + + // A special, fast, case for the most common use of each + } else { + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback.call( obj[ i ], i, obj[ i ] ); + + if ( value === false ) { + break; + } + } + } else { + for ( i in obj ) { + value = callback.call( obj[ i ], i, obj[ i ] ); + + if ( value === false ) { + break; + } + } + } + } + + return obj; + }, + + // Support: Android<4.1, IE<9 + trim: function( text ) { + return text == null ? + "" : + ( text + "" ).replace( rtrim, "" ); + }, + + // results is for internal usage only + makeArray: function( arr, results ) { + var ret = results || []; + + if ( arr != null ) { + if ( isArraylike( Object(arr) ) ) { + jQuery.merge( ret, + typeof arr === "string" ? + [ arr ] : arr + ); + } else { + push.call( ret, arr ); + } + } + + return ret; + }, + + inArray: function( elem, arr, i ) { + var len; + + if ( arr ) { + if ( indexOf ) { + return indexOf.call( arr, elem, i ); + } + + len = arr.length; + i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0; + + for ( ; i < len; i++ ) { + // Skip accessing in sparse arrays + if ( i in arr && arr[ i ] === elem ) { + return i; + } + } + } + + return -1; + }, + + merge: function( first, second ) { + var len = +second.length, + j = 0, + i = first.length; + + while ( j < len ) { + first[ i++ ] = second[ j++ ]; + } + + // Support: IE<9 + // Workaround casting of .length to NaN on otherwise arraylike objects (e.g., NodeLists) + if ( len !== len ) { + while ( second[j] !== undefined ) { + first[ i++ ] = second[ j++ ]; + } + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, invert ) { + var callbackInverse, + matches = [], + i = 0, + length = elems.length, + callbackExpect = !invert; + + // Go through the array, only saving the items + // that pass the validator function + for ( ; i < length; i++ ) { + callbackInverse = !callback( elems[ i ], i ); + if ( callbackInverse !== callbackExpect ) { + matches.push( elems[ i ] ); + } + } + + return matches; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var value, + i = 0, + length = elems.length, + isArray = isArraylike( elems ), + ret = []; + + // Go through the array, translating each of the items to their new values + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + + // Go through every key on the object, + } else { + for ( i in elems ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + } + + // Flatten any nested arrays + return concat.apply( [], ret ); + }, + + // A global GUID counter for objects + guid: 1, + + // Bind a function to a context, optionally partially applying any + // arguments. + proxy: function( fn, context ) { + var args, proxy, tmp; + + if ( typeof context === "string" ) { + tmp = fn[ context ]; + context = fn; + fn = tmp; + } + + // Quick check to determine if target is callable, in the spec + // this throws a TypeError, but we will just return undefined. + if ( !jQuery.isFunction( fn ) ) { + return undefined; + } + + // Simulated bind + args = slice.call( arguments, 2 ); + proxy = function() { + return fn.apply( context || this, args.concat( slice.call( arguments ) ) ); + }; + + // Set the guid of unique handler to the same of original handler, so it can be removed + proxy.guid = fn.guid = fn.guid || jQuery.guid++; + + return proxy; + }, + + now: function() { + return +( new Date() ); + }, + + // jQuery.support is not used in Core but other projects attach their + // properties to it so it needs to exist. + support: support +}); + +// Populate the class2type map +jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); +}); + +function isArraylike( obj ) { + var length = obj.length, + type = jQuery.type( obj ); + + if ( type === "function" || jQuery.isWindow( obj ) ) { + return false; + } + + if ( obj.nodeType === 1 && length ) { + return true; + } + + return type === "array" || length === 0 || + typeof length === "number" && length > 0 && ( length - 1 ) in obj; +} +var Sizzle = +/*! + * Sizzle CSS Selector Engine v1.10.19 + * http://sizzlejs.com/ + * + * Copyright 2013 jQuery Foundation, Inc. and other contributors + * Released under the MIT license + * http://jquery.org/license + * + * Date: 2014-04-18 + */ +(function( window ) { + +var i, + support, + Expr, + getText, + isXML, + tokenize, + compile, + select, + outermostContext, + sortInput, + hasDuplicate, + + // Local document vars + setDocument, + document, + docElem, + documentIsHTML, + rbuggyQSA, + rbuggyMatches, + matches, + contains, + + // Instance-specific data + expando = "sizzle" + -(new Date()), + preferredDoc = window.document, + dirruns = 0, + done = 0, + classCache = createCache(), + tokenCache = createCache(), + compilerCache = createCache(), + sortOrder = function( a, b ) { + if ( a === b ) { + hasDuplicate = true; + } + return 0; + }, + + // General-purpose constants + strundefined = typeof undefined, + MAX_NEGATIVE = 1 << 31, + + // Instance methods + hasOwn = ({}).hasOwnProperty, + arr = [], + pop = arr.pop, + push_native = arr.push, + push = arr.push, + slice = arr.slice, + // Use a stripped-down indexOf if we can't use a native one + indexOf = arr.indexOf || function( elem ) { + var i = 0, + len = this.length; + for ( ; i < len; i++ ) { + if ( this[i] === elem ) { + return i; + } + } + return -1; + }, + + booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped", + + // Regular expressions + + // Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace + whitespace = "[\\x20\\t\\r\\n\\f]", + // http://www.w3.org/TR/css3-syntax/#characters + characterEncoding = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+", + + // Loosely modeled on CSS identifier characters + // An unquoted value should be a CSS identifier http://www.w3.org/TR/css3-selectors/#attribute-selectors + // Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier + identifier = characterEncoding.replace( "w", "w#" ), + + // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors + attributes = "\\[" + whitespace + "*(" + characterEncoding + ")(?:" + whitespace + + // Operator (capture 2) + "*([*^$|!~]?=)" + whitespace + + // "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]" + "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace + + "*\\]", + + pseudos = ":(" + characterEncoding + ")(?:\\((" + + // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: + // 1. quoted (capture 3; capture 4 or capture 5) + "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + + // 2. simple (capture 6) + "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + + // 3. anything else (capture 2) + ".*" + + ")\\)|)", + + // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter + rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ), + + rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), + rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ), + + rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*?)" + whitespace + "*\\]", "g" ), + + rpseudo = new RegExp( pseudos ), + ridentifier = new RegExp( "^" + identifier + "$" ), + + matchExpr = { + "ID": new RegExp( "^#(" + characterEncoding + ")" ), + "CLASS": new RegExp( "^\\.(" + characterEncoding + ")" ), + "TAG": new RegExp( "^(" + characterEncoding.replace( "w", "w*" ) + ")" ), + "ATTR": new RegExp( "^" + attributes ), + "PSEUDO": new RegExp( "^" + pseudos ), + "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), + "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), + // For use in libraries implementing .is() + // We use this for POS matching in `select` + "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + + whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) + }, + + rinputs = /^(?:input|select|textarea|button)$/i, + rheader = /^h\d$/i, + + rnative = /^[^{]+\{\s*\[native \w/, + + // Easily-parseable/retrievable ID or TAG or CLASS selectors + rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, + + rsibling = /[+~]/, + rescape = /'|\\/g, + + // CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters + runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ), + funescape = function( _, escaped, escapedWhitespace ) { + var high = "0x" + escaped - 0x10000; + // NaN means non-codepoint + // Support: Firefox<24 + // Workaround erroneous numeric interpretation of +"0x" + return high !== high || escapedWhitespace ? + escaped : + high < 0 ? + // BMP codepoint + String.fromCharCode( high + 0x10000 ) : + // Supplemental Plane codepoint (surrogate pair) + String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); + }; + +// Optimize for push.apply( _, NodeList ) +try { + push.apply( + (arr = slice.call( preferredDoc.childNodes )), + preferredDoc.childNodes + ); + // Support: Android<4.0 + // Detect silently failing push.apply + arr[ preferredDoc.childNodes.length ].nodeType; +} catch ( e ) { + push = { apply: arr.length ? + + // Leverage slice if possible + function( target, els ) { + push_native.apply( target, slice.call(els) ); + } : + + // Support: IE<9 + // Otherwise append directly + function( target, els ) { + var j = target.length, + i = 0; + // Can't trust NodeList.length + while ( (target[j++] = els[i++]) ) {} + target.length = j - 1; + } + }; +} + +function Sizzle( selector, context, results, seed ) { + var match, elem, m, nodeType, + // QSA vars + i, groups, old, nid, newContext, newSelector; + + if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) { + setDocument( context ); + } + + context = context || document; + results = results || []; + + if ( !selector || typeof selector !== "string" ) { + return results; + } + + if ( (nodeType = context.nodeType) !== 1 && nodeType !== 9 ) { + return []; + } + + if ( documentIsHTML && !seed ) { + + // Shortcuts + if ( (match = rquickExpr.exec( selector )) ) { + // Speed-up: Sizzle("#ID") + if ( (m = match[1]) ) { + if ( nodeType === 9 ) { + elem = context.getElementById( m ); + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document (jQuery #6963) + if ( elem && elem.parentNode ) { + // Handle the case where IE, Opera, and Webkit return items + // by name instead of ID + if ( elem.id === m ) { + results.push( elem ); + return results; + } + } else { + return results; + } + } else { + // Context is not a document + if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) && + contains( context, elem ) && elem.id === m ) { + results.push( elem ); + return results; + } + } + + // Speed-up: Sizzle("TAG") + } else if ( match[2] ) { + push.apply( results, context.getElementsByTagName( selector ) ); + return results; + + // Speed-up: Sizzle(".CLASS") + } else if ( (m = match[3]) && support.getElementsByClassName && context.getElementsByClassName ) { + push.apply( results, context.getElementsByClassName( m ) ); + return results; + } + } + + // QSA path + if ( support.qsa && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) { + nid = old = expando; + newContext = context; + newSelector = nodeType === 9 && selector; + + // qSA works strangely on Element-rooted queries + // We can work around this by specifying an extra ID on the root + // and working up from there (Thanks to Andrew Dupont for the technique) + // IE 8 doesn't work on object elements + if ( nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { + groups = tokenize( selector ); + + if ( (old = context.getAttribute("id")) ) { + nid = old.replace( rescape, "\\$&" ); + } else { + context.setAttribute( "id", nid ); + } + nid = "[id='" + nid + "'] "; + + i = groups.length; + while ( i-- ) { + groups[i] = nid + toSelector( groups[i] ); + } + newContext = rsibling.test( selector ) && testContext( context.parentNode ) || context; + newSelector = groups.join(","); + } + + if ( newSelector ) { + try { + push.apply( results, + newContext.querySelectorAll( newSelector ) + ); + return results; + } catch(qsaError) { + } finally { + if ( !old ) { + context.removeAttribute("id"); + } + } + } + } + } + + // All others + return select( selector.replace( rtrim, "$1" ), context, results, seed ); +} + +/** + * Create key-value caches of limited size + * @returns {Function(string, Object)} Returns the Object data after storing it on itself with + * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) + * deleting the oldest entry + */ +function createCache() { + var keys = []; + + function cache( key, value ) { + // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) + if ( keys.push( key + " " ) > Expr.cacheLength ) { + // Only keep the most recent entries + delete cache[ keys.shift() ]; + } + return (cache[ key + " " ] = value); + } + return cache; +} + +/** + * Mark a function for special use by Sizzle + * @param {Function} fn The function to mark + */ +function markFunction( fn ) { + fn[ expando ] = true; + return fn; +} + +/** + * Support testing using an element + * @param {Function} fn Passed the created div and expects a boolean result + */ +function assert( fn ) { + var div = document.createElement("div"); + + try { + return !!fn( div ); + } catch (e) { + return false; + } finally { + // Remove from its parent by default + if ( div.parentNode ) { + div.parentNode.removeChild( div ); + } + // release memory in IE + div = null; + } +} + +/** + * Adds the same handler for all of the specified attrs + * @param {String} attrs Pipe-separated list of attributes + * @param {Function} handler The method that will be applied + */ +function addHandle( attrs, handler ) { + var arr = attrs.split("|"), + i = attrs.length; + + while ( i-- ) { + Expr.attrHandle[ arr[i] ] = handler; + } +} + +/** + * Checks document order of two siblings + * @param {Element} a + * @param {Element} b + * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b + */ +function siblingCheck( a, b ) { + var cur = b && a, + diff = cur && a.nodeType === 1 && b.nodeType === 1 && + ( ~b.sourceIndex || MAX_NEGATIVE ) - + ( ~a.sourceIndex || MAX_NEGATIVE ); + + // Use IE sourceIndex if available on both nodes + if ( diff ) { + return diff; + } + + // Check if b follows a + if ( cur ) { + while ( (cur = cur.nextSibling) ) { + if ( cur === b ) { + return -1; + } + } + } + + return a ? 1 : -1; +} + +/** + * Returns a function to use in pseudos for input types + * @param {String} type + */ +function createInputPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for buttons + * @param {String} type + */ +function createButtonPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return (name === "input" || name === "button") && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for positionals + * @param {Function} fn + */ +function createPositionalPseudo( fn ) { + return markFunction(function( argument ) { + argument = +argument; + return markFunction(function( seed, matches ) { + var j, + matchIndexes = fn( [], seed.length, argument ), + i = matchIndexes.length; + + // Match elements found at the specified indexes + while ( i-- ) { + if ( seed[ (j = matchIndexes[i]) ] ) { + seed[j] = !(matches[j] = seed[j]); + } + } + }); + }); +} + +/** + * Checks a node for validity as a Sizzle context + * @param {Element|Object=} context + * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value + */ +function testContext( context ) { + return context && typeof context.getElementsByTagName !== strundefined && context; +} + +// Expose support vars for convenience +support = Sizzle.support = {}; + +/** + * Detects XML nodes + * @param {Element|Object} elem An element or a document + * @returns {Boolean} True iff elem is a non-HTML XML node + */ +isXML = Sizzle.isXML = function( elem ) { + // documentElement is verified for cases where it doesn't yet exist + // (such as loading iframes in IE - #4833) + var documentElement = elem && (elem.ownerDocument || elem).documentElement; + return documentElement ? documentElement.nodeName !== "HTML" : false; +}; + +/** + * Sets document-related variables once based on the current document + * @param {Element|Object} [doc] An element or document object to use to set the document + * @returns {Object} Returns the current document + */ +setDocument = Sizzle.setDocument = function( node ) { + var hasCompare, + doc = node ? node.ownerDocument || node : preferredDoc, + parent = doc.defaultView; + + // If no document and documentElement is available, return + if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) { + return document; + } + + // Set our document + document = doc; + docElem = doc.documentElement; + + // Support tests + documentIsHTML = !isXML( doc ); + + // Support: IE>8 + // If iframe document is assigned to "document" variable and if iframe has been reloaded, + // IE will throw "permission denied" error when accessing "document" variable, see jQuery #13936 + // IE6-8 do not support the defaultView property so parent will be undefined + if ( parent && parent !== parent.top ) { + // IE11 does not have attachEvent, so all must suffer + if ( parent.addEventListener ) { + parent.addEventListener( "unload", function() { + setDocument(); + }, false ); + } else if ( parent.attachEvent ) { + parent.attachEvent( "onunload", function() { + setDocument(); + }); + } + } + + /* Attributes + ---------------------------------------------------------------------- */ + + // Support: IE<8 + // Verify that getAttribute really returns attributes and not properties (excepting IE8 booleans) + support.attributes = assert(function( div ) { + div.className = "i"; + return !div.getAttribute("className"); + }); + + /* getElement(s)By* + ---------------------------------------------------------------------- */ + + // Check if getElementsByTagName("*") returns only elements + support.getElementsByTagName = assert(function( div ) { + div.appendChild( doc.createComment("") ); + return !div.getElementsByTagName("*").length; + }); + + // Check if getElementsByClassName can be trusted + support.getElementsByClassName = rnative.test( doc.getElementsByClassName ) && assert(function( div ) { + div.innerHTML = "
"; + + // Support: Safari<4 + // Catch class over-caching + div.firstChild.className = "i"; + // Support: Opera<10 + // Catch gEBCN failure to find non-leading classes + return div.getElementsByClassName("i").length === 2; + }); + + // Support: IE<10 + // Check if getElementById returns elements by name + // The broken getElementById methods don't pick up programatically-set names, + // so use a roundabout getElementsByName test + support.getById = assert(function( div ) { + docElem.appendChild( div ).id = expando; + return !doc.getElementsByName || !doc.getElementsByName( expando ).length; + }); + + // ID find and filter + if ( support.getById ) { + Expr.find["ID"] = function( id, context ) { + if ( typeof context.getElementById !== strundefined && documentIsHTML ) { + var m = context.getElementById( id ); + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + return m && m.parentNode ? [ m ] : []; + } + }; + Expr.filter["ID"] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + return elem.getAttribute("id") === attrId; + }; + }; + } else { + // Support: IE6/7 + // getElementById is not reliable as a find shortcut + delete Expr.find["ID"]; + + Expr.filter["ID"] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + var node = typeof elem.getAttributeNode !== strundefined && elem.getAttributeNode("id"); + return node && node.value === attrId; + }; + }; + } + + // Tag + Expr.find["TAG"] = support.getElementsByTagName ? + function( tag, context ) { + if ( typeof context.getElementsByTagName !== strundefined ) { + return context.getElementsByTagName( tag ); + } + } : + function( tag, context ) { + var elem, + tmp = [], + i = 0, + results = context.getElementsByTagName( tag ); + + // Filter out possible comments + if ( tag === "*" ) { + while ( (elem = results[i++]) ) { + if ( elem.nodeType === 1 ) { + tmp.push( elem ); + } + } + + return tmp; + } + return results; + }; + + // Class + Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) { + if ( typeof context.getElementsByClassName !== strundefined && documentIsHTML ) { + return context.getElementsByClassName( className ); + } + }; + + /* QSA/matchesSelector + ---------------------------------------------------------------------- */ + + // QSA and matchesSelector support + + // matchesSelector(:active) reports false when true (IE9/Opera 11.5) + rbuggyMatches = []; + + // qSa(:focus) reports false when true (Chrome 21) + // We allow this because of a bug in IE8/9 that throws an error + // whenever `document.activeElement` is accessed on an iframe + // So, we allow :focus to pass through QSA all the time to avoid the IE error + // See http://bugs.jquery.com/ticket/13378 + rbuggyQSA = []; + + if ( (support.qsa = rnative.test( doc.querySelectorAll )) ) { + // Build QSA regex + // Regex strategy adopted from Diego Perini + assert(function( div ) { + // Select is set to empty string on purpose + // This is to test IE's treatment of not explicitly + // setting a boolean content attribute, + // since its presence should be enough + // http://bugs.jquery.com/ticket/12359 + div.innerHTML = ""; + + // Support: IE8, Opera 11-12.16 + // Nothing should be selected when empty strings follow ^= or $= or *= + // The test attribute must be unknown in Opera but "safe" for WinRT + // http://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section + if ( div.querySelectorAll("[msallowclip^='']").length ) { + rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); + } + + // Support: IE8 + // Boolean attributes and "value" are not treated correctly + if ( !div.querySelectorAll("[selected]").length ) { + rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); + } + + // Webkit/Opera - :checked should return selected option elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + // IE8 throws error here and will not see later tests + if ( !div.querySelectorAll(":checked").length ) { + rbuggyQSA.push(":checked"); + } + }); + + assert(function( div ) { + // Support: Windows 8 Native Apps + // The type and name attributes are restricted during .innerHTML assignment + var input = doc.createElement("input"); + input.setAttribute( "type", "hidden" ); + div.appendChild( input ).setAttribute( "name", "D" ); + + // Support: IE8 + // Enforce case-sensitivity of name attribute + if ( div.querySelectorAll("[name=d]").length ) { + rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); + } + + // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) + // IE8 throws error here and will not see later tests + if ( !div.querySelectorAll(":enabled").length ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Opera 10-11 does not throw on post-comma invalid pseudos + div.querySelectorAll("*,:x"); + rbuggyQSA.push(",.*:"); + }); + } + + if ( (support.matchesSelector = rnative.test( (matches = docElem.matches || + docElem.webkitMatchesSelector || + docElem.mozMatchesSelector || + docElem.oMatchesSelector || + docElem.msMatchesSelector) )) ) { + + assert(function( div ) { + // Check to see if it's possible to do matchesSelector + // on a disconnected node (IE 9) + support.disconnectedMatch = matches.call( div, "div" ); + + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call( div, "[s!='']:x" ); + rbuggyMatches.push( "!=", pseudos ); + }); + } + + rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") ); + rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") ); + + /* Contains + ---------------------------------------------------------------------- */ + hasCompare = rnative.test( docElem.compareDocumentPosition ); + + // Element contains another + // Purposefully does not implement inclusive descendent + // As in, an element does not contain itself + contains = hasCompare || rnative.test( docElem.contains ) ? + function( a, b ) { + var adown = a.nodeType === 9 ? a.documentElement : a, + bup = b && b.parentNode; + return a === bup || !!( bup && bup.nodeType === 1 && ( + adown.contains ? + adown.contains( bup ) : + a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 + )); + } : + function( a, b ) { + if ( b ) { + while ( (b = b.parentNode) ) { + if ( b === a ) { + return true; + } + } + } + return false; + }; + + /* Sorting + ---------------------------------------------------------------------- */ + + // Document order sorting + sortOrder = hasCompare ? + function( a, b ) { + + // Flag for duplicate removal + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + // Sort on method existence if only one input has compareDocumentPosition + var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; + if ( compare ) { + return compare; + } + + // Calculate position if both inputs belong to the same document + compare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ? + a.compareDocumentPosition( b ) : + + // Otherwise we know they are disconnected + 1; + + // Disconnected nodes + if ( compare & 1 || + (!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) { + + // Choose the first element that is related to our preferred document + if ( a === doc || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) { + return -1; + } + if ( b === doc || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) { + return 1; + } + + // Maintain original order + return sortInput ? + ( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) : + 0; + } + + return compare & 4 ? -1 : 1; + } : + function( a, b ) { + // Exit early if the nodes are identical + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + var cur, + i = 0, + aup = a.parentNode, + bup = b.parentNode, + ap = [ a ], + bp = [ b ]; + + // Parentless nodes are either documents or disconnected + if ( !aup || !bup ) { + return a === doc ? -1 : + b === doc ? 1 : + aup ? -1 : + bup ? 1 : + sortInput ? + ( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) : + 0; + + // If the nodes are siblings, we can do a quick check + } else if ( aup === bup ) { + return siblingCheck( a, b ); + } + + // Otherwise we need full lists of their ancestors for comparison + cur = a; + while ( (cur = cur.parentNode) ) { + ap.unshift( cur ); + } + cur = b; + while ( (cur = cur.parentNode) ) { + bp.unshift( cur ); + } + + // Walk down the tree looking for a discrepancy + while ( ap[i] === bp[i] ) { + i++; + } + + return i ? + // Do a sibling check if the nodes have a common ancestor + siblingCheck( ap[i], bp[i] ) : + + // Otherwise nodes in our document sort first + ap[i] === preferredDoc ? -1 : + bp[i] === preferredDoc ? 1 : + 0; + }; + + return doc; +}; + +Sizzle.matches = function( expr, elements ) { + return Sizzle( expr, null, null, elements ); +}; + +Sizzle.matchesSelector = function( elem, expr ) { + // Set document vars if needed + if ( ( elem.ownerDocument || elem ) !== document ) { + setDocument( elem ); + } + + // Make sure that attribute selectors are quoted + expr = expr.replace( rattributeQuotes, "='$1']" ); + + if ( support.matchesSelector && documentIsHTML && + ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && + ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { + + try { + var ret = matches.call( elem, expr ); + + // IE 9's matchesSelector returns false on disconnected nodes + if ( ret || support.disconnectedMatch || + // As well, disconnected nodes are said to be in a document + // fragment in IE 9 + elem.document && elem.document.nodeType !== 11 ) { + return ret; + } + } catch(e) {} + } + + return Sizzle( expr, document, null, [ elem ] ).length > 0; +}; + +Sizzle.contains = function( context, elem ) { + // Set document vars if needed + if ( ( context.ownerDocument || context ) !== document ) { + setDocument( context ); + } + return contains( context, elem ); +}; + +Sizzle.attr = function( elem, name ) { + // Set document vars if needed + if ( ( elem.ownerDocument || elem ) !== document ) { + setDocument( elem ); + } + + var fn = Expr.attrHandle[ name.toLowerCase() ], + // Don't get fooled by Object.prototype properties (jQuery #13807) + val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? + fn( elem, name, !documentIsHTML ) : + undefined; + + return val !== undefined ? + val : + support.attributes || !documentIsHTML ? + elem.getAttribute( name ) : + (val = elem.getAttributeNode(name)) && val.specified ? + val.value : + null; +}; + +Sizzle.error = function( msg ) { + throw new Error( "Syntax error, unrecognized expression: " + msg ); +}; + +/** + * Document sorting and removing duplicates + * @param {ArrayLike} results + */ +Sizzle.uniqueSort = function( results ) { + var elem, + duplicates = [], + j = 0, + i = 0; + + // Unless we *know* we can detect duplicates, assume their presence + hasDuplicate = !support.detectDuplicates; + sortInput = !support.sortStable && results.slice( 0 ); + results.sort( sortOrder ); + + if ( hasDuplicate ) { + while ( (elem = results[i++]) ) { + if ( elem === results[ i ] ) { + j = duplicates.push( i ); + } + } + while ( j-- ) { + results.splice( duplicates[ j ], 1 ); + } + } + + // Clear input after sorting to release objects + // See https://github.com/jquery/sizzle/pull/225 + sortInput = null; + + return results; +}; + +/** + * Utility function for retrieving the text value of an array of DOM nodes + * @param {Array|Element} elem + */ +getText = Sizzle.getText = function( elem ) { + var node, + ret = "", + i = 0, + nodeType = elem.nodeType; + + if ( !nodeType ) { + // If no nodeType, this is expected to be an array + while ( (node = elem[i++]) ) { + // Do not traverse comment nodes + ret += getText( node ); + } + } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { + // Use textContent for elements + // innerText usage removed for consistency of new lines (jQuery #11153) + if ( typeof elem.textContent === "string" ) { + return elem.textContent; + } else { + // Traverse its children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + ret += getText( elem ); + } + } + } else if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + // Do not include comment or processing instruction nodes + + return ret; +}; + +Expr = Sizzle.selectors = { + + // Can be adjusted by the user + cacheLength: 50, + + createPseudo: markFunction, + + match: matchExpr, + + attrHandle: {}, + + find: {}, + + relative: { + ">": { dir: "parentNode", first: true }, + " ": { dir: "parentNode" }, + "+": { dir: "previousSibling", first: true }, + "~": { dir: "previousSibling" } + }, + + preFilter: { + "ATTR": function( match ) { + match[1] = match[1].replace( runescape, funescape ); + + // Move the given value to match[3] whether quoted or unquoted + match[3] = ( match[3] || match[4] || match[5] || "" ).replace( runescape, funescape ); + + if ( match[2] === "~=" ) { + match[3] = " " + match[3] + " "; + } + + return match.slice( 0, 4 ); + }, + + "CHILD": function( match ) { + /* matches from matchExpr["CHILD"] + 1 type (only|nth|...) + 2 what (child|of-type) + 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) + 4 xn-component of xn+y argument ([+-]?\d*n|) + 5 sign of xn-component + 6 x of xn-component + 7 sign of y-component + 8 y of y-component + */ + match[1] = match[1].toLowerCase(); + + if ( match[1].slice( 0, 3 ) === "nth" ) { + // nth-* requires argument + if ( !match[3] ) { + Sizzle.error( match[0] ); + } + + // numeric x and y parameters for Expr.filter.CHILD + // remember that false/true cast respectively to 0/1 + match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) ); + match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" ); + + // other types prohibit arguments + } else if ( match[3] ) { + Sizzle.error( match[0] ); + } + + return match; + }, + + "PSEUDO": function( match ) { + var excess, + unquoted = !match[6] && match[2]; + + if ( matchExpr["CHILD"].test( match[0] ) ) { + return null; + } + + // Accept quoted arguments as-is + if ( match[3] ) { + match[2] = match[4] || match[5] || ""; + + // Strip excess characters from unquoted arguments + } else if ( unquoted && rpseudo.test( unquoted ) && + // Get excess from tokenize (recursively) + (excess = tokenize( unquoted, true )) && + // advance to the next closing parenthesis + (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) { + + // excess is a negative index + match[0] = match[0].slice( 0, excess ); + match[2] = unquoted.slice( 0, excess ); + } + + // Return only captures needed by the pseudo filter method (type and argument) + return match.slice( 0, 3 ); + } + }, + + filter: { + + "TAG": function( nodeNameSelector ) { + var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); + return nodeNameSelector === "*" ? + function() { return true; } : + function( elem ) { + return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; + }; + }, + + "CLASS": function( className ) { + var pattern = classCache[ className + " " ]; + + return pattern || + (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) && + classCache( className, function( elem ) { + return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== strundefined && elem.getAttribute("class") || "" ); + }); + }, + + "ATTR": function( name, operator, check ) { + return function( elem ) { + var result = Sizzle.attr( elem, name ); + + if ( result == null ) { + return operator === "!="; + } + if ( !operator ) { + return true; + } + + result += ""; + + return operator === "=" ? result === check : + operator === "!=" ? result !== check : + operator === "^=" ? check && result.indexOf( check ) === 0 : + operator === "*=" ? check && result.indexOf( check ) > -1 : + operator === "$=" ? check && result.slice( -check.length ) === check : + operator === "~=" ? ( " " + result + " " ).indexOf( check ) > -1 : + operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : + false; + }; + }, + + "CHILD": function( type, what, argument, first, last ) { + var simple = type.slice( 0, 3 ) !== "nth", + forward = type.slice( -4 ) !== "last", + ofType = what === "of-type"; + + return first === 1 && last === 0 ? + + // Shortcut for :nth-*(n) + function( elem ) { + return !!elem.parentNode; + } : + + function( elem, context, xml ) { + var cache, outerCache, node, diff, nodeIndex, start, + dir = simple !== forward ? "nextSibling" : "previousSibling", + parent = elem.parentNode, + name = ofType && elem.nodeName.toLowerCase(), + useCache = !xml && !ofType; + + if ( parent ) { + + // :(first|last|only)-(child|of-type) + if ( simple ) { + while ( dir ) { + node = elem; + while ( (node = node[ dir ]) ) { + if ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) { + return false; + } + } + // Reverse direction for :only-* (if we haven't yet done so) + start = dir = type === "only" && !start && "nextSibling"; + } + return true; + } + + start = [ forward ? parent.firstChild : parent.lastChild ]; + + // non-xml :nth-child(...) stores cache data on `parent` + if ( forward && useCache ) { + // Seek `elem` from a previously-cached index + outerCache = parent[ expando ] || (parent[ expando ] = {}); + cache = outerCache[ type ] || []; + nodeIndex = cache[0] === dirruns && cache[1]; + diff = cache[0] === dirruns && cache[2]; + node = nodeIndex && parent.childNodes[ nodeIndex ]; + + while ( (node = ++nodeIndex && node && node[ dir ] || + + // Fallback to seeking `elem` from the start + (diff = nodeIndex = 0) || start.pop()) ) { + + // When found, cache indexes on `parent` and break + if ( node.nodeType === 1 && ++diff && node === elem ) { + outerCache[ type ] = [ dirruns, nodeIndex, diff ]; + break; + } + } + + // Use previously-cached element index if available + } else if ( useCache && (cache = (elem[ expando ] || (elem[ expando ] = {}))[ type ]) && cache[0] === dirruns ) { + diff = cache[1]; + + // xml :nth-child(...) or :nth-last-child(...) or :nth(-last)?-of-type(...) + } else { + // Use the same loop as above to seek `elem` from the start + while ( (node = ++nodeIndex && node && node[ dir ] || + (diff = nodeIndex = 0) || start.pop()) ) { + + if ( ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) && ++diff ) { + // Cache the index of each encountered element + if ( useCache ) { + (node[ expando ] || (node[ expando ] = {}))[ type ] = [ dirruns, diff ]; + } + + if ( node === elem ) { + break; + } + } + } + } + + // Incorporate the offset, then check against cycle size + diff -= last; + return diff === first || ( diff % first === 0 && diff / first >= 0 ); + } + }; + }, + + "PSEUDO": function( pseudo, argument ) { + // pseudo-class names are case-insensitive + // http://www.w3.org/TR/selectors/#pseudo-classes + // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters + // Remember that setFilters inherits from pseudos + var args, + fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || + Sizzle.error( "unsupported pseudo: " + pseudo ); + + // The user may use createPseudo to indicate that + // arguments are needed to create the filter function + // just as Sizzle does + if ( fn[ expando ] ) { + return fn( argument ); + } + + // But maintain support for old signatures + if ( fn.length > 1 ) { + args = [ pseudo, pseudo, "", argument ]; + return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? + markFunction(function( seed, matches ) { + var idx, + matched = fn( seed, argument ), + i = matched.length; + while ( i-- ) { + idx = indexOf.call( seed, matched[i] ); + seed[ idx ] = !( matches[ idx ] = matched[i] ); + } + }) : + function( elem ) { + return fn( elem, 0, args ); + }; + } + + return fn; + } + }, + + pseudos: { + // Potentially complex pseudos + "not": markFunction(function( selector ) { + // Trim the selector passed to compile + // to avoid treating leading and trailing + // spaces as combinators + var input = [], + results = [], + matcher = compile( selector.replace( rtrim, "$1" ) ); + + return matcher[ expando ] ? + markFunction(function( seed, matches, context, xml ) { + var elem, + unmatched = matcher( seed, null, xml, [] ), + i = seed.length; + + // Match elements unmatched by `matcher` + while ( i-- ) { + if ( (elem = unmatched[i]) ) { + seed[i] = !(matches[i] = elem); + } + } + }) : + function( elem, context, xml ) { + input[0] = elem; + matcher( input, null, xml, results ); + return !results.pop(); + }; + }), + + "has": markFunction(function( selector ) { + return function( elem ) { + return Sizzle( selector, elem ).length > 0; + }; + }), + + "contains": markFunction(function( text ) { + return function( elem ) { + return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1; + }; + }), + + // "Whether an element is represented by a :lang() selector + // is based solely on the element's language value + // being equal to the identifier C, + // or beginning with the identifier C immediately followed by "-". + // The matching of C against the element's language value is performed case-insensitively. + // The identifier C does not have to be a valid language name." + // http://www.w3.org/TR/selectors/#lang-pseudo + "lang": markFunction( function( lang ) { + // lang value must be a valid identifier + if ( !ridentifier.test(lang || "") ) { + Sizzle.error( "unsupported lang: " + lang ); + } + lang = lang.replace( runescape, funescape ).toLowerCase(); + return function( elem ) { + var elemLang; + do { + if ( (elemLang = documentIsHTML ? + elem.lang : + elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) { + + elemLang = elemLang.toLowerCase(); + return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; + } + } while ( (elem = elem.parentNode) && elem.nodeType === 1 ); + return false; + }; + }), + + // Miscellaneous + "target": function( elem ) { + var hash = window.location && window.location.hash; + return hash && hash.slice( 1 ) === elem.id; + }, + + "root": function( elem ) { + return elem === docElem; + }, + + "focus": function( elem ) { + return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex); + }, + + // Boolean properties + "enabled": function( elem ) { + return elem.disabled === false; + }, + + "disabled": function( elem ) { + return elem.disabled === true; + }, + + "checked": function( elem ) { + // In CSS3, :checked should return both checked and selected elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + var nodeName = elem.nodeName.toLowerCase(); + return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected); + }, + + "selected": function( elem ) { + // Accessing this property makes selected-by-default + // options in Safari work properly + if ( elem.parentNode ) { + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + // Contents + "empty": function( elem ) { + // http://www.w3.org/TR/selectors/#empty-pseudo + // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), + // but not by others (comment: 8; processing instruction: 7; etc.) + // nodeType < 6 works because attributes (2) do not appear as children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + if ( elem.nodeType < 6 ) { + return false; + } + } + return true; + }, + + "parent": function( elem ) { + return !Expr.pseudos["empty"]( elem ); + }, + + // Element/input types + "header": function( elem ) { + return rheader.test( elem.nodeName ); + }, + + "input": function( elem ) { + return rinputs.test( elem.nodeName ); + }, + + "button": function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === "button" || name === "button"; + }, + + "text": function( elem ) { + var attr; + return elem.nodeName.toLowerCase() === "input" && + elem.type === "text" && + + // Support: IE<8 + // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" + ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === "text" ); + }, + + // Position-in-collection + "first": createPositionalPseudo(function() { + return [ 0 ]; + }), + + "last": createPositionalPseudo(function( matchIndexes, length ) { + return [ length - 1 ]; + }), + + "eq": createPositionalPseudo(function( matchIndexes, length, argument ) { + return [ argument < 0 ? argument + length : argument ]; + }), + + "even": createPositionalPseudo(function( matchIndexes, length ) { + var i = 0; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "odd": createPositionalPseudo(function( matchIndexes, length ) { + var i = 1; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "lt": createPositionalPseudo(function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; --i >= 0; ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "gt": createPositionalPseudo(function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; ++i < length; ) { + matchIndexes.push( i ); + } + return matchIndexes; + }) + } +}; + +Expr.pseudos["nth"] = Expr.pseudos["eq"]; + +// Add button/input type pseudos +for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { + Expr.pseudos[ i ] = createInputPseudo( i ); +} +for ( i in { submit: true, reset: true } ) { + Expr.pseudos[ i ] = createButtonPseudo( i ); +} + +// Easy API for creating new setFilters +function setFilters() {} +setFilters.prototype = Expr.filters = Expr.pseudos; +Expr.setFilters = new setFilters(); + +tokenize = Sizzle.tokenize = function( selector, parseOnly ) { + var matched, match, tokens, type, + soFar, groups, preFilters, + cached = tokenCache[ selector + " " ]; + + if ( cached ) { + return parseOnly ? 0 : cached.slice( 0 ); + } + + soFar = selector; + groups = []; + preFilters = Expr.preFilter; + + while ( soFar ) { + + // Comma and first run + if ( !matched || (match = rcomma.exec( soFar )) ) { + if ( match ) { + // Don't consume trailing commas as valid + soFar = soFar.slice( match[0].length ) || soFar; + } + groups.push( (tokens = []) ); + } + + matched = false; + + // Combinators + if ( (match = rcombinators.exec( soFar )) ) { + matched = match.shift(); + tokens.push({ + value: matched, + // Cast descendant combinators to space + type: match[0].replace( rtrim, " " ) + }); + soFar = soFar.slice( matched.length ); + } + + // Filters + for ( type in Expr.filter ) { + if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] || + (match = preFilters[ type ]( match ))) ) { + matched = match.shift(); + tokens.push({ + value: matched, + type: type, + matches: match + }); + soFar = soFar.slice( matched.length ); + } + } + + if ( !matched ) { + break; + } + } + + // Return the length of the invalid excess + // if we're just parsing + // Otherwise, throw an error or return tokens + return parseOnly ? + soFar.length : + soFar ? + Sizzle.error( selector ) : + // Cache the tokens + tokenCache( selector, groups ).slice( 0 ); +}; + +function toSelector( tokens ) { + var i = 0, + len = tokens.length, + selector = ""; + for ( ; i < len; i++ ) { + selector += tokens[i].value; + } + return selector; +} + +function addCombinator( matcher, combinator, base ) { + var dir = combinator.dir, + checkNonElements = base && dir === "parentNode", + doneName = done++; + + return combinator.first ? + // Check against closest ancestor/preceding element + function( elem, context, xml ) { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + return matcher( elem, context, xml ); + } + } + } : + + // Check against all ancestor/preceding elements + function( elem, context, xml ) { + var oldCache, outerCache, + newCache = [ dirruns, doneName ]; + + // We can't set arbitrary data on XML nodes, so they don't benefit from dir caching + if ( xml ) { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + if ( matcher( elem, context, xml ) ) { + return true; + } + } + } + } else { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + outerCache = elem[ expando ] || (elem[ expando ] = {}); + if ( (oldCache = outerCache[ dir ]) && + oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { + + // Assign to newCache so results back-propagate to previous elements + return (newCache[ 2 ] = oldCache[ 2 ]); + } else { + // Reuse newcache so results back-propagate to previous elements + outerCache[ dir ] = newCache; + + // A match means we're done; a fail means we have to keep checking + if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) { + return true; + } + } + } + } + } + }; +} + +function elementMatcher( matchers ) { + return matchers.length > 1 ? + function( elem, context, xml ) { + var i = matchers.length; + while ( i-- ) { + if ( !matchers[i]( elem, context, xml ) ) { + return false; + } + } + return true; + } : + matchers[0]; +} + +function multipleContexts( selector, contexts, results ) { + var i = 0, + len = contexts.length; + for ( ; i < len; i++ ) { + Sizzle( selector, contexts[i], results ); + } + return results; +} + +function condense( unmatched, map, filter, context, xml ) { + var elem, + newUnmatched = [], + i = 0, + len = unmatched.length, + mapped = map != null; + + for ( ; i < len; i++ ) { + if ( (elem = unmatched[i]) ) { + if ( !filter || filter( elem, context, xml ) ) { + newUnmatched.push( elem ); + if ( mapped ) { + map.push( i ); + } + } + } + } + + return newUnmatched; +} + +function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { + if ( postFilter && !postFilter[ expando ] ) { + postFilter = setMatcher( postFilter ); + } + if ( postFinder && !postFinder[ expando ] ) { + postFinder = setMatcher( postFinder, postSelector ); + } + return markFunction(function( seed, results, context, xml ) { + var temp, i, elem, + preMap = [], + postMap = [], + preexisting = results.length, + + // Get initial elements from seed or context + elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ), + + // Prefilter to get matcher input, preserving a map for seed-results synchronization + matcherIn = preFilter && ( seed || !selector ) ? + condense( elems, preMap, preFilter, context, xml ) : + elems, + + matcherOut = matcher ? + // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, + postFinder || ( seed ? preFilter : preexisting || postFilter ) ? + + // ...intermediate processing is necessary + [] : + + // ...otherwise use results directly + results : + matcherIn; + + // Find primary matches + if ( matcher ) { + matcher( matcherIn, matcherOut, context, xml ); + } + + // Apply postFilter + if ( postFilter ) { + temp = condense( matcherOut, postMap ); + postFilter( temp, [], context, xml ); + + // Un-match failing elements by moving them back to matcherIn + i = temp.length; + while ( i-- ) { + if ( (elem = temp[i]) ) { + matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem); + } + } + } + + if ( seed ) { + if ( postFinder || preFilter ) { + if ( postFinder ) { + // Get the final matcherOut by condensing this intermediate into postFinder contexts + temp = []; + i = matcherOut.length; + while ( i-- ) { + if ( (elem = matcherOut[i]) ) { + // Restore matcherIn since elem is not yet a final match + temp.push( (matcherIn[i] = elem) ); + } + } + postFinder( null, (matcherOut = []), temp, xml ); + } + + // Move matched elements from seed to results to keep them synchronized + i = matcherOut.length; + while ( i-- ) { + if ( (elem = matcherOut[i]) && + (temp = postFinder ? indexOf.call( seed, elem ) : preMap[i]) > -1 ) { + + seed[temp] = !(results[temp] = elem); + } + } + } + + // Add elements to results, through postFinder if defined + } else { + matcherOut = condense( + matcherOut === results ? + matcherOut.splice( preexisting, matcherOut.length ) : + matcherOut + ); + if ( postFinder ) { + postFinder( null, results, matcherOut, xml ); + } else { + push.apply( results, matcherOut ); + } + } + }); +} + +function matcherFromTokens( tokens ) { + var checkContext, matcher, j, + len = tokens.length, + leadingRelative = Expr.relative[ tokens[0].type ], + implicitRelative = leadingRelative || Expr.relative[" "], + i = leadingRelative ? 1 : 0, + + // The foundational matcher ensures that elements are reachable from top-level context(s) + matchContext = addCombinator( function( elem ) { + return elem === checkContext; + }, implicitRelative, true ), + matchAnyContext = addCombinator( function( elem ) { + return indexOf.call( checkContext, elem ) > -1; + }, implicitRelative, true ), + matchers = [ function( elem, context, xml ) { + return ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( + (checkContext = context).nodeType ? + matchContext( elem, context, xml ) : + matchAnyContext( elem, context, xml ) ); + } ]; + + for ( ; i < len; i++ ) { + if ( (matcher = Expr.relative[ tokens[i].type ]) ) { + matchers = [ addCombinator(elementMatcher( matchers ), matcher) ]; + } else { + matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches ); + + // Return special upon seeing a positional matcher + if ( matcher[ expando ] ) { + // Find the next relative operator (if any) for proper handling + j = ++i; + for ( ; j < len; j++ ) { + if ( Expr.relative[ tokens[j].type ] ) { + break; + } + } + return setMatcher( + i > 1 && elementMatcher( matchers ), + i > 1 && toSelector( + // If the preceding token was a descendant combinator, insert an implicit any-element `*` + tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" }) + ).replace( rtrim, "$1" ), + matcher, + i < j && matcherFromTokens( tokens.slice( i, j ) ), + j < len && matcherFromTokens( (tokens = tokens.slice( j )) ), + j < len && toSelector( tokens ) + ); + } + matchers.push( matcher ); + } + } + + return elementMatcher( matchers ); +} + +function matcherFromGroupMatchers( elementMatchers, setMatchers ) { + var bySet = setMatchers.length > 0, + byElement = elementMatchers.length > 0, + superMatcher = function( seed, context, xml, results, outermost ) { + var elem, j, matcher, + matchedCount = 0, + i = "0", + unmatched = seed && [], + setMatched = [], + contextBackup = outermostContext, + // We must always have either seed elements or outermost context + elems = seed || byElement && Expr.find["TAG"]( "*", outermost ), + // Use integer dirruns iff this is the outermost matcher + dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1), + len = elems.length; + + if ( outermost ) { + outermostContext = context !== document && context; + } + + // Add elements passing elementMatchers directly to results + // Keep `i` a string if there are no elements so `matchedCount` will be "00" below + // Support: IE<9, Safari + // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id + for ( ; i !== len && (elem = elems[i]) != null; i++ ) { + if ( byElement && elem ) { + j = 0; + while ( (matcher = elementMatchers[j++]) ) { + if ( matcher( elem, context, xml ) ) { + results.push( elem ); + break; + } + } + if ( outermost ) { + dirruns = dirrunsUnique; + } + } + + // Track unmatched elements for set filters + if ( bySet ) { + // They will have gone through all possible matchers + if ( (elem = !matcher && elem) ) { + matchedCount--; + } + + // Lengthen the array for every element, matched or not + if ( seed ) { + unmatched.push( elem ); + } + } + } + + // Apply set filters to unmatched elements + matchedCount += i; + if ( bySet && i !== matchedCount ) { + j = 0; + while ( (matcher = setMatchers[j++]) ) { + matcher( unmatched, setMatched, context, xml ); + } + + if ( seed ) { + // Reintegrate element matches to eliminate the need for sorting + if ( matchedCount > 0 ) { + while ( i-- ) { + if ( !(unmatched[i] || setMatched[i]) ) { + setMatched[i] = pop.call( results ); + } + } + } + + // Discard index placeholder values to get only actual matches + setMatched = condense( setMatched ); + } + + // Add matches to results + push.apply( results, setMatched ); + + // Seedless set matches succeeding multiple successful matchers stipulate sorting + if ( outermost && !seed && setMatched.length > 0 && + ( matchedCount + setMatchers.length ) > 1 ) { + + Sizzle.uniqueSort( results ); + } + } + + // Override manipulation of globals by nested matchers + if ( outermost ) { + dirruns = dirrunsUnique; + outermostContext = contextBackup; + } + + return unmatched; + }; + + return bySet ? + markFunction( superMatcher ) : + superMatcher; +} + +compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { + var i, + setMatchers = [], + elementMatchers = [], + cached = compilerCache[ selector + " " ]; + + if ( !cached ) { + // Generate a function of recursive functions that can be used to check each element + if ( !match ) { + match = tokenize( selector ); + } + i = match.length; + while ( i-- ) { + cached = matcherFromTokens( match[i] ); + if ( cached[ expando ] ) { + setMatchers.push( cached ); + } else { + elementMatchers.push( cached ); + } + } + + // Cache the compiled function + cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) ); + + // Save selector and tokenization + cached.selector = selector; + } + return cached; +}; + +/** + * A low-level selection function that works with Sizzle's compiled + * selector functions + * @param {String|Function} selector A selector or a pre-compiled + * selector function built with Sizzle.compile + * @param {Element} context + * @param {Array} [results] + * @param {Array} [seed] A set of elements to match against + */ +select = Sizzle.select = function( selector, context, results, seed ) { + var i, tokens, token, type, find, + compiled = typeof selector === "function" && selector, + match = !seed && tokenize( (selector = compiled.selector || selector) ); + + results = results || []; + + // Try to minimize operations if there is no seed and only one group + if ( match.length === 1 ) { + + // Take a shortcut and set the context if the root selector is an ID + tokens = match[0] = match[0].slice( 0 ); + if ( tokens.length > 2 && (token = tokens[0]).type === "ID" && + support.getById && context.nodeType === 9 && documentIsHTML && + Expr.relative[ tokens[1].type ] ) { + + context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0]; + if ( !context ) { + return results; + + // Precompiled matchers will still verify ancestry, so step up a level + } else if ( compiled ) { + context = context.parentNode; + } + + selector = selector.slice( tokens.shift().value.length ); + } + + // Fetch a seed set for right-to-left matching + i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length; + while ( i-- ) { + token = tokens[i]; + + // Abort if we hit a combinator + if ( Expr.relative[ (type = token.type) ] ) { + break; + } + if ( (find = Expr.find[ type ]) ) { + // Search, expanding context for leading sibling combinators + if ( (seed = find( + token.matches[0].replace( runescape, funescape ), + rsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context + )) ) { + + // If seed is empty or no tokens remain, we can return early + tokens.splice( i, 1 ); + selector = seed.length && toSelector( tokens ); + if ( !selector ) { + push.apply( results, seed ); + return results; + } + + break; + } + } + } + } + + // Compile and execute a filtering function if one is not provided + // Provide `match` to avoid retokenization if we modified the selector above + ( compiled || compile( selector, match ) )( + seed, + context, + !documentIsHTML, + results, + rsibling.test( selector ) && testContext( context.parentNode ) || context + ); + return results; +}; + +// One-time assignments + +// Sort stability +support.sortStable = expando.split("").sort( sortOrder ).join("") === expando; + +// Support: Chrome<14 +// Always assume duplicates if they aren't passed to the comparison function +support.detectDuplicates = !!hasDuplicate; + +// Initialize against the default document +setDocument(); + +// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) +// Detached nodes confoundingly follow *each other* +support.sortDetached = assert(function( div1 ) { + // Should return 1, but returns 4 (following) + return div1.compareDocumentPosition( document.createElement("div") ) & 1; +}); + +// Support: IE<8 +// Prevent attribute/property "interpolation" +// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx +if ( !assert(function( div ) { + div.innerHTML = ""; + return div.firstChild.getAttribute("href") === "#" ; +}) ) { + addHandle( "type|href|height|width", function( elem, name, isXML ) { + if ( !isXML ) { + return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); + } + }); +} + +// Support: IE<9 +// Use defaultValue in place of getAttribute("value") +if ( !support.attributes || !assert(function( div ) { + div.innerHTML = ""; + div.firstChild.setAttribute( "value", "" ); + return div.firstChild.getAttribute( "value" ) === ""; +}) ) { + addHandle( "value", function( elem, name, isXML ) { + if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { + return elem.defaultValue; + } + }); +} + +// Support: IE<9 +// Use getAttributeNode to fetch booleans when getAttribute lies +if ( !assert(function( div ) { + return div.getAttribute("disabled") == null; +}) ) { + addHandle( booleans, function( elem, name, isXML ) { + var val; + if ( !isXML ) { + return elem[ name ] === true ? name.toLowerCase() : + (val = elem.getAttributeNode( name )) && val.specified ? + val.value : + null; + } + }); +} + +return Sizzle; + +})( window ); + + + +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; +jQuery.expr[":"] = jQuery.expr.pseudos; +jQuery.unique = Sizzle.uniqueSort; +jQuery.text = Sizzle.getText; +jQuery.isXMLDoc = Sizzle.isXML; +jQuery.contains = Sizzle.contains; + + + +var rneedsContext = jQuery.expr.match.needsContext; + +var rsingleTag = (/^<(\w+)\s*\/?>(?:<\/\1>|)$/); + + + +var risSimple = /^.[^:#\[\.,]*$/; + +// Implement the identical functionality for filter and not +function winnow( elements, qualifier, not ) { + if ( jQuery.isFunction( qualifier ) ) { + return jQuery.grep( elements, function( elem, i ) { + /* jshint -W018 */ + return !!qualifier.call( elem, i, elem ) !== not; + }); + + } + + if ( qualifier.nodeType ) { + return jQuery.grep( elements, function( elem ) { + return ( elem === qualifier ) !== not; + }); + + } + + if ( typeof qualifier === "string" ) { + if ( risSimple.test( qualifier ) ) { + return jQuery.filter( qualifier, elements, not ); + } + + qualifier = jQuery.filter( qualifier, elements ); + } + + return jQuery.grep( elements, function( elem ) { + return ( jQuery.inArray( elem, qualifier ) >= 0 ) !== not; + }); +} + +jQuery.filter = function( expr, elems, not ) { + var elem = elems[ 0 ]; + + if ( not ) { + expr = ":not(" + expr + ")"; + } + + return elems.length === 1 && elem.nodeType === 1 ? + jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [] : + jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { + return elem.nodeType === 1; + })); +}; + +jQuery.fn.extend({ + find: function( selector ) { + var i, + ret = [], + self = this, + len = self.length; + + if ( typeof selector !== "string" ) { + return this.pushStack( jQuery( selector ).filter(function() { + for ( i = 0; i < len; i++ ) { + if ( jQuery.contains( self[ i ], this ) ) { + return true; + } + } + }) ); + } + + for ( i = 0; i < len; i++ ) { + jQuery.find( selector, self[ i ], ret ); + } + + // Needed because $( selector, context ) becomes $( context ).find( selector ) + ret = this.pushStack( len > 1 ? jQuery.unique( ret ) : ret ); + ret.selector = this.selector ? this.selector + " " + selector : selector; + return ret; + }, + filter: function( selector ) { + return this.pushStack( winnow(this, selector || [], false) ); + }, + not: function( selector ) { + return this.pushStack( winnow(this, selector || [], true) ); + }, + is: function( selector ) { + return !!winnow( + this, + + // If this is a positional/relative selector, check membership in the returned set + // so $("p:first").is("p:last") won't return true for a doc with two "p". + typeof selector === "string" && rneedsContext.test( selector ) ? + jQuery( selector ) : + selector || [], + false + ).length; + } +}); + + +// Initialize a jQuery object + + +// A central reference to the root jQuery(document) +var rootjQuery, + + // Use the correct document accordingly with window argument (sandbox) + document = window.document, + + // A simple way to check for HTML strings + // Prioritize #id over to avoid XSS via location.hash (#9521) + // Strict HTML recognition (#11290: must start with <) + rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/, + + init = jQuery.fn.init = function( selector, context ) { + var match, elem; + + // HANDLE: $(""), $(null), $(undefined), $(false) + if ( !selector ) { + return this; + } + + // Handle HTML strings + if ( typeof selector === "string" ) { + if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) { + // Assume that strings that start and end with <> are HTML and skip the regex check + match = [ null, selector, null ]; + + } else { + match = rquickExpr.exec( selector ); + } + + // Match html or make sure no context is specified for #id + if ( match && (match[1] || !context) ) { + + // HANDLE: $(html) -> $(array) + if ( match[1] ) { + context = context instanceof jQuery ? context[0] : context; + + // scripts is true for back-compat + // Intentionally let the error be thrown if parseHTML is not present + jQuery.merge( this, jQuery.parseHTML( + match[1], + context && context.nodeType ? context.ownerDocument || context : document, + true + ) ); + + // HANDLE: $(html, props) + if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) { + for ( match in context ) { + // Properties of context are called as methods if possible + if ( jQuery.isFunction( this[ match ] ) ) { + this[ match ]( context[ match ] ); + + // ...and otherwise set as attributes + } else { + this.attr( match, context[ match ] ); + } + } + } + + return this; + + // HANDLE: $(#id) + } else { + elem = document.getElementById( match[2] ); + + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + if ( elem && elem.parentNode ) { + // Handle the case where IE and Opera return items + // by name instead of ID + if ( elem.id !== match[2] ) { + return rootjQuery.find( selector ); + } + + // Otherwise, we inject the element directly into the jQuery object + this.length = 1; + this[0] = elem; + } + + this.context = document; + this.selector = selector; + return this; + } + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return ( context || rootjQuery ).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return this.constructor( context ).find( selector ); + } + + // HANDLE: $(DOMElement) + } else if ( selector.nodeType ) { + this.context = this[0] = selector; + this.length = 1; + return this; + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( jQuery.isFunction( selector ) ) { + return typeof rootjQuery.ready !== "undefined" ? + rootjQuery.ready( selector ) : + // Execute immediately if ready is not present + selector( jQuery ); + } + + if ( selector.selector !== undefined ) { + this.selector = selector.selector; + this.context = selector.context; + } + + return jQuery.makeArray( selector, this ); + }; + +// Give the init function the jQuery prototype for later instantiation +init.prototype = jQuery.fn; + +// Initialize central reference +rootjQuery = jQuery( document ); + + +var rparentsprev = /^(?:parents|prev(?:Until|All))/, + // methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; + +jQuery.extend({ + dir: function( elem, dir, until ) { + var matched = [], + cur = elem[ dir ]; + + while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) { + if ( cur.nodeType === 1 ) { + matched.push( cur ); + } + cur = cur[dir]; + } + return matched; + }, + + sibling: function( n, elem ) { + var r = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + r.push( n ); + } + } + + return r; + } +}); + +jQuery.fn.extend({ + has: function( target ) { + var i, + targets = jQuery( target, this ), + len = targets.length; + + return this.filter(function() { + for ( i = 0; i < len; i++ ) { + if ( jQuery.contains( this, targets[i] ) ) { + return true; + } + } + }); + }, + + closest: function( selectors, context ) { + var cur, + i = 0, + l = this.length, + matched = [], + pos = rneedsContext.test( selectors ) || typeof selectors !== "string" ? + jQuery( selectors, context || this.context ) : + 0; + + for ( ; i < l; i++ ) { + for ( cur = this[i]; cur && cur !== context; cur = cur.parentNode ) { + // Always skip document fragments + if ( cur.nodeType < 11 && (pos ? + pos.index(cur) > -1 : + + // Don't pass non-elements to Sizzle + cur.nodeType === 1 && + jQuery.find.matchesSelector(cur, selectors)) ) { + + matched.push( cur ); + break; + } + } + } + + return this.pushStack( matched.length > 1 ? jQuery.unique( matched ) : matched ); + }, + + // Determine the position of an element within + // the matched set of elements + index: function( elem ) { + + // No argument, return index in parent + if ( !elem ) { + return ( this[0] && this[0].parentNode ) ? this.first().prevAll().length : -1; + } + + // index in selector + if ( typeof elem === "string" ) { + return jQuery.inArray( this[0], jQuery( elem ) ); + } + + // Locate the position of the desired element + return jQuery.inArray( + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[0] : elem, this ); + }, + + add: function( selector, context ) { + return this.pushStack( + jQuery.unique( + jQuery.merge( this.get(), jQuery( selector, context ) ) + ) + ); + }, + + addBack: function( selector ) { + return this.add( selector == null ? + this.prevObject : this.prevObject.filter(selector) + ); + } +}); + +function sibling( cur, dir ) { + do { + cur = cur[ dir ]; + } while ( cur && cur.nodeType !== 1 ); + + return cur; +} + +jQuery.each({ + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return jQuery.dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, i, until ) { + return jQuery.dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return sibling( elem, "nextSibling" ); + }, + prev: function( elem ) { + return sibling( elem, "previousSibling" ); + }, + nextAll: function( elem ) { + return jQuery.dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return jQuery.dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, i, until ) { + return jQuery.dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, i, until ) { + return jQuery.dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem ); + }, + children: function( elem ) { + return jQuery.sibling( elem.firstChild ); + }, + contents: function( elem ) { + return jQuery.nodeName( elem, "iframe" ) ? + elem.contentDocument || elem.contentWindow.document : + jQuery.merge( [], elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var ret = jQuery.map( this, fn, until ); + + if ( name.slice( -5 ) !== "Until" ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + ret = jQuery.filter( selector, ret ); + } + + if ( this.length > 1 ) { + // Remove duplicates + if ( !guaranteedUnique[ name ] ) { + ret = jQuery.unique( ret ); + } + + // Reverse order for parents* and prev-derivatives + if ( rparentsprev.test( name ) ) { + ret = ret.reverse(); + } + } + + return this.pushStack( ret ); + }; +}); +var rnotwhite = (/\S+/g); + + + +// String to Object options format cache +var optionsCache = {}; + +// Convert String-formatted options into Object-formatted ones and store in cache +function createOptions( options ) { + var object = optionsCache[ options ] = {}; + jQuery.each( options.match( rnotwhite ) || [], function( _, flag ) { + object[ flag ] = true; + }); + return object; +} + +/* + * Create a callback list using the following parameters: + * + * options: an optional list of space-separated options that will change how + * the callback list behaves or a more traditional option object + * + * By default a callback list will act like an event callback list and can be + * "fired" multiple times. + * + * Possible options: + * + * once: will ensure the callback list can only be fired once (like a Deferred) + * + * memory: will keep track of previous values and will call any callback added + * after the list has been fired right away with the latest "memorized" + * values (like a Deferred) + * + * unique: will ensure a callback can only be added once (no duplicate in the list) + * + * stopOnFalse: interrupt callings when a callback returns false + * + */ +jQuery.Callbacks = function( options ) { + + // Convert options from String-formatted to Object-formatted if needed + // (we check in cache first) + options = typeof options === "string" ? + ( optionsCache[ options ] || createOptions( options ) ) : + jQuery.extend( {}, options ); + + var // Flag to know if list is currently firing + firing, + // Last fire value (for non-forgettable lists) + memory, + // Flag to know if list was already fired + fired, + // End of the loop when firing + firingLength, + // Index of currently firing callback (modified by remove if needed) + firingIndex, + // First callback to fire (used internally by add and fireWith) + firingStart, + // Actual callback list + list = [], + // Stack of fire calls for repeatable lists + stack = !options.once && [], + // Fire callbacks + fire = function( data ) { + memory = options.memory && data; + fired = true; + firingIndex = firingStart || 0; + firingStart = 0; + firingLength = list.length; + firing = true; + for ( ; list && firingIndex < firingLength; firingIndex++ ) { + if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) { + memory = false; // To prevent further calls using add + break; + } + } + firing = false; + if ( list ) { + if ( stack ) { + if ( stack.length ) { + fire( stack.shift() ); + } + } else if ( memory ) { + list = []; + } else { + self.disable(); + } + } + }, + // Actual Callbacks object + self = { + // Add a callback or a collection of callbacks to the list + add: function() { + if ( list ) { + // First, we save the current length + var start = list.length; + (function add( args ) { + jQuery.each( args, function( _, arg ) { + var type = jQuery.type( arg ); + if ( type === "function" ) { + if ( !options.unique || !self.has( arg ) ) { + list.push( arg ); + } + } else if ( arg && arg.length && type !== "string" ) { + // Inspect recursively + add( arg ); + } + }); + })( arguments ); + // Do we need to add the callbacks to the + // current firing batch? + if ( firing ) { + firingLength = list.length; + // With memory, if we're not firing then + // we should call right away + } else if ( memory ) { + firingStart = start; + fire( memory ); + } + } + return this; + }, + // Remove a callback from the list + remove: function() { + if ( list ) { + jQuery.each( arguments, function( _, arg ) { + var index; + while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { + list.splice( index, 1 ); + // Handle firing indexes + if ( firing ) { + if ( index <= firingLength ) { + firingLength--; + } + if ( index <= firingIndex ) { + firingIndex--; + } + } + } + }); + } + return this; + }, + // Check if a given callback is in the list. + // If no argument is given, return whether or not list has callbacks attached. + has: function( fn ) { + return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length ); + }, + // Remove all callbacks from the list + empty: function() { + list = []; + firingLength = 0; + return this; + }, + // Have the list do nothing anymore + disable: function() { + list = stack = memory = undefined; + return this; + }, + // Is it disabled? + disabled: function() { + return !list; + }, + // Lock the list in its current state + lock: function() { + stack = undefined; + if ( !memory ) { + self.disable(); + } + return this; + }, + // Is it locked? + locked: function() { + return !stack; + }, + // Call all callbacks with the given context and arguments + fireWith: function( context, args ) { + if ( list && ( !fired || stack ) ) { + args = args || []; + args = [ context, args.slice ? args.slice() : args ]; + if ( firing ) { + stack.push( args ); + } else { + fire( args ); + } + } + return this; + }, + // Call all the callbacks with the given arguments + fire: function() { + self.fireWith( this, arguments ); + return this; + }, + // To know if the callbacks have already been called at least once + fired: function() { + return !!fired; + } + }; + + return self; +}; + + +jQuery.extend({ + + Deferred: function( func ) { + var tuples = [ + // action, add listener, listener list, final state + [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ], + [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ], + [ "notify", "progress", jQuery.Callbacks("memory") ] + ], + state = "pending", + promise = { + state: function() { + return state; + }, + always: function() { + deferred.done( arguments ).fail( arguments ); + return this; + }, + then: function( /* fnDone, fnFail, fnProgress */ ) { + var fns = arguments; + return jQuery.Deferred(function( newDefer ) { + jQuery.each( tuples, function( i, tuple ) { + var fn = jQuery.isFunction( fns[ i ] ) && fns[ i ]; + // deferred[ done | fail | progress ] for forwarding actions to newDefer + deferred[ tuple[1] ](function() { + var returned = fn && fn.apply( this, arguments ); + if ( returned && jQuery.isFunction( returned.promise ) ) { + returned.promise() + .done( newDefer.resolve ) + .fail( newDefer.reject ) + .progress( newDefer.notify ); + } else { + newDefer[ tuple[ 0 ] + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments ); + } + }); + }); + fns = null; + }).promise(); + }, + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj ) { + return obj != null ? jQuery.extend( obj, promise ) : promise; + } + }, + deferred = {}; + + // Keep pipe for back-compat + promise.pipe = promise.then; + + // Add list-specific methods + jQuery.each( tuples, function( i, tuple ) { + var list = tuple[ 2 ], + stateString = tuple[ 3 ]; + + // promise[ done | fail | progress ] = list.add + promise[ tuple[1] ] = list.add; + + // Handle state + if ( stateString ) { + list.add(function() { + // state = [ resolved | rejected ] + state = stateString; + + // [ reject_list | resolve_list ].disable; progress_list.lock + }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock ); + } + + // deferred[ resolve | reject | notify ] + deferred[ tuple[0] ] = function() { + deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments ); + return this; + }; + deferred[ tuple[0] + "With" ] = list.fireWith; + }); + + // Make the deferred a promise + promise.promise( deferred ); + + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); + } + + // All done! + return deferred; + }, + + // Deferred helper + when: function( subordinate /* , ..., subordinateN */ ) { + var i = 0, + resolveValues = slice.call( arguments ), + length = resolveValues.length, + + // the count of uncompleted subordinates + remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0, + + // the master Deferred. If resolveValues consist of only a single Deferred, just use that. + deferred = remaining === 1 ? subordinate : jQuery.Deferred(), + + // Update function for both resolve and progress values + updateFunc = function( i, contexts, values ) { + return function( value ) { + contexts[ i ] = this; + values[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; + if ( values === progressValues ) { + deferred.notifyWith( contexts, values ); + + } else if ( !(--remaining) ) { + deferred.resolveWith( contexts, values ); + } + }; + }, + + progressValues, progressContexts, resolveContexts; + + // add listeners to Deferred subordinates; treat others as resolved + if ( length > 1 ) { + progressValues = new Array( length ); + progressContexts = new Array( length ); + resolveContexts = new Array( length ); + for ( ; i < length; i++ ) { + if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) { + resolveValues[ i ].promise() + .done( updateFunc( i, resolveContexts, resolveValues ) ) + .fail( deferred.reject ) + .progress( updateFunc( i, progressContexts, progressValues ) ); + } else { + --remaining; + } + } + } + + // if we're not waiting on anything, resolve the master + if ( !remaining ) { + deferred.resolveWith( resolveContexts, resolveValues ); + } + + return deferred.promise(); + } +}); + + +// The deferred used on DOM ready +var readyList; + +jQuery.fn.ready = function( fn ) { + // Add the callback + jQuery.ready.promise().done( fn ); + + return this; +}; + +jQuery.extend({ + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // A counter to track how many items to wait for before + // the ready event fires. See #6781 + readyWait: 1, + + // Hold (or release) the ready event + holdReady: function( hold ) { + if ( hold ) { + jQuery.readyWait++; + } else { + jQuery.ready( true ); + } + }, + + // Handle when the DOM is ready + ready: function( wait ) { + + // Abort if there are pending holds or we're already ready + if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { + return; + } + + // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). + if ( !document.body ) { + return setTimeout( jQuery.ready ); + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If a normal DOM Ready event fired, decrement, and wait if need be + if ( wait !== true && --jQuery.readyWait > 0 ) { + return; + } + + // If there are functions bound, to execute + readyList.resolveWith( document, [ jQuery ] ); + + // Trigger any bound ready events + if ( jQuery.fn.triggerHandler ) { + jQuery( document ).triggerHandler( "ready" ); + jQuery( document ).off( "ready" ); + } + } +}); + +/** + * Clean-up method for dom ready events + */ +function detach() { + if ( document.addEventListener ) { + document.removeEventListener( "DOMContentLoaded", completed, false ); + window.removeEventListener( "load", completed, false ); + + } else { + document.detachEvent( "onreadystatechange", completed ); + window.detachEvent( "onload", completed ); + } +} + +/** + * The ready event handler and self cleanup method + */ +function completed() { + // readyState === "complete" is good enough for us to call the dom ready in oldIE + if ( document.addEventListener || event.type === "load" || document.readyState === "complete" ) { + detach(); + jQuery.ready(); + } +} + +jQuery.ready.promise = function( obj ) { + if ( !readyList ) { + + readyList = jQuery.Deferred(); + + // Catch cases where $(document).ready() is called after the browser event has already occurred. + // we once tried to use readyState "interactive" here, but it caused issues like the one + // discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15 + if ( document.readyState === "complete" ) { + // Handle it asynchronously to allow scripts the opportunity to delay ready + setTimeout( jQuery.ready ); + + // Standards-based browsers support DOMContentLoaded + } else if ( document.addEventListener ) { + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", completed, false ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", completed, false ); + + // If IE event model is used + } else { + // Ensure firing before onload, maybe late but safe also for iframes + document.attachEvent( "onreadystatechange", completed ); + + // A fallback to window.onload, that will always work + window.attachEvent( "onload", completed ); + + // If IE and not a frame + // continually check to see if the document is ready + var top = false; + + try { + top = window.frameElement == null && document.documentElement; + } catch(e) {} + + if ( top && top.doScroll ) { + (function doScrollCheck() { + if ( !jQuery.isReady ) { + + try { + // Use the trick by Diego Perini + // http://javascript.nwbox.com/IEContentLoaded/ + top.doScroll("left"); + } catch(e) { + return setTimeout( doScrollCheck, 50 ); + } + + // detach all dom ready events + detach(); + + // and execute any waiting functions + jQuery.ready(); + } + })(); + } + } + } + return readyList.promise( obj ); +}; + + +var strundefined = typeof undefined; + + + +// Support: IE<9 +// Iteration over object's inherited properties before its own +var i; +for ( i in jQuery( support ) ) { + break; +} +support.ownLast = i !== "0"; + +// Note: most support tests are defined in their respective modules. +// false until the test is run +support.inlineBlockNeedsLayout = false; + +// Execute ASAP in case we need to set body.style.zoom +jQuery(function() { + // Minified: var a,b,c,d + var val, div, body, container; + + body = document.getElementsByTagName( "body" )[ 0 ]; + if ( !body || !body.style ) { + // Return for frameset docs that don't have a body + return; + } + + // Setup + div = document.createElement( "div" ); + container = document.createElement( "div" ); + container.style.cssText = "position:absolute;border:0;width:0;height:0;top:0;left:-9999px"; + body.appendChild( container ).appendChild( div ); + + if ( typeof div.style.zoom !== strundefined ) { + // Support: IE<8 + // Check if natively block-level elements act like inline-block + // elements when setting their display to 'inline' and giving + // them layout + div.style.cssText = "display:inline;margin:0;border:0;padding:1px;width:1px;zoom:1"; + + support.inlineBlockNeedsLayout = val = div.offsetWidth === 3; + if ( val ) { + // Prevent IE 6 from affecting layout for positioned elements #11048 + // Prevent IE from shrinking the body in IE 7 mode #12869 + // Support: IE<8 + body.style.zoom = 1; + } + } + + body.removeChild( container ); +}); + + + + +(function() { + var div = document.createElement( "div" ); + + // Execute the test only if not already executed in another module. + if (support.deleteExpando == null) { + // Support: IE<9 + support.deleteExpando = true; + try { + delete div.test; + } catch( e ) { + support.deleteExpando = false; + } + } + + // Null elements to avoid leaks in IE. + div = null; +})(); + + +/** + * Determines whether an object can have data + */ +jQuery.acceptData = function( elem ) { + var noData = jQuery.noData[ (elem.nodeName + " ").toLowerCase() ], + nodeType = +elem.nodeType || 1; + + // Do not set data on non-element DOM nodes because it will not be cleared (#8335). + return nodeType !== 1 && nodeType !== 9 ? + false : + + // Nodes accept data unless otherwise specified; rejection can be conditional + !noData || noData !== true && elem.getAttribute("classid") === noData; +}; + + +var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, + rmultiDash = /([A-Z])/g; + +function dataAttr( elem, key, data ) { + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + + var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase(); + + data = elem.getAttribute( name ); + + if ( typeof data === "string" ) { + try { + data = data === "true" ? true : + data === "false" ? false : + data === "null" ? null : + // Only convert to a number if it doesn't change the string + +data + "" === data ? +data : + rbrace.test( data ) ? jQuery.parseJSON( data ) : + data; + } catch( e ) {} + + // Make sure we set the data so it isn't changed later + jQuery.data( elem, key, data ); + + } else { + data = undefined; + } + } + + return data; +} + +// checks a cache object for emptiness +function isEmptyDataObject( obj ) { + var name; + for ( name in obj ) { + + // if the public data object is empty, the private is still empty + if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) { + continue; + } + if ( name !== "toJSON" ) { + return false; + } + } + + return true; +} + +function internalData( elem, name, data, pvt /* Internal Use Only */ ) { + if ( !jQuery.acceptData( elem ) ) { + return; + } + + var ret, thisCache, + internalKey = jQuery.expando, + + // We have to handle DOM nodes and JS objects differently because IE6-7 + // can't GC object references properly across the DOM-JS boundary + isNode = elem.nodeType, + + // Only DOM nodes need the global jQuery cache; JS object data is + // attached directly to the object so GC can occur automatically + cache = isNode ? jQuery.cache : elem, + + // Only defining an ID for JS objects if its cache already exists allows + // the code to shortcut on the same path as a DOM node with no cache + id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey; + + // Avoid doing any more work than we need to when trying to get data on an + // object that has no data at all + if ( (!id || !cache[id] || (!pvt && !cache[id].data)) && data === undefined && typeof name === "string" ) { + return; + } + + if ( !id ) { + // Only DOM nodes need a new unique ID for each element since their data + // ends up in the global cache + if ( isNode ) { + id = elem[ internalKey ] = deletedIds.pop() || jQuery.guid++; + } else { + id = internalKey; + } + } + + if ( !cache[ id ] ) { + // Avoid exposing jQuery metadata on plain JS objects when the object + // is serialized using JSON.stringify + cache[ id ] = isNode ? {} : { toJSON: jQuery.noop }; + } + + // An object can be passed to jQuery.data instead of a key/value pair; this gets + // shallow copied over onto the existing cache + if ( typeof name === "object" || typeof name === "function" ) { + if ( pvt ) { + cache[ id ] = jQuery.extend( cache[ id ], name ); + } else { + cache[ id ].data = jQuery.extend( cache[ id ].data, name ); + } + } + + thisCache = cache[ id ]; + + // jQuery data() is stored in a separate object inside the object's internal data + // cache in order to avoid key collisions between internal data and user-defined + // data. + if ( !pvt ) { + if ( !thisCache.data ) { + thisCache.data = {}; + } + + thisCache = thisCache.data; + } + + if ( data !== undefined ) { + thisCache[ jQuery.camelCase( name ) ] = data; + } + + // Check for both converted-to-camel and non-converted data property names + // If a data property was specified + if ( typeof name === "string" ) { + + // First Try to find as-is property data + ret = thisCache[ name ]; + + // Test for null|undefined property data + if ( ret == null ) { + + // Try to find the camelCased property + ret = thisCache[ jQuery.camelCase( name ) ]; + } + } else { + ret = thisCache; + } + + return ret; +} + +function internalRemoveData( elem, name, pvt ) { + if ( !jQuery.acceptData( elem ) ) { + return; + } + + var thisCache, i, + isNode = elem.nodeType, + + // See jQuery.data for more information + cache = isNode ? jQuery.cache : elem, + id = isNode ? elem[ jQuery.expando ] : jQuery.expando; + + // If there is already no cache entry for this object, there is no + // purpose in continuing + if ( !cache[ id ] ) { + return; + } + + if ( name ) { + + thisCache = pvt ? cache[ id ] : cache[ id ].data; + + if ( thisCache ) { + + // Support array or space separated string names for data keys + if ( !jQuery.isArray( name ) ) { + + // try the string as a key before any manipulation + if ( name in thisCache ) { + name = [ name ]; + } else { + + // split the camel cased version by spaces unless a key with the spaces exists + name = jQuery.camelCase( name ); + if ( name in thisCache ) { + name = [ name ]; + } else { + name = name.split(" "); + } + } + } else { + // If "name" is an array of keys... + // When data is initially created, via ("key", "val") signature, + // keys will be converted to camelCase. + // Since there is no way to tell _how_ a key was added, remove + // both plain key and camelCase key. #12786 + // This will only penalize the array argument path. + name = name.concat( jQuery.map( name, jQuery.camelCase ) ); + } + + i = name.length; + while ( i-- ) { + delete thisCache[ name[i] ]; + } + + // If there is no data left in the cache, we want to continue + // and let the cache object itself get destroyed + if ( pvt ? !isEmptyDataObject(thisCache) : !jQuery.isEmptyObject(thisCache) ) { + return; + } + } + } + + // See jQuery.data for more information + if ( !pvt ) { + delete cache[ id ].data; + + // Don't destroy the parent cache unless the internal data object + // had been the only thing left in it + if ( !isEmptyDataObject( cache[ id ] ) ) { + return; + } + } + + // Destroy the cache + if ( isNode ) { + jQuery.cleanData( [ elem ], true ); + + // Use delete when supported for expandos or `cache` is not a window per isWindow (#10080) + /* jshint eqeqeq: false */ + } else if ( support.deleteExpando || cache != cache.window ) { + /* jshint eqeqeq: true */ + delete cache[ id ]; + + // When all else fails, null + } else { + cache[ id ] = null; + } +} + +jQuery.extend({ + cache: {}, + + // The following elements (space-suffixed to avoid Object.prototype collisions) + // throw uncatchable exceptions if you attempt to set expando properties + noData: { + "applet ": true, + "embed ": true, + // ...but Flash objects (which have this classid) *can* handle expandos + "object ": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" + }, + + hasData: function( elem ) { + elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ]; + return !!elem && !isEmptyDataObject( elem ); + }, + + data: function( elem, name, data ) { + return internalData( elem, name, data ); + }, + + removeData: function( elem, name ) { + return internalRemoveData( elem, name ); + }, + + // For internal use only. + _data: function( elem, name, data ) { + return internalData( elem, name, data, true ); + }, + + _removeData: function( elem, name ) { + return internalRemoveData( elem, name, true ); + } +}); + +jQuery.fn.extend({ + data: function( key, value ) { + var i, name, data, + elem = this[0], + attrs = elem && elem.attributes; + + // Special expections of .data basically thwart jQuery.access, + // so implement the relevant behavior ourselves + + // Gets all values + if ( key === undefined ) { + if ( this.length ) { + data = jQuery.data( elem ); + + if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) { + i = attrs.length; + while ( i-- ) { + + // Support: IE11+ + // The attrs elements can be null (#14894) + if ( attrs[ i ] ) { + name = attrs[ i ].name; + if ( name.indexOf( "data-" ) === 0 ) { + name = jQuery.camelCase( name.slice(5) ); + dataAttr( elem, name, data[ name ] ); + } + } + } + jQuery._data( elem, "parsedAttrs", true ); + } + } + + return data; + } + + // Sets multiple values + if ( typeof key === "object" ) { + return this.each(function() { + jQuery.data( this, key ); + }); + } + + return arguments.length > 1 ? + + // Sets one value + this.each(function() { + jQuery.data( this, key, value ); + }) : + + // Gets one value + // Try to fetch any internally stored data first + elem ? dataAttr( elem, key, jQuery.data( elem, key ) ) : undefined; + }, + + removeData: function( key ) { + return this.each(function() { + jQuery.removeData( this, key ); + }); + } +}); + + +jQuery.extend({ + queue: function( elem, type, data ) { + var queue; + + if ( elem ) { + type = ( type || "fx" ) + "queue"; + queue = jQuery._data( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( data ) { + if ( !queue || jQuery.isArray(data) ) { + queue = jQuery._data( elem, type, jQuery.makeArray(data) ); + } else { + queue.push( data ); + } + } + return queue || []; + } + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), + startLength = queue.length, + fn = queue.shift(), + hooks = jQuery._queueHooks( elem, type ), + next = function() { + jQuery.dequeue( elem, type ); + }; + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + startLength--; + } + + if ( fn ) { + + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift( "inprogress" ); + } + + // clear up the last queue stop function + delete hooks.stop; + fn.call( elem, next, hooks ); + } + + if ( !startLength && hooks ) { + hooks.empty.fire(); + } + }, + + // not intended for public consumption - generates a queueHooks object, or returns the current one + _queueHooks: function( elem, type ) { + var key = type + "queueHooks"; + return jQuery._data( elem, key ) || jQuery._data( elem, key, { + empty: jQuery.Callbacks("once memory").add(function() { + jQuery._removeData( elem, type + "queue" ); + jQuery._removeData( elem, key ); + }) + }); + } +}); + +jQuery.fn.extend({ + queue: function( type, data ) { + var setter = 2; + + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + setter--; + } + + if ( arguments.length < setter ) { + return jQuery.queue( this[0], type ); + } + + return data === undefined ? + this : + this.each(function() { + var queue = jQuery.queue( this, type, data ); + + // ensure a hooks for this queue + jQuery._queueHooks( this, type ); + + if ( type === "fx" && queue[0] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + }); + }, + dequeue: function( type ) { + return this.each(function() { + jQuery.dequeue( this, type ); + }); + }, + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + }, + // Get a promise resolved when queues of a certain type + // are emptied (fx is the type by default) + promise: function( type, obj ) { + var tmp, + count = 1, + defer = jQuery.Deferred(), + elements = this, + i = this.length, + resolve = function() { + if ( !( --count ) ) { + defer.resolveWith( elements, [ elements ] ); + } + }; + + if ( typeof type !== "string" ) { + obj = type; + type = undefined; + } + type = type || "fx"; + + while ( i-- ) { + tmp = jQuery._data( elements[ i ], type + "queueHooks" ); + if ( tmp && tmp.empty ) { + count++; + tmp.empty.add( resolve ); + } + } + resolve(); + return defer.promise( obj ); + } +}); +var pnum = (/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/).source; + +var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; + +var isHidden = function( elem, el ) { + // isHidden might be called from jQuery#filter function; + // in that case, element will be second argument + elem = el || elem; + return jQuery.css( elem, "display" ) === "none" || !jQuery.contains( elem.ownerDocument, elem ); + }; + + + +// Multifunctional method to get and set values of a collection +// The value/s can optionally be executed if it's a function +var access = jQuery.access = function( elems, fn, key, value, chainable, emptyGet, raw ) { + var i = 0, + length = elems.length, + bulk = key == null; + + // Sets many values + if ( jQuery.type( key ) === "object" ) { + chainable = true; + for ( i in key ) { + jQuery.access( elems, fn, i, key[i], true, emptyGet, raw ); + } + + // Sets one value + } else if ( value !== undefined ) { + chainable = true; + + if ( !jQuery.isFunction( value ) ) { + raw = true; + } + + if ( bulk ) { + // Bulk operations run against the entire set + if ( raw ) { + fn.call( elems, value ); + fn = null; + + // ...except when executing function values + } else { + bulk = fn; + fn = function( elem, key, value ) { + return bulk.call( jQuery( elem ), value ); + }; + } + } + + if ( fn ) { + for ( ; i < length; i++ ) { + fn( elems[i], key, raw ? value : value.call( elems[i], i, fn( elems[i], key ) ) ); + } + } + } + + return chainable ? + elems : + + // Gets + bulk ? + fn.call( elems ) : + length ? fn( elems[0], key ) : emptyGet; +}; +var rcheckableType = (/^(?:checkbox|radio)$/i); + + + +(function() { + // Minified: var a,b,c + var input = document.createElement( "input" ), + div = document.createElement( "div" ), + fragment = document.createDocumentFragment(); + + // Setup + div.innerHTML = "
a"; + + // IE strips leading whitespace when .innerHTML is used + support.leadingWhitespace = div.firstChild.nodeType === 3; + + // Make sure that tbody elements aren't automatically inserted + // IE will insert them into empty tables + support.tbody = !div.getElementsByTagName( "tbody" ).length; + + // Make sure that link elements get serialized correctly by innerHTML + // This requires a wrapper element in IE + support.htmlSerialize = !!div.getElementsByTagName( "link" ).length; + + // Makes sure cloning an html5 element does not cause problems + // Where outerHTML is undefined, this still works + support.html5Clone = + document.createElement( "nav" ).cloneNode( true ).outerHTML !== "<:nav>"; + + // Check if a disconnected checkbox will retain its checked + // value of true after appended to the DOM (IE6/7) + input.type = "checkbox"; + input.checked = true; + fragment.appendChild( input ); + support.appendChecked = input.checked; + + // Make sure textarea (and checkbox) defaultValue is properly cloned + // Support: IE6-IE11+ + div.innerHTML = ""; + support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; + + // #11217 - WebKit loses check when the name is after the checked attribute + fragment.appendChild( div ); + div.innerHTML = ""; + + // Support: Safari 5.1, iOS 5.1, Android 4.x, Android 2.3 + // old WebKit doesn't clone checked state correctly in fragments + support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; + + // Support: IE<9 + // Opera does not clone events (and typeof div.attachEvent === undefined). + // IE9-10 clones events bound via attachEvent, but they don't trigger with .click() + support.noCloneEvent = true; + if ( div.attachEvent ) { + div.attachEvent( "onclick", function() { + support.noCloneEvent = false; + }); + + div.cloneNode( true ).click(); + } + + // Execute the test only if not already executed in another module. + if (support.deleteExpando == null) { + // Support: IE<9 + support.deleteExpando = true; + try { + delete div.test; + } catch( e ) { + support.deleteExpando = false; + } + } +})(); + + +(function() { + var i, eventName, + div = document.createElement( "div" ); + + // Support: IE<9 (lack submit/change bubble), Firefox 23+ (lack focusin event) + for ( i in { submit: true, change: true, focusin: true }) { + eventName = "on" + i; + + if ( !(support[ i + "Bubbles" ] = eventName in window) ) { + // Beware of CSP restrictions (https://developer.mozilla.org/en/Security/CSP) + div.setAttribute( eventName, "t" ); + support[ i + "Bubbles" ] = div.attributes[ eventName ].expando === false; + } + } + + // Null elements to avoid leaks in IE. + div = null; +})(); + + +var rformElems = /^(?:input|select|textarea)$/i, + rkeyEvent = /^key/, + rmouseEvent = /^(?:mouse|pointer|contextmenu)|click/, + rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, + rtypenamespace = /^([^.]*)(?:\.(.+)|)$/; + +function returnTrue() { + return true; +} + +function returnFalse() { + return false; +} + +function safeActiveElement() { + try { + return document.activeElement; + } catch ( err ) { } +} + +/* + * Helper functions for managing events -- not part of the public interface. + * Props to Dean Edwards' addEvent library for many of the ideas. + */ +jQuery.event = { + + global: {}, + + add: function( elem, types, handler, data, selector ) { + var tmp, events, t, handleObjIn, + special, eventHandle, handleObj, + handlers, type, namespaces, origType, + elemData = jQuery._data( elem ); + + // Don't attach events to noData or text/comment nodes (but allow plain objects) + if ( !elemData ) { + return; + } + + // Caller can pass in an object of custom data in lieu of the handler + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + selector = handleObjIn.selector; + } + + // Make sure that the handler has a unique ID, used to find/remove it later + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure and main handler, if this is the first + if ( !(events = elemData.events) ) { + events = elemData.events = {}; + } + if ( !(eventHandle = elemData.handle) ) { + eventHandle = elemData.handle = function( e ) { + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded + return typeof jQuery !== strundefined && (!e || jQuery.event.triggered !== e.type) ? + jQuery.event.dispatch.apply( eventHandle.elem, arguments ) : + undefined; + }; + // Add elem as a property of the handle fn to prevent a memory leak with IE non-native events + eventHandle.elem = elem; + } + + // Handle multiple events separated by a space + types = ( types || "" ).match( rnotwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[t] ) || []; + type = origType = tmp[1]; + namespaces = ( tmp[2] || "" ).split( "." ).sort(); + + // There *must* be a type, no attaching namespace-only handlers + if ( !type ) { + continue; + } + + // If event changes its type, use the special event handlers for the changed type + special = jQuery.event.special[ type ] || {}; + + // If selector defined, determine special event api type, otherwise given type + type = ( selector ? special.delegateType : special.bindType ) || type; + + // Update special based on newly reset type + special = jQuery.event.special[ type ] || {}; + + // handleObj is passed to all event handlers + handleObj = jQuery.extend({ + type: type, + origType: origType, + data: data, + handler: handler, + guid: handler.guid, + selector: selector, + needsContext: selector && jQuery.expr.match.needsContext.test( selector ), + namespace: namespaces.join(".") + }, handleObjIn ); + + // Init the event handler queue if we're the first + if ( !(handlers = events[ type ]) ) { + handlers = events[ type ] = []; + handlers.delegateCount = 0; + + // Only use addEventListener/attachEvent if the special events handler returns false + if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + // Bind the global event handler to the element + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle, false ); + + } else if ( elem.attachEvent ) { + elem.attachEvent( "on" + type, eventHandle ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add to the element's handler list, delegates in front + if ( selector ) { + handlers.splice( handlers.delegateCount++, 0, handleObj ); + } else { + handlers.push( handleObj ); + } + + // Keep track of which events have ever been used, for event optimization + jQuery.event.global[ type ] = true; + } + + // Nullify elem to prevent memory leaks in IE + elem = null; + }, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, selector, mappedTypes ) { + var j, handleObj, tmp, + origCount, t, events, + special, handlers, type, + namespaces, origType, + elemData = jQuery.hasData( elem ) && jQuery._data( elem ); + + if ( !elemData || !(events = elemData.events) ) { + return; + } + + // Once for each type.namespace in types; type may be omitted + types = ( types || "" ).match( rnotwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[t] ) || []; + type = origType = tmp[1]; + namespaces = ( tmp[2] || "" ).split( "." ).sort(); + + // Unbind all events (on this namespace, if provided) for the element + if ( !type ) { + for ( type in events ) { + jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); + } + continue; + } + + special = jQuery.event.special[ type ] || {}; + type = ( selector ? special.delegateType : special.bindType ) || type; + handlers = events[ type ] || []; + tmp = tmp[2] && new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ); + + // Remove matching events + origCount = j = handlers.length; + while ( j-- ) { + handleObj = handlers[ j ]; + + if ( ( mappedTypes || origType === handleObj.origType ) && + ( !handler || handler.guid === handleObj.guid ) && + ( !tmp || tmp.test( handleObj.namespace ) ) && + ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) { + handlers.splice( j, 1 ); + + if ( handleObj.selector ) { + handlers.delegateCount--; + } + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + } + + // Remove generic event handler if we removed something and no more handlers exist + // (avoids potential for endless recursion during removal of special event handlers) + if ( origCount && !handlers.length ) { + if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) { + jQuery.removeEvent( elem, type, elemData.handle ); + } + + delete events[ type ]; + } + } + + // Remove the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + delete elemData.handle; + + // removeData also checks for emptiness and clears the expando if empty + // so use it instead of delete + jQuery._removeData( elem, "events" ); + } + }, + + trigger: function( event, data, elem, onlyHandlers ) { + var handle, ontype, cur, + bubbleType, special, tmp, i, + eventPath = [ elem || document ], + type = hasOwn.call( event, "type" ) ? event.type : event, + namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split(".") : []; + + cur = tmp = elem = elem || document; + + // Don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + // focus/blur morphs to focusin/out; ensure we're not firing them right now + if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { + return; + } + + if ( type.indexOf(".") >= 0 ) { + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split("."); + type = namespaces.shift(); + namespaces.sort(); + } + ontype = type.indexOf(":") < 0 && "on" + type; + + // Caller can pass in a jQuery.Event object, Object, or just an event type string + event = event[ jQuery.expando ] ? + event : + new jQuery.Event( type, typeof event === "object" && event ); + + // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) + event.isTrigger = onlyHandlers ? 2 : 3; + event.namespace = namespaces.join("."); + event.namespace_re = event.namespace ? + new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ) : + null; + + // Clean up the event in case it is being reused + event.result = undefined; + if ( !event.target ) { + event.target = elem; + } + + // Clone any incoming data and prepend the event, creating the handler arg list + data = data == null ? + [ event ] : + jQuery.makeArray( data, [ event ] ); + + // Allow special events to draw outside the lines + special = jQuery.event.special[ type ] || {}; + if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { + return; + } + + // Determine event propagation path in advance, per W3C events spec (#9951) + // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) + if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) { + + bubbleType = special.delegateType || type; + if ( !rfocusMorph.test( bubbleType + type ) ) { + cur = cur.parentNode; + } + for ( ; cur; cur = cur.parentNode ) { + eventPath.push( cur ); + tmp = cur; + } + + // Only add window if we got to document (e.g., not plain obj or detached DOM) + if ( tmp === (elem.ownerDocument || document) ) { + eventPath.push( tmp.defaultView || tmp.parentWindow || window ); + } + } + + // Fire handlers on the event path + i = 0; + while ( (cur = eventPath[i++]) && !event.isPropagationStopped() ) { + + event.type = i > 1 ? + bubbleType : + special.bindType || type; + + // jQuery handler + handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" ); + if ( handle ) { + handle.apply( cur, data ); + } + + // Native handler + handle = ontype && cur[ ontype ]; + if ( handle && handle.apply && jQuery.acceptData( cur ) ) { + event.result = handle.apply( cur, data ); + if ( event.result === false ) { + event.preventDefault(); + } + } + } + event.type = type; + + // If nobody prevented the default action, do it now + if ( !onlyHandlers && !event.isDefaultPrevented() ) { + + if ( (!special._default || special._default.apply( eventPath.pop(), data ) === false) && + jQuery.acceptData( elem ) ) { + + // Call a native DOM method on the target with the same name name as the event. + // Can't use an .isFunction() check here because IE6/7 fails that test. + // Don't do default actions on window, that's where global variables be (#6170) + if ( ontype && elem[ type ] && !jQuery.isWindow( elem ) ) { + + // Don't re-trigger an onFOO event when we call its FOO() method + tmp = elem[ ontype ]; + + if ( tmp ) { + elem[ ontype ] = null; + } + + // Prevent re-triggering of the same event, since we already bubbled it above + jQuery.event.triggered = type; + try { + elem[ type ](); + } catch ( e ) { + // IE<9 dies on focus/blur to hidden element (#1486,#12518) + // only reproducible on winXP IE8 native, not IE9 in IE8 mode + } + jQuery.event.triggered = undefined; + + if ( tmp ) { + elem[ ontype ] = tmp; + } + } + } + } + + return event.result; + }, + + dispatch: function( event ) { + + // Make a writable jQuery.Event from the native event object + event = jQuery.event.fix( event ); + + var i, ret, handleObj, matched, j, + handlerQueue = [], + args = slice.call( arguments ), + handlers = ( jQuery._data( this, "events" ) || {} )[ event.type ] || [], + special = jQuery.event.special[ event.type ] || {}; + + // Use the fix-ed jQuery.Event rather than the (read-only) native event + args[0] = event; + event.delegateTarget = this; + + // Call the preDispatch hook for the mapped type, and let it bail if desired + if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { + return; + } + + // Determine handlers + handlerQueue = jQuery.event.handlers.call( this, event, handlers ); + + // Run delegates first; they may want to stop propagation beneath us + i = 0; + while ( (matched = handlerQueue[ i++ ]) && !event.isPropagationStopped() ) { + event.currentTarget = matched.elem; + + j = 0; + while ( (handleObj = matched.handlers[ j++ ]) && !event.isImmediatePropagationStopped() ) { + + // Triggered event must either 1) have no namespace, or + // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace). + if ( !event.namespace_re || event.namespace_re.test( handleObj.namespace ) ) { + + event.handleObj = handleObj; + event.data = handleObj.data; + + ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler ) + .apply( matched.elem, args ); + + if ( ret !== undefined ) { + if ( (event.result = ret) === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + } + } + } + + // Call the postDispatch hook for the mapped type + if ( special.postDispatch ) { + special.postDispatch.call( this, event ); + } + + return event.result; + }, + + handlers: function( event, handlers ) { + var sel, handleObj, matches, i, + handlerQueue = [], + delegateCount = handlers.delegateCount, + cur = event.target; + + // Find delegate handlers + // Black-hole SVG instance trees (#13180) + // Avoid non-left-click bubbling in Firefox (#3861) + if ( delegateCount && cur.nodeType && (!event.button || event.type !== "click") ) { + + /* jshint eqeqeq: false */ + for ( ; cur != this; cur = cur.parentNode || this ) { + /* jshint eqeqeq: true */ + + // Don't check non-elements (#13208) + // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) + if ( cur.nodeType === 1 && (cur.disabled !== true || event.type !== "click") ) { + matches = []; + for ( i = 0; i < delegateCount; i++ ) { + handleObj = handlers[ i ]; + + // Don't conflict with Object.prototype properties (#13203) + sel = handleObj.selector + " "; + + if ( matches[ sel ] === undefined ) { + matches[ sel ] = handleObj.needsContext ? + jQuery( sel, this ).index( cur ) >= 0 : + jQuery.find( sel, this, null, [ cur ] ).length; + } + if ( matches[ sel ] ) { + matches.push( handleObj ); + } + } + if ( matches.length ) { + handlerQueue.push({ elem: cur, handlers: matches }); + } + } + } + } + + // Add the remaining (directly-bound) handlers + if ( delegateCount < handlers.length ) { + handlerQueue.push({ elem: this, handlers: handlers.slice( delegateCount ) }); + } + + return handlerQueue; + }, + + fix: function( event ) { + if ( event[ jQuery.expando ] ) { + return event; + } + + // Create a writable copy of the event object and normalize some properties + var i, prop, copy, + type = event.type, + originalEvent = event, + fixHook = this.fixHooks[ type ]; + + if ( !fixHook ) { + this.fixHooks[ type ] = fixHook = + rmouseEvent.test( type ) ? this.mouseHooks : + rkeyEvent.test( type ) ? this.keyHooks : + {}; + } + copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props; + + event = new jQuery.Event( originalEvent ); + + i = copy.length; + while ( i-- ) { + prop = copy[ i ]; + event[ prop ] = originalEvent[ prop ]; + } + + // Support: IE<9 + // Fix target property (#1925) + if ( !event.target ) { + event.target = originalEvent.srcElement || document; + } + + // Support: Chrome 23+, Safari? + // Target should not be a text node (#504, #13143) + if ( event.target.nodeType === 3 ) { + event.target = event.target.parentNode; + } + + // Support: IE<9 + // For mouse/key events, metaKey==false if it's undefined (#3368, #11328) + event.metaKey = !!event.metaKey; + + return fixHook.filter ? fixHook.filter( event, originalEvent ) : event; + }, + + // Includes some event props shared by KeyEvent and MouseEvent + props: "altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "), + + fixHooks: {}, + + keyHooks: { + props: "char charCode key keyCode".split(" "), + filter: function( event, original ) { + + // Add which for key events + if ( event.which == null ) { + event.which = original.charCode != null ? original.charCode : original.keyCode; + } + + return event; + } + }, + + mouseHooks: { + props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "), + filter: function( event, original ) { + var body, eventDoc, doc, + button = original.button, + fromElement = original.fromElement; + + // Calculate pageX/Y if missing and clientX/Y available + if ( event.pageX == null && original.clientX != null ) { + eventDoc = event.target.ownerDocument || document; + doc = eventDoc.documentElement; + body = eventDoc.body; + + event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 ); + event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 ); + } + + // Add relatedTarget, if necessary + if ( !event.relatedTarget && fromElement ) { + event.relatedTarget = fromElement === event.target ? original.toElement : fromElement; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + // Note: button is not normalized, so don't use it + if ( !event.which && button !== undefined ) { + event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) ); + } + + return event; + } + }, + + special: { + load: { + // Prevent triggered image.load events from bubbling to window.load + noBubble: true + }, + focus: { + // Fire native event if possible so blur/focus sequence is correct + trigger: function() { + if ( this !== safeActiveElement() && this.focus ) { + try { + this.focus(); + return false; + } catch ( e ) { + // Support: IE<9 + // If we error on focus to hidden element (#1486, #12518), + // let .trigger() run the handlers + } + } + }, + delegateType: "focusin" + }, + blur: { + trigger: function() { + if ( this === safeActiveElement() && this.blur ) { + this.blur(); + return false; + } + }, + delegateType: "focusout" + }, + click: { + // For checkbox, fire native event so checked state will be right + trigger: function() { + if ( jQuery.nodeName( this, "input" ) && this.type === "checkbox" && this.click ) { + this.click(); + return false; + } + }, + + // For cross-browser consistency, don't fire native .click() on links + _default: function( event ) { + return jQuery.nodeName( event.target, "a" ); + } + }, + + beforeunload: { + postDispatch: function( event ) { + + // Support: Firefox 20+ + // Firefox doesn't alert if the returnValue field is not set. + if ( event.result !== undefined && event.originalEvent ) { + event.originalEvent.returnValue = event.result; + } + } + } + }, + + simulate: function( type, elem, event, bubble ) { + // Piggyback on a donor event to simulate a different one. + // Fake originalEvent to avoid donor's stopPropagation, but if the + // simulated event prevents default then we do the same on the donor. + var e = jQuery.extend( + new jQuery.Event(), + event, + { + type: type, + isSimulated: true, + originalEvent: {} + } + ); + if ( bubble ) { + jQuery.event.trigger( e, null, elem ); + } else { + jQuery.event.dispatch.call( elem, e ); + } + if ( e.isDefaultPrevented() ) { + event.preventDefault(); + } + } +}; + +jQuery.removeEvent = document.removeEventListener ? + function( elem, type, handle ) { + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle, false ); + } + } : + function( elem, type, handle ) { + var name = "on" + type; + + if ( elem.detachEvent ) { + + // #8545, #7054, preventing memory leaks for custom events in IE6-8 + // detachEvent needed property on element, by name of that event, to properly expose it to GC + if ( typeof elem[ name ] === strundefined ) { + elem[ name ] = null; + } + + elem.detachEvent( name, handle ); + } + }; + +jQuery.Event = function( src, props ) { + // Allow instantiation without the 'new' keyword + if ( !(this instanceof jQuery.Event) ) { + return new jQuery.Event( src, props ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = src.defaultPrevented || + src.defaultPrevented === undefined && + // Support: IE < 9, Android < 4.0 + src.returnValue === false ? + returnTrue : + returnFalse; + + // Event type + } else { + this.type = src; + } + + // Put explicitly provided properties onto the event object + if ( props ) { + jQuery.extend( this, props ); + } + + // Create a timestamp if incoming event doesn't have one + this.timeStamp = src && src.timeStamp || jQuery.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; +}; + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse, + + preventDefault: function() { + var e = this.originalEvent; + + this.isDefaultPrevented = returnTrue; + if ( !e ) { + return; + } + + // If preventDefault exists, run it on the original event + if ( e.preventDefault ) { + e.preventDefault(); + + // Support: IE + // Otherwise set the returnValue property of the original event to false + } else { + e.returnValue = false; + } + }, + stopPropagation: function() { + var e = this.originalEvent; + + this.isPropagationStopped = returnTrue; + if ( !e ) { + return; + } + // If stopPropagation exists, run it on the original event + if ( e.stopPropagation ) { + e.stopPropagation(); + } + + // Support: IE + // Set the cancelBubble property of the original event to true + e.cancelBubble = true; + }, + stopImmediatePropagation: function() { + var e = this.originalEvent; + + this.isImmediatePropagationStopped = returnTrue; + + if ( e && e.stopImmediatePropagation ) { + e.stopImmediatePropagation(); + } + + this.stopPropagation(); + } +}; + +// Create mouseenter/leave events using mouseover/out and event-time checks +jQuery.each({ + mouseenter: "mouseover", + mouseleave: "mouseout", + pointerenter: "pointerover", + pointerleave: "pointerout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + delegateType: fix, + bindType: fix, + + handle: function( event ) { + var ret, + target = this, + related = event.relatedTarget, + handleObj = event.handleObj; + + // For mousenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if ( !related || (related !== target && !jQuery.contains( target, related )) ) { + event.type = handleObj.origType; + ret = handleObj.handler.apply( this, arguments ); + event.type = fix; + } + return ret; + } + }; +}); + +// IE submit delegation +if ( !support.submitBubbles ) { + + jQuery.event.special.submit = { + setup: function() { + // Only need this for delegated form submit events + if ( jQuery.nodeName( this, "form" ) ) { + return false; + } + + // Lazy-add a submit handler when a descendant form may potentially be submitted + jQuery.event.add( this, "click._submit keypress._submit", function( e ) { + // Node name check avoids a VML-related crash in IE (#9807) + var elem = e.target, + form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined; + if ( form && !jQuery._data( form, "submitBubbles" ) ) { + jQuery.event.add( form, "submit._submit", function( event ) { + event._submit_bubble = true; + }); + jQuery._data( form, "submitBubbles", true ); + } + }); + // return undefined since we don't need an event listener + }, + + postDispatch: function( event ) { + // If form was submitted by the user, bubble the event up the tree + if ( event._submit_bubble ) { + delete event._submit_bubble; + if ( this.parentNode && !event.isTrigger ) { + jQuery.event.simulate( "submit", this.parentNode, event, true ); + } + } + }, + + teardown: function() { + // Only need this for delegated form submit events + if ( jQuery.nodeName( this, "form" ) ) { + return false; + } + + // Remove delegated handlers; cleanData eventually reaps submit handlers attached above + jQuery.event.remove( this, "._submit" ); + } + }; +} + +// IE change delegation and checkbox/radio fix +if ( !support.changeBubbles ) { + + jQuery.event.special.change = { + + setup: function() { + + if ( rformElems.test( this.nodeName ) ) { + // IE doesn't fire change on a check/radio until blur; trigger it on click + // after a propertychange. Eat the blur-change in special.change.handle. + // This still fires onchange a second time for check/radio after blur. + if ( this.type === "checkbox" || this.type === "radio" ) { + jQuery.event.add( this, "propertychange._change", function( event ) { + if ( event.originalEvent.propertyName === "checked" ) { + this._just_changed = true; + } + }); + jQuery.event.add( this, "click._change", function( event ) { + if ( this._just_changed && !event.isTrigger ) { + this._just_changed = false; + } + // Allow triggered, simulated change events (#11500) + jQuery.event.simulate( "change", this, event, true ); + }); + } + return false; + } + // Delegated event; lazy-add a change handler on descendant inputs + jQuery.event.add( this, "beforeactivate._change", function( e ) { + var elem = e.target; + + if ( rformElems.test( elem.nodeName ) && !jQuery._data( elem, "changeBubbles" ) ) { + jQuery.event.add( elem, "change._change", function( event ) { + if ( this.parentNode && !event.isSimulated && !event.isTrigger ) { + jQuery.event.simulate( "change", this.parentNode, event, true ); + } + }); + jQuery._data( elem, "changeBubbles", true ); + } + }); + }, + + handle: function( event ) { + var elem = event.target; + + // Swallow native change events from checkbox/radio, we already triggered them above + if ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox") ) { + return event.handleObj.handler.apply( this, arguments ); + } + }, + + teardown: function() { + jQuery.event.remove( this, "._change" ); + + return !rformElems.test( this.nodeName ); + } + }; +} + +// Create "bubbling" focus and blur events +if ( !support.focusinBubbles ) { + jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { + + // Attach a single capturing handler on the document while someone wants focusin/focusout + var handler = function( event ) { + jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true ); + }; + + jQuery.event.special[ fix ] = { + setup: function() { + var doc = this.ownerDocument || this, + attaches = jQuery._data( doc, fix ); + + if ( !attaches ) { + doc.addEventListener( orig, handler, true ); + } + jQuery._data( doc, fix, ( attaches || 0 ) + 1 ); + }, + teardown: function() { + var doc = this.ownerDocument || this, + attaches = jQuery._data( doc, fix ) - 1; + + if ( !attaches ) { + doc.removeEventListener( orig, handler, true ); + jQuery._removeData( doc, fix ); + } else { + jQuery._data( doc, fix, attaches ); + } + } + }; + }); +} + +jQuery.fn.extend({ + + on: function( types, selector, data, fn, /*INTERNAL*/ one ) { + var type, origFn; + + // Types can be a map of types/handlers + if ( typeof types === "object" ) { + // ( types-Object, selector, data ) + if ( typeof selector !== "string" ) { + // ( types-Object, data ) + data = data || selector; + selector = undefined; + } + for ( type in types ) { + this.on( type, selector, data, types[ type ], one ); + } + return this; + } + + if ( data == null && fn == null ) { + // ( types, fn ) + fn = selector; + data = selector = undefined; + } else if ( fn == null ) { + if ( typeof selector === "string" ) { + // ( types, selector, fn ) + fn = data; + data = undefined; + } else { + // ( types, data, fn ) + fn = data; + data = selector; + selector = undefined; + } + } + if ( fn === false ) { + fn = returnFalse; + } else if ( !fn ) { + return this; + } + + if ( one === 1 ) { + origFn = fn; + fn = function( event ) { + // Can use an empty set, since event contains the info + jQuery().off( event ); + return origFn.apply( this, arguments ); + }; + // Use same guid so caller can remove using origFn + fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); + } + return this.each( function() { + jQuery.event.add( this, types, fn, data, selector ); + }); + }, + one: function( types, selector, data, fn ) { + return this.on( types, selector, data, fn, 1 ); + }, + off: function( types, selector, fn ) { + var handleObj, type; + if ( types && types.preventDefault && types.handleObj ) { + // ( event ) dispatched jQuery.Event + handleObj = types.handleObj; + jQuery( types.delegateTarget ).off( + handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType, + handleObj.selector, + handleObj.handler + ); + return this; + } + if ( typeof types === "object" ) { + // ( types-object [, selector] ) + for ( type in types ) { + this.off( type, selector, types[ type ] ); + } + return this; + } + if ( selector === false || typeof selector === "function" ) { + // ( types [, fn] ) + fn = selector; + selector = undefined; + } + if ( fn === false ) { + fn = returnFalse; + } + return this.each(function() { + jQuery.event.remove( this, types, fn, selector ); + }); + }, + + trigger: function( type, data ) { + return this.each(function() { + jQuery.event.trigger( type, data, this ); + }); + }, + triggerHandler: function( type, data ) { + var elem = this[0]; + if ( elem ) { + return jQuery.event.trigger( type, data, elem, true ); + } + } +}); + + +function createSafeFragment( document ) { + var list = nodeNames.split( "|" ), + safeFrag = document.createDocumentFragment(); + + if ( safeFrag.createElement ) { + while ( list.length ) { + safeFrag.createElement( + list.pop() + ); + } + } + return safeFrag; +} + +var nodeNames = "abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|" + + "header|hgroup|mark|meter|nav|output|progress|section|summary|time|video", + rinlinejQuery = / jQuery\d+="(?:null|\d+)"/g, + rnoshimcache = new RegExp("<(?:" + nodeNames + ")[\\s/>]", "i"), + rleadingWhitespace = /^\s+/, + rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi, + rtagName = /<([\w:]+)/, + rtbody = /\s*$/g, + + // We have to close these tags to support XHTML (#13200) + wrapMap = { + option: [ 1, "" ], + legend: [ 1, "
", "
" ], + area: [ 1, "", "" ], + param: [ 1, "", "" ], + thead: [ 1, "", "
" ], + tr: [ 2, "", "
" ], + col: [ 2, "", "
" ], + td: [ 3, "", "
" ], + + // IE6-8 can't serialize link, script, style, or any html5 (NoScope) tags, + // unless wrapped in a div with non-breaking characters in front of it. + _default: support.htmlSerialize ? [ 0, "", "" ] : [ 1, "X
", "
" ] + }, + safeFragment = createSafeFragment( document ), + fragmentDiv = safeFragment.appendChild( document.createElement("div") ); + +wrapMap.optgroup = wrapMap.option; +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +function getAll( context, tag ) { + var elems, elem, + i = 0, + found = typeof context.getElementsByTagName !== strundefined ? context.getElementsByTagName( tag || "*" ) : + typeof context.querySelectorAll !== strundefined ? context.querySelectorAll( tag || "*" ) : + undefined; + + if ( !found ) { + for ( found = [], elems = context.childNodes || context; (elem = elems[i]) != null; i++ ) { + if ( !tag || jQuery.nodeName( elem, tag ) ) { + found.push( elem ); + } else { + jQuery.merge( found, getAll( elem, tag ) ); + } + } + } + + return tag === undefined || tag && jQuery.nodeName( context, tag ) ? + jQuery.merge( [ context ], found ) : + found; +} + +// Used in buildFragment, fixes the defaultChecked property +function fixDefaultChecked( elem ) { + if ( rcheckableType.test( elem.type ) ) { + elem.defaultChecked = elem.checked; + } +} + +// Support: IE<8 +// Manipulating tables requires a tbody +function manipulationTarget( elem, content ) { + return jQuery.nodeName( elem, "table" ) && + jQuery.nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ? + + elem.getElementsByTagName("tbody")[0] || + elem.appendChild( elem.ownerDocument.createElement("tbody") ) : + elem; +} + +// Replace/restore the type attribute of script elements for safe DOM manipulation +function disableScript( elem ) { + elem.type = (jQuery.find.attr( elem, "type" ) !== null) + "/" + elem.type; + return elem; +} +function restoreScript( elem ) { + var match = rscriptTypeMasked.exec( elem.type ); + if ( match ) { + elem.type = match[1]; + } else { + elem.removeAttribute("type"); + } + return elem; +} + +// Mark scripts as having already been evaluated +function setGlobalEval( elems, refElements ) { + var elem, + i = 0; + for ( ; (elem = elems[i]) != null; i++ ) { + jQuery._data( elem, "globalEval", !refElements || jQuery._data( refElements[i], "globalEval" ) ); + } +} + +function cloneCopyEvent( src, dest ) { + + if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) { + return; + } + + var type, i, l, + oldData = jQuery._data( src ), + curData = jQuery._data( dest, oldData ), + events = oldData.events; + + if ( events ) { + delete curData.handle; + curData.events = {}; + + for ( type in events ) { + for ( i = 0, l = events[ type ].length; i < l; i++ ) { + jQuery.event.add( dest, type, events[ type ][ i ] ); + } + } + } + + // make the cloned public data object a copy from the original + if ( curData.data ) { + curData.data = jQuery.extend( {}, curData.data ); + } +} + +function fixCloneNodeIssues( src, dest ) { + var nodeName, e, data; + + // We do not need to do anything for non-Elements + if ( dest.nodeType !== 1 ) { + return; + } + + nodeName = dest.nodeName.toLowerCase(); + + // IE6-8 copies events bound via attachEvent when using cloneNode. + if ( !support.noCloneEvent && dest[ jQuery.expando ] ) { + data = jQuery._data( dest ); + + for ( e in data.events ) { + jQuery.removeEvent( dest, e, data.handle ); + } + + // Event data gets referenced instead of copied if the expando gets copied too + dest.removeAttribute( jQuery.expando ); + } + + // IE blanks contents when cloning scripts, and tries to evaluate newly-set text + if ( nodeName === "script" && dest.text !== src.text ) { + disableScript( dest ).text = src.text; + restoreScript( dest ); + + // IE6-10 improperly clones children of object elements using classid. + // IE10 throws NoModificationAllowedError if parent is null, #12132. + } else if ( nodeName === "object" ) { + if ( dest.parentNode ) { + dest.outerHTML = src.outerHTML; + } + + // This path appears unavoidable for IE9. When cloning an object + // element in IE9, the outerHTML strategy above is not sufficient. + // If the src has innerHTML and the destination does not, + // copy the src.innerHTML into the dest.innerHTML. #10324 + if ( support.html5Clone && ( src.innerHTML && !jQuery.trim(dest.innerHTML) ) ) { + dest.innerHTML = src.innerHTML; + } + + } else if ( nodeName === "input" && rcheckableType.test( src.type ) ) { + // IE6-8 fails to persist the checked state of a cloned checkbox + // or radio button. Worse, IE6-7 fail to give the cloned element + // a checked appearance if the defaultChecked value isn't also set + + dest.defaultChecked = dest.checked = src.checked; + + // IE6-7 get confused and end up setting the value of a cloned + // checkbox/radio button to an empty string instead of "on" + if ( dest.value !== src.value ) { + dest.value = src.value; + } + + // IE6-8 fails to return the selected option to the default selected + // state when cloning options + } else if ( nodeName === "option" ) { + dest.defaultSelected = dest.selected = src.defaultSelected; + + // IE6-8 fails to set the defaultValue to the correct value when + // cloning other types of input fields + } else if ( nodeName === "input" || nodeName === "textarea" ) { + dest.defaultValue = src.defaultValue; + } +} + +jQuery.extend({ + clone: function( elem, dataAndEvents, deepDataAndEvents ) { + var destElements, node, clone, i, srcElements, + inPage = jQuery.contains( elem.ownerDocument, elem ); + + if ( support.html5Clone || jQuery.isXMLDoc(elem) || !rnoshimcache.test( "<" + elem.nodeName + ">" ) ) { + clone = elem.cloneNode( true ); + + // IE<=8 does not properly clone detached, unknown element nodes + } else { + fragmentDiv.innerHTML = elem.outerHTML; + fragmentDiv.removeChild( clone = fragmentDiv.firstChild ); + } + + if ( (!support.noCloneEvent || !support.noCloneChecked) && + (elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) { + + // We eschew Sizzle here for performance reasons: http://jsperf.com/getall-vs-sizzle/2 + destElements = getAll( clone ); + srcElements = getAll( elem ); + + // Fix all IE cloning issues + for ( i = 0; (node = srcElements[i]) != null; ++i ) { + // Ensure that the destination node is not null; Fixes #9587 + if ( destElements[i] ) { + fixCloneNodeIssues( node, destElements[i] ); + } + } + } + + // Copy the events from the original to the clone + if ( dataAndEvents ) { + if ( deepDataAndEvents ) { + srcElements = srcElements || getAll( elem ); + destElements = destElements || getAll( clone ); + + for ( i = 0; (node = srcElements[i]) != null; i++ ) { + cloneCopyEvent( node, destElements[i] ); + } + } else { + cloneCopyEvent( elem, clone ); + } + } + + // Preserve script evaluation history + destElements = getAll( clone, "script" ); + if ( destElements.length > 0 ) { + setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); + } + + destElements = srcElements = node = null; + + // Return the cloned set + return clone; + }, + + buildFragment: function( elems, context, scripts, selection ) { + var j, elem, contains, + tmp, tag, tbody, wrap, + l = elems.length, + + // Ensure a safe fragment + safe = createSafeFragment( context ), + + nodes = [], + i = 0; + + for ( ; i < l; i++ ) { + elem = elems[ i ]; + + if ( elem || elem === 0 ) { + + // Add nodes directly + if ( jQuery.type( elem ) === "object" ) { + jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); + + // Convert non-html into a text node + } else if ( !rhtml.test( elem ) ) { + nodes.push( context.createTextNode( elem ) ); + + // Convert html into DOM nodes + } else { + tmp = tmp || safe.appendChild( context.createElement("div") ); + + // Deserialize a standard representation + tag = (rtagName.exec( elem ) || [ "", "" ])[ 1 ].toLowerCase(); + wrap = wrapMap[ tag ] || wrapMap._default; + + tmp.innerHTML = wrap[1] + elem.replace( rxhtmlTag, "<$1>" ) + wrap[2]; + + // Descend through wrappers to the right content + j = wrap[0]; + while ( j-- ) { + tmp = tmp.lastChild; + } + + // Manually add leading whitespace removed by IE + if ( !support.leadingWhitespace && rleadingWhitespace.test( elem ) ) { + nodes.push( context.createTextNode( rleadingWhitespace.exec( elem )[0] ) ); + } + + // Remove IE's autoinserted from table fragments + if ( !support.tbody ) { + + // String was a , *may* have spurious + elem = tag === "table" && !rtbody.test( elem ) ? + tmp.firstChild : + + // String was a bare or + wrap[1] === "
" && !rtbody.test( elem ) ? + tmp : + 0; + + j = elem && elem.childNodes.length; + while ( j-- ) { + if ( jQuery.nodeName( (tbody = elem.childNodes[j]), "tbody" ) && !tbody.childNodes.length ) { + elem.removeChild( tbody ); + } + } + } + + jQuery.merge( nodes, tmp.childNodes ); + + // Fix #12392 for WebKit and IE > 9 + tmp.textContent = ""; + + // Fix #12392 for oldIE + while ( tmp.firstChild ) { + tmp.removeChild( tmp.firstChild ); + } + + // Remember the top-level container for proper cleanup + tmp = safe.lastChild; + } + } + } + + // Fix #11356: Clear elements from fragment + if ( tmp ) { + safe.removeChild( tmp ); + } + + // Reset defaultChecked for any radios and checkboxes + // about to be appended to the DOM in IE 6/7 (#8060) + if ( !support.appendChecked ) { + jQuery.grep( getAll( nodes, "input" ), fixDefaultChecked ); + } + + i = 0; + while ( (elem = nodes[ i++ ]) ) { + + // #4087 - If origin and destination elements are the same, and this is + // that element, do not do anything + if ( selection && jQuery.inArray( elem, selection ) !== -1 ) { + continue; + } + + contains = jQuery.contains( elem.ownerDocument, elem ); + + // Append to fragment + tmp = getAll( safe.appendChild( elem ), "script" ); + + // Preserve script evaluation history + if ( contains ) { + setGlobalEval( tmp ); + } + + // Capture executables + if ( scripts ) { + j = 0; + while ( (elem = tmp[ j++ ]) ) { + if ( rscriptType.test( elem.type || "" ) ) { + scripts.push( elem ); + } + } + } + } + + tmp = null; + + return safe; + }, + + cleanData: function( elems, /* internal */ acceptData ) { + var elem, type, id, data, + i = 0, + internalKey = jQuery.expando, + cache = jQuery.cache, + deleteExpando = support.deleteExpando, + special = jQuery.event.special; + + for ( ; (elem = elems[i]) != null; i++ ) { + if ( acceptData || jQuery.acceptData( elem ) ) { + + id = elem[ internalKey ]; + data = id && cache[ id ]; + + if ( data ) { + if ( data.events ) { + for ( type in data.events ) { + if ( special[ type ] ) { + jQuery.event.remove( elem, type ); + + // This is a shortcut to avoid jQuery.event.remove's overhead + } else { + jQuery.removeEvent( elem, type, data.handle ); + } + } + } + + // Remove cache only if it was not already removed by jQuery.event.remove + if ( cache[ id ] ) { + + delete cache[ id ]; + + // IE does not allow us to delete expando properties from nodes, + // nor does it have a removeAttribute function on Document nodes; + // we must handle all of these cases + if ( deleteExpando ) { + delete elem[ internalKey ]; + + } else if ( typeof elem.removeAttribute !== strundefined ) { + elem.removeAttribute( internalKey ); + + } else { + elem[ internalKey ] = null; + } + + deletedIds.push( id ); + } + } + } + } + } +}); + +jQuery.fn.extend({ + text: function( value ) { + return access( this, function( value ) { + return value === undefined ? + jQuery.text( this ) : + this.empty().append( ( this[0] && this[0].ownerDocument || document ).createTextNode( value ) ); + }, null, value, arguments.length ); + }, + + append: function() { + return this.domManip( arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.appendChild( elem ); + } + }); + }, + + prepend: function() { + return this.domManip( arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.insertBefore( elem, target.firstChild ); + } + }); + }, + + before: function() { + return this.domManip( arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this ); + } + }); + }, + + after: function() { + return this.domManip( arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this.nextSibling ); + } + }); + }, + + remove: function( selector, keepData /* Internal Use Only */ ) { + var elem, + elems = selector ? jQuery.filter( selector, this ) : this, + i = 0; + + for ( ; (elem = elems[i]) != null; i++ ) { + + if ( !keepData && elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem ) ); + } + + if ( elem.parentNode ) { + if ( keepData && jQuery.contains( elem.ownerDocument, elem ) ) { + setGlobalEval( getAll( elem, "script" ) ); + } + elem.parentNode.removeChild( elem ); + } + } + + return this; + }, + + empty: function() { + var elem, + i = 0; + + for ( ; (elem = this[i]) != null; i++ ) { + // Remove element nodes and prevent memory leaks + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + } + + // Remove any remaining nodes + while ( elem.firstChild ) { + elem.removeChild( elem.firstChild ); + } + + // If this is a select, ensure that it displays empty (#12336) + // Support: IE<9 + if ( elem.options && jQuery.nodeName( elem, "select" ) ) { + elem.options.length = 0; + } + } + + return this; + }, + + clone: function( dataAndEvents, deepDataAndEvents ) { + dataAndEvents = dataAndEvents == null ? false : dataAndEvents; + deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; + + return this.map(function() { + return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); + }); + }, + + html: function( value ) { + return access( this, function( value ) { + var elem = this[ 0 ] || {}, + i = 0, + l = this.length; + + if ( value === undefined ) { + return elem.nodeType === 1 ? + elem.innerHTML.replace( rinlinejQuery, "" ) : + undefined; + } + + // See if we can take a shortcut and just use innerHTML + if ( typeof value === "string" && !rnoInnerhtml.test( value ) && + ( support.htmlSerialize || !rnoshimcache.test( value ) ) && + ( support.leadingWhitespace || !rleadingWhitespace.test( value ) ) && + !wrapMap[ (rtagName.exec( value ) || [ "", "" ])[ 1 ].toLowerCase() ] ) { + + value = value.replace( rxhtmlTag, "<$1>" ); + + try { + for (; i < l; i++ ) { + // Remove element nodes and prevent memory leaks + elem = this[i] || {}; + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + elem.innerHTML = value; + } + } + + elem = 0; + + // If using innerHTML throws an exception, use the fallback method + } catch(e) {} + } + + if ( elem ) { + this.empty().append( value ); + } + }, null, value, arguments.length ); + }, + + replaceWith: function() { + var arg = arguments[ 0 ]; + + // Make the changes, replacing each context element with the new content + this.domManip( arguments, function( elem ) { + arg = this.parentNode; + + jQuery.cleanData( getAll( this ) ); + + if ( arg ) { + arg.replaceChild( elem, this ); + } + }); + + // Force removal if there was no new content (e.g., from empty arguments) + return arg && (arg.length || arg.nodeType) ? this : this.remove(); + }, + + detach: function( selector ) { + return this.remove( selector, true ); + }, + + domManip: function( args, callback ) { + + // Flatten any nested arrays + args = concat.apply( [], args ); + + var first, node, hasScripts, + scripts, doc, fragment, + i = 0, + l = this.length, + set = this, + iNoClone = l - 1, + value = args[0], + isFunction = jQuery.isFunction( value ); + + // We can't cloneNode fragments that contain checked, in WebKit + if ( isFunction || + ( l > 1 && typeof value === "string" && + !support.checkClone && rchecked.test( value ) ) ) { + return this.each(function( index ) { + var self = set.eq( index ); + if ( isFunction ) { + args[0] = value.call( this, index, self.html() ); + } + self.domManip( args, callback ); + }); + } + + if ( l ) { + fragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, this ); + first = fragment.firstChild; + + if ( fragment.childNodes.length === 1 ) { + fragment = first; + } + + if ( first ) { + scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); + hasScripts = scripts.length; + + // Use the original fragment for the last item instead of the first because it can end up + // being emptied incorrectly in certain situations (#8070). + for ( ; i < l; i++ ) { + node = fragment; + + if ( i !== iNoClone ) { + node = jQuery.clone( node, true, true ); + + // Keep references to cloned scripts for later restoration + if ( hasScripts ) { + jQuery.merge( scripts, getAll( node, "script" ) ); + } + } + + callback.call( this[i], node, i ); + } + + if ( hasScripts ) { + doc = scripts[ scripts.length - 1 ].ownerDocument; + + // Reenable scripts + jQuery.map( scripts, restoreScript ); + + // Evaluate executable scripts on first document insertion + for ( i = 0; i < hasScripts; i++ ) { + node = scripts[ i ]; + if ( rscriptType.test( node.type || "" ) && + !jQuery._data( node, "globalEval" ) && jQuery.contains( doc, node ) ) { + + if ( node.src ) { + // Optional AJAX dependency, but won't run scripts if not present + if ( jQuery._evalUrl ) { + jQuery._evalUrl( node.src ); + } + } else { + jQuery.globalEval( ( node.text || node.textContent || node.innerHTML || "" ).replace( rcleanScript, "" ) ); + } + } + } + } + + // Fix #11809: Avoid leaking memory + fragment = first = null; + } + } + + return this; + } +}); + +jQuery.each({ + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after", + replaceAll: "replaceWith" +}, function( name, original ) { + jQuery.fn[ name ] = function( selector ) { + var elems, + i = 0, + ret = [], + insert = jQuery( selector ), + last = insert.length - 1; + + for ( ; i <= last; i++ ) { + elems = i === last ? this : this.clone(true); + jQuery( insert[i] )[ original ]( elems ); + + // Modern browsers can apply jQuery collections as arrays, but oldIE needs a .get() + push.apply( ret, elems.get() ); + } + + return this.pushStack( ret ); + }; +}); + + +var iframe, + elemdisplay = {}; + +/** + * Retrieve the actual display of a element + * @param {String} name nodeName of the element + * @param {Object} doc Document object + */ +// Called only from within defaultDisplay +function actualDisplay( name, doc ) { + var style, + elem = jQuery( doc.createElement( name ) ).appendTo( doc.body ), + + // getDefaultComputedStyle might be reliably used only on attached element + display = window.getDefaultComputedStyle && ( style = window.getDefaultComputedStyle( elem[ 0 ] ) ) ? + + // Use of this method is a temporary fix (more like optmization) until something better comes along, + // since it was removed from specification and supported only in FF + style.display : jQuery.css( elem[ 0 ], "display" ); + + // We don't have any data stored on the element, + // so use "detach" method as fast way to get rid of the element + elem.detach(); + + return display; +} + +/** + * Try to determine the default display value of an element + * @param {String} nodeName + */ +function defaultDisplay( nodeName ) { + var doc = document, + display = elemdisplay[ nodeName ]; + + if ( !display ) { + display = actualDisplay( nodeName, doc ); + + // If the simple way fails, read from inside an iframe + if ( display === "none" || !display ) { + + // Use the already-created iframe if possible + iframe = (iframe || jQuery( " +
ArabicHebrewPolish
BulgarianHindiPortuguese
CatalanHmong DawRomanian
Chinese SimplifiedHungarianRussian
Chinese TraditionalIndonesianSlovak
CzechItalianSlovenian
DanishJapaneseSpanish
DutchKlingonSwedish
EnglishKoreanThai
EstonianLatvianTurkish
FinnishLithuanianUkrainian
FrenchMalayUrdu
GermanMalteseVietnamese
GreekNorwegianWelsh
Haitian CreolePersian
\ No newline at end of file diff --git a/html_parsing/foxtrot_com_ua__ru__search/main.py b/html_parsing/foxtrot_com_ua__ru__search/main.py index 6f681f3b3..8368e2bf6 100644 --- a/html_parsing/foxtrot_com_ua__ru__search/main.py +++ b/html_parsing/foxtrot_com_ua__ru__search/main.py @@ -1,13 +1,11 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -import datetime as DT -import time +import datetime as dt -from typing import List, Tuple, Union from pathlib import Path # pip install pandas @@ -17,6 +15,7 @@ from selenium import webdriver from selenium.webdriver.firefox.options import Options from selenium.common.exceptions import NoSuchElementException +from selenium.webdriver.common.by import By # TODO: Using logging instead print @@ -25,14 +24,14 @@ def get_text_by_css(parent, css_selector: str, default: str) -> str: try: - return parent.find_element_by_css_selector(css_selector).text + return parent.find_element(By.CSS_SELECTOR, css_selector).text except: return default -def parse(url_search: str) -> List[Tuple[str, str, str]]: +def parse(url_search: str) -> list[tuple[str, str, str]]: options = Options() - options.add_argument('--headless') + options.add_argument("--headless") items = [] @@ -44,15 +43,15 @@ def parse(url_search: str) -> List[Tuple[str, str, str]]: while page <= last_page: url = url_search if page > 1: - url = f'{url_search}&page={page}' + url = f"{url_search}&page={page}" - print(f'Load: {url}') + print(f"Load: {url}") driver.get(url) - for item_el in driver.find_elements_by_css_selector(".card[data-url]"): - name = get_text_by_css(item_el, '.card__title', 'Null') - price = get_text_by_css(item_el, '.card-price', '-') - nal = get_text_by_css(item_el, '.card__buttons', '-') + for item_el in driver.find_elements(By.CSS_SELECTOR, ".card[data-url]"): + name = get_text_by_css(item_el, ".card__title", "Null") + price = get_text_by_css(item_el, ".card-price", "-") + nal = get_text_by_css(item_el, ".card__buttons", "-") row = name, price, nal print(row) @@ -61,8 +60,8 @@ def parse(url_search: str) -> List[Tuple[str, str, str]]: # Обновление номера последней страницы try: - pages_count_el = driver.find_element_by_css_selector('*[data-pages-count]') - last_page = int(pages_count_el.get_attribute('data-pages-count')) + pages_count_el = driver.find_element(By.CSS_SELECTOR, "*[data-pages-count]") + last_page = int(pages_count_el.get_attribute("data-pages-count")) except NoSuchElementException: break @@ -75,21 +74,20 @@ def parse(url_search: str) -> List[Tuple[str, str, str]]: return items - def save_goods( - file_name: Union[str, Path], - items: List[Tuple[str, str, str]], - encoding='utf-8' -): - df = pd.DataFrame(items, columns=['Name', 'Price', 'Nal']) + file_name: str | Path, + items: list[tuple[str, str, str]], + encoding="utf-8", +) -> None: + df = pd.DataFrame(items, columns=["Name", "Price", "Nal"]) df.to_csv(file_name, encoding=encoding) -if __name__ == '__main__': +if __name__ == "__main__": url = "https://www.foxtrot.com.ua/ru/search?query=gazer&filter=_195_588" items = parse(url) - print(f'Total goods: {len(items)}') + print(f"Total goods: {len(items)}") - file_name = f'foxtrot_parser_{DT.datetime.now():%Y-%m-%d}.csv' - print(f'Saved to {file_name}') + file_name = f"foxtrot_parser_{dt.datetime.now():%Y-%m-%d}.csv" + print(f"Saved to {file_name}") save_goods(file_name, items) diff --git a/html_parsing/freelance_habr_com__tasks.py b/html_parsing/freelance_habr_com__tasks.py index 1d236ac7f..40d04b1fa 100644 --- a/html_parsing/freelance_habr_com__tasks.py +++ b/html_parsing/freelance_habr_com__tasks.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from urllib.parse import urljoin @@ -10,11 +10,11 @@ from bs4 import BeautifulSoup -rs = requests.get('https://freelance.habr.com/tasks?q=python') -root = BeautifulSoup(rs.content, 'html.parser') +rs = requests.get("https://freelance.habr.com/tasks?q=python") +root = BeautifulSoup(rs.content, "html.parser") urls = [ - (urljoin(rs.url, a['href']), a.get_text(strip=True)) - for a in root.select('.task__title > a[href]') + (urljoin(rs.url, a["href"]), a.get_text(strip=True)) + for a in root.select(".task__title > a[href]") ] print(len(urls), urls) # 25 [('https://freelance.habr.com/tasks/349695', 'Парсер '), ..., 'Доработка бота Telegram на Python')] diff --git a/html_parsing/gamestatus_info__lastcrackedgames.py b/html_parsing/gamestatus_info__lastcrackedgames.py deleted file mode 100644 index 64bcb8625..000000000 --- a/html_parsing/gamestatus_info__lastcrackedgames.py +++ /dev/null @@ -1,67 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -__author__ = 'ipetrash' - - -import datetime as DT -import json - -from dataclasses import dataclass - -import requests - - -session = requests.session() -session.headers['User-Agent'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:107.0) Gecko/20100101 Firefox/107.0' - - -URL_BASE = 'https://gamestatus.info' - - -@dataclass -class Game: - title: str - url: str - protection: str - release_date: DT.date - crack_date: DT.date - - @classmethod - def parse_from(cls, data: dict) -> 'Game': - # Example: "[\"DENUVO\"]" -> "DENUVO", "denuvo" -> "DENUVO" - protection: str = data["protections"].upper() - if protection.startswith('['): - protection = ', '.join(json.loads(protection)) - - return cls( - title=data["title"], - url=f'{URL_BASE}/{data["slug"]}', - protection=protection, - release_date=DT.date.fromisoformat(data["release_date"]), - crack_date=DT.date.fromisoformat(data["crack_date"]), - ) - - -def get_games() -> list[Game]: - rs = session.get(f'{URL_BASE}/back/api/gameinfo/game/lastcrackedgames/') - rs.raise_for_status() - - return [Game.parse_from(game) for game in rs.json()['list_crack_games']] - - -if __name__ == '__main__': - items = get_games() - print(f'Games ({len(items)}):') - for i, game in enumerate(items, 1): - print(f'{i}. {game}') - """ - Games (200): - 1. Game(title='High On Life', url='https://gamestatus.info/high-on-life', protection='STEAM', release_date=datetime.date(2022, 12, 13), crack_date=datetime.date(2022, 12, 13)) - 2. Game(title='CRISIS CORE –FINAL FANTASY VII– REUNION', url='https://gamestatus.info/crisis-core-final-fantasy-vii-reunion', protection='STEAM', release_date=datetime.date(2022, 12, 13), crack_date=datetime.date(2022, 12, 13)) - 3. Game(title='Wavetale', url='https://gamestatus.info/wavetale', protection='STEAM', release_date=datetime.date(2022, 12, 12), crack_date=datetime.date(2022, 12, 12)) - ... - 198. Game(title='Spire of Sorcery', url='https://gamestatus.info/spire-of-sorcery', protection='STEAM', release_date=datetime.date(2021, 10, 21), crack_date=datetime.date(2021, 10, 21)) - 199. Game(title='Inscryption', url='https://gamestatus.info/Inscryption', protection='STEAM', release_date=datetime.date(2021, 10, 19), crack_date=datetime.date(2021, 10, 19)) - 200. Game(title='Youtubers Life 2', url='https://gamestatus.info/youtubers-life-2', protection='STEAM', release_date=datetime.date(2021, 10, 19), crack_date=datetime.date(2021, 10, 19)) - """ diff --git a/html_parsing/gametime__use_cubiq_ru/extract_all_games.py b/html_parsing/gametime__use_cubiq_ru/extract_all_games.py deleted file mode 100644 index 3d5bd457b..000000000 --- a/html_parsing/gametime__use_cubiq_ru/extract_all_games.py +++ /dev/null @@ -1,61 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -__author__ = 'ipetrash' - - -import json -import time -from pathlib import Path - -from bs4 import BeautifulSoup - -from main import get, session - - -FILE = Path(__file__).resolve() -DIR = FILE.parent -FILE_CACHE = DIR / f'{FILE.stem}.json' - -try: - cache = json.load(open(FILE_CACHE, encoding='utf-8')) -except: - cache = dict() - - -URL_PAGE = 'https://cubiq.ru/gametime/page/{page}/' - -if __name__ == '__main__': - page = 1 - while True: - url = URL_PAGE.format(page=page) - print(url) - - try: - rs = session.get(url) - root = BeautifulSoup(rs.content, 'html.parser') - for a in root.select('.gridlove-post > .entry-image > a'): - game = a['title'] - if game in cache: - continue - - url = a['href'] - if data := get(url): - time_obj = data['Основной сюжет'] - cache[game] = { - 'text': time_obj.text, - 'seconds': time_obj.seconds, - } - print(f'Saved {game!r}: {time_obj.text}') - - json.dump(cache, open(FILE_CACHE, 'w', encoding='utf-8'), indent=4, ensure_ascii=False) - time.sleep(1) - - next_page = root.select_one('.gridlove-pagination > .next.page-numbers') - if not next_page: - break - - page += 1 - - except Exception: - time.sleep(1) diff --git a/html_parsing/get_comments__russianfood_com.py b/html_parsing/get_comments__russianfood_com.py index 1c15d2151..31de4cd40 100644 --- a/html_parsing/get_comments__russianfood_com.py +++ b/html_parsing/get_comments__russianfood_com.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from urllib.parse import urljoin @@ -11,7 +11,7 @@ import requests -PATTERN_NEXT = re.compile('Следующая →') +PATTERN_NEXT = re.compile("Следующая →") def get_comments(url: str, no_quote=False) -> list: @@ -19,43 +19,43 @@ def get_comments(url: str, no_quote=False) -> list: while True: rs = requests.get(url) - root = BeautifulSoup(rs.content, 'html.parser') + root = BeautifulSoup(rs.content, "html.parser") # Перебор комментариев - for comment_text_el in root.select('.comment > div'): + for comment_text_el in root.select(".comment > div"): # Перебор цитат - for blockquote_el in comment_text_el.select('blockquote'): + for blockquote_el in comment_text_el.select("blockquote"): if no_quote: blockquote_el.decompose() continue blockquote_el.replace_with( - "«" + blockquote_el.get_text(separator='\n', strip=True) + "»" + "«" + blockquote_el.get_text(separator="\n", strip=True) + "»" ) - for br in comment_text_el.select('br'): - br.replace_with('\n') + for br in comment_text_el.select("br"): + br.replace_with("\n") - comment_text = comment_text_el.get_text(separator='\n', strip=True) + comment_text = comment_text_el.get_text(separator="\n", strip=True) items.append(comment_text) - a_next = root.find(name='a', text=PATTERN_NEXT) + a_next = root.find(name="a", text=PATTERN_NEXT) if not a_next: break - url = urljoin(rs.url, a_next.get('href')) + url = urljoin(rs.url, a_next.get("href")) return items -if __name__ == '__main__': +if __name__ == "__main__": for url in [ - 'https://www.russianfood.com/recipes/recipe.php?rid=107392', - 'https://www.russianfood.com/recipes/recipe.php?rid=121323', + "https://www.russianfood.com/recipes/recipe.php?rid=107392", + "https://www.russianfood.com/recipes/recipe.php?rid=121323", ]: print(url) for i, comment_text in enumerate(get_comments(url), 1): print(i, repr(comment_text)) - print('\n' + '-' * 100 + '\n') \ No newline at end of file + print("\n" + "-" * 100 + "\n") diff --git a/html_parsing/get_comments__stopgame_ru.py b/html_parsing/get_comments__stopgame_ru.py index 98d34de69..c3d11d58f 100644 --- a/html_parsing/get_comments__stopgame_ru.py +++ b/html_parsing/get_comments__stopgame_ru.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from bs4 import BeautifulSoup @@ -10,40 +10,40 @@ def get_comments(url: str, no_quote=False) -> list: rs = requests.get(url) - root = BeautifulSoup(rs.content, 'html.parser') + root = BeautifulSoup(rs.content, "html.parser") items = [] # Перебор комментариев - for comment_text_el in root.select('.comment-text'): + for comment_text_el in root.select(".comment-text"): # Перебор цитат - for blockquote_el in comment_text_el.select('blockquote'): + for blockquote_el in comment_text_el.select("blockquote"): if no_quote: blockquote_el.decompose() continue blockquote_el.replace_with( - "«" + blockquote_el.get_text(separator='\n', strip=True) + "»" + "«" + blockquote_el.get_text(separator="\n", strip=True) + "»" ) - for br in comment_text_el.select('br'): - br.replace_with('\n') + for br in comment_text_el.select("br"): + br.replace_with("\n") - comment_text = comment_text_el.get_text(separator='\n', strip=True) + comment_text = comment_text_el.get_text(separator="\n", strip=True) items.append(comment_text) return items -if __name__ == '__main__': +if __name__ == "__main__": for url in [ - 'https://stopgame.ru/show/102770/atom_rpg_review', - 'https://stopgame.ru/show/43787/project_dark_review', - 'https://stopgame.ru/show/82379/dark_souls_iii_videoreview', + "https://stopgame.ru/show/102770/atom_rpg_review", + "https://stopgame.ru/show/43787/project_dark_review", + "https://stopgame.ru/show/82379/dark_souls_iii_videoreview", ]: print(url) for i, comment_text in enumerate(get_comments(url), 1): print(i, repr(comment_text)) - print('\n' + '-' * 100 + '\n') + print("\n" + "-" * 100 + "\n") diff --git a/html_parsing/get_customers_of_bink.py b/html_parsing/get_customers_of_bink.py index 7fbb5321f..9d7306231 100644 --- a/html_parsing/get_customers_of_bink.py +++ b/html_parsing/get_customers_of_bink.py @@ -1,17 +1,17 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import requests from bs4 import BeautifulSoup -url = 'http://www.radgametools.com/binkgames.htm' +url = "http://www.radgametools.com/binkgames.htm" rs = requests.get(url) -root = BeautifulSoup(rs.content, 'html.parser') +root = BeautifulSoup(rs.content, "html.parser") -items = [x.get_text(strip=True) for x in root.select('.gameslist > dl > dt')] -print(f'Items ({len(items)}): {items}') +items = [x.get_text(strip=True) for x in root.select(".gameslist > dl > dt")] +print(f"Items ({len(items)}): {items}") # Items (403): ['1C', '2015 Inc', '2XL Games', ..., 'Zombie', 'Zoë Mode', 'Zoo Digital Publishing'] diff --git a/html_parsing/get_population_from_wikidata.py b/html_parsing/get_population_from_wikidata.py index b298fdfe9..d845cdd57 100644 --- a/html_parsing/get_population_from_wikidata.py +++ b/html_parsing/get_population_from_wikidata.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import requests @@ -10,17 +10,17 @@ def get_populations(url: str) -> dict: rs = requests.get(url) - root = BeautifulSoup(rs.content, 'html.parser') + root = BeautifulSoup(rs.content, "html.parser") # P1082 -- идентификатор для population - population_node = root.select_one('#P1082') + population_node = root.select_one("#P1082") populations = dict() # Перебор строк в соседнем от population столбце - for row in population_node.select('.wikibase-statementview'): + for row in population_node.select(".wikibase-statementview"): # Небольшая хитрость -- берем только первые 2 значения, поидеи это будут: количество людей и дата - number_str, data_str = row.select('.wikibase-snakview-value')[:2] + number_str, data_str = row.select(".wikibase-snakview-value")[:2] # Вытаскиваем текст из number_str = number_str.text.strip() @@ -48,14 +48,15 @@ def get_population_from_url_by_year(url: str, year: int) -> str: return get_population_by_year(populations, year) -if __name__ == '__main__': - url = 'https://www.wikidata.org/wiki/Q148' +if __name__ == "__main__": + url = "https://www.wikidata.org/wiki/Q148" populations = get_populations(url) - print(populations) # {2012: '1,375,198,619', 2010: '1,359,755,102', 2015: '1,397,028,553', ... + print(populations) + # {2012: '1,375,198,619', 2010: '1,359,755,102', 2015: '1,397,028,553', ... # Выводим данные с сортировкой по ключу: по возрастанию for year in sorted(populations): - print("{}: {}".format(year, populations[year])) + print(f"{year}: {populations[year]}") # 2010: 1,359,755,102 # 2011: 1,367,480,264 diff --git a/html_parsing/get_price_game/from_direct2drive.py b/html_parsing/get_price_game/from_direct2drive.py index 61637d174..5c7c77cfb 100644 --- a/html_parsing/get_price_game/from_direct2drive.py +++ b/html_parsing/get_price_game/from_direct2drive.py @@ -1,20 +1,26 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -text = 'titan' -url = 'https://www.direct2drive.com/backend/api/productquery/findpage?search.keywords=' + text +import requests -import requests +text = "titan" +url = "https://www.direct2drive.com/backend/api/productquery/findpage?search.keywords={text}" + rs = requests.get(url) print(rs) data = rs.json() print(data) -for game in data['products']['items']: - offer = game['offerActions'][0] - print(game['title'], offer['purchasePrice']['amount'] + ' ' + offer['purchasePrice']['currency']['isoCode']) +for game in data["products"]["items"]: + offer = game["offerActions"][0] + print( + game["title"], + offer["purchasePrice"]["amount"] + + " " + + offer["purchasePrice"]["currency"]["isoCode"], + ) diff --git a/html_parsing/get_price_game/from_gama-gama.py b/html_parsing/get_price_game/from_gama-gama.py index eec7bfe0a..46337d083 100644 --- a/html_parsing/get_price_game/from_gama-gama.py +++ b/html_parsing/get_price_game/from_gama-gama.py @@ -1,17 +1,22 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" + + +import re + +from bs4 import BeautifulSoup + +from PyQt5.QtCore import QUrl +from PyQt5.QtWidgets import QApplication +from PyQt5.QtWebEngineWidgets import QWebEnginePage # Основа взята из http://stackoverflow.com/a/37755811/5909792 def get_html(url): - from PyQt5.QtCore import QUrl - from PyQt5.QtWidgets import QApplication - from PyQt5.QtWebEngineWidgets import QWebEnginePage - class ExtractorHtml: - def __init__(self, url): + def __init__(self, url) -> None: _app = QApplication([]) self._page = QWebEnginePage() self._page.loadFinished.connect(self._load_finished_handler) @@ -35,10 +40,10 @@ def __init__(self, url): # Чтобы избежать падений скрипта self._page = None - def _callable(self, data): + def _callable(self, data) -> None: self.html = data - def _load_finished_handler(self, _): + def _load_finished_handler(self, _) -> None: self._counter_finished += 1 if self._counter_finished == 2: @@ -47,35 +52,31 @@ def _load_finished_handler(self, _): return ExtractorHtml(url).html -text = 'mad' -url = 'http://gama-gama.ru/search/?searchField=' + text +text = "mad" +url = f"http://gama-gama.ru/search/?searchField={text}" html = get_html(url) -from bs4 import BeautifulSoup -root = BeautifulSoup(html, 'lxml') +root = BeautifulSoup(html, "lxml") -for game in root.select('.catalog-content > a'): - name = game['title'].strip() - name = name.replace('Купить ', '') +for game in root.select(".catalog-content > a"): + name = game["title"].strip() + name = name.replace("Купить ", "") price = None - price_holder = game.select_one('.catalog_price_holder') + price_holder = game.select_one(".catalog_price_holder") - price_1 = price_holder.select_one('.price_1') + price_1 = price_holder.select_one(".price_1") if price_1: price = price_1.text.strip() else: # Содержит описание цены со скидкой. Вытаскиваем цену со скидкой - price_2 = price_holder.select_one('.price_2') + price_2 = price_holder.select_one(".price_2") if price_2: - price = price_2.select_one('.price_group > .promo_price').text + price = price_2.select_one(".price_group > .promo_price").text # Удаление пустых символов пробелом - import re - price = re.sub(r'\s+', ' ', price) - - price = price.strip() + price = re.sub(r"\s+", " ", price).strip() print(name, price) diff --git a/html_parsing/get_price_game/from_gog.py b/html_parsing/get_price_game/from_gog.py index b134b3787..08aa0bcb7 100644 --- a/html_parsing/get_price_game/from_gog.py +++ b/html_parsing/get_price_game/from_gog.py @@ -1,26 +1,47 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -import sys import requests -text = 'titan quest' -url = 'https://www.gog.com/games/ajax/filtered?language=en&mediaType=game&page=1&sort=bestselling' \ - '&system=windows_10,windows_7,windows_8,windows_vista,windows_xp&search=' + text +session = requests.Session() +session.headers[ + "User-Agent" +] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:91.0) Gecko/20100101 Firefox/91.0" -rs = requests.get(url) -print(rs) -data = rs.json() -print(data) +def get_games(name: str) -> list[tuple[str, str]]: + url = ( + "https://www.gog.com/games/ajax/filtered?" + f"language=ru&mediaType=game&page=1&search={name}" + ) -if not data['totalGamesFound']: - print('Not found game') - sys.exit() + rs = session.get(url) + rs.raise_for_status() -for game in data['products']: - print(game['title'], game['price']['amount'] + game['price']['symbol']) + data = rs.json() + + return [ + (game["title"], game["price"]["baseAmount"]) + for game in data["products"] + ] + + +if __name__ == "__main__": + print(get_games("titan quest")) + # [('Titan Quest: Eternal Embers', '649'), ('Titan Quest Anniversary Edition', '649'), ('Titan Quest: Atlantis', '449'), ('Titan Quest: Ragnarök', '649')] + + print(get_games("Titan Quest: Atlantis")) + # [('Titan Quest: Atlantis', '449')] + + print(get_games("Prodeus")) + # [('Prodeus', '465')] + + print(get_games("Psychonauts 2")) + # [] + + print(get_games("dfsfsdfdsf")) + # [] diff --git a/html_parsing/get_price_game/from_gog_v2.py b/html_parsing/get_price_game/from_gog_v2.py new file mode 100644 index 000000000..1a007b5f4 --- /dev/null +++ b/html_parsing/get_price_game/from_gog_v2.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import requests + + +session = requests.Session() +session.headers[ + "User-Agent" +] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:91.0) Gecko/20100101 Firefox/91.0" + + +def get_games(name: str) -> list[tuple[str, str]]: + url = ( + f"https://catalog.gog.com/v1/catalog?" + f"countryCode=RU¤cyCode=RUB&limit=20" + f"&locale=ru-RU&order=desc:score" + f"&page=1&productType=in:game,pack,dlc,extras" + f"&query=like:{name}" + ) + + rs = session.get(url) + rs.raise_for_status() + + data = rs.json() + return [ + (game["title"], game["price"]["baseMoney"]["amount"]) + for game in data["products"] + if game["price"] and game["price"]["baseMoney"]["currency"] == "RUB" + ] + + +if __name__ == "__main__": + print(get_games("titan quest")) + # [('Titan Quest: Eternal Embers', '649.00'), ('Titan Quest: Atlantis', '449.00'), ('Titan Quest: Ragnarök', '649.00'), ('Titan Quest Anniversary Edition', '649.00')] + + print(get_games("Titan Quest: Atlantis")) + # [('Titan Quest: Atlantis', '449.00')] + + print(get_games("Prodeus")) + # [('Prodeus', '465.00'), ('Prodeus MIDI Soundtrack', '0.00'), ('Prodeus Soundtrack', '892.00')] + + print(get_games("Psychonauts 2")) + # [('Psychonauts 2', '1085.00'), ('Psychonauts', '249.00')] + + print(get_games("dfsfsdfdsf")) + # [] diff --git a/html_parsing/get_price_game/from_origin.py b/html_parsing/get_price_game/from_origin.py index cd5c1ce97..833aa63e2 100644 --- a/html_parsing/get_price_game/from_origin.py +++ b/html_parsing/get_price_game/from_origin.py @@ -1,17 +1,20 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" + + +from bs4 import BeautifulSoup + +from PyQt5.QtCore import QUrl, QTimer +from PyQt5.QtWidgets import QApplication +from PyQt5.QtWebEngineWidgets import QWebEnginePage # Основа взята из http://stackoverflow.com/a/37755811/5909792 def get_html(url): - from PyQt5.QtCore import QUrl, QTimer - from PyQt5.QtWidgets import QApplication - from PyQt5.QtWebEngineWidgets import QWebEnginePage - class ExtractorHtml: - def __init__(self, url): + def __init__(self, url) -> None: _app = QApplication([]) self._page = QWebEnginePage() @@ -40,35 +43,32 @@ def __init__(self, url): self._page = None - def _callable(self, data): + def _callable(self, data) -> None: self.html = data - def _load_finished_handler(self): + def _load_finished_handler(self) -> None: self._page.toHtml(self._callable) return ExtractorHtml(url).html -text = 'titan' -url = 'https://www.origin.com/rus/ru-ru/search?searchString=' + text +text = "titan" +url = f"https://www.origin.com/rus/ru-ru/search?searchString={text}" html = get_html(url) +root = BeautifulSoup(html, "lxml") - -from bs4 import BeautifulSoup -root = BeautifulSoup(html, 'lxml') - -for game in root.select('.origin-search-section .origin-storegametile-details'): - name = game.select_one('.origin-storegametile-title').text.strip() +for game in root.select(".origin-search-section .origin-storegametile-details"): + name = game.select_one(".origin-storegametile-title").text.strip() price = None - storeprice = game.select_one('.origin-storeprice') + storeprice = game.select_one(".origin-storeprice") - otkprice = storeprice.select_one('.otkprice') + otkprice = storeprice.select_one(".otkprice") if otkprice: price = otkprice.text.strip() else: - free = storeprice.select_one('.otkprice-free') + free = storeprice.select_one(".otkprice-free") if free: price = 0 diff --git a/html_parsing/get_price_game/from_shop_buka_ru.py b/html_parsing/get_price_game/from_shop_buka_ru.py index d43d1beb2..fc6a4c853 100644 --- a/html_parsing/get_price_game/from_shop_buka_ru.py +++ b/html_parsing/get_price_game/from_shop_buka_ru.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import requests @@ -9,21 +9,21 @@ def search(text: str) -> list[tuple[str, str]]: - rs = requests.post('https://shop.buka.ru/search', params=dict(q=text)) + rs = requests.post("https://shop.buka.ru/search", params=dict(q=text)) rs.raise_for_status() items = [] - root = BeautifulSoup(rs.content, 'html.parser') - for game in root.select('.product-thumb'): - name = game['data-name'] - price = game['data-price'] + root = BeautifulSoup(rs.content, "html.parser") + for game in root.select(".product-thumb"): + name = game["data-name"] + price = game["data-price"] items.append((name, price)) return items -if __name__ == '__main__': - text = 'titan' +if __name__ == "__main__": + text = "titan" for name, price in search(text): print(name, price) diff --git a/html_parsing/get_price_game/from_steam.py b/html_parsing/get_price_game/from_steam.py index a0d1b0e9c..2a6dd97a5 100644 --- a/html_parsing/get_price_game/from_steam.py +++ b/html_parsing/get_price_game/from_steam.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import re @@ -12,32 +12,31 @@ def search_game_price_list(name: str) -> list: # category1 = 998 (Game) - url = 'https://store.steampowered.com/search/?category1=998&os=win&supportedlang=english&term=' + name + url = f"https://store.steampowered.com/search/?category1=998&os=win&supportedlang=english&term={name}" game_price_list = [] rs = requests.get(url) if not rs.ok: - print(f'Что-то пошло не так: {rs.status_code}\n{rs.text}') + print(f"Что-то пошло не так: {rs.status_code}\n{rs.text}") return game_price_list - root = BeautifulSoup(rs.content, 'html.parser') + root = BeautifulSoup(rs.content, "html.parser") - for div in root.select('.search_result_row'): - name = div.select_one('.title').text.strip() + for div in root.select(".search_result_row"): + name = div.select_one(".title").text.strip() - # Ищем тег скидки - if div.select_one('.search_discount > span'): - price = div.select_one('.search_price > span > strike').text.strip() - else: - price = div.select_one('.search_price').text.strip() + # Ищем тег скидки, чтобы вытащить оригинальную цену, а не ту, что получилась со скидкой + price_el = div.select_one(".discount_original_price") or div.select_one(".discount_final_price") # Если цены нет (например, игра еще не продается) - if not price: + if not price_el: price = None else: + price = price_el.get_text(strip=True) + # Если в цене нет цифры считаем что это "Free To Play" или что-то подобное - match = re.search(r'\d', price) + match = re.search(r"\d", price) if not match: price = 0 @@ -46,14 +45,14 @@ def search_game_price_list(name: str) -> list: return game_price_list -if __name__ == '__main__': - text = 'resident evil 6' +if __name__ == "__main__": + text = "resident evil 6" game_price_list = search_game_price_list(text) for name, price in game_price_list: - print(name, price, sep=' / ') + print(name, price, sep=" / ") print() - text = 'prey' + text = "prey" game_price_list = search_game_price_list(text) for name, price in game_price_list: - print(name, price, sep=' / ') + print(name, price, sep=" / ") diff --git a/html_parsing/get_product_with_category cenoteka.rs.py b/html_parsing/get_product_with_category cenoteka.rs.py index 5fa7074a0..6b27238dc 100644 --- a/html_parsing/get_product_with_category cenoteka.rs.py +++ b/html_parsing/get_product_with_category cenoteka.rs.py @@ -1,29 +1,32 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://ru.stackoverflow.com/questions/894302/ -from bs4 import BeautifulSoup import requests +from bs4 import BeautifulSoup -url = 'https://cenoteka.rs/proizvodi/mlecni-proizvodi/jogurt?page=3' +url = "https://cenoteka.rs/proizvodi/mlecni-proizvodi/jogurt?page=3" rs = requests.get(url) -root = BeautifulSoup(rs.content, 'lxml') +root = BeautifulSoup(rs.content, "lxml") -for category in root.select('#products > .row.section'): - category_title = category.select_one('.section-title').get_text(strip=True) +for category in root.select("#products > .row.section"): + category_title = category.select_one(".section-title").get_text(strip=True) print(category_title) - for product in category.select('[data-product-id]'): + for product in category.select("[data-product-id]"): try: - title = product.select_one('.article-name').get_text(strip=True) - price_list = [price.get_text(strip=True) for price in product.select('.article-price')] - print(' {}: {}'.format(title, price_list)) + title = product.select_one(".article-name").get_text(strip=True) + price_list = [ + price.get_text(strip=True) + for price in product.select(".article-price") + ] + print(f" {title}: {price_list}") except: pass diff --git a/html_parsing/get_seasons_anime_Dorohedoro.py b/html_parsing/get_seasons_anime_Dorohedoro.py deleted file mode 100644 index 03eadb8e8..000000000 --- a/html_parsing/get_seasons_anime_Dorohedoro.py +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -__author__ = 'ipetrash' - - -import re -from typing import List - -import requests - - -def get_seasons() -> List[str]: - rs = requests.get('https://en.wikipedia.org/wiki/List_of_Dorohedoro_episodes') - rs.raise_for_status() - - items = re.findall(r'Season \w+', rs.text, flags=re.IGNORECASE) - return sorted(set(items)) - - -if __name__ == '__main__': - print(get_seasons()) - # ['Season One'] diff --git a/html_parsing/get_set_sushi_list__sushivkusno.com.py b/html_parsing/get_set_sushi_list__sushivkusno.com.py deleted file mode 100644 index f4d3b0d3b..000000000 --- a/html_parsing/get_set_sushi_list__sushivkusno.com.py +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -__author__ = 'ipetrash' - - -import re -from bs4 import BeautifulSoup -import requests - - -rs = requests.get('http://sushivkusno.com/category/nabory-siety') -print(rs) - -root = BeautifulSoup(rs.content, 'lxml') -items = root.select('.CardContent') - -for i, product in enumerate(items, 1): - title = product.select_one('.CardText__title').text - weight = product.select_one('.CardText__subtitle > span > b').text - - price = product.select_one('.ProductParams__price').text - price = int(re.sub('\D', '', price)) - - print('{}. "{}": {}, {}'.format(i, title, weight, price)) diff --git a/html_parsing/get_stackoverflow_people_reached.py b/html_parsing/get_stackoverflow_people_reached.py index 43e696d08..25b2ac804 100644 --- a/html_parsing/get_stackoverflow_people_reached.py +++ b/html_parsing/get_stackoverflow_people_reached.py @@ -1,51 +1,50 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -from typing import Optional import re -from bs4 import BeautifulSoup import requests +from bs4 import BeautifulSoup -def get_stackoverflow_people_reached(url: str) -> Optional[str]: +def get_stackoverflow_people_reached(url: str) -> str | None: rs = requests.get(url) rs.raise_for_status() - root = BeautifulSoup(rs.content, 'html.parser') - profile_avatar_el = root.select_one('#main-content .s-card') + root = BeautifulSoup(rs.content, "html.parser") + profile_avatar_el = root.select_one("#main-content .s-card") if not profile_avatar_el: return - text = profile_avatar_el.get_text(strip=True, separator='\n') - m = re.search(r'(\d+\.?\d*[km]?)\s*(затронуто|reached)', text) + text = profile_avatar_el.get_text(strip=True, separator="\n") + m = re.search(r"(\d+\.?\d*[km]?)\s*(затронуто|reached)", text) if not m: - raise Exception('Reached not found!') + raise Exception("Reached not found!") return m.group(1) -if __name__ == '__main__': - url = 'https://ru.stackoverflow.com/users/201445/gil9red' +if __name__ == "__main__": + url = "https://ru.stackoverflow.com/users/201445/gil9red" print(get_stackoverflow_people_reached(url)) # 1.5m - url = 'https://ru.stackoverflow.com' + url = "https://ru.stackoverflow.com" print(get_stackoverflow_people_reached(url)) # None print() urls = [ - 'https://ru.stackoverflow.com/users/213987/a-k', - 'https://ru.stackoverflow.com/users/17609/%d0%ae%d1%80%d0%b8%d0%b9%d0%a1%d0%9f%d0%b1', - 'https://ru.stackoverflow.com/users/1984/nofate', - 'https://stackoverflow.com/users/541136/aaron-hall', - 'https://stackoverflow.com/users/106224/boltclock', - 'https://stackoverflow.com/users/168175/flexo', + "https://ru.stackoverflow.com/users/213987/a-k", + "https://ru.stackoverflow.com/users/17609/%d0%ae%d1%80%d0%b8%d0%b9%d0%a1%d0%9f%d0%b1", + "https://ru.stackoverflow.com/users/1984/nofate", + "https://stackoverflow.com/users/541136/aaron-hall", + "https://stackoverflow.com/users/106224/boltclock", + "https://stackoverflow.com/users/168175/flexo", ] for url in urls: print(get_stackoverflow_people_reached(url)) diff --git a/html_parsing/get_video_list__xcadr.com.py b/html_parsing/get_video_list__xcadr.com.py index a829c687f..219ab0db6 100644 --- a/html_parsing/get_video_list__xcadr.com.py +++ b/html_parsing/get_video_list__xcadr.com.py @@ -1,71 +1,77 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -def get_video_list(url): - import requests - rs = requests.get(url) +import re + +import requests +from bs4 import BeautifulSoup - from bs4 import BeautifulSoup - root = BeautifulSoup(rs.content, 'lxml') - import re - pattern_get_duration = re.compile('(\d+).:(\d+).') +def get_video_list(url) -> list[dict]: + rs = requests.get(url) + root = BeautifulSoup(rs.content, "lxml") + + pattern_get_duration = re.compile(r"(\d+).:(\d+).") items = [] - for item in root.select('#playlist_view_playlist_view_items > .item'): - url = item.a['href'] - title = item.a['title'] - url_thumb = item.select_one('img')['src'] + for item in root.select("#playlist_view_playlist_view_items > .item"): + url = item.a["href"] + title = item.a["title"] + url_thumb = item.select_one("img")["src"] # Example: "60%", "80%" - rating = item.select_one('.rating').text - rating = int(rating.replace('%', '')) + rating = item.select_one(".rating").text + rating = int(rating.replace("%", "")) # Example: "0м:31с", "6м:32с" - duration = item.select_one('.duration').text + duration = item.select_one(".duration").text minutes, seconds = map(int, pattern_get_duration.findall(duration)[0]) duration = minutes * 60 + seconds items.append({ - 'title': title, - 'url': url, - 'duration': duration, - 'rating': rating, - 'url_thumb': url_thumb + "title": title, + "url": url, + "duration": duration, + "rating": rating, + "url_thumb": url_thumb, }) return items -if __name__ == '__main__': - items = get_video_list('http://xcadr.com/collection/seks-v-poze-naezdnicy/') - print('Total:', len(items)) +if __name__ == "__main__": + items = get_video_list("http://xcadr.com/collection/seks-v-poze-naezdnicy/") + print("Total:", len(items)) - items = get_video_list('http://xcadr.com/collection/sceny-bdsm-v-filmah/') - print('Total:', len(items)) + items = get_video_list("http://xcadr.com/collection/sceny-bdsm-v-filmah/") + print("Total:", len(items)) print() - items = get_video_list('http://xcadr.com/collection/luchshie-sceny-v-bane/') - print('Total:', len(items)) + items = get_video_list("http://xcadr.com/collection/luchshie-sceny-v-bane/") + print("Total:", len(items)) - def print_items(items): + def print_items(items) -> None: for i, item in enumerate(items, 1): - print('{0:2}. "{title}" ({duration} secs, rating: {rating}): {url} [{url_thumb}]'.format(i, **item)) - - print('Sorted by duration (top 5):') - new_items = sorted(items, key=lambda x: x['duration'], reverse=True)[:5] + print( + '{0:2}. "{title}" ({duration} secs, rating: {rating}): {url} [{url_thumb}]'.format( + i, **item + ) + ) + + print("Sorted by duration (top 5):") + new_items = sorted(items, key=lambda x: x["duration"], reverse=True)[:5] print_items(new_items) print() - print('Sorted by duration:') - new_items = sorted(items, key=lambda x: x['duration'], reverse=True) + print("Sorted by duration:") + new_items = sorted(items, key=lambda x: x["duration"], reverse=True) print_items(new_items) print() - print('Sorted by rating:') - new_items = sorted(items, key=lambda x: x['rating'], reverse=True) + print("Sorted by rating:") + new_items = sorted(items, key=lambda x: x["rating"], reverse=True) print_items(new_items) diff --git a/html_parsing/gifer_com__ru__gifs__loading.py b/html_parsing/gifer_com__ru__gifs__loading.py index c9f72180c..9b5b7011d 100644 --- a/html_parsing/gifer_com__ru__gifs__loading.py +++ b/html_parsing/gifer_com__ru__gifs__loading.py @@ -1,50 +1,51 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -from typing import List - # pip install selenium from selenium import webdriver from selenium.webdriver.firefox.options import Options +from selenium.webdriver.common.by import By -def get_urls(driver) -> List[str]: +def get_urls(driver) -> list[str]: urls = [] - for link in driver.find_elements_by_css_selector('figure.media-thumb.desktop link[itemprop=contentUrl]'): - url_gif = link.get_attribute('content') - gif_id = url_gif.split('/')[-1] + for link in driver.find_elements( + By.CSS_SELECTOR, "figure.media-thumb.desktop link[itemprop=contentUrl]" + ): + url_gif = link.get_attribute("content") + gif_id = url_gif.split("/")[-1] url_download = URL_DOWNLOAD_TEMPLATE + gif_id urls.append(url_download) return urls -URL_DOWNLOAD_TEMPLATE = 'https://i.gifer.com/embedded/download/' +URL_DOWNLOAD_TEMPLATE = "https://i.gifer.com/embedded/download/" options = Options() -options.add_argument('--headless') +options.add_argument("--headless") driver = webdriver.Firefox(options=options) -driver.get('https://gifer.com/ru/gifs/loading') +driver.get("https://gifer.com/ru/gifs/loading") print(f'Title: "{driver.title}"') driver.implicitly_wait(20) urls = get_urls(driver) -print(f'{len(urls)}: {urls}') +print(f"{len(urls)}: {urls}") # 4: ['https://i.gifer.com/embedded/download/g0R5.gif', 'https://i.gifer.com/embedded/download/VAyR.gif', 'https://i.gifer.com/embedded/download/ZKZx.gif', 'https://i.gifer.com/embedded/download/ZZ5H.gif'] # Small scroll down -driver.execute_script(f'window.scrollTo(0, 200);') +driver.execute_script(f"window.scrollTo(0, 200);") urls = get_urls(driver) -print(f'{len(urls)}: {urls}') +print(f"{len(urls)}: {urls}") # 8: ['https://i.gifer.com/embedded/download/g0R5.gif', 'https://i.gifer.com/embedded/download/VAyR.gif', 'https://i.gifer.com/embedded/download/ZKZx.gif', 'https://i.gifer.com/embedded/download/ZZ5H.gif', 'https://i.gifer.com/embedded/download/g0R9.gif', 'https://i.gifer.com/embedded/download/ZWdx.gif', 'https://i.gifer.com/embedded/download/7pld.gif', 'https://i.gifer.com/embedded/download/AqCa.gif'] driver.quit() diff --git a/html_parsing/gitmanga_com__get_chapters.py b/html_parsing/gitmanga_com__get_chapters.py new file mode 100644 index 000000000..002f867dc --- /dev/null +++ b/html_parsing/gitmanga_com__get_chapters.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from dataclasses import dataclass +from urllib.parse import urljoin + +import requests +from bs4 import BeautifulSoup + + +session = requests.session() +session.headers[ + "User-Agent" +] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/110.0" + + +@dataclass +class Chapter: + title: str + url: str + + +def get_chapters(url: str) -> list[Chapter]: + rs = session.get(url) + rs.raise_for_status() + + soup = BeautifulSoup(rs.content, "html.parser") + return [ + Chapter( + title=el.select_one(".item-title").get_text(strip=True), + url=urljoin(rs.url, el.a["href"]), + ) + for el in soup.select(".chapters-list__item") + ] + + +if __name__ == "__main__": + + def print_chapter(items: list[Chapter]) -> None: + print(f"Chapters ({len(items)}):") + print(f" {items[0]}") + print(" ...") + print(f" {items[-1]}") + + url = "https://gitmanga.com/764-berserk.html" + items = get_chapters(url) + print_chapter(items) + """ + Chapters (387): + Chapter(title='Том 1. Глава 0 - Berserk - The Prototype', url='https://gitmanga.com/read-764-berserk.html?t=1&g=0&p=1') + ... + Chapter(title='Том 42. Глава 371 - Угасающий свет в гнетущей тёмной ночи', url='https://gitmanga.com/read-764-berserk.html?t=42&g=371&p=1') + """ + + print() + + url = "https://gitmanga.com/605-igrok.html" + items = get_chapters(url) + print_chapter(items) + """ + Chapters (447): + Chapter(title='Том 1. Глава 1', url='https://gitmanga.com/read-605-igrok.html?t=1&g=1&p=1') + ... + Chapter(title='Том 6. Глава 15', url='https://gitmanga.com/read-605-igrok.html?t=6&g=15&p=1') + """ diff --git a/html_parsing/goldapple_ru/goldapple.ru_brands__products_API.py b/html_parsing/goldapple_ru/goldapple.ru_brands__products_API.py index 4e3664482..9a16c5c6f 100644 --- a/html_parsing/goldapple_ru/goldapple.ru_brands__products_API.py +++ b/html_parsing/goldapple_ru/goldapple.ru_brands__products_API.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import re @@ -9,30 +9,32 @@ session = requests.session() -session.headers['User-Agent'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:89.0) Gecko/20100101 Firefox/89.0' +session.headers[ + "User-Agent" +] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:89.0) Gecko/20100101 Firefox/89.0" -URL_BRAND = 'https://goldapple.ru/brands/elian-russia' +URL_BRAND = "https://goldapple.ru/brands/elian-russia" rs = session.get(URL_BRAND) m = re.search(r'"productsApiUrl":\s*"(.+?)",', rs.text) if not m: - raise Exception('Не получилось найти ссылку на API!') + raise Exception("Не получилось найти ссылку на API!") URL_API_PRODUCTS = m.group(1) -print(f'Using API: {URL_API_PRODUCTS}') +print(f"Using API: {URL_API_PRODUCTS}") # Using API: https://goldapple.ru/web_scripts/discover/category/products?cat=6577 total = 0 page = 1 while True: - rs = session.get(URL_API_PRODUCTS, params={'page': page}) + rs = session.get(URL_API_PRODUCTS, params={"page": page}) rs.raise_for_status() - products = rs.json().get('products') + products = rs.json().get("products") if not products: break diff --git a/html_parsing/grouple_co/[deprecated]_get_updates_from_rss.py b/html_parsing/grouple_co/[deprecated]_get_updates_from_rss.py new file mode 100644 index 000000000..5db8b2338 --- /dev/null +++ b/html_parsing/grouple_co/[deprecated]_get_updates_from_rss.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +# pip install feedparser==6.0.8 +import feedparser + + +URL_USER_RSS = "https://grouple.co/user/rss/315828?filter=" +USER_AGENT = ( + "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:91.0) Gecko/20100101 Firefox/91.0" +) + + +def get_feeds_by_manga_chapters(url: str = URL_USER_RSS) -> list[str]: + feed = feedparser.parse(url, agent=USER_AGENT) + + feeds = [] + for entry in feed.entries: + title: str = entry.title + title = ( + title.replace(""", '"') + .replace("Манга", "") + .replace("Взрослая манга", "") + .strip() + ) + + feeds.append(title) + + return feeds + + +if __name__ == "__main__": + items = get_feeds_by_manga_chapters() + print(f"Items ({len(items)}):", items) diff --git a/html_parsing/grouple_co/common.py b/html_parsing/grouple_co/common.py index 98aa480f2..4af15f367 100644 --- a/html_parsing/grouple_co/common.py +++ b/html_parsing/grouple_co/common.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import enum @@ -10,7 +10,7 @@ import time from dataclasses import dataclass, field -from typing import List, Dict +from urllib.parse import urljoin, urlsplit import requests from bs4 import BeautifulSoup, Tag @@ -22,10 +22,10 @@ class Bookmark: title: str url: str - tags: List[str] = field(default_factory=list) + tags: list[str] = field(default_factory=list) def get_title_with_tags(self) -> str: - return self.title + (f" [{', '.join(self.tags)}]" if self.tags else '') + return self.title + (f" [{', '.join(self.tags)}]" if self.tags else "") class AutoName(enum.Enum): @@ -34,43 +34,115 @@ def _generate_next_value_(name, start, count, last_values): class Status(AutoName): - WATCHING = enum.auto() # В процессе + WATCHING = enum.auto() # В процессе USER_DEFINED = enum.auto() # Пользовательская - ON_HOLD = enum.auto() # Пока бросил - PLANED = enum.auto() # В планах - COMPLETED = enum.auto() # Готово - FAVORITE = enum.auto() # Любимая + ON_HOLD = enum.auto() # Пока бросил + PLANED = enum.auto() # В планах + COMPLETED = enum.auto() # Готово + FAVORITE = enum.auto() # Любимая + + +HOST: str = "https://grouple.co" +ADDITIONAL_HOSTS: list[str] = [ + "https://1.grouple.co", + "https://2.grouple.co", + "https://3.grouple.co", +] session = requests.Session() -session.headers['User-Agent'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:92.0) Gecko/20100101 Firefox/92.0' +session.headers[ + "User-Agent" +] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:92.0) Gecko/20100101 Firefox/92.0" + + +def get_url(uri: str) -> str: + return urljoin(HOST, uri) + + +def do_get(uri: str, **kwargs) -> requests.Response: + rs = session.get(get_url(uri), **kwargs) + rs.raise_for_status() + + return rs + + +def do_post(uri: str, **kwargs) -> requests.Response: + rs = session.post(get_url(uri), **kwargs) + rs.raise_for_status() + + return rs def do_auth() -> requests.Response: form_data = { - 'username': LOGIN, - 'password': PASSWORD, + "username": LOGIN, + "password": PASSWORD, + "remember_me": "true", + "_remember_me_yes": "", + "remember_me_yes": "on", } - rs = session.post('https://grouple.co/login/authenticate', data=form_data) + rs = do_get("/internal/auth/login") + rs.raise_for_status() + + rs = do_post("/login/authenticate", data=form_data) rs.raise_for_status() # Example: https://grouple.co/internal/auth/login?login_error=1 - if 'error' in rs.url: - raise Exception('Invalid auth!') + if "error" in rs.url: + raise Exception("Invalid auth!") return rs -def load(url: str, **kwargs) -> requests.Response: - while True: - rs = session.get(url, **kwargs) - rs.raise_for_status() +# NOTE: Была замечена смена домена +def update_host(): + global HOST + + def _update(url: str) -> bool: + global HOST + + if "grouple" in url and not url.startswith(HOST): + HOST = urlsplit(url)._replace(path="", query="", fragment="").geturl() + return True + + return False + + try: + rs = do_get("/") + except Exception as e: + # Перебор дополнительных хостов при ошибках + if HOST == ADDITIONAL_HOSTS[-1]: + raise e + + try: + idx = ADDITIONAL_HOSTS.index(HOST) + 1 + except ValueError: + idx = 0 + + HOST = ADDITIONAL_HOSTS[idx] + + update_host() + return + if not _update(rs.url): + rs = do_get("/internal/auth") + _update(rs.url) + + rs = do_auth() + _update(rs.url) + + +def load(uri: str, **kwargs) -> requests.Response: + update_host() + + while True: + rs = do_get(uri, **kwargs) time.sleep(1) # Если нужно авторизоваться - if '/login/auth' in rs.url: + if "/login/auth" in rs.url: do_auth() continue @@ -80,29 +152,28 @@ def load(url: str, **kwargs) -> requests.Response: def parse_bookmark(el: Tag) -> Bookmark: tags = [] if el.sup: - tags += [x.get_text(strip=True).lower() for x in el.sup.select('span[class]')] + tags += [x.get_text(strip=True).lower() for x in el.sup.select("span[class]")] # Удаление сноски ("Выпуск завершен", "переведено" и т.п.), чтобы в title она не попала el.sup.decompose() title = el.get_text(strip=True) - url = el['href'] + url = el["href"] return Bookmark(title=title, url=url, tags=tags) -def get_bookmarks_by_status(status: Status) -> List[Bookmark]: - url_bookmarks = 'https://grouple.co/private/bookmarks' - rs = load(url_bookmarks) +def get_bookmarks_by_status(status: Status) -> list[Bookmark]: + rs = load("/private/bookmarks") - m = re.search(r'var SITES = (\[.+]);', rs.text) + m = re.search(r"var SITES = (\[.+]);", rs.text) if not m: raise Exception('Не удалось найти "var SITES = "!') # Example: var SITES = [{"id":1,"title":"ReadManga","url":"https://readmanga.io"}, ... sites_str = m.group(1) sites = json.loads(sites_str) - site_id_by_url = {x['id']: x['url'] for x in sites} + site_id_by_url = {x["id"]: x["url"] for x in sites} items: list[Bookmark] = [] @@ -114,45 +185,36 @@ def get_bookmarks_by_status(status: Status) -> List[Bookmark]: "bookmarkSort": "NAME", "query": "", "elementFilter": [], - "statusFilter": [ - status.value - ], + "statusFilter": [status.value], "limit": limit, - "offset": offset + "offset": offset, } headers = { - 'Authorization': f'Bearer {session.cookies["gwt"]}', - 'Referer': url_bookmarks, + "Authorization": f'Bearer {session.cookies["gwt"]}', + "Referer": rs.url, } - rs = session.post( - 'https://grouple.co/api/bookmark/list', - json=data, - headers=headers - ) - rs.raise_for_status() + rs = do_post("/api/bookmark/list", json=data, headers=headers) result = rs.json() - for item in result['list']: - title = item['element']['name'] + for item in result["list"]: + title = item["element"]["name"] - site_id = item['element']['elementId']['siteId'] - element_url = item['element']['elementUrl'] + site_id = item["element"]["elementId"]["siteId"] + element_url = item["element"]["elementUrl"] url = site_id_by_url[site_id] + element_url # Example: # tags_str = " СборникOnline" # tags = ['Сборник', 'Online'] - tags_str = item['element']['tagsString'] + tags_str = item["element"]["tagsString"] tags = [ el.get_text(strip=True).lower() - for el in BeautifulSoup(tags_str, 'html.parser').select('span') + for el in BeautifulSoup(tags_str, "html.parser").select("span") ] - items.append( - Bookmark(title=title, url=url, tags=tags) - ) + items.append(Bookmark(title=title, url=url, tags=tags)) - if result['offset'] + result['limit'] >= result['total']: + if result["offset"] + result["limit"] >= result["total"]: break offset += limit @@ -161,49 +223,55 @@ def get_bookmarks_by_status(status: Status) -> List[Bookmark]: return items -def get_plain_all_bookmarks_from_user(user_id: int) -> List[Bookmark]: - url = f'https://grouple.co/user/{user_id}/bookmarks' - rs = load(url) - root = BeautifulSoup(rs.content, 'html.parser') +def get_plain_all_bookmarks_from_user(user_id: int) -> list[Bookmark]: + rs = load(f"/user/{user_id}/bookmarks") + root = BeautifulSoup(rs.content, "html.parser") - return [ - parse_bookmark(row) - for row in root.select('a.site-element') - ] + return [parse_bookmark(row) for row in root.select("a.site-element")] -def get_all_bookmarks() -> Dict[Status, List[Bookmark]]: - return { - status: get_bookmarks_by_status(status) - for status in Status - } +def get_all_bookmarks() -> dict[Status, list[Bookmark]]: + return {status: get_bookmarks_by_status(status) for status in Status} -if __name__ == '__main__': +if __name__ == "__main__": assert Status.WATCHING.name == Status.WATCHING.value assert Status.WATCHING.name == "WATCHING" + print("HOST:", HOST) + # HOST: https://grouple.co + print(session.cookies) + print(load("/")) print(do_auth()) print(session.cookies) - print('\n' + '-' * 50 + '\n') + print("HOST:", HOST) + # HOST: https://1.grouple.co + + print("\n" + "-" * 50 + "\n") print(get_plain_all_bookmarks_from_user(315828)) - print('\n' + '-' * 50 + '\n') + print("\n" + "-" * 50 + "\n") bookmarks = get_bookmarks_by_status(Status.WATCHING) - print(f'Bookmarks ({len(bookmarks)}):') + print(f"Bookmarks ({len(bookmarks)}):") for i, bookmark in enumerate(bookmarks, 1): - print(f'{i}. {bookmark}') + print(f"{i}. {bookmark}") """ - Bookmarks (28): - 1. Bookmark(title='Башня Бога', url='https://readmanga.io/bashnia_boga__A339d2', tags=[]) - 2. Bookmark(title='Берсерк', url='https://readmanga.io/berserk', tags=[]) - 3. Bookmark(title='Боруто', url='https://readmanga.io/boruto__A5327', tags=[]) - ... - 26. Bookmark(title='Священная земля', url='https://readmanga.io/sviachennaia__zemlia__A533b', tags=['переведено']) - 27. Bookmark(title='Терраформирование', url='https://mintmanga.live/terraformirovanie__A5327', tags=[]) - 28. Bookmark(title='Фейри Тейл. Начало', url='https://readmanga.io/feiri_teil__nachalo', tags=['переведено', 'Без глав']) + Bookmarks (13): + 1. Bookmark(title='Башня Бога', url='https://web.usagi.one/tower_of_god', tags=[]) + 2. Bookmark(title='Боруто. Наруто: Новое поколение', url='https://web.usagi.one/boruto__naruto_next_generations', tags=['завершён']) + 3. Bookmark(title='Ван Пис', url='https://web.usagi.one/van_pis', tags=[]) + 4. Bookmark(title='Ванпанчмен', url='https://1.seimanga.me/vanpanchmen', tags=[]) + 5. Bookmark(title='Ванпанчмен (ONE)', url='https://1.seimanga.me/vanpanchmen__one_', tags=[]) + 6. Bookmark(title='Выживание в игре за варвара', url='https://web.usagi.one/surviving_the_game_as_a_barbarian', tags=[]) + 7. Bookmark(title='Необъятный океан', url='https://1.seimanga.me/neobiatnyi_okean', tags=[]) + 8. Bookmark(title='Охотник × Охотник', url='https://web.usagi.one/hunter_x_hunter', tags=[]) + 9. Bookmark(title='Священная земля', url='https://web.usagi.one/holyland', tags=['завершён']) + 10. Bookmark(title='Семь смертных грехов', url='https://web.usagi.one/the_seven_deadly_sins', tags=['завершён', 'без глав']) + 11. Bookmark(title='Серафим конца', url='https://web.usagi.one/seraph_of_the_end', tags=[]) + 12. Bookmark(title='Терраформирование', url='https://1.seimanga.me/terraformirovanie', tags=[]) + 13. Bookmark(title='Чародейки', url='https://web.usagi.one/witch', tags=['завершён']) """ diff --git a/html_parsing/grouple_co/config.py b/html_parsing/grouple_co/config.py index 6164dcf54..3a49963e2 100644 --- a/html_parsing/grouple_co/config.py +++ b/html_parsing/grouple_co/config.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import os @@ -9,7 +9,7 @@ DIR = Path(__file__).resolve().parent -TOKEN_FILE_NAME = DIR / 'TOKEN.txt' +TOKEN_FILE_NAME = DIR / "TOKEN.txt" -TOKEN = os.environ.get('TOKEN') or TOKEN_FILE_NAME.read_text('utf-8').strip() +TOKEN = os.environ.get("TOKEN") or TOKEN_FILE_NAME.read_text("utf-8").strip() LOGIN, PASSWORD = TOKEN.splitlines() diff --git a/html_parsing/grouple_co/get_updates_from_api.py b/html_parsing/grouple_co/get_updates_from_api.py new file mode 100644 index 000000000..ce223c428 --- /dev/null +++ b/html_parsing/grouple_co/get_updates_from_api.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from common import session, do_post, load + + +def get_feeds_by_manga_chapters() -> list[str]: + # Auth and load + rs = load("/private/bookmarks/index#") + + data = { + "bookmarkSort": "NAME", + "elementFilter": [], + "statusFilter": ["ANOTHER_UPDATES"], + "includeUpdates": True, + "limit": 50, + "offset": 0, + } + + session.headers.update( + { + "Authorization": f'Bearer {session.cookies["gwt"]}', + "Referer": rs.url, + } + ) + + rs = do_post("/api/bookmark/activitiesList", json=data) + return [ + f'{item["element"]["name"]} {item["title"]}' + for item in rs.json()["list"] + if item["type"] == "CHAPTER_NEW" + ] + + +if __name__ == "__main__": + items = get_feeds_by_manga_chapters() + print(f"Items ({len(items)}):", items) diff --git a/html_parsing/grouple_co/print_all_bookmarks.py b/html_parsing/grouple_co/print_all_bookmarks.py index fc834d14d..5d8314c68 100644 --- a/html_parsing/grouple_co/print_all_bookmarks.py +++ b/html_parsing/grouple_co/print_all_bookmarks.py @@ -1,22 +1,25 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from common import get_all_bookmarks status_by_bookmarks = get_all_bookmarks() -print('Total bookmarks:', sum(len(bookmarks) for bookmarks in status_by_bookmarks.values())) +print( + "Total bookmarks:", + sum(len(bookmarks) for bookmarks in status_by_bookmarks.values()), +) # Total bookmarks: 143 print() for status, bookmarks in status_by_bookmarks.items(): - print(f'{status.value}. Bookmarks ({len(bookmarks)}):') + print(f"{status.value}. Bookmarks ({len(bookmarks)}):") for i, bookmark in enumerate(bookmarks, 1): - print(f'{i}. {bookmark}') + print(f"{i}. {bookmark}") print() diff --git a/html_parsing/grouple_co/print_all_bookmarks_from_user.py b/html_parsing/grouple_co/print_all_bookmarks_from_user.py index fc1769b73..f6e8368fc 100644 --- a/html_parsing/grouple_co/print_all_bookmarks_from_user.py +++ b/html_parsing/grouple_co/print_all_bookmarks_from_user.py @@ -1,16 +1,16 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from common import get_plain_all_bookmarks_from_user bookmarks = get_plain_all_bookmarks_from_user(315828) -print(f'Bookmarks ({len(bookmarks)}):') +print(f"Bookmarks ({len(bookmarks)}):") for i, bookmark in enumerate(bookmarks, 1): - print(f'{i}. {bookmark}') + print(f"{i}. {bookmark}") """ Bookmarks (85): 1. Bookmark(title='Башня Бога', url='https://readmanga.io/bashnia_boga__A339d2', tags=[]) @@ -20,4 +20,4 @@ 83. Bookmark(title='Школа мертвецов', url='/internal/red/14905', tags=['переведено']) 84. Bookmark(title='Школьный эфир!', url='https://readmanga.io/love_live__dj___school_live', tags=['сингл']) 85. Bookmark(title='Энигма', url='https://readmanga.io/enigma__A5274', tags=['переведено']) -""" \ No newline at end of file +""" diff --git a/html_parsing/grouple_co/print_bookmarks_completed__mangaTranslationCompleted.py b/html_parsing/grouple_co/print_bookmarks_completed__mangaTranslationCompleted.py index 2e34cc5c5..34d962d81 100644 --- a/html_parsing/grouple_co/print_bookmarks_completed__mangaTranslationCompleted.py +++ b/html_parsing/grouple_co/print_bookmarks_completed__mangaTranslationCompleted.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from common import Status, get_bookmarks_by_status @@ -9,13 +9,13 @@ items = get_bookmarks_by_status(Status.WATCHING) -print(f'Total bookmarks ({len(items)}):') +print(f"Total bookmarks ({len(items)}):") for x in items: - print(f' {x.title!r}: {x.url}') + print(f" {x.title!r}: {x.url}") -print('\n') +print("\n") -completed = [x for x in items if 'переведено' in x.tags] -print(f'Total bookmarks completed ({len(completed)}):') +completed = [x for x in items if "завершён" in x.tags] +print(f"Total bookmarks completed ({len(completed)}):") for x in completed: - print(f' {x.title!r}: {x.url}') + print(f" {x.title!r}: {x.url}") diff --git a/html_parsing/grouple_co/print_pretty_title_all_bookmarks.py b/html_parsing/grouple_co/print_pretty_title_all_bookmarks.py index e4583ed84..b0ccecf97 100644 --- a/html_parsing/grouple_co/print_pretty_title_all_bookmarks.py +++ b/html_parsing/grouple_co/print_pretty_title_all_bookmarks.py @@ -1,33 +1,29 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -from typing import List from common import get_all_bookmarks -def get_all_pretty_title_of_bookmarks() -> List[str]: +def get_all_pretty_title_of_bookmarks() -> list[str]: items = [] for bookmarks in get_all_bookmarks().values(): - items += [ - x.get_title_with_tags() - for x in bookmarks - ] + items += [x.get_title_with_tags() for x in bookmarks] return items -if __name__ == '__main__': +if __name__ == "__main__": all_bookmarks = get_all_pretty_title_of_bookmarks() - print('Total bookmarks:', len(all_bookmarks)) + print("Total bookmarks:", len(all_bookmarks)) # Total bookmarks: 143 print() for i, bookmark_title in enumerate(all_bookmarks, 1): - print(f'{i}. {bookmark_title}') + print(f"{i}. {bookmark_title}") """ 1. Башня Бога diff --git a/html_parsing/hentailib_me.py b/html_parsing/hentailib_me.py index 1dd309bea..1978dac33 100644 --- a/html_parsing/hentailib_me.py +++ b/html_parsing/hentailib_me.py @@ -1,45 +1,44 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -import re import json -from typing import List +import re import requests -def get_images(url: str) -> List[str]: +def get_images(url: str) -> list[str]: rs = requests.get(url) - info = re.search('window.__info = (.+?);', rs.text) + info = re.search("window.__info = (.+?);", rs.text) if not info: - print('[#] Not found window.__info!') + print("[#] Not found window.__info!") return [] - pages = re.search('window.__pg = (.+?);', rs.text) + pages = re.search("window.__pg = (.+?);", rs.text) if not pages: - print('[#] Not found window.__pg!') + print("[#] Not found window.__pg!") return [] info = json.loads(info.group(1)) pages = json.loads(pages.group(1)) - url_chapter = info['img']['url'] - url_base = info['servers']['main'] + url_chapter + url_chapter = info["img"]["url"] + url_base = info["servers"]["main"] + url_chapter - return [url_base + p['u'] for p in pages] + return [url_base + p["u"] for p in pages] -if __name__ == '__main__': - url = 'https://hentailib.me/koshkodevochki-eto-lozh/v1/c1?page=1' +if __name__ == "__main__": + url = "https://hentailib.me/koshkodevochki-eto-lozh/v1/c1?page=1" items = get_images(url) - print(f'Images ({len(items)}):') + print(f"Images ({len(items)}):") for i, url in enumerate(items, 1): - print(f' {i}. {url}') + print(f" {i}. {url}") # Images (22): # 1. https://img2.hentailib.me/manga/koshkodevochki-eto-lozh/chapters/468545/02_iips.png diff --git a/html_parsing/horoscope/horo.mail.ru_pisces_today.py b/html_parsing/horoscope/horo.mail.ru_pisces_today.py index a394f5564..76e4e6b05 100644 --- a/html_parsing/horoscope/horo.mail.ru_pisces_today.py +++ b/html_parsing/horoscope/horo.mail.ru_pisces_today.py @@ -1,14 +1,15 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import requests -rs = requests.get('https://horo.mail.ru/prediction/pisces/today/') - from bs4 import BeautifulSoup -root = BeautifulSoup(rs.content, 'lxml') -text = root.select_one('.article__text').text.strip() + + +rs = requests.get("https://horo.mail.ru/prediction/pisces/today/") +root = BeautifulSoup(rs.content, "lxml") +text = root.select_one(".article__text").text.strip() print(repr(text)) print(text) diff --git a/html_parsing/howlongtobeat_com/common.py b/html_parsing/howlongtobeat_com/common.py new file mode 100644 index 000000000..8c244d9ef --- /dev/null +++ b/html_parsing/howlongtobeat_com/common.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from dataclasses import dataclass, field +from typing import Any + +import requests + +from seconds_to_str import seconds_to_str + + +URL_BASE = "https://howlongtobeat.com" + +USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:104.0) Gecko/20100101 Firefox/104.0" + +session = requests.session() +session.headers["User-Agent"] = USER_AGENT + + +@dataclass +class Game: + id: int + title: str + aliases: list[str] + + duration_main_seconds: int # Main Story + duration_main_title: str = field(init=False) + + duration_plus_seconds: int # Main + Sides + duration_plus_title: str = field(init=False) + + duration_100_seconds: int # Completionist + duration_100_title: str = field(init=False) + + duration_all_seconds: int # All Styles + duration_all_title: str = field(init=False) + + release_world: int + profile_platforms: list[str] + + profile_genres: list[str] = field(default_factory=list) + + def __post_init__(self) -> None: + self.duration_main_title = seconds_to_str(self.duration_main_seconds) + self.duration_plus_title = seconds_to_str(self.duration_plus_seconds) + self.duration_100_title = seconds_to_str(self.duration_100_seconds) + self.duration_all_title = seconds_to_str(self.duration_all_seconds) + + @classmethod + def parse(cls, data: dict[str, Any]) -> "Game": + return cls( + id=data["game_id"], + title=data["game_name"], + aliases=data["game_alias"].split(", "), + duration_main_seconds=data["comp_main"], + duration_plus_seconds=data["comp_plus"], + duration_100_seconds=data["comp_100"], + duration_all_seconds=data["comp_all"], + release_world=data["release_world"], + + # В случаи поиска некоторые из полей недоступны в результатах - нужно на страницу игры идти + profile_platforms=data.get("profile_platform", "").split(", "), + profile_genres=data.get("profile_genre", "").split(", "), + ) diff --git a/html_parsing/howlongtobeat_com/get_game_info.py b/html_parsing/howlongtobeat_com/get_game_info.py new file mode 100644 index 000000000..a574df17b --- /dev/null +++ b/html_parsing/howlongtobeat_com/get_game_info.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import json + +# pip install dpath +import dpath.util + +from bs4 import BeautifulSoup + +from common import session, URL_BASE, Game + + +def get_game_info(game_id: int) -> Game: + url = f"{URL_BASE}/game/{game_id}" + + rs = session.get(url) + rs.raise_for_status() + + soup = BeautifulSoup(rs.content, "html.parser") + + next_data_el = soup.select_one("#__NEXT_DATA__") + if not next_data_el: + raise Exception('Not found id="__NEXT_DATA__"!') + + next_data_str = next_data_el.string + + next_data = json.loads(next_data_str) + return Game.parse( + data=dpath.util.get(next_data, "**/data/game/0") + ) + + +if __name__ == "__main__": + print( + get_game_info(game_id=3505) + ) + # Game(id=3505, title='Final Fantasy IX', aliases=['Final Fantasy 9', 'FF9'], duration_main_seconds=138721, duration_main_title='38:32:01', duration_plus_seconds=189924, duration_plus_title='52:45:24', duration_100_seconds=299502, duration_100_title='83:11:42', duration_all_seconds=171503, duration_all_title='47:38:23', release_world='2000-02-16', profile_platforms=['Mobile', 'Nintendo Switch', 'PC', 'PlayStation', 'PlayStation 4', 'Xbox One'], profile_genres=['Role-Playing']) + + print( + get_game_info(game_id=3519) + ) + # Game(id=3519, title='Final Fantasy VI', aliases=['Final Fantasy III [NA]', 'Final Fantasy 3 [NA]', 'Final Fantasy 6', 'FF6', 'Final Fantasy VI Advance', 'Final Fantasy VI: Pixel Remaster'], duration_main_seconds=124906, duration_main_title='34:41:46', duration_plus_seconds=147288, duration_plus_title='40:54:48', duration_100_seconds=220653, duration_100_title='61:17:33', duration_all_seconds=148583, duration_all_title='41:16:23', release_world='1994-04-02', profile_platforms=['Game Boy Advance', 'Mobile', 'Nintendo Switch', 'PC', 'PlayStation', 'PlayStation 4', 'Super Nintendo'], profile_genres=['Turn-Based', 'Role-Playing']) diff --git a/html_parsing/howlongtobeat_com/search.py b/html_parsing/howlongtobeat_com/search.py index 9c395921c..8b4abb967 100644 --- a/html_parsing/howlongtobeat_com/search.py +++ b/html_parsing/howlongtobeat_com/search.py @@ -1,74 +1,100 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -from dataclasses import dataclass, field -from typing import Any - -import requests - -from seconds_to_str import seconds_to_str - - -@dataclass -class Game: - id: int - title: str - aliases: list[str] - - duration_main_seconds: int # Main Story - duration_main_title: str = field(init=False) - - duration_plus_seconds: int # Main + Sides - duration_plus_title: str = field(init=False) +# import re # TODO: Удалить - duration_100_seconds: int # Completionist - duration_100_title: str = field(init=False) +from datetime import datetime - duration_all_seconds: int # All Styles - duration_all_title: str = field(init=False) - - release_world: int - profile_platforms: list[str] - - def __post_init__(self): - self.duration_main_title = seconds_to_str(self.duration_main_seconds) - self.duration_plus_title = seconds_to_str(self.duration_plus_seconds) - self.duration_100_title = seconds_to_str(self.duration_100_seconds) - self.duration_all_title = seconds_to_str(self.duration_all_seconds) - - @classmethod - def parse(cls, data: dict[str, Any]) -> 'Game': - return cls( - id=data['game_id'], - title=data['game_name'], - aliases=data['game_alias'].split(', '), - duration_main_seconds=data['comp_main'], - duration_plus_seconds=data['comp_plus'], - duration_100_seconds=data['comp_100'], - duration_all_seconds=data['comp_all'], - release_world=data['release_world'], - profile_platforms=data['profile_platform'].split(', '), - ) - - -USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:104.0) Gecko/20100101 Firefox/104.0' +# from urllib.parse import urljoin # TODO: Удалить +from typing import Any -URL_BASE = 'https://howlongtobeat.com' -URL_SEARCH = f'{URL_BASE}/api/search' +from common import URL_BASE, session, Game -session = requests.session() -session.headers['User-Agent'] = USER_AGENT +def api_search(text: str, page: int = 1) -> dict[str, Any]: + url_first = f"{URL_BASE}/?q={text}" + rs = session.get(url_first) + rs.raise_for_status() -def api_search(text: str, page: int = 1) -> dict[str, Any]: - headers = { - 'Referer': f'{URL_BASE}/?q={text}', - } - data = { + headers = {"Referer": url_first} + + # TODO: Перестала быть рабочей. Работа API теперь в файлах вида "/_next/static/chunks/\w+.js" + # # NOTE: Получение url из js. Старая защита. Возможно, будет комбинироваться с новой + # m = re.search(r' + + + + + +
+

{{ title }}

+ +
+
+ + + + {% for header in headers %} + + {% endfor %} + + + +
{{ header }}
+
+
+
+ +
+
+ +
+
+
+
+ + + \ No newline at end of file diff --git "a/https_\320\263\320\270\320\261\320\264\320\264_\321\200\321\204__crash_statistics/web.py" "b/https_\320\263\320\270\320\261\320\264\320\264_\321\200\321\204__crash_statistics/web.py" index 2953120cd..684a9e88d 100644 --- "a/https_\320\263\320\270\320\261\320\264\320\264_\321\200\321\204__crash_statistics/web.py" +++ "b/https_\320\263\320\270\320\261\320\264\320\264_\321\200\321\204__crash_statistics/web.py" @@ -1,178 +1,51 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -import datetime as DT +import datetime as dt -from flask import Flask, render_template_string +from flask import Flask, render_template from common import get_crash_statistics_list_db app = Flask(__name__) -@app.route('/') +@app.route("/") def index(): headers = ["Дата", "ДТП", "Погибли", "Погибло детей", "Ранены", "Ранено детей"] rows = get_crash_statistics_list_db() rows.reverse() - data = [] - for date, dtp, died, children_died, wounded, wounded_children in rows: + data: list[dict] = [] + year_by_number: dict[int, int] = dict() + for date_str, dtp, died, children_died, wounded, wounded_children in rows: + date = dt.datetime.strptime(date_str, "%d.%m.%Y").date() + dtp = int(dtp) + data.append({ - 'date': date, - 'date_iso': DT.datetime.strptime(date, '%d.%m.%Y').date().isoformat(), - 'dtp': int(dtp), - 'died': int(died), - 'children_died': int(children_died), - 'wounded': int(wounded), - 'wounded_children': int(wounded_children), + "date": date_str, + "date_iso": date.isoformat(), + "dtp": dtp, + "died": int(died), + "children_died": int(children_died), + "wounded": int(wounded), + "wounded_children": int(wounded_children), }) - return render_template_string(""" - - - - - {{ title }} - - - - - - - - -
-

{{ title }}

- -
-
- - - - {% for header in headers %} - - {% endfor %} - - - -
{{ header }}
-
-
- -
-
-
- - - - """, title="АВАРИЙНОСТЬ НА ДОРОГАХ РОССИИ", headers=headers, data=data -) + if date.year not in year_by_number: + year_by_number[date.year] = 0 + year_by_number[date.year] += dtp + + return render_template( + "index.html", + title="АВАРИЙНОСТЬ НА ДОРОГАХ РОССИИ", + headers=headers, + data=data, + year_by_number=year_by_number, + ) if __name__ == "__main__": diff --git "a/https_\320\263\320\270\320\261\320\264\320\264_\321\200\321\204_request_main__auto_fill_fields__web_gui/main.py" "b/https_\320\263\320\270\320\261\320\264\320\264_\321\200\321\204_request_main__auto_fill_fields__web_gui/main.py" index 5188ac0e2..f3278d76b 100644 --- "a/https_\320\263\320\270\320\261\320\264\320\264_\321\200\321\204_request_main__auto_fill_fields__web_gui/main.py" +++ "b/https_\320\263\320\270\320\261\320\264\320\264_\321\200\321\204_request_main__auto_fill_fields__web_gui/main.py" @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import sys @@ -14,12 +14,12 @@ from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEnginePage -def log_uncaught_exceptions(ex_cls, ex, tb): - text = '{}: {}:\n'.format(ex_cls.__name__, ex) - text += ''.join(traceback.format_tb(tb)) +def log_uncaught_exceptions(ex_cls, ex, tb) -> None: + text = f"{ex_cls.__name__}: {ex}:\n" + text += "".join(traceback.format_tb(tb)) print(text) - QMessageBox.critical(None, 'Error', text) + QMessageBox.critical(None, "Error", text) sys.exit() @@ -64,16 +64,13 @@ def log_uncaught_exceptions(ex_cls, ex, tb): """ - def run_js_code(page: QWebEnginePage, code: str) -> object: loop = QEventLoop() - result_value = { - 'value': None - } + result_value = {"value": None} - def _on_callback(result: object): - result_value['value'] = result + def _on_callback(result: object) -> None: + result_value["value"] = result loop.quit() @@ -81,15 +78,24 @@ def _on_callback(result: object): loop.exec() - return result_value['value'] + return result_value["value"] class MyWebEnginePage(QWebEnginePage): - def javaScriptConsoleMessage(self, level: 'JavaScriptConsoleMessage', message: str, line_number: int, source_id: str): - print(f'javascript_console_message: {level}, {message}, {line_number}, {source_id}', file=sys.stderr) - - -with open('js/jquery-3.1.1.min.js') as f: + def javaScriptConsoleMessage( + self, + level: "JavaScriptConsoleMessage", + message: str, + line_number: int, + source_id: str, + ) -> None: + print( + f"javascript_console_message: {level}, {message}, {line_number}, {source_id}", + file=sys.stderr, + ) + + +with open("js/jquery-3.1.1.min.js") as f: jquery_text = f.read() jquery_text += "\nvar qt = { 'jQuery': jQuery.noConflict(true) };" @@ -103,7 +109,7 @@ def javaScriptConsoleMessage(self, level: 'JavaScriptConsoleMessage', message: s # mw.setWindowTitle(str(url)) -url = 'https://гибдд.рф/request_main' +url = "https://гибдд.рф/request_main" page = MyWebEnginePage() page.load(QUrl(url)) @@ -114,17 +120,17 @@ def javaScriptConsoleMessage(self, level: 'JavaScriptConsoleMessage', message: s # page.urlChanged.connect(_on_url_changed) -def _on_load_finished(ok: bool): +def _on_load_finished(ok: bool) -> None: print(page.url().toString()) page.runJavaScript(jquery_text) result = run_js_code(page, "document.title") - print('run_java_script:', result) + print("run_java_script:", result) # TODO: обернуть в функцию has с css-selector result = run_js_code(page, "qt.jQuery('#surname_check').length > 0") - print('run_java_script:', result) + print("run_java_script:", result) # Клик на флажок "С информацией ознакомлен" run_js_code(page, """qt.jQuery('input[name="agree"]').click();""") @@ -138,7 +144,10 @@ def _on_load_finished(ok: bool): run_js_code(page, """qt.jQuery('#email_check').val('FOOBAR@EMAIL.COM');""") - run_js_code(page, """qt.jQuery('#message_check > textarea').val({});""".format(repr(TEXT_PATTERN))) + run_js_code( + page, + f"qt.jQuery('#message_check > textarea').val({TEXT_PATTERN!r});", + ) # code = """ # console.log('testetst'); @@ -159,7 +168,9 @@ def _on_load_finished(ok: bool): print() -view.loadProgress.connect(lambda value: mw.setWindowTitle('{} ({}%)'.format(view.url().toString(), value))) +view.loadProgress.connect( + lambda value: mw.setWindowTitle(f"{view.url().toString()} ({value}%)") +) view.loadFinished.connect(_on_load_finished) mw = QMainWindow() diff --git a/human__timestamp_to_date_string_format.py b/human__timestamp_to_date_string_format.py index da724d428..d0738ca77 100644 --- a/human__timestamp_to_date_string_format.py +++ b/human__timestamp_to_date_string_format.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://ru.stackoverflow.com/questions/843386/ @@ -9,20 +9,20 @@ def timestamp_to_human(time_s): items = [ - (31536000, '{} г. '), - (2592000, '{} мес. '), - (604800, '{} нед. '), - (86400, '{} д. '), - (3600, '{} ч.'), + (31536000, "{} г. "), + (2592000, "{} мес. "), + (604800, "{} нед. "), + (86400, "{} д. "), + (3600, "{} ч."), ] if time_s == 0: return None if time_s < 3600: - return 'меньше часа' + return "меньше часа" - result = '' + result = "" for value, fmt in items: if time_s >= value: @@ -32,9 +32,9 @@ def timestamp_to_human(time_s): return result -if __name__ == '__main__': +if __name__ == "__main__": print(timestamp_to_human(2827567.5759670734)) # 1 мес. 2 д. 17 ч. assert timestamp_to_human(2827567.5759670734) == "1 мес. 2 д. 17 ч." - print(timestamp_to_human(269649.6857390404)) # 3 д. 2 ч. + print(timestamp_to_human(269649.6857390404)) # 3 д. 2 ч. assert timestamp_to_human(269649.6857390404) == "3 д. 2 ч." diff --git a/human_byte_size.py b/human_byte_size.py index bf3eb0e56..964f17b9f 100644 --- a/human_byte_size.py +++ b/human_byte_size.py @@ -1,28 +1,26 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -from typing import Union - - -def sizeof_fmt(num: Union[int, float]) -> str: - for x in ['bytes', 'KB', 'MB', 'GB']: +def sizeof_fmt(num: int | float) -> str: + for x in ["bytes", "KB", "MB", "GB"]: if num < 1024.0: return "%.1f %s" % (num, x) num /= 1024.0 - return "%.1f %s" % (num, 'TB') + return "%.1f %s" % (num, "TB") -if __name__ == '__main__': +if __name__ == "__main__": print(sizeof_fmt(25000000000)) print() import shutil - usage = shutil.disk_usage('C://') - print('total: {:>8} ({} bytes)'.format(sizeof_fmt(usage.total), usage.total)) - print('used: {:>8} ({} bytes)'.format(sizeof_fmt(usage.used), usage.used)) - print('free: {:>8} ({} bytes)'.format(sizeof_fmt(usage.free), usage.free)) + + usage = shutil.disk_usage("C://") + print(f"total: {sizeof_fmt(usage.total):>8} ({usage.total} bytes)") + print(f"used: {sizeof_fmt(usage.used):>8} ({usage.used} bytes)") + print(f"free: {sizeof_fmt(usage.free):>8} ({usage.free} bytes)") diff --git a/human_byte_size__using_humanize.py b/human_byte_size__using_humanize.py index 56fad2941..7ab4add8b 100644 --- a/human_byte_size__using_humanize.py +++ b/human_byte_size__using_humanize.py @@ -1,19 +1,28 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" +import shutil + # pip install humanize import humanize -print(humanize.naturalsize(25000000000, binary=True)) # '23.3 GiB' -print(humanize.naturalsize(25000000000, binary=True).replace('i', '')) # '23.3 GB', Strange, misleading +print(humanize.naturalsize(25000000000, binary=True)) # '23.3 GiB' +print( + humanize.naturalsize(25000000000, binary=True).replace("i", "") +) # '23.3 GB', Strange, misleading print() -import shutil -usage = shutil.disk_usage('C://') -print('total: {:>8} ({} bytes)'.format(humanize.naturalsize(usage.total, binary=True), usage.total)) -print('used: {:>8} ({} bytes)'.format(humanize.naturalsize(usage.used, binary=True), usage.used)) -print('free: {:>8} ({} bytes)'.format(humanize.naturalsize(usage.free, binary=True), usage.free)) +usage = shutil.disk_usage("C://") +print( + f"total: {humanize.naturalsize(usage.total, binary=True):>8} ({usage.total} bytes)" +) +print( + f"used: {humanize.naturalsize(usage.used, binary=True):>8} ({usage.used} bytes)" +) +print( + f"free: {humanize.naturalsize(usage.free, binary=True):>8} ({usage.free} bytes)" +) diff --git a/human_format_number__1000_separator.py b/human_format_number__1000_separator.py index 9805b833d..c7e8f96b5 100644 --- a/human_format_number__1000_separator.py +++ b/human_format_number__1000_separator.py @@ -1,10 +1,10 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -print('{:,d}'.format(5213234)) # 5,213,234 -print('{:,d}'.format(123)) # 123 -print('{:,d}'.format(1004)) # 1,004 -print('{:,d}'.format(4321)) # 4,321 +print(f"{5213234:,d}") # 5,213,234 +print(f"{123:,d}") # 123 +print(f"{1004:,d}") # 1,004 +print(f"{4321:,d}") # 4,321 diff --git a/humanize__examples/date_and_time__humanization.py b/humanize__examples/date_and_time__humanization.py index 6f1c97951..6710dd1ec 100644 --- a/humanize__examples/date_and_time__humanization.py +++ b/humanize__examples/date_and_time__humanization.py @@ -1,34 +1,38 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/jmoiron/humanize +import datetime as dt + # pip install humanize import humanize -# Date & time humanization: -import datetime as DT -print(humanize.naturalday(DT.datetime.now())) # 'today' -print(humanize.naturalday(DT.datetime.now() - DT.timedelta(days=1))) # 'yesterday' -print(humanize.naturalday(DT.date(2007, 6, 5))) # 'Jun 05' +# Date & time humanization +print(humanize.naturalday(dt.datetime.now())) # 'today' +print(humanize.naturalday(dt.datetime.now() - dt.timedelta(days=1))) # 'yesterday' +print(humanize.naturalday(dt.date(2007, 6, 5))) # 'Jun 05' print() -print(DT.timedelta(seconds=1001)) # '0:16:41' -print(humanize.naturaldelta(DT.timedelta(seconds=1001))) # '16 minutes' -print(humanize.naturaldelta(DT.timedelta(seconds=5))) # '5 seconds' -print(humanize.naturaldelta(DT.timedelta(hours=30))) # 'a day' -print(humanize.naturaldelta(DT.timedelta(hours=60))) # '2 days' +print(dt.timedelta(seconds=1001)) # '0:16:41' +print(humanize.naturaldelta(dt.timedelta(seconds=1001))) # '16 minutes' +print(humanize.naturaldelta(dt.timedelta(seconds=5))) # '5 seconds' +print(humanize.naturaldelta(dt.timedelta(hours=30))) # 'a day' +print(humanize.naturaldelta(dt.timedelta(hours=60))) # '2 days' print() -print(humanize.naturaldate(DT.date(2007, 6, 5))) # 'Jun 05 2007' -print(humanize.naturaldate(DT.date(2007, 6, 5))) # 'Jun 05 2007' +print(humanize.naturaldate(dt.date(2007, 6, 5))) # 'Jun 05 2007' +print(humanize.naturaldate(dt.date(2007, 6, 5))) # 'Jun 05 2007' print() -print(humanize.naturaltime(DT.datetime.now() - DT.timedelta(seconds=1))) # 'a second ago' -print(humanize.naturaltime(DT.datetime.now() - DT.timedelta(seconds=3600))) # 'an hour ago' -print(humanize.naturaltime(DT.datetime.now() - DT.timedelta(hours=30))) # 'a day ago' +print(humanize.naturaltime(dt.datetime.now() - dt.timedelta(seconds=1))) +# 'a second ago' +print(humanize.naturaltime(dt.datetime.now() - dt.timedelta(seconds=3600))) +# 'an hour ago' +print(humanize.naturaltime(dt.datetime.now() - dt.timedelta(hours=30))) +# 'a day ago' diff --git a/humanize__examples/file_size__humanization.py b/humanize__examples/file_size__humanization.py index c9f20d515..467a1ac63 100644 --- a/humanize__examples/file_size__humanization.py +++ b/humanize__examples/file_size__humanization.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/jmoiron/humanize @@ -12,7 +12,7 @@ # File size humanization: -print(humanize.naturalsize(100)) # '100 Bytes' -print(humanize.naturalsize(10 ** 9)) # '1.0 GB' -print(humanize.naturalsize(10 ** 9, binary=True)) # '953.7 MiB' -print(humanize.naturalsize(10 ** 9, gnu=True)) # '953.7M' +print(humanize.naturalsize(100)) # '100 Bytes' +print(humanize.naturalsize(10**9)) # '1.0 GB' +print(humanize.naturalsize(10**9, binary=True)) # '953.7 MiB' +print(humanize.naturalsize(10**9, gnu=True)) # '953.7M' diff --git a/humanize__examples/integer__humanization.py b/humanize__examples/integer__humanization.py index eab43bbe4..74741cbc8 100644 --- a/humanize__examples/integer__humanization.py +++ b/humanize__examples/integer__humanization.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/jmoiron/humanize @@ -12,15 +12,15 @@ # Integer humanization: -print(humanize.intcomma(12345)) # '12,345' +print(humanize.intcomma(12345)) # '12,345' print(humanize.intcomma(123456789)) # '123,456,789' print() -print(humanize.intword(123455913)) # '123.5 million' -print(humanize.intword(12345591313)) # '12.3 billion' +print(humanize.intword(123455913)) # '123.5 million' +print(humanize.intword(12345591313)) # '12.3 billion' print(humanize.intword(1339014900000)) # '1.3 trillion' print() -print(humanize.apnumber(4)) # 'four' -print(humanize.apnumber(7)) # 'seven' +print(humanize.apnumber(4)) # 'four' +print(humanize.apnumber(7)) # 'seven' print(humanize.apnumber(41)) # '41' diff --git a/humanize__examples/localization__change_locale_in_runtime.py b/humanize__examples/localization__change_locale_in_runtime.py index 210973124..4069cc376 100644 --- a/humanize__examples/localization__change_locale_in_runtime.py +++ b/humanize__examples/localization__change_locale_in_runtime.py @@ -1,41 +1,42 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/jmoiron/humanize +import datetime as dt + # pip install humanize import humanize -import datetime as DT # Localization. How to change locale in runtime -print(humanize.naturaltime(DT.timedelta(seconds=3))) # '3 seconds ago' -print(humanize.intword(123455913)) # '123.5 million' -print(humanize.intword(12345591313)) # '12.3 billion' +print(humanize.naturaltime(dt.timedelta(seconds=3))) # '3 seconds ago' +print(humanize.intword(123455913)) # '123.5 million' +print(humanize.intword(12345591313)) # '12.3 billion' print(humanize.intword(1339014900000)) # '1.3 trillion' -print(humanize.apnumber(4)) # 'four' -print(humanize.apnumber(7)) # 'seven' +print(humanize.apnumber(4)) # 'four' +print(humanize.apnumber(7)) # 'seven' print() -_t = humanize.i18n.activate('ru_RU') +_t = humanize.i18n.activate("ru_RU") -print(humanize.naturaltime(DT.timedelta(seconds=3))) # '3 секунды назад' -print(humanize.intword(123455913)) # '123.5 миллиона' -print(humanize.intword(12345591313)) # '12.3 миллиарда' +print(humanize.naturaltime(dt.timedelta(seconds=3))) # '3 секунды назад' +print(humanize.intword(123455913)) # '123.5 миллиона' +print(humanize.intword(12345591313)) # '12.3 миллиарда' print(humanize.intword(1339014900000)) # '1.3 триллиона' -print(humanize.apnumber(4)) # 'четыре' -print(humanize.apnumber(7)) # 'семь' +print(humanize.apnumber(4)) # 'четыре' +print(humanize.apnumber(7)) # 'семь' print() humanize.i18n.deactivate() -print(humanize.naturaltime(DT.timedelta(seconds=3))) # '3 seconds ago' -print(humanize.intword(123455913)) # '123.5 million' -print(humanize.intword(12345591313)) # '12.3 billion' +print(humanize.naturaltime(dt.timedelta(seconds=3))) # '3 seconds ago' +print(humanize.intword(123455913)) # '123.5 million' +print(humanize.intword(12345591313)) # '12.3 billion' print(humanize.intword(1339014900000)) # '1.3 trillion' -print(humanize.apnumber(4)) # 'four' -print(humanize.apnumber(7)) # 'seven' +print(humanize.apnumber(4)) # 'four' +print(humanize.apnumber(7)) # 'seven' diff --git a/i_watching_u.py b/i_watching_u.py index e6cd4d033..09b45012e 100644 --- a/i_watching_u.py +++ b/i_watching_u.py @@ -1,18 +1,20 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """Программа делает скриншоты, сохраняет их в zip-архив.""" -if __name__ == '__main__': - from PIL import ImageGrab - from datetime import datetime +if __name__ == "__main__": import time - from zipfile import ZipFile + + from datetime import datetime from io import BytesIO + from zipfile import ZipFile + + from PIL import ImageGrab # Сбор статистики о действиях пользователя @@ -21,32 +23,34 @@ today = datetime.today() # Получаем строку текущего времени - str_today_time = today.strftime('%H.%M.%S') + str_today_time = today.strftime("%H.%M.%S") # Имя zip-архива - zip_file_name = 'screenshots_' + today.strftime('%d.%m.%Y') + '.zip' + zip_file_name = "screenshots_" + today.strftime("%d.%m.%Y") + ".zip" # Составляем имя файла, содержащее в названии текущее время - file_name = 'screenshot_{}.png'.format(str_today_time) + file_name = f"screenshot_{str_today_time}.png" # Делаем скриншот im = ImageGrab.grab() - print('Сделан скриншот "{}"'.format(file_name)) + print(f'Сделан скриншот "{file_name}"') # Открываем zip-файл - with ZipFile(zip_file_name, 'a') as zip_file: + with ZipFile(zip_file_name, "a") as zip_file: # Сохраняем изображение в буфер io = BytesIO() - im.save(io, 'png') - + im.save(io, "png") + # Байты изображения image_bytes = io.getvalue() # Добавляем изображение в архив zip_file.writestr(file_name, image_bytes) - print(' Скриншот "{}" добавлен в архив "{}"\n'.format(file_name, zip_file_name)) + print( + f' Скриншот "{file_name}" добавлен в архив "{zip_file_name}"\n' + ) # Ожидание каждые 15 минут time.sleep(15 * 60) diff --git a/imagehash__examples/find_similar_images__phash.py b/imagehash__examples/find_similar_images__phash.py index 5c3f27bd1..139d3d426 100644 --- a/imagehash__examples/find_similar_images__phash.py +++ b/imagehash__examples/find_similar_images__phash.py @@ -1,14 +1,14 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/JohannesBuchner/imagehash -from glob import glob import itertools +from glob import glob # pip install imagehash import imagehash @@ -17,8 +17,8 @@ from PIL import Image -file_names = glob('D:\все фотки\**\*.jpg', recursive=True) -print(f'Files: {len(file_names)}') +file_names = glob(r"D:\все фотки\**\*.jpg", recursive=True) +print(f"Files: {len(file_names)}") img_by_hash = dict() @@ -31,7 +31,7 @@ img_by_hash[file_name] = hash_img -print('Find similar images') +print("Find similar images") for img_1, img_2 in itertools.combinations(img_by_hash.items(), 2): file_name_1, hash_img_1 = img_1 diff --git a/imagehash__examples/find_similar_images_with_group_and_sort__phash.py b/imagehash__examples/find_similar_images_with_group_and_sort__phash.py index 69780bde2..25b5f54e4 100644 --- a/imagehash__examples/find_similar_images_with_group_and_sort__phash.py +++ b/imagehash__examples/find_similar_images_with_group_and_sort__phash.py @@ -1,15 +1,16 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/JohannesBuchner/imagehash +import itertools + from collections import defaultdict from glob import glob -import itertools # pip install imagehash import imagehash @@ -18,8 +19,8 @@ from PIL import Image -file_names = glob('D:\все фотки\**\*.jpg', recursive=True) -print(f'Files: {len(file_names)}') +file_names = glob(r"D:\все фотки\**\*.jpg", recursive=True) +print(f"Files: {len(file_names)}") img_by_hash = dict() @@ -32,7 +33,7 @@ img_by_hash[file_name] = hash_img -print('\nFind similar images') +print("\nFind similar images") file_name_by_similars = defaultdict(list) diff --git a/imagehash__examples/greedy_find_similar_images_with_group_and_sort__phash.py b/imagehash__examples/greedy_find_similar_images_with_group_and_sort__phash.py index bfec48b2a..9bdbedc92 100644 --- a/imagehash__examples/greedy_find_similar_images_with_group_and_sort__phash.py +++ b/imagehash__examples/greedy_find_similar_images_with_group_and_sort__phash.py @@ -1,15 +1,16 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/JohannesBuchner/imagehash +import itertools + from collections import defaultdict from glob import glob -import itertools # pip install imagehash import imagehash @@ -18,8 +19,8 @@ from PIL import Image -file_names = glob('D:\все фотки\**\*.jpg', recursive=True) -print(f'Files: {len(file_names)}') +file_names = glob(r"D:\все фотки\**\*.jpg", recursive=True) +print(f"Files: {len(file_names)}") img_by_hash = dict() @@ -32,7 +33,7 @@ img_by_hash[file_name] = hash_img -print('\nFind similar images') +print("\nFind similar images") file_name_by_similars = defaultdict(list) diff --git a/img_to_base64_html/main.py b/img_to_base64_html/main.py index d011e3e27..e123b21d7 100644 --- a/img_to_base64_html/main.py +++ b/img_to_base64_html/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import base64 @@ -11,14 +11,14 @@ from PIL import Image -def img_to_base64_html(file_name__or__bytes__or__file_object): +def img_to_base64_html(file_name__or__bytes__or__file_object: str | bytes | io.IOBase) -> str: arg = file_name__or__bytes__or__file_object - if type(arg) == str: - with open(arg, mode='rb') as f: + if isinstance(arg, str): + with open(arg, mode="rb") as f: img_bytes = f.read() - elif type(arg) == bytes: + elif isinstance(arg, bytes): img_bytes = arg else: @@ -27,14 +27,14 @@ def img_to_base64_html(file_name__or__bytes__or__file_object): bytes_io = io.BytesIO(img_bytes) img = Image.open(bytes_io) - img_base64 = base64.b64encode(img_bytes).decode('utf-8') - return f'data:image/{img.format.lower()};base64,{img_base64}' + img_base64 = base64.b64encode(img_bytes).decode("utf-8") + return f"data:image/{img.format.lower()};base64,{img_base64}" -if __name__ == '__main__': - file_name = 'img.jpg' +if __name__ == "__main__": + file_name = "img.jpg" img_base64 = img_to_base64_html(file_name) - print(f'[len {len(img_base64)}]: {img_base64[:50]}...') + print(f"[len {len(img_base64)}]: {img_base64[:50]}...") - with open(file_name + '_base64.txt', mode='w', encoding='utf-8') as f: + with open(file_name + "_base64.txt", mode="w", encoding="utf-8") as f: f.write(img_base64) diff --git a/incremental timeouts.py b/incremental timeouts.py index c7428222f..319f02832 100644 --- a/incremental timeouts.py +++ b/incremental timeouts.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """ @@ -10,12 +10,15 @@ """ +import random +import time + + def work(): - import random if random.randint(0, 5): raise Exception() - print('Work!') + print("Work!") timeout = 0.5 @@ -29,9 +32,7 @@ def work(): break except: - print('Неудача с таймаутом {}.'.format(timeout)) + print(f"Неудача с таймаутом {timeout}.") - import time time.sleep(timeout) timeout += 0.1 - diff --git a/infinity_iterator.py b/infinity_iterator.py index c7a440a4f..5d3d98e7b 100644 --- a/infinity_iterator.py +++ b/infinity_iterator.py @@ -1,14 +1,15 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" def inf_it() -> iter: return iter(lambda: 0, 1) -if __name__ == '__main__': +if __name__ == "__main__": from tqdm import tqdm + for _ in tqdm(inf_it()): pass diff --git a/info_from_bitkurs.ru.py b/info_from_bitkurs.ru.py index f1c587f6b..6803a9b0e 100644 --- a/info_from_bitkurs.ru.py +++ b/info_from_bitkurs.ru.py @@ -1,20 +1,20 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import re import grab -URL = 'http://bitkurs.ru/' +URL = "http://bitkurs.ru/" # Copy page yandex # URL = 'http://hghltd.yandex.net/yandbtm?fmode=inject&url=http%3A%2F%2Fbitkurs.ru%2F&tld=ru&lang=ru&la=1453455360&tm=1454924280&text=bitkurs.ru&l10n=ru&mime=html&sign=2105e628b413520a0e087ae4ac0db180&keyno=0' g = grab.Grab() -g.setup(proxy='proxy.compassplus.ru:3128', proxy_type='http') +g.setup(proxy="proxy.compassplus.ru:3128", proxy_type="http") g.go(URL) #
@@ -30,14 +30,15 @@ def get_amt(text): # Удаление всех символов, кроме цифр и точки - text = re.sub('[^\\d\.]+', '', text) + text = re.sub(r"[^\d.]+", "", text) # Вытаскивание строки, описывающей число - text = re.search('\\d+(\.\\d*)?', text).group() + text = re.search(r"\d+(\.\d*)?", text).group() return text + usd = get_amt(usd) rub = get_amt(rub) eur = get_amt(eur) -print('1 BTC:\n {} USD\n {} RUB\n {} EUR'.format(usd, rub, eur)) +print(f"1 BTC:\n {usd} USD\n {rub} RUB\n {eur} EUR") diff --git a/ini__configparser__examples/hello_world.py b/ini__configparser__examples/hello_world.py index 23eb83e61..ba4269454 100644 --- a/ini__configparser__examples/hello_world.py +++ b/ini__configparser__examples/hello_world.py @@ -1,36 +1,36 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import configparser ini = configparser.ConfigParser() -ini['Default'] = { - 'x': 10, - 'y': 15, - 'z': 3, +ini["Default"] = { + "x": 10, + "y": 15, + "z": 3, } -ini['Additional'] = {} -additional = ini['Additional'] -additional['top'] = str(True) -additional['text'] = "Hello World!" -additional['arrays'] = str([1, 2, 3, 4, 5]) +ini["Additional"] = {} +additional = ini["Additional"] +additional["top"] = str(True) +additional["text"] = "Hello World!" +additional["arrays"] = str([1, 2, 3, 4, 5]) -ini['Empty'] = {} +ini["Empty"] = {} -with open('config.ini', 'w') as f: +with open("config.ini", "w") as f: ini.write(f) ini_read = configparser.ConfigParser() -ini_read.read('config.ini') +ini_read.read("config.ini") print(ini_read.sections()) -print(ini_read['Additional']['top']) -print(ini_read['Additional']['text']) -print(ini_read['Additional']['arrays']) -print(ini_read['Additional']['arrays'].replace('[', '').replace(']', '').split(', ')) +print(ini_read["Additional"]["top"]) +print(ini_read["Additional"]["text"]) +print(ini_read["Additional"]["arrays"]) +print(ini_read["Additional"]["arrays"].replace("[", "").replace("]", "").split(", ")) diff --git a/ini__configparser__examples/read__list_of_dicts.py b/ini__configparser__examples/read__list_of_dicts.py index e8d396fc6..c0d427268 100644 --- a/ini__configparser__examples/read__list_of_dicts.py +++ b/ini__configparser__examples/read__list_of_dicts.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://ru.stackoverflow.com/q/1270426/201445 @@ -11,9 +11,9 @@ config = configparser.ConfigParser() -config.read('example.ini') +config.read("example.ini") -print(dict(config['DEFAULT'])) +print(dict(config["DEFAULT"])) for section in config.sections(): print(dict(config[section])) diff --git a/ini__configparser__examples/write__list_of_dicts.py b/ini__configparser__examples/write__list_of_dicts.py index ddd72b078..2485467fb 100644 --- a/ini__configparser__examples/write__list_of_dicts.py +++ b/ini__configparser__examples/write__list_of_dicts.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://ru.stackoverflow.com/q/1270426/201445 @@ -11,17 +11,42 @@ dict1 = [ - {'object': 'label', 'icon': 'icon', 'icon_hover': 'icon_hover', 'command': 'command'}, - {'object': 'button', 'icon': 'icon', 'icon_hover': 'icon_hover', 'command': 'command'}, - {'object': 'color', 'icon': 'icon', 'icon_hover': 'icon_hover', 'command': 'command'}, - {'object': 'line', 'icon': 'icon', 'icon_hover': 'icon_hover', 'command': 'command'}, - {'object': 'unknown', 'icon': 'icon', 'icon_hover': 'icon_hover', 'command': 'command'}, + { + "object": "label", + "icon": "icon", + "icon_hover": "icon_hover", + "command": "command", + }, + { + "object": "button", + "icon": "icon", + "icon_hover": "icon_hover", + "command": "command", + }, + { + "object": "color", + "icon": "icon", + "icon_hover": "icon_hover", + "command": "command", + }, + { + "object": "line", + "icon": "icon", + "icon_hover": "icon_hover", + "command": "command", + }, + { + "object": "unknown", + "icon": "icon", + "icon_hover": "icon_hover", + "command": "command", + }, ] config = configparser.ConfigParser() for d in dict1: - config[d['object']] = d + config[d["object"]] = d -with open('example.ini', 'w') as f: +with open("example.ini", "w") as f: config.write(f) diff --git a/input_email_list.py b/input_email_list.py index 541fed476..5f365969a 100644 --- a/input_email_list.py +++ b/input_email_list.py @@ -1,12 +1,13 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # http://stackoverflow.com/questions/35332611/a-program-which-creates-emails -def fill_list(l, promt): + +def fill_list(l, promt) -> None: while True: x = input(promt) if not x: @@ -15,15 +16,15 @@ def fill_list(l, promt): l.append(x) -if __name__ == '__main__': +if __name__ == "__main__": first_names = list() last_names = list() fill_list(first_names, "Input first name: ") - print('first_names: {}'.format(first_names)) + print(f"first_names: {first_names}") fill_list(last_names, "Input last name: ") - print('last_names: {}'.format(last_names)) + print(f"last_names: {last_names}") for first, last in zip(first_names, last_names): - print('{}.{}@abc.mail.com'.format(first, last)) + print(f"{first}.{last}@abc.mail.com") diff --git a/inputs__examples/read_events_from_gamepad.py b/inputs__examples/read_events_from_gamepad.py new file mode 100644 index 000000000..1fd065a29 --- /dev/null +++ b/inputs__examples/read_events_from_gamepad.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +# pip install inputs +from inputs import get_gamepad + + +if __name__ == "__main__": + while True: + events = get_gamepad() + for event in events: + print(event.ev_type, event.code, event.state) diff --git a/inputs__examples/show_all_devices.py b/inputs__examples/show_all_devices.py new file mode 100644 index 000000000..ff435b884 --- /dev/null +++ b/inputs__examples/show_all_devices.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import time + +# pip install inputs +from inputs import DeviceManager + + +while True: + devices = DeviceManager() + print( + f"keyboards: {len(devices.keyboards)}, mice: {len(devices.mice)}, " + f"gamepads: {len(devices.gamepads)}, microbits: {len(devices.microbits)}, " + f"leds: {len(devices.leds)}, other_devices: {len(devices.other_devices)}, " + f"all_devices: {len(devices.all_devices)}" + ) + time.sleep(1) diff --git a/inspect__examples/check_is_empty_function__use__inspect__dis.py b/inspect__examples/check_is_empty_function__use__inspect__dis.py index 53900e818..bc9188079 100644 --- a/inspect__examples/check_is_empty_function__use__inspect__dis.py +++ b/inspect__examples/check_is_empty_function__use__inspect__dis.py @@ -1,24 +1,24 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from dis__bytecode__is_empty_function import check_is_empty_function as check_is_empty_function__use_dis from inspect__get_source__function import check_is_empty_function as check_is_empty_function__use_inspect -def foo(): +def foo() -> None: pass -def foo2(): +def foo2() -> int: return 1 -print('check_is_empty_function__use_inspect:', check_is_empty_function__use_inspect(foo)) -print('check_is_empty_function__use_inspect:', check_is_empty_function__use_inspect(foo2)) +print("check_is_empty_function__use_inspect:", check_is_empty_function__use_inspect(foo)) +print("check_is_empty_function__use_inspect:", check_is_empty_function__use_inspect(foo2)) print() -print('check_is_empty_function__use_dis:', check_is_empty_function__use_dis(foo)) -print('check_is_empty_function__use_dis:', check_is_empty_function__use_dis(foo2)) +print("check_is_empty_function__use_dis:", check_is_empty_function__use_dis(foo)) +print("check_is_empty_function__use_dis:", check_is_empty_function__use_dis(foo2)) diff --git a/inspect__examples/hello_world.py b/inspect__examples/hello_world.py index aa4c602dc..7ee1f58d3 100644 --- a/inspect__examples/hello_world.py +++ b/inspect__examples/hello_world.py @@ -1,29 +1,34 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import inspect -print('Current line:', inspect.getframeinfo(inspect.currentframe()).lineno) -print('Current line:', inspect.getframeinfo(inspect.currentframe()).lineno) -print('Current line:', inspect.getframeinfo(inspect.currentframe()).lineno) +print("Current line:", inspect.getframeinfo(inspect.currentframe()).lineno) +print("Current line:", inspect.getframeinfo(inspect.currentframe()).lineno) +print("Current line:", inspect.getframeinfo(inspect.currentframe()).lineno) print() -print('Current filename:', inspect.getframeinfo(inspect.currentframe()).filename) +print("Current filename:", inspect.getframeinfo(inspect.currentframe()).filename) print() -print('Current code context:', inspect.getframeinfo(inspect.currentframe()).code_context) +print( + "Current code context:", inspect.getframeinfo(inspect.currentframe()).code_context +) print() -print('Current function:', inspect.getframeinfo(inspect.currentframe()).function) -print('Current function:', (lambda: inspect.getframeinfo(inspect.currentframe()).function)()) +print("Current function:", inspect.getframeinfo(inspect.currentframe()).function) +print( + "Current function:", + (lambda: inspect.getframeinfo(inspect.currentframe()).function)(), +) def foo(): return inspect.getframeinfo(inspect.currentframe()).function -print('Current function:', foo()) +print("Current function:", foo()) diff --git a/inspect__examples/inspect__get_source__function.py b/inspect__examples/inspect__get_source__function.py index b17eab794..1f9cbbcba 100644 --- a/inspect__examples/inspect__get_source__function.py +++ b/inspect__examples/inspect__get_source__function.py @@ -1,21 +1,24 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" + + +import inspect def check_is_empty_function(function): - import inspect lines = inspect.getsourcelines(function) - return lines[0][-1].strip() == 'pass' + return lines[0][-1].strip() == "pass" + +if __name__ == "__main__": -if __name__ == '__main__': - def foo(): + def foo() -> None: pass - def foo2(): + def foo2() -> int: return 1 - print('Empty:', check_is_empty_function(foo)) - print('Empty:', check_is_empty_function(foo2)) + print("Empty:", check_is_empty_function(foo)) + print("Empty:", check_is_empty_function(foo2)) diff --git a/inspect__examples/is_function.py b/inspect__examples/is_function.py index 9419af8a0..ba007dc9c 100644 --- a/inspect__examples/is_function.py +++ b/inspect__examples/is_function.py @@ -1,30 +1,34 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -def foo(): +import inspect + + + +def foo() -> int: return 1 class Foo: - def __call__(self): + def __call__(self) -> int: return 1 -import inspect print(inspect.isroutine(lambda: 1)) # True -print(inspect.isroutine(foo)) # True +print(inspect.isroutine(foo)) # True print() # Functor foo = Foo() -print(inspect.isroutine(foo)) # False -print(hasattr(foo, '__call__')) # True -print(callable(foo)) # True +print(inspect.isroutine(foo)) # False +print(hasattr(foo, "__call__")) # True +print(callable(foo)) # True print() import math + print(inspect.isroutine(math.sin)) # True -print(inspect.isroutine(print)) # True +print(inspect.isroutine(print)) # True diff --git a/invert_number.py b/invert_number.py index 18fb08adf..b32538251 100644 --- a/invert_number.py +++ b/invert_number.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" def invert_number(number: int) -> int: @@ -14,18 +14,18 @@ def invert_number(number: int) -> int: :return: invert number """ - return int(''.join('1' if i == '0' else '0' for i in bin(number)[2:]), base=2) + return int("".join("1" if i == "0" else "0" for i in bin(number)[2:]), base=2) -if __name__ == '__main__': +if __name__ == "__main__": print(invert_number(0)) print(invert_number(1)) print(invert_number(7)) print(invert_number(111)) - print(invert_number(10 ** 10)) + print(invert_number(10**10)) assert invert_number(0) == 1 assert invert_number(1) == 0 assert invert_number(7) == 0 assert invert_number(111) == 16 - assert invert_number(10 ** 10) == 7179869183 + assert invert_number(10**10) == 7179869183 diff --git a/iri_to_uri__cyrillic__no_ascii_url__idna.py b/iri_to_uri__cyrillic__no_ascii_url__idna.py index 43ea72bfb..ca08bd768 100644 --- a/iri_to_uri__cyrillic__no_ascii_url__idna.py +++ b/iri_to_uri__cyrillic__no_ascii_url__idna.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://ru.stackoverflow.com/a/487922/201445 @@ -10,45 +10,53 @@ from urllib.parse import quote, urlsplit, urlunsplit -def iri_to_uri(iri): +def iri_to_uri(iri: str) -> str: parts = urlsplit(iri) - uri = urlunsplit(( - parts.scheme, - parts.netloc.encode('idna').decode('ascii'), - quote(parts.path), - quote(parts.query, '=&'), - quote(parts.fragment), - )) + uri = urlunsplit( + ( + parts.scheme, + parts.netloc.encode("idna").decode("ascii"), + quote(parts.path), + quote(parts.query, "=&"), + quote(parts.fragment), + ) + ) return uri -if __name__ == '__main__': - url = 'https://сайт.рф/документы?номер=1234&устройство=телефон' +if __name__ == "__main__": + url = "https://сайт.рф/документы?номер=1234&устройство=телефон" new_url = iri_to_uri(url) print(new_url) - assert new_url == 'https://xn--80aswg.xn--p1ai/%D0%B4%D0%BE%D0%BA%D1%83%D0%BC%D0%B5%D0%BD%D1%82%D1%8B?%D0%BD%D0%BE%D0%BC%D0%B5%D1%80=1234&%D1%83%D1%81%D1%82%D1%80%D0%BE%D0%B9%D1%81%D1%82%D0%B2%D0%BE=%D1%82%D0%B5%D0%BB%D0%B5%D1%84%D0%BE%D0%BD' + assert ( + new_url + == "https://xn--80aswg.xn--p1ai/%D0%B4%D0%BE%D0%BA%D1%83%D0%BC%D0%B5%D0%BD%D1%82%D1%8B?%D0%BD%D0%BE%D0%BC%D0%B5%D1%80=1234&%D1%83%D1%81%D1%82%D1%80%D0%BE%D0%B9%D1%81%D1%82%D0%B2%D0%BE=%D1%82%D0%B5%D0%BB%D0%B5%D1%84%D0%BE%D0%BD" + ) - url = 'https://anapa.russianrealty.ru/Продажа-квартир/' + url = "https://anapa.russianrealty.ru/Продажа-квартир/" new_url = iri_to_uri(url) print(new_url) - assert new_url == 'https://anapa.russianrealty.ru/%D0%9F%D1%80%D0%BE%D0%B4%D0%B0%D0%B6%D0%B0-%D0%BA%D0%B2%D0%B0%D1%80%D1%82%D0%B8%D1%80/' + assert ( + new_url + == "https://anapa.russianrealty.ru/%D0%9F%D1%80%D0%BE%D0%B4%D0%B0%D0%B6%D0%B0-%D0%BA%D0%B2%D0%B0%D1%80%D1%82%D0%B8%D1%80/" + ) - url = 'http://www.a\u0131b.com/a\u0131b' + url = "http://www.a\u0131b.com/a\u0131b" new_url = iri_to_uri(url) print(new_url) - assert new_url == 'http://www.xn--ab-hpa.com/a%C4%B1b' + assert new_url == "http://www.xn--ab-hpa.com/a%C4%B1b" - url = 'https://ya.ru' + url = "https://ya.ru" new_url = iri_to_uri(url) print(new_url) - assert new_url == 'https://ya.ru' + assert new_url == "https://ya.ru" - url = 'https://google.com' + url = "https://google.com" new_url = iri_to_uri(url) print(new_url) - assert new_url == 'https://google.com' + assert new_url == "https://google.com" - url = 'http://домены.рф' + url = "http://домены.рф" new_url = iri_to_uri(url) print(new_url) - assert new_url == 'http://xn--d1acufc5f.xn--p1ai' + assert new_url == "http://xn--d1acufc5f.xn--p1ai" diff --git a/iri_to_uri__get_ascii_url.py b/iri_to_uri__get_ascii_url.py index 41cf72661..e49d9da6a 100644 --- a/iri_to_uri__get_ascii_url.py +++ b/iri_to_uri__get_ascii_url.py @@ -1,53 +1,59 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -from urllib.parse import urlparse, urlunparse import re +from urllib.parse import urlparse, urlunparse def url_encode_non_ascii(b: bytes) -> bytes: - return re.sub(b'[\x80-\xFF]', lambda c: b'%%%02X' % ord(c.group(0)), b) + return re.sub(b"[\x80-\xFF]", lambda c: b"%%%02X" % ord(c.group(0)), b) # SOURCE: https://stackoverflow.com/a/4391299/5909792 def iri_to_uri(iri: str) -> str: parts = urlparse(iri) return urlunparse( - part.encode('idna') if parti == 1 else url_encode_non_ascii(part.encode('utf-8')) + part.encode("idna") if parti == 1 else url_encode_non_ascii(part.encode("utf-8")) for parti, part in enumerate(parts) - ).decode('utf-8') + ).decode("utf-8") -if __name__ == '__main__': - url = 'https://сайт.рф/документы?номер=1234&устройство=телефон' +if __name__ == "__main__": + url = "https://сайт.рф/документы?номер=1234&устройство=телефон" new_url = iri_to_uri(url) print(new_url) - assert new_url == 'https://xn--80aswg.xn--p1ai/%D0%B4%D0%BE%D0%BA%D1%83%D0%BC%D0%B5%D0%BD%D1%82%D1%8B?%D0%BD%D0%BE%D0%BC%D0%B5%D1%80=1234&%D1%83%D1%81%D1%82%D1%80%D0%BE%D0%B9%D1%81%D1%82%D0%B2%D0%BE=%D1%82%D0%B5%D0%BB%D0%B5%D1%84%D0%BE%D0%BD' + assert ( + new_url + == "https://xn--80aswg.xn--p1ai/%D0%B4%D0%BE%D0%BA%D1%83%D0%BC%D0%B5%D0%BD%D1%82%D1%8B?%D0%BD%D0%BE%D0%BC%D0%B5%D1%80=1234&%D1%83%D1%81%D1%82%D1%80%D0%BE%D0%B9%D1%81%D1%82%D0%B2%D0%BE=%D1%82%D0%B5%D0%BB%D0%B5%D1%84%D0%BE%D0%BD" + ) - url = 'https://anapa.russianrealty.ru/Продажа-квартир/' + url = "https://anapa.russianrealty.ru/Продажа-квартир/" new_url = iri_to_uri(url) print(new_url) - assert new_url == 'https://anapa.russianrealty.ru/%D0%9F%D1%80%D0%BE%D0%B4%D0%B0%D0%B6%D0%B0-%D0%BA%D0%B2%D0%B0%D1%80%D1%82%D0%B8%D1%80/' + assert ( + new_url + == "https://anapa.russianrealty.ru/%D0%9F%D1%80%D0%BE%D0%B4%D0%B0%D0%B6%D0%B0-%D0%BA%D0%B2%D0%B0%D1%80%D1%82%D0%B8%D1%80/" + ) - url = 'http://www.a\u0131b.com/a\u0131b' + url = "http://www.a\u0131b.com/a\u0131b" new_url = iri_to_uri(url) print(new_url) - assert new_url == 'http://www.xn--ab-hpa.com/a%C4%B1b' + assert new_url == "http://www.xn--ab-hpa.com/a%C4%B1b" - url = 'https://ya.ru' + url = "https://ya.ru" new_url = iri_to_uri(url) print(new_url) - assert new_url == 'https://ya.ru' + assert new_url == "https://ya.ru" - url = 'https://google.com' + url = "https://google.com" new_url = iri_to_uri(url) print(new_url) - assert new_url == 'https://google.com' + assert new_url == "https://google.com" - url = 'http://домены.рф' + url = "http://домены.рф" new_url = iri_to_uri(url) print(new_url) - assert new_url == 'http://xn--d1acufc5f.xn--p1ai' + assert new_url == "http://xn--d1acufc5f.xn--p1ai" diff --git a/is_correct_brackets.py b/is_correct_brackets.py index b9941d0e4..77bf8f993 100644 --- a/is_correct_brackets.py +++ b/is_correct_brackets.py @@ -1,23 +1,23 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -def is_correct_brackets(text): - while '()' in text or '[]' in text or '{}' in text: - text = text.replace('()', '') - text = text.replace('[]', '') - text = text.replace('{}', '') +def is_correct_brackets(text) -> bool: + while "()" in text or "[]" in text or "{}" in text: + text = text.replace("()", "") + text = text.replace("[]", "") + text = text.replace("{}", "") # Возвращаем True, если text с пустой строкой return not text -if __name__ == '__main__': - assert is_correct_brackets('(((())))') - assert is_correct_brackets('(((())') is False - assert is_correct_brackets('())))') is False - assert is_correct_brackets('((((){}[]{}[])))') - assert is_correct_brackets('(){}[]{}[])))') is False - assert is_correct_brackets('(){}[]{}[]') +if __name__ == "__main__": + assert is_correct_brackets("(((())))") + assert is_correct_brackets("(((())") is False + assert is_correct_brackets("())))") is False + assert is_correct_brackets("((((){}[]{}[])))") + assert is_correct_brackets("(){}[]{}[])))") is False + assert is_correct_brackets("(){}[]{}[]") diff --git a/is_even__is_odd.py b/is_even__is_odd.py index 1d31fb9ae..2228e8c6c 100644 --- a/is_even__is_odd.py +++ b/is_even__is_odd.py @@ -1,23 +1,23 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -def is_even(num): +def is_even(num) -> bool: return num % 2 == 0 -def is_odd(num): +def is_odd(num) -> bool: return not is_even(num) -def is_even_2(num): +def is_even_2(num) -> bool: return num & 1 == 0 -if __name__ == '__main__': +if __name__ == "__main__": for i in range(10): - print('{} is even: {}, {}'.format(i, is_even(i), is_even_2(i))) - print('{} is odd: {}'.format(i, is_odd(i))) + print(f"{i} is even: {is_even(i)}, {is_even_2(i)}") + print(f"{i} is odd: {is_odd(i)}") print() diff --git a/is_free_port.py b/is_free_port.py new file mode 100644 index 000000000..d825d8a80 --- /dev/null +++ b/is_free_port.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import socket + + +def is_free_port(port: int) -> bool: + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + try: + s.bind(("", port)) + return True + except OSError: + return False + + +if __name__ == "__main__": + print(is_free_port(5510)) + print(is_free_port(8080)) + print(is_free_port(9999)) diff --git a/is_ip.py b/is_ip.py index 21b9ebbda..ec07a4be1 100644 --- a/is_ip.py +++ b/is_ip.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import re @@ -9,17 +9,21 @@ # SOURCE: https://stackoverflow.com/a/23166561/5909792 IP_RANGE = "(?:[0-1]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])" -IP_REGEXP = f'^{IP_RANGE}\.{IP_RANGE}\.{IP_RANGE}\.{IP_RANGE}$' +IP_REGEXP = f"^{IP_RANGE}\.{IP_RANGE}\.{IP_RANGE}\.{IP_RANGE}$" def is_ip(ip: str) -> bool: return bool(re.match(IP_REGEXP, ip)) -if __name__ == '__main__': +if __name__ == "__main__": tests = [ - ('127.0.0.1', True), ('100.100.100.100', True), ('0.0.0.0', True), ('300.300.300.300', False), - ('300.0.0.1', False), ('255.255.255.255', True) + ("127.0.0.1", True), + ("100.100.100.100", True), + ("0.0.0.0", True), + ("300.300.300.300", False), + ("300.0.0.1", False), + ("255.255.255.255", True), ] for ip, expected in tests: diff --git a/is_math_magic_square.py b/is_math_magic_square.py index 0ee9f457d..6715cc329 100644 --- a/is_math_magic_square.py +++ b/is_math_magic_square.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # Для решения головоломки в локации аэропорта из Grim Tales 9: Threads Of Destiny diff --git a/is_user_admin.py b/is_user_admin.py index c3bbc9440..d1495c90b 100644 --- a/is_user_admin.py +++ b/is_user_admin.py @@ -1,34 +1,43 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://stackoverflow.com/a/19719292/5909792 -def is_user_admin(): - import os +import ctypes +import os +import traceback - if os.name == 'nt': - import ctypes + +def is_windows() -> bool: + return os.name == "nt" + + +# Linux or mac +def is_posix() -> bool: + return os.name == "posix" + + +def is_user_admin() -> bool: + if is_windows(): try: - # WARNING: requires Windows XP SP2 or higher! + # WARNING: Requires Windows XP SP2 or higher! return bool(ctypes.windll.shell32.IsUserAnAdmin()) - - except: - import traceback + except (AttributeError, OSError): traceback.print_exc() print("Admin check failed, assuming not an admin.") return False - elif os.name == 'posix': + elif is_posix(): # Check for root on Posix - return os.getuid() == 0 + return os.geteuid() == 0 else: - raise RuntimeError("Unsupported operating system for this module: %s" % (os.name,)) + raise RuntimeError(f"Unsupported operating system for this module: {os.name}") -if __name__ == '__main__': - print(is_user_admin()) +if __name__ == "__main__": + print("is_user_admin:", is_user_admin()) diff --git a/is_valid_ip.py b/is_valid_ip.py index 10c180608..c1082430f 100644 --- a/is_valid_ip.py +++ b/is_valid_ip.py @@ -1,22 +1,22 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -def is_valid_ip(ip): - import ipaddress +import ipaddress + +def is_valid_ip(ip) -> bool: try: ipaddress.IPv4Address(ip) return True - except ipaddress.AddressValueError: return False -if __name__ == '__main__': - assert is_valid_ip('127.0.0.1') is True - assert is_valid_ip('255.255.255.255') is True - assert is_valid_ip('127.0.0.0.1') is False - assert is_valid_ip('127.0.0.0.-1') is False +if __name__ == "__main__": + assert is_valid_ip("127.0.0.1") is True + assert is_valid_ip("255.255.255.255") is True + assert is_valid_ip("127.0.0.0.1") is False + assert is_valid_ip("127.0.0.0.-1") is False diff --git a/isinstance_example/isinstance.py b/isinstance_example/isinstance.py index 1f0e40533..c7f5bc24a 100644 --- a/isinstance_example/isinstance.py +++ b/isinstance_example/isinstance.py @@ -1,9 +1,8 @@ -__author__ = 'ipetrash' +__author__ = "ipetrash" -if __name__ == '__main__': - print(isinstance(5, str)) - print(isinstance(5, int)) - print(isinstance(5, bool)) - print(isinstance(False, bool)) - print(isinstance("Hello", str)) \ No newline at end of file +print(isinstance(5, str)) +print(isinstance(5, int)) +print(isinstance(5, bool)) +print(isinstance(False, bool)) +print(isinstance("Hello", str)) diff --git a/jazzcinema_ru/print_all_coming_films.py b/jazzcinema_ru/print_all_coming_films.py index 33291057e..36d705982 100644 --- a/jazzcinema_ru/print_all_coming_films.py +++ b/jazzcinema_ru/print_all_coming_films.py @@ -1,48 +1,50 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" +from datetime import datetime + from urllib.parse import urljoin from urllib.request import urlopen -from datetime import datetime - from bs4 import BeautifulSoup -if __name__ == '__main__': - url = 'http://www.jazzcinema.ru/' +if __name__ == "__main__": + url = "http://www.jazzcinema.ru/" with urlopen(url) as f: - root = BeautifulSoup(f.read(), 'lxml') + root = BeautifulSoup(f.read(), "lxml") # Список расписаний - schedule_list = root.select('.schedule') + schedule_list = root.select(".schedule") for schedule in schedule_list: - schedule_date = datetime.strptime(schedule['rel'], 'calendar-%Y-%m-%d-schedule').strftime('%d/%m/%Y') - border_list = schedule.select('.border') + schedule_date = datetime.strptime( + schedule["rel"], "calendar-%Y-%m-%d-schedule" + ).strftime("%d/%m/%Y") + border_list = schedule.select(".border") # Если фильмов нет if not border_list: continue - print('Расписание фильмов за {}:'.format(schedule_date)) + print(f"Расписание фильмов за {schedule_date}:") # Получение фильмов в текущей вкладке (по идеи, текущая вкладка -- текущий день) - for border in schedule.select('.border'): - a = border.select_one('.movie .title > a') - url = urljoin(url, a['href']) - print(' "{}": {}'.format(a['title'], url)) - genre = border.select_one('.genre') + for border in schedule.select(".border"): + a = border.select_one(".movie .title > a") + url = urljoin(url, a["href"]) + print(f' "{a["title"]}": {url}') + genre = border.select_one(".genre") if genre: - print(' {}'.format(genre.text)) + print(f" {genre.text}") - for seanse in border.select('.seanses li'): - time = seanse.select_one('a').text - price = seanse.select_one('.price').text - print(' {} : {}'.format(time, price)) + for seanse in border.select(".seanses li"): + time = seanse.select_one("a").text + price = seanse.select_one(".price").text + print(f" {time} : {price}") print() diff --git a/jazzcinema_ru/print_today_films.py b/jazzcinema_ru/print_today_films.py index 3e964530b..c73f5a354 100644 --- a/jazzcinema_ru/print_today_films.py +++ b/jazzcinema_ru/print_today_films.py @@ -1,52 +1,54 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" +from datetime import datetime, date + from urllib.parse import urljoin from urllib.request import urlopen from bs4 import BeautifulSoup -if __name__ == '__main__': - url = 'http://www.jazzcinema.ru/' +if __name__ == "__main__": + url = "http://www.jazzcinema.ru/" with urlopen(url) as f: - root = BeautifulSoup(f.read(), 'lxml') + root = BeautifulSoup(f.read(), "lxml") # Список расписаний - schedule_list = root.select('.schedule') + schedule_list = root.select(".schedule") - from datetime import datetime, date today = date.today() - today_found = False # Проходим по списку и ищем расписание на текущую дату for schedule in schedule_list: - schedule_date = datetime.strptime(schedule['rel'], 'calendar-%Y-%m-%d-schedule').date() - schedule_date_str = schedule_date.strftime('%d/%m/%Y') + schedule_date = datetime.strptime( + schedule["rel"], "calendar-%Y-%m-%d-schedule" + ).date() + schedule_date_str = schedule_date.strftime("%d/%m/%Y") - border_list = schedule.select('.border') + border_list = schedule.select(".border") if schedule_date == today and border_list: today_found = True - print('Расписание фильмов на сегодня {}:'.format(schedule_date_str)) + print(f"Расписание фильмов на сегодня {schedule_date_str}:") # Получение фильмов в текущей вкладке (по идеи, текущая вкладка -- текущий день) for border in border_list: - a = border.select_one('.movie .title > a') - url = urljoin(url, a['href']) - print(' "{}": {}'.format(a['title'], url)) - print(' {}'.format(border.select_one('.genre').text)) + a = border.select_one(".movie .title > a") + url = urljoin(url, a["href"]) + print(f' "{a["title"]}": {url}') + print(f" {border.select_one('.genre').text}") - for seanse in border.select('.seanses li'): - time = seanse.select_one('a').text - price = seanse.select_one('.price').text - print(' {} : {}'.format(time, price)) + for seanse in border.select(".seanses li"): + time = seanse.select_one("a").text + price = seanse.select_one(".price").text + print(f" {time} : {price}") print() if not today_found: - print("Фильмов на сегодня ({}) нет".format(today.strftime('%d/%m/%Y'))) + print(f"Фильмов на сегодня ({today.strftime('%d/%m/%Y')}) нет") diff --git a/jazzcinema_ru/show_schedule_gui/main.py b/jazzcinema_ru/show_schedule_gui/main.py index 80ec6d82a..b49e40b43 100644 --- a/jazzcinema_ru/show_schedule_gui/main.py +++ b/jazzcinema_ru/show_schedule_gui/main.py @@ -1,15 +1,18 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -from urllib.parse import urljoin -from urllib.request import urlopen - -from datetime import datetime +import base64 +import logging +import traceback +import sys from collections import OrderedDict +from datetime import datetime +from urllib.parse import urljoin +from urllib.request import urlopen from bs4 import BeautifulSoup @@ -18,47 +21,43 @@ from qtpy.QtCore import * -def log_uncaught_exceptions(ex_cls, ex, tb): - text = '{}: {}:\n'.format(ex_cls.__name__, ex) +def log_uncaught_exceptions(ex_cls, ex, tb) -> None: + text = f"{ex_cls.__name__}: {ex}:\n" + text += "".join(traceback.format_tb(tb)) - import traceback - text += ''.join(traceback.format_tb(tb)) - - import logging logging.critical(text) - QMessageBox.critical(None, 'Error', text) + QMessageBox.critical(None, "Error", text) sys.exit(1) -import sys sys.excepthook = log_uncaught_exceptions -URL = 'http://www.jazzcinema.ru/schedule/' +URL = "http://www.jazzcinema.ru/schedule/" class Movie: - def __init__(self, border): - a = border.select_one('.movie .title > a') - self.movie_url = urljoin(URL, a['href']) + def __init__(self, border) -> None: + a = border.select_one(".movie .title > a") + self.movie_url = urljoin(URL, a["href"]) - self.title = a['title'] + self.title = a["title"] - self.genre = border.select_one('.genre') + self.genre = border.select_one(".genre") if self.genre: self.genre = self.genre.text self.seanses = OrderedDict() - for seanse in border.select('.seanses li'): - time = seanse.select_one('a').text - price = seanse.select_one('.price').text + for seanse in border.select(".seanses li"): + time = seanse.select_one("a").text + price = seanse.select_one(".price").text self.seanses[time] = price movie_info = border.next_sibling - self.annotation = movie_info.select_one('.text').text.strip() + self.annotation = movie_info.select_one(".text").text.strip() - img_url = movie_info.select_one('.poster img')["src"] + img_url = movie_info.select_one(".poster img")["src"] self.img_url = urljoin(URL, img_url) # Начало и конец проката @@ -69,29 +68,29 @@ def __init__(self, border): self.producer = None self.actors = None self.duration = None - self.age_restrictions = movie_info.select_one('.text.age-count').text.strip() + self.age_restrictions = movie_info.select_one(".text.age-count").text.strip() - for td in movie_info.select('table td'): + for td in movie_info.select("table td"): key = td.text.strip() if key.endswith(":"): value = td.next_sibling.text.strip() - if 'Начало проката:' == key: + if "Начало проката:" == key: self.start_rentals = value - elif 'Окончание проката:' == key: + elif "Окончание проката:" == key: self.end_rentals = value - elif 'Страна:' == key: + elif "Страна:" == key: self.country = value - elif 'Режиссёр:' == key: + elif "Режиссёр:" == key: self.producer = value - elif 'В ролях:' == key: + elif "В ролях:" == key: self.actors = value - elif 'Продолжительность:' == key: + elif "Продолжительность:" == key: self.duration = value class MovieInfoWidget(QWidget): - def __init__(self): + def __init__(self) -> None: super().__init__() # TODO: можно и видео добавить @@ -104,19 +103,20 @@ def __init__(self): self.setLayout(layout) - def set_movie(self, movie): + def set_movie(self, movie) -> None: data = dict() # Скачивание обложки и получени base64 with urlopen(movie.img_url) as f: - import base64 - img_base64 = base64.standard_b64encode(f.read()).decode('utf-8') + img_base64 = base64.standard_b64encode(f.read()).decode("utf-8") data["img_url"] = "data:image/png;base64," + img_base64 - seanses_table = '' + seanses_table = "
" for time, price in movie.seanses.items(): - seanses_table += "".format(time, price) + seanses_table += ( + f"" + ) seanses_table += "
{}    {}
{time}    {price}
" @@ -159,20 +159,27 @@ def set_movie(self, movie): - """.format(movie, **data) + """.format( + movie, **data + ) self.browser.setHtml(html) class SchedulerMoviePage(QWidget): - def __init__(self, schedule): + def __init__(self, schedule) -> None: super().__init__() - self.schedule_date_str = datetime.strptime(schedule['rel'], 'calendar-%Y-%m-%d-schedule').strftime('%d/%m/%Y') + self.schedule_date_str = datetime.strptime( + schedule["rel"], "calendar-%Y-%m-%d-schedule" + ).strftime("%d/%m/%Y") self.movie_list_widget = QListWidget() - self.movie_list_widget.currentItemChanged.connect(lambda current, previous: - self.movie_info.set_movie(current.data(Qt.UserRole))) + self.movie_list_widget.currentItemChanged.connect( + lambda current, previous: self.movie_info.set_movie( + current.data(Qt.UserRole) + ) + ) self.movie_info = MovieInfoWidget() @@ -185,7 +192,7 @@ def __init__(self, schedule): self.setLayout(layout) # Получение фильмов в текущей вкладке (в каждой вкладке будет свой список фильмов на день) - for border in schedule.select('.border'): + for border in schedule.select(".border"): movie = Movie(border) item = QListWidgetItem(movie.title) @@ -196,15 +203,15 @@ def __init__(self, schedule): class MainWindow(QMainWindow): - def __init__(self): + def __init__(self) -> None: super().__init__() - self.setWindowTitle('show_schedule_gui.py') + self.setWindowTitle("show_schedule_gui.py") self.tab_widget = QTabWidget() layout = QVBoxLayout() - layout.addWidget(QLabel('Расписание фильмов на:')) + layout.addWidget(QLabel("Расписание фильмов на:")) layout.addWidget(self.tab_widget) widget = QWidget() @@ -212,24 +219,24 @@ def __init__(self): self.setCentralWidget(widget) - def load(self): + def load(self) -> None: with urlopen(URL) as f: - root = BeautifulSoup(f.read(), 'lxml') + root = BeautifulSoup(f.read(), "lxml") # Список расписаний - schedule_list = root.select('.schedule') + schedule_list = root.select(".schedule") # Проходим по списку расписаний for schedule in schedule_list: # Если фильмов нет - if not schedule.select('.border'): + if not schedule.select(".border"): continue tab = SchedulerMoviePage(schedule) self.tab_widget.addTab(tab, tab.schedule_date_str) -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) mw = MainWindow() diff --git a/jinja2__examples/hello_world.py b/jinja2__examples/hello_world.py index 4f44a8558..afb190cc0 100644 --- a/jinja2__examples/hello_world.py +++ b/jinja2__examples/hello_world.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from dataclasses import dataclass @@ -16,19 +16,21 @@ class User: url: str -template = jinja2.Template("""\ +template = jinja2.Template( + """\ {{ title }} -""") +""" +) users = [ - User('1', 'https://a.bc/user/1'), - User('2', 'https://a.bc/user/2'), - User('3', 'https://a.bc/user/3'), + User("1", "https://a.bc/user/1"), + User("2", "https://a.bc/user/2"), + User("3", "https://a.bc/user/3"), ] html = template.render(title="Hello World!", users=users) diff --git a/jinja2__examples/table_by_date_name_number.py b/jinja2__examples/table_by_date_name_number.py index 4f1d332b7..3a1de2392 100644 --- a/jinja2__examples/table_by_date_name_number.py +++ b/jinja2__examples/table_by_date_name_number.py @@ -1,24 +1,24 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -import datetime from collections import defaultdict +from datetime import datetime import jinja2 array1 = [ - [datetime.datetime(2020, 10, 27, 12, 37), 'Саша'], - [datetime.datetime(2020, 10, 28, 16, 2), 'Олег'], - [datetime.datetime(2020, 10, 27, 16, 40), 'Саша'], - [datetime.datetime(2020, 10, 27, 16, 41), 'Саша'], - [datetime.datetime(2020, 10, 27, 12, 54), 'Костя'], - [datetime.datetime(2020, 10, 27, 12, 27), 'Костя'], - [datetime.datetime(2020, 10, 27, 12, 27), 'Олег'], - [datetime.datetime(2020, 10, 27, 12, 54), 'Саша'] + [datetime(2020, 10, 27, 12, 37), "Саша"], + [datetime(2020, 10, 28, 16, 2), "Олег"], + [datetime(2020, 10, 27, 16, 40), "Саша"], + [datetime(2020, 10, 27, 16, 41), "Саша"], + [datetime(2020, 10, 27, 12, 54), "Костя"], + [datetime(2020, 10, 27, 12, 27), "Костя"], + [datetime(2020, 10, 27, 12, 27), "Олег"], + [datetime(2020, 10, 27, 12, 54), "Саша"], ] result = defaultdict(dict) @@ -37,7 +37,8 @@ dates.add(date) -template = jinja2.Template("""\ +template = jinja2.Template( + """\ @@ -54,7 +55,8 @@ {% endfor %}
Отчет
-""") +""" +) html_tab = template.render( dates=sorted(dates), diff --git a/jinja2__examples/table_tr_class_by_condition.py b/jinja2__examples/table_tr_class_by_condition.py index b1f2e4f12..d879f4662 100644 --- a/jinja2__examples/table_tr_class_by_condition.py +++ b/jinja2__examples/table_tr_class_by_condition.py @@ -1,14 +1,15 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install jinja2 import jinja2 -template = jinja2.Template("""\ +template = jinja2.Template( + """\ {% for x in rows_1 %} @@ -21,18 +22,19 @@ {% endfor %}
-""") +""" +) in_dict = { - 'a': [1, 2, 3], - 'b': [3, 4, 5], - 'c': [99, 3], + "a": [1, 2, 3], + "b": [3, 4, 5], + "c": [99, 3], } html = template.render( rows_1=[k for k, v in in_dict.items()], - rows_2=[sum(v) for k, v in in_dict.items()] + rows_2=[sum(v) for k, v in in_dict.items()], ) print(html) """ @@ -56,4 +58,4 @@ -""" \ No newline at end of file +""" diff --git a/jira_logged_human_time_to_seconds.py b/jira_logged_human_time_to_seconds.py new file mode 100644 index 000000000..1a92ae403 --- /dev/null +++ b/jira_logged_human_time_to_seconds.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from enum import IntEnum + + +class UnitSeconds(IntEnum): + MINUTE = 60 + HOUR = 60 * MINUTE + DAY = 8 * HOUR + WEEK = 5 * DAY + + +def logged_human_time_to_seconds(human_time: str) -> int: + """Конвертирование человеко-читаемого времени в секунды. + + >>> logged_human_time_to_seconds("1 minute") + 60 + >>> logged_human_time_to_seconds("1 hour") + 3600 + >>> logged_human_time_to_seconds("6 hours, 30 minutes") + 23400 + >>> logged_human_time_to_seconds("1 day, 30 minutes") + 30600 + """ + + # Jira help: + # You can specify a time unit after a time value 'X', such as Xw, Xd, Xh or Xm, to represent weeks (w), + # days (d), hours (h) and minutes (m), respectively. + # If you do not specify a time unit, minute will be assumed. + # Your current conversion rates are 1w = 5d and 1d = 8h. + + total_seconds = 0 + for part in human_time.split(", "): + value, metric = part.upper().split() + + value = int(value) + metric = metric.removesuffix("S") # NOTE: "HOURS" -> "HOUR" + + total_seconds += value * UnitSeconds[metric] + + return total_seconds + + +def seconds_to_logged_human_time(seconds: int) -> str: + weeks, seconds = divmod(seconds, UnitSeconds.WEEK) + days, seconds = divmod(seconds, UnitSeconds.DAY) + hours, seconds = divmod(seconds, UnitSeconds.HOUR) + minutes, _ = divmod(seconds, UnitSeconds.MINUTE) + + get_suffix = lambda n: 's' if n > 1 else '' + items = [ + f"{weeks} week{get_suffix(weeks)}" if weeks else '', + f"{days} day{get_suffix(days)}" if days else '', + f"{hours} hour{get_suffix(hours)}" if hours else '', + f"{minutes} minute{get_suffix(minutes)}" if minutes else '', + ] + return ", ".join(filter(None, items)) + + +if __name__ == "__main__": + items = [ + ("2 hours", 2 * 60 * 60), + ("1 hour, 15 minutes", 3600 + 15 * 60), + ("1 hour, 30 minutes", 3600 + 1800), + ("4 hours", 4 * 3600), + ("7 hours", 7 * 3600), + ("1 day, 1 hour", 8 * 3600 + 3600), + ("6 hours, 30 minutes", 6 * 3600 + 1800), + ("30 minutes", 30 * 60), + ("1 day", 8 * 3600), + ("1 hour", 3600), + ("4 hours, 30 minutes", 4 * 3600 + 1800), + ("40 minutes", 40 * 60), + ("45 minutes", 45 * 60), + ("7 hours, 30 minutes", 7 * 3600 + 1800), + ("1 day, 30 minutes", 8 * 3600 + 1800), + ("5 hours", 5 * 3600), + ("5 hours, 30 minutes", 5 * 3600 + 1800), + ("1 minute", 60), + ("1 hour, 20 minutes", 3600 + 20 * 60), + ("6 hours", 6 * 3600), + ("3 hours, 30 minutes", 3 * 3600 + 1800), + ("15 minutes", 15 * 60), + ("3 hours", 3 * 3600), + ("1 week", 5 * 8 * 3600), + ( + "1 week, 2 days, 1 hour", + logged_human_time_to_seconds("5 day") + + logged_human_time_to_seconds("2 day") + + logged_human_time_to_seconds("1 hour") + ), + ] + for value, expected in items: + seconds = logged_human_time_to_seconds(value) + print(f"{value!r} -> {seconds} seconds") + assert expected == seconds + + logged_human = seconds_to_logged_human_time(seconds) + print(f"{seconds} seconds -> {logged_human!r}") + assert value == logged_human + + print() + + assert logged_human_time_to_seconds("1 day") == logged_human_time_to_seconds("8 hours") + assert logged_human_time_to_seconds("5 days") == logged_human_time_to_seconds("1 week") + + value = "1 day, 4 hours, 15 minutes" + assert seconds_to_logged_human_time(logged_human_time_to_seconds(value)) == value diff --git a/jira_time.py b/jira_time.py index 0d3a7c1c7..b83b4eb61 100644 --- a/jira_time.py +++ b/jira_time.py @@ -1,75 +1,81 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -def parser_my_jira_time_logs(log): - """ Функция принимает список строк вида: - 7417 10:00-12:00 - 7417 12:19-14:00 - 7417 14:37-15:30 - 7417 15:58-17:50 +import re - 7415 15:58-15:59 +from collections import defaultdict +from datetime import datetime - 7456 14:28-15:59 - То, что перед ' ' -- уникальный номер задания - Диапазон после ' ' -- отрезок времени вида: начало - конец +def seconds_to_jira_str(seconds: int) -> str: + hours, minutes = divmod(seconds, 3600) + minutes //= 60 + days, hours = divmod(hours, 8) + weeks, days = divmod(days, 5) - Далее функция подсчитает количество часов и минут для каждого задания - и выведет их - """ - - # TODO: Защита от копипаста: строки могут повторяться и время подсчитается неправильно - # TODO: Если часы перевалят за 24, то начнется отсчет заного - # TODO: Для джиры дни и недели не астрономические: 1d = 8h и 1w = 5d + items = [ + f"{weeks}w" if weeks else "", + f"{days}d" if days else "", + f"{hours}h" if hours else "", + f"{minutes}m" if minutes else "", + ] + return " ".join(filter(None, items)) - import re - pattern = re.compile(r'(.+) (\d\d:\d\d)-(\d\d:\d\d)') - from datetime import datetime as dt - import time - from collections import defaultdict +def str_to_time(s: str) -> datetime: + return datetime.strptime(s, "%H:%M") - jira_time = defaultdict(int) - for line in log.split('\n'): - if line: - m = pattern.match(line.strip()) +def parser_my_jira_time_logs(log: str) -> str: + pattern = re.compile(r"(.+) (\d\d:\d\d)-(\d\d:\d\d)") - jira = m.group(1) - t1 = m.group(2) - t2 = m.group(3) - delta = dt.strptime(t2, '%H:%M') - dt.strptime(t1, '%H:%M') - seconds = delta.seconds + jira_by_seconds: dict[str, int] = defaultdict(int) - jira_time[jira] += seconds + for line in log.splitlines(): + line = line.strip() + if not line or line.startswith("#"): + continue - for jira, secs in jira_time.items(): - t = time.gmtime(secs) - h = t.tm_hour - m = t.tm_min - jira_time = None - if h: - jira_time = str(h) + 'h' - if m: - if jira_time: - jira_time += ' ' + str(m) + 'm' - else: - jira_time = str(m) + 'm' + m = pattern.match(line) - print('%s: %s' % (jira, jira_time)) + jira: str = m.group(1) + t1: str = m.group(2) + t2: str = m.group(3) + seconds: int = (str_to_time(t2) - str_to_time(t1)).seconds + jira_by_seconds[jira] += seconds -parser_my_jira_time_logs(""" -7417 10:00-12:00 -7417 12:19-14:00 -7417 14:37-15:30 -7417 15:58-17:50 + return "\n".join( + f"{jira}: {seconds_to_jira_str(seconds)}" + for jira, seconds in jira_by_seconds.items() + ) -7415 15:58-15:59 -7456 14:28-15:59 -""") +if __name__ == "__main__": + print( + parser_my_jira_time_logs( + """ + # Foo + 7417 10:00-18:00 + + # Bar + 7417 10:00-12:00 + 7417 12:19-14:00 + 7417 14:37-15:30 + 7417 15:58-17:50 + + 7415 15:58-15:59 + 7456 14:28-15:59 + 7425 10:00-18:10 + """ + ) + ) + """ + 7417: 1d 6h 26m + 7415: 1m + 7456: 1h 31m + 77425: 1d 10m + """ diff --git a/job_compassplus/job_report/main.py b/job_compassplus/.deprecated/job_report/main.py similarity index 57% rename from job_compassplus/job_report/main.py rename to job_compassplus/.deprecated/job_report/main.py index c0835b90c..832529c6e 100644 --- a/job_compassplus/job_report/main.py +++ b/job_compassplus/.deprecated/job_report/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from itertools import chain @@ -14,15 +14,22 @@ person_list = set(chain(*report_dict.values())) # Проверка того, что сортировка работает (в принципе, думаю можно удалить) -assert sorted(person_list, key=lambda x: x.deviation_of_time) == \ - sorted(person_list, key=lambda x: x.deviation_of_time.total) +assert sorted(person_list, key=lambda x: x.deviation_of_time) == sorted( + person_list, key=lambda x: x.deviation_of_time.total +) -sorted_person_list = sorted(person_list, key=lambda x: x.deviation_of_time, reverse=True) +sorted_person_list = sorted( + person_list, key=lambda x: x.deviation_of_time, reverse=True +) for i, person in enumerate(sorted_person_list, 1): - print(f'{i:>3}. {person.full_name} {person.deviation_of_time}') + print(f"{i:>3}. {person.full_name} {person.deviation_of_time}") print() -person = get_person_info(second_name='Петраш', first_name='Илья', report_dict=report_dict) +person = get_person_info( + second_name="Петраш", first_name="Илья", report_dict=report_dict +) if person: - print(f'#{sorted_person_list.index(person) + 1}. {person.full_name} {person.deviation_of_time}') + print( + f"#{sorted_person_list.index(person) + 1}. {person.full_name} {person.deviation_of_time}" + ) diff --git a/job_compassplus/job_report/report_person.py b/job_compassplus/.deprecated/job_report/report_person.py similarity index 67% rename from job_compassplus/job_report/report_person.py rename to job_compassplus/.deprecated/job_report/report_person.py index 29fa6946d..4d52fc0c9 100644 --- a/job_compassplus/job_report/report_person.py +++ b/job_compassplus/.deprecated/job_report/report_person.py @@ -1,17 +1,16 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from functools import total_ordering -from typing import List class ReportPerson: """Класс для описания сотрудника в отчете.""" - def __init__(self, tags: List[str]): + def __init__(self, tags: list[str]): # ФИО self.second_name, self.first_name, self.middle_name = tags[0].split(maxsplit=2) @@ -33,20 +32,21 @@ def __init__(self, tags: List[str]): self.deviation_of_time = self.get_work_time(tags[7]) @property - def full_name(self): - return self.second_name + ' ' + self.first_name + ' ' + self.middle_name + def full_name(self) -> str: + return f"{self.second_name} {self.first_name} {self.middle_name}" @staticmethod - def get_work_day(day_str): - return int(day_str) if '=' not in day_str else int(day_str.split('=')[0].strip()) + def get_work_day(day_str) -> int: + return ( + int(day_str) if "=" not in day_str else int(day_str.split("=")[0].strip()) + ) @total_ordering class Time: """Простой класс для хранения даты работы.""" def __init__(self, time_str: str): - # TODO: supports self._seconds - self._hours, self._minutes, self._seconds = map(int, time_str.split(':')) + self._hours, self._minutes, self._seconds = map(int, time_str.split(":")) @property def total(self) -> int: @@ -54,27 +54,29 @@ def total(self) -> int: return self._hours * 60 + self._minutes - def __repr__(self): - return "{:0>2}:{:0>2}".format(self._hours, self._minutes) + def __repr__(self) -> str: + return f"{self._hours:0>2}:{self._minutes:0>2}" - def __eq__(self, other): + def __eq__(self, other: "Time") -> bool: return self.total == other.total - def __lt__(self, other): + def __lt__(self, other: "Time") -> bool: return self.total < other.total @staticmethod - def get_work_time(time_str): + def get_work_time(time_str: str) -> Time: return ReportPerson.Time(time_str) - def __hash__(self): + def __hash__(self) -> int: return hash(self.full_name) - def __eq__(self, other): + def __eq__(self, other: "ReportPerson") -> bool: return self.full_name == other.full_name - def __repr__(self): + def __repr__(self) -> str: return ( - f"{self.full_name}. Невыходов на работу: {self.absence_from_work}. По календарю ({self.need_to_work_days} смен / {self.need_to_work_on_time} ч:мин). " - f"Фактически ({self.worked_days} смен / {self.worked_time} ч:мин) Отклонение ({self.deviation_of_day} смен / {self.deviation_of_time} ч:мин)" + f"{self.full_name}. Невыходов на работу: {self.absence_from_work}. " + f"По календарю ({self.need_to_work_days} смен / {self.need_to_work_on_time} ч:мин). " + f"Фактически ({self.worked_days} смен / {self.worked_time} ч:мин). " + f"Отклонение ({self.deviation_of_day} смен / {self.deviation_of_time} ч:мин)" ) diff --git a/job_compassplus/job_report/utils.py b/job_compassplus/.deprecated/job_report/utils.py similarity index 68% rename from job_compassplus/job_report/utils.py rename to job_compassplus/.deprecated/job_report/utils.py index d5c5603ef..679acf7e0 100644 --- a/job_compassplus/job_report/utils.py +++ b/job_compassplus/.deprecated/job_report/utils.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import os.path @@ -11,14 +11,12 @@ from collections import defaultdict from itertools import chain from pathlib import Path -from typing import Dict, Set DIR = Path(__file__).resolve().parent sys.path.append(str(DIR.parent)) -from root_common import session -from current_job_report.get_user_and_deviation_hours import get_report_context +from current_job_report.utils import get_report_context from bs4 import BeautifulSoup @@ -30,43 +28,46 @@ if LOGGING_DEBUG: import logging + logging.basicConfig(level=logging.DEBUG) -def get_report_persons_info() -> Dict[str, Set[ReportPerson]]: - today = datetime.today().strftime('%Y-%m-%d') - report_file_name = f'report_{today}.html' +def get_report_persons_info() -> dict[str, set[ReportPerson]]: + today = datetime.today().strftime("%Y-%m-%d") + report_file_name = f"report_{today}.html" # Если кэш-файл отчета не существует, загружаем новые данные и сохраняем в кэш-файл if not os.path.exists(report_file_name): if LOGGING_DEBUG: - print(f'{report_file_name} not exist') + print(f"{report_file_name} not exist") context = get_report_context() - with open(report_file_name, mode='w', encoding='utf-8') as f: + with open(report_file_name, mode="w", encoding="utf-8") as f: f.write(context) else: if LOGGING_DEBUG: - print(f'{report_file_name} exist') + print(f"{report_file_name} exist") - with open(report_file_name, encoding='utf-8') as f: + with open(report_file_name, encoding="utf-8") as f: context = f.read() - html = BeautifulSoup(context, 'lxml') - report = html.select('#report tbody tr') + html = BeautifulSoup(context, "lxml") + report = html.select("#report tbody tr") current_dep = None report_dict = defaultdict(set) for row in report: children = list(row.children) - if len(children) == 1 and children[0].name == 'th': + if len(children) == 1 and children[0].name == "th": current_dep = children[0].text.strip() continue - if children[0].has_attr('class') and children[0].attrs['class'][0] == 'person': - person_tags = [children[0].text] + [i.text for i in row.nextSibling.select('td')[1:]] + if children[0].has_attr("class") and children[0].attrs["class"][0] == "person": + person_tags = [children[0].text] + [ + i.text for i in row.nextSibling.select("td")[1:] + ] if len(person_tags) != 8: continue @@ -94,15 +95,15 @@ def get_person_info(second_name, first_name=None, middle_name=None, report_dict= return person -if __name__ == '__main__': +if __name__ == "__main__": report_dict = get_report_persons_info() print(len(report_dict)) print(sum(map(len, report_dict.values()))) print() for dep, persons in report_dict.items(): - print(f'{dep} ({len(persons)})') + print(f"{dep} ({len(persons)})") for p in persons: - print(f' {p}') + print(f" {p}") print() diff --git a/job_compassplus/statistics_by_names/print_statistic_all_names.py b/job_compassplus/.deprecated/statistics_by_names/print_statistic_all_names.py similarity index 70% rename from job_compassplus/statistics_by_names/print_statistic_all_names.py rename to job_compassplus/.deprecated/statistics_by_names/print_statistic_all_names.py index e794d1f8a..a069def45 100644 --- a/job_compassplus/statistics_by_names/print_statistic_all_names.py +++ b/job_compassplus/.deprecated/statistics_by_names/print_statistic_all_names.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import sys @@ -11,7 +11,7 @@ DIR = Path(__file__).resolve().parent sys.path.append(str(DIR.parent)) -from current_job_report.get_user_and_deviation_hours import get_report_context +from current_job_report.utils import get_report_context def get_all_names(split_name=False): @@ -22,10 +22,12 @@ def get_all_names(split_name=False): """ text = get_report_context() - root = BeautifulSoup(text, 'html.parser') + root = BeautifulSoup(text, "html.parser") # Имена описаны как "<Фамилия> <Имя> <Отчество>" - items = sorted({' '.join(report.text.split()) for report in root.select('#report .person')}) + items = sorted( + {" ".join(report.text.split()) for report in root.select("#report .person")} + ) if split_name: return [x.split(maxsplit=2) for x in items] @@ -33,14 +35,14 @@ def get_all_names(split_name=False): return items -if __name__ == '__main__': +if __name__ == "__main__": # Имена описаны как "<Фамилия> <Имя> <Отчество>" name_list = get_all_names() total = len(name_list) - print('Total:', total) + print("Total:", total) - print_line_format = '{:%s}. {}' % len(str(total)) + print_line_format = "{:%s}. {}" % len(str(total)) for i, name in enumerate(name_list, 1): print(print_line_format.format(i, name)) diff --git a/job_compassplus/statistics_by_names/print_statistic_find_namesake.py b/job_compassplus/.deprecated/statistics_by_names/print_statistic_find_namesake.py similarity index 70% rename from job_compassplus/statistics_by_names/print_statistic_find_namesake.py rename to job_compassplus/.deprecated/statistics_by_names/print_statistic_find_namesake.py index b6df885db..d6c2ca880 100644 --- a/job_compassplus/statistics_by_names/print_statistic_find_namesake.py +++ b/job_compassplus/.deprecated/statistics_by_names/print_statistic_find_namesake.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """ @@ -20,7 +20,7 @@ name_list = get_all_names() total = len(name_list) -print('Total:', total) +print("Total:", total) print() @@ -40,11 +40,15 @@ def get_normal_form(word): second_name_by_full_name_list[normal_form].append(name) # Фильтр по однофамильцам -second_name_by_full_name_list = filter(lambda x: len(x[1]) > 1, second_name_by_full_name_list.items()) +second_name_by_full_name_list = filter( + lambda x: len(x[1]) > 1, second_name_by_full_name_list.items() +) # Сортировка по количеству фамилий -second_name_by_full_name_list = sorted(second_name_by_full_name_list, key=lambda x: len(x[1]), reverse=True) +second_name_by_full_name_list = sorted( + second_name_by_full_name_list, key=lambda x: len(x[1]), reverse=True +) # Вывод итоговых данных for second_name, full_name_list in second_name_by_full_name_list: - print(f'{second_name.upper()} ({len(full_name_list)}): {sorted(full_name_list)}') + print(f"{second_name.upper()} ({len(full_name_list)}): {sorted(full_name_list)}") diff --git a/job_compassplus/statistics_by_names/print_statistic_number_male_and_female.py b/job_compassplus/.deprecated/statistics_by_names/print_statistic_number_male_and_female.py similarity index 61% rename from job_compassplus/statistics_by_names/print_statistic_number_male_and_female.py rename to job_compassplus/.deprecated/statistics_by_names/print_statistic_number_male_and_female.py index ca0d8a115..f4785dd05 100644 --- a/job_compassplus/statistics_by_names/print_statistic_number_male_and_female.py +++ b/job_compassplus/.deprecated/statistics_by_names/print_statistic_number_male_and_female.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """ @@ -18,7 +18,7 @@ name_list = get_all_names(split_name=True) total = len(name_list) -print('Total:', total) +print("Total:", total) print() morph = pymorphy2.MorphAnalyzer() @@ -31,12 +31,22 @@ parsed_word = None if len(parsed_word_list) > 1: - parsed_word_list_filtered = list(filter(lambda x: x.normal_form.lower() == first_name.lower(), parsed_word_list)) + parsed_word_list_filtered = list( + filter( + lambda x: x.normal_form.lower() == first_name.lower(), parsed_word_list + ) + ) if not parsed_word_list_filtered: # Алена -> Алёна - parsed_word_list_filtered = list(filter(lambda x: x.normal_form.lower() == first_name.lower().replace('е', 'ё'), parsed_word_list)) + parsed_word_list_filtered = list( + filter( + lambda x: x.normal_form.lower() + == first_name.lower().replace("е", "ё"), + parsed_word_list, + ) + ) if not parsed_word_list_filtered: - print('Error by parsing name:', first_name.upper(), parsed_word_list) + print("Error by parsing name:", first_name.upper(), parsed_word_list) continue parsed_word = parsed_word_list_filtered[0] @@ -47,7 +57,7 @@ gender = parsed_word.tag.gender - if gender == 'masc': + if gender == "masc": masc_name_list.append(first_name) else: femn_name_list.append(first_name) @@ -56,5 +66,5 @@ masc_name_list.sort() femn_name_list.sort() -print(f'Masc ({len(masc_name_list)}): {masc_name_list}') -print(f'Femn ({len(femn_name_list)}): {femn_name_list}') +print(f"Masc ({len(masc_name_list)}): {masc_name_list}") +print(f"Femn ({len(femn_name_list)}): {femn_name_list}") diff --git a/job_compassplus/statistics_by_names/print_statistic_top_first_name.py b/job_compassplus/.deprecated/statistics_by_names/print_statistic_top_first_name.py similarity index 89% rename from job_compassplus/statistics_by_names/print_statistic_top_first_name.py rename to job_compassplus/.deprecated/statistics_by_names/print_statistic_top_first_name.py index bb4c8c6b4..d11f22212 100644 --- a/job_compassplus/statistics_by_names/print_statistic_top_first_name.py +++ b/job_compassplus/.deprecated/statistics_by_names/print_statistic_top_first_name.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """ @@ -35,12 +35,12 @@ first_name_list = [name[1] for name in get_all_names(split_name=True)] total = len(first_name_list) -print('Total:', total) +print("Total:", total) print() -print('Top 15:') +print("Top 15:") counter = Counter(first_name_list) # Сортировка по количеству for name, number in sorted(counter.items(), key=lambda x: x[1], reverse=True)[:15]: - print(f' {name}: {number} ({number * 100 / total:.1f}%)') + print(f" {name}: {number} ({number * 100 / total:.1f}%)") diff --git a/job_compassplus/statistics_by_names/print_statistic_unique_first_name.py b/job_compassplus/.deprecated/statistics_by_names/print_statistic_unique_first_name.py similarity index 68% rename from job_compassplus/statistics_by_names/print_statistic_unique_first_name.py rename to job_compassplus/.deprecated/statistics_by_names/print_statistic_unique_first_name.py index 41c549fc6..2f84c9a99 100644 --- a/job_compassplus/statistics_by_names/print_statistic_unique_first_name.py +++ b/job_compassplus/.deprecated/statistics_by_names/print_statistic_unique_first_name.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """ @@ -14,7 +14,7 @@ first_name_list = [name[1] for name in get_all_names(split_name=True)] -print('Total:', len(first_name_list)) +print("Total:", len(first_name_list)) unique_first_name_list = list(set(first_name_list)) -print(f'Total unique ({len(unique_first_name_list)}): {unique_first_name_list}') +print(f"Total unique ({len(unique_first_name_list)}): {unique_first_name_list}") diff --git a/UpdateTxEnums/README.md b/job_compassplus/UpdateTxEnums/README.md similarity index 100% rename from UpdateTxEnums/README.md rename to job_compassplus/UpdateTxEnums/README.md diff --git a/UpdateTxEnums/UpdateTxEnums.py b/job_compassplus/UpdateTxEnums/UpdateTxEnums.py similarity index 99% rename from UpdateTxEnums/UpdateTxEnums.py rename to job_compassplus/UpdateTxEnums/UpdateTxEnums.py index 34e444277..014b5c416 100644 --- a/UpdateTxEnums/UpdateTxEnums.py +++ b/job_compassplus/UpdateTxEnums/UpdateTxEnums.py @@ -2,7 +2,7 @@ # TXSST-148: # Нужно написать скрипт, который будет автоматически генерировать классы -# перечеслений на C# в соответствии с этими перечислениями в транзаксисе. +# перечислений на C# в соответствии с этими перечислениями в транзаксисе. # Особенно актуально для CustInfoKind, т.к. оно периодически обновляется # и его можно задавать при настройке объектов сценария. @@ -43,7 +43,7 @@ ) ## Комментарии констант перечислений. -# Ключем является id перечисления, а значением словарь: ключ - имя константы, значение - комментарий константы. +# Ключом является id перечисления, а значением словарь: ключ - имя константы, значение - комментарий константы. dict_enum_constants_comments = { # # AppContextType # 'acsIFBAJ3UCLJCJ7GPUOYTFLPG6SA' : { @@ -727,7 +727,7 @@ def rowsToCSharpXmlComment(rows): # @param rows list[unicode] Список строк текста, который нужно "обернуть" в документирущий комментарий C# # @return unicode Текст в комментарии , строки которого разделены '\n' - return u'/// \n%s/// ' % ''.join([u'/// {}\n'.format(row) + '' for row in rows]) + return u'/// \n%s/// ' % ''.join(f'/// {row}\n' + '' for row in rows) def getSortedEnumItems(enum_items): @@ -761,7 +761,7 @@ def addCommentBlockAboutTxEnum(enum): # @param enum TxEnum Перечисление, по которому нужно сгенерировать дополнительную информацию. # @return str Текст с комментарием c#, содержащий информацию об перечислении TX. - return "/// \n" % (enum.getId(), enum.getStrValType()) + return f"/// \n" def addStandartUsingDirective(): diff --git a/job_compassplus/copying_jar_from_radix/main.py b/job_compassplus/copying_jar_from_radix/main.py new file mode 100644 index 000000000..5f30b83cc --- /dev/null +++ b/job_compassplus/copying_jar_from_radix/main.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import shutil +import sys +from pathlib import Path + +sys.path.append("..") +from tx_parse_xml.get_layer_version import get_layer_version + + +# Example: org.radixware\kernel\common\bin +KERNEL_COMMON_BIN_JARS: list[str] = [ + "xb_acs.jar", + "xb_adsdef.jar", + "xb_commondef.jar", + "xb_utils.jar", + "general.jar", + "xb_msdl.jar", + "xb_types.jar", +] + +# Example: org.radixware\kernel\common\lib +KERNEL_COMMON_LIB_JARS: list[str] = [ + "log4j-api-2.17.1.jar", + "log4j-core-2.17.1.jar", + "saxon-HE-10.3.jar", + "xbean.jar", + "xbean_xpath.jar", + "xercesImpl-2.9.1.jar", +] + +OTHER_PATHS: dict[str, str] = { + "ads/ServiceBus/bin/common.jar": "ServiceBus-common.jar", +} + + +def process(root_dir: Path): + path_radix = root_dir / "org.radixware" + if not path_radix.exists(): + raise Exception(f'Not found path {str(path_radix)!r}!') + + radix_version = get_layer_version(path_radix) + print("Radix version:", radix_version) + + # NOTE: Текущая папка скрипта + path_to_copy = Path(__file__).resolve().parent / f"radix-{radix_version}" + + path_kernel_common_bin = path_radix / "kernel/common/bin" + if not path_kernel_common_bin.exists(): + raise Exception(f'Not found path {str(path_kernel_common_bin)!r}!') + + path_kernel_common_lib = path_radix / "kernel/common/lib" + if not path_kernel_common_lib.exists(): + raise Exception(f'Not found path {str(path_kernel_common_lib)!r}!') + + old_to_new_paths: dict[Path, Path] = dict() + + for file in KERNEL_COMMON_BIN_JARS: + old_path = path_kernel_common_bin / file + old_to_new_paths[old_path] = path_to_copy / old_path.name + + for file in KERNEL_COMMON_LIB_JARS: + old_path = path_kernel_common_lib / file + old_to_new_paths[old_path] = path_to_copy / old_path.name + + for file, new_file in OTHER_PATHS.items(): + old_path = path_radix / file + old_to_new_paths[old_path] = path_to_copy / new_file + + # Проверка наличия всех путей + for old_path in old_to_new_paths.keys(): + if not old_path.exists(): + raise Exception(f'Not found path {str(old_path)!r}!') + + path_to_copy.mkdir(exist_ok=True, parents=True) + for old_path, new_path in old_to_new_paths.items(): + print(f"Copy {str(old_path)!r} to {str(new_path)!r}") + shutil.copy(old_path, new_path) + + +if __name__ == "__main__": + import argparse + + parser = argparse.ArgumentParser( + description="Copying jar from radix" + ) + parser.add_argument( + "root_path", + metavar="/PATH/TO/PROJECT", + type=Path, + help="Path to source tree (the directory containing branch.xml)", + ) + args = parser.parse_args() + + process(args.root_path) diff --git a/job_compassplus/current_job_report/.deprecated/get_latecomers.py b/job_compassplus/current_job_report/.deprecated/get_latecomers.py new file mode 100644 index 000000000..88f30d32a --- /dev/null +++ b/job_compassplus/current_job_report/.deprecated/get_latecomers.py @@ -0,0 +1,147 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import re +from collections import defaultdict +from datetime import date, datetime +from typing import Callable + +from bs4 import BeautifulSoup, Tag +from utils import NotFoundReport, get_report_context + + +def get_latecomers( + dep: str = "dep12", + period_date: date = None, + is_latecome: Callable[[datetime], bool] = lambda day: day.hour >= 11, +) -> dict[str, list[datetime]]: + html = get_report_context( + dep=dep, + rep="rep2", # Детальный отчет + period=period_date.strftime("%Y-%m"), + ) + + root = BeautifulSoup(html, "html.parser") + + person_by_dates: dict[str, list[datetime]] = defaultdict(list) + + report_table = root.select_one("table#report") + if not report_table: # Отчет еще не готов + raise NotFoundReport() + + dates: list[str | None] = [] + for el in report_table.select("thead > tr:nth-child(2) > th"): + # NOTE: "17.08" -> (17, 08) + day, month = map(int, el.get_text(strip=True).split(".")) + + d = date(year=period_date.year, month=month, day=day) + dates.append(d.isoformat() if d.isoweekday() not in (6, 7) else None) + + tr_list = report_table.select("tr:has(td.person)") + assert tr_list, "Другой формат таблицы. Не нашлось строк с пользователями" + + for tr in tr_list: + tds: list[Tag] = tr.select("td") + if len(tds) < 3: + continue + + # NOTE: Содержит ячейки с временем входа, типа "08:41:18" + td_person, td_total, td_enter, *td_enter_times = tds + assert ( + td_enter.get_text(strip=True) == "Вход" + ), "Другой формат таблицы. Не строка с временем входа" + assert len(td_enter_times) == len( + dates + ), "Другой формат таблицы. Количество столбцов в заголовке и в строке не совпадает" + + # NOTE: Содержит ячейки с временем нахождения, типа "08:41:18" или отсутствия: + # "О\n-" - отпуск, "Т\n07:00:00" - временный отпуск и т.п. Время есть, если в офис входили в этот день + # (исключение для Т) + td_time, *td_total_day_times = ( + tr.find_next_sibling("tr").find_next_sibling("tr").select("td") + ) + assert ( + td_time.get_text(strip=True) == "Время" + ), "Другой формат таблицы. Не строка с временем нахождения" + assert len(td_total_day_times) == len( + dates + ), "Другой формат таблицы. Количество столбцов в заголовке и в строке не совпадает" + + person: str = td_person.get_text(strip=True) + + # Применительно для текущего пользователя + if person in person_by_dates: + continue + + enter_times: list[str | None] = [ + el if re.search(r"\d{2}:\d{2}:\d{2}", el) else None + for el in map(lambda el: el.get_text(strip=True), td_enter_times) + ] + total_day_times: list[str | None] = [ + # Проверка, что в строке есть только цифры и двоеточие + None if re.sub(r"[\d:\s]", "", el) else el + for el in map(lambda el: el.get_text(strip=True), td_total_day_times) + ] + + for date_str, enter_time_str, total_day_time_str in zip( + dates, enter_times, total_day_times + ): + if not date_str or not enter_time_str or not total_day_time_str: + continue + + day: datetime = datetime.fromisoformat(f"{date_str} {enter_time_str}") + if is_latecome(day): + person_by_dates[person].append(day) + + return person_by_dates + + +if __name__ == "__main__": + DEP: str = "dep12" + + period_date: date = date.today() + # NOTE: За конкретную дату + # period_date: date = date(year=2024, month=9, day=1) + print(f"Отчет за {period_date:%Y-%m}") + + try: + person_by_dates: dict[str, list[datetime]] = get_latecomers( + dep=DEP, period_date=period_date + ) + print(f"Опоздавшие ({len(person_by_dates)}):") + + for person, days in person_by_dates.items(): + print(f" {person} ({len(days)}):") + for day in days: + print(f" {day}") + + print() + + except NotFoundReport: + print("Не готов") + + # NOTE: За период + # print() + # + # person_by_dates: dict[str, list[datetime]] = defaultdict(list) + # for month in [7, 8, 9]: + # try: + # result = get_latecomers( + # dep=DEP, period_date=date(year=2024, month=month, day=1) + # ) + # except NotFoundReport: + # continue + # + # for person, days in result.items(): + # person_by_dates[person] += days + # + # print(f"Опоздавшие ({len(person_by_dates)}):") + # for person, days in person_by_dates.items(): + # print(f" {person} ({len(days)}):") + # for day in days: + # print(f" {day}") + # + # print() diff --git a/job_compassplus/current_job_report/favicon.ico b/job_compassplus/current_job_report/favicon.ico deleted file mode 100644 index d0a42183a..000000000 Binary files a/job_compassplus/current_job_report/favicon.ico and /dev/null differ diff --git a/job_compassplus/current_job_report/favicon.png b/job_compassplus/current_job_report/favicon.png new file mode 100644 index 000000000..7fbeb22cf Binary files /dev/null and b/job_compassplus/current_job_report/favicon.png differ diff --git a/job_compassplus/current_job_report/get_hours_worked.py b/job_compassplus/current_job_report/get_hours_worked.py new file mode 100644 index 000000000..89c05ffad --- /dev/null +++ b/job_compassplus/current_job_report/get_hours_worked.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import re +from bs4 import BeautifulSoup, Tag +from utils import ( + NotFoundReport, + ReportTypeEnum, + PeriodTypeEnum, + get_report, + clear_hours, +) + + +def get_text_children(el: Tag, idx: int) -> str: + try: + return el.find_all(recursive=False)[idx].text.strip() + except Exception: + return "" + + +def get_tr_for_current_user(html: str) -> Tag: + root = BeautifulSoup(html, "html.parser") + + tbody_el = root.select_one("#report > tbody") + if not tbody_el: + tbody_el = root.select_one(".report > tbody") + if not tbody_el: + raise NotFoundReport() + + pattern_current_user: re.Pattern = re.compile(".*Текущий пользователь.*") + + th_el = tbody_el.find("th", string=pattern_current_user) + if not th_el: + raise NotFoundReport() + + return th_el.parent + + +def parse_current_user_deviation_hours(html: str) -> tuple[str, str]: + current_user_tr: Tag = get_tr_for_current_user(html) + + # Получение следующего элемента после текущего, у него получение первого ребенка, у которого вытаскивается текст + tr_name: Tag = current_user_tr.find_next_sibling() + name: str = get_text_children(tr_name, idx=0) + + # NOTE: В новом отчете в первой строке указано отклонение + deviation_hours: str = get_text_children(tr_name, idx=7) + if not deviation_hours: # Если старый отчет, то в самом низу + # Получение следующего элемента после текущего, у него получение последнего + # ребенка, у которого вытаскивается текст + # Ищем последнюю строку текущего пользователя -- в ней и находится время работы + # Ее легко найти -- ее первая ячейка пустая + deviation_tr = current_user_tr.find_next_sibling() + + # Ищем строку с пустой ячейкой + while get_text_children(deviation_tr, idx=0): + deviation_tr = deviation_tr.find_next_sibling() + + deviation_hours: str = get_text_children(deviation_tr, idx=-1) + + return name, clear_hours(deviation_hours) + + +def get_user_and_deviation_hours() -> tuple[str, str]: + report: str = get_report( + report_type=ReportTypeEnum.SUMMARY, + period_type=PeriodTypeEnum.MONTH, + ) + return parse_current_user_deviation_hours(report) + + +def get_quarter_user_and_deviation_hours() -> tuple[str, str]: + report: str = get_report( + report_type=ReportTypeEnum.SUMMARY, + period_type=PeriodTypeEnum.QUARTER, + ) + return parse_current_user_deviation_hours(report) + + +if __name__ == "__main__": + name, deviation_hours = get_user_and_deviation_hours() + print(name) + print( + ("Недоработка" if deviation_hours[0] == "-" else "Переработка") + + " " + + deviation_hours + ) + """ + Петраш Илья Андреевич + Переработка 04:19:57 + """ + + print() + + name, deviation_hours = get_quarter_user_and_deviation_hours() + print(name) + print( + ("Недоработка" if deviation_hours[0] == "-" else "Переработка") + + " за квартал " + + deviation_hours + ) + """ + Петраш Илья Андреевич + Переработка за квартал 15:09 + """ + + print() + + from get_worklog import get_worklog + + print(get_worklog()) + """ + Worklog(actually='103:05:14', logged='66:35', logged_percent=65) + """ diff --git a/job_compassplus/current_job_report/get_time_spent_in_office.py b/job_compassplus/current_job_report/get_time_spent_in_office.py new file mode 100644 index 000000000..b344c7a91 --- /dev/null +++ b/job_compassplus/current_job_report/get_time_spent_in_office.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import re +from dataclasses import dataclass + +from bs4 import BeautifulSoup + +from utils import session, HOST, NotFoundReport + + +URL = f"{HOST}/pa-reports-new/report/" + + +@dataclass +class TimeSpent: + first_enter: str + today: str + + +def get_time_spent_in_office() -> TimeSpent: + rs = session.get(URL) + if not rs.ok: + raise NotFoundReport(f"HTTP status is {rs.status_code}") + + soup = BeautifulSoup(rs.content, "html.parser") + text = soup.get_text(strip=True) + + def _find(pattern: str, about: str) -> str: + if m := re.search(pattern, text, flags=re.IGNORECASE): + return m.group(1) + raise Exception(f"Not found {about!r}") + + return TimeSpent( + first_enter=_find( + pattern=r"First enter:\s*([\d+:]+)", + about="First enter", + ), + today=_find( + pattern=r"Today\s*\(Possible\):\s*([\d+:]+)", + about="Today(Possible)", + ), + ) + + +if __name__ == "__main__": + print(get_time_spent_in_office()) + """ + TimeSpent(first_enter='10:53:30', today='07:31:36') + """ diff --git a/job_compassplus/current_job_report/get_user_and_deviation_hours.py b/job_compassplus/current_job_report/get_user_and_deviation_hours.py deleted file mode 100644 index 78a74bcc6..000000000 --- a/job_compassplus/current_job_report/get_user_and_deviation_hours.py +++ /dev/null @@ -1,162 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - - -__author__ = 'ipetrash' - - -import datetime as DT -import re -import sys - -from pathlib import Path -from typing import Tuple - -from lxml import etree - - -DIR = Path(__file__).resolve().parent - -sys.path.append(str(DIR.parent)) -from root_common import session - -sys.path.append(str(DIR.parent.parent)) -from get_quarter import get_quarter, get_quarter_num # NOTE: оставить get_quarter_num для main.py - - -class NotFoundReport(Exception): - pass - - -URL = 'https://helpdesk.compassluxe.com/pa-reports/' - - -def clear_hours(hours: str) -> str: - return re.sub(r'[^\d:-]', '', hours) - - -def _send_data(data: dict) -> str: - # В какой-то момент адрес временно поменялся, тогда предварительный GET поможет получить актуальный адрес - rs = session.get(URL) - if not rs.ok: - raise NotFoundReport(f"HTTP status is {rs.status_code}") - - rs = session.post(rs.url, data=data) - if not rs.ok: - raise NotFoundReport(f"HTTP status is {rs.status_code}") - - return rs.text - - -def get_report_context() -> str: - today = DT.datetime.today() - data = { - 'dep': 'all', - 'rep': 'rep1', - 'period': today.strftime('%Y-%m'), - 'v': int(today.timestamp() * 1000), - 'type': 'normal', - } - return _send_data(data) - - -def get_quarter_report_context() -> str: - today = DT.datetime.today() - data = { - 'dep': 'all', - 'rep': 'rep1', - 'quarter': 'quarter', - 'period': f'{today.year}-q{get_quarter(today)}', - 'v': int(today.timestamp() * 1000), - 'type': 'normal', - } - return _send_data(data) - - -def parse_current_user_deviation_hours(html: str) -> Tuple[str, str]: - root = etree.HTML(html) - - XPATH_1 = '//table[@id="report"]/tbody/tr[th[contains(text(),"Текущий пользователь")]]' - XPATH_2 = '//table[@class="report"]/tbody/tr[th[contains(text(),"Текущий пользователь")]]' - - # Вытаскивание tr, у которого есть вложенный th, имеющий в содержимом текст "Текущий пользователь" - try: - items = root.xpath(XPATH_1) - if not items: - items = root.xpath(XPATH_2) - - current_user_tr = items[0] - - except IndexError: - raise NotFoundReport() - - # Получение следующего элемента после текущего, у него получение первого ребенка, у которого вытаскивается текст - name = next(current_user_tr.getnext().iterchildren()).text.strip() - - # Получение следующего следующего элемента после текущего, у него получение последнего - # ребенка, у которого вытаскивается текст - # Ищем последную строку текущего пользователя -- в ней и находится время работы - # Ее легко найти -- ее первая ячейка пустая - deviation_tr = current_user_tr.getnext() - - # Ищем строку с пустой ячейкой - while next(deviation_tr.iterchildren()).text.strip(): - deviation_tr = deviation_tr.getnext() - - deviation_hours = deviation_tr.getchildren()[-1].text.strip() - return name, clear_hours(deviation_hours) - - -def parse_user_deviation_hours(html: str, user_name: str = 'Петраш') -> Tuple[str, str]: - root = etree.HTML(html) - - XPATH_1 = f'//table[@id="report"]/tbody/tr[td[contains(text(),"{user_name}")]]' - XPATH_2 = f'//table[@class="report"]/tbody/tr[td[contains(text(),"{user_name}")]]' - - # Вытаскивание tr, у которого есть вложенный th, имеющий в содержимом текст "Текущий пользователь" - try: - items = root.xpath(XPATH_1) - if not items: - items = root.xpath(XPATH_2) - - current_user_tr = items[0] - - except IndexError: - raise NotFoundReport() - - # Получение текста текущего элемента - name = next(current_user_tr.iterchildren()).text.strip() - - # Получение следующего следующего элемента после текущего, у него получение последнего - # ребенка, у которого вытаскивается текст - # Ищем последную строку текущего пользователя -- в ней и находится время работы - # Ее легко найти -- ее первая ячейка пустая - deviation_tr = current_user_tr.getnext() - - # Ищем строку с пустой ячейкой - while next(deviation_tr.iterchildren()).text.strip(): - deviation_tr = deviation_tr.getnext() - - deviation_hours = deviation_tr.getchildren()[-1].text.strip() - return name, clear_hours(deviation_hours) - - -def get_user_and_deviation_hours() -> Tuple[str, str]: - content = get_report_context() - return parse_current_user_deviation_hours(content) - - -def get_quarter_user_and_deviation_hours() -> Tuple[str, str]: - content = get_quarter_report_context() - return parse_user_deviation_hours(content) - - -if __name__ == '__main__': - name, deviation_hours = get_user_and_deviation_hours() - print(name) - print(('Недоработка' if deviation_hours[0] == '-' else 'Переработка') + ' ' + deviation_hours) - print() - - name, deviation_hours = get_quarter_user_and_deviation_hours() - print(name) - print(('Недоработка' if deviation_hours[0] == '-' else 'Переработка') + ' за квартал ' + deviation_hours) diff --git a/job_compassplus/current_job_report/get_worklog.py b/job_compassplus/current_job_report/get_worklog.py new file mode 100644 index 000000000..5a80ddd21 --- /dev/null +++ b/job_compassplus/current_job_report/get_worklog.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from dataclasses import dataclass +from bs4 import BeautifulSoup +from utils import get_report, ReportTypeEnum, PeriodTypeEnum, NotFoundReport + + +@dataclass +class Worklog: + actually: str # Отработано фактически (чч:мм:сс) + logged: str # Зафиксировано трудозатрат (чч:мм) + logged_percent: int # Процент зафиксированного времени + + @classmethod + def parse_from(cls, data: tuple[str, str, str]) -> "Worklog": + try: + logged_percent: int = int(data[2].replace("%", "")) + except Exception: + raise NotFoundReport() + + return cls( + actually=data[0], + logged=data[1], + logged_percent=logged_percent, + ) + + +def get_worklog() -> Worklog: + report: str = get_report( + report_type=ReportTypeEnum.WORKLOG, + period_type=PeriodTypeEnum.MONTH, + ) + + soup = BeautifulSoup(report, "html.parser") + current_user_tr = soup.select_one("table > tbody > tr.current") + if not current_user_tr: + raise NotFoundReport() + + td_list = current_user_tr.select("td") + return Worklog.parse_from( + ( + # Отработано фактически (чч:мм:сс) + td_list[1].get_text(strip=True), + # Зафиксировано трудозатрат (чч:мм) + td_list[-2].get_text(strip=True), + # Процент зафиксированного времени + td_list[-1].get_text(strip=True), + ) + ) + + +if __name__ == "__main__": + print(get_worklog()) + """ + Worklog(actually='103:05:14', logged='66:35', logged_percent=65) + """ diff --git a/job_compassplus/current_job_report/main.py b/job_compassplus/current_job_report/main.py index 036ca8566..eccf2e245 100644 --- a/job_compassplus/current_job_report/main.py +++ b/job_compassplus/current_job_report/main.py @@ -1,41 +1,83 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -import datetime as DT +import datetime as dt import sys import time import traceback -import os.path + +from pathlib import Path +from threading import Thread from PyQt5.QtWidgets import ( - QApplication, QWidget, QLabel, QToolButton, QPlainTextEdit, QVBoxLayout, QHBoxLayout, QSystemTrayIcon, - QMenu, QWidgetAction, QMessageBox + QApplication, + QWidget, + QLabel, + QToolButton, + QPlainTextEdit, + QVBoxLayout, + QHBoxLayout, + QSystemTrayIcon, + QMenu, + QWidgetAction, + QMessageBox, ) -from PyQt5.QtGui import QColor, QPainter, QIcon -from PyQt5.QtCore import Qt, pyqtSignal, QThread +from PyQt5.QtGui import QColor, QPainter, QIcon, QPixmap, QCursor +from PyQt5.QtCore import Qt, pyqtSignal, QThread, QRectF -from get_user_and_deviation_hours import ( - get_user_and_deviation_hours, get_quarter_user_and_deviation_hours, get_quarter_num, - NotFoundReport +from get_hours_worked import ( + get_user_and_deviation_hours, + get_quarter_user_and_deviation_hours, ) +from utils import NotFoundReport, get_quarter_roman + + +# SOURCE: https://github.com/gil9red/SimplePyScripts/blob/405f08fcbf8b99ea64a58a73ee699cb1c0b5230e/qt__pyqt__pyside__pyqode/pyqt__QPainter__dynamic_draw_emoji_on_img/main.py#L44-L66 +def draw_text_to_bottom_right( + img: QPixmap, text: str, scale_text_from_img: float = 0.5 +): + p = QPainter(img) + + factor = (img.width() * scale_text_from_img) / p.fontMetrics().width(text) + if factor < 1 or factor > 1.25: + f = p.font() + point_size = f.pointSizeF() * factor + if point_size > 0: + f.setPointSizeF(point_size) + p.setFont(f) + + # Bottom + right + text_rect = p.fontMetrics().boundingRect(text) + rect = QRectF( + img.width() - text_rect.width(), + img.height() - text_rect.height(), + img.width(), + img.height(), + ) + + p.drawText(rect, text) + + p = None # NOTE: Иначе, почему-то будет ошибка # Для отлова всех исключений, которые в слотах Qt могут "затеряться" и привести к тихому падению -def log_uncaught_exceptions(ex_cls, ex, tb): - text = f'{ex_cls.__name__}: {ex}:\n' - text += ''.join(traceback.format_tb(tb)) +def log_uncaught_exceptions(ex_cls, ex, tb) -> None: + text = f"{ex_cls.__name__}: {ex}:\n" + text += "".join(traceback.format_tb(tb)) - print('Error: ', text) - QMessageBox.critical(None, 'Error', text) + print("Error: ", text) + QMessageBox.critical(None, "Error", text) sys.exit(1) sys.excepthook = log_uncaught_exceptions -TRAY_ICON = os.path.join(os.path.dirname(__file__), 'favicon.ico') + +DIR: Path = Path(__file__).parent.resolve() +TRAY_ICON: str = str(DIR / "favicon.png") class CheckJobReportThread(QThread): @@ -43,19 +85,19 @@ class CheckJobReportThread(QThread): about_ok = pyqtSignal(bool) about_log = pyqtSignal(str) - def __init__(self): + def __init__(self) -> None: super().__init__() self.last_text = None self.ok = None - def do_run(self): + def do_run(self) -> None: def _get_title(deviation_hours): - ok = deviation_hours[0] != '-' - return 'Переработка' if ok else 'Недоработка' + ok = deviation_hours[0] != "-" + return "Переработка" if ok else "Недоработка" - today = DT.datetime.today().strftime('%d/%m/%Y %H:%M:%S') - self.about_log.emit(f'Check for {today}') + today = dt.datetime.today().strftime("%d.%m.%Y %H:%M:%S") + self.about_log.emit(f"Проверка за {today}") text = "" deviation_hours = None @@ -63,23 +105,18 @@ def _get_title(deviation_hours): try: name, deviation_hours = get_user_and_deviation_hours() - ok = deviation_hours[0] != '-' - text += name + '\n\n' + _get_title(deviation_hours) + ' ' + deviation_hours - - except NotFoundReport: - text = "Отчет на сегодня еще не готов." - ok = True + ok = deviation_hours[0] != "-" + text += name + "\n\n" + _get_title(deviation_hours) + " " + deviation_hours - try: _, quarter_deviation_hours = get_quarter_user_and_deviation_hours() - if quarter_deviation_hours.count(':') == 1: + if quarter_deviation_hours.count(":") == 1: quarter_deviation_hours += ":00" - text += "\n" + _get_title(quarter_deviation_hours) + ' за квартал ' + get_quarter_num() \ - + " " + quarter_deviation_hours + text += f"\n{_get_title(quarter_deviation_hours)} за квартал {get_quarter_roman()} {quarter_deviation_hours}" except NotFoundReport: - pass + text = "Отчет на сегодня еще не готов." + ok = True # Если часы за месяц не готовы, но часы за квартал есть if not deviation_hours and quarter_deviation_hours: @@ -94,36 +131,40 @@ def _get_title(deviation_hours): else: self.about_log.emit(" Ничего не изменилось\n") - if self.ok != ok: - self.ok = ok - self.about_ok.emit(self.ok) + self.ok = ok + self.about_ok.emit(self.ok) - def run(self): + def run(self) -> None: while True: try: - self.do_run() + # Между 08:00 и 20:00 + now_hour = dt.datetime.now().hour + if now_hour in range(8, 20 + 1): + self.do_run() time.sleep(3600) except Exception as e: - self.about_log.emit("Error: " + str(e)) + self.about_log.emit(f"Error: {e}") self.about_log.emit("Wait 60 secs") time.sleep(60) class JobReportWidget(QWidget): - def __init__(self): + def __init__(self) -> None: super().__init__() self.info = QLabel() - self.ok = None + self.info.setWordWrap(True) + + self.ok: bool | None = None self.quit_button = QToolButton() - self.quit_button.setText('Quit') + self.quit_button.setText("Quit") self.quit_button.setAutoRaise(True) self.quit_button.clicked.connect(QApplication.instance().quit) self.hide_button = QToolButton() - self.hide_button.setText('Hide') + self.hide_button.setText("Hide") self.hide_button.setAutoRaise(True) self.hide_button.clicked.connect(lambda x=None: self.parent().hide()) @@ -164,25 +205,36 @@ def __init__(self): self.setLayout(layout) self.thread = CheckJobReportThread() - self.thread.about_new_text.connect(self.info.setText) + self.thread.about_new_text.connect(self.set_text) self.thread.about_ok.connect(self._set_ok) self.thread.about_log.connect(self._add_log) self.thread.start() - button_refresh.clicked.connect(self.thread.do_run) + button_refresh.clicked.connect(self.refresh) + + def set_text(self, text: str) -> None: + print(text) + self.info.setText(text) + + def refresh(self) -> None: + # Выполнение метода в отдельном потоке, а не в GUI + Thread(target=self.thread.do_run, daemon=True).start() - def _set_ok(self, val): + def _set_ok(self, val: bool) -> None: self.ok = val self.update() - def _add_log(self, val): + def _add_log(self, val: str) -> None: print(val) self.log.appendPlainText(val) - def paintEvent(self, event): + def paintEvent(self, event) -> None: super().paintEvent(event) - color = QColor('#29AB87') if self.ok else QColor(255, 0, 0, 128) + if self.ok is None: + return + + color = QColor("#29AB87") if self.ok else QColor(255, 0, 0, 128) painter = QPainter(self) painter.setBrush(color) @@ -190,15 +242,27 @@ def paintEvent(self, event): painter.drawRect(self.rect()) -# TODO: Нарисовать график -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) app.setQuitOnLastWindowClosed(False) tray = QSystemTrayIcon(QIcon(TRAY_ICON)) + def _on_about_log_or_ok(value: str | bool) -> None: + if isinstance(value, str): + text = "🔄" + else: + text = "✅" if value else "⛔" + + img = QPixmap(TRAY_ICON) + draw_text_to_bottom_right(img, text, 0.65) + tray.setIcon(QIcon(img)) + job_report_widget = JobReportWidget() - job_report_widget.setFixedSize(230, 130) + job_report_widget.thread.about_log.connect(_on_about_log_or_ok) + job_report_widget.thread.about_ok.connect(_on_about_log_or_ok) + job_report_widget.refresh() + job_report_widget_action = QWidgetAction(job_report_widget) job_report_widget_action.setDefaultWidget(job_report_widget) @@ -206,9 +270,14 @@ def paintEvent(self, event): menu.addAction(job_report_widget_action) tray.setContextMenu(menu) - tray.activated.connect(lambda x: menu.exec(tray.geometry().center())) - - tray.setToolTip('Compass Plus. Рапорт учета рабочего времени') + tray.activated.connect( + lambda _: ( + tray.contextMenu().resize(job_report_widget.sizeHint()), + tray.contextMenu().popup(QCursor.pos()), + ) + ) + + tray.setToolTip("Compass Plus. Рапорт учета рабочего времени") tray.show() app.exec() diff --git a/job_compassplus/current_job_report/utils.py b/job_compassplus/current_job_report/utils.py new file mode 100644 index 000000000..ede069598 --- /dev/null +++ b/job_compassplus/current_job_report/utils.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import enum +import re +import sys + +from datetime import datetime +from pathlib import Path + +from bs4 import BeautifulSoup + +DIR = Path(__file__).resolve().parent +sys.path.append(str(DIR.parent)) +from root_config import JIRA_HOST as HOST +from root_common import session + +sys.path.append(str(DIR.parent.parent)) +from get_quarter import ( + get_quarter_num, + get_quarter_roman, # NOTE: оставить get_quarter_roman для main.py +) + + +@enum.unique +class PeriodTypeEnum(enum.IntEnum): + MONTH = 0 + QUARTER = 1 + PERIOD = 2 + + +@enum.unique +class ReportTypeEnum(enum.IntEnum): + WORKLOG = 5 # Отчет по зафиксированным трудозатратам + SUMMARY = 6 # Сводный отчет + + +class NotFoundReport(Exception): + pass + + +URL: str = f"{HOST}/pa-reports-new/report/" + + +def clear_hours(hours: str) -> str: + return re.sub(r"[^\d:-]", "", hours) + + +def _send_data(data: dict[str, str | int]) -> str: + # В какой-то момент адрес временно поменялся, тогда предварительный GET поможет получить актуальный адрес + rs = session.get(URL) + if not rs.ok: + raise NotFoundReport(f"HTTP status is {rs.status_code}") + + # Добавление полей, типа токенов, если явно не были заданы + soup = BeautifulSoup(rs.content, "html.parser") + for el in soup.select('input[name][value][type="hidden"]'): + name: str = el["name"] + if name not in data: + data[name] = el["value"] + + rs = session.post(rs.url, data=data) + if not rs.ok: + raise NotFoundReport(f"HTTP status is {rs.status_code}") + + return rs.text + + +def get_report(report_type: ReportTypeEnum, period_type: PeriodTypeEnum) -> str: + today = datetime.today() + + data = { + "reporttype": report_type.value, + "PeriodType": period_type.value, + "Month": today.month, + "Year": today.year, + "QuarterNum": get_quarter_num(today) - 1, # NOTE: I квартал в отчете это 0 + "FromMonth": 1, + "ToMonth": 12, + } + + match report_type: + case PeriodTypeEnum.QUARTER: + data["quarter"] = "quarter" + case PeriodTypeEnum.PERIOD: + data["total"] = "total" + + return _send_data(data) + + +if __name__ == "__main__": + dt = datetime.now() + print(dt, get_quarter_num(dt), get_quarter_roman(dt)) + + print() + + report: str = get_report( + report_type=ReportTypeEnum.SUMMARY, + period_type=PeriodTypeEnum.MONTH, + ) + soup = BeautifulSoup(report, "html.parser") + for tr in soup.select("table.report tr:has(> td)"): + print([td.get_text(strip=True) for td in tr.select("td")]) diff --git a/job_compassplus/cyr_lat_audit.py b/job_compassplus/cyr_lat_audit.py new file mode 100644 index 000000000..f85977092 --- /dev/null +++ b/job_compassplus/cyr_lat_audit.py @@ -0,0 +1,172 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import argparse +import re + +from collections import Counter +from datetime import datetime +from pathlib import Path + + +def check_mixed_layout(text: str) -> list[tuple[str, list[str]]]: + words: list[str] = re.findall(r"\b[a-zA-Zа-яА-ЯёЁ]+\b", text) + corrupted_words: list[tuple[str, list[str]]] = [] + + for word in words: + has_lat: bool = bool(re.search(r"[a-zA-Z]", word)) + has_cyr: bool = bool(re.search(r"[а-яА-ЯёЁ]", word)) + + if has_lat and has_cyr: + cyr_letters: list[str] = re.findall(r"[а-яА-ЯёЁ]", word) + corrupted_words.append((word, cyr_letters)) + + return corrupted_words + + +def process( + path: Path, + extensions: set[str], + ignore_words: set[str], + ignore_files: set[str], + ignore_dirs: set[str], +) -> None: + path = path.resolve() + + if not path.is_dir(): + raise Exception(f"Path {str(path)!r} must be a directory") + + total_files_count: int = 0 + checked_files_count: int = 0 + corrupted_files_count: int = 0 + corrupted_words_count: int = 0 + ignored_words_count: int = 0 + + suffix_total_counter: dict[str, int] = Counter() + suffix_checked_counter: dict[str, int] = Counter() + suffix_corrupted_files_counter: dict[str, int] = Counter() + suffix_corrupted_words_counter: dict[str, int] = Counter() + + start_dt: datetime = datetime.now() + + for f in path.rglob("*.*"): + if not f.is_file() or f.name.lower() in ignore_files: + continue + + has_any: bool = any( + parent_dir.name.lower() in ignore_dirs for parent_dir in f.parents + ) + if has_any: + continue + + total_files_count += 1 + suffix_total_counter[f.suffix] += 1 + + if f.suffix not in extensions: + continue + + try: + checked_files_count += 1 + suffix_checked_counter[f.suffix] += 1 + + text: str = f.read_text("utf-8") + corrupted_words: list[tuple[str, list[str]]] = check_mixed_layout(text) + if corrupted_words: + corrupted_files_count += 1 + suffix_corrupted_files_counter[f.suffix] += 1 + + valid_corrupted_words: list[tuple[str, list[str]]] = [] + for word, cyr_letters in corrupted_words: + if word.lower() in ignore_words: + ignored_words_count += 1 + else: + valid_corrupted_words.append((word, cyr_letters)) + + if valid_corrupted_words: + corrupted_words_count += len(valid_corrupted_words) + suffix_corrupted_words_counter[f.suffix] += len( + valid_corrupted_words + ) + + print(f"File: {str(f)!r}") + for word, cyr_letters in valid_corrupted_words: + print(f" Word: {word!r}, letters: {cyr_letters}") + print() + + except UnicodeError as e: + print(f"{f} skip, error: {e}") + + print(f"Elapsed: {datetime.now() - start_dt}") + print() + + print("=== SUMMARY STATISTICS ===") + print(f"Total files found: {total_files_count}") + print(f"Total checked files: {checked_files_count}") + print(f"Files with mixed layout: {corrupted_files_count}") + print(f"Words with mixed layout: {corrupted_words_count}") + print(f"Ignored words: {ignored_words_count}") + print() + print(f"All extensions in path: {dict(suffix_total_counter)}") + print(f"Checked extensions: {dict(suffix_checked_counter)}") + print(f"Corrupted files by suffix: {dict(suffix_corrupted_files_counter)}") + print(f"Corrupted words by suffix: {dict(suffix_corrupted_words_counter)}") + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description="Cyrillic and Latin alphabet audit", + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + ) + parser.add_argument( + "path", + metavar="/PATH/TO/DIRECTORY", + type=Path, + help="Path to directory", + ) + parser.add_argument( + "-e", + "--ext", + nargs="+", + default=[".xml", ".java", ".xsd"], + help="File extensions to check", + ) + parser.add_argument( + "-iw", + "--ignore-words", + nargs="+", + default=[], + help="Words to ignore (case-insensitive)", + ) + parser.add_argument( + "-if", + "--ignore-files", + nargs="+", + default=[], + help="Files to ignore (case-insensitive)", + ) + parser.add_argument( + "-id", + "--ignore-dirs", + nargs="+", + default=["build"], + help="Directories to ignore (case-insensitive)", + ) + args = parser.parse_args() + + target_extensions: set[str] = { + ex if ex.startswith(".") else f".{ex}" for ex in args.ext + } + target_ignore_words: set[str] = {word.lower() for word in args.ignore_words} + target_ignore_files: set[str] = {word.lower() for word in args.ignore_files} + target_ignore_dirs: set[str] = {word.lower() for word in args.ignore_dirs} + + process( + path=args.path, + extensions=target_extensions, + ignore_words=target_ignore_words, + ignore_files=target_ignore_files, + ignore_dirs=target_ignore_dirs, + ) diff --git a/job_compassplus/designer/common.py b/job_compassplus/designer/common.py new file mode 100644 index 000000000..ae63b4ff4 --- /dev/null +++ b/job_compassplus/designer/common.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import re + +from dataclasses import dataclass, field +from pathlib import Path + + +PATTERN: re.Pattern = re.compile( + r"\* \[(?P\w+)\] (?P\d+) - (?P[^\n]+)\n\s*at\s*(?P[^\n]+)" +) + + +PATH_PROBLEMS_TAB: Path = Path(__file__).parent / "problems_tab.txt" + + +@dataclass +class Problem: + severity: str + code: int + error: str + at: str + maintainers: list[str] = field(default_factory=list) + + +def parse_text(text: str) -> list[Problem]: + items: list[Problem] = [] + + for m in PATTERN.finditer(text): + maintainers: list[str] = [] + for brackets in re.findall(r"\[(.*?)]", m.group()): + if "@" in brackets: + maintainers += [ + email.split("@")[0] + for email in brackets.split(", ") + ] + + items.append( + Problem( + severity=m.group("severity"), + code=int(m.group("code")), + error=m.group("error"), + at=m.group("at"), + maintainers=maintainers, + ) + ) + + return items + + +if __name__ == "__main__": + from pathlib import Path + + text = PATH_PROBLEMS_TAB.read_text("utf-8") + problems: list[Problem] = parse_text(text) + + print(f"Problems ({len(problems)}):") + for i, p in enumerate(problems, 1): + print(f"{i}. {p}") diff --git a/job_compassplus/designer/parse_problems_tab__code_by_number.py b/job_compassplus/designer/parse_problems_tab__code_by_number.py new file mode 100644 index 000000000..d28eb8535 --- /dev/null +++ b/job_compassplus/designer/parse_problems_tab__code_by_number.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from common import PATH_PROBLEMS_TAB, parse_text + + +text = PATH_PROBLEMS_TAB.read_text("utf-8") + +total: int = 0 +code_by_number: dict[int, int] = dict() + +for p in parse_text(text): + if p.code not in code_by_number: + code_by_number[p.code] = 0 + code_by_number[p.code] += 1 + + total += 1 + +print(f"Code ({len(code_by_number)}):") +for code, number in sorted(code_by_number.items(), key=lambda x: x[1], reverse=True): + print(f" {code}: {number}") +print(f"Total: {total}") +""" +Code (4): + 51: 4 + 137: 3 + 132: 2 + 999: 1 +Total: 10 +""" diff --git a/job_compassplus/designer/parse_problems_tab__maintainer_by_number.py b/job_compassplus/designer/parse_problems_tab__maintainer_by_number.py new file mode 100644 index 000000000..4fb387851 --- /dev/null +++ b/job_compassplus/designer/parse_problems_tab__maintainer_by_number.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from common import PATH_PROBLEMS_TAB, parse_text + + +text = PATH_PROBLEMS_TAB.read_text("utf-8") + +# TODO: +filter_by_codes: list[int] = [ + # 127, 137 +] + +user_by_number: dict[str, int] = dict() + +for p in parse_text(text): + if filter_by_codes and p.code not in filter_by_codes: + continue + + users = p.maintainers.copy() + if not users: + users.append("") + + for user in users: + if user not in user_by_number: + user_by_number[user] = 0 + user_by_number[user] += 1 + +print(f"Users ({len(user_by_number)}):") +for user, number in sorted(user_by_number.items(), key=lambda x: x[1], reverse=True): + print(f" {user!r}: {number}") +""" +Users (3): + 'bar': 7 + 'foo': 3 + '': 2 +""" diff --git a/job_compassplus/designer/parse_problems_tab__maintainer_by_number_details.py b/job_compassplus/designer/parse_problems_tab__maintainer_by_number_details.py new file mode 100644 index 000000000..893dc9167 --- /dev/null +++ b/job_compassplus/designer/parse_problems_tab__maintainer_by_number_details.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from collections import defaultdict +from common import PATH_PROBLEMS_TAB, Problem, parse_text + + +text = PATH_PROBLEMS_TAB.read_text("utf-8") + +# TODO: +filter_by_codes: list[int] = [ + # 127, 137 +] +print(f"Filter by codes: {filter_by_codes}") + +user_by_number_details: dict[str, list[Problem]] = defaultdict(list) +code_by_number: dict[int, int] = defaultdict(int) + +for p in parse_text(text): + code: int = p.code + if filter_by_codes and code not in filter_by_codes: + continue + + code_by_number[code] += 1 + + users = p.maintainers.copy() + if not users: + users.append("") + + user_by_number_details[", ".join(users)].append(p) + +print(f"Total users: {len(user_by_number_details)}") +print(f"Stats (codes {len(code_by_number)}, total {sum(code_by_number.values())}):") +for code, number in sorted(code_by_number.items(), key=lambda x: x[1], reverse=True): + print(f" Code {code}: {number}") +print() + +INDENT_1: str = " " + +for user, all_problems in sorted( + user_by_number_details.items(), + key=lambda x: len(x[1]), + reverse=True, +): + print(f"{user!r} ({len(all_problems)}):") + + code_by_problems: dict[int, list[Problem]] = defaultdict(list) + for p in all_problems: + code_by_problems[p.code].append(p) + + for code, problems in code_by_problems.items(): + print(f"{INDENT_1}Code {code} ({len(problems)}):") + + for p in problems: + print(f"{INDENT_1 * 2}[{p.severity}] {p.error} at {p.at}") + + print() + +""" +Filter by codes: [] +Total users: 4 +Stats (codes 4, total 10): + Code 51: 4 + Code 137: 3 + Code 132: 2 + Code 999: 1 + +'bar' (5): + Code 137 (1): + [NOTE] XXX [bar@example.com] [Fix available] at ABC::Common::Foo::Bar + Code 51 (4): + [WARNING] The definition 'ABC::Common::Foo:Bar:ownerPid' is XXX (See RADIX-24309, RADIX-17843. Ignore werror.) [Link] [bar@example.com] at ABC::Common::Foo:Model:Bar + [WARNING] The definition 'ABC::Common::Foo:Bar:ownerPid' is XXX (See RADIX-24309, RADIX-17843. Ignore werror.) [Link] [bar@example.com] at ABC::Common::Foo:Model:Bar + [WARNING] The definition 'ABC::Common::Foo:Bar:ownerPid' is XXX (See RADIX-24309, RADIX-17843. Ignore werror.) [Link] [bar@example.com] at ABC::Common::Foo:Model:Bar + [WARNING] The definition 'ABC::Common::Foo:Bar:ownerPid' is XXX (See RADIX-24309, RADIX-17843. Ignore werror.) [Link] [bar@example.com] at ABC::Common::Foo:Model:Bar + +'' (2): + Code 132 (2): + [NOTE] Editor page does not contain property `XXX` from `overridden` base page (See RADIX-23441) [Link] at ABC::FOO::BAR:General + [NOTE] Editor page does not contain property `XXX` from `overridden` base page (See RADIX-23441) [Link] at ABC::FOO::BAR:General + +'foo, bar' (2): + Code 137 (1): + [NOTE] XXX [foo@example.com, bar@example.com] [Fix available] at ABC::Common::Foo::Bar + Code 999 (1): + [ERROR] XXX [foo@example.com, bar@example.com] at ABC::Common::Foo::Bar + +'foo' (1): + Code 137 (1): + [NOTE] XXX [foo@example.com] [Fix available] at ABC::Interfacing.Foo::Bar +""" diff --git a/job_compassplus/designer/problems_tab.txt b/job_compassplus/designer/problems_tab.txt new file mode 100644 index 000000000..5d2e25805 --- /dev/null +++ b/job_compassplus/designer/problems_tab.txt @@ -0,0 +1,29 @@ +Cursor Class 'ABC::Interfacing.Foo::Bar' +* [NOTE] 137 - XXX [foo@example.com] [Fix available] + at ABC::Interfacing.Foo::Bar + +Statement Class 'ABC::Common::Foo::Bar' +* [NOTE] 137 - XXX [bar@example.com] [Fix available] + at ABC::Common::Foo::Bar + +User Method 'ABC::Common::Foo:Model:Bar' +* [WARNING] 51 - The definition 'ABC::Common::Foo:Bar:ownerPid' is XXX (See RADIX-24309, RADIX-17843. Ignore werror.) [Link] [bar@example.com] + at ABC::Common::Foo:Model:Bar +* [WARNING] 51 - The definition 'ABC::Common::Foo:Bar:ownerPid' is XXX (See RADIX-24309, RADIX-17843. Ignore werror.) [Link] [bar@example.com] + at ABC::Common::Foo:Model:Bar +* [WARNING] 51 - The definition 'ABC::Common::Foo:Bar:ownerPid' is XXX (See RADIX-24309, RADIX-17843. Ignore werror.) [Link] [bar@example.com] + at ABC::Common::Foo:Model:Bar +* [WARNING] 51 - The definition 'ABC::Common::Foo:Bar:ownerPid' is XXX (See RADIX-24309, RADIX-17843. Ignore werror.) [Link] [bar@example.com] + at ABC::Common::Foo:Model:Bar + +Editor Page 'ABC::FOO::BAR:General' +* [NOTE] 132 - Editor page does not contain property `XXX` from `overridden` base page (See RADIX-23441) [Link] + at ABC::FOO::BAR:General +* [NOTE] 132 - Editor page does not contain property `XXX` from `overridden` base page (See RADIX-23441) [Link] + at ABC::FOO::BAR:General + +Statement Class 'ABC::Common::Foo::Bar' +* [NOTE] 137 - XXX [foo@example.com, bar@example.com] [Fix available] + at ABC::Common::Foo::Bar +* [ERROR] 999 - XXX [foo@example.com, bar@example.com] + at ABC::Common::Foo::Bar diff --git a/job_compassplus/doc_vs_docx.py b/job_compassplus/doc_vs_docx.py new file mode 100644 index 000000000..d74a1bd0b --- /dev/null +++ b/job_compassplus/doc_vs_docx.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from pathlib import Path + +# pip install python-docx==1.1.0 +from docx import Document + + +PATH_DIR: str = r"C:\DOC\Specifications\Interfaces" + + +docx_num = 0 +doc_num = 0 +for f in Path(PATH_DIR).glob("*.doc*"): + if f.name.startswith("~"): + continue + + try: + Document(f) + ok = True + docx_num += 1 + except: + ok = False + doc_num += 1 + + print(f"[{'+' if ok else '-'}]", f) + +print() +print("Total:", docx_num + doc_num) +print("Docx:", docx_num) +print("Doc:", doc_num) diff --git a/job_compassplus/find_duplicated_specs.py b/job_compassplus/find_duplicated_specs.py new file mode 100644 index 000000000..374129698 --- /dev/null +++ b/job_compassplus/find_duplicated_specs.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from collections import defaultdict +from pathlib import Path +from zlib import crc32 + + +DIR = Path(r"C:\DOC\W4") + +crc_by_files: dict[int, list[Path]] = defaultdict(list) +for f in DIR.rglob("*"): + if not f.is_file(): + continue + + crc = crc32(f.read_bytes()) + crc_by_files[crc].append(f) + +for files in crc_by_files.values(): + if len(files) == 1: + continue + + print(*files, sep="\n") + print() diff --git a/job_compassplus/find_invalid_utf_8_files_java.py b/job_compassplus/find_invalid_utf_8_files_java.py new file mode 100644 index 000000000..3deca20d0 --- /dev/null +++ b/job_compassplus/find_invalid_utf_8_files_java.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from pathlib import Path + + +def process(path: str): + if isinstance(path, str): + path = Path(path) + + if not path.exists(): + raise Exception(f"Not exists: {path}") + + for file in path.rglob("*.java"): + try: + file.read_text("utf-8") + except UnicodeError as e: + print(f"{file}, error: {e}") + for line in file.read_bytes().splitlines(): + try: + str(line, "utf-8") + except UnicodeError: + print(f" Line: {line}") + + print() + + +if __name__ == "__main__": + import argparse + + parser = argparse.ArgumentParser( + description="Find invalid utf-8 files java" + ) + parser.add_argument( + "path", + metavar="/PATH/TO", + help="Path to source tree", + ) + args = parser.parse_args() + + process(args.path) diff --git a/job_compassplus/get_all_department_employees/config.py b/job_compassplus/get_all_department_employees/config.py deleted file mode 100644 index d414df7d4..000000000 --- a/job_compassplus/get_all_department_employees/config.py +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -__author__ = 'ipetrash' - - -import os -import sys - -from pathlib import Path - - -DIR = Path(__file__).resolve().parent -TOKEN_FILE_NAME = DIR / 'TOKEN.txt' -SEP = '|' - -try: - TOKEN = os.environ.get('TOKEN') - if not TOKEN: - TOKEN = TOKEN_FILE_NAME.read_text('utf-8').strip() - if not TOKEN: - raise Exception('TOKEN пустой!') - - USERNAME, PASSWORD = TOKEN.split(SEP) - -except: - print( - f'Нужно в {TOKEN_FILE_NAME.name} или в переменную окружения ' - f'TOKEN добавить логин/пароль (формат <домен>\\<логин>{SEP}<пароль>)' - ) - TOKEN_FILE_NAME.touch() - sys.exit() diff --git a/job_compassplus/get_all_department_employees/main.py b/job_compassplus/get_all_department_employees/main.py deleted file mode 100644 index 973c4b988..000000000 --- a/job_compassplus/get_all_department_employees/main.py +++ /dev/null @@ -1,82 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -__author__ = 'ipetrash' - - -import sys - -from dataclasses import dataclass -from pathlib import Path - -from bs4 import BeautifulSoup - -# pip install requests_ntlm2 -from requests_ntlm2 import HttpNtlmAuth - -DIR = Path(__file__).resolve().parent -ROOT_DIR = DIR.parent - -sys.path.append(str(ROOT_DIR)) -from root_common import session - -from config import USERNAME, PASSWORD - - -@dataclass -class Employee: - full_name: str - user_name: str - position: str - url: str - - -session.auth = HttpNtlmAuth(USERNAME, PASSWORD) - -URL = 'https://mysite.compassplus.com/Person.aspx?accountname={}' - - -def get_employees(boss_username: str) -> list[Employee]: - url = URL.format(boss_username) - rs = session.get(url) - rs.raise_for_status() - - root = BeautifulSoup(rs.content, 'html.parser') - root_table = root.select_one('#ReportingHierarchy') - - items = [] - - for td in root_table.select('.ms-orgname'): - a = td.a - url = a['href'] - items.append( - Employee( - full_name=a.get_text(strip=True), - # Example: "https://.../Person.aspx?accountname=CP%5Cipetrash" -> "ipetrash" - user_name=url.split('%5C')[-1], - url=url, - position=td.select_one('.ms_metadata').get_text(strip=True), - ) - ) - - return items - - -if __name__ == '__main__': - from base64 import b64decode - boss_username = b64decode('Q1BcbnZheW5lcg==').decode('utf-8') - - employees = get_employees(boss_username) - print(f'Employees ({len(employees)}):') - for i, employee in enumerate(employees, 1): - print(f'{i}. {employee}') - """ - ... - 9. Employee(full_name='Ilya A. Petrash', user_name='ipetrash', position='Senior Software Engineer', url='https://mysite.compassplus.com:443/Person.aspx?accountname=CP%5Cipetrash') - ... - """ - print() - - usernames = sorted(x.user_name for x in employees) - print(usernames) - print(', '.join(usernames)) diff --git a/job_compassplus/jira_avatars/get_avatars.py b/job_compassplus/jira_avatars/get_avatars.py new file mode 100644 index 000000000..a714a0cde --- /dev/null +++ b/job_compassplus/jira_avatars/get_avatars.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import sys + +from dataclasses import dataclass +from pathlib import Path +from typing import Any, Self + +DIR = Path(__file__).resolve().parent +ROOT_DIR = DIR.parent + +sys.path.append(str(ROOT_DIR)) +from root_config import JIRA_HOST +from root_common import session + + +# Examples: +""" +{ + "system": [ + {"id":"10122","isSystemAvatar":true,"isSelected":false,"isDeletable":false,"urls":{"48x48":"https://.../useravatar?avatarId=10122","24x24":"https://.../useravatar?size=small&avatarId=10122","16x16":"https://.../useravatar?size=xsmall&avatarId=10122","32x32":"https://.../useravatar?size=medium&avatarId=10122"},"selected":false}, + ... + {"id":"17241","isSystemAvatar":true,"isSelected":true,"isDeletable":false,"urls":{"48x48":"https://.../useravatar?avatarId=17241","24x24":"https://.../useravatar?size=small&avatarId=17241","16x16":"https://.../useravatar?size=xsmall&avatarId=17241","32x32":"https://.../useravatar?size=medium&avatarId=17241"},"selected":true}, + ... + {"id":"17252","isSystemAvatar":true,"isSelected":false,"isDeletable":false,"urls":{"48x48":"https://.../useravatar?avatarId=17252","24x24":"https://.../useravatar?size=small&avatarId=17252","16x16":"https://.../useravatar?size=xsmall&avatarId=17252","32x32":"https://.../useravatar?size=medium&avatarId=17252"},"selected":false}], + "custom": [] +} +""" + + +@dataclass(frozen=True) +class AvatarUrls: + size_48x48: str + size_32x32: str + size_24x24: str + size_16x16: str + + @classmethod + def from_dict(cls, data: dict[str, str]) -> Self: + return cls( + size_48x48=data.get("48x48"), + size_24x24=data.get("24x24"), + size_32x32=data.get("32x32"), + size_16x16=data.get("16x16"), + ) + + +@dataclass(frozen=True) +class JiraAvatar: + id: str + is_system_avatar: bool + is_selected: bool + is_deletable: bool + urls: AvatarUrls + selected: bool # NOTE: Дублирует isSelected + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> Self: + return cls( + id=data["id"], + is_system_avatar=data["isSystemAvatar"], + is_selected=data["isSelected"], + is_deletable=data["isDeletable"], + urls=AvatarUrls.from_dict(data["urls"]), + selected=data["selected"], + ) + + +@dataclass(frozen=True) +class JiraAvatarResponse: + system: list[JiraAvatar] + custom: list[JiraAvatar] + + @classmethod + def from_dict(cls, data: dict[str, list[dict[str, Any]]]) -> Self: + def _get_avatars(key: str) -> list[JiraAvatar]: + return [JiraAvatar.from_dict(d) for d in data[key]] + + return cls( + system=_get_avatars("system"), + custom=_get_avatars("custom"), + ) + + +def get_avatars(username: str) -> JiraAvatarResponse: + url = f"{JIRA_HOST}/rest/api/latest/user/avatars?username={username}" + + rs = session.get(url) + rs.raise_for_status() + + return JiraAvatarResponse.from_dict(rs.json()) + + +if __name__ == "__main__": + rs: JiraAvatarResponse = get_avatars("ipetrash") + + print(f"System ({len(rs.system)}):") + for i, avatar in enumerate(rs.system, 1): + print(f" {i}. {avatar}") + + print() + + print(f"Custom ({len(rs.custom)}):") + for i, avatar in enumerate(rs.custom, 1): + print(f" {i}. {avatar}") diff --git a/job_compassplus/jira_dump_issues.py b/job_compassplus/jira_dump_issues.py new file mode 100644 index 000000000..13a3008dc --- /dev/null +++ b/job_compassplus/jira_dump_issues.py @@ -0,0 +1,132 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import time +import sqlite3 + +from pathlib import Path +from typing import Any + +from root_config import JIRA_HOST +from root_common import session + + +DIR: Path = Path(__file__).resolve().parent +FILE_NAME_DB: Path = DIR / "jira_local.sqlite" + +URL_SEARCH = f"{JIRA_HOST}/rest/api/latest/search" + + +def init_sqlite(filename: Path) -> sqlite3.Connection: + conn = sqlite3.connect(filename) + conn.execute( + """ + CREATE TABLE IF NOT EXISTS Task ( + id INTEGER PRIMARY KEY, + key TEXT, + summary TEXT, + description TEXT, + status TEXT, + issue_type TEXT, + created TEXT, + is_indexed INTEGER DEFAULT 0 + ) + """ + ) + conn.commit() + return conn + + +def get_last_id(conn: sqlite3.Connection, project: str) -> int: + res = conn.execute( + "SELECT MAX(id) FROM Task WHERE key LIKE ?", (f"{project}-%",) + ).fetchone() + return res[0] if res[0] else 0 + + +def sync_jira_to_sqlite(conn: sqlite3.Connection, projects: list[str]): + max_results: int = 100 + + for project in projects: + last_id: int = get_last_id(conn, project) + + while True: + print(f"[*] Поиск задач в Jira проекта {projects} от ID > {last_id}...") + + filter_id: str = f"AND id > {last_id}" if last_id > 0 else "" + jql: str = f"project = {project} {filter_id} ORDER BY id ASC" + + query: dict[str, Any] = { + "jql": jql, + "fields": "key,summary,description,status,issuetype,created", + "maxResults": max_results, + } + + rs = session.get(URL_SEARCH, params=query) + try: + rs.raise_for_status() + except Exception as e: + print(rs.json()) + raise e + + issues: list[dict] = rs.json().get("issues") + if not issues: + print("Больше нет задач") + break + + for issue in issues: + print(issue) + + issue_id: int = int(issue["id"]) + issue_key: str = issue["key"] + issue_summary: str = issue["fields"].get("summary", "") + issue_desc: str = str(issue["fields"].get("description")) + issue_status: str = issue["fields"]["status"]["name"] + issue_type: str = issue["fields"]["issuetype"]["name"] + issue_created: str = issue["fields"]["created"] + + conn.execute( + """ + INSERT OR IGNORE INTO Task + (id, key, summary, description, status, issue_type, created) + VALUES + (?, ?, ?, ?, ?, ?, ?) + """, + ( + issue_id, + issue_key, + issue_summary, + issue_desc, + issue_status, + issue_type, + issue_created, + ), + ) + + last_id = issue_id + + conn.commit() + + print(f"[+] Загружено {len(issues)} задач...") + + time.sleep(5) + + +if __name__ == "__main__": + # TODO: Возможность задавать аргументом + projects: list[str] = [ + # "OPTT", + "TXI", + "TWRBS", + "TXCORE", + "TXACQ", + ] + + # TODO: Возможность задавать аргументом путь к БД + # FILE_NAME_DB: Path = DIR / "jira_local_OPTT.sqlite" + + db_conn = init_sqlite(FILE_NAME_DB) + sync_jira_to_sqlite(db_conn, projects) diff --git a/job_compassplus/jira_get_avatars/main.py b/job_compassplus/jira_get_avatars/main.py deleted file mode 100644 index 65c3a4a9e..000000000 --- a/job_compassplus/jira_get_avatars/main.py +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -__author__ = 'ipetrash' - - -import sys -from pathlib import Path - -DIR = Path(__file__).resolve().parent -ROOT_DIR = DIR.parent - -sys.path.append(str(ROOT_DIR)) -from root_common import session - - -url = 'https://helpdesk.compassluxe.com/rest/api/latest/user/avatars?username=ipetrash' - -rs = session.get(url) -print(rs) -for kind, avatars in rs.json().items(): - print(f'{kind} ({len(avatars)}):') - for item in avatars: - print(f' {item}') diff --git a/job_compassplus/jira_get_current_user.py b/job_compassplus/jira_get_current_user.py new file mode 100644 index 000000000..32078bc7f --- /dev/null +++ b/job_compassplus/jira_get_current_user.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from typing import Any + +from root_config import JIRA_HOST +from root_common import session + + +URL_API_MYSELF = f"{JIRA_HOST}/rest/api/latest/myself" + + +def get_current_user() -> dict[str, Any]: + rs = session.get(URL_API_MYSELF) + rs.raise_for_status() + + return rs.json() + + +if __name__ == "__main__": + user = get_current_user() + print("Current user:", user) + print("Current user name:", user["name"]) + print() + + import json + print(f"Current user (pretty):\n{json.dumps(user, indent=4)}") diff --git a/job_compassplus/jira_get_total_resolved.py b/job_compassplus/jira_get_total_resolved.py new file mode 100644 index 000000000..87eb9fcaa --- /dev/null +++ b/job_compassplus/jira_get_total_resolved.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import time +from dataclasses import dataclass + +from root_config import JIRA_HOST +from root_common import session + + +URL_SEARCH = f"{JIRA_HOST}/rest/api/latest/search" + +JQL_RESOLUTION_DATE = "assignee = currentUser() AND resolutiondate" +JQL_TOTAL = f"{JQL_RESOLUTION_DATE} IS NOT EMPTY" +JQL_LAST_WEEK = f"{JQL_RESOLUTION_DATE} >= startOfDay(-7)" +JQL_LAST_MONTH = f"{JQL_RESOLUTION_DATE} >= startOfMonth(-1)" +JQL_LAST_YEAR = f"{JQL_RESOLUTION_DATE} >= startOfMonth(-12)" + + +@dataclass +class Stats: + last_7_days: int + last_month: int + last_year: int + total: int + + +def get_total(jql: str) -> int: + query = { + "jql": jql, + "fields": "key", + "maxResults": 0, + } + + rs = session.get(URL_SEARCH, params=query) + rs.raise_for_status() + + return rs.json()["total"] + + +def get_stats(sleep: float = 0.5) -> Stats: + last_7_days = get_total(JQL_LAST_WEEK) + time.sleep(sleep) + + last_month = get_total(JQL_LAST_MONTH) + time.sleep(sleep) + + last_year = get_total(JQL_LAST_YEAR) + time.sleep(sleep) + + total = get_total(JQL_TOTAL) + + return Stats( + last_7_days=last_7_days, + last_month=last_month, + last_year=last_year, + total=total, + ) + + +if __name__ == "__main__": + print(get_stats()) diff --git a/job_compassplus/jira_search_by_avatar/main.py b/job_compassplus/jira_search_by_avatar/main.py new file mode 100644 index 000000000..a080f57e3 --- /dev/null +++ b/job_compassplus/jira_search_by_avatar/main.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import sys +import time +from pathlib import Path + +DIR = Path(__file__).resolve().parent +ROOT_DIR = DIR.parent + +sys.path.append(str(ROOT_DIR)) +from root_config import JIRA_HOST +from root_common import session + + +URL_PROFILE_FORMAT = f"{JIRA_HOST}/secure/ViewProfile.jspa?name={{name}}" +URL_USER_SEARCH = f"{JIRA_HOST}/rest/api/latest/user/search" + + +def search_by_avatar(avatar_id: int) -> list[dict]: + start_at = 0 + max_results = 500 + + avatar_id = str(avatar_id) + + items = [] + while True: + params = { + "username": ".", + "startAt": start_at, + "maxResults": max_results, + } + + rs = session.get(URL_USER_SEARCH, params=params) + rs.raise_for_status() + + result = rs.json() + if not result: + break + + for user in result: + if avatar_id in user["avatarUrls"]["48x48"]: + items.append(user) + + time.sleep(5) + + start_at += max_results + + return items + + +if __name__ == "__main__": + avatar_id = 17250 # pirate + users = search_by_avatar(avatar_id) + print(f"Users ({len(users)}):") + for i, user in enumerate(users, 1): + display_name = user["displayName"] + user_url = URL_PROFILE_FORMAT.format(name=user["key"]) + print(f"{i}. '{display_name}': {user_url}") diff --git a/job_compassplus/jira_show_last_issue/main.py b/job_compassplus/jira_show_last_issue/main.py new file mode 100644 index 000000000..062ee8a21 --- /dev/null +++ b/job_compassplus/jira_show_last_issue/main.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import sys +from pathlib import Path + +DIR = Path(__file__).resolve().parent +ROOT_DIR = DIR.parent +sys.path.append(str(ROOT_DIR)) +from root_config import JIRA_HOST +from root_common import session + + +URL_SEARCH = f"{JIRA_HOST}/rest/api/latest/search" + + +def get_last_issue_key(project: str) -> str | None: + query = { + "jql": f"project={project} ORDER BY created DESC", + "fields": "key", + "maxResults": 1, + } + + rs = session.get(URL_SEARCH, params=query) + if rs.status_code == 400: + return + + rs.raise_for_status() + + try: + return rs.json()["issues"][0]["key"] + except Exception: + return + + +if __name__ == "__main__": + import time + + for project in [ + "OPTT", + "NOT_FOUND", + "RADIX", + "TXI", + "TXACQ", + "TXCORE", + "TXISS", + "TXPG", + "TWO", + "FLORA", + ]: + last_issue_key: str | None = get_last_issue_key(project=project) + print(f"{project}: {last_issue_key if last_issue_key else '-'}", ) + time.sleep(0.5) diff --git a/job_compassplus/jira_show_last_issue/notify_me.py b/job_compassplus/jira_show_last_issue/notify_me.py new file mode 100644 index 000000000..35401d6d2 --- /dev/null +++ b/job_compassplus/jira_show_last_issue/notify_me.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import time +from datetime import datetime + +from tkinter.messagebox import showinfo + +from main import get_last_issue_key + + +NEED_PROJECT: str = "OPTT" +NEED_ISSUE_ID: int = 999 + + +while True: + last_issue_key: str = get_last_issue_key(NEED_PROJECT) + print(f'[{datetime.now():%d/%m/%Y %H:%M:%S}] last_issue_key: {last_issue_key}') + + # NOTE: "OPTT-1234" -> 1234 + issue_id: int = int(last_issue_key.split("-")[-1]) + if issue_id >= NEED_ISSUE_ID: + showinfo(title="Информация", message=f"Появилась задача {last_issue_key}!") + + time.sleep(30) diff --git a/job_compassplus/jira_sprint_get_total_overtime_hours.py b/job_compassplus/jira_sprint_get_total_overtime_hours.py new file mode 100644 index 000000000..4c7b48ccd --- /dev/null +++ b/job_compassplus/jira_sprint_get_total_overtime_hours.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import logging +import sys + +from dataclasses import dataclass +from datetime import datetime + +from root_config import JIRA_HOST +from root_common import session + + +URL_SEARCH = f"{JIRA_HOST}/rest/api/latest/search" + +FIELD_OVERTIME_HOURS = "customfield_13440" + +QUERY = { + "jql": ( + "assignee = currentUser()" + " AND project = Sprint AND type = Sub-task" + " AND created >= startOfYear() AND created <= endOfYear()" + " ORDER BY created DESC" + ), + "fields": f"key,created,{FIELD_OVERTIME_HOURS}", +} + + +@dataclass +class Sprint: + key: str + created: datetime + overtime_hours: int + + +default_handler = logging.StreamHandler(stream=sys.stdout) +default_handler.setFormatter( + logging.Formatter( + "[%(asctime)s] %(filename)s:%(lineno)d %(levelname)-8s %(message)s" + ) +) +logger = logging.getLogger("jira_sprint_get_total_overtime_hours") +logger.setLevel(logging.WARNING) +logger.addHandler(default_handler) + + +def get_sprints_with_overtime_hours() -> list[Sprint]: + logger.debug(f"Load: {URL_SEARCH}") + + rs = session.get(URL_SEARCH, params=QUERY) + logger.debug(f"Response: {rs}") + rs.raise_for_status() + + items: list[Sprint] = [] + + issues = rs.json()["issues"] + logger.info(f"Total issues: {len(issues)}") + + for issue in issues: + key = issue["key"] + created_str = issue["fields"]["created"] + + overtime_hours = issue["fields"][FIELD_OVERTIME_HOURS] + overtime_hours: int = int(overtime_hours) if overtime_hours else 0 + + logger.info(f"Issue: {key}, created_str: {created_str}, overtime hours: {overtime_hours}") + + items.append( + Sprint( + key=key, + created=datetime.strptime(created_str, "%Y-%m-%dT%H:%M:%S.%f%z"), + overtime_hours=overtime_hours, + ) + ) + + return items + + +if __name__ == "__main__": + # NOTE: Debug + # logger.setLevel(logging.DEBUG) + + total_overtime_hours = 0 + sprints = get_sprints_with_overtime_hours() + print(f"Sprints ({len(sprints)}):") + for i, sprint in enumerate(sprints, 1): + print(f" {i}. {sprint}") + total_overtime_hours += sprint.overtime_hours + + print() + print(f"Total overtime hours: {total_overtime_hours}") diff --git a/job_compassplus/merge_tc33a_files.py b/job_compassplus/merge_tc33a_files.py new file mode 100644 index 000000000..7c6fbe9fb --- /dev/null +++ b/job_compassplus/merge_tc33a_files.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import re +from pathlib import Path + + +DIR = Path(__file__).parent.resolve() +path_output = DIR / "tc33a-all-files.txt" + +with open(path_output, "w", encoding="ascii") as f_out: + path_dir = Path(r"C:\DOC\Visa спецификации\Base II TC33A\TC33_Capture_Transaction") + for f in path_dir.rglob("*.*"): + if not f.is_file(): + print(f"[#] Не файл: {str(f)!r}") + continue + + if any( + name in str(f) + for name in [ + # NOTE: Замечены файлы с кривым форматом, типа дат 222222 и 123456. + # В задаче не было таких, вероятно кривой пример + "TWICH-4378", + # NOTE: Файл был дополнен, переделан и отдельно занесен в тест + "TC33_CaptureTran_All_CP_Test.ctf", + # NOTE: Была ошибка при парсинге, постфикс имени файла вызывает сомнения + "TC33_CaptureTran_All_CP_Test (No CP07, CP05 TCR7)_VISA_err.ctf", + ] + ): + print(f"[#] Пропуск файла: {str(f)!r}") + continue + + print(f) + print(f.relative_to(path_dir)) + + try: + text: str = f.read_text("ascii") + print("Длина:", len(text)) + + first_line_length = len(text.splitlines()[0]) + print("Длина первой строки:", first_line_length) + if first_line_length != 168: + print("[#] Не CTF, пропуск файла") + continue + + # NOTE: Кривой формат даты + if "_MC" in str(f) and "CP12" in text and "12345678 " in text: + print(f"[#] Замена кривой даты в {str(f)!r}") + text = text.replace("12345678 ", "1030140815") + + if "inctf TC33A.EPIN.txt" in str(f) and "262012" in text: + print(f"[#] Замена кривой даты в {str(f)!r}") + text = text.replace("262012", "261220") + + lines = text.splitlines() + for i, line in enumerate(lines): + if line.startswith("3301") and re.search("^3300.+?CP06", lines[i - 1]): + print( + f"[#] Комментирование строки CP06 TCR1 - не поддерживается запись" + ) + lines[i] = f"# NOTE: Not supported: {line}" + + elif line.startswith("3309170"): + print( + f"[#] Комментирование строки CP01 TCR9 - не поддерживается запись" + ) + lines[i] = f"# NOTE: Not supported: {line}" + + text = "\n".join(lines) + + f_out.write(f"# START FILE: {f.relative_to(path_dir)}\n") + f_out.write(text) + f_out.write(f"\n# END FILE: {f.relative_to(path_dir)}\n\n") + + except UnicodeDecodeError as e: + print("[#]", e) + + print() diff --git a/job_compassplus/mysite_compassplus_com/common.py b/job_compassplus/mysite_compassplus_com/common.py new file mode 100644 index 000000000..147655507 --- /dev/null +++ b/job_compassplus/mysite_compassplus_com/common.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import sys +from pathlib import Path +from typing import Any + +from bs4 import Tag + +DIR = Path(__file__).resolve().parent +ROOT_DIR = DIR.parent + +sys.path.append(str(ROOT_DIR)) +from site_common import do_get, do_post +from root_config import JIRA_HOST + + +URL_BASE = "https://mysite.compassplus.com" +URL = f"{URL_BASE}/Person.aspx?accountname={{}}" + + +def get_profile_organization(full_username: str) -> dict[str, Any]: + url = f"{URL_BASE}/_vti_bin/SilverlightProfileService.json/GetUserSLProfileData" + rs = do_post(url, json={"AccountNames": [full_username]}) + return rs.json() + + +def is_active_profile(full_username: str) -> bool: + organization = get_profile_organization(full_username) + data = organization["d"][0] + # Если хотя бы один из них имеет значение, отличное от [] или None + return bool(data["Parent"] or data["Siblings"] or data["Children"]) + + +def get_jira_user_active(username: str) -> bool: + url = f"{JIRA_HOST}/rest/api/latest/user?username={username}" + return do_get(url).json()["active"] + + +def get_text(el: Tag) -> str: + return el.get_text(strip=True) + + +if __name__ == "__main__": + full_username = r"CP\ipetrash" + + rs = do_get(URL.format(full_username)) + print(rs) + + organization = get_profile_organization(full_username) + print(organization) + + data = organization["d"][0] + print("Parent:", data["Parent"]) + print("Siblings:", data["Siblings"]) + print("Children:", data["Children"]) + print() + + print("Is active (mysite):", is_active_profile(full_username)) + print("Is active (jira):", get_jira_user_active(full_username.rsplit("\\")[-1])) diff --git a/job_compassplus/mysite_compassplus_com/get_all_department_employees.py b/job_compassplus/mysite_compassplus_com/get_all_department_employees.py new file mode 100644 index 000000000..94cf26894 --- /dev/null +++ b/job_compassplus/mysite_compassplus_com/get_all_department_employees.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from dataclasses import dataclass +from bs4 import BeautifulSoup +from common import URL, do_get, get_text + + +@dataclass +class Employee: + full_name: str + user_name: str + position: str + url: str + + +def get_employees(boss_username: str) -> list[Employee]: + url: str = URL.format(boss_username) + print("Load", url) + + rs = do_get(url) + + root = BeautifulSoup(rs.content, "html.parser") + root_table = root.select_one("#ReportingHierarchy") + + items = [] + + for td in root_table.select(".ms-orgname"): + a = td.a + url = a["href"] + items.append( + Employee( + full_name=get_text(a), + # Example: "https://.../Person.aspx?accountname=CP%5Cipetrash" -> "ipetrash" + user_name=url.split("%5C")[-1], + url=url, + position=get_text(td.select_one(".ms_metadata")), + ) + ) + + return items + + +if __name__ == "__main__": + from base64 import b64decode + + for boss_username in [ + b64decode("Q1BcbnZheW5lcg==").decode("utf-8"), + b64decode("Q1BcYXZvc3RyaWtvdg==").decode("utf-8"), + b64decode("Q1BceXJlbWl6b3Y=").decode("utf-8"), + ]: + print(f"Boss: {boss_username!r}") + + employees = get_employees(boss_username) + print(f"Employees ({len(employees)}):") + for i, employee in enumerate(employees, 1): + print(f"{i}. {employee}") + """ + ... + 9. Employee(full_name='Ilya A. Petrash', user_name='ipetrash', position='Senior Software Engineer', url='https://mysite.compassplus.com:443/Person.aspx?accountname=CP%5Cipetrash') + ... + """ + print() + + usernames = sorted(x.user_name for x in employees) + print(usernames) + print(", ".join(usernames)) + + print("\n" + "-" * 100 + "\n") diff --git a/job_compassplus/mysite_compassplus_com/get_person_info.py b/job_compassplus/mysite_compassplus_com/get_person_info.py new file mode 100644 index 000000000..6a26aa2eb --- /dev/null +++ b/job_compassplus/mysite_compassplus_com/get_person_info.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from dataclasses import dataclass +from urllib.parse import urljoin + +from bs4 import BeautifulSoup +from common import URL, do_get, get_text, is_active_profile, get_jira_user_active + + +@dataclass +class Person: + name: str + position: str + department: str + img_url: str + location: str + birthday: str + is_active: bool | None = None + + def download_img(self) -> bytes: + return do_get(self.img_url).content + + +def get_person_info(name: str, domain: str = "CP") -> Person | None: + full_username = rf"{domain}\{name}" + + url = URL.format(full_username) + rs = do_get(url) + soup = BeautifulSoup(rs.content, "html.parser") + + img_el = soup.select_one("#ctl00_PictureUrlImage") + if not img_el: # Сайт не умеет показывать 404 при отсутствующем пользователе + return + + default_value = "N/A" + + try: + position = get_text(soup.select_one("#ProfileViewer_ValueTitle")) + if not position: + position = default_value + except Exception: + position = default_value + + try: + department = get_text(soup.select_one("#ProfileViewer_ValueDepartment")) + if not department: + department = default_value + except Exception: + department = default_value + + try: + css_path = 'div[id *= "_ProfileViewer_SPS-Location"] > .ms-profile-detailsValue' + location = get_text(soup.select_one(css_path)) + if not location: + location = default_value + except Exception: + location = default_value + + try: + css_path = 'div[id *= "_ProfileViewer_SPS-Birthday"] > .ms-profile-detailsValue' + birthday = get_text(soup.select_one(css_path)) + if not birthday: + birthday = default_value + except Exception: + birthday = default_value + + try: + # NOTE: is_active_profile для mysite не подходит - не всегда оттуда убирают информацию + # В джире эта информация, похоже, всегда актуальная + is_active = get_jira_user_active(name) + except Exception: + try: + is_active = is_active_profile(full_username) + except Exception: + is_active = None + + return Person( + name=name, + position=position, + department=department, + img_url=urljoin(rs.url, img_el["src"]), + location=location, + birthday=birthday, + is_active=is_active, + ) + + +if __name__ == "__main__": + username = "ipetrash" + + info = get_person_info(username) + print(info) + # Person(name='ipetrash', position='Senior Software Engineer', department='TX SPD, Application Platforms Division', img_url='https://portal.compassplus.com/my/User%20Photos/Profile%20Pictures/ipetrash.jpg', location='Magnitogorsk', birthday='August 18', is_active=True) + + import json + from dataclasses import asdict + + print(json.dumps(asdict(info), ensure_ascii=False, indent=4)) + """ + { + "name": "ipetrash", + "position": "Senior Software Engineer", + "department": "TX SPD, Application Platforms Division", + "img_url": "https://portal.compassplus.com/my/User%20Photos/Profile%20Pictures/ipetrash.jpg", + "location": "Magnitogorsk", + "birthday": "August 18", + "is_active": true + } + """ diff --git a/job_compassplus/mysite_compassplus_com/get_profile_image.py b/job_compassplus/mysite_compassplus_com/get_profile_image.py new file mode 100644 index 000000000..01e0e9edf --- /dev/null +++ b/job_compassplus/mysite_compassplus_com/get_profile_image.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from urllib.parse import urljoin +from bs4 import BeautifulSoup +from common import URL, do_get + + +def get_profile_image(username: str, domain: str = "CP") -> bytes | None: + url = URL.format(fr"{domain}\{username}") + + rs = do_get(url) + + root = BeautifulSoup(rs.content, "html.parser") + img_el = root.select_one("#ctl00_PictureUrlImage") + if not img_el: # Сайт не умеет показывать 404 при отсутствующем пользователе + return + + img_url = urljoin(rs.url, img_el["src"]) + + rs = do_get(img_url) + return rs.content + + +if __name__ == "__main__": + username = "ipetrash" + + img_data = get_profile_image(username) + print(len(img_data), img_data[:20]) + + from pathlib import Path + img_path = Path(__file__).parent / f"{username}.jpg" + img_path.write_bytes(img_data) diff --git a/job_compassplus/mysite_compassplus_com/get_profile_info_from_web/config.py b/job_compassplus/mysite_compassplus_com/get_profile_info_from_web/config.py new file mode 100644 index 000000000..0031cc312 --- /dev/null +++ b/job_compassplus/mysite_compassplus_com/get_profile_info_from_web/config.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from pathlib import Path + + +DIR = Path(__file__).resolve().parent + +DB_DIR_NAME: Path = DIR / "database" +DB_DIR_NAME.mkdir(parents=True, exist_ok=True) + +DB_FILE_NAME: Path = DB_DIR_NAME / "db.sqlite" + +DIR_DB_BACKUP: Path = DIR / "database-backup" +DIR_DB_BACKUP.mkdir(parents=True, exist_ok=True) + +MAX_LAST_CHECK_DATE_DAYS: int = 30 diff --git a/job_compassplus/mysite_compassplus_com/get_profile_info_from_web/db.py b/job_compassplus/mysite_compassplus_com/get_profile_info_from_web/db.py new file mode 100644 index 000000000..ca72e6789 --- /dev/null +++ b/job_compassplus/mysite_compassplus_com/get_profile_info_from_web/db.py @@ -0,0 +1,190 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import enum +import time +import sys + +from datetime import date +from typing import Type, Iterable, Optional, Any + +# pip install peewee +from peewee import ( + Model, + TextField, + ForeignKeyField, + CharField, + DateField, + BlobField, + BooleanField, +) +from playhouse.shortcuts import model_to_dict +from playhouse.sqliteq import SqliteQueueDatabase + +from config import DIR, DB_FILE_NAME + +sys.path.append(str(DIR.parent.parent.parent)) +from shorten import shorten + + +class NotDefinedParameterException(ValueError): + def __init__(self, parameter_name: str) -> None: + self.parameter_name = parameter_name + text = f'Parameter "{self.parameter_name}" must be defined!' + + super().__init__(text) + + +# This working with multithreading +# SOURCE: http://docs.peewee-orm.com/en/latest/peewee/playhouse.html#sqliteq +db = SqliteQueueDatabase( + DB_FILE_NAME, + pragmas={ + "foreign_keys": 1, + "journal_mode": "wal", # WAL-mode + "cache_size": -1024 * 64, # 64MB page-cache + }, + use_gevent=False, # Use the standard library "threading" module. + autostart=True, + queue_max_size=64, # Max. # of pending writes that can accumulate. + results_timeout=5.0, # Max. time to wait for query to be executed. +) + + +class BaseModel(Model): + class Meta: + database = db + + @classmethod + def get_inherited_models(cls) -> list[Type["BaseModel"]]: + return sorted(cls.__subclasses__(), key=lambda x: x.__name__) + + @classmethod + def print_count_of_tables(cls) -> None: + items = [] + for sub_cls in cls.get_inherited_models(): + name = sub_cls.__name__ + count = sub_cls.select().count() + items.append(f"{name}: {count}") + + print(", ".join(items)) + + @classmethod + def count(cls, filters: Iterable = None) -> int: + query = cls.select() + if filters: + query = query.filter(*filters) + return query.count() + + def to_dict(self) -> dict[str, Any]: + return model_to_dict(self, recurse=False) + + def __str__(self) -> str: + fields = [] + for k, field in self._meta.fields.items(): + v = getattr(self, k) + + if isinstance(field, (TextField, CharField)): + if isinstance(v, enum.Enum): + v = v.value + + if v: + v = repr(shorten(v, length=30)) + + if isinstance(field, BlobField) and v is not None: + v = f"<{len(v)} bytes>" + + elif isinstance(field, ForeignKeyField): + k = f"{k}_id" + if v: + v = v.id + + fields.append(f"{k}={v}") + + return self.__class__.__name__ + "(" + ", ".join(fields) + ")" + + +class Person(BaseModel): + name = TextField() + position = TextField() + department = TextField() + img = BlobField(default=True) + location = TextField() + birthday = TextField() + create_date = DateField(default=date.today) + last_check_date = DateField(default=date.today) + is_active = BooleanField(default=None, null=True) + prev_name = TextField(default=None, null=True) + + class Meta: + indexes = ( + # Уникальный индекс по name и дате создания + (("name", "create_date"), True), + ) + + @classmethod + def get_last_by_name(cls, name: str) -> Optional["Person"]: + items: list[Person] = cls.get_all(name) + return items[0] if items else None + + @classmethod + def get_all(cls, name: str) -> list["Person"]: + prev_names: set[str] = set() + + persons: set[Person] = set() + for p in cls.select().where(cls.name == name): + persons.add(p) + + if p.prev_name: + prev_names.add(p.prev_name) + + for prev_name in prev_names: + if prev_name in persons: + continue + + for p in Person.get_all(prev_name): + persons.add(p) + + return sorted(persons, key=lambda p: p.id, reverse=True) + + @classmethod + def get_all_name(cls) -> list[str]: + return [p.name for p in cls.select(cls.name).distinct()] + + +db.connect() +db.create_tables(BaseModel.get_inherited_models()) + + +# Задержка в 50мс, чтобы дать время на запуск SqliteQueueDatabase и создание таблиц +# Т.к. в SqliteQueueDatabase запросы на чтение выполняются сразу, а на запись попадают в очередь +time.sleep(0.050) + + +if __name__ == "__main__": + BaseModel.print_count_of_tables() + print() + + names: list[str] = Person.get_all_name() + persons: list[Person] = [ + Person.get_last_by_name(name) + for name in names + ] + print(f"Total: {len(persons)}") + print(f"Active: {len([p for p in persons if p.is_active])}") + print(f"Non active: {len([p for p in persons if not p.is_active])}") + + # Поиск людей с одинаковой картинкой в профиле + # from collections import defaultdict + # img_by_persons: defaultdict[bytes, set[str]] = defaultdict(set) + # + # for p in Person: + # img_by_persons[p.img].add(p.name) + # + # for img, persons in sorted(img_by_persons.items(), key=lambda x: len(x[1]), reverse=True): + # if len(persons) > 1: + # print(len(persons), persons) + # diff --git a/job_compassplus/mysite_compassplus_com/get_profile_info_from_web/db_backup.py b/job_compassplus/mysite_compassplus_com/get_profile_info_from_web/db_backup.py new file mode 100644 index 000000000..3b465039a --- /dev/null +++ b/job_compassplus/mysite_compassplus_com/get_profile_info_from_web/db_backup.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import sqlite3 +import traceback +import time +import zipfile + +from datetime import date +from pathlib import Path + +from config import DIR_DB_BACKUP +from db import db + + +# SOURCE: https://github.com/gil9red/SimplePyScripts/blob/b144a9c8fa6e737ef177fe1f7ae07d61794fc037/sqlite3__examples/backup__examples/common.py#L15 +def create_zip_for_file( + file_name_zip: str | Path, + file_name: Path, + delete_file_name: bool = True, +): + with zipfile.ZipFile( + file_name_zip, mode="w", compression=zipfile.ZIP_DEFLATED + ) as f: + f.write(file_name, arcname=file_name.name) + + if delete_file_name: + file_name.unlink() + + +# SOURCE: https://github.com/gil9red/SimplePyScripts/blob/bfac99ef66038562f16c94341535e0ce02f1dec2/sqlite3__examples/backup__examples/backup_via_api.py#L17 +def backup( + connect: sqlite3.Connection, + file_name: Path, + use_zip: bool = True, + delete_file_name_after_zip: bool = True, +) -> Path: + dst = sqlite3.connect(file_name) + connect.backup(dst) + dst.close() + + if use_zip: + file_name_zip = Path(f"{file_name}.zip") + create_zip_for_file( + file_name_zip, file_name, delete_file_name=delete_file_name_after_zip + ) + return file_name_zip + + return file_name + + +def do_backup_db() -> None: + prefix: str = "[do_backup_db]" + + print(f"{prefix} Start") + + while True: + print(f"{prefix} Check") + try: + file_name_backup = backup( + connect=db.connection(), + file_name=DIR_DB_BACKUP / f"{date.today().isoformat()}.sqlite", + ) + print(f"{prefix} Backup saved to {file_name_backup}") + + except Exception: + # Выводим ошибку в консоль + tb = traceback.format_exc() + print(f"{prefix} Error:\n{tb}") + + finally: + time.sleep(30 * 24 * 60 * 60) # Раз в 30 дней + + +if __name__ == "__main__": + do_backup_db() diff --git a/job_compassplus/mysite_compassplus_com/get_profile_info_from_web/db_updater.py b/job_compassplus/mysite_compassplus_com/get_profile_info_from_web/db_updater.py new file mode 100644 index 000000000..3fc3275cf --- /dev/null +++ b/job_compassplus/mysite_compassplus_com/get_profile_info_from_web/db_updater.py @@ -0,0 +1,141 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import sys +import time +import traceback +from datetime import date, timedelta + +import db + +from config import DIR, MAX_LAST_CHECK_DATE_DAYS + +sys.path.append(str(DIR.parent)) +from get_person_info import Person, get_person_info, get_jira_user_active + + +def create_person_from_info(info: Person) -> db.Person: + return db.Person.create( + name=info.name, + position=info.position, + department=info.department, + img=info.download_img(), + location=info.location, + birthday=info.birthday, + is_active=info.is_active, + ) + + +def is_person_eq_info(person: db.Person, info: Person) -> bool: + if person.img != info.download_img(): + return False + + if person.department != info.department: + return False + + if person.position != info.position: + return False + + if person.location != info.location: + return False + + if person.birthday != info.birthday: + return False + + if person.is_active != info.is_active: + return False + + return True + + +def is_need_to_check(person: db.Person, d: date) -> bool: + return d > person.last_check_date + timedelta(days=MAX_LAST_CHECK_DATE_DAYS) + + +def add_or_get_db(name: str, forced: bool = False) -> db.Person | None: + person = db.Person.get_last_by_name(name) + + # Если нет, то создать + if not person: + info = get_person_info(name) + if not info: + return + + # Пусть первые пользователи всегда будут активными + info.is_active = True + + return create_person_from_info(info) + + # Проверить дату проверку + # Если с даты последней проверки прошло больше MAX_LAST_CHECK_DATE_DAYS дней, то + # нужно проверить изменения полей + today = date.today() + if is_need_to_check(person, d=today) or forced: + info = get_person_info(name) + if info: + if is_person_eq_info(person, info): + person.last_check_date = today + person.save() + else: + # Создание новой записи с актуальными полями + person = create_person_from_info(info) + + elif person.is_active: # Если пользователь был в БД и активным, а потом его удалили из mysite + # Создание новой записи + return db.Person.create( + name=person.name, + position=person.position, + department=person.department, + img=person.img, + location=person.location, + birthday=person.birthday, + is_active=False, + ) + + return person + + +def do_update_db(forced: bool = False) -> None: + prefix: str = "[do_update_db]" + + print(f"{prefix} Start") + + while True: + print(f"{prefix} Check all") + try: + # Запрос для получения ников + names: list[str] = db.Person.get_all_name() + for i, name in enumerate(names, 1): + print(f"{prefix} Check {name} ({i}/{len(names)})") + + person: db.Person | None = None + try: + person = add_or_get_db(name, forced=forced) + except Exception as e: + print(f"{prefix} Error: {e}") + finally: + # Оптимизация, чтобы не делать лишней задержку, если не было запроса по сети в add_or_get_db + # (это косвенно можно понять по last_check_date) + if not forced and person and not is_need_to_check(person, d=date.today()): + continue + + time.sleep(10) + + except Exception: + # Выводим ошибку в консоль + tb = traceback.format_exc() + print(f"{prefix} Error:\n{tb}") + + finally: + time.sleep(24 * 60 * 60) # Раз в сутки + + +if __name__ == "__main__": + user_name = "akrylov" + print(get_person_info(user_name)) + print(add_or_get_db(user_name, forced=True)) + + # do_update_db(forced=True) diff --git a/job_compassplus/mysite_compassplus_com/get_profile_info_from_web/for_https/.gitignore b/job_compassplus/mysite_compassplus_com/get_profile_info_from_web/for_https/.gitignore new file mode 100644 index 000000000..41bca1bd2 --- /dev/null +++ b/job_compassplus/mysite_compassplus_com/get_profile_info_from_web/for_https/.gitignore @@ -0,0 +1,3 @@ +cert.pem +key.pem +keyStore.p12 \ No newline at end of file diff --git a/job_compassplus/mysite_compassplus_com/get_profile_info_from_web/for_https/create_p12_from_self_signed_cert.bat b/job_compassplus/mysite_compassplus_com/get_profile_info_from_web/for_https/create_p12_from_self_signed_cert.bat new file mode 100644 index 000000000..a540f6036 --- /dev/null +++ b/job_compassplus/mysite_compassplus_com/get_profile_info_from_web/for_https/create_p12_from_self_signed_cert.bat @@ -0,0 +1 @@ +openssl pkcs12 -export -out keyStore.p12 -inkey key.pem -in cert.pem \ No newline at end of file diff --git a/job_compassplus/mysite_compassplus_com/get_profile_info_from_web/for_https/create_self_signed_cert.bat b/job_compassplus/mysite_compassplus_com/get_profile_info_from_web/for_https/create_self_signed_cert.bat new file mode 100644 index 000000000..4bbe769e3 --- /dev/null +++ b/job_compassplus/mysite_compassplus_com/get_profile_info_from_web/for_https/create_self_signed_cert.bat @@ -0,0 +1 @@ +openssl req -x509 -newkey rsa:4096 -nodes -out cert.pem -keyout key.pem -days 1095 -config openssl.cnf \ No newline at end of file diff --git a/job_compassplus/mysite_compassplus_com/get_profile_info_from_web/for_https/openssl.cnf b/job_compassplus/mysite_compassplus_com/get_profile_info_from_web/for_https/openssl.cnf new file mode 100644 index 000000000..4d23e835a --- /dev/null +++ b/job_compassplus/mysite_compassplus_com/get_profile_info_from_web/for_https/openssl.cnf @@ -0,0 +1,72 @@ +# +# OpenSSL configuration file. +# + +# Establish working directory. + +dir = . + +[ ca ] +default_ca = CA_default + +[ CA_default ] +serial = $dir/serial +database = $dir/certindex.txt +new_certs_dir = $dir/certs +certificate = $dir/cacert.pem +private_key = $dir/private/cakey.pem +default_days = 365 +default_md = md5 +preserve = no +email_in_dn = no +nameopt = default_ca +certopt = default_ca +policy = policy_match + +[ policy_match ] +countryName = match +stateOrProvinceName = match +organizationName = match +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +[ req ] +default_bits = 2048 # Size of keys +default_keyfile = key.pem # name of generated keys +default_md = md5 # message digest algorithm +string_mask = nombstr # permitted characters +distinguished_name = req_distinguished_name +req_extensions = v3_req + +[ req_distinguished_name ] +# Variable name Prompt string +#------------------------- ---------------------------------- +0.organizationName = Organization Name (company) +organizationalUnitName = Organizational Unit Name (department, division) +emailAddress = Email Address +emailAddress_max = 40 +localityName = Locality Name (city, district) +stateOrProvinceName = State or Province Name (full name) +countryName = Country Name (2 letter code) +countryName_min = 2 +countryName_max = 2 +commonName = Common Name (hostname, IP, or your name) +commonName_max = 64 + +# Default values for the above, for consistency and less typing. +# Variable name Value +#------------------------ ------------------------------ +0.organizationName_default = My Company +localityName_default = My Town +stateOrProvinceName_default = State or Providence +countryName_default = US + +[ v3_ca ] +basicConstraints = CA:TRUE +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid:always,issuer:always + +[ v3_req ] +basicConstraints = CA:FALSE +subjectKeyIdentifier = hash diff --git a/job_compassplus/mysite_compassplus_com/get_profile_info_from_web/main.py b/job_compassplus/mysite_compassplus_com/get_profile_info_from_web/main.py new file mode 100644 index 000000000..b213e8a46 --- /dev/null +++ b/job_compassplus/mysite_compassplus_com/get_profile_info_from_web/main.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import base64 +import os +from datetime import date, datetime + +# pip install flask==2.3.3 +from flask import Flask, Response, abort, jsonify +from flask.json.provider import DefaultJSONProvider + +# pip install flask-cors==4.0.0 +from flask_cors import CORS + +from requests.exceptions import RequestException + +import db +from db_updater import add_or_get_db + + +config = { + # "DEBUG": True, # some Flask specific configs + "CACHE_TYPE": "SimpleCache", # Flask-Caching related configs + "CACHE_DEFAULT_TIMEOUT": 300, +} + + +class UpdatedJSONProvider(DefaultJSONProvider): + sort_keys = False + + def default(self, o): + if isinstance(o, (date, datetime)): + return o.isoformat() + return super().default(o) + + +app = Flask(__name__) + +# Tell Flask to use the above defined config +app.config.from_mapping(config) + +app.json = UpdatedJSONProvider(app) + +CORS(app) + + +@app.errorhandler(RequestException) +def handle_requests_error(e: RequestException) -> Response: + return Response( + response=str(e), + status=e.response.status_code if e.response else 404, + ) + + +@app.route("/api/get_all_person_info/") +def api_get_all_person_info(username: str) -> tuple[Response, int]: + has_person: bool = db.Person.get_last_by_name(username) is not None + + # Проверка наличия и попытка добавить в базу для первого раза + person: db.Person = add_or_get_db(username) + if not person: + abort(404) + + img_by_idx: dict[str, int] = dict() + items: list[dict] = [] + + for i, person in enumerate(db.Person.get_all(username)): + data = person.to_dict() + + # NOTE: Картинки возвращаются в одном запросе + data_base64 = base64.b64encode(person.img).decode("utf-8") + img_base64 = f"data:image/jpg;base64,{data_base64}" + + # Оптимизация, чтобы не возвращать одинаковые картинки + if img_base64 not in img_by_idx: + img_by_idx[img_base64] = i + else: + img_base64 = f"={img_by_idx[img_base64]}" # Символа "=" нет в base64 + + data["img"] = img_base64 + + items.append(data) + + return jsonify(items), 200 if has_person else 201 + + +if __name__ == "__main__": + from threading import Thread + from db_backup import do_backup_db + from db_updater import do_update_db + + Thread(target=do_backup_db, daemon=True).start() + Thread(target=do_update_db, daemon=True).start() + + app.run( + host="0.0.0.0", + port=int(os.environ.get("FLASK_PORT", 50000)), + # TODO: for https + # ssl_context=("for_https/cert.pem", "for_https/key.pem"), + ) diff --git a/job_compassplus/mysite_compassplus_com/get_profile_info_from_web/migrations/001.py b/job_compassplus/mysite_compassplus_com/get_profile_info_from_web/migrations/001.py new file mode 100644 index 000000000..c428fa821 --- /dev/null +++ b/job_compassplus/mysite_compassplus_com/get_profile_info_from_web/migrations/001.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +# SOURCE: http://docs.peewee-orm.com/en/latest/peewee/playhouse.html#schema-migrations + + +from playhouse.migrate import SqliteDatabase, SqliteMigrator, migrate +from db import TextField, DB_FILE_NAME, Person + + +db = SqliteDatabase(DB_FILE_NAME) +migrator = SqliteMigrator(db) + + +with db.atomic(): + migrate( + migrator.add_column( + Person._meta.table_name, + "prev_name", + TextField(default=None, null=True), + ), + ) diff --git a/job_compassplus/outlook_jira_issues_autoread.py b/job_compassplus/outlook_jira_issues_autoread.py new file mode 100644 index 000000000..eeeecee44 --- /dev/null +++ b/job_compassplus/outlook_jira_issues_autoread.py @@ -0,0 +1,247 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import argparse +import email +import imaplib +import re +import time + +from dataclasses import dataclass +from datetime import datetime, timedelta +from email.header import decode_header +from email.utils import parseaddr +from typing import Any + +from root_config import JIRA_HOST +from root_common import session + + +PATTERN_JIRA: re.Pattern = re.compile(r"\((\w+-\d+)\)") + + +@dataclass +class IssueInfo: + key: str + is_done: bool + updated: datetime | None + resolution_date: datetime | None + + +def get_issue_info(issue_key: str) -> IssueInfo: + def _get_datetime(value: str) -> datetime | None: + if value: + return datetime.fromisoformat(value).replace(tzinfo=None) + + url_issue: str = f"{JIRA_HOST}/rest/api/latest/issue/{issue_key}" + + rs = session.get(url_issue) + rs.raise_for_status() + + rs_fields: dict[str, Any] = rs.json()["fields"] + + return IssueInfo( + key=issue_key, + is_done=rs_fields["status"]["statusCategory"]["key"] == "done", + updated=_get_datetime(rs_fields.get("updated")), + resolution_date=_get_datetime(rs_fields.get("resolutiondate")), + ) + + +def get_args(): + parser = argparse.ArgumentParser( + description="Скрипт для автоматического прочтения неактуальных писем из Jira в Outlook." + ) + + # Обязательные параметры + parser.add_argument( + "-u", + "--user", + required=True, + help="Логин (email) от почты", + ) + parser.add_argument( + "-p", + "--password", + required=True, + help="Пароль от почты (или пароль приложения)", + ) + parser.add_argument( + "-f", + "--folder", + default="INBOX", + help="Папка для поиска (например, INBOX или 'Jira/Issues', по умолчанию: %(default)s))", + ) + + # Опциональные параметры + parser.add_argument( + "--server", + default="mail.compassplus.com", + help="Адрес IMAP сервера (по умолчанию: %(default)s)", + ) + + parser.add_argument( + "--limit", + type=int, + default=None, + help="Ограничение количества обрабатываемых писем (для тестирования, например 100)", + ) + + # Флаг прочтения (action='store_true' делает его булевым: если указан - True, если нет - False) + parser.add_argument( + "--mark", + action="store_true", + help="Если указан, письма при совпадении условий будут отмечены как прочитанные на сервере", + ) + + return parser.parse_args() + + +def process( + user: str, + password: str, + server: str, + folder: str, + limit: int | None = None, + mark_as_read: bool = False, +) -> None: + print(f"Подключение к {server} для пользователя {user}...") + + mail = imaplib.IMAP4_SSL(server) + mail.login(user, password) + + mail.select(f'"{folder}"') + + # Письма от старых к новым + status, response = mail.search(None, "(UNSEEN)") + + issue_by_info: dict[str, Any] = dict() + + msg_ids = response[0].split() + if limit: + print(f"Лимит обработки: {limit} писем") + msg_ids = msg_ids[:limit] + + for num in msg_ids: + # BODY.PEEK[] - читаем, не меняя статус на "Прочитано" + status, data = mail.fetch(num, "(BODY.PEEK[HEADER])") + raw_email: bytes = data[0][1] + msg = email.message_from_bytes(raw_email) + + # Декодируем заголовок + decoded_parts = decode_header(msg["Subject"]) + + subject = "" + for content, encoding in decoded_parts: + if isinstance(content, bytes): + # Используем кодировку из письма, если её нет — пробуем utf-8 + enc = encoding if encoding else "utf-8" + try: + subject += content.decode(enc) + except (UnicodeDecodeError, LookupError): + # Если всё равно ошибка, декодируем с заменой битых символов + subject += content.decode("utf-8", errors="replace") + else: + # Если это уже строка (например, ASCII) + subject += content + + raw_from: str = msg.get("From") + name, email_address = parseaddr(raw_from) + + raw_date: str = msg.get("Date") + date_tuple = email.utils.parsedate_tz(raw_date) + local_date: datetime | None = None + if date_tuple: + local_date = datetime.fromtimestamp( + email.utils.mktime_tz(date_tuple) + ).replace(tzinfo=None) + else: + print(f"[#] Неправильная дата письма: {raw_date!r}") + + read_it: bool = False + + # NOTE: Можно оптимизировать и не ходить в API Jira, если письмо старше такой-то даты + # Но хочется посмотреть информацию по задачам из писем + m: re.Match | None = PATTERN_JIRA.search(subject) + if m: + issue_key: str = m.group(1) + print( + "[+]", + num, + f"{local_date.strftime('%d.%m.%Y %H:%M:%S') if local_date else None}", + repr(subject), + repr(name), + email_address, + issue_key, + ) + + issue: IssueInfo | None = issue_by_info.get(issue_key) + if not issue: + try: + issue = get_issue_info(issue_key) + issue_by_info[issue_key] = issue + except Exception as e: + print(f"[#] Не удалось получить информацию по задаче {issue_key!r}: {e}") + + time.sleep(1) + + print(f" Информация по задаче: {issue if issue else '<неизвестно>'}") + + if issue: + # Определяем запас времени (например, 10 минут) + buffer = timedelta(minutes=10) + + # Если письма приходили до даты решения задачи, то уже не актуальные + if ( + issue.is_done + and issue.resolution_date + and local_date + # Если дата решения задачи больше даты отправки письма + and (issue.resolution_date - buffer) > local_date + ): + print(" Письмо приходило раньше решения задачи") + read_it = True + + else: + print( + "[?]", + num, + f"{local_date.strftime('%d.%m.%Y %H:%M:%S') if local_date else None}", + repr(subject), + repr(name), + email_address, + ) + + # Если прошло больше 1 месяца + if local_date and (datetime.now() - timedelta(weeks=4)) > local_date: + print(" Прошло больше 1 месяца") + read_it = True + + if read_it: + status_msg = ( + "[ПРОЧИТАНО]" + if mark_as_read + else "[DRY-RUN: БЫЛО БЫ ПРОЧИТАНО С ФЛАГОМ --mark]" + ) + print(f" {status_msg}") + + if mark_as_read: + mail.store(num, "+FLAGS", r"\Seen") + + mail.logout() + + +if __name__ == "__main__": + args = get_args() + + process( + user=args.user, + password=args.password, + server=args.server, + folder=args.folder, + limit=args.limit, + mark_as_read=args.mark, + ) diff --git a/parse__radix__CacheContent_log/CacheContent_log.txt b/job_compassplus/parse__radix__CacheContent_log/CacheContent_log.txt similarity index 100% rename from parse__radix__CacheContent_log/CacheContent_log.txt rename to job_compassplus/parse__radix__CacheContent_log/CacheContent_log.txt diff --git a/parse__radix__CacheContent_log/main.py b/job_compassplus/parse__radix__CacheContent_log/main.py similarity index 64% rename from parse__radix__CacheContent_log/main.py rename to job_compassplus/parse__radix__CacheContent_log/main.py index 7dd1a4543..d70a7bed9 100644 --- a/parse__radix__CacheContent_log/main.py +++ b/job_compassplus/parse__radix__CacheContent_log/main.py @@ -2,18 +2,16 @@ # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -import typing +import re -def get_cache_object_info(file_name) -> typing.Dict[str, typing.Dict[str, int]]: +def get_cache_object_info(file_name) -> dict[str, dict[str, int]]: # new_object: Tran[180409000010273756] # existing_object: Hold[13708] - - import re - pattern_object = re.compile('^(\w+_object): (.+)\[.+$') + pattern_object = re.compile(r"^(\w+_object): (.+)\[.+$") object_by_number = dict() @@ -34,10 +32,14 @@ def get_cache_object_info(file_name) -> typing.Dict[str, typing.Dict[str, int]]: return object_by_number -def get_cache_existing_object_list(file_name, top_values=None) -> typing.List[typing.Tuple[str, int]]: +def get_cache_existing_object_list( + file_name, top_values=None +) -> list[tuple[str, int]]: object_by_number = get_cache_object_info(file_name) - existing_object_list = sorted(object_by_number['existing_object'].items(), key=lambda x: x[1], reverse=True) + existing_object_list = sorted( + object_by_number["existing_object"].items(), key=lambda x: x[1], reverse=True + ) if top_values: existing_object_list = existing_object_list[:top_values] @@ -45,13 +47,12 @@ def get_cache_existing_object_list(file_name, top_values=None) -> typing.List[ty return rows -if __name__ == '__main__': - file_name = 'CacheContent_log.txt' +if __name__ == "__main__": + file_name = "CacheContent_log.txt" print(file_name) rows = get_cache_existing_object_list(file_name, top_values=5) # pip install tabulate from tabulate import tabulate - print(tabulate(rows, headers=('NAME', 'NUMBER'), tablefmt="grid")) - print() + print(tabulate(rows, headers=("NAME", "NUMBER"), tablefmt="grid")) diff --git a/job_compassplus/parse_jira_Assigned_Open_Issues_per_Project/common.py b/job_compassplus/parse_jira_Assigned_Open_Issues_per_Project/common.py index 29ee1a04a..a653ed575 100644 --- a/job_compassplus/parse_jira_Assigned_Open_Issues_per_Project/common.py +++ b/job_compassplus/parse_jira_Assigned_Open_Issues_per_Project/common.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import logging @@ -9,22 +9,23 @@ from logging.handlers import RotatingFileHandler from pathlib import Path -from typing import Dict DIR = Path(__file__).resolve().parent ROOT_DIR = DIR.parent -# For import ascii_table__simple_pretty__ljust.py -sys.path.append(str(ROOT_DIR.parent)) -from ascii_table__simple_pretty__ljust import pretty_table +# pip install tabulate +from tabulate import tabulate -def get_table(assigned_open_issues_per_project: Dict[str, int]) -> str: - data = [("PROJECT", 'Issues')] + list(assigned_open_issues_per_project.items()) - return pretty_table(data) +def get_table(assigned_open_issues_per_project: dict[str, int]) -> str: + return tabulate( + list(assigned_open_issues_per_project.items()), + headers=("PROJECT", "Issues"), + tablefmt="grid", + ) -def print_table(assigned_open_issues_per_project: Dict[str, int]): +def print_table(assigned_open_issues_per_project: dict[str, int]) -> None: print(get_table(assigned_open_issues_per_project)) # PROJECT | Issues # --------+------- @@ -33,14 +34,20 @@ def print_table(assigned_open_issues_per_project: Dict[str, int]): # zzz | 3 -def get_logger(name, file='log.txt', encoding='utf-8', log_stdout=True, log_file=True) -> logging.Logger: +def get_logger( + name, file="log.txt", encoding="utf-8", log_stdout=True, log_file=True +) -> logging.Logger: log = logging.getLogger(name) log.setLevel(logging.DEBUG) - formatter = logging.Formatter('[%(asctime)s] %(filename)s:%(lineno)d %(levelname)-8s %(message)s') + formatter = logging.Formatter( + "[%(asctime)s] %(filename)s:%(lineno)d %(levelname)-8s %(message)s" + ) if log_file: - fh = RotatingFileHandler(file, maxBytes=10000000, backupCount=5, encoding=encoding) + fh = RotatingFileHandler( + file, maxBytes=10_000_000, backupCount=5, encoding=encoding + ) fh.setFormatter(formatter) log.addHandler(fh) @@ -52,4 +59,4 @@ def get_logger(name, file='log.txt', encoding='utf-8', log_stdout=True, log_file return log -logger = get_logger('parse_jira_Assigned_Open_Issues_per_Project') +logger = get_logger("parse_jira_Assigned_Open_Issues_per_Project") diff --git a/job_compassplus/parse_jira_Assigned_Open_Issues_per_Project/db.py b/job_compassplus/parse_jira_Assigned_Open_Issues_per_Project/db.py index ebaead338..5b6f7fe69 100644 --- a/job_compassplus/parse_jira_Assigned_Open_Issues_per_Project/db.py +++ b/job_compassplus/parse_jira_Assigned_Open_Issues_per_Project/db.py @@ -1,17 +1,23 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -import datetime as DT +import datetime as dt import os import shutil import sys -from typing import Dict, Optional - -from peewee import SqliteDatabase, Model, TextField, CharField, ForeignKeyField, DateField, IntegerField +from peewee import ( + SqliteDatabase, + Model, + TextField, + CharField, + ForeignKeyField, + DateField, + IntegerField, +) from common import ROOT_DIR, DIR, print_table @@ -19,28 +25,29 @@ sys.path.append(str(ROOT_DIR.parent)) from shorten import shorten + # Absolute file name -DB_FILE_NAME = str(DIR / 'database.sqlite') +DB_FILE_NAME = str(DIR / "database.sqlite") -def db_create_backup(backup_dir=DIR / 'backup'): +def db_create_backup(backup_dir=DIR / "backup") -> None: os.makedirs(backup_dir, exist_ok=True) - file_name = str(DT.datetime.today().date()) + '.sqlite' + file_name = str(dt.datetime.today().date()) + ".sqlite" file_name = os.path.join(backup_dir, file_name) shutil.copy(DB_FILE_NAME, file_name) # Ensure foreign-key constraints are enforced. -db = SqliteDatabase(DB_FILE_NAME, pragmas={'foreign_keys': 1}) +db = SqliteDatabase(DB_FILE_NAME, pragmas={"foreign_keys": 1}) class BaseModel(Model): class Meta: database = db - def __str__(self): + def __str__(self) -> str: fields = [] for k, field in self._meta.fields.items(): v = getattr(self, k) @@ -50,26 +57,26 @@ def __str__(self): v = repr(shorten(v)) elif isinstance(field, ForeignKeyField): - k = f'{k}_id' + k = f"{k}_id" if v: v = v.id - fields.append(f'{k}={v}') + fields.append(f"{k}={v}") - return self.__class__.__name__ + '(' + ', '.join(fields) + ')' + return self.__class__.__name__ + "(" + ", ".join(fields) + ")" class Run(BaseModel): - date = DateField(default=DT.date.today) + date = DateField(default=dt.date.today) def get_total_issues(self) -> int: return sum(x.value for x in self.issue_numbers) - def get_project_by_issue_numbers(self) -> Dict[str, int]: + def get_project_by_issue_numbers(self) -> dict[str, int]: return {issue.project.name: issue.value for issue in self.issue_numbers} - def __str__(self): - return f'{self.__class__.__name__}(id={self.id}, date={self.date}, total_issues={self.get_total_issues()})' + def __str__(self) -> str: + return f"{self.__class__.__name__}(id={self.id}, date={self.date}, total_issues={self.get_total_issues()})" class Project(BaseModel): @@ -78,27 +85,19 @@ class Project(BaseModel): class IssueNumber(BaseModel): value = IntegerField() - run = ForeignKeyField(Run, backref='issue_numbers') - project = ForeignKeyField(Project, backref='issue_numbers') - + run = ForeignKeyField(Run, backref="issue_numbers") + project = ForeignKeyField(Project, backref="issue_numbers") -def add(assigned_open_issues_per_project: Dict[str, int]) -> Optional[bool]: - last_run = Run.select().order_by(Run.id.desc()).get() - if assigned_open_issues_per_project == last_run.get_project_by_issue_numbers(): - return - run, created = Run.get_or_create(date=DT.date.today()) +def add(assigned_open_issues_per_project: dict[str, int]) -> bool: + run, created = Run.get_or_create(date=dt.date.today()) if not created: return False for project_name, issue_numbers in assigned_open_issues_per_project.items(): project, _ = Project.get_or_create(name=project_name) - IssueNumber.create( - value=issue_numbers, - run=run, - project=project - ) + IssueNumber.create(value=issue_numbers, run=run, project=project) db_create_backup() @@ -109,13 +108,13 @@ def add(assigned_open_issues_per_project: Dict[str, int]) -> Optional[bool]: db.create_tables([Run, Project, IssueNumber]) -if __name__ == '__main__': +if __name__ == "__main__": projects = [p.name for p in Project.select()] print(f"Projects ({len(projects)}): {projects}\n") # Print last rows for run in Run.select().order_by(Run.id.desc()).limit(5): - print(run, '\n') + print(run) print_table(run.get_project_by_issue_numbers()) - print('\n' + '-' * 100 + '\n') + print("\n" + "-" * 100 + "\n") diff --git a/job_compassplus/parse_jira_Assigned_Open_Issues_per_Project/get_assigned_open_issues_per_project.py b/job_compassplus/parse_jira_Assigned_Open_Issues_per_Project/get_assigned_open_issues_per_project.py index ee93df1a2..88dcfc1cd 100644 --- a/job_compassplus/parse_jira_Assigned_Open_Issues_per_Project/get_assigned_open_issues_per_project.py +++ b/job_compassplus/parse_jira_Assigned_Open_Issues_per_Project/get_assigned_open_issues_per_project.py @@ -1,42 +1,44 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import sys -from typing import Dict from bs4 import BeautifulSoup from common import ROOT_DIR, print_table sys.path.append(str(ROOT_DIR)) +from root_config import JIRA_HOST from root_common import session -URL = 'https://helpdesk.compassluxe.com/secure/ViewProfile.jspa?name=ipetrash' +URL = f"{JIRA_HOST}/secure/ViewProfile.jspa?name=ipetrash" -def get_assigned_open_issues_per_project() -> Dict[str, int]: +def get_assigned_open_issues_per_project() -> dict[str, int]: rs = session.get(URL) - root = BeautifulSoup(rs.content, 'html.parser') + rs.raise_for_status() + + root = BeautifulSoup(rs.content, "html.parser") data = dict() - for item in root.select('#assigned-and-open > .mod-content > .stat-list > li'): - name = item.select_one('a[title]').get_text(strip=True) - value = int(item.select_one('.stat').get_text(strip=True)) + for item in root.select("#assigned-and-open > .mod-content > .stat-list > li"): + name = item.select_one("a[title]").get_text(strip=True) + value = int(item.select_one(".stat").get_text(strip=True)) data[name] = value return data -def get_and_prints() -> Dict[str, int]: +def get_and_prints() -> dict[str, int]: assigned_open_issues_per_project = get_assigned_open_issues_per_project() # {'xxx': 1, 'yyy': 2, 'zzz': 3} - print('Total issues:', sum(assigned_open_issues_per_project.values())) + print("Total issues:", sum(assigned_open_issues_per_project.values())) print() print_table(assigned_open_issues_per_project) @@ -49,5 +51,5 @@ def get_and_prints() -> Dict[str, int]: return assigned_open_issues_per_project -if __name__ == '__main__': +if __name__ == "__main__": get_and_prints() diff --git a/job_compassplus/parse_jira_Assigned_Open_Issues_per_Project/gui.py b/job_compassplus/parse_jira_Assigned_Open_Issues_per_Project/gui.py index 9be348f48..11dc03b4d 100644 --- a/job_compassplus/parse_jira_Assigned_Open_Issues_per_Project/gui.py +++ b/job_compassplus/parse_jira_Assigned_Open_Issues_per_Project/gui.py @@ -1,22 +1,35 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import calendar -import datetime as DT import math import sys import time import traceback -from typing import List, Dict +from datetime import datetime, date, timedelta from PyQt5.QtWidgets import ( - QApplication, QMessageBox, QMainWindow, QSystemTrayIcon, QTabWidget, QTableWidget, - QTableWidgetItem, QVBoxLayout, QWidget, QPushButton, QSplitter, QLabel, QGridLayout, - QHeaderView, QProgressBar, QMenu + QApplication, + QMessageBox, + QMainWindow, + QSystemTrayIcon, + QTabWidget, + QTableWidget, + QTableWidgetItem, + QVBoxLayout, + QWidget, + QPushButton, + QSplitter, + QLabel, + QGridLayout, + QHeaderView, + QProgressBar, + QMenu, + QComboBox, ) from PyQt5.QtGui import QIcon, QPainter, QCloseEvent from PyQt5.QtCore import QEvent, QTimer, Qt, QThread, pyqtSignal @@ -26,23 +39,44 @@ from get_assigned_open_issues_per_project import get_assigned_open_issues_per_project from db import Run -sys.path.append(str(ROOT_DIR.parent / 'qt__pyqt__pyside__pyqode')) +sys.path.append(str(ROOT_DIR.parent / "qt__pyqt__pyside__pyqode")) from chart_line__show_tooltip_on_series__QtChart import ChartViewToolTips -def log_uncaught_exceptions(ex_cls, ex, tb): - text = f'{ex_cls.__name__}: {ex}:\n' - text += ''.join(traceback.format_tb(tb)) +def log_uncaught_exceptions(ex_cls, ex, tb) -> None: + text = f"{ex_cls.__name__}: {ex}:\n" + text += "".join(traceback.format_tb(tb)) print(text) - QMessageBox.critical(None, 'Error', text) + QMessageBox.critical(None, "Error", text) sys.exit(1) sys.excepthook = log_uncaught_exceptions -WINDOW_TITLE = DIR.name +WINDOW_TITLE: str = DIR.name + +DATE_FORMAT: str = "%d.%m.%Y" +TIME_FORMAT: str = "%H:%M:%S" + + +def get_human_datetime(dt: datetime | None = None) -> str: + if not dt: + dt = datetime.now() + return dt.strftime(f"{DATE_FORMAT} {TIME_FORMAT}") + + +def get_human_date(d: datetime | date | None = None) -> str: + if not d: + d = date.today() + return d.strftime(DATE_FORMAT) + + +def get_human_time(dt: datetime | None = None) -> str: + if not dt: + dt = datetime.now() + return dt.strftime(TIME_FORMAT) def get_table_widget(header_labels: list) -> QTableWidget: @@ -61,9 +95,9 @@ class GetAssignedOpenIssuesPerProjectThread(QThread): about_items = pyqtSignal(dict) about_error = pyqtSignal(Exception) - def run(self): + def run(self) -> None: try: - items = get_assigned_open_issues_per_project() + items: dict[str, int] = get_assigned_open_issues_per_project() self.about_items.emit(items) # Даем время на отображение и анимацию прогресс-бара @@ -74,13 +108,15 @@ def run(self): class TableWidgetRun(QWidget): - def __init__(self): + def __init__(self) -> None: super().__init__() - self.table_run = get_table_widget(['DATE', 'TOTAL ISSUES']) - self.table_run.selectionModel().selectionChanged.connect(self._on_table_run_item_clicked) + self.table_run = get_table_widget(["DATE", "TOTAL ISSUES"]) + self.table_run.selectionModel().selectionChanged.connect( + self._on_table_run_item_clicked + ) - self.table_issues = get_table_widget(['PROJECT', 'NUMBER']) + self.table_issues = get_table_widget(["PROJECT", "NUMBER"]) self.table_run.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) self.table_issues.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) @@ -93,7 +129,7 @@ def __init__(self): self.setLayout(main_layout) - def refresh(self, items: List[Run]): + def refresh(self, items: list[Run]) -> None: # Удаление строк таблицы while self.table_run.rowCount(): self.table_run.removeRow(0) @@ -101,7 +137,7 @@ def refresh(self, items: List[Run]): for i, run in enumerate(items): self.table_run.setRowCount(self.table_run.rowCount() + 1) - item = QTableWidgetItem(run.date.strftime('%d/%m/%Y')) + item = QTableWidgetItem(get_human_date(run.date)) item.setData(Qt.UserRole, run.get_project_by_issue_numbers()) self.table_run.setItem(i, 0, item) @@ -112,7 +148,7 @@ def refresh(self, items: List[Run]): self.table_run.setFocus() self._on_table_run_item_clicked() - def _on_table_run_item_clicked(self): + def _on_table_run_item_clicked(self) -> None: # Удаление строк таблицы while self.table_issues.rowCount(): self.table_issues.removeRow(0) @@ -129,10 +165,10 @@ def _on_table_run_item_clicked(self): class CurrentAssignedOpenIssues(QWidget): - def __init__(self): + def __init__(self) -> None: super().__init__() - self.table = get_table_widget(['PROJECT', 'NUMBER']) + self.table = get_table_widget(["PROJECT", "NUMBER"]) self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) self.progress_bar = QProgressBar() @@ -156,7 +192,9 @@ def __init__(self): main_layout = QGridLayout() main_layout.addWidget(self.label_total, 0, 0, Qt.AlignLeft | Qt.AlignCenter) main_layout.addWidget(self.progress_bar, 0, 1) - main_layout.addWidget(self.label_last_refresh_date, 0, 2, Qt.AlignRight | Qt.AlignCenter) + main_layout.addWidget( + self.label_last_refresh_date, 0, 2, Qt.AlignRight | Qt.AlignCenter + ) main_layout.addWidget(self.table, 1, 0, 2, 0) self.setLayout(main_layout) @@ -167,97 +205,106 @@ def __init__(self): self.thread.about_items.connect(self._on_set_items) self.thread.about_error.connect(self._on_error) - self._update_total_issues('-') + self.current_items: dict[str, int] | None = None + self._update_total_issues("-") + + def _update_total_issues(self, value) -> None: + self.label_total.setText(f"Total issues: {value}") - def _update_total_issues(self, value): - self.label_total.setText(f'Total issues: {value}') + def _on_set_items(self, items: dict[str, int]) -> None: + self.current_items = items - def _on_set_items(self, items: Dict[str, int]): - self._update_total_issues(sum(items.values())) + self._update_total_issues(sum(self.current_items.values())) # Удаление строк таблицы while self.table.rowCount(): self.table.removeRow(0) - for i, (project_name, number) in enumerate(items.items()): + for i, (project_name, number) in enumerate(self.current_items.items()): self.table.setRowCount(self.table.rowCount() + 1) self.table.setItem(i, 0, QTableWidgetItem(project_name)) self.table.setItem(i, 1, QTableWidgetItem(str(number))) self.label_last_refresh_date.setText( - "Last refresh date: " + DT.datetime.now().strftime('%d/%m/%Y %H:%M:%S') + f"Last refresh date: {get_human_datetime()}" ) - def _on_error(self, e: Exception): + def _on_error(self, e: Exception) -> None: tb_str = "".join(traceback.format_tb(e.__traceback__)) print(tb_str) - QMessageBox.warning(self, 'ERROR', str(e)) + QMessageBox.warning(self, "ERROR", str(e)) - def refresh(self): - if self.thread.isRunning(): - return + def refresh(self) -> None: + self.current_items = None self.thread.start() class MyChartViewToolTips(ChartViewToolTips): - def __init__(self, timestamp_by_run: dict): + def __init__(self, timestamp_by_info: dict[int, dict[str, int]]) -> None: super().__init__() - self._callout_font_family = 'Courier' - self.timestamp_by_run = timestamp_by_run + self._callout_font_family = "Courier" + self.timestamp_by_info: dict[int, dict[str, int]] = timestamp_by_info - def show_series_tooltip(self, point, state: bool): + def show_series_tooltip(self, point, state: bool) -> None: # value -> pos point = self.chart().mapToPosition(point) if not self._tooltip: self._tooltip = self._add_Callout() - if state: - distance = 25 - - for series in self.chart().series(): - for p_value in series.pointsVector(): - p = self.chart().mapToPosition(p_value) + if not state: + self._tooltip.hide() + return - current_distance = math.sqrt( - (p.x() - point.x()) * (p.x() - point.x()) - + (p.y() - point.y()) * (p.y() - point.y()) + distance = 25 + + for series in self.chart().series(): + for p_value in series.pointsVector(): + p = self.chart().mapToPosition(p_value) + + current_distance = math.sqrt( + (p.x() - point.x()) * (p.x() - point.x()) + + (p.y() - point.y()) * (p.y() - point.y()) + ) + + if current_distance < distance: + time_ms = int(p_value.x()) + info: dict[str, int] = self.timestamp_by_info[time_ms] + table = get_table(info) + text = ( + f"{get_human_date(date.fromtimestamp(time_ms / 1000))}" + "\n\n" + f"Total issues: {sum(info.values())}" + "\n" + f"{table}" ) - if current_distance < distance: - date_value = int(p_value.x()) - run = self.timestamp_by_run[date_value] - table = get_table(run.get_project_by_issue_numbers()) - text = f"Total issues: {run.get_total_issues()}\n\n{table}" - - self._tooltip.setText(text) - self._tooltip.setAnchor(p_value) - self._tooltip.setZValue(11) - self._tooltip.updateGeometry() - self._tooltip.show() - else: - self._tooltip.hide() + self._tooltip.setText(text) + self._tooltip.setAnchor(p_value) + self._tooltip.setZValue(11) + self._tooltip.updateGeometry() + self._tooltip.show() class MainWindow(QMainWindow): - def __init__(self): + def __init__(self) -> None: super().__init__() self.setWindowTitle(WINDOW_TITLE) - file_name = str(DIR / 'favicon.ico') + file_name = str(DIR / "favicon.ico") icon = QIcon(file_name) self.setWindowIcon(icon) - self.timestamp_by_run = dict() + self.timestamp_by_info: dict[int, dict[str, int]] = dict() menu = QMenu() - menu.addAction('Show / hide', (lambda: self.setVisible(not self.isVisible()))) + menu.addAction("Show / hide", (lambda: self.setVisible(not self.isVisible()))) menu.addSeparator() - menu.addAction('Quit', QApplication.instance().quit) + menu.addAction("Quit", QApplication.instance().quit) self.tray = QSystemTrayIcon(icon) self.tray.setContextMenu(menu) @@ -265,20 +312,31 @@ def __init__(self): self.tray.activated.connect(self._on_tray_activated) self.tray.show() - self.chart_view = MyChartViewToolTips(self.timestamp_by_run) + self.chart_view = MyChartViewToolTips(self.timestamp_by_info) self.chart_view.setRenderHint(QPainter.Antialiasing) + self.cb_chart_filter = QComboBox() + self.cb_chart_filter.addItem("", userData=0) + self.cb_chart_filter.activated.connect(self.refresh) + layout_chart_view = QVBoxLayout(self.chart_view) + layout_chart_view.addWidget(self.cb_chart_filter) + layout_chart_view.setAlignment( + self.cb_chart_filter, Qt.AlignTop | Qt.AlignRight + ) + self.table_run = TableWidgetRun() self.table_run.layout().setContentsMargins(0, 0, 0, 0) self.current_assigned_open_issues = CurrentAssignedOpenIssues() self.tab_widget = QTabWidget() - self.tab_widget.addTab(self.chart_view, 'CHART') - self.tab_widget.addTab(self.table_run, 'TABLE RUN') - self.tab_widget.addTab(self.current_assigned_open_issues, 'Current Assigned Open Issues') + self.tab_widget.addTab(self.chart_view, "CHART") + self.tab_widget.addTab(self.table_run, "TABLE RUN") + self.tab_widget.addTab( + self.current_assigned_open_issues, "Current Assigned Open Issues" + ) - self.pb_refresh = QPushButton('REFRESH') + self.pb_refresh = QPushButton("REFRESH") self.pb_refresh.clicked.connect(self.refresh) main_layout = QVBoxLayout() @@ -291,30 +349,57 @@ def __init__(self): self.setCentralWidget(central_widget) @staticmethod - def _get_timegm(date: DT.date) -> int: + def _get_timegm(date: date) -> int: return calendar.timegm(date.timetuple()) * 1000 - def _get_datetime(self, date: DT.date, delta: DT.timedelta = None) -> DT.datetime: - dt = DT.datetime.combine(date, DT.datetime.min.time()) + def _get_datetime(self, date: date, delta: timedelta = None) -> datetime: + dt = datetime.combine(date, datetime.min.time()) if delta: dt += delta return dt - def _fill_chart(self, items: List[Run]): + def _fill_chart_filter(self, items: list[Run]) -> None: + years: list[int] = sorted({run.date.year for run in items}) + filter_years: list[int] = [ + self.cb_chart_filter.itemData(i) + for i in range(self.cb_chart_filter.count()) + ] + for year in years: + if year not in filter_years: + self.cb_chart_filter.addItem(f"{year}", userData=year) + + def _fill_chart(self, items: list[Run]) -> None: + # Фильтрация данных из графика + year: int = self.cb_chart_filter.currentData() + if year: + items = [run for run in items if run.date.year == year] + series = QLineSeries() series.setPointsVisible(True) series.setPointLabelsVisible(True) series.setPointLabelsFormat("@yPoint") series.hovered.connect(self.chart_view.show_series_tooltip) - self.timestamp_by_run.clear() + self.timestamp_by_info.clear() + issues_number = [] for run in items: date_value = self._get_timegm(run.date) total_issues = run.get_total_issues() series.append(date_value, total_issues) + issues_number.append(total_issues) - self.timestamp_by_run[date_value] = run + self.timestamp_by_info[date_value] = run.get_project_by_issue_numbers() + + now_date_timestamp = self._get_timegm(date.today()) + if now_date_timestamp not in self.timestamp_by_info: + # Использование данных из соседнего виджета + self.current_assigned_open_issues.refresh() + while not self.current_assigned_open_issues.current_items: + QApplication.instance().processEvents() + + self.timestamp_by_info[now_date_timestamp] = self.current_assigned_open_issues.current_items + series.append(now_date_timestamp, sum(self.current_assigned_open_issues.current_items.values())) chart = QChart() chart.setTheme(QChart.ChartThemeDark) @@ -327,36 +412,44 @@ def _fill_chart(self, items: List[Run]): chart.setBackgroundRoundness(0) axisX = QDateTimeAxis() - axisX.setRange( - self._get_datetime(items[0].date, DT.timedelta(days=-30)), - self._get_datetime(items[-1].date, DT.timedelta(days=30)) - ) + if items: + axisX.setRange( + self._get_datetime(items[0].date, timedelta(days=-30)), + self._get_datetime(items[-1].date, timedelta(days=30)), + ) axisX.setFormat("dd/MM/yyyy") - axisX.setTitleText('Date') + axisX.setTitleText("Date") chart.addAxis(axisX, Qt.AlignBottom) series.attachAxis(axisX) axisY = QValueAxis() - axisY.setLabelFormat('%d') - axisY.setTitleText('Total issues') + if issues_number: + axisY.setRange(min(issues_number) * 0.8, max(issues_number) * 1.2) + axisY.setLabelFormat("%d") + axisY.setTitleText("Total issues") chart.addAxis(axisY, Qt.AlignLeft) series.attachAxis(axisY) self.chart_view.clear_all_tooltips() self.chart_view.setChart(chart) - def refresh(self): + def refresh(self) -> None: self.current_assigned_open_issues.refresh() items = list(Run.select()) + + self._fill_chart_filter(items) + self._fill_chart(items) items.reverse() self.table_run.refresh(items) - self.setWindowTitle(f"{WINDOW_TITLE}. Last refresh date: {DT.datetime.now():%d/%m/%Y %H:%M:%S}") + self.setWindowTitle( + f"{WINDOW_TITLE}. Last refresh date: {get_human_datetime()}" + ) - def _on_tray_activated(self, reason: QSystemTrayIcon.ActivationReason): + def _on_tray_activated(self, reason: QSystemTrayIcon.ActivationReason) -> None: # Если запрошено меню if reason == QSystemTrayIcon.Context: return @@ -367,19 +460,19 @@ def _on_tray_activated(self, reason: QSystemTrayIcon.ActivationReason): self.showNormal() self.activateWindow() - def changeEvent(self, event: QEvent): + def changeEvent(self, event: QEvent) -> None: if event.type() == QEvent.WindowStateChange: # Если окно свернули if self.isMinimized(): # Прячем окно с панели задач QTimer.singleShot(0, self.hide) - def closeEvent(self, event: QCloseEvent): + def closeEvent(self, event: QCloseEvent) -> None: self.hide() event.ignore() -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) app.setQuitOnLastWindowClosed(False) diff --git a/job_compassplus/parse_jira_Assigned_Open_Issues_per_Project/main.py b/job_compassplus/parse_jira_Assigned_Open_Issues_per_Project/main.py index f56199ca5..79bb07dcd 100644 --- a/job_compassplus/parse_jira_Assigned_Open_Issues_per_Project/main.py +++ b/job_compassplus/parse_jira_Assigned_Open_Issues_per_Project/main.py @@ -1,59 +1,68 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -import sys import time +import sys # pip install schedule import schedule +# pip install simple-wait +from simple_wait import wait + import db -from common import ROOT_DIR, get_table, logger +from common import get_table, logger from get_assigned_open_issues_per_project import get_assigned_open_issues_per_project -sys.path.append(str(ROOT_DIR.parent / 'wait')) -from wait import wait + +IS_SINGLE: bool = "--single" in sys.argv def run(): + attempts_for_single: int = 5 + while True: try: - logger.info(f'Начало') + logger.info(f"Начало") assigned_open_issues_per_project = get_assigned_open_issues_per_project() logger.info( - 'Всего задач: %s\n\n%s\n', + "Всего задач: %s\n\n%s\n", sum(assigned_open_issues_per_project.values()), - get_table(assigned_open_issues_per_project) + get_table(assigned_open_issues_per_project), ) ok = db.add(assigned_open_issues_per_project) - if ok is None: - logger.info("Количество открытых задач в проектах не поменялось. Пропускаю...") - elif ok: + if ok: logger.info("Добавляю запись") else: logger.info("Сегодня запись уже была добавлена. Пропускаю...") - logger.info('\n' + '-' * 100 + '\n') + logger.info("\n" + "-" * 100 + "\n") break - except Exception: - logger.exception('Ошибка:') + except Exception as e: + if IS_SINGLE: + attempts_for_single -= 1 + if attempts_for_single <= 0: + raise e + + logger.exception("Ошибка:") - logger.info('Через 15 минут попробую снова...') + logger.info("Через 15 минут попробую снова...") wait(minutes=15) -if __name__ == '__main__': +if __name__ == "__main__": + if IS_SINGLE: + run() + sys.exit() + # Каждую неделю, в субботу, в 12:00 - schedule\ - .every().week\ - .saturday.at("12:00")\ - .do(run) + schedule.every().week.saturday.at("12:00").do(run) while True: schedule.run_pending() diff --git a/job_compassplus/parse_total_efforts.py b/job_compassplus/parse_total_efforts.py new file mode 100644 index 000000000..cf660badc --- /dev/null +++ b/job_compassplus/parse_total_efforts.py @@ -0,0 +1,175 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import re + + +PATTERN_ARG: re.Pattern = re.compile(r"<(\w+)>") +PATTERN_RESULT: re.Pattern = re.compile(r"<=([\w +]+)>") + +DEFAULT_ARG_VALUE: str = "NaN" + +SAMPLE_TEMPLATE = """ +*Итоговая трудоемкость в человеко-днях:* <=a+b+c+d+e> ч/д, в том числе: +|Аналитика (проектирование/техническая спецификация)| ч/д| +|Разработка (программирование)| ч/д| +|Рецензирование решения и кода| ч/д| +|Тестирование (только силами разработчика)| ч/д| +|Резерв на непредвиденные работы и риски| ч/д| + +*Total efforts in man-days:* <= a+b+c+d+e> m/d, including: +|Analysis (design and tech specs)| m/d| +|Implementation (including coding)| m/d| +|Code and solution review| m/d| +|Testing (only by the developer)| m/d| +|Reserve for potential gaps and risks| m/d| +""".strip() + + +def get_args(template: str) -> list[str]: + args: list[str] = [] + for m in PATTERN_ARG.finditer(template): + value: str = m.group(1) + if value not in args: + args.append(value) + + return args + + +def process(template: str, arg_by_value: dict[str, str]) -> str: + def _get_norm_str_float(value: str) -> str: + return DEFAULT_ARG_VALUE if value == "nan" else value.removesuffix(".0") + + def _get_value_from_arg( + arg: str, + default_value: str = DEFAULT_ARG_VALUE, + get_float: bool = True, + ) -> str: + value = arg_by_value.get(arg) + if value: + value = value.strip() + value = _get_norm_str_float(value) + + # Проверка валидности значения + try: + float(value) + except Exception: + value = None + + if not value: + value = default_value + + if get_float: + value = f"float({value!r})" + + return value + + text: str = PATTERN_ARG.sub( + lambda m: _get_value_from_arg( + arg=m.group(1), + get_float=False, + ), + template, + ) + + def _process_result(m: re.Match) -> str: + template_expr: str = m.group(1) + + expr: str = re.sub( + r"\w+", + lambda m: _get_value_from_arg(arg=m.group()), + template_expr, + ) + + try: + value = eval(expr) + except SyntaxError: + value = float(DEFAULT_ARG_VALUE) + + result: str = f"{value:.1f}" + return _get_norm_str_float(result) + + return PATTERN_RESULT.sub(_process_result, text) + + +if __name__ == "__main__": + args: list[str] = get_args(SAMPLE_TEMPLATE) + print(args) + assert args == ["a", "b", "c", "d", "e"] + + print() + + arg_by_value: dict[str, str] = dict( + a="1.1", + b="0.1", + c="0.5", + d="12", + e="1.0", + ) + text = process(SAMPLE_TEMPLATE, arg_by_value) + print(text) + assert text == """ +*Итоговая трудоемкость в человеко-днях:* 14.7 ч/д, в том числе: +|Аналитика (проектирование/техническая спецификация)|1.1 ч/д| +|Разработка (программирование)|0.1 ч/д| +|Рецензирование решения и кода|0.5 ч/д| +|Тестирование (только силами разработчика)|12 ч/д| +|Резерв на непредвиденные работы и риски|1 ч/д| + +*Total efforts in man-days:* 14.7 m/d, including: +|Analysis (design and tech specs)|1.1 m/d| +|Implementation (including coding)|0.1 m/d| +|Code and solution review|0.5 m/d| +|Testing (only by the developer)|12 m/d| +|Reserve for potential gaps and risks|1 m/d| + """.strip() + + print("\n" + "-" * 10 + "\n") + + arg_by_value["e"] = "1.3" + text = process(SAMPLE_TEMPLATE, arg_by_value) + print(text) + assert text == """ +*Итоговая трудоемкость в человеко-днях:* 15 ч/д, в том числе: +|Аналитика (проектирование/техническая спецификация)|1.1 ч/д| +|Разработка (программирование)|0.1 ч/д| +|Рецензирование решения и кода|0.5 ч/д| +|Тестирование (только силами разработчика)|12 ч/д| +|Резерв на непредвиденные работы и риски|1.3 ч/д| + +*Total efforts in man-days:* 15 m/d, including: +|Analysis (design and tech specs)|1.1 m/d| +|Implementation (including coding)|0.1 m/d| +|Code and solution review|0.5 m/d| +|Testing (only by the developer)|12 m/d| +|Reserve for potential gaps and risks|1.3 m/d| + """.strip() + + print("\n" + "-" * 10 + "\n") + + arg_by_value: dict[str, str] = dict( + a="", + b="b", + c=" 3 ", + d="0", + ) + text = process(SAMPLE_TEMPLATE, arg_by_value) + print(text) + assert text == """ +*Итоговая трудоемкость в человеко-днях:* NaN ч/д, в том числе: +|Аналитика (проектирование/техническая спецификация)|NaN ч/д| +|Разработка (программирование)|NaN ч/д| +|Рецензирование решения и кода|3 ч/д| +|Тестирование (только силами разработчика)|0 ч/д| +|Резерв на непредвиденные работы и риски|NaN ч/д| + +*Total efforts in man-days:* NaN m/d, including: +|Analysis (design and tech specs)|NaN m/d| +|Implementation (including coding)|NaN m/d| +|Code and solution review|3 m/d| +|Testing (only by the developer)|0 m/d| +|Reserve for potential gaps and risks|NaN m/d| + """.strip() diff --git a/job_compassplus/compassplus_employees/config.py b/job_compassplus/portal_compassplus_com/employees_gui/config.py similarity index 87% rename from job_compassplus/compassplus_employees/config.py rename to job_compassplus/portal_compassplus_com/employees_gui/config.py index 088bed65b..f6e4fb044 100644 --- a/job_compassplus/compassplus_employees/config.py +++ b/job_compassplus/portal_compassplus_com/employees_gui/config.py @@ -1,16 +1,20 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -URL = 'https://portal.compassplus.com/Employees/Pages/OfficeReferenceBook.aspx' -URL_GET_EMPLOYEES_LIST = 'https://portal.compassplus.com/_layouts/15/tbi/employees.ashx?' \ - 'p=50&c=1&s={}&fl=MPhotoUrl;NameLink;JobTitle;Department;WorkPhone' +URL = "https://portal.compassplus.com/Employees/Pages/OfficeReferenceBook.aspx" +URL_GET_EMPLOYEES_LIST = ( + "https://portal.compassplus.com/_layouts/15/tbi/employees.ashx?" + "p=50&c=1&s={}&fl=MPhotoUrl;NameLink;JobTitle;Department;WorkPhone" +) -URL_GET_EMPLOYEE_INFO = 'https://portal.compassplus.com/_layouts/15/tbi/ui.ashx?u={}' \ - '&ctrl=TBI.SharePoint.Employees.WebParts/EmployeeFlyout' +URL_GET_EMPLOYEE_INFO = ( + "https://portal.compassplus.com/_layouts/15/tbi/ui.ashx?u={}" + "&ctrl=TBI.SharePoint.Employees.WebParts/EmployeeFlyout" +) -SETTINGS_FILE_NAME = 'settings' +SETTINGS_FILE_NAME = "settings" PERSON_PLACEHOLDER_PHOTO = "iVBORw0KGgoAAAANSUhEUgAAAMgAAACWCAYAAACb3McZAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAkbSURBVHhe7Z0LUxNJFIX3//8bRPDBW1FUFA0g+AAEBN+KBQHxgeOe1LBbu7nTmSQzSd/Od6q+Klezydx0n8x09+3bf42NjWUAYINBAAJgEIAAGAQgAAYBCIBBAAJgEIAAGAQgAAYBCIBBAAJgEIAAGAQgAAYBCIBBAAJgEIAAGAQgAAYBCIBBAAJgEIAAGAQgAAYBCIBBAAJgEIAAGAQgAAYBCIBBBsjU1FS2srKSbW9vZ+/fv8+Ojo5afP/+/Z8/v3v3rvXvep1eb70PDA4MUjPXr1/PXrx4kZ2enma9SP/fy5cvs9nZWfP9oV4wSE3Mz89n+/v72cXFRd7V+9eXL1+ypaUl8/OgHjBIxczNzWUfP37Mu3Q9klFkQOvzoVowSEWMj49nz58/r/SOEdLv37+zra2t7OrVq+b1QDVgkAq4ceNG61d9GNLnapxjXRf0Dwbpk4WFhez8/DzvrsPR2dlZ69HOuj7oDwzSB7dv385+/vyZd9PhStfBAL56MEiP3Lp1K/v161fePeOQxiWPHj0yrxd6A4P0wPT0dGtxL0ZpkuDu3bvmdUP3YJAumZiYyE5OTvLuGKd0Z2NhsRowSJfs7u7m3TBuHR8fMwVcARikC5aXl/Pu50N7e3tmHFAeDFISPVp9+/Yt73o+pEE7K+79gUFKooRDj/r8+bMZD5QDg5Tg2rVr0ax39KIHDx6YcUFnMEgJvN49LtVsNlu5YlZsEAaDdEBjD893j0utrq6a8UEYDNKBx48f513MtzTta8UHYTBIB4aVpVuHFhcXzRihGAwS4ObNm3nXSkPa4WjFCcVgkACNRiPvWmlI+WNXrlwxYwUbDBLg7du3eddKR0rRt2IFGwwSINaM3X707NkzM1awwSAFpDb+uJQKSljxgg0GKeD+/ft5l0pLP378MOMFGwxSwPr6et6l0pOKTFgxQzsYpACV/0xVd+7cMWOGdjBIAYeHh3l3Sk+q+2vFDO1gkAJURDpVra2tmTFDOxikAO2jSFWbm5tmzNAOBikgZYMofd+KGdrBIAXo/I5UpeMUrJihHQxSQMqDdBXZtmKGdjBIAcp8TVUbGxtmzNAOBilAv7Kp6smTJ2bM0A4GKUA1blMVpUnLg0EKUHHqVDUzM2PGDO1gkAJUtlOF11KT6vayaao8GCSAjmVOTdpjb8UKNhgkQIoJi4rJihVsMEiAe/fu5d0qHSkmK1awwSABNA5Jadutxh+Tk5NmrGCDQTqQ0oKhilBYMUIxGKQDKU33UsS6ezBICVKYzaImVm9gkBLol9e7KPfTGxikJJ73h6g6vc44seKCMBikJDrKzOvKOuntvYNBuuD169d5l/MjnauoM06seKAzGKQLVE/K22E6ykq2YoFyYJAuUckcLzo4ODBjgPJgkB7w8Kh1dnbGwLwCMEgPKAUl5pOnlFKiBU7r2qE7MEiP6Nf55OQk75LxSDNtKrxtXTN0DwbpAw3aYzKJzEFZ0WrBIH2iO0kMqSgXFxfZ8vKyeY3QOxikAjQmGebA/fz8nKPVagKDVIjK6eiAmkFKJ0Zx3kd9YJCK0SPXq1evak9LkRGpb1U/GKQmZmdna6nvq9QRnX7FzsDBgEFqRusRe3t7faeofP36tZU2wp6OwYJBCtBzvY4q02OMsmHVOZXRa722DBrILy0ttSqra5ExZBg9nuk1qkCiz52enjbfs1sWFhZa8ezs7GS7u7utuDQtrLud9XrAIP9BFT+0b1sr0UXSukdVCYAyoTrt/7Fe2w8aF3WaZdOOQ+2/13fAXepfRt4gU1NTrQNlNFXajT59+tQ6S916z5jQ3afZbOZXXU76Lp4+fUou19+MpEH0C6m7gDp5P9JMUsyFEBRjP2Mf3Uk1I6cfEev9R4GRM4g69PHxcd4FqpGe5zXGsD5vGGiGq+qFS71fVWMhT4yMQdS4/d4xQtLYZG5uzvzsQaKJAE0F1yGls+iOMkqPXiNhEM3UDGInoB5JVldXzWuoG22r1Z1sENKAflTOGEnaIBprDKMyombCBvkru7i4mJ2enuafPjg1Gg3zelIiWYOMj49nb968yZty8NKvbFXTwUXIhIO6axRJs13WtaVCkgbRnUO/4jFIC35Vz3RdTk0POjHSkhY1U64Yn6RBtFIcm7RHfGtrq6dndxleq/qqjhhjATsZNdUZruQMok1DsUuPX0pT1+OROr3SP7Sf43IlXf+tv9e/6w6k2aPYpanzFBMokzKInsm7XRFH1SnF4xWSMojm6NFwVffExKBJxiAauNa9SQl1lqabNYNotZFHkjGI9lygOLS2tma2kUeSMIgGh6EUdTRYKe3GaiePJGEQreiiuJRKlZUkDBJzGdBRlR55rbbyhnuDKIMWxadUzkR0bxDtq0ZxKoUC2u4NUuceD9SfNjY2zDbzhGuD6Bbu7cSnUZLqglnt5gnXBtHiIIpXGodY7eYJ1wbRORgobnnP8nVtEG3WQXHr4cOHZtt5wbVBhr2bDnWW9zPaXRuEBcL4pW3PVtt5wbVB2PsRv7SRymo7L7g1iAq1ofil3ZBW+3nBrUFUkRz5kOcTsNwaRJU0kA95TjlxaxBVMEQ+5Pn0XbcG0UE0yIc87zB0a5DDw8P860exy/NaiFuDsAbiR6o2Y7WhB9wahDUQPzo4ODDb0AMuDaKyMpT48aMPHz6Y7egBlwYhzd2Xjo6OzHb0gEuDqGIG8iMVk7Pa0QMuDaLylsiPtOvTakcPuDTI+vp6/tUjL4rpkNNucGmQ7e3t/GtHXuQ1H8ulQVgk9Kf5+XmzLWPHpUFiPGUJhaUTsqy2jB2XBmk2m/nXjrzIa8KiS4NQyd2fdKyc1Zax484gOmYN+dPm5qbZnrHjziAUq/YpnfBrtWfsuDOIjlFG/rS/v2+2Z+y4M8jKykr+lSNP8noCrjuDqGI48idV4bfaM3bcGWRnZyf/ypEnea2P5c4gqtSH/Ekb3Kz2jB13BmGrrU95LSDnziCsovvVxMSE2aYx484grKL7lceMXlcGmZyczL9q5FEzMzNmu8aMK4PoC0Z+5bEEqSuDsBfdtzymvLsyiFKmkV/pTEmrXWPGlUFU4xX5lYptWO0aM64MohqvyK8ajYbZrvEylv0BRxk2BoRZQcsAAAAASUVORK5CYII=" diff --git a/job_compassplus/compassplus_employees/db.py b/job_compassplus/portal_compassplus_com/employees_gui/db.py similarity index 67% rename from job_compassplus/compassplus_employees/db.py rename to job_compassplus/portal_compassplus_com/employees_gui/db.py index 8d63f10ae..7634add2a 100644 --- a/job_compassplus/compassplus_employees/db.py +++ b/job_compassplus/portal_compassplus_com/employees_gui/db.py @@ -1,30 +1,36 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -from sqlalchemy import Column, String -from sqlalchemy.ext.declarative import declarative_base -Base = declarative_base() +import base64 +import os +from urllib.parse import urljoin -import base64 from lxml import etree -from urllib.parse import urljoin + +from sqlalchemy import Column, String, create_engine +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import sessionmaker import config # TODO: добавить модуль logging + +Base = declarative_base() + + class Employee(Base): """ Класс описывает сотрудника. """ - __tablename__ = 'Employees' + __tablename__ = "Employees" id = Column(String, primary_key=True) url = Column(String) @@ -48,30 +54,34 @@ def parse(xml, session): # Если одно из них не будет определено, прекратить парсинг try: - employee.id = root.xpath('//node()[@class="lookup-item"]/@id')[0].replace('user-', '') + employee.id = root.xpath('//node()[@class="lookup-item"]/@id')[0].replace( + "user-", "" + ) employee.short_name = root.xpath('//node()[@class="lookup-item"]/@value')[0] employee.name = root.xpath('//node()[@class="lookup-item"]/@name')[0] employee.url = root.xpath('//node()[@class="employee-name"]//a/@href')[0] except IndexError as e: - print('error', 'employee.id/short_name/name/url', e) + print("error", "employee.id/short_name/name/url", e) raise Exception("Bad parsing!") try: # Загрузка страницы пользователя и вытаскивание дня его рождения rs = session.get(employee.url) user_root = etree.HTML(rs.text) - rs = user_root.xpath('//node()[contains(@id, "Birthday")]' - '/node()[@class="ms-tableCell ms-profile-detailsValue"]/text()') + rs = user_root.xpath( + '//node()[contains(@id, "Birthday")]' + '/node()[@class="ms-tableCell ms-profile-detailsValue"]/text()' + ) employee.birthday = rs[0] except Exception as e: - print('error', 'employee.birthday', e, employee.url) - employee.birthday = '' + print("error", "employee.birthday", e, employee.url) + employee.birthday = "" try: photo_url = root.xpath('//node()[@class="employee-photo"]/img/@src')[0] # Относительный адрес делаем абсолютным - if photo_url.startswith('/'): + if photo_url.startswith("/"): photo_url = urljoin(config.URL, photo_url) rs = session.get(photo_url) @@ -79,19 +89,23 @@ def parse(xml, session): employee.photo = base64.b64encode(rs.content).decode() except Exception as e: - print('warn', 'employee.photo', e) + print("warn", "employee.photo", e) employee.photo = "" try: - employee.job = root.xpath('//node()[@class="employee-jobtitle"]/span[2]/text()')[0].strip() + employee.job = root.xpath( + '//node()[@class="employee-jobtitle"]/span[2]/text()' + )[0].strip() except IndexError as e: - print('warn', 'employee.job', e) + print("warn", "employee.job", e) employee.job = "" try: - employee.department = root.xpath('//node()[@class="employee-department"]/span[2]/text()')[0].strip() + employee.department = root.xpath( + '//node()[@class="employee-department"]/span[2]/text()' + )[0].strip() except IndexError as e: - print('warn', 'employee.department', e) + print("warn", "employee.department", e) employee.department = "" try: @@ -107,42 +121,40 @@ def parse(xml, session): employee.mobile_phone = text except Exception as e: - print('warn', 'employee.work_phone/mobile_phone', e) + print("warn", "employee.work_phone/mobile_phone", e) pass try: - employee.email = root.xpath('//node()[@class="employee-email"]/a/span/text()')[0].strip() + employee.email = root.xpath( + '//node()[@class="employee-email"]/a/span/text()' + )[0].strip() except IndexError as e: - print('warn', 'employee.email', e) + print("warn", "employee.email", e) employee.email = "" return employee - def __str__(self): - return ''.format( - self.name, self.short_name, self.job, self.department) + def __str__(self) -> str: + return f'' - def __repr__(self): + def __repr__(self) -> str: return self.__str__() def get_session(): - import os DIR = os.path.dirname(__file__) - DB_FILE_NAME = 'sqlite:///' + os.path.join(DIR, 'database') + DB_FILE_NAME = "sqlite:///" + os.path.join(DIR, "database") # DB_FILE_NAME = 'sqlite:///:memory:' # Создаем базу, включаем логирование и автообновление подключения каждые 2 часа (7200 секунд) - from sqlalchemy import create_engine engine = create_engine( DB_FILE_NAME, # echo=True, - pool_recycle=7200 + pool_recycle=7200, ) Base.metadata.create_all(engine) - from sqlalchemy.orm import sessionmaker Session = sessionmaker(bind=engine) return Session() @@ -150,5 +162,8 @@ def get_session(): db_session = get_session() -def exists(employee_id): - return db_session.query(Employee).filter(Employee.id == employee_id).scalar() is not None +def exists(employee_id) -> bool: + return ( + db_session.query(Employee).filter(Employee.id == employee_id).scalar() + is not None + ) diff --git a/job_compassplus/compassplus_employees/main.py b/job_compassplus/portal_compassplus_com/employees_gui/main.py similarity index 80% rename from job_compassplus/compassplus_employees/main.py rename to job_compassplus/portal_compassplus_com/employees_gui/main.py index fdb9d5ee6..d77012be3 100644 --- a/job_compassplus/compassplus_employees/main.py +++ b/job_compassplus/portal_compassplus_com/employees_gui/main.py @@ -1,35 +1,42 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -# Для отлова всех исключений, которые в слотах Qt могут "затеряться" и привести к тихому падению -def log_uncaught_exceptions(ex_cls, ex, tb): - import traceback - text = '{}: {}:\n'.format(ex_cls.__name__, ex) - text += ''.join(traceback.format_tb(tb)) +import base64 +import sys +import traceback - print('Error: ', text) - QMessageBox.critical(None, 'Error', text) - sys.exit(1) +import requests +from PyQt5.QtGui import * +from PyQt5.QtWidgets import * +from PyQt5.QtCore import * -import sys -sys.excepthook = log_uncaught_exceptions +from requests_ntlm2 import HttpNtlmAuth +from sqlalchemy import or_ import config +from db import * -def get_url(page): - return config.URL_GET_EMPLOYEES_LIST.format((page - 1) * 50) +# Для отлова всех исключений, которые в слотах Qt могут "затеряться" и привести к тихому падению +def log_uncaught_exceptions(ex_cls, ex, tb) -> None: + text = f"{ex_cls.__name__}: {ex}:\n" + text += "".join(traceback.format_tb(tb)) + print("Error: ", text) + QMessageBox.critical(None, "Error", text) + sys.exit(1) -from db import * -import requests -from requests_ntlm2 import HttpNtlmAuth +sys.excepthook = log_uncaught_exceptions + + +def get_url(page): + return config.URL_GET_EMPLOYEES_LIST.format((page - 1) * 50) # # TODO: показывать короткое имя пользователя: ipetrash, ypaliy и т.п. @@ -37,13 +44,6 @@ def get_url(page): # if __name__ == '__main__': # fill_db() -from PyQt5.QtGui import * -from PyQt5.QtWidgets import * -from PyQt5.QtCore import * - - -import base64 - def pixmap_from_base64(base64_text): pixmap = QPixmap() @@ -54,7 +54,7 @@ def pixmap_from_base64(base64_text): class EmployeeInfo(QWidget): - def __init__(self): + def __init__(self) -> None: super().__init__() self.photo = QLabel() @@ -107,7 +107,7 @@ def __init__(self): for label in self.findChildren(QLabel): label.setTextInteractionFlags(Qt.TextBrowserInteraction) - def set_employee(self, employee): + def set_employee(self, employee) -> None: if not employee: self.photo.setPixmap(pixmap_from_base64(config.PERSON_PLACEHOLDER_PHOTO)) @@ -130,19 +130,19 @@ def set_employee(self, employee): self.job.setText(employee.job) self.department.setText(employee.department) self.birthday.setText(employee.birthday) - self.url.setText('{0}'.format(employee.url)) + self.url.setText(f'{employee.url}') self.work_phone.setText(employee.work_phone) self.mobile_phone.setText(employee.mobile_phone) self.id.setText(employee.id) - self.email.setText('{0}'.format(employee.email)) + self.email.setText(f'{employee.email}') # TODO: ввод с клавы при фокусе на таблицу меняет редактор фильтра class MainWindow(QMainWindow): - def __init__(self): + def __init__(self) -> None: super().__init__() - self.setWindowTitle('Compass Plus Employees') + self.setWindowTitle("Compass Plus Employees") self.setContextMenuPolicy(Qt.NoContextMenu) # TODO: окно с информацией о выделенном сотруднике умеет показывать его переработку/недоработку и прочее @@ -155,11 +155,15 @@ def __init__(self): # Добавление в редактор фильтра кнопки очищения содержимого clear_icon = self.style().standardIcon(QStyle.SP_LineEditClearButton) - clear_action = self.filter_line_edit.addAction(clear_icon, QLineEdit.TrailingPosition) + clear_action = self.filter_line_edit.addAction( + clear_icon, QLineEdit.TrailingPosition + ) clear_action.setVisible(len(self.filter_line_edit.text()) > 0) clear_action.triggered.connect(self.filter_line_edit.clear) - self.filter_line_edit.textChanged.connect(lambda text: clear_action.setVisible(len(text) > 0)) + self.filter_line_edit.textChanged.connect( + lambda text: clear_action.setVisible(len(text) > 0) + ) # Если SP_LineEditClearButton не найден except AttributeError: @@ -170,10 +174,12 @@ def __init__(self): self.employees_table = QTableWidget() self.employees_table.setSelectionBehavior(QTableWidget.SelectRows) self.employees_table.setSelectionMode(QTableWidget.SingleSelection) - self.employees_table.currentItemChanged.connect(lambda item, _: self._item_click(item)) + self.employees_table.currentItemChanged.connect( + lambda item, _: self._item_click(item) + ) layout_filter = QHBoxLayout() - layout_filter.addWidget(QLabel('Filter:')) + layout_filter.addWidget(QLabel("Filter:")) layout_filter.addWidget(self.filter_line_edit) layout = QVBoxLayout() @@ -195,20 +201,24 @@ def __init__(self): tool_bar = self.addToolBar("General") tool_bar.setObjectName("General") action_refill = tool_bar.addAction("Parse and refill") - action_refill.setToolTip("Clear database, parse site with employees, and fill database") + action_refill.setToolTip( + "Clear database, parse site with employees, and fill database" + ) action_refill.setStatusTip(action_refill.toolTip()) action_refill.triggered.connect(self.refill) self.setStatusBar(QStatusBar()) - def refill(self): + def refill(self) -> None: # TODO: можно в отдельный класс вынести dialog = QDialog() - dialog.setWindowTitle('Auth and refill database') + dialog.setWindowTitle("Auth and refill database") info = QLabel() - info.setText("""When you click on OK, you will be cleansing a database of employees, -start parsing for the collection of employees and populate the database.""") + info.setText( + """When you click on OK, you will be cleansing a database of employees, +start parsing for the collection of employees and populate the database.""" + ) login = QLineEdit("CP\\") password = QLineEdit() @@ -242,9 +252,9 @@ def refill(self): rs = session.get(config.URL) if not rs.ok: QMessageBox.information(self, "Info", "Failed to login") - print('Не удалось авторизоваться') - print('rs.status_code = {}'.format(rs.status_code)) - print('rs.headers = {}'.format(rs.headers)) + print("Не удалось авторизоваться") + print(f"rs.status_code = {rs.status_code}") + print(f"rs.headers = {rs.headers}") return # TODO: move to db.py @@ -257,17 +267,17 @@ def refill(self): self.fill_db(session) self.run_filter() - def fill_db(self, session): + def fill_db(self, session) -> None: page = 1 rs = session.get(get_url(page)) data = rs.json() - max_page = data['Pages'] + max_page = data["Pages"] # TODO: наверное тоже нужно в QProgressDialog обернуть, хоть сбор и быстрый employee_list = list() - employee_list += data['Properties'] + employee_list += data["Properties"] while page < max_page: page += 1 @@ -275,10 +285,12 @@ def fill_db(self, session): rs = session.get(get_url(page)) data = rs.json() - employee_list += data['Properties'] + employee_list += data["Properties"] # Для отображения диалога парсинга и заполнения базы - progress = QProgressDialog("Operation in progress...", "Cancel", 0, len(employee_list), self) + progress = QProgressDialog( + "Operation in progress...", "Cancel", 0, len(employee_list), self + ) progress.setWindowTitle("Parsing") progress.setWindowModality(Qt.WindowModal) @@ -288,17 +300,19 @@ def fill_db(self, session): if progress.wasCanceled(): break - employee_id = row['Id'] + employee_id = row["Id"] if exists(employee_id): - print('Employee with id = {} already exist.'.format(employee_id)) + print(f"Employee with id = {employee_id} already exist.") continue rs = session.get(config.URL_GET_EMPLOYEE_INFO.format(employee_id)) if not rs.ok: - print("Request getting employee info (id={}) not ok.".format(employee_id)) - print("rs.status_code = {}".format(rs.status_code)) - print("rs.headers = {}".format(rs.headers)) + print( + f"Request getting employee info (id={employee_id}) not ok." + ) + print(f"rs.status_code = {rs.status_code}") + print(f"rs.headers = {rs.headers}") continue employee = Employee.parse(rs.text, session) @@ -310,7 +324,7 @@ def fill_db(self, session): progress.setValue(len(employee_list)) - def _item_click(self, item): + def _item_click(self, item) -> None: employee = None if item and self.employees_table.rowCount() > 0: @@ -319,7 +333,7 @@ def _item_click(self, item): self.employee_info.set_employee(employee) - def run_filter(self): + def run_filter(self) -> None: # TODO: лучше использовать модель # TODO: лучше использовать стандартный фильтр qt # TODO: поиграться с делегатами для красивого отображения описания + ссылки на гист @@ -328,9 +342,8 @@ def run_filter(self): self._item_click(None) # TODO: db.py - from sqlalchemy import or_ filter_text = self.filter_line_edit.text() - filter_text = "%{}%".format(filter_text) + filter_text = f"%{filter_text}%" sql_filter = or_( Employee.name.like(filter_text), Employee.short_name.like(filter_text), @@ -349,8 +362,17 @@ def run_filter(self): self.employees_table.setRowCount(rows) headers = [ - "Name", "Short Name", "Job", "Department", - "Birthday", "Url", "Work Phone", "Mobile Phone", "Id", "Email", "Photo" + "Name", + "Short Name", + "Job", + "Department", + "Birthday", + "Url", + "Work Phone", + "Mobile Phone", + "Id", + "Email", + "Photo", ] self.employees_table.setColumnCount(len(headers)) self.employees_table.setHorizontalHeaderLabels(headers) @@ -364,7 +386,9 @@ def run_filter(self): self.employees_table.setItem(row, 4, QTableWidgetItem(employee.birthday)) self.employees_table.setItem(row, 5, QTableWidgetItem(employee.url)) self.employees_table.setItem(row, 6, QTableWidgetItem(employee.work_phone)) - self.employees_table.setItem(row, 7, QTableWidgetItem(employee.mobile_phone)) + self.employees_table.setItem( + row, 7, QTableWidgetItem(employee.mobile_phone) + ) self.employees_table.setItem(row, 8, QTableWidgetItem(employee.id)) self.employees_table.setItem(row, 9, QTableWidgetItem(employee.email)) self.employees_table.setItem(row, 10, QTableWidgetItem(employee.photo)) @@ -376,28 +400,30 @@ def run_filter(self): # Запрет редактирования ячеек таблицы for row in range(self.employees_table.rowCount()): for column in range(self.employees_table.columnCount()): - self.employees_table.item(row, column).setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled) + self.employees_table.item(row, column).setFlags( + Qt.ItemIsSelectable | Qt.ItemIsEnabled + ) # Показываем информацию о первом сотруднике if self.employees_table.rowCount() > 0: item = self.employees_table.item(0, 0) self.employees_table.setCurrentItem(item) - def read_settings(self): + def read_settings(self) -> None: ini = QSettings(config.SETTINGS_FILE_NAME, QSettings.IniFormat) - state = ini.value('MainWindow_State') + state = ini.value("MainWindow_State") if state: self.restoreState(state) - geometry = ini.value('MainWindow_Geometry') + geometry = ini.value("MainWindow_Geometry") if geometry: self.restoreGeometry(geometry) - def write_settings(self): + def write_settings(self) -> None: ini = QSettings(config.SETTINGS_FILE_NAME, QSettings.IniFormat) - ini.setValue('MainWindow_State', self.saveState()) - ini.setValue('MainWindow_Geometry', self.saveGeometry()) + ini.setValue("MainWindow_State", self.saveState()) + ini.setValue("MainWindow_Geometry", self.saveGeometry()) def eventFilter(self, object, event): # В окне вводе при клике на стрелку вниз фокус переходит в таблицу @@ -408,13 +434,13 @@ def eventFilter(self, object, event): return super().eventFilter(object, event) - def closeEvent(self, _): + def closeEvent(self, _) -> None: self.write_settings() QApplication.instance().quit() -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) mw = MainWindow() diff --git a/job_compassplus/portal_compassplus_com/get_vacation_forecast.py b/job_compassplus/portal_compassplus_com/get_vacation_forecast.py new file mode 100644 index 000000000..dd2f35a2f --- /dev/null +++ b/job_compassplus/portal_compassplus_com/get_vacation_forecast.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import sys +from pathlib import Path + +DIR = Path(__file__).resolve().parent +ROOT_DIR = DIR.parent + +sys.path.append(str(ROOT_DIR)) +from site_common import do_get, do_post + + +def get_vacation_forecast() -> int: + url = "https://helpdesk.compassluxe.com/pa-reports-new/vacation/VacationForecast" + rs = do_get(url) + data: dict[str, int] = rs.json() + return data["id"] + + +if __name__ == "__main__": + vacation_forecast: int = get_vacation_forecast() + print(f"Vacations:", vacation_forecast) diff --git a/job_compassplus/portal_compassplus_com/get_vacations.py b/job_compassplus/portal_compassplus_com/get_vacations.py new file mode 100644 index 000000000..25ed9e379 --- /dev/null +++ b/job_compassplus/portal_compassplus_com/get_vacations.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import sys + +from dataclasses import dataclass +from datetime import datetime, date, timezone +from pathlib import Path +from typing import Any + +DIR = Path(__file__).resolve().parent +ROOT_DIR = DIR.parent + +sys.path.append(str(ROOT_DIR)) +from site_common import do_get, do_post + + +@dataclass +class Vacation: + id: int + subject: str + subject_email: str + start_date: date + end_date: date + deputy: str + + @classmethod + def parse_from_dict(cls, data: dict[str, Any]) -> "Vacation": + def utc2local(dt_utc: datetime) -> datetime: + epoch = dt_utc.timestamp() + dt_local = datetime.fromtimestamp(epoch).replace(tzinfo=None) + + offset = dt_local - dt_utc.replace(tzinfo=None) + return (dt_utc + offset).replace(tzinfo=None) + + def parse_datetime(date_time_str: str) -> datetime: + # NOTE: Разбор даты из UTC "2024-08-18T19:00:00Z" в локальную дату + return utc2local(datetime.strptime(date_time_str, "%Y-%m-%dT%H:%M:%S%z")) + + return cls( + id=data["Id"], + subject=data["Subject"], + subject_email=data["EmailEmployee"], + start_date=parse_datetime(data["StartDate"]), + end_date=parse_datetime(data["EndDate"]), + deputy=data["Location"], + ) + + +def get_vacations() -> list[Vacation]: + url = "https://portal.compassplus.com/_api/web/lists/GetByTitle('Employee%20Vacations')/items" + rs = do_get( + url, + headers={ + # NOTE: С такими заголовками сервер вернет JSON, а не XML + "Accept": "application/json;odata.metadata=minimal", + "odata-version": "4.0", + }, + params={ + "$select": "Id,EndDate,Subject,Location,StartDate,EmailEmployee", + "$top": "5000", + "$orderby": "Subject asc", + "$filter": f"(EndDate ge '{datetime.now(timezone.utc).date()}T00:00:00')", + }, + ) + return [Vacation.parse_from_dict(value) for value in rs.json()["value"]] + + +if __name__ == "__main__": + vacations = get_vacations() + print(f"Vacations ({len(vacations)}):") + for i, vacation in enumerate(vacations, 1): + print(f" {i}. {vacation}") diff --git a/job_compassplus/portal_compassplus_com/notify_about_new_employees.py b/job_compassplus/portal_compassplus_com/notify_about_new_employees.py new file mode 100644 index 000000000..737926e0e --- /dev/null +++ b/job_compassplus/portal_compassplus_com/notify_about_new_employees.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import os +import traceback +import time + +from urllib.parse import urljoin + +import requests +from playwright.sync_api import sync_playwright, Page + + +URL_PORTAL: str = os.getenv("URL_PORTAL") +URL_NOTIFY: str = os.getenv("URL_NOTIFY") + +if not URL_PORTAL: + raise Exception("URL_PORTAL environment is not set!") + +if not URL_NOTIFY: + raise Exception("URL_NOTIFY environment variable is not set!") + + +def parse(page: Page, url_portal: str = URL_PORTAL, url_notify: str = URL_NOTIFY) -> None: + print(f"Opening page: {url_portal}") + page.goto(url_portal) + + css_selector = "a#NameFieldLink[href]" + + items = page.locator(css_selector).all() + print("Users:", len(items)) + + for user_el in items: + url_mysite: str = user_el.get_attribute("href") + username: str = url_mysite.rsplit("\\")[-1] + + full_name: str = user_el.text_content().strip() + + print(f"Check {full_name!r} ({username})") + + url_check: str = urljoin(url_notify, username) + print(f"Sending a notification: {url_check}") + + rs = requests.get(url_check) + rs.raise_for_status() + + # 201 вернется, если в ходе запроса был добавлен пользователь + print(f"[{'+' if rs.status_code == 201 else '='}] {full_name!r} ({username})") + print() + + +with sync_playwright() as p: + print("Launching a browser") + browser = p.firefox.launch() + + try: + page = browser.new_page() + + max_attempts = 3 + attempt = 0 + while True: + try: + attempt += 1 + parse(page) + break + except Exception as e: + print(f"#{attempt}.\n{traceback.format_exc()}") + + if attempt >= max_attempts: + raise e + + time.sleep(30) + + finally: + browser.close() diff --git a/job_compassplus/print_connections__radixware_explorer.py b/job_compassplus/print_connections__radixware_explorer.py new file mode 100644 index 000000000..d20b36e50 --- /dev/null +++ b/job_compassplus/print_connections__radixware_explorer.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import os + +from bs4 import BeautifulSoup + +# pip install tabulate +from tabulate import tabulate + + +FILE_NAME = r"%APPDATA%\radixware.org\explorer\connections.xml" +ABS_FILE_NAME = os.path.expandvars(FILE_NAME) + + +with open(ABS_FILE_NAME, "rb") as f: + root = BeautifulSoup(f.read(), "html.parser") + + +def get_tag_text(tag): + return "" if tag is None else tag.text + + +headers = [ + "NAME", + "ID", + "COMMENT", + "USERNAME", + "STATIONNAME", + "INITIALADDRESS", + "LANGUAGE", + "COUNTRY", + "EXPLORERROOTID", + "TRACELEVEL", +] +rows = [] + +for connection in root.select("connection"): + rows.append( + [ + connection["name"], + connection["id"], + get_tag_text(connection.comment), + get_tag_text(connection.username), + get_tag_text(connection.stationname), + get_tag_text(connection.initialaddress), + get_tag_text(connection.language), + get_tag_text(connection.country), + get_tag_text(connection.explorerrootid), + get_tag_text(connection.tracelevel), + ] + ) + + +print(tabulate(rows, headers=headers, tablefmt="grid")) diff --git a/job_compassplus/print_statistics_by_img.py b/job_compassplus/print_statistics_by_img.py new file mode 100644 index 000000000..c07c1779b --- /dev/null +++ b/job_compassplus/print_statistics_by_img.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from collections import Counter, defaultdict +from hashlib import sha1 +from pathlib import Path + + +def process(path: Path) -> None: + files: list[Path] = [ + p + for p in path.glob("*/ads/*/img/*") + if p.suffix.lower() in [".svg", ".png", ".img", ".gif", ".jpg"] + ] + + suffix_by_number = Counter(p.suffix.lower() for p in files) + print(f"Suffixes ({len(suffix_by_number)}):") + for suffix, number in suffix_by_number.items(): + print(f" {suffix}: {number}") + + print() + + duplicated: dict[str, list[Path]] = defaultdict(list) + for p in files: + hash_file = sha1(p.read_bytes()).hexdigest() + duplicated[hash_file].append(p) + + duplicated = {k: v for k, v in duplicated.items() if len(v) > 1} + + print(f"Duplicates ({len(duplicated)}):") + for hash_file, items in sorted(duplicated.items(), key=lambda x: len(x[1]), reverse=True): + print(f" Hash {hash_file} ({len(items)}):") + for p in items: + print(f" {p}") + + +if __name__ == "__main__": + import argparse + + parser = argparse.ArgumentParser( + description="Search in locale" + ) + parser.add_argument( + "path_trunk", + type=Path, + metavar="/PATH/TO/TRUNK", + help="Path to TX source tree (the directory containing branch.xml)", + ) + args = parser.parse_args() + + process(args.path_trunk) diff --git a/job_compassplus/root_common.py b/job_compassplus/root_common.py index c251116c6..bad178e83 100644 --- a/job_compassplus/root_common.py +++ b/job_compassplus/root_common.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import ssl @@ -14,21 +14,30 @@ class TLSAdapter(requests.adapters.HTTPAdapter): def init_poolmanager(self, *args, **kwargs): ctx = ssl.create_default_context() - ctx.set_ciphers('DEFAULT@SECLEVEL=1') - kwargs['ssl_context'] = ctx + ctx.set_ciphers("DEFAULT@SECLEVEL=1") + kwargs["ssl_context"] = ctx return super(TLSAdapter, self).init_poolmanager(*args, **kwargs) -USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:94.0) Gecko/20100101 Firefox/94.0' +USER_AGENT = ( + "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:94.0) Gecko/20100101 Firefox/94.0" +) session = requests.session() session.cert = str(PATH_CERT) -session.mount('https://', TLSAdapter()) -session.headers['User-Agent'] = USER_AGENT +session.mount("https://", TLSAdapter()) +session.headers["User-Agent"] = USER_AGENT -if __name__ == '__main__': +if __name__ == "__main__": + from root_config import JIRA_HOST + # Check - rs = session.get('https://helpdesk.compassluxe.com/pa-reports/') + rs = session.get(f"{JIRA_HOST}/pa-reports/") + print(rs) + rs.raise_for_status() + + rs = session.get(f"{JIRA_HOST}/secure/ViewProfile.jspa?name=ipetrash") print(rs) + rs.raise_for_status() diff --git a/job_compassplus/root_config.py b/job_compassplus/root_config.py index d410c0c75..658a260af 100644 --- a/job_compassplus/root_config.py +++ b/job_compassplus/root_config.py @@ -1,34 +1,35 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from pathlib import Path +JIRA_HOST = "https://helpdesk.compassluxe.com" + # NOTE: Get : openssl pkcs12 -nodes -out ipetrash.pem -in ipetrash.p12 -NAME_CERT = 'ipetrash.pem' +NAME_CERT = "ipetrash.pem" ROOT_DIR = Path(__file__).resolve().parent PATH_LOCAL_CERT = ROOT_DIR / NAME_CERT -DIR_CURRENT_KEYS = Path(r'C:\keys\bin\current') +DIR_CURRENT_KEYS = Path(r"C:\keys\bin\current") PATH_COMMON_CERT = DIR_CURRENT_KEYS / NAME_CERT -if not DIR_CURRENT_KEYS.exists(): - print(f"[#] {DIR_CURRENT_KEYS} don't exists!") - if PATH_LOCAL_CERT.exists(): PATH_CERT = PATH_LOCAL_CERT -elif PATH_COMMON_CERT: +elif PATH_COMMON_CERT.exists(): PATH_CERT = PATH_COMMON_CERT else: - raise Exception(f'File {NAME_CERT} not found in: {PATH_LOCAL_CERT}, {PATH_COMMON_CERT}!') + raise Exception( + f"File {NAME_CERT} not found in: {PATH_LOCAL_CERT}, {PATH_COMMON_CERT}!" + ) -if __name__ == '__main__': - print(f'ROOT_DIR: {ROOT_DIR}') - print(f'PATH_LOCAL_CERT: {PATH_LOCAL_CERT} (exists: {PATH_LOCAL_CERT.exists()})') - print(f'PATH_COMMON_CERT: {PATH_COMMON_CERT} (exists: {PATH_COMMON_CERT.exists()})') - print(f'PATH_CERT: {PATH_CERT} (exists: {PATH_CERT.exists()})') +if __name__ == "__main__": + print(f"ROOT_DIR: {ROOT_DIR}") + print(f"PATH_LOCAL_CERT: {PATH_LOCAL_CERT} (exists: {PATH_LOCAL_CERT.exists()})") + print(f"PATH_COMMON_CERT: {PATH_COMMON_CERT} (exists: {PATH_COMMON_CERT.exists()})") + print(f"PATH_CERT: {PATH_CERT} (exists: {PATH_CERT.exists()})") diff --git a/job_compassplus/site_common.py b/job_compassplus/site_common.py new file mode 100644 index 000000000..84c281b99 --- /dev/null +++ b/job_compassplus/site_common.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import time + +# pip install requests_ntlm==1.3.0 +from requests_ntlm import HttpNtlmAuth + +from requests import Response +from requests.exceptions import RequestException + +from root_common import session +from site_config_token import USERNAME, PASSWORD + + +def do_request(method, url: str, *args, **kwargs) -> Response: + attempts = 0 + max_attempts = 5 + + while True: + attempts += 1 + try: + rs = method( + url, + auth=HttpNtlmAuth(USERNAME, PASSWORD), + *args, + **kwargs + ) + + # Через какое-то отваливается доступ, почистим куки и заново авторизуемся + if rs.status_code == 401: + session.cookies.clear() + + rs.raise_for_status() + + return rs + + except RequestException as e: + if attempts >= max_attempts: + raise e + + time.sleep(5) # 5 seconds + + +def do_get(url: str, *args, **kwargs) -> Response: + return do_request(session.get, url, *args, **kwargs) + + +def do_post(url: str, *args, **kwargs) -> Response: + return do_request(session.post, url, *args, **kwargs) + + +if __name__ == "__main__": + rs = do_get("https://mysite.compassplus.com:443/Person.aspx?accountname=CP%5Cipetrash") + print(rs) + + rs = do_get("https://portal.compassplus.com/Pages/default.aspx") + print(rs) diff --git a/job_compassplus/site_config_token.py b/job_compassplus/site_config_token.py new file mode 100644 index 000000000..328724b8b --- /dev/null +++ b/job_compassplus/site_config_token.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import os +import sys + +from pathlib import Path + + +DIR = Path(__file__).resolve().parent + +TOKEN_FILE_NAME = DIR / "TOKEN.txt" +SEP = "|" + +try: + TOKEN = os.environ.get("TOKEN") + if not TOKEN: + TOKEN = TOKEN_FILE_NAME.read_text("utf-8").strip() + if not TOKEN: + raise Exception("TOKEN пустой!") + + USERNAME, PASSWORD = TOKEN.split(SEP) + +except: + print( + f"Нужно в {TOKEN_FILE_NAME.name} или в переменную окружения " + f"TOKEN добавить логин/пароль (формат <домен>\\<логин>{SEP}<пароль>)" + ) + TOKEN_FILE_NAME.touch() + sys.exit() diff --git a/job_compassplus/svn/find_release_version.py b/job_compassplus/svn/find_release_version.py new file mode 100644 index 000000000..cf783e534 --- /dev/null +++ b/job_compassplus/svn/find_release_version.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import re +import subprocess +import xml.etree.ElementTree as ET + +from datetime import date, timedelta + +from get_last_release_version import get_last_release_version, URL_DEFAULT_SVN_PATH + + +def find_release_version( + text: str, + version: str, + last_days: int = 30, + url_svn_path: str = URL_DEFAULT_SVN_PATH, +) -> str: + url = f"{url_svn_path}/{version}" + + end_date = date.today() - timedelta(days=last_days) + + data: bytes = subprocess.check_output( + [ + "svn", + "log", + # "--verbose", + "--xml", + "--search", + text, + "--revision", + # Если в паре значений первым идет большее значение, то поиск будет идти от большего к меньшему + f"HEAD:{{{end_date}}}", + url, + ] + ) + root = ET.fromstring(data) + + last_revision = None + for logentry_el in root.findall(".//logentry"): + last_revision = logentry_el.attrib["revision"] + break + + if not last_revision: + raise Exception("Не удалось найти ревизию!") + + last_release_version: str = get_last_release_version( + version=version, + start_revision=last_revision, + last_days=last_days, + url_svn_path=url_svn_path, + ) + + # Первый коммит, который искали попал уже в следующую версию, поэтому + # нужно добавить 1 к последней версии: + # 3.2.35.10.10 -> 3.2.35.10.11 + last_release_version = re.sub( + r"\.(\d+)$", # Последнее число в версии + lambda m: f".{int(m.group(1)) + 1}", # Увеличение числа на 1 + last_release_version + ) + + return last_release_version + + +if __name__ == '__main__': + text = "TXI-8197" + print(find_release_version(text=text, version="3.2.35.10")) + # 3.2.35.10.11 + + print(find_release_version(text=text, version="3.2.34.10")) + # 3.2.34.10.18 diff --git a/job_compassplus/svn/get_last_release_version.py b/job_compassplus/svn/get_last_release_version.py new file mode 100644 index 000000000..886ec6a6b --- /dev/null +++ b/job_compassplus/svn/get_last_release_version.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import re +import subprocess +import xml.etree.ElementTree as ET + +from datetime import date, timedelta + + +TEXT_RELEASE_VERSION = "Release version " +PATTERN_RELEASE_VERSION = re.compile(rf"{TEXT_RELEASE_VERSION}([\d.]+) ") + +URL_DEFAULT_SVN_PATH = "svn+cplus://svn2.compassplus.ru/twrbs/trunk/dev" + + +def get_last_release_version( + version: str, + start_revision: str = "HEAD", + last_days: int = 30, + url_svn_path: str = URL_DEFAULT_SVN_PATH, +) -> str: + url = f"{url_svn_path}/{version}" + + end_date = date.today() - timedelta(days=last_days) + + data: bytes = subprocess.check_output( + [ + "svn", + "log", + "--xml", + "--search", + TEXT_RELEASE_VERSION, + "--revision", + # Если в паре значений первым идет большее значение, то поиск будет идти от большего к меньшему + f"{start_revision}:{{{end_date}}}", + url, + ] + ) + root = ET.fromstring(data) + + last_release_version_msg = None + for logentry_el in root.findall(".//logentry"): + last_release_version_msg = logentry_el.find("msg").text + break + + if not last_release_version_msg: + raise Exception("Не удалось найти коммит релиза!") + + m = PATTERN_RELEASE_VERSION.search(last_release_version_msg) + if not m: + raise Exception( + f"Не удалось вытащить версию релиза из {last_release_version_msg!r}" + ) + + return m.group(1) + + +if __name__ == "__main__": + print(get_last_release_version(version="trunk", last_days=60)) + # 3.2.36.10 + + print(get_last_release_version(version="3.2.35.10")) + # 3.2.35.10.10 + + print(get_last_release_version(version="3.2.34.10")) + # 3.2.34.10.17 diff --git a/job_compassplus/svn/search_by_versions.py b/job_compassplus/svn/search_by_versions.py new file mode 100644 index 000000000..a225af75b --- /dev/null +++ b/job_compassplus/svn/search_by_versions.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import re +import subprocess +import xml.etree.ElementTree as ET + +from datetime import date, timedelta + + +PATTERN_VERSION = re.compile(r"/dev/(.+?)/") +URL_DEFAULT_SVN_PATH = "svn+cplus://svn2.compassplus.ru/twrbs/trunk/dev" + + +def search( + text: str, + last_days: int = 30, + url_svn_path: str = URL_DEFAULT_SVN_PATH, +) -> list[str]: + start_date = date.today() - timedelta(days=last_days) + + data: bytes = subprocess.check_output( + [ + "svn", + "log", + "--verbose", + "--xml", + "--search", + text, + "--revision", + # Порядок имеет значение - выдача ревизий тут будет от меньшей к большей + f"{{{start_date}}}:HEAD", + url_svn_path, + ] + ) + + root = ET.fromstring(data) + + versions = [] + for logentry_el in root.findall(".//logentry"): + for path_el in logentry_el.findall("./paths/path"): + if m := PATTERN_VERSION.search(path_el.text): + version = m.group(1) + if version not in versions: + versions.append(version) + + return versions + + +if __name__ == "__main__": + versions: list[str] = search(text="TXI-8210") + print(versions) + # ['trunk', '3.2.36.10', '3.2.35.10'] + + versions: list[str] = search( + text="OPTT-441", + last_days=365, + url_svn_path="svn+cplus://svn2.compassplus.ru/twrbs/csm/optt", + ) + print(versions) + # ['trunk', '2.1.12.1'] diff --git a/tx_parse_xml/acl__add_attr__IsPresentable=false.py b/job_compassplus/tx_parse_xml/acl__add_attr__IsPresentable=false.py similarity index 78% rename from tx_parse_xml/acl__add_attr__IsPresentable=false.py rename to job_compassplus/tx_parse_xml/acl__add_attr__IsPresentable=false.py index 299d4229b..dd5f1b993 100644 --- a/tx_parse_xml/acl__add_attr__IsPresentable=false.py +++ b/job_compassplus/tx_parse_xml/acl__add_attr__IsPresentable=false.py @@ -1,25 +1,27 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from pathlib import Path from bs4 import BeautifulSoup -FILE_NAME_ACL = Path(r'C:\DEV__TX\trunk_tx\com.tranzaxis\ads\Interfacing.W4\src\aclTJQ2GCOP7NFZPERU23FQA76GXQ.xml') +FILE_NAME_ACL = Path( + r"C:\DEV__TX\trunk_tx\com.tranzaxis\ads\Interfacing.W4\src\aclTJQ2GCOP7NFZPERU23FQA76GXQ.xml" +) # NOTE: PROP_IDS = "prd45TLQNSG4ZHKBNA47FXMPPU2DA prdSMVLMMHGBNFZDPKTLREQIMZQQI prdO2NOBXHOF5GQ7GZNU72X7AHINA prdUCW5JGR3ERH57BHQZW6A5ICD3Y prdOBM2S7OY3NEGNKNH2ICNFYBBJM prd44YUSXQGX5EWRDH5KNLZW3HQY4 prdK6OZ5JDUPFHDFEOYPUV743YE4Q prdZBIQDTOORBBJ5LCGRJIZUTOR2M prdPIVNTR5EERAHLGOWNB2H3OM674 prdZ77U22XJOZA75HI3VOPQMRGEMQ prd7HEZYF7WEBE33BTYCW5JZBR7TM prdKDRVNDR2WZHDZNAWVUFTNQDMXM prdWW4PDFFYP5DHVNGYY5PFUDZKSU prdH273DPBJCJH5RIO7XOEDA264K4 prdLOZ3LGQ6O5DCJMCJKCDCNHCDTY prdXIJRCNSMUVB5NM7XWSEKNIB7VM prdM3TWEJNXGZHJPF2RYCSOVO5DCY prdU2ZTHQSTMFGSHHDZMAN4HHVEQY prd67BZTCTYZ5B5VJ2XHTEMF4GPTU prdU4ZPUTPJOVEAXB2STEOR75N4PM prdOSEMPZYRR5HMVG7QPW6HQJJUNQ prdE3XVETDHQVAG7F4AI2XMHTTNAE prdDSS3XKFQ7VFV3FTNVY72ZPFXNI prdMVSBYDI2MZA2ZJN7E5NIYKFOS4 prd3YGIB5LSA5CY5L6Z5SPX2IDHNU prdVBGG254XNNDFXIKVTOGGGC7RCE prdFJWE4R7BFRC7JDREKN45LKJE6Y prdDLGUFA7MFJEOXK2TDN5NEXK6JY prdJII6QJNJVFHZ5NWE6NPJJYJNAY prdMJQG63HJ4JAZ5KWGNUJASMEE6M prdDEIUSASLFJDV3P7FEEBJ3JC4IY prdIFMA4QRICFDD3GVNV4ERFEXIKQ prdGK3RLNHSJVD5DBZOU4MF3IZQHU prdDKTJ7TT7W5G4RGOLSQGRXDSDIA prd5CVKNF4PPNGAJDAWKIOJRSGGL4".split() -root_acl = BeautifulSoup(open(FILE_NAME_ACL, 'rb'), 'xml') +root_acl = BeautifulSoup(open(FILE_NAME_ACL, "rb"), "xml") for prop_id in PROP_IDS: prop_el = root_acl.select_one(f'[Id="{prop_id}"]') - if not prop_el.Presentation.has_attr('IsPresentable'): - prop_el.Presentation['IsPresentable'] = 'false' + if not prop_el.Presentation.has_attr("IsPresentable"): + prop_el.Presentation["IsPresentable"] = "false" -with open(FILE_NAME_ACL, 'w', encoding='utf-8') as f: +with open(FILE_NAME_ACL, "w", encoding="utf-8") as f: f.write(str(root_acl)) diff --git a/tx_parse_xml/acl__generate_new_Property.py b/job_compassplus/tx_parse_xml/acl__generate_new_Property.py similarity index 69% rename from tx_parse_xml/acl__generate_new_Property.py rename to job_compassplus/tx_parse_xml/acl__generate_new_Property.py index 07418a0e6..cbaad3548 100644 --- a/tx_parse_xml/acl__generate_new_Property.py +++ b/job_compassplus/tx_parse_xml/acl__generate_new_Property.py @@ -1,20 +1,26 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -from pathlib import Path import re import uuid +from pathlib import Path + from bs4 import BeautifulSoup -FILE_NAME_ACL = Path(r'C:\DEV__TX\trunk_tx\com.tranzaxis\ads\Interfacing.W4\src\aclTJQ2GCOP7NFZPERU23FQA76GXQ.xml') -FILE_NAME_ACL_LOCALE = FILE_NAME_ACL.parent.parent / 'locale' / 'en' / ('mlb' + FILE_NAME_ACL.name) -root_acl = BeautifulSoup(open(FILE_NAME_ACL, 'rb'), 'xml') -root_acl_locale = BeautifulSoup(open(FILE_NAME_ACL_LOCALE, 'rb'), 'xml') +FILE_NAME_ACL = Path( + r"C:\DEV__TX\trunk_tx\com.tranzaxis\ads\Interfacing.W4\src\aclTJQ2GCOP7NFZPERU23FQA76GXQ.xml" +) +FILE_NAME_ACL_LOCALE = ( + FILE_NAME_ACL.parent.parent / "locale" / "en" / ("mlb" + FILE_NAME_ACL.name) +) + +root_acl = BeautifulSoup(open(FILE_NAME_ACL, "rb"), "xml") +root_acl_locale = BeautifulSoup(open(FILE_NAME_ACL_LOCALE, "rb"), "xml") # NOTE: PROP_IDS = "prd45TLQNSG4ZHKBNA47FXMPPU2DA prdSMVLMMHGBNFZDPKTLREQIMZQQI prdO2NOBXHOF5GQ7GZNU72X7AHINA prdUCW5JGR3ERH57BHQZW6A5ICD3Y prdOBM2S7OY3NEGNKNH2ICNFYBBJM prd44YUSXQGX5EWRDH5KNLZW3HQY4 prdK6OZ5JDUPFHDFEOYPUV743YE4Q prdZBIQDTOORBBJ5LCGRJIZUTOR2M prdPIVNTR5EERAHLGOWNB2H3OM674 prdZ77U22XJOZA75HI3VOPQMRGEMQ prd7HEZYF7WEBE33BTYCW5JZBR7TM prdKDRVNDR2WZHDZNAWVUFTNQDMXM prdWW4PDFFYP5DHVNGYY5PFUDZKSU prdH273DPBJCJH5RIO7XOEDA264K4 prdLOZ3LGQ6O5DCJMCJKCDCNHCDTY prdXIJRCNSMUVB5NM7XWSEKNIB7VM prdM3TWEJNXGZHJPF2RYCSOVO5DCY prdU2ZTHQSTMFGSHHDZMAN4HHVEQY prd67BZTCTYZ5B5VJ2XHTEMF4GPTU prdU4ZPUTPJOVEAXB2STEOR75N4PM prdOSEMPZYRR5HMVG7QPW6HQJJUNQ prdE3XVETDHQVAG7F4AI2XMHTTNAE prdDSS3XKFQ7VFV3FTNVY72ZPFXNI prdMVSBYDI2MZA2ZJN7E5NIYKFOS4 prd3YGIB5LSA5CY5L6Z5SPX2IDHNU prdVBGG254XNNDFXIKVTOGGGC7RCE prdFJWE4R7BFRC7JDREKN45LKJE6Y prdDLGUFA7MFJEOXK2TDN5NEXK6JY prdJII6QJNJVFHZ5NWE6NPJJYJNAY prdMJQG63HJ4JAZ5KWGNUJASMEE6M prdDEIUSASLFJDV3P7FEEBJ3JC4IY prdIFMA4QRICFDD3GVNV4ERFEXIKQ prdGK3RLNHSJVD5DBZOU4MF3IZQHU prdDKTJ7TT7W5G4RGOLSQGRXDSDIA prd5CVKNF4PPNGAJDAWKIOJRSGGL4".split() @@ -22,19 +28,19 @@ new_prop_ids = [] -with open('new_props.txt', 'w', encoding='utf-8') as f: +with open("new_props.txt", "w", encoding="utf-8") as f: for prop_id in PROP_IDS: prop_el = root_acl.select_one(f'[Id="{prop_id}"]') - prop_name = prop_el['Name'] + prop_name = prop_el["Name"] - title_id = prop_el.Presentation['TitleId'] + title_id = prop_el.Presentation["TitleId"] title = root_acl_locale.select_one(f'[Id="{title_id}"]').Value.text # print(name, title) - prop_el['Id'] = f'prd{uuid.uuid4().hex.upper()[:26]}' - prop_el['Name'] = f'{prop_name}_Title_{title}' + prop_el["Id"] = f"prd{uuid.uuid4().hex.upper()[:26]}" + prop_el["Name"] = f"{prop_name}_Title_{title}" - new_prop_ids.append(prop_el['Id']) + new_prop_ids.append(prop_el["Id"]) new_src = f"""\ @@ -52,8 +58,10 @@ \ """ - new_prop_el_str = re.sub('.+?', new_src, str(prop_el), flags=re.DOTALL) + new_prop_el_str = re.sub( + ".+?", new_src, str(prop_el), flags=re.DOTALL + ) print(new_prop_el_str, file=f) - f.write('\n\n') - f.write('new_prop_ids: "' + ' '.join(new_prop_ids) + '"') + f.write("\n\n") + f.write('new_prop_ids: "' + " ".join(new_prop_ids) + '"') diff --git a/tx_parse_xml/acl__generate_new_case_of_switch__with_XSD.py b/job_compassplus/tx_parse_xml/acl__generate_new_case_of_switch__with_XSD.py similarity index 58% rename from tx_parse_xml/acl__generate_new_case_of_switch__with_XSD.py rename to job_compassplus/tx_parse_xml/acl__generate_new_case_of_switch__with_XSD.py index 9700bb54d..cd308284d 100644 --- a/tx_parse_xml/acl__generate_new_case_of_switch__with_XSD.py +++ b/job_compassplus/tx_parse_xml/acl__generate_new_case_of_switch__with_XSD.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # Example: @@ -59,22 +59,69 @@ """ ITEMS = [ - 'CalcArpc', 'CalcArqc', 'CalcCsc', 'CalcCvv', 'CalcEmvMac', 'CalcHmac', 'CalcKeyCheck', 'CalcMac', 'CalcMacDukpt', - 'CalcPinOffset', 'CalcPvv', 'CheckArpc', 'CheckArqc', 'CheckCapToken', 'CheckCsc', 'CheckCvc3', 'CheckCvv', - 'CheckDcsc', 'CheckDcvv', 'CheckHmac', 'CheckKey', 'CheckMac', 'CheckMacDukpt', 'CheckPinOffset', 'CheckPvv', - 'CheckRsaKey', 'DecryptDukpt', 'DesCrypt', 'DesRecrypt', 'EncryptDukpt', 'ExportEmvPinBlock', 'ExportHmacKey', - 'ExportKey', 'ExportPinBlock', 'ExportRsaKey', 'GenHmacKey', 'GenKey', 'GenRsaKey', 'GeneratePin', 'GetHsmInfo', - 'ImportClearPin', 'ImportHmacKey', 'ImportKey', 'ImportPinBlock', 'ImportPinBlockDukpt', 'ImportPinOffset', - 'ImportRsaKey', 'JwtDecode', 'JwtEncode', 'MigrateKey', 'RebuildPinBlock', 'RklGenerateAndEncryptKey', - 'RklImportPublicKey', 'RklImportRootPublicKey', 'RsaSign', 'RsaVerify', 'TdsSign', 'TranslateKey' + "CalcArpc", + "CalcArqc", + "CalcCsc", + "CalcCvv", + "CalcEmvMac", + "CalcHmac", + "CalcKeyCheck", + "CalcMac", + "CalcMacDukpt", + "CalcPinOffset", + "CalcPvv", + "CheckArpc", + "CheckArqc", + "CheckCapToken", + "CheckCsc", + "CheckCvc3", + "CheckCvv", + "CheckDcsc", + "CheckDcvv", + "CheckHmac", + "CheckKey", + "CheckMac", + "CheckMacDukpt", + "CheckPinOffset", + "CheckPvv", + "CheckRsaKey", + "DecryptDukpt", + "DesCrypt", + "DesRecrypt", + "EncryptDukpt", + "ExportEmvPinBlock", + "ExportHmacKey", + "ExportKey", + "ExportPinBlock", + "ExportRsaKey", + "GenHmacKey", + "GenKey", + "GenRsaKey", + "GeneratePin", + "GetHsmInfo", + "ImportClearPin", + "ImportHmacKey", + "ImportKey", + "ImportPinBlock", + "ImportPinBlockDukpt", + "ImportPinOffset", + "ImportRsaKey", + "JwtDecode", + "JwtEncode", + "MigrateKey", + "RebuildPinBlock", + "RklGenerateAndEncryptKey", + "RklImportPublicKey", + "RklImportRootPublicKey", + "RsaSign", + "RsaVerify", + "TdsSign", + "TranslateKey", ] -cases = [ - TEMPLATE_CASE.replace('{OPERATION}', operation) - for operation in ITEMS -] -text = TEMPLATE.replace('{SWITCHES}', '\n'.join(cases)) +cases = [TEMPLATE_CASE.replace("{OPERATION}", operation) for operation in ITEMS] +text = TEMPLATE.replace("{SWITCHES}", "\n".join(cases)) -with open('switch_case.xml', 'w', encoding='utf-8') as f: +with open("switch_case.xml", "w", encoding="utf-8") as f: print(text) print(text, file=f) diff --git a/tx_parse_xml/acl__prop_to_title.py b/job_compassplus/tx_parse_xml/acl__prop_to_title.py similarity index 68% rename from tx_parse_xml/acl__prop_to_title.py rename to job_compassplus/tx_parse_xml/acl__prop_to_title.py index cef3b1644..dbba66698 100644 --- a/tx_parse_xml/acl__prop_to_title.py +++ b/job_compassplus/tx_parse_xml/acl__prop_to_title.py @@ -1,17 +1,22 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from pathlib import Path from bs4 import BeautifulSoup -FILE_NAME_ACL = Path(r'C:\DEV__TX\trunk_tx\com.tranzaxis\ads\Interfacing.W4\src\aclTJQ2GCOP7NFZPERU23FQA76GXQ.xml') -FILE_NAME_ACL_LOCALE = FILE_NAME_ACL.parent.parent / 'locale' / 'en' / ('mlb' + FILE_NAME_ACL.name) -root_acl = BeautifulSoup(open(FILE_NAME_ACL, 'rb'), 'html.parser') -root_acl_locale = BeautifulSoup(open(FILE_NAME_ACL_LOCALE, 'rb'), 'html.parser') +FILE_NAME_ACL = Path( + r"C:\DEV__TX\trunk_tx\com.tranzaxis\ads\Interfacing.W4\src\aclTJQ2GCOP7NFZPERU23FQA76GXQ.xml" +) +FILE_NAME_ACL_LOCALE = ( + FILE_NAME_ACL.parent.parent / "locale" / "en" / ("mlb" + FILE_NAME_ACL.name) +) + +root_acl = BeautifulSoup(open(FILE_NAME_ACL, "rb"), "html.parser") +root_acl_locale = BeautifulSoup(open(FILE_NAME_ACL_LOCALE, "rb"), "html.parser") # NOTE: PROP_IDS = "prd45TLQNSG4ZHKBNA47FXMPPU2DA prdSMVLMMHGBNFZDPKTLREQIMZQQI prdO2NOBXHOF5GQ7GZNU72X7AHINA prdUCW5JGR3ERH57BHQZW6A5ICD3Y prdOBM2S7OY3NEGNKNH2ICNFYBBJM prd44YUSXQGX5EWRDH5KNLZW3HQY4 prdK6OZ5JDUPFHDFEOYPUV743YE4Q prdZBIQDTOORBBJ5LCGRJIZUTOR2M prdPIVNTR5EERAHLGOWNB2H3OM674 prdZ77U22XJOZA75HI3VOPQMRGEMQ prd7HEZYF7WEBE33BTYCW5JZBR7TM prdKDRVNDR2WZHDZNAWVUFTNQDMXM prdWW4PDFFYP5DHVNGYY5PFUDZKSU prdH273DPBJCJH5RIO7XOEDA264K4 prdLOZ3LGQ6O5DCJMCJKCDCNHCDTY prdXIJRCNSMUVB5NM7XWSEKNIB7VM prdM3TWEJNXGZHJPF2RYCSOVO5DCY prdU2ZTHQSTMFGSHHDZMAN4HHVEQY prd67BZTCTYZ5B5VJ2XHTEMF4GPTU prdU4ZPUTPJOVEAXB2STEOR75N4PM prdOSEMPZYRR5HMVG7QPW6HQJJUNQ prdE3XVETDHQVAG7F4AI2XMHTTNAE prdDSS3XKFQ7VFV3FTNVY72ZPFXNI prdMVSBYDI2MZA2ZJN7E5NIYKFOS4 prd3YGIB5LSA5CY5L6Z5SPX2IDHNU prdVBGG254XNNDFXIKVTOGGGC7RCE prdFJWE4R7BFRC7JDREKN45LKJE6Y prdDLGUFA7MFJEOXK2TDN5NEXK6JY prdJII6QJNJVFHZ5NWE6NPJJYJNAY prdMJQG63HJ4JAZ5KWGNUJASMEE6M prdDEIUSASLFJDV3P7FEEBJ3JC4IY prdIFMA4QRICFDD3GVNV4ERFEXIKQ prdGK3RLNHSJVD5DBZOU4MF3IZQHU prdDKTJ7TT7W5G4RGOLSQGRXDSDIA prd5CVKNF4PPNGAJDAWKIOJRSGGL4".split() @@ -19,15 +24,15 @@ items = [] for prop_id in PROP_IDS: - prop_el = root_acl.select_one('#' + prop_id) - name = prop_el['name'] + prop_el = root_acl.select_one("#" + prop_id) + name = prop_el["name"] - title_id = prop_el.presentation['titleid'] - title = root_acl_locale.select_one('#' + title_id).value.text + title_id = prop_el.presentation["titleid"] + title = root_acl_locale.select_one("#" + title_id).value.text items.append((name, title)) items.sort() for name, title in items: - print(name, title, sep='\t') + print(name, title, sep="\t") diff --git a/tx_parse_xml/acl_update_License_for_GetterSources.py b/job_compassplus/tx_parse_xml/acl_update_License_for_GetterSources.py similarity index 72% rename from tx_parse_xml/acl_update_License_for_GetterSources.py rename to job_compassplus/tx_parse_xml/acl_update_License_for_GetterSources.py index f12d0fe66..e48cb39af 100644 --- a/tx_parse_xml/acl_update_License_for_GetterSources.py +++ b/job_compassplus/tx_parse_xml/acl_update_License_for_GetterSources.py @@ -1,16 +1,18 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -from pathlib import Path import re +from pathlib import Path from bs4 import BeautifulSoup -FILE_NAME_ACL = Path(r'C:\DEV__TX\trunk_tx\com.tranzaxis\ads\Interfacing.W4\src\aclTJQ2GCOP7NFZPERU23FQA76GXQ.xml') +FILE_NAME_ACL = Path( + r"C:\DEV__TX\trunk_tx\com.tranzaxis\ads\Interfacing.W4\src\aclTJQ2GCOP7NFZPERU23FQA76GXQ.xml" +) LICENSE_PATH = "com.tranzaxis/Interface/Online/W4/" TEMPLATE_THIS_PROP = """ @@ -71,28 +73,32 @@ """ -ITEMS = [ - ... -] +ITEMS = [...] -root_acl = BeautifulSoup(open(FILE_NAME_ACL, 'rb'), 'xml') +root_acl = BeautifulSoup(open(FILE_NAME_ACL, "rb"), "xml") root_acl_str = str(root_acl) for license_name, license_id, prop_name, prop_id in ITEMS: print(license_name, license_id, prop_name, prop_id) - new_getter_src = TEMPLATE \ - .replace('{TEMPLATE_THIS_PROP}', TEMPLATE_THIS_PROP) \ - .replace('{prop_id}', prop_id) \ - .replace('{prop_name}', prop_name) \ - .replace('{license_path}', LICENSE_PATH) \ - .replace('{license_name}', license_id) \ - .replace('{acl_id}', FILE_NAME_ACL.stem) + new_getter_src = ( + TEMPLATE.replace("{TEMPLATE_THIS_PROP}", TEMPLATE_THIS_PROP) + .replace("{prop_id}", prop_id) + .replace("{prop_name}", prop_name) + .replace("{license_path}", LICENSE_PATH) + .replace("{license_name}", license_id) + .replace("{acl_id}", FILE_NAME_ACL.stem) + ) prop_el = root_acl.select_one(f'[Id="{prop_id}"]') old_prop_el_str = str(prop_el) - new_prop_el_str = re.sub('.+?', new_getter_src, old_prop_el_str, flags=re.DOTALL) + new_prop_el_str = re.sub( + ".+?", + new_getter_src, + old_prop_el_str, + flags=re.DOTALL, + ) root_acl_str = root_acl_str.replace(old_prop_el_str, new_prop_el_str) -with open('new_' + FILE_NAME_ACL.name, 'w', encoding='utf-8') as f: +with open("new_" + FILE_NAME_ACL.name, "w", encoding="utf-8") as f: f.write(root_acl_str) diff --git a/tx_parse_xml/acl_update_name_props.py b/job_compassplus/tx_parse_xml/acl_update_name_props.py similarity index 59% rename from tx_parse_xml/acl_update_name_props.py rename to job_compassplus/tx_parse_xml/acl_update_name_props.py index 34cdd86e0..5c6d252d2 100644 --- a/tx_parse_xml/acl_update_name_props.py +++ b/job_compassplus/tx_parse_xml/acl_update_name_props.py @@ -1,20 +1,20 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from pathlib import Path from bs4 import BeautifulSoup -FILE_NAME_ACL = Path(r'C:\DEV__TX\trunk_tx\com.tranzaxis\ads\Interfacing.W4\src\aclTJQ2GCOP7NFZPERU23FQA76GXQ.xml') +FILE_NAME_ACL = Path( + r"C:\DEV__TX\trunk_tx\com.tranzaxis\ads\Interfacing.W4\src\aclTJQ2GCOP7NFZPERU23FQA76GXQ.xml" +) -ITEMS = [ - ... -] +ITEMS = [...] -root_acl = BeautifulSoup(open(FILE_NAME_ACL, 'rb'), 'xml') +root_acl = BeautifulSoup(open(FILE_NAME_ACL, "rb"), "xml") root_acl_str = str(root_acl) for license_name, license_id, prop_name, prop_id in ITEMS: @@ -23,10 +23,10 @@ prop_el = root_acl.select_one(f'[Id="{prop_id}"]') old_prop_el_str = str(prop_el) - prop_el['Name'] = f"{prop_name}_{license_id}" + prop_el["Name"] = f"{prop_name}_{license_id}" new_prop_el_str = str(prop_el) root_acl_str = root_acl_str.replace(old_prop_el_str, new_prop_el_str) -with open('new_' + FILE_NAME_ACL.name, 'w', encoding='utf-8') as f: +with open("new_" + FILE_NAME_ACL.name, "w", encoding="utf-8") as f: f.write(root_acl_str) diff --git a/job_compassplus/tx_parse_xml/count_TODO_NOTE_FIXME_of_ADS_classes.py b/job_compassplus/tx_parse_xml/count_TODO_NOTE_FIXME_of_ADS_classes.py new file mode 100644 index 000000000..0fa9efc26 --- /dev/null +++ b/job_compassplus/tx_parse_xml/count_TODO_NOTE_FIXME_of_ADS_classes.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import xml.etree.ElementTree as ET + +from collections import defaultdict +from pathlib import Path +from xml.etree.ElementTree import ParseError + + +NS = dict( + xsc="http://schemas.radixware.org/xscml.xsd", +) + + +def process(path: str): + if isinstance(path, str): + path = Path(path) + + if not path.exists(): + raise Exception(f"Not exists: {path}") + + assignee_by_number: dict[str, int] = defaultdict(int) + type_by_number: dict[str, int] = defaultdict(int) + behavior_by_number: dict[str, int] = defaultdict(int) + + for layer_dir in path.glob("*"): + if not layer_dir.is_dir(): + continue + + layer_ads_dir: Path = layer_dir / "ads" + if not layer_ads_dir.is_dir(): + continue + + for class_path in layer_ads_dir.glob("*/src/*.xml"): + try: + model = ET.fromstring(class_path.read_bytes()) + except ParseError as e: + print(f"[#] Invalid XML by path {str(class_path)!r}\nError: {e}\n") + continue + + for task_el in model.findall(".//xsc:Task", namespaces=NS): + assignee: str = task_el.attrib["Assignee"].strip() + if not assignee: + assignee = "" + + assignee_by_number[assignee] += 1 + + task_type: str = task_el.attrib["Type"] + type_by_number[task_type] += 1 + + behavior: str = task_el.attrib["Behavior"] + behavior_by_number[behavior] += 1 + + indent1: str = " " + + print("Total:", sum(assignee_by_number.values())) + print() + + print(f"assignee_by_number ({len(assignee_by_number)}):") + for assignee, number in sorted( + assignee_by_number.items(), key=lambda x: x[1], reverse=True + ): + print(f"{indent1}{assignee}: {number}") + print() + + print(f"type_by_number ({len(type_by_number)}):") + for task_type, number in sorted( + type_by_number.items(), key=lambda x: x[1], reverse=True + ): + print(f"{indent1}{task_type}: {number}") + print() + + print(f"behavior_by_number ({len(behavior_by_number)}):") + for behavior, number in sorted( + behavior_by_number.items(), key=lambda x: x[1], reverse=True + ): + print(f"{indent1}{behavior}: {number}") + + +if __name__ == "__main__": + import argparse + + parser = argparse.ArgumentParser( + description="Count TODO, NOTE, FIXME of ADS classes" + ) + parser.add_argument( + "path_trunk", + metavar="/PATH/TO/TRUNK", + help="Path to source tree (the directory containing branch.xml)", + ) + args = parser.parse_args() + + process(args.path_trunk) diff --git a/job_compassplus/tx_parse_xml/counting_size_bin_of_ADS.py b/job_compassplus/tx_parse_xml/counting_size_bin_of_ADS.py new file mode 100644 index 000000000..bdf444a5c --- /dev/null +++ b/job_compassplus/tx_parse_xml/counting_size_bin_of_ADS.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from pathlib import Path +from collections import Counter + +# pip install humanize +from humanize import naturalsize as sizeof_fmt + + +def process(path_layer: str): + if isinstance(path_layer, str): + path_layer = Path(path_layer) + + if not path_layer.exists(): + raise Exception(f"Not exists: {path_layer}") + + total_files: int = 0 + module_by_size = Counter() + + for path_bin in Path(path_layer).glob("ads/*/bin"): + module: str = path_bin.parent.name + sizes: list[int] = [f.stat().st_size for f in path_bin.glob("*.jar")] + if not sizes: + continue + + total_files += len(sizes) + module_by_size[module] = sum(sizes) + + print(f"Total modules: {len(module_by_size)}") + print(f"Total files: {total_files}") + print("Top10 modules by size:") + for module, size in module_by_size.most_common(10): + print(f" {module}: {sizeof_fmt(size)} ({size} bytes)") + + +if __name__ == "__main__": + import argparse + + parser = argparse.ArgumentParser( + description="Counting size bin of ADS modules" + ) + parser.add_argument( + "path_layer", + metavar="/PATH/TO/TRUNK/LAYER", + help="Path to layer (the directory containing layer.xml)", + ) + args = parser.parse_args() + + process(args.path_layer) diff --git a/job_compassplus/tx_parse_xml/export_ids.py b/job_compassplus/tx_parse_xml/export_ids.py new file mode 100644 index 000000000..e83ff4bb7 --- /dev/null +++ b/job_compassplus/tx_parse_xml/export_ids.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import re +from pathlib import Path + + +PATTERN_ID: re.Pattern = re.compile(rb'\bId="([a-zA-Z]{3}\w+)"') + + +def process(path_layer: Path, path_extract: Path): + if not path_layer.exists(): + raise Exception(f"Not exists: {path_layer}") + + items: set[bytes] = set() + for f in path_layer.rglob("*.xml"): + # TODO: Добавить статистику: файлы, количество найденных + ids: list[bytes] = PATTERN_ID.findall(f.read_bytes()) + print(f"{f} ids: {len(ids)}") + items.update(ids) + + with open(path_extract, "wb") as f: + for value in items: + f.write(value + b"\n") + + +if __name__ == "__main__": + import argparse + + parser = argparse.ArgumentParser( + description="Exporting IDs to a file" + ) + parser.add_argument( + "path_layer", + metavar="/PATH/TO/TRUNK/LAYER", + help="Path to layer (the directory containing layer.xml)", + type=Path, + ) + parser.add_argument( + "path_extract", + help="Path to extract", + type=Path, + ) + args = parser.parse_args() + + process(args.path_layer, args.path_extract) diff --git a/job_compassplus/tx_parse_xml/gen_svn_ignore_layers.py b/job_compassplus/tx_parse_xml/gen_svn_ignore_layers.py new file mode 100644 index 000000000..9bc577412 --- /dev/null +++ b/job_compassplus/tx_parse_xml/gen_svn_ignore_layers.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import fnmatch +import argparse + +from pathlib import Path + + +def process( + root_path: Path, + include_glob: str, + exclude_patterns: list[str], + encoding: str = "utf-8", +) -> None: + file_exclude: Path = root_path / "svn_layers_exclude.bat" + file_restore: Path = root_path / "svn_layers_restore.bat" + + print(f"Scanning directory: {root_path.absolute()}") + + target_dirs: list[Path] = [p for p in root_path.glob(include_glob) if p.is_dir()] + if not target_dirs: + print("No matching directories found.") + return + + # Подготавливаем списки строк для записи + lines_exclude: list[str] = ["@echo off"] + lines_restore: list[str] = ["@echo off"] + + for p in target_dirs: + dir_name: str = p.name + is_excluded: bool = any( + fnmatch.fnmatch(dir_name, pat) for pat in exclude_patterns + ) + + prefix: str = "REM " if is_excluded else "" + lines_exclude.append(f"{prefix}svn update --set-depth exclude {dir_name}") + lines_restore.append(f"{prefix}svn update --set-depth infinity {dir_name}") + + file_exclude.write_text("\n".join(lines_exclude) + "\n", encoding=encoding) + file_restore.write_text("\n".join(lines_restore) + "\n", encoding=encoding) + + print(f"Done! Created '{file_exclude.name}' and '{file_restore.name}'") + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description="Generates SVN batch scripts to manage folder depth (exclude/include).", + ) + parser.add_argument( + "path", + metavar="PROJECT_PATH", + type=Path, + help="Path to the project directory", + ) + parser.add_argument( + "--include", + default="com.tranzaxis.*", + help="Glob pattern for directories to process (default: %(default)s)", + ) + parser.add_argument( + "--excludes", + nargs="+", + default=["com.tranzaxis.experimental", "com.tranzaxis.demo"], + help="Globs for directories to comment out in exclude script (default: %(default)s)", + ) + + args = parser.parse_args() + + if not args.path.exists(): + parser.error(f"The path {args.path} does not exist.") + + process(args.path, args.include, args.excludes) diff --git a/job_compassplus/tx_parse_xml/generate_new_licenses_of_layer.py b/job_compassplus/tx_parse_xml/generate_new_licenses_of_layer.py new file mode 100644 index 000000000..334458c19 --- /dev/null +++ b/job_compassplus/tx_parse_xml/generate_new_licenses_of_layer.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +ITEMS = [ + "W000", + "W001", + "W002", + "W003", + "W004", + "W005", + "W006", + "W007", + "W008", + "W009", + "W010", + "W011", + "W012", + "W014", + "W015", + "W016", + "W017", + "W018", + "W019", + "W021", + "W022", + "W023", + "W024", + "W025", + "W026", + "W027", + "W028", + "W029", + "W032", + "W033", + "W034", + "W035", + "W036", + "W037", +] + +text = "" + +for name in ITEMS: + text += f"""\ + + + +""" + +print(text) diff --git a/job_compassplus/tx_parse_xml/get_layer_version.py b/job_compassplus/tx_parse_xml/get_layer_version.py new file mode 100644 index 000000000..920a34654 --- /dev/null +++ b/job_compassplus/tx_parse_xml/get_layer_version.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import re +from pathlib import Path + + +def get_layer_version(path: Path) -> str: + path = path.resolve() + + if not path.is_dir(): + raise Exception(f"Path {str(path)!r} must be directory") + + path = path / "layer.xml" + if not path.exists(): + raise Exception(f"Path {str(path)!r} is not exists!") + + m = re.search(r'\bReleaseNumber="(.+?)"', path.read_text("utf-8")) + if not m: + raise Exception(f'Not found "ReleaseNumber" in path {str(path)!r}!') + + return m.group(1) + + +def process(path: Path) -> None: + print(get_layer_version(path)) + + +if __name__ == "__main__": + import argparse + + parser = argparse.ArgumentParser( + description="Print a layer version" + ) + parser.add_argument( + "path", + metavar="/PATH/TO/LAYER", + type=Path, + help="Path to layer (the directory containing layer.xml)", + ) + args = parser.parse_args() + + process(args.path) diff --git a/job_compassplus/tx_parse_xml/get_project_versions.py b/job_compassplus/tx_parse_xml/get_project_versions.py new file mode 100644 index 000000000..2612cc5c6 --- /dev/null +++ b/job_compassplus/tx_parse_xml/get_project_versions.py @@ -0,0 +1,329 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import re +import subprocess + +from dataclasses import dataclass, field, fields +from pathlib import Path +from typing import Optional, Any + + +def get_diff_fields(obj1, obj2) -> dict[str, tuple[Any, Any]]: + """ + Compares two dataclass instances and returns a dictionary of differing fields. + The dictionary keys are the field names, and values are a tuple + (value_in_obj1, value_in_obj2). + """ + + if not isinstance(obj1, type(obj2)): + raise TypeError("Both objects must be instances of the same dataclass.") + + diffs: dict[str, tuple[Any, Any]] = dict() + for field_info in fields(obj1): + field_name: str = field_info.name + value1: Any = getattr(obj1, field_name) + value2: Any = getattr(obj2, field_name) + + if value1 != value2: + diffs[field_name] = value1, value2 + + return diffs + + +def parse_xml_attrs(text: str) -> dict[str, str]: + return dict(re.findall(r'(\w+)\s*=\s*"(.+?)"', text)) + + +def parse_line_xml_text(text: str, line_contains: list[str]) -> dict[str, str]: + for line in text.splitlines(): + if all(x in line for x in line_contains): + return parse_xml_attrs(line) + + return dict() + + +def get_remote_file_from_svn(file_name: Path) -> str: + try: + result: bytes = subprocess.check_output( + args=["svn", "info", file_name], + stderr=subprocess.PIPE, + ) + except subprocess.CalledProcessError as e: + # NOTE: svn: warning: W155010: The node 'xxx' was not found. + if b"W155010" in e.stderr: + print(f"[#] Не найден {file_name} в SVN") + return "" + + raise e + + url_svn_file: str | None = None + for line in result.splitlines(): + parts: list[bytes] = line.split(b"URL:", maxsplit=1) + if len(parts) < 2: + continue + + url_svn_file = parts[1].strip().decode("ascii") + break + + if not url_svn_file: + raise Exception( + f"Не удалось получить URL файла {file_name} в репозитории SVN из результата svn info:\n{result!r}" + ) + + return subprocess.check_output( + args=["svn", "cat", url_svn_file], + encoding="utf-8", + ) + + +@dataclass +class BranchInfo: + # TODO: Не все поля были поддержаны + base_dev_uri: str + type: str + base_release: str | None + last_release: str | None + + @classmethod + def parse_from_dict(cls, d: dict[str, str]) -> "BranchInfo": + return cls( + base_dev_uri=d["BaseDevUri"], + type=d["Type"], + base_release=d.get("BaseRelease"), + last_release=d.get("LastRelease"), + ) + + @classmethod + def parse_from_text(cls, text: str) -> Optional["BranchInfo"]: + xml_attrs: dict[str, str] = parse_line_xml_text( + text=text, + line_contains=["Branch", "BaseDevUri", "Type"], + ) + if xml_attrs: + return cls.parse_from_dict(xml_attrs) + + +@dataclass +class LayerInfo: + # TODO: Не все поля были поддержаны + uri: str + name: str + release_number: str | None + base_layer_uris: list[str] = field(default_factory=list) + + @classmethod + def parse_from_dict(cls, d: dict[str, str]) -> "LayerInfo": + return cls( + uri=d["Uri"], + name=d["Name"], + release_number=d.get("ReleaseNumber"), + base_layer_uris=d.get("BaseLayerURIs", "").split(), + ) + + @classmethod + def parse_from_text(cls, text: str) -> Optional["LayerInfo"]: + xml_attrs: dict[str, str] = parse_line_xml_text( + text=text, + line_contains=["Layer", "Uri", "Name"], + ) + if xml_attrs: + return cls.parse_from_dict(xml_attrs) + + +@dataclass +class ProjectInfo: + branch: BranchInfo | None + layers: list[LayerInfo] + + +@dataclass +class TotalProjectInfo: + local: ProjectInfo + remote: ProjectInfo + + +def collect_all_layer_infos( + path_layer: Path, + layer_infos: list[LayerInfo], + remote: bool = False, +): + if not path_layer.exists(): + raise Exception(f"Путь {path_layer} не существует!") + + layer_info: LayerInfo | None = LayerInfo.parse_from_text( + get_remote_file_from_svn(path_layer) + if remote + else path_layer.read_text(encoding="utf-8") + ) + + if not layer_info: + text = f"Не удалось получить информацию из {path_layer}!" + if not remote: + raise Exception(f"{text} (local)") + + print(f"[#] {text} (remote)") + return + + if layer_info in layer_infos: + print(f"[#] Обнаружилось зацикливание с {layer_info}") + return + + layer_infos.append(layer_info) + + if not layer_info.base_layer_uris: + return + + for base_layer_uri in layer_info.base_layer_uris: + path_layer = path_layer.parent.parent / base_layer_uri / "layer.xml" + collect_all_layer_infos(path_layer, layer_infos, remote=remote) + + +def get_project_versions(path: Path) -> TotalProjectInfo: + path = path.resolve() + + if not path.is_dir(): + raise Exception(f"Путь {path} должен быть директорией") + + path_branch = path / "branch.xml" + if not path_branch.exists(): + raise Exception(f"Не существует: {path_branch}") + + branch_info_local: BranchInfo | None = BranchInfo.parse_from_text( + path_branch.read_text(encoding="utf-8") + ) + if not branch_info_local: + raise Exception( + f"Не удалось получить информацию из {branch_info_local}! (local)" + ) + + branch_info_remote: BranchInfo | None = BranchInfo.parse_from_text( + get_remote_file_from_svn(path_branch) + ) + if not branch_info_remote: + print(f"[#] Не удалось получить информацию из {branch_info_remote}! (remote)") + + path_base_dev_layer = path / branch_info_local.base_dev_uri / "layer.xml" + + layer_infos_local: list[LayerInfo] = [] + collect_all_layer_infos(path_base_dev_layer, layer_infos_local, remote=False) + + layer_infos_remote: list[LayerInfo] = [] + collect_all_layer_infos(path_base_dev_layer, layer_infos_remote, remote=True) + + return TotalProjectInfo( + local=ProjectInfo( + branch=branch_info_local, + layers=layer_infos_local, + ), + remote=ProjectInfo( + branch=branch_info_remote, + layers=layer_infos_remote, + ), + ) + + +def process(path: Path) -> None: + print(f"Информация по версиям из {path}") + + total_project_info: TotalProjectInfo = get_project_versions(path) + + if not total_project_info.local.branch: + print() + print("[#] Local branch: <нет информации>") + elif not total_project_info.remote.branch: + print() + print("[#] Remote branch: <нет информации>") + else: + diff_branches: dict[str, tuple[Any, Any]] = get_diff_fields( + total_project_info.local.branch, total_project_info.remote.branch + ) + if diff_branches: + print() + + print("Обнаружена разница в полях Branch между local и remote:") + for field_name, (value_local, value_remote) in diff_branches.items(): + print(f" {field_name}: {value_local} != {value_remote}") + + @dataclass + class DiffLayersResult: + local: LayerInfo | None = None + remote: LayerInfo | None = None + + uri_by_layers: dict[str, DiffLayersResult] = { + uri: DiffLayersResult() + for uri in set( + l.uri + for l in ( + total_project_info.local.layers + total_project_info.remote.layers + ) + ) + } + + for l in total_project_info.local.layers: + uri_by_layers[l.uri].local = l + for l in total_project_info.remote.layers: + uri_by_layers[l.uri].remote = l + + diff_layers_local: list[LayerInfo] = [] + diff_layers_remote: list[LayerInfo] = [] + for uri, layers in uri_by_layers.items(): + if layers.local and layers.remote: + diff_layers: dict[str, tuple[Any, Any]] = get_diff_fields( + layers.local, layers.remote + ) + if diff_layers: + print() + print(f"Обнаружена разница в полях Layer ({uri}) между local и remote:") + for field_name, (value_local, value_remote) in diff_layers.items(): + print(f" {field_name}: {value_local} != {value_remote}") + + elif layers.local: + diff_layers_remote.append(layers.local) + elif layers.remote: + diff_layers_local.append(layers.remote) + + if diff_layers_local: + print() + print(f"В Local нет слоев из Remote ({len(diff_layers_local)}):") + for l in diff_layers_local: + print(f" {l}") + + if diff_layers_remote: + print() + print(f"В Local нет слоев из Remote ({len(diff_layers_remote)}):") + for l in diff_layers_remote: + print(f" {l}") + + print() + print("Версии:") + + def _print_project_info(project_info: ProjectInfo, is_local: bool) -> None: + ind1: str = " " + print(ind1 + ("Local:" if is_local else "Remote:")) + print(f"{ind1 * 2}Branch: {project_info.branch}") + print(f"{ind1 * 2}Layers:") + for layer in project_info.layers: + print(f"{ind1 * 3}{layer}") + + _print_project_info(total_project_info.local, is_local=True) + print() + _print_project_info(total_project_info.remote, is_local=False) + + +if __name__ == "__main__": + import argparse + + parser = argparse.ArgumentParser(description="Print a layer version") + parser.add_argument( + "path", + metavar="/PATH/TO/PROJECT", + type=Path, + help="Path to project (the directory containing branch.xml)", + ) + args = parser.parse_args() + + process(args.path) diff --git a/job_compassplus/tx_parse_xml/module_id_by_name.py b/job_compassplus/tx_parse_xml/module_id_by_name.py new file mode 100644 index 000000000..286ad1be2 --- /dev/null +++ b/job_compassplus/tx_parse_xml/module_id_by_name.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from pathlib import Path +from show_maintainers import parse_xml_partially + + +def get_module_id_by_name(project_dir: str | Path) -> dict[str, str]: + if isinstance(project_dir, str): + project_dir = Path(project_dir) + + module_id_by_name: dict[str, str] = dict() + for layer_path in project_dir.glob("*/layer.xml"): + for path in layer_path.parent.glob("ads/*/module.xml"): + root_module = parse_xml_partially( + path, line_contains=["Module ", " Id", " Name"] + ) + module_id = root_module.module["id"] + if module_id not in module_id_by_name: + module_id_by_name[module_id] = root_module.module["name"] + + return module_id_by_name + + +if __name__ == "__main__": + project_dir = "C:/DEV__TX/trunk" + owner_by_modules = get_module_id_by_name(project_dir) + + for module_id, module_name in owner_by_modules.items(): + print(f"{module_id} = {module_name}") diff --git a/tx_parse_xml/save_maintainers.py b/job_compassplus/tx_parse_xml/save_maintainers.py similarity index 61% rename from tx_parse_xml/save_maintainers.py rename to job_compassplus/tx_parse_xml/save_maintainers.py index 31329187e..831180160 100644 --- a/tx_parse_xml/save_maintainers.py +++ b/job_compassplus/tx_parse_xml/save_maintainers.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from show_maintainers import get_owner_by_modules @@ -9,7 +9,7 @@ project_dir = "C:/DEV__TX/trunk_tx" -with open('maintainers.txt', 'w', encoding='utf-8') as f: +with open("maintainers.txt", "w", encoding="utf-8") as f: owner_by_modules = get_owner_by_modules(project_dir) lines = [] @@ -17,10 +17,10 @@ modules = owner_by_modules[email] modules.sort() - lines.append(f'{email} ({len(modules)}):') + lines.append(f"{email} ({len(modules)}):") for module in modules: - lines.append(f' {module}') - lines.append('') + lines.append(f" {module}") + lines.append("") - text = '\n'.join(lines).strip() + text = "\n".join(lines).strip() f.write(text) diff --git a/job_compassplus/tx_parse_xml/search_in_locale_files_of_ADS.py b/job_compassplus/tx_parse_xml/search_in_locale_files_of_ADS.py new file mode 100644 index 000000000..710166a3e --- /dev/null +++ b/job_compassplus/tx_parse_xml/search_in_locale_files_of_ADS.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import re +import xml.etree.ElementTree as ET + +from pathlib import Path + + +NS = dict( + ads="http://schemas.radixware.org/adsdef.xsd", +) + + +def get_text(el: ET.Element) -> str: + if el is None: + return "" + return "".join(text for text in el.itertext()) + + +def process(path: str, regexp: str) -> None: + pattern = re.compile(regexp) + + for p in Path(path).glob("*/ads/*/locale/*/mlb*.xml"): + try: + mlb = ET.fromstring(p.read_bytes()) + except Exception: + # Ignore + continue + + items = [] + for string_el in mlb.findall(".//ads:String", namespaces=NS): + string_value_el = string_el.find("./ads:Value", namespaces=NS) + string_value = get_text(string_value_el) + + if pattern.search(string_value): + string_id = string_el.attrib["Id"] + string_lang = string_value_el.attrib["Language"] + + items.append(f"Found with id={string_id} language={string_lang}: {string_value!r}") + + if items: + items.sort() + print(*items, sep="\n") + + +if __name__ == "__main__": + import argparse + + parser = argparse.ArgumentParser( + description="Search in locale" + ) + parser.add_argument( + "path_trunk", + metavar="/PATH/TO/TRUNK", + help="Path to TX source tree (the directory containing branch.xml)", + ) + parser.add_argument( + "regexp", + help="Regular expression", + ) + args = parser.parse_args() + + process(args.path_trunk, args.regexp) diff --git a/job_compassplus/tx_parse_xml/show_SQL_of_ADS_classes.py b/job_compassplus/tx_parse_xml/show_SQL_of_ADS_classes.py new file mode 100644 index 000000000..f57c651b4 --- /dev/null +++ b/job_compassplus/tx_parse_xml/show_SQL_of_ADS_classes.py @@ -0,0 +1,340 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import xml.etree.ElementTree as ET + +from collections import defaultdict +from dataclasses import dataclass +from pathlib import Path + + +@dataclass +class ADS: + title: str + props: dict[str, list[str]] + editors: dict[str, list[str]] + selectors: dict[str, list[str]] + filters: dict[str, list[str]] + sortings: dict[str, list[str]] + + def __bool__(self) -> bool: + return bool( + self.props + or self.editors + or self.selectors + or self.filters + or self.sortings + ) + + +NS = dict( + ads="http://schemas.radixware.org/adsdef.xsd", + xsc="http://schemas.radixware.org/xscml.xsd", +) + + +def has_text(el: ET.Element) -> bool: + return any(text.strip() for text in el.itertext()) + + +def get_xml_object_title(el: ET.Element) -> str: + el_id = el.attrib["Id"] + el_name = el.attrib["Name"] + return f"{el_name}({el_id})" + + +def get_databases_specific_sqml_expression(el: ET.Element, tag_name: str) -> list[str]: + items = [] + if not el: + return items + + specific_sqml_expression_el = el.findall(f"./ads:{tag_name}", namespaces=NS) + for sqml_el in specific_sqml_expression_el: + if sqml_el and has_text(sqml_el.find("./xsc:Content", namespaces=NS)): + database = sqml_el.find("./xsc:Database", namespaces=NS).text + items.append(database) + + return items + + +def get_exists_condition_names(condition_el: ET.Element) -> list[str]: + items = [] + if not condition_el: + return items + + condition_where_el = condition_el.find("./ads:ConditionWhere", namespaces=NS) + if condition_where_el and has_text(condition_where_el): + items.append("ConditionWhere") + + condition_from_el = condition_el.find("./ads:ConditionFrom", namespaces=NS) + if condition_from_el and has_text(condition_from_el): + items.append("ConditionFrom") + + for tag_name in ["SpecificConditionWhere", "SpecificConditionFrom"]: + for database in get_databases_specific_sqml_expression(condition_el, tag_name): + items.append(f"{tag_name}={database}") + + return items + + +def get_ads_filters(ads_el: ET.Element) -> dict[str, list[str]]: + obj_by_items = defaultdict(list) + + for filter_el in ads_el.findall( + "./ads:Presentations/ads:Filters/ads:Filter", namespaces=NS + ): + filter_title = get_xml_object_title(filter_el) + + condition_el = filter_el.find("./ads:Condition", namespaces=NS) + if condition_el and has_text(condition_el): + obj_by_items[filter_title].append("Condition") + + condition_from_el = filter_el.find("./ads:ConditionFrom", namespaces=NS) + if condition_from_el and has_text(condition_from_el): + obj_by_items[filter_title].append("ConditionFrom") + + hint_el = filter_el.find("./ads:Hint", namespaces=NS) + if hint_el and has_text(hint_el): + obj_by_items[filter_title].append("Hint") + + for tag_name in ["SpecificCondition", "SpecificConditionFrom", "SpecificHint"]: + for database in get_databases_specific_sqml_expression(filter_el, tag_name): + obj_by_items[filter_title].append(f"{tag_name}={database}") + + enabled_sorting_el = filter_el.find("./ads:EnabledSorting", namespaces=NS) + if enabled_sorting_el: + enabled_sorting_hint_el = enabled_sorting_el.find( + "./ads:Hint", namespaces=NS + ) + if enabled_sorting_hint_el and has_text(enabled_sorting_hint_el): + obj_by_items[filter_title].append("EnabledSorting/Hint") + + for database in get_databases_specific_sqml_expression( + enabled_sorting_el, "SpecificHint" + ): + obj_by_items[filter_title].append( + f"EnabledSorting/SpecificHint={database}" + ) + + return obj_by_items + + +def get_ads_props(ads_el: ET.Element) -> dict[str, list[str]]: + prop_by_items = defaultdict(list) + + for prop_el in ads_el.findall("./ads:Properties/ads:Property", namespaces=NS): + prop_title = get_xml_object_title(prop_el) + + sqml_expression_el = prop_el.find("./ads:SqmlExpression", namespaces=NS) + if sqml_expression_el and has_text(sqml_expression_el): + prop_by_items[prop_title].append("SqmlExpression") + + for database in get_databases_specific_sqml_expression( + prop_el, "SpecificSqmlExpression" + ): + prop_by_items[prop_title].append(f"SpecificSqmlExpression={database}") + + parent_select_condition_el = prop_el.find( + "./ads:Presentation/ads:ParentSelect/ads:ParentSelectCondition", + namespaces=NS, + ) + for condition_name in get_exists_condition_names(parent_select_condition_el): + prop_by_items[prop_title].append(f"ParentSelect/{condition_name}") + + return prop_by_items + + +def get_ads_editor_presentations(ads_el: ET.Element) -> dict[str, list[str]]: + obj_by_items = defaultdict(list) + + for presentation_el in ads_el.findall( + "./ads:Presentations/ads:EditorPresentations/ads:EditorPresentation", + namespaces=NS, + ): + editor_title = get_xml_object_title(presentation_el) + + for child_item_ref_el in presentation_el.findall( + "./ads:ChildExplorerItems/ads:Item/ads:ChildRef", namespaces=NS + ): + condition_el = child_item_ref_el.find("./ads:Condition", namespaces=NS) + child_sqml_items = get_exists_condition_names(condition_el) + if child_sqml_items: + child_ref_title = get_xml_object_title(child_item_ref_el) + obj_by_items[editor_title].append( + f'{child_ref_title}: {", ".join(child_sqml_items)}' + ) + + for child_item_entity_el in presentation_el.findall( + "./ads:ChildExplorerItems/ads:Item/ads:Entity", namespaces=NS + ): + condition_el = child_item_entity_el.find("./ads:Condition", namespaces=NS) + child_sqml_items = get_exists_condition_names(condition_el) + if child_sqml_items: + child_entity_title = get_xml_object_title(child_item_entity_el) + obj_by_items[editor_title].append( + f'{child_entity_title}: {", ".join(child_sqml_items)}' + ) + + return obj_by_items + + +def get_ads_selector_presentations(ads_el: ET.Element) -> dict[str, list[str]]: + obj_by_items = defaultdict(list) + + for presentation_el in ads_el.findall( + "./ads:Presentations/ads:SelectorPresentations/ads:SelectorPresentation", + namespaces=NS, + ): + selector_title = get_xml_object_title(presentation_el) + + condition_el = presentation_el.find("./ads:Condition", namespaces=NS) + for condition_name in get_exists_condition_names(condition_el): + obj_by_items[selector_title].append(condition_name) + + addons_el = presentation_el.find("./ads:Addons", namespaces=NS) + if addons_el: + default_hint_el = addons_el.find("./ads:DefaultHint", namespaces=NS) + if default_hint_el and has_text(default_hint_el): + obj_by_items[selector_title].append("Addons/DefaultHint") + + specific_hint_el = addons_el.find("./ads:SpecificHint", namespaces=NS) + for database in get_databases_specific_sqml_expression( + specific_hint_el, "SpecificDefaultHint" + ): + obj_by_items[selector_title].append( + f"Addons/SpecificDefaultHint={database}" + ) + + return obj_by_items + + +def get_ads_sortings(ads_el: ET.Element) -> dict[str, list[str]]: + obj_by_items = defaultdict(list) + + sorting_el = ads_el.find( + "./ads:Presentations/ads:Sortings/ads:Sorting", namespaces=NS + ) + if sorting_el: + sorting_title = get_xml_object_title(sorting_el) + + hint_el = sorting_el.find("./ads:Hint", namespaces=NS) + if hint_el and has_text(hint_el): + obj_by_items[sorting_title].append("Hint") + + specific_hint_el = sorting_el.find("./ads:SpecificHint", namespaces=NS) + for database in get_databases_specific_sqml_expression( + specific_hint_el, "SpecificHint" + ): + obj_by_items[sorting_title].append(f"SpecificHint={database}") + + return obj_by_items + + +def get_ads(model_path: Path) -> ADS | None: + model = ET.fromstring(model_path.read_text(encoding="utf-8")) + ads_el = model.find("./ads:AdsClassDefinition", namespaces=NS) + if not ads_el: + return + + return ADS( + title=get_xml_object_title(ads_el), + props=get_ads_props(ads_el), + editors=get_ads_editor_presentations(ads_el), + selectors=get_ads_selector_presentations(ads_el), + filters=get_ads_filters(ads_el), + sortings=get_ads_sortings(ads_el), + ) + + +def get_ads_list(branch_dir: Path | str) -> dict[str, list[ADS]]: + if isinstance(branch_dir, str): + branch_dir = Path(branch_dir) + + if not branch_dir.exists(): + raise Exception(f"Not exists: {branch_dir}") + + layer_module_by_ads_list = defaultdict(list) + + for layer_dir in branch_dir.glob("*"): + if not layer_dir.is_dir(): + continue + + layer_ads_dir = layer_dir / "ads" + if layer_ads_dir.is_dir(): + layer = layer_dir.name + for module_path in layer_ads_dir.glob("*/src"): + module = module_path.parent.name + layer_module = f"{layer}/{module}" + + for class_path in module_path.glob("*.xml"): + if ads := get_ads(class_path): + layer_module_by_ads_list[layer_module].append(ads) + + return layer_module_by_ads_list + + +def process(path: str) -> None: + indent1 = " " + indent2 = indent1 * 2 + indent3 = indent1 * 3 + indent4 = indent1 * 4 + for layer_module, ads_list in get_ads_list(path).items(): + print(layer_module) + + for ads in ads_list: + print(indent1 + ads.title) + + prop_by_sqmls = ads.props + if prop_by_sqmls: + print(indent2 + "Props:") + for prop, items in prop_by_sqmls.items(): + print(indent3 + f'{prop}: {", ".join(items)}') + + editor_by_sqmls = ads.editors + if editor_by_sqmls: + print(indent2 + "Editor presentations:") + for editor, items in editor_by_sqmls.items(): + print(indent3 + f"{editor}:") + for sqml in items: + print(indent4 + f"{sqml}") + + selector_by_sqmls = ads.selectors + if selector_by_sqmls: + print(indent2 + "Selector presentations:") + for selector, items in selector_by_sqmls.items(): + print(indent3 + f'{selector}: {", ".join(items)}') + + filter_by_sqmls = ads.filters + if filter_by_sqmls: + print(indent2 + "Filters:") + for filter_name, items in filter_by_sqmls.items(): + print(indent3 + f'{filter_name}: {", ".join(items)}') + + sorting_by_sqmls = ads.sortings + if sorting_by_sqmls: + print(indent2 + "Sortings:") + for sorting, items in sorting_by_sqmls.items(): + print(indent3 + f'{sorting}: {", ".join(items)}') + + print() + + print("\n" + "-" * 100 + "\n") + + +if __name__ == "__main__": + import argparse + + parser = argparse.ArgumentParser( + description="Shows ADS entities with SQL definitions" + ) + parser.add_argument( + "path_trunk", + metavar="/PATH/TO/TRUNK", + help="Path to TX source tree (the directory containing branch.xml)", + ) + args = parser.parse_args() + + process(args.path_trunk) diff --git a/job_compassplus/tx_parse_xml/show_ads_modules_without_maintainer.py b/job_compassplus/tx_parse_xml/show_ads_modules_without_maintainer.py new file mode 100644 index 000000000..11713a16f --- /dev/null +++ b/job_compassplus/tx_parse_xml/show_ads_modules_without_maintainer.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import xml.etree.ElementTree as ET +from pathlib import Path + + +def get_module_ids_with_maintainter(path_branch: str | Path) -> list[str]: + ns = dict( + p="http://schemas.radixware.org/product.xsd", + ) + + ids: list[str] = [] + + branch = ET.fromstring(path_branch.read_bytes()) + for module_info_el in branch.findall(".//p:ModuleInfo", namespaces=ns): + module_id = module_info_el.find("./p:ModuleId", namespaces=ns).text + has_maintainters = module_info_el.find(".//p:OwnerEmail", namespaces=ns) is not None + if has_maintainters and module_id not in ids: + ids.append(module_id) + + return ids + + +def process(path_layer: str): + if isinstance(path_layer, str): + path_layer = Path(path_layer) + + if not path_layer.exists(): + raise Exception(f"Not exists: {path_layer}") + + path_branch = path_layer.parent / "branch.xml" + if not path_branch.exists(): + raise Exception(f"Not exists: {path_branch}") + + module_ids: list[str] = get_module_ids_with_maintainter(path_branch) + + deprecated_module_without_maintainters: list[tuple[str, str]] = [] + module_without_maintainters: list[tuple[str, str]] = [] + + for path_module in path_layer.glob("ads/*/module.xml"): + module_name = path_module.parent.name + + module_el = ET.fromstring(path_module.read_bytes()) + module_id = module_el.get("Id") + if module_id in module_ids: + continue + + is_deprecated = module_el.get("IsDeprecated") == "true" + if is_deprecated: + deprecated_module_without_maintainters.append((module_name, module_id)) + else: + module_without_maintainters.append((module_name, module_id)) + + print(f"Modules ({len(module_without_maintainters)}):") + for module_name, module_id in module_without_maintainters: + print(f" {module_name} ({module_id})") + + print() + + print(f"Deprecated modules ({len(deprecated_module_without_maintainters)}):") + for module_name, module_id in deprecated_module_without_maintainters: + print(f" {module_name} ({module_id})") + + +if __name__ == "__main__": + import argparse + + parser = argparse.ArgumentParser( + description="Show ads modules without maintainer" + ) + parser.add_argument( + "path_layer", + metavar="/PATH/TO/TRUNK/LAYER", + help="Path to layer (the directory containing layer.xml)", + ) + args = parser.parse_args() + + process(args.path_layer) diff --git a/tx_parse_xml/show_db_columns_tables_with_DefaultVal_as_Expression.py b/job_compassplus/tx_parse_xml/show_db_columns_tables_with_DefaultVal_as_Expression.py similarity index 64% rename from tx_parse_xml/show_db_columns_tables_with_DefaultVal_as_Expression.py rename to job_compassplus/tx_parse_xml/show_db_columns_tables_with_DefaultVal_as_Expression.py index 044504711..3789a9c9d 100644 --- a/tx_parse_xml/show_db_columns_tables_with_DefaultVal_as_Expression.py +++ b/job_compassplus/tx_parse_xml/show_db_columns_tables_with_DefaultVal_as_Expression.py @@ -1,15 +1,14 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" +import xml.etree.ElementTree as ET + from collections import defaultdict from dataclasses import dataclass from pathlib import Path -from typing import Union - -import xml.etree.ElementTree as ET @dataclass @@ -21,41 +20,45 @@ class Column: def get_table_by_columns(model_path: Path) -> dict[str, list[Column]]: ns = dict( - dds='http://schemas.radixware.org/ddsdef.xsd', - com='http://schemas.radixware.org/commondef.xsd', + dds="http://schemas.radixware.org/ddsdef.xsd", + com="http://schemas.radixware.org/commondef.xsd", ) table_by_columns = defaultdict(list) - model = ET.fromstring(model_path.read_text(encoding='utf-8')) - for table in model.findall('.//dds:Tables/dds:Table', namespaces=ns): + model = ET.fromstring(model_path.read_text(encoding="utf-8")) + for table in model.findall(".//dds:Tables/dds:Table", namespaces=ns): table_title = f"{table.attrib['Name']}({table.attrib['Id']})" - for column in table.findall('./dds:Columns/dds:Column', namespaces=ns): - default_value = column.find("./dds:DefaultVal[@Type='Expression']/com:Value", namespaces=ns) + for column in table.findall("./dds:Columns/dds:Column", namespaces=ns): + default_value = column.find( + "./dds:DefaultVal[@Type='Expression']/com:Value", namespaces=ns + ) if default_value is not None: - table_by_columns[table_title].append(Column( - id=column.attrib['Id'], - name=column.attrib['Name'], - default_value=default_value.text.strip(), - )) + table_by_columns[table_title].append( + Column( + id=column.attrib["Id"], + name=column.attrib["Name"], + default_value=default_value.text.strip(), + ) + ) return table_by_columns -def process(branch_dir: Union[Path, str]) -> dict[str, dict[str, list[Column]]]: +def process(branch_dir: Path | str) -> dict[str, dict[str, list[Column]]]: if isinstance(branch_dir, str): branch_dir = Path(branch_dir) layer_module_by_tables = defaultdict(dict) - for layer_dir in branch_dir.glob('*'): + for layer_dir in branch_dir.glob("*"): if not layer_dir.is_dir(): continue - layer_dds_dir = layer_dir / 'dds' + layer_dds_dir = layer_dir / "dds" if layer_dds_dir.is_dir(): layer = layer_dir.name - for model_xml in layer_dds_dir.glob('*/model.xml'): + for model_xml in layer_dds_dir.glob("*/model.xml"): module = model_xml.parent.name for table, columns in get_table_by_columns(model_xml).items(): @@ -66,14 +69,14 @@ def process(branch_dir: Union[Path, str]) -> dict[str, dict[str, list[Column]]]: if __name__ == "__main__": - path = r'C:\DEV__OPTT\trunk_optt' + path = r"C:\DEV__OPTT\trunk_optt" for key, table_by_columns in process(path).items(): - print(f'{key} ({len(table_by_columns)})') + print(f"{key} ({len(table_by_columns)})") for i, (table, columns) in enumerate(table_by_columns.items(), 1): - print(f' {i}. {table}:') + print(f" {i}. {table}:") for column in columns: - print(f' {column.name}({column.id}) = {column.default_value!r}') + print(f" {column.name}({column.id}) = {column.default_value!r}") print() """ com.optt/ProtocolSetup (7) diff --git a/tx_parse_xml/show_db_custom_triggers.py b/job_compassplus/tx_parse_xml/show_db_custom_triggers.py similarity index 50% rename from tx_parse_xml/show_db_custom_triggers.py rename to job_compassplus/tx_parse_xml/show_db_custom_triggers.py index c1e0fdfe8..c0e63725e 100644 --- a/tx_parse_xml/show_db_custom_triggers.py +++ b/job_compassplus/tx_parse_xml/show_db_custom_triggers.py @@ -1,15 +1,14 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" +import xml.etree.ElementTree as ET + from collections import defaultdict from dataclasses import dataclass from pathlib import Path -from typing import Union - -import xml.etree.ElementTree as ET @dataclass @@ -21,39 +20,43 @@ class Trigger: def get_triggers(model_path: Path) -> list[Trigger]: ns = dict( - dds='http://schemas.radixware.org/ddsdef.xsd', + dds="http://schemas.radixware.org/ddsdef.xsd", ) items = [] - model = ET.fromstring(model_path.read_text(encoding='utf-8')) - for table in model.findall('.//dds:Tables/dds:Table', namespaces=ns): - for trigger in table.findall('./dds:Triggers/dds:Trigger', namespaces=ns): - if trigger.attrib.get('Type'): # При True - триггер был создан автоматически радиксом + model = ET.fromstring(model_path.read_text(encoding="utf-8")) + for table in model.findall(".//dds:Tables/dds:Table", namespaces=ns): + for trigger in table.findall("./dds:Triggers/dds:Trigger", namespaces=ns): + if trigger.attrib.get( + "Type" + ): # При True - триггер был создан автоматически радиксом continue - items.append(Trigger( - table_name=table.attrib['Name'], - name=trigger.attrib['Name'], - db_name=trigger.attrib['DbName'], - )) + items.append( + Trigger( + table_name=table.attrib["Name"], + name=trigger.attrib["Name"], + db_name=trigger.attrib["DbName"], + ) + ) return items -def process(branch_dir: Union[Path, str]) -> dict[str, list[Trigger]]: +def process(branch_dir: Path | str) -> dict[str, list[Trigger]]: if isinstance(branch_dir, str): branch_dir = Path(branch_dir) layer_module_by_triggers = defaultdict(list) - for layer_dir in branch_dir.glob('*'): + for layer_dir in branch_dir.glob("*"): if not layer_dir.is_dir(): continue - layer_dds_dir = layer_dir / 'dds' + layer_dds_dir = layer_dir / "dds" if layer_dds_dir.is_dir(): layer = layer_dir.name - for model_xml in layer_dds_dir.glob('*/model.xml'): + for model_xml in layer_dds_dir.glob("*/model.xml"): module = model_xml.parent.name for trigger in get_triggers(model_xml): @@ -64,11 +67,11 @@ def process(branch_dir: Union[Path, str]) -> dict[str, list[Trigger]]: if __name__ == "__main__": - path = r'C:\DEV__OPTT\trunk_optt' + path = r"C:\DEV__OPTT\trunk_optt" layer_module_by_triggers = process(path) for key, triggers in layer_module_by_triggers.items(): - print(f'{key} ({len(triggers)})') + print(f"{key} ({len(triggers)})") for i, trigger in enumerate(triggers, 1): - print(f' {i}. {trigger.table_name}. {trigger.name} ({trigger.db_name})') + print(f" {i}. {trigger.table_name}. {trigger.name} ({trigger.db_name})") print() diff --git a/tx_parse_xml/show_db_tables_with_ExcludeTargetDatabases.py b/job_compassplus/tx_parse_xml/show_db_tables_with_ExcludeTargetDatabases.py similarity index 67% rename from tx_parse_xml/show_db_tables_with_ExcludeTargetDatabases.py rename to job_compassplus/tx_parse_xml/show_db_tables_with_ExcludeTargetDatabases.py index d1db8ca7d..d2fd6eb44 100644 --- a/tx_parse_xml/show_db_tables_with_ExcludeTargetDatabases.py +++ b/job_compassplus/tx_parse_xml/show_db_tables_with_ExcludeTargetDatabases.py @@ -1,14 +1,13 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" +import xml.etree.ElementTree as ET + from collections import defaultdict from pathlib import Path -from typing import Union - -import xml.etree.ElementTree as ET # NOTE: ExcludeTargetDatabases еще присутствует во множестве объектов таблицы: индексы, триггеры, столбцы и т.п. @@ -17,33 +16,33 @@ def get_tables(model_path: Path) -> list[str]: ns = dict( - dds='http://schemas.radixware.org/ddsdef.xsd', + dds="http://schemas.radixware.org/ddsdef.xsd", ) items = [] - model = ET.fromstring(model_path.read_text(encoding='utf-8')) - for table in model.findall('.//dds:Tables/dds:Table', namespaces=ns): - if table.attrib['ExcludeTargetDatabases']: + model = ET.fromstring(model_path.read_text(encoding="utf-8")) + for table in model.findall(".//dds:Tables/dds:Table", namespaces=ns): + if table.attrib["ExcludeTargetDatabases"]: title = f"{table.attrib['Name']}({table.attrib['Id']})" items.append(title) return items -def process(branch_dir: Union[Path, str]) -> dict[str, list[str]]: +def process(branch_dir: Path | str) -> dict[str, list[str]]: if isinstance(branch_dir, str): branch_dir = Path(branch_dir) layer_module_by_tables = defaultdict(list) - for layer_dir in branch_dir.glob('*'): + for layer_dir in branch_dir.glob("*"): if not layer_dir.is_dir(): continue - layer_dds_dir = layer_dir / 'dds' + layer_dds_dir = layer_dir / "dds" if layer_dds_dir.is_dir(): layer = layer_dir.name - for model_xml in layer_dds_dir.glob('*/model.xml'): + for model_xml in layer_dds_dir.glob("*/model.xml"): module = model_xml.parent.name for table in get_tables(model_xml): @@ -54,10 +53,10 @@ def process(branch_dir: Union[Path, str]) -> dict[str, list[str]]: if __name__ == "__main__": - path = r'C:\DEV__OPTT\trunk_optt' + path = r"C:\DEV__OPTT\trunk_optt" for key, tables in process(path).items(): - print(f'{key} ({len(tables)})') + print(f"{key} ({len(tables)})") for i, table in enumerate(tables, 1): - print(f' {i}. {table}') + print(f" {i}. {table}") print() diff --git a/tx_parse_xml/show_db_views.py b/job_compassplus/tx_parse_xml/show_db_views.py similarity index 58% rename from tx_parse_xml/show_db_views.py rename to job_compassplus/tx_parse_xml/show_db_views.py index c943144fa..46755d2cb 100644 --- a/tx_parse_xml/show_db_views.py +++ b/job_compassplus/tx_parse_xml/show_db_views.py @@ -1,15 +1,14 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" +import xml.etree.ElementTree as ET + from collections import defaultdict from dataclasses import dataclass from pathlib import Path -from typing import Union - -import xml.etree.ElementTree as ET @dataclass @@ -21,35 +20,37 @@ class View: def get_views(model_path: Path) -> list[View]: ns = dict( - dds='http://schemas.radixware.org/ddsdef.xsd', + dds="http://schemas.radixware.org/ddsdef.xsd", ) items = [] - model = ET.fromstring(model_path.read_text(encoding='utf-8')) - for view in model.findall('.//dds:Views/dds:View', namespaces=ns): - items.append(View( - id=view.attrib['Id'], - name=view.attrib['Name'], - db_name=view.attrib['DbName'], - )) + model = ET.fromstring(model_path.read_text(encoding="utf-8")) + for view in model.findall(".//dds:Views/dds:View", namespaces=ns): + items.append( + View( + id=view.attrib["Id"], + name=view.attrib["Name"], + db_name=view.attrib["DbName"], + ) + ) return items -def process(branch_dir: Union[Path, str]) -> dict[str, list[View]]: +def process(branch_dir: Path | str) -> dict[str, list[View]]: if isinstance(branch_dir, str): branch_dir = Path(branch_dir) layer_module_by_triggers = defaultdict(list) - for layer_dir in branch_dir.glob('*'): + for layer_dir in branch_dir.glob("*"): if not layer_dir.is_dir(): continue - layer_dds_dir = layer_dir / 'dds' + layer_dds_dir = layer_dir / "dds" if layer_dds_dir.is_dir(): layer = layer_dir.name - for model_xml in layer_dds_dir.glob('*/model.xml'): + for model_xml in layer_dds_dir.glob("*/model.xml"): module = model_xml.parent.name for view in get_views(model_xml): @@ -60,11 +61,11 @@ def process(branch_dir: Union[Path, str]) -> dict[str, list[View]]: if __name__ == "__main__": - path = r'C:\DEV__OPTT\trunk_optt' + path = r"C:\DEV__OPTT\trunk_optt" layer_module_by_triggers = process(path) for key, views in layer_module_by_triggers.items(): - print(f'{key} ({len(views)})') + print(f"{key} ({len(views)})") for i, view in enumerate(views, 1): - print(f' {i}. {view.name} ({view.db_name}, {view.id})') + print(f" {i}. {view.name} ({view.db_name}, {view.id})") print() diff --git a/job_compassplus/tx_parse_xml/show_duplicates_SQL_of_ADS_classes.py b/job_compassplus/tx_parse_xml/show_duplicates_SQL_of_ADS_classes.py new file mode 100644 index 000000000..4cd63df91 --- /dev/null +++ b/job_compassplus/tx_parse_xml/show_duplicates_SQL_of_ADS_classes.py @@ -0,0 +1,384 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import re +import xml.etree.ElementTree as ET + +from collections import defaultdict +from dataclasses import dataclass +from pathlib import Path + + +@dataclass +class ADS: + title: str + props: dict[str, list[str]] + editors: dict[str, list[str]] + selectors: dict[str, list[str]] + filters: dict[str, list[str]] + sortings: dict[str, list[str]] + + def __bool__(self) -> bool: + return bool( + self.props + or self.editors + or self.selectors + or self.filters + or self.sortings + ) + + +NS = dict( + ads="http://schemas.radixware.org/adsdef.xsd", + xsc="http://schemas.radixware.org/xscml.xsd", +) + + +def get_text(el: ET.Element) -> str: + return "".join(text.strip() for text in el.itertext()) + + +def get_xml_object_title(el: ET.Element) -> str: + el_id = el.attrib["Id"] + el_name = el.attrib["Name"] + return f"{el_name}({el_id})" + + +def get_databases_specific_sqml_expression( + el: ET.Element, tag_name: str +) -> list[tuple[str, str]]: + items = [] + if not el: + return items + + specific_sqml_expression_el = el.findall(f"./ads:{tag_name}", namespaces=NS) + for sqml_el in specific_sqml_expression_el: + if sqml_el: + content_el = sqml_el.find("./xsc:Content", namespaces=NS) + sqml_text = get_sqml_text(content_el) + if sqml_text: + database = sqml_el.find("./xsc:Database", namespaces=NS).text + items.append((database, sqml_text)) + + return items + + +def process_sqml_text(text: str) -> str: + return re.sub(r"\s", "", text).lower() + + +def get_sqml_text(el: ET.Element) -> str: + if not el: + return "" + + lines = [] + for item in el.iterfind("./xsc:Item", namespaces=NS): + for child in item: + if child.tag.endswith("Sql"): + if inner_text := get_text(child): + # Пропуск комментариев + if not inner_text.startswith("--"): + lines.append(process_sqml_text(inner_text)) + else: + # Например: [('TableId', 'tbl42K4K2TTGLNRDHRZABQAQH3XQ4'), ('PropId', 'colXJ7GB2ILWZGAVHRCJAZCMCM6E4'), ('Owner', 'CHILD')] + if child.items(): + if owner_type := child.get("Owner"): + attr_str = ( + owner_type + + "." + + ".".join(v for k, v in child.items() if k != "Owner") + ) + else: + attr_str = ",".join(f"{k}={v}" for k, v in child.items()) + lines.append(f"[{attr_str}]") + + return "".join(lines) + + +def get_exists_condition_sqml_text(condition_el: ET.Element) -> list[str]: + items = [] + if not condition_el: + return items + + condition_where_el = condition_el.find("./ads:ConditionWhere", namespaces=NS) + if sqml_text := get_sqml_text(condition_where_el): + items.append(sqml_text) + + condition_from_el = condition_el.find("./ads:ConditionFrom", namespaces=NS) + if sqml_text := get_sqml_text(condition_from_el): + items.append(sqml_text) + + for tag_name in ["SpecificConditionWhere", "SpecificConditionFrom"]: + for database, sqml_text in get_databases_specific_sqml_expression( + condition_el, tag_name + ): + items.append(f"{database}:{sqml_text}") + + return items + + +def get_ads_filters(ads_el: ET.Element) -> dict[str, list[str]]: + obj_by_items = defaultdict(list) + + for filter_el in ads_el.findall( + "./ads:Presentations/ads:Filters/ads:Filter", namespaces=NS + ): + filter_title = get_xml_object_title(filter_el) + + condition_el = filter_el.find("./ads:Condition", namespaces=NS) + if sqml_text := get_sqml_text(condition_el): + obj_by_items[filter_title].append(sqml_text) + + condition_from_el = filter_el.find("./ads:ConditionFrom", namespaces=NS) + if sqml_text := get_sqml_text(condition_from_el): + obj_by_items[filter_title].append(sqml_text) + + hint_el = filter_el.find("./ads:Hint", namespaces=NS) + if sqml_text := get_sqml_text(hint_el): + obj_by_items[filter_title].append(sqml_text) + + for tag_name in ["SpecificCondition", "SpecificConditionFrom", "SpecificHint"]: + for database, sqml_text in get_databases_specific_sqml_expression( + filter_el, tag_name + ): + obj_by_items[filter_title].append(f"{database}:{sqml_text}") + + enabled_sorting_el = filter_el.find("./ads:EnabledSorting", namespaces=NS) + if enabled_sorting_el: + enabled_sorting_hint_el = enabled_sorting_el.find( + "./ads:Hint", namespaces=NS + ) + if sqml_text := get_sqml_text(enabled_sorting_hint_el): + obj_by_items[filter_title].append(sqml_text) + + for database, sqml_text in get_databases_specific_sqml_expression( + enabled_sorting_el, "SpecificHint" + ): + obj_by_items[filter_title].append(f"{database}:{sqml_text}") + + return obj_by_items + + +def get_ads_props(ads_el: ET.Element) -> dict[str, list[str]]: + prop_by_items = defaultdict(list) + + for prop_el in ads_el.findall("./ads:Properties/ads:Property", namespaces=NS): + prop_title = get_xml_object_title(ads_el) + "/" + get_xml_object_title(prop_el) + + sqml_expression_el = prop_el.find("./ads:SqmlExpression", namespaces=NS) + if sqml_text := get_sqml_text(sqml_expression_el): + prop_by_items[prop_title].append(sqml_text) + + for database, sqml_text in get_databases_specific_sqml_expression( + prop_el, "SpecificSqmlExpression" + ): + prop_by_items[prop_title].append(f"{database}:{sqml_text}") + + parent_select_condition_el = prop_el.find( + "./ads:Presentation/ads:ParentSelect/ads:ParentSelectCondition", + namespaces=NS, + ) + for sqml_text in get_exists_condition_sqml_text(parent_select_condition_el): + prop_by_items[prop_title].append(sqml_text) + + return prop_by_items + + +def get_ads_editor_presentations(ads_el: ET.Element) -> dict[str, list[str]]: + obj_by_items = defaultdict(list) + + for presentation_el in ads_el.findall( + "./ads:Presentations/ads:EditorPresentations/ads:EditorPresentation", + namespaces=NS, + ): + editor_title = get_xml_object_title(presentation_el) + + for child_item_ref_el in presentation_el.findall( + "./ads:ChildExplorerItems/ads:Item/ads:ChildRef", namespaces=NS + ): + condition_el = child_item_ref_el.find("./ads:Condition", namespaces=NS) + child_sqml_items = get_exists_condition_sqml_text(condition_el) + if child_sqml_items: + child_ref_title = get_xml_object_title(child_item_ref_el) + + obj_by_items[editor_title].append( + f'{editor_title}/{child_ref_title}: {", ".join(child_sqml_items)}' + ) + + for child_item_entity_el in presentation_el.findall( + "./ads:ChildExplorerItems/ads:Item/ads:Entity", namespaces=NS + ): + condition_el = child_item_entity_el.find("./ads:Condition", namespaces=NS) + child_sqml_items = get_exists_condition_sqml_text(condition_el) + if child_sqml_items: + child_entity_title = get_xml_object_title(child_item_entity_el) + obj_by_items[editor_title].append( + f'{editor_title}/{child_entity_title}: {", ".join(child_sqml_items)}' + ) + + return obj_by_items + + +def get_ads_selector_presentations(ads_el: ET.Element) -> dict[str, list[str]]: + obj_by_items = defaultdict(list) + + for presentation_el in ads_el.findall( + "./ads:Presentations/ads:SelectorPresentations/ads:SelectorPresentation", + namespaces=NS, + ): + selector_title = get_xml_object_title(presentation_el) + + condition_el = presentation_el.find("./ads:Condition", namespaces=NS) + for sqml_text in get_exists_condition_sqml_text(condition_el): + obj_by_items[selector_title].append(sqml_text) + + addons_el = presentation_el.find("./ads:Addons", namespaces=NS) + if addons_el: + default_hint_el = addons_el.find("./ads:DefaultHint", namespaces=NS) + if sqml_text := get_sqml_text(default_hint_el): + obj_by_items[selector_title].append(sqml_text) + + specific_hint_el = addons_el.find("./ads:SpecificHint", namespaces=NS) + for database, sqml_text in get_databases_specific_sqml_expression( + specific_hint_el, "SpecificDefaultHint" + ): + obj_by_items[selector_title].append(f"{database}:{sqml_text}") + + return obj_by_items + + +def get_ads_sortings(ads_el: ET.Element) -> dict[str, list[str]]: + obj_by_items = defaultdict(list) + + sorting_el = ads_el.find( + "./ads:Presentations/ads:Sortings/ads:Sorting", namespaces=NS + ) + if sorting_el: + sorting_title = get_xml_object_title(sorting_el) + + hint_el = sorting_el.find("./ads:Hint", namespaces=NS) + if sqml_text := get_sqml_text(hint_el): + obj_by_items[sorting_title].append(sqml_text) + + specific_hint_el = sorting_el.find("./ads:SpecificHint", namespaces=NS) + for database, sqml_text in get_databases_specific_sqml_expression( + specific_hint_el, "SpecificHint" + ): + obj_by_items[sorting_title].append(f"{database}:{sqml_text}") + + return obj_by_items + + +def get_ads(model_path: Path) -> ADS | None: + model = ET.fromstring(model_path.read_text(encoding="utf-8")) + ads_el = model.find("./ads:AdsClassDefinition", namespaces=NS) + if not ads_el: + return + + return ADS( + title=get_xml_object_title(ads_el), + props=get_ads_props(ads_el), + editors=get_ads_editor_presentations(ads_el), + selectors=get_ads_selector_presentations(ads_el), + filters=get_ads_filters(ads_el), + sortings=get_ads_sortings(ads_el), + ) + + +def get_ads_list(branch_dir: Path | str) -> dict[str, list[ADS]]: + if isinstance(branch_dir, str): + branch_dir = Path(branch_dir) + + if not branch_dir.exists(): + raise Exception(f"Not exists: {branch_dir}") + + layer_module_by_ads_list = defaultdict(list) + + for layer_dir in branch_dir.glob("*"): + if not layer_dir.is_dir(): + continue + + layer_ads_dir = layer_dir / "ads" + if layer_ads_dir.is_dir(): + layer = layer_dir.name + for module_path in layer_ads_dir.glob("*/src"): + module = module_path.parent.name + layer_module = f"{layer}/{module}" + + for class_path in module_path.glob("*.xml"): + if ads := get_ads(class_path): + layer_module_by_ads_list[layer_module].append(ads) + + return layer_module_by_ads_list + + +def process(path: str) -> None: + indent1 = " " + indent2 = indent1 * 2 + + sqml_text_by_ids: dict[str, list[str]] = defaultdict(list) + + for layer_module, ads_list in get_ads_list(path).items(): + for ads in ads_list: + prop_by_sqmls = ads.props + if prop_by_sqmls: + for prop, items in prop_by_sqmls.items(): + for sqml_text in items: + sqml_text_by_ids[sqml_text].append(prop) + + editor_by_sqmls = ads.editors + if editor_by_sqmls: + for editor, items in editor_by_sqmls.items(): + for sqml in items: + id_str, sqml_text = sqml.split(": ") + sqml_text_by_ids[sqml_text].append(id_str) + + selector_by_sqmls = ads.selectors + if selector_by_sqmls: + for selector, items in selector_by_sqmls.items(): + for sqml_text in items: + sqml_text_by_ids[sqml_text].append(selector) + + filter_by_sqmls = ads.filters + if filter_by_sqmls: + for filter_name, items in filter_by_sqmls.items(): + for sqml_text in items: + sqml_text_by_ids[sqml_text].append(filter_name) + + sorting_by_sqmls = ads.sortings + if sorting_by_sqmls: + for sorting, items in sorting_by_sqmls.items(): + for sqml_text in items: + sqml_text_by_ids[sqml_text].append(sorting) + + for sqml_text, ids in sorted( + sqml_text_by_ids.items(), key=lambda x: len(x[1]), reverse=True + ): + # Если меньше 5, то пропускаем + if len(ids) < 5: + continue + + print(f"Sql: {sqml_text}") + print(f"{indent1}{len(ids)}:") + for id_str in ids: + print(f"{indent2}{id_str}") + print() + + +if __name__ == "__main__": + import argparse + + parser = argparse.ArgumentParser( + description="Shows ADS entities with duplicates SQL definitions" + ) + parser.add_argument( + "path_trunk", + metavar="/PATH/TO/TRUNK", + help="Path to TX source tree (the directory containing branch.xml)", + ) + args = parser.parse_args() + + process(args.path_trunk) diff --git a/tx_parse_xml/show_duplicates_of_branch_xml.py b/job_compassplus/tx_parse_xml/show_duplicates_of_branch_xml.py similarity index 57% rename from tx_parse_xml/show_duplicates_of_branch_xml.py rename to job_compassplus/tx_parse_xml/show_duplicates_of_branch_xml.py index 696e05340..8599ced08 100644 --- a/tx_parse_xml/show_duplicates_of_branch_xml.py +++ b/job_compassplus/tx_parse_xml/show_duplicates_of_branch_xml.py @@ -1,49 +1,45 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from collections import Counter from pathlib import Path -from typing import Union from bs4 import BeautifulSoup -def get_duplicates(project_dir: Union[str, Path]) -> dict[str, int]: +def get_duplicates(project_dir: str | Path) -> dict[str, int]: if isinstance(project_dir, str): project_dir = Path(project_dir) file_name_branch = project_dir / "branch.xml" - with open(file_name_branch, encoding='utf-8') as f: - root = BeautifulSoup(f, 'html.parser') + with open(file_name_branch, encoding="utf-8") as f: + root = BeautifulSoup(f, "html.parser") items = [] - for module in root.select('moduleinfo'): + for module in root.select("moduleinfo"): layer_url = module.layerurl.text module_id = module.moduleid.text - name = f'{layer_url}-{module_id}' + name = f"{layer_url}-{module_id}" definition_path = None if module.definition: - definition_path = module.definition.get('path') + definition_path = module.definition.get("path") if definition_path and definition_path != module_id: name += f': {module.definition["path"]}' items.append(name) - return { - name: number - for name, number in Counter(items).items() if number > 1 - } + return {name: number for name, number in Counter(items).items() if number > 1} -if __name__ == '__main__': +if __name__ == "__main__": project_dir = "C:/DEV__TX/trunk_tx" duplicates = get_duplicates(project_dir) for name, number in sorted(duplicates.items(), reverse=True): - print(f'{name} = {number}') + print(f"{name} = {number}") diff --git a/tx_parse_xml/show_func_tags_service_bus.py b/job_compassplus/tx_parse_xml/show_func_tags_service_bus.py similarity index 93% rename from tx_parse_xml/show_func_tags_service_bus.py rename to job_compassplus/tx_parse_xml/show_func_tags_service_bus.py index ee10d19ce..53ee5c9bc 100644 --- a/tx_parse_xml/show_func_tags_service_bus.py +++ b/job_compassplus/tx_parse_xml/show_func_tags_service_bus.py @@ -1,35 +1,43 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from collections import defaultdict from bs4 import BeautifulSoup, Tag -file_name = r'C:\DEV__TX\trunk_tx\com.tranzaxis\uds\Interfacing.Examples\etc\DinersClub\DinersClub.xml' +file_name = r"C:\DEV__TX\trunk_tx\com.tranzaxis\uds\Interfacing.Examples\etc\DinersClub\DinersClub.xml" -root = BeautifulSoup(open(file_name), 'xml') +root = BeautifulSoup(open(file_name), "xml") def get_func_title(func_el: Tag) -> str: - description = func_el.Description.text if func_el.Description else '' + description = func_el.Description.text if func_el.Description else "" attrs_title = ", ".join(f"{k}={v}" for k, v in func_el.attrs.items()) - return f'{attrs_title}. Description: {description}' + return f"{attrs_title}. Description: {description}" def get_funcs_from_user_funcs(node_el: Tag) -> list[Tag]: try: - return [func_el for func_el in node_el.find('UserFuncs', recursive=False).find_all('Func', recursive=False)] + return [ + func_el + for func_el in node_el.find("UserFuncs", recursive=False).find_all( + "Func", recursive=False + ) + ] except: return [] def get_funcs_from_user_props(node_el: Tag) -> list[Tag]: try: - return [func_el for func_el in node_el.find('UserProps', recursive=False).find_all('Func')] + return [ + func_el + for func_el in node_el.find("UserProps", recursive=False).find_all("Func") + ] except: return [] @@ -37,14 +45,14 @@ def get_funcs_from_user_props(node_el: Tag) -> list[Tag]: node_by_funcs = defaultdict(list) nodes = [] -if pipeline_nodes := root.find('PipelineNodes'): - nodes += pipeline_nodes.find_all('Node') -if other_nodes := root.find('OtherNodes'): - nodes += other_nodes.find_all('Node') +if pipeline_nodes := root.find("PipelineNodes"): + nodes += pipeline_nodes.find_all("Node") +if other_nodes := root.find("OtherNodes"): + nodes += other_nodes.find_all("Node") for node_el in nodes: - node_title = node_el.Title.text if node_el.Title else '' - node_class_id = node_el['ClassId'] + node_title = node_el.Title.text if node_el.Title else "" + node_class_id = node_el["ClassId"] parent_title = f"{node_el.name} (Title: {node_title}. ClassId: {node_el['ClassId']}, EntityPid: {node_el['EntityPid']})" @@ -59,7 +67,9 @@ def get_funcs_from_user_props(node_el: Tag) -> list[Tag]: node_by_funcs[parent_title].append(func_title) try: - for stage_el in node_el.TransformStages.find_all('TransformStage', recursive=False): + for stage_el in node_el.TransformStages.find_all( + "TransformStage", recursive=False + ): parent_title = f"{node_el.name}/Stage#{stage_el.Seq.text} (ClassId: {stage_el['ClassId']}, NodeTitle: {node_title}, NodeClassId: {node_el['ClassId']}, NodeEntityPid: {node_el['EntityPid']})" for func_el in get_funcs_from_user_funcs(stage_el): @@ -75,9 +85,9 @@ def get_funcs_from_user_props(node_el: Tag) -> list[Tag]: i = 1 for node_title, funcs in node_by_funcs.items(): - print(f'{node_title} ({len(funcs)}):') + print(f"{node_title} ({len(funcs)}):") for func_title in funcs: - print(f' {i}. {func_title}') + print(f" {i}. {func_title}") i += 1 print() diff --git a/tx_parse_xml/show_maintainers.py b/job_compassplus/tx_parse_xml/show_maintainers.py similarity index 51% rename from tx_parse_xml/show_maintainers.py rename to job_compassplus/tx_parse_xml/show_maintainers.py index 3c1bbcc77..e45d69121 100644 --- a/tx_parse_xml/show_maintainers.py +++ b/job_compassplus/tx_parse_xml/show_maintainers.py @@ -1,54 +1,57 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from collections import defaultdict from pathlib import Path -from typing import Union from bs4 import BeautifulSoup -def parse_xml_partially(path: Union[str, Path], line_contains: list) -> BeautifulSoup: +def parse_xml_partially(path: str | Path, line_contains: list) -> BeautifulSoup: # Считаем только ту часть XML, где имеются необходимые атрибуты xml_part = [] - for line in open(path, encoding='utf-8'): + for line in open(path, encoding="utf-8"): xml_part.append(line) if all(x in line for x in line_contains): break - return BeautifulSoup('\n'.join(xml_part), 'html.parser') + return BeautifulSoup("\n".join(xml_part), "html.parser") -def get_owner_by_modules(project_dir: Union[str, Path]) -> dict[str, list[str]]: +def get_owner_by_modules(project_dir: str | Path) -> dict[str, list[str]]: if isinstance(project_dir, str): project_dir = Path(project_dir) layer_url_by_name: dict[str, str] = dict() layer_module_id_by_name: dict[tuple[str, str], str] = dict() - for layer_path in project_dir.glob('*/layer.xml'): - root_layer = parse_xml_partially(layer_path, line_contains=['Layer ', ' Uri', ' Name']) - layer_url = root_layer.layer['uri'] - name = root_layer.layer['name'] + for layer_path in project_dir.glob("*/layer.xml"): + root_layer = parse_xml_partially( + layer_path, line_contains=["Layer ", " Uri", " Name"] + ) + layer_url = root_layer.layer["uri"] + name = root_layer.layer["name"] layer_url_by_name[layer_url] = name - for path in layer_path.parent.glob('ads/*/module.xml'): - root_module = parse_xml_partially(path, line_contains=['Module ', ' Id', ' Name']) - module_id = root_module.module['id'] + for path in layer_path.parent.glob("ads/*/module.xml"): + root_module = parse_xml_partially( + path, line_contains=["Module ", " Id", " Name"] + ) + module_id = root_module.module["id"] - layer_module_id_by_name[layer_url, module_id] = root_module.module['name'] + layer_module_id_by_name[layer_url, module_id] = root_module.module["name"] file_name_branch = project_dir / "branch.xml" - with open(file_name_branch, encoding='utf-8') as f: - root = BeautifulSoup(f, 'html.parser') + with open(file_name_branch, encoding="utf-8") as f: + root = BeautifulSoup(f, "html.parser") owner_by_modules = defaultdict(list) - for module in root.select('moduleinfo'): + for module in root.select("moduleinfo"): layer_url = module.layerurl.text layer_name = layer_url_by_name[layer_url] @@ -57,14 +60,14 @@ def get_owner_by_modules(project_dir: Union[str, Path]) -> dict[str, list[str]]: definition_path_id = None if module.definition: - definition_path_id = module.definition.get('path') + definition_path_id = module.definition.get("path") if definition_path_id and definition_path_id != module_id: - title = f'{layer_name}::{module_name}/{definition_path_id} ({module_id}/{definition_path_id})' + title = f"{layer_name}::{module_name}/{definition_path_id} ({module_id}/{definition_path_id})" else: - title = f'{layer_name}::{module_name} ({module_id})' + title = f"{layer_name}::{module_name} ({module_id})" - owner_emails = [el.text for el in module.select('owneremail')] + owner_emails = [el.text for el in module.select("owneremail")] for email in owner_emails: if title not in owner_by_modules[email]: owner_by_modules[email].append(title) @@ -72,15 +75,15 @@ def get_owner_by_modules(project_dir: Union[str, Path]) -> dict[str, list[str]]: return owner_by_modules -if __name__ == '__main__': - project_dir = "C:/DEV__TX/trunk_tx" +if __name__ == "__main__": + project_dir = "C:/DEV__TX/trunk" owner_by_modules = get_owner_by_modules(project_dir) for email in sorted(owner_by_modules): modules = owner_by_modules[email] modules.sort() - print(f'{email} ({len(modules)}):') + print(f"{email} ({len(modules)}):") for module in modules: - print(f' {module}') + print(f" {module}") print() diff --git a/tx_remove_hs_err_pid_log/main.py b/job_compassplus/tx_remove_hs_err_pid_log/main.py similarity index 51% rename from tx_remove_hs_err_pid_log/main.py rename to job_compassplus/tx_remove_hs_err_pid_log/main.py index 99399ffb7..93083d911 100644 --- a/tx_remove_hs_err_pid_log/main.py +++ b/job_compassplus/tx_remove_hs_err_pid_log/main.py @@ -1,26 +1,25 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -import datetime as DT +import datetime as dt import logging import time import sys from logging.handlers import RotatingFileHandler from pathlib import Path -from typing import Union def get_logger( - name=__file__, - file: Union[str, Path] = 'log.txt', - formatter: str = '[%(asctime)s] %(filename)s:%(lineno)d %(levelname)-8s %(message)s', - encoding='utf-8', - log_stdout=True, - log_file=True, + name=__file__, + file: str | Path = "log.txt", + formatter: str = "[%(asctime)s] %(filename)s:%(lineno)d %(levelname)-8s %(message)s", + encoding="utf-8", + log_stdout=True, + log_file=True, ): log = logging.getLogger(name) log.setLevel(logging.DEBUG) @@ -28,7 +27,9 @@ def get_logger( formatter = logging.Formatter(formatter) if log_file: - fh = RotatingFileHandler(file, maxBytes=10000000, backupCount=5, encoding=encoding) + fh = RotatingFileHandler( + file, maxBytes=10_000_000, backupCount=5, encoding=encoding + ) fh.setFormatter(formatter) log.addHandler(fh) @@ -43,34 +44,40 @@ def get_logger( DIR = Path(__file__).resolve().parent log = get_logger( - file=DIR / 'deleted.txt', - formatter='[%(asctime)s] %(message)s', + file=DIR / "deleted.txt", + formatter="[%(asctime)s] %(message)s", ) -DIRS = [r'C:\DEV__TX', r'C:\DEV__OPTT', r'C:\DEV__RADIX'] +DIRS = [r"C:\DEV__TX", r"C:\DEV__OPTT", r"C:\DEV__RADIX"] -def run(dirs: list[Union[str, Path]]): - print(f'\n{DT.datetime.today()}') +def run(dirs: list[str | Path]) -> None: + print(f"\nStarted {dt.datetime.today()}") for dir_path in dirs: print(dir_path) - for file_name in Path(dir_path).glob('*/hs_err_pid*.log'): + for file_name in Path(dir_path).glob("*/hs_err_pid*.log"): ctime_timestamp = file_name.stat().st_ctime - ctime = DT.datetime.fromtimestamp(ctime_timestamp) + ctime = dt.datetime.fromtimestamp(ctime_timestamp) ctime = ctime.replace(microsecond=0) - text = f'{file_name} (date creation: {ctime})' + text = f"{file_name} (date creation: {ctime})" print(text) # Удаление, если с даты создания прошло больше 1 часа - if DT.datetime.today() > ctime + DT.timedelta(hours=1): + if dt.datetime.today() > ctime + dt.timedelta(hours=1): log.info(text) file_name.unlink(missing_ok=True) + print(f"\nFinished {dt.datetime.today()}") + + +if __name__ == "__main__": + if "--one" in sys.argv: + run(DIRS) + sys.exit() -if __name__ == '__main__': while True: run(DIRS) time.sleep(2 * 60 * 60) diff --git a/job_compassplus/tx_remove_java_pid_hprof/main.py b/job_compassplus/tx_remove_java_pid_hprof/main.py new file mode 100644 index 000000000..3d84d5697 --- /dev/null +++ b/job_compassplus/tx_remove_java_pid_hprof/main.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import datetime as dt +import logging +import time +import sys + +from logging.handlers import RotatingFileHandler +from pathlib import Path + +# pip install humanize +from humanize import naturalsize as sizeof_fmt + + +def get_logger( + name=__file__, + file: str | Path = "log.txt", + formatter: str = "[%(asctime)s] %(filename)s:%(lineno)d %(levelname)-8s %(message)s", + encoding="utf-8", + log_stdout=True, + log_file=True, +): + log = logging.getLogger(name) + log.setLevel(logging.DEBUG) + + formatter = logging.Formatter(formatter) + + if log_file: + fh = RotatingFileHandler( + file, maxBytes=10_000_000, backupCount=5, encoding=encoding + ) + fh.setFormatter(formatter) + log.addHandler(fh) + + if log_stdout: + sh = logging.StreamHandler(stream=sys.stdout) + sh.setFormatter(formatter) + log.addHandler(sh) + + return log + + +DIR = Path(__file__).resolve().parent +DIRS = [r"C:\DEV__TX", r"C:\DEV__OPTT", r"C:\DEV__RADIX"] + + +log = get_logger( + file=DIR / "deleted.txt", + formatter="[%(asctime)s] %(message)s", +) + + +def run(dirs: list[str | Path]) -> None: + print(f"\nStarted {dt.datetime.today()}") + + for dir_path in dirs: + print(dir_path) + path = Path(dir_path) + + files = list(path.glob("*/*.hprof")) + files += list(path.glob("*/.config/var/log/heapdump.hprof.old")) + for f in files: + ctime_timestamp = f.stat().st_ctime + ctime = dt.datetime.fromtimestamp(ctime_timestamp) + ctime = ctime.replace(microsecond=0) + + file_size = f.stat().st_size + + text = f"{f} (date creation: {ctime}, size: {sizeof_fmt(file_size)})" + print(text) + + # Удаление, если с даты создания прошло больше 1 часа + if dt.datetime.today() > ctime + dt.timedelta(hours=1): + log.info(text) + f.unlink(missing_ok=True) + + print(f"\nFinished {dt.datetime.today()}") + + +if __name__ == "__main__": + if "--one" in sys.argv: + run(DIRS) + + sys.exit() + + while True: + run(DIRS) + time.sleep(2 * 60 * 60) diff --git a/job_compassplus/visa_base2_tc_counter.py b/job_compassplus/visa_base2_tc_counter.py new file mode 100644 index 000000000..a995cc86d --- /dev/null +++ b/job_compassplus/visa_base2_tc_counter.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from collections import Counter +from pathlib import Path + + +# TODO: argparser +DIR = Path(r"C:\Users\ipetrash\Desktop\Visa Base 2") + +for path in DIR.rglob("*"): + if not path.is_file() or "errors" in str(path): + continue + + print(path) + + tc_by_counter = Counter() + tc_tcr_by_counter = Counter() + + with open(path, "rb") as f: + for line in f: + is_ctf: bool = len(line.rstrip(b"\n\r")) == 168 + tc_raw: bytes = line[:2] + tcr_raw: bytes = line[3:4] if is_ctf else line[5:6] + # TODO: Support EBCDIC, example b'\xf9\xf0' -> 90 + try: + tc: str = tc_raw.decode("ascii") + except UnicodeDecodeError as e: + print(f" [#] Skip. Error on {tc_raw}: {e}") + break + + try: + tcr: str = tcr_raw.decode("ascii") + except UnicodeDecodeError as e: + print(f" [#] Skip. Error on {tcr_raw}: {e}") + break + + if not tc.strip() or tc in ["90", "91", "92", "00"]: + continue + + tc_by_counter.update([tc]) + tc_tcr_by_counter.update([f"{tc}-{tcr}"]) + + print(" TC: " + ", ".join(f"{k}: {v}" for k, v in tc_by_counter.items())) + print(" TC-TCR: " + ", ".join(f"{k}: {v}" for k, v in tc_tcr_by_counter.items())) diff --git a/job_compassplus/web__show_job_report/main.py b/job_compassplus/web__show_job_report/main.py index cceade056..c23ea825b 100644 --- a/job_compassplus/web__show_job_report/main.py +++ b/job_compassplus/web__show_job_report/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """Вывод переработки текущего (или конкретного) пользователя""" @@ -13,36 +13,35 @@ from flask import Flask +DIR = Path(__file__).resolve().parent +sys.path.append(str(DIR.parent / "job_report")) +from utils import get_report_persons_info, get_person_info + app = Flask(__name__) logging.basicConfig(level=logging.DEBUG) -DIR = Path(__file__).resolve().parent -sys.path.append(str(DIR.parent / 'job_report')) -from utils import get_report_persons_info, get_person_info - - @app.route("/") -def index(): +def index() -> str: report_dict = get_report_persons_info() try: - person = report_dict['Текущий пользователь'][0] + person = report_dict["Текущий пользователь"][0] except: person = None if person is None: - person = get_person_info(second_name='Петраш', report_dict=report_dict) + person = get_person_info(second_name="Петраш", report_dict=report_dict) if person: - return '{} {}'.format(person.full_name, person.deviation_of_time) + return f"{person.full_name} {person.deviation_of_time}" - return 'Not found specific user' + return "Not found specific user" -if __name__ == '__main__': +if __name__ == "__main__": # Localhost app.run(port=5001) diff --git a/job_compassplus/web_counter/README.md b/job_compassplus/web_counter/README.md new file mode 100644 index 000000000..09a991ce9 --- /dev/null +++ b/job_compassplus/web_counter/README.md @@ -0,0 +1,8 @@ +### Links: + * http://127.0.0.1:7777/docs + * http://127.0.0.1:7777/redoc + +### Run: +``` +python.exe -m uvicorn main:app --reload --port=7777 +``` \ No newline at end of file diff --git a/job_compassplus/web_counter/config.py b/job_compassplus/web_counter/config.py new file mode 100644 index 000000000..2fcb616c2 --- /dev/null +++ b/job_compassplus/web_counter/config.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from pathlib import Path + + +DIR = Path(__file__).resolve().parent + +DB_DIR_NAME: Path = DIR / "database" +DB_DIR_NAME.mkdir(parents=True, exist_ok=True) + +DB_FILE_NAME: Path = DB_DIR_NAME / "db.sqlite" diff --git a/job_compassplus/web_counter/db.py b/job_compassplus/web_counter/db.py new file mode 100644 index 000000000..260e1d0af --- /dev/null +++ b/job_compassplus/web_counter/db.py @@ -0,0 +1,130 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import enum +import time +import sys + +from typing import Type, Iterable, Any + +# pip install peewee +from peewee import ( + Model, + TextField, + ForeignKeyField, + CharField, + BlobField, + IntegerField, +) +from playhouse.shortcuts import model_to_dict +from playhouse.sqliteq import SqliteQueueDatabase + +from config import DIR, DB_FILE_NAME + +sys.path.append(str(DIR.parent.parent)) +from shorten import shorten + + +# This working with multithreading +# SOURCE: http://docs.peewee-orm.com/en/latest/peewee/playhouse.html#sqliteq +db = SqliteQueueDatabase( + DB_FILE_NAME, + pragmas={ + "foreign_keys": 1, + "journal_mode": "wal", # WAL-mode + "cache_size": -1024 * 64, # 64MB page-cache + }, + use_gevent=False, # Use the standard library "threading" module. + autostart=True, + queue_max_size=64, # Max. # of pending writes that can accumulate. + results_timeout=5.0, # Max. time to wait for query to be executed. +) + + +class BaseModel(Model): + class Meta: + database = db + + @classmethod + def get_inherited_models(cls) -> list[Type["BaseModel"]]: + return sorted(cls.__subclasses__(), key=lambda x: x.__name__) + + @classmethod + def print_count_of_tables(cls) -> None: + items = [] + for sub_cls in cls.get_inherited_models(): + name = sub_cls.__name__ + count = sub_cls.select().count() + items.append(f"{name}: {count}") + + print(", ".join(items)) + + @classmethod + def count(cls, filters: Iterable = None) -> int: + query = cls.select() + if filters: + query = query.filter(*filters) + return query.count() + + def to_dict(self) -> dict[str, Any]: + return model_to_dict(self, recurse=False) + + def __str__(self) -> str: + fields = [] + for k, field in self._meta.fields.items(): + v = getattr(self, k) + + if isinstance(field, (TextField, CharField)): + if isinstance(v, enum.Enum): + v = v.value + + if v: + v = repr(shorten(v, length=30)) + + if isinstance(field, BlobField) and v is not None: + v = f"<{len(v)} bytes>" + + elif isinstance(field, ForeignKeyField): + k = f"{k}_id" + if v: + v = v.id + + fields.append(f"{k}={v}") + + return self.__class__.__name__ + "(" + ", ".join(fields) + ")" + + +class Counter(BaseModel): + name = CharField(primary_key=True) + value = IntegerField(default=0) + + @classmethod + def get_or_create(cls, name: str) -> "Counter": + obj: Counter | None = cls.get_or_none(name=name) + if not obj: + obj = cls.create(name=name) + return obj + + @classmethod + def increment(cls, name: str) -> int: + # Create or ignore + cls.get_or_create(name) + + cls.update(value=cls.value + 1).where(cls.name == name).execute() + return cls.get_or_create(name).value + + +db.connect() +db.create_tables(BaseModel.get_inherited_models()) + + +# Задержка в 50мс, чтобы дать время на запуск SqliteQueueDatabase и создание таблиц +# Т.к. в SqliteQueueDatabase запросы на чтение выполняются сразу, а на запись попадают в очередь +time.sleep(0.050) + + +if __name__ == "__main__": + BaseModel.print_count_of_tables() diff --git a/job_compassplus/web_counter/main.py b/job_compassplus/web_counter/main.py new file mode 100644 index 000000000..d520728c4 --- /dev/null +++ b/job_compassplus/web_counter/main.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from fastapi import FastAPI +from db import Counter + + +app = FastAPI() + + +@app.get("/increment/{name}") +def increment(name: str) -> int: + return Counter.increment(name) diff --git a/job_compassplus/web_counter/run_python10.bat b/job_compassplus/web_counter/run_python10.bat new file mode 100644 index 000000000..789784ee1 --- /dev/null +++ b/job_compassplus/web_counter/run_python10.bat @@ -0,0 +1 @@ +C:\Users\ipetrash\AppData\Local\Programs\Python\Python310\python.exe -m uvicorn main:app --reload --port=7777 \ No newline at end of file diff --git a/join_dict_values_if_key_in_other_values.py b/join_dict_values_if_key_in_other_values.py index 751420d5a..157fab459 100644 --- a/join_dict_values_if_key_in_other_values.py +++ b/join_dict_values_if_key_in_other_values.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # https://ru.stackoverflow.com/questions/854015/ @@ -11,7 +11,7 @@ """ -total = {'a': ['b', 'd'], 'b': ['c', 'd'], 'c': ['d']} +total = {"a": ["b", "d"], "b": ["c", "d"], "c": ["d"]} for k in total: for k1, v1 in total.items(): diff --git a/join_words.py b/join_words.py new file mode 100644 index 000000000..41444b98c --- /dev/null +++ b/join_words.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +def join_words(word_1: str, word_2: str) -> str: + result: str = word_1 + word_2 + for i in range(min(len(word_1), len(word_2))): + if word_1[-i:].upper() == word_2[:i].upper(): + result = word_1.removesuffix(word_1[-i:]) + word_2 + + return result + + +if __name__ == "__main__": + result = join_words("Капитан", "АНИМЕ") + print(result) + assert result == "КапитАНИМЕ" diff --git a/json_examples/counter/main.py b/json_examples/counter/main.py index 6300229d9..16070beef 100644 --- a/json_examples/counter/main.py +++ b/json_examples/counter/main.py @@ -1,17 +1,19 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -file_name = 'counter.json' - import json -json_data = json.load(open(file_name, encoding='utf-8')) + + +file_name = "counter.json" + +json_data = json.load(open(file_name, encoding="utf-8")) print(json_data) # Изменение объекта -json_data['counter'] += 1 +json_data["counter"] += 1 # Сохранение -json.dump(json_data, open(file_name, mode='w', encoding='utf-8')) +json.dump(json_data, open(file_name, mode="w", encoding="utf-8")) diff --git a/json_examples/dump__serialization__custom_object__default.py b/json_examples/dump__serialization__custom_object__default.py index ff57de312..41e484b89 100644 --- a/json_examples/dump__serialization__custom_object__default.py +++ b/json_examples/dump__serialization__custom_object__default.py @@ -1,11 +1,57 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" +import json from decimal import Decimal -data = ((69695, 'CASTROL', '156f9d', 'Castrol EDGE Professional A5 5W30 (1л) Lаnd Rover', 'Castrol-156F9D-Castrol EDGE Professional A5 5W30 (1л) Lаnd Rover', Decimal('1.00'), '', Decimal('684.25'), 0, 13155264, '', Decimal('0.00'), Decimal('0.00'), Decimal('0.00'), Decimal('0.00'), Decimal('0.00'), '7', '', ''), (69695, 'CASTROL', '15667c', 'Castrol EDGE Titanium 5W30 LL 504.00/507.00 (1L).Масло моторное', 'Castrol-15667C-Castrol EDGE Titanium 5W30 LL 504.00/507.00 (1L).Масло моторное', Decimal('40.00'), '', Decimal('599.15'), 0, 13155265, '', Decimal('0.00'), Decimal('0.00'), Decimal('0.00'), Decimal('0.00'), Decimal('0.00'), '7', '', '')) + + +data = ( + ( + 69695, + "CASTROL", + "156f9d", + "Castrol EDGE Professional A5 5W30 (1л) Lаnd Rover", + "Castrol-156F9D-Castrol EDGE Professional A5 5W30 (1л) Lаnd Rover", + Decimal("1.00"), + "", + Decimal("684.25"), + 0, + 13155264, + "", + Decimal("0.00"), + Decimal("0.00"), + Decimal("0.00"), + Decimal("0.00"), + Decimal("0.00"), + "7", + "", + "", + ), + ( + 69695, + "CASTROL", + "15667c", + "Castrol EDGE Titanium 5W30 LL 504.00/507.00 (1L).Масло моторное", + "Castrol-15667C-Castrol EDGE Titanium 5W30 LL 504.00/507.00 (1L).Масло моторное", + Decimal("40.00"), + "", + Decimal("599.15"), + 0, + 13155265, + "", + Decimal("0.00"), + Decimal("0.00"), + Decimal("0.00"), + Decimal("0.00"), + Decimal("0.00"), + "7", + "", + "", + ), +) print(data) @@ -21,7 +67,6 @@ def my_default(obj): return str(obj) -import json json_data = json.dumps(data, ensure_ascii=False, default=str) print(json_data) diff --git a/json_examples/e.py b/json_examples/e.py index 36086d2ca..5397e5aa3 100644 --- a/json_examples/e.py +++ b/json_examples/e.py @@ -1,31 +1,35 @@ -__author__ = 'ipetrash' +__author__ = "ipetrash" import json -if __name__ == '__main__': +if __name__ == "__main__": # Compact encoding: - print(json.dumps([1, 2, 3, {'4': 5, '6': 7}])) + print(json.dumps([1, 2, 3, {"4": 5, "6": 7}])) # Custom separators: - print(json.dumps([1, 2, 3, {'4': 5, '6': 7}], separators=(',', ':'))) + print(json.dumps([1, 2, 3, {"4": 5, "6": 7}], separators=(",", ":"))) # Pretty print: - print(json.dumps([1, 2, 3, {'4': 5, '6': 7}], indent=4)) + print(json.dumps([1, 2, 3, {"4": 5, "6": 7}], indent=4)) # Sort key: - print(json.dumps(["z", "b", "d", {'b': 5, 'a': 7, 'c': 2}], sort_keys=True, indent=4)) + print( + json.dumps(["z", "b", "d", {"b": 5, "a": 7, "c": 2}], sort_keys=True, indent=4) + ) # Create object: obj = [ - 3, 2, 1, + 3, + 2, + 1, [2, 1, 1, 4], { - 'a': 1, - 'b': 2, - 'c': 3, - } + "a": 1, + "b": 2, + "c": 3, + }, ] print(obj) @@ -39,5 +43,5 @@ # Test: print(obj2[3][0]) - print(obj2[4]['c']) - print(obj2[4]['b']) + print(obj2[4]["c"]) + print(obj2[4]["b"]) diff --git a/json_examples/load_with_replacing_value.py b/json_examples/load_with_replacing_value.py index 10ce4e5f8..86edf39a0 100644 --- a/json_examples/load_with_replacing_value.py +++ b/json_examples/load_with_replacing_value.py @@ -1,17 +1,14 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import json def dict_clean(items, default): - return { - k: default if v is None else v - for k, v in items - } + return {k: default if v is None else v for k, v in items} text = """ @@ -30,15 +27,17 @@ def dict_clean(items, default): genre_translate = json.loads( - text, encoding='utf-8', - object_pairs_hook=lambda items: dict_clean(items, default=[]) + text, + encoding="utf-8", + object_pairs_hook=lambda items: dict_clean(items, default=[]), ) print(genre_translate) # {'Action-bar': [], 'Action': 'Action', 'Children': [{'Action': []}, {'Action': True}, {'Action': 'false'}, {'Action': {'need': []}}], 'RGB-bar': []} genre_translate = json.loads( - text, encoding='utf-8', - object_pairs_hook=lambda items: dict_clean(items, default="") + text, + encoding="utf-8", + object_pairs_hook=lambda items: dict_clean(items, default=""), ) print(genre_translate) # {'Action-bar': '', 'Action': 'Action', 'Children': [{'Action': ''}, {'Action': True}, {'Action': 'false'}, {'Action': {'need': ''}}], 'RGB-bar': ''} diff --git a/jsonmerge.py b/jsonmerge.py index 3c30fc8e3..9abd250a6 100644 --- a/jsonmerge.py +++ b/jsonmerge.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import json @@ -40,8 +40,7 @@ # TODO: нужно тестить, возможно, алгоритм где-то дырявый - -def jsonmerge(j1, j2): +def jsonmerge(j1, j2) -> None: for k, v in j2.items(): if k in j1 and isinstance(j1[k], dict) and isinstance(v, dict): jsonmerge(j1[k], v) diff --git a/k_notation_to_number.py b/k_notation_to_number.py index 84985a5c0..1aedeed5e 100644 --- a/k_notation_to_number.py +++ b/k_notation_to_number.py @@ -1,16 +1,16 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" def k_notation_to_number(num_str: str) -> int: - i = num_str.index('k') + i = num_str.index("k") num, size = num_str[:i], len(num_str[i:]) - return int(float(num) * (1000 ** size)) + return int(float(num) * (1000**size)) -if __name__ == '__main__': - print(k_notation_to_number("1k")) # 1000 +if __name__ == "__main__": + print(k_notation_to_number("1k")) # 1000 print(k_notation_to_number("1.5kk")) # 1500000 - print(k_notation_to_number("1kkk")) # 1000000000 + print(k_notation_to_number("1kkk")) # 1000000000 diff --git a/kivy_examples/generate_form_with_cycle.py b/kivy_examples/generate_form_with_cycle.py index e7b20072d..9cab7a17e 100644 --- a/kivy_examples/generate_form_with_cycle.py +++ b/kivy_examples/generate_form_with_cycle.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from kivy.app import App @@ -10,17 +10,17 @@ class MyWidget(GridLayout): - def __init__(self, **kwargs): + def __init__(self, **kwargs) -> None: super().__init__(**kwargs) self.cols = 1 self.spacing = 10 self.padding = 10 - label_list = ['Собака', 'Сосед', 'Кот', 'Биткоин'] + label_list = ["Собака", "Сосед", "Кот", "Биткоин"] for i, title in enumerate(label_list): label = Label(text=title) - label.id = 'id:' + str(i) + label.id = "id:" + str(i) self.add_widget(label) @@ -30,5 +30,5 @@ def build(self): return MyWidget() -if __name__ == '__main__': +if __name__ == "__main__": MyApp().run() diff --git a/kivy_examples/hello_world.py b/kivy_examples/hello_world.py index 375774a11..0b5d70f2f 100644 --- a/kivy_examples/hello_world.py +++ b/kivy_examples/hello_world.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # Install kivy: @@ -15,8 +15,8 @@ class MyApp(App): def build(self): - return Label(text='Hello world') + return Label(text="Hello world") -if __name__ == '__main__': +if __name__ == "__main__": MyApp().run() diff --git a/kivy_examples/kivymd__example.py b/kivy_examples/kivymd__example.py index 524c65ef8..d46d8b185 100644 --- a/kivy_examples/kivymd__example.py +++ b/kivy_examples/kivymd__example.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import os @@ -11,23 +11,23 @@ from kivymd.uix.list import OneLineListItem -KV = ''' +KV = """ ScrollView: MDList: id: container -''' +""" class Test(MDApp): def build(self): return Builder.load_string(KV) - def on_start(self): - for i in os.listdir(path='.'): + def on_start(self) -> None: + for i in os.listdir(path="."): self.root.ids.container.add_widget( OneLineListItem(text=i, on_release=lambda item, i=i: print(i)) ) -if __name__ == '__main__': +if __name__ == "__main__": Test().run() diff --git a/kivy_examples/kv_file_switch_between_screens/main.py b/kivy_examples/kv_file_switch_between_screens/main.py new file mode 100644 index 000000000..495422a27 --- /dev/null +++ b/kivy_examples/kv_file_switch_between_screens/main.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from kivy.app import App +from kivy.uix.screenmanager import Screen + + +class EegScreen(Screen): + pass + + +class SettingsScreen(Screen): + pass + + +class ExchangeScreen(Screen): + pass + + +class ConnectionsScreen(Screen): + pass + + +class MyApp(App): + pass + + +if __name__ == "__main__": + MyApp().run() diff --git a/kivy_examples/kv_file_switch_between_screens/my.kv b/kivy_examples/kv_file_switch_between_screens/my.kv new file mode 100644 index 000000000..905ab225f --- /dev/null +++ b/kivy_examples/kv_file_switch_between_screens/my.kv @@ -0,0 +1,66 @@ +BoxLayout: + orientation: 'horizontal' + + BoxLayout: + orientation: 'vertical' + + Label: + text: 'PERMANENT' + + Button: + text: 'Connections' + font_size: 20 + on_release: screen_manager.current = 'ConnectionsScreen' + Button: + text: 'Exchange' + font_size: 20 + on_release: screen_manager.current = 'ExchangeScreen' + + Button: + text: 'EEG Signal' + font_size: 20 + on_release: screen_manager.current = 'EegScreen' + + Button: + text: 'Settings' + font_size: 20 + on_release: screen_manager.current = 'SettingsScreen' + + ScreenManager: + id: screen_manager + + EegScreen: + id: eeg_screen + name: 'EegScreen' + + SettingsScreen: + id: settings_screen + name: 'SettingsScreen' + + ExchangeScreen: + id: exchange_screen + name: 'ExchangeScreen' + + ConnectionsScreen: + id: connections_screen + name: 'ConnectionsScreen' + +: + FloatLayout: + Label: + text: 'EegScreen' + +: + FloatLayout: + Label: + text: 'SettingsScreen' + +: + FloatLayout: + Label: + text: 'ExchangeScreen' + +: + FloatLayout: + Label: + text: 'ConnectionsScreen' \ No newline at end of file diff --git a/kivy_examples/login_screen.py b/kivy_examples/login_screen.py index f13d0054c..f745d35ed 100644 --- a/kivy_examples/login_screen.py +++ b/kivy_examples/login_screen.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from kivy.app import App @@ -13,26 +13,26 @@ class LoginScreen(GridLayout): - def __init__(self, **kwargs): + def __init__(self, **kwargs) -> None: super().__init__(**kwargs) self.cols = 2 self.spacing = 10 self.padding = 10 - self.add_widget(Label(text='User Name')) + self.add_widget(Label(text="User Name")) self.username = TextInput(multiline=False) - self.username.text = 'admin' + self.username.text = "admin" self.add_widget(self.username) - self.add_widget(Label(text='password')) + self.add_widget(Label(text="password")) self.password = TextInput(password=True, multiline=False) - self.password.text = 'admin' + self.password.text = "admin" self.add_widget(self.password) self.add_widget(Button(text="Ok", on_press=self.check)) - def check(self, button): + def check(self, button) -> None: print("auth called") message = "Success!" if self.username.text == "admin" else "Need admin!" @@ -53,5 +53,5 @@ def build(self): return LoginScreen() -if __name__ == '__main__': +if __name__ == "__main__": MyLoginScreenApp().run() diff --git a/kivy_examples/switch_between_widgets.py b/kivy_examples/switch_between_widgets.py index 678292c07..2009ce5a2 100644 --- a/kivy_examples/switch_between_widgets.py +++ b/kivy_examples/switch_between_widgets.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from kivy.app import App @@ -11,7 +11,7 @@ class ScreenMain(Screen): - def __init__ (self, **kwargs): + def __init__(self, **kwargs) -> None: super().__init__(**kwargs) box_layout = BoxLayout(orientation="vertical", spacing=5, padding=[10]) @@ -26,13 +26,13 @@ def __init__ (self, **kwargs): box_layout.add_widget(button_new_pasword) self.add_widget(box_layout) - def _on_press_button_new_pasword(self, *args): - self.manager.transition.direction = 'left' - self.manager.current = 'len_password' + def _on_press_button_new_pasword(self, *args) -> None: + self.manager.transition.direction = "left" + self.manager.current = "len_password" class ScreenLenPassword(Screen): - def __init__(self, **kwargs): + def __init__(self, **kwargs) -> None: super().__init__(**kwargs) box_layout = BoxLayout(orientation="vertical", spacing=5, padding=[10]) @@ -47,16 +47,16 @@ def __init__(self, **kwargs): box_layout.add_widget(button_new_pasword) self.add_widget(box_layout) - def _on_press_button_new_pasword(self, *args): - self.manager.transition.direction = 'right' - self.manager.current = 'main_screen' + def _on_press_button_new_pasword(self, *args) -> None: + self.manager.transition.direction = "right" + self.manager.current = "main_screen" class MainApp(App): def build(self): sm = ScreenManager() - sm.add_widget(ScreenMain(name='main_screen')) - sm.add_widget(ScreenLenPassword(name='len_password')) + sm.add_widget(ScreenMain(name="main_screen")) + sm.add_widget(ScreenLenPassword(name="len_password")) return sm diff --git a/krakozyabry_tx/krakozyabry_tx.py b/krakozyabry_tx/krakozyabry_tx.py index d17fb9f99..0535c6803 100644 --- a/krakozyabry_tx/krakozyabry_tx.py +++ b/krakozyabry_tx/krakozyabry_tx.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """Пример конвертирования кракозябры "РќРµ указана точка" в человеческий вид.""" @@ -11,6 +11,6 @@ from PySide.QtGui import QApplication, QMessageBox -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication(sys.argv) - QMessageBox.information(None, None, open('t', encoding='utf-8').read()) + QMessageBox.information(None, None, open("t", encoding="utf-8").read()) diff --git a/languagetool/check.py b/languagetool/check.py index 09f440aba..53effbed8 100644 --- a/languagetool/check.py +++ b/languagetool/check.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """ @@ -14,6 +14,9 @@ """ +import requests + + def check_ru(text: str) -> list(): """ Функция делает запрос на https://languagetool.org/ чтобы проверить на правильность указанный текст. @@ -22,20 +25,19 @@ def check_ru(text: str) -> list(): """ - url = 'https://languagetool.org/api/v2/check' + url = "https://languagetool.org/api/v2/check" post_data = { - 'disabledRules': 'WHITESPACE_RULE', - 'allowIncompleteResults': 'true', - 'text': text, - 'language': 'ru', + "disabledRules": "WHITESPACE_RULE", + "allowIncompleteResults": "true", + "text": text, + "language": "ru", } - import requests rs = requests.post(url, data=post_data) if not rs.ok: - raise Exception('Проблема с {}, status_code = {}'.format(url, rs.status_code)) + raise Exception(f"Проблема с {url}, status_code = {rs.status_code}") - return rs.json()['matches'] + return rs.json()["matches"] def is_ok_ru(text: str) -> bool: @@ -47,7 +49,7 @@ def is_ok_ru(text: str) -> bool: return not check_ru(text) -if __name__ == '__main__': +if __name__ == "__main__": text = """\ Интересует другое почему она возникла? Дубаль два, надеюсь все пройдет отлично. @@ -58,20 +60,22 @@ def is_ok_ru(text: str) -> bool: if not matches: print("Ошибок нет") else: - print('Найденные проблемы:') + print("Найденные проблемы:") for match in matches: - error = match['message'] - context = match['context'] - offset = context['offset'] - length = context['length'] + error = match["message"] + context = match["context"] + offset = context["offset"] + length = context["length"] - error_text = context['text'][offset: offset + length] - print('"{}" [{}:{}]: "{}" -> {}'.format(error_text, offset, length, error, - [i['value'] for i in match['replacements']])) + error_text = context["text"][offset : offset + length] + replacements = [i["value"] for i in match["replacements"]] + print( + f'"{error_text}" [{offset}:{length}]: "{error}" -> {replacements}' + ) print() - print('-' * 20) + print("-" * 20) print(is_ok_ru(text)) print(is_ok_ru("Все хорошо!")) diff --git a/leftpad.py b/leftpad.py index 3f15b5e23..d54f944c1 100644 --- a/leftpad.py +++ b/leftpad.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # Для интереса написать аналог NPM leftpad. @@ -18,7 +18,7 @@ # // => "01" -def leftpad(text, size, ch=' '): +def leftpad(text: str | int, size: int, ch: str | int = " "): text = str(text) ch = str(ch) @@ -27,17 +27,17 @@ def leftpad(text, size, ch=' '): # Версия с использованием общего алгоритм -def leftpad2(text, size, ch=' '): +def leftpad2(text: str | int, size: int, ch: str | int = " "): text = str(text) - ch = str(ch) - text_size = len(text) - result = '' pad_len = size - text_size if pad_len <= 0: return text + ch = str(ch) + + result = "" for i in range(pad_len): result += ch result += text @@ -45,11 +45,11 @@ def leftpad2(text, size, ch=' '): return result -if __name__ == '__main__': - assert leftpad('foo', 5) == " foo" - assert leftpad('foobar', 6) == "foobar" +if __name__ == "__main__": + assert leftpad("foo", 5) == " foo" + assert leftpad("foobar", 6) == "foobar" assert leftpad(1, 2, 0) == "01" - assert leftpad('foo', 5) == leftpad2('foo', 5) - assert leftpad('foobar', 6) == leftpad2('foobar', 6) - assert leftpad(1, 2, 0) == leftpad(1, 2, 0) + assert leftpad("foo", 5) == leftpad2("foo", 5) + assert leftpad("foobar", 6) == leftpad2("foobar", 6) + assert leftpad(1, 2, 0) == leftpad2(1, 2, 0) diff --git a/lex_yacc__examples/sly__examples/added_string_and_print.py b/lex_yacc__examples/sly__examples/added_string_and_print.py index e4fbd0215..2b69916a5 100644 --- a/lex_yacc__examples/sly__examples/added_string_and_print.py +++ b/lex_yacc__examples/sly__examples/added_string_and_print.py @@ -1,49 +1,59 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/dabeaz/sly/ # SOURCE: https://sly.readthedocs.io/en/latest/sly.html -from typing import Union - # pip install sly from sly import Lexer, Parser class MyLexer(Lexer): - tokens = {NAME, TEXT, NUMBER, PRINT, PLUS, TIMES, MINUS, DIVIDE, ASSIGN, LPAREN, RPAREN} - ignore = ' \t' + tokens = { + NAME, + TEXT, + NUMBER, + PRINT, + PLUS, + TIMES, + MINUS, + DIVIDE, + ASSIGN, + LPAREN, + RPAREN, + } + ignore = " \t" # Tokens - NUMBER = r'\d+' - NAME = r'[a-zA-Z_][a-zA-Z0-9_]*' - NAME['print'] = PRINT + NUMBER = r"\d+" + NAME = r"[a-zA-Z_][a-zA-Z0-9_]*" + NAME["print"] = PRINT @_(r'".+?"', r"'.+?'") def TEXT(self, t): return t # Special symbols - PLUS = r'\+' - MINUS = r'-' - TIMES = r'\*' - DIVIDE = r'/' - ASSIGN = r'=' - LPAREN = r'\(' - RPAREN = r'\)' + PLUS = r"\+" + MINUS = r"-" + TIMES = r"\*" + DIVIDE = r"/" + ASSIGN = r"=" + LPAREN = r"\(" + RPAREN = r"\)" # Ignored pattern - ignore_newline = r'\n+' + ignore_newline = r"\n+" # Extra action for newlines - def ignore_newline(self, t): - self.lineno += t.value.count('\n') + def ignore_newline(self, t) -> None: + self.lineno += t.value.count("\n") - def error(self, t): + def error(self, t) -> None: print(f"Illegal character '{t.value[0]}'") self.index += 1 @@ -52,27 +62,27 @@ class MyParser(Parser): tokens = MyLexer.tokens precedence = ( - ('left', PLUS, MINUS), - ('left', TIMES, DIVIDE), - ('right', UMINUS), + ("left", PLUS, MINUS), + ("left", TIMES, DIVIDE), + ("right", UMINUS), ) - def __init__(self): + def __init__(self) -> None: self.names = dict() - @_('NAME ASSIGN expr') - def statement(self, p): + @_("NAME ASSIGN expr") + def statement(self, p) -> None: self.names[p.NAME] = p.expr - @_('expr') - def statement(self, p) -> Union[int, str]: + @_("expr") + def statement(self, p) -> int | str: return p.expr - @_('PRINT expr') - def statement(self, p): + @_("PRINT expr") + def statement(self, p) -> None: print(p.expr) - @_('expr PLUS expr') + @_("expr PLUS expr") def expr(self, p): # Javascript style :D if isinstance(p.expr0, str) or isinstance(p.expr1, str): @@ -80,44 +90,44 @@ def expr(self, p): return p.expr0 + p.expr1 - @_('expr MINUS expr') + @_("expr MINUS expr") def expr(self, p): return p.expr0 - p.expr1 - @_('expr TIMES expr') + @_("expr TIMES expr") def expr(self, p): return p.expr0 * p.expr1 - @_('expr DIVIDE expr') + @_("expr DIVIDE expr") def expr(self, p): return p.expr0 / p.expr1 - @_('MINUS expr %prec UMINUS') + @_("MINUS expr %prec UMINUS") def expr(self, p): return -p.expr - @_('LPAREN expr RPAREN') + @_("LPAREN expr RPAREN") def expr(self, p): return p.expr - @_('NUMBER') + @_("NUMBER") def expr(self, p) -> int: return int(p.NUMBER) - @_('TEXT') + @_("TEXT") def expr(self, p) -> str: return p.TEXT[1:-1] - @_('NAME') + @_("NAME") def expr(self, p): try: return self.names[p.NAME] except LookupError: - print(f'Undefined name {p.NAME!r}') + print(f"Undefined name {p.NAME!r}") return 0 -if __name__ == '__main__': +if __name__ == "__main__": lexer = MyLexer() parser = MyParser() @@ -125,7 +135,7 @@ def expr(self, p): "12" + '3' + 456 """.strip() value = parser.parse(lexer.tokenize(text)) - print(f'{text} = {value!r}') + print(f"{text} = {value!r}") # 12 + '3' + 456 = '123456' print() @@ -134,19 +144,19 @@ def expr(self, p): "12" + '3' """.strip() value = parser.parse(lexer.tokenize(text)) - print(f'{text} = {value!r}') + print(f"{text} = {value!r}") # "12" + '3' = '123' print() items = [ """text = "Hello " + 'World!'""", - 'text', + "text", ] for line in items: value = parser.parse(lexer.tokenize(line)) if value is not None: - print(f'{line} = {value!r}') + print(f"{line} = {value!r}") else: print(line) # text = "Hello " + 'World!' @@ -164,7 +174,7 @@ def expr(self, p): while True: try: - text = input('calc > ') + text = input("calc > ") except EOFError: break if text: diff --git a/lex_yacc__examples/sly__examples/added_ternary_operator.py b/lex_yacc__examples/sly__examples/added_ternary_operator.py index 1885cd90e..3ab9e16bc 100644 --- a/lex_yacc__examples/sly__examples/added_ternary_operator.py +++ b/lex_yacc__examples/sly__examples/added_ternary_operator.py @@ -1,73 +1,88 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/dabeaz/sly/ # SOURCE: https://sly.readthedocs.io/en/latest/sly.html -from typing import Union - # pip install sly from sly import Lexer, Parser class MyLexer(Lexer): tokens = { - NAME, NUMBER, IF, ELSE, TRUE, FALSE, - PLUS, TIMES, MINUS, DIVIDE, LPAREN, RPAREN, - ASSIGN, EQ, LT, LE, GT, GE, NE, - QUESTION, COLON, + NAME, + NUMBER, + IF, + ELSE, + TRUE, + FALSE, + PLUS, + TIMES, + MINUS, + DIVIDE, + LPAREN, + RPAREN, + ASSIGN, + EQ, + LT, + LE, + GT, + GE, + NE, + QUESTION, + COLON, } - ignore = ' \t' + ignore = " \t" # Tokens - NUMBER = r'\d+' - NAME = r'[a-zA-Z_][a-zA-Z0-9_]*' - NAME['if'] = IF - NAME['else'] = ELSE - NAME['true'] = TRUE - NAME['false'] = FALSE + NUMBER = r"\d+" + NAME = r"[a-zA-Z_][a-zA-Z0-9_]*" + NAME["if"] = IF + NAME["else"] = ELSE + NAME["true"] = TRUE + NAME["false"] = FALSE # Special symbols - PLUS = r'\+' - MINUS = r'-' - TIMES = r'\*' - DIVIDE = r'/' - LPAREN = r'\(' - RPAREN = r'\)' - - QUESTION = r'\?' - COLON = r'\:' - - EQ = r'==' - ASSIGN = r'=' - LE = r'<=' - LT = r'<' - GE = r'>=' - GT = r'>' - NE = r'!=' - - @_(r'true') + PLUS = r"\+" + MINUS = r"-" + TIMES = r"\*" + DIVIDE = r"/" + LPAREN = r"\(" + RPAREN = r"\)" + + QUESTION = r"\?" + COLON = r"\:" + + EQ = r"==" + ASSIGN = r"=" + LE = r"<=" + LT = r"<" + GE = r">=" + GT = r">" + NE = r"!=" + + @_(r"true") def TRUE(self, t): t.value = True return t - @_(r'false') + @_(r"false") def FALSE(self, t): t.value = False return t # Ignored pattern - ignore_newline = r'\n+' + ignore_newline = r"\n+" # Extra action for newlines - def ignore_newline(self, t): - self.lineno += t.value.count('\n') + def ignore_newline(self, t) -> None: + self.lineno += t.value.count("\n") - def error(self, t): + def error(self, t) -> None: print(f"Illegal character '{t.value[0]}'") self.index += 1 @@ -77,113 +92,113 @@ class MyParser(Parser): tokens = MyLexer.tokens precedence = ( - ('left', IF, ELSE), - ('left', EQ, NE, LT, LE, GT, GE), - ('left', PLUS, MINUS), - ('left', TIMES, DIVIDE), - ('right', UMINUS), + ("left", IF, ELSE), + ("left", EQ, NE, LT, LE, GT, GE), + ("left", PLUS, MINUS), + ("left", TIMES, DIVIDE), + ("right", UMINUS), ) - def __init__(self): + def __init__(self) -> None: self.names = dict() - @_('NAME ASSIGN expr') - def statement(self, p): + @_("NAME ASSIGN expr") + def statement(self, p) -> None: self.names[p.NAME] = p.expr - @_('expr') + @_("expr") def statement(self, p) -> int: return p.expr # Ternary operator python - @_('expr IF expr ELSE expr') + @_("expr IF expr ELSE expr") def expr(self, p): return p.expr0 if p.expr1 else p.expr2 # Ternary operator c - @_('expr QUESTION expr COLON expr') + @_("expr QUESTION expr COLON expr") def expr(self, p): return p.expr1 if p.expr0 else p.expr2 - @_('expr LT expr') + @_("expr LT expr") def expr(self, p) -> bool: return p.expr0 < p.expr1 - @_('expr LE expr') + @_("expr LE expr") def expr(self, p) -> bool: return p.expr0 <= p.expr1 - @_('expr GT expr') + @_("expr GT expr") def expr(self, p) -> bool: return p.expr0 > p.expr1 - @_('expr GE expr') + @_("expr GE expr") def expr(self, p) -> bool: return p.expr0 >= p.expr1 - @_('expr EQ expr') + @_("expr EQ expr") def expr(self, p) -> bool: return p.expr0 == p.expr1 - @_('expr NE expr') + @_("expr NE expr") def expr(self, p) -> bool: return p.expr0 != p.expr1 - @_('expr PLUS expr') + @_("expr PLUS expr") def expr(self, p): return p.expr0 + p.expr1 - @_('expr MINUS expr') + @_("expr MINUS expr") def expr(self, p): return p.expr0 - p.expr1 - @_('expr TIMES expr') + @_("expr TIMES expr") def expr(self, p): return p.expr0 * p.expr1 - @_('expr DIVIDE expr') + @_("expr DIVIDE expr") def expr(self, p): return p.expr0 / p.expr1 - @_('MINUS expr %prec UMINUS') + @_("MINUS expr %prec UMINUS") def expr(self, p): return -p.expr - @_('LPAREN expr RPAREN') + @_("LPAREN expr RPAREN") def expr(self, p): return p.expr - @_('NUMBER') + @_("NUMBER") def expr(self, p) -> int: return int(p.NUMBER) - @_('TRUE', 'FALSE') + @_("TRUE", "FALSE") def expr(self, p) -> bool: return p[0] - @_('NAME') + @_("NAME") def expr(self, p): try: return self.names[p.NAME] except LookupError: - print(f'Undefined name {p.NAME!r}') + print(f"Undefined name {p.NAME!r}") return 0 -if __name__ == '__main__': +if __name__ == "__main__": lexer = MyLexer() parser = MyParser() items = [ - 'value = 2 > 1', - 'value', - 'value = 2 + 1 == 6 / 2', - 'value', + "value = 2 > 1", + "value", + "value = 2 + 1 == 6 / 2", + "value", ] for line in items: value = parser.parse(lexer.tokenize(line)) if value is not None: - print(f'{line} = {value!r}') + print(f"{line} = {value!r}") else: print(line) # value = 2 > 1 @@ -195,22 +210,22 @@ def expr(self, p): items = [ # Ternary operator python - '123 if value else 456', - '1 if true else 0', - '1 if false else 0', - 'true if 2 + 2 == 4 else false', + "123 if value else 456", + "1 if true else 0", + "1 if false else 0", + "true if 2 + 2 == 4 else false", # Ternary operator c - '2 + 1 == 6 / 2 ? 123 : 456', - 'true ? 1 : 0', - 'false ? 1 : 0', - 'x = true ? 123 : 456', - 'x' + "2 + 1 == 6 / 2 ? 123 : 456", + "true ? 1 : 0", + "false ? 1 : 0", + "x = true ? 123 : 456", + "x", ] for line in items: value = parser.parse(lexer.tokenize(line)) if value is not None: - print(f'({line}) -> {value!r}') + print(f"({line}) -> {value!r}") else: print(line) # (123 if value else 456) -> 123 @@ -227,7 +242,7 @@ def expr(self, p): while True: try: - text = input('calc > ') + text = input("calc > ") except EOFError: break if text: diff --git a/lex_yacc__examples/sly__examples/calc.py b/lex_yacc__examples/sly__examples/calc.py index f4c59f893..9441833f6 100644 --- a/lex_yacc__examples/sly__examples/calc.py +++ b/lex_yacc__examples/sly__examples/calc.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/dabeaz/sly/ @@ -14,29 +14,29 @@ class CalcLexer(Lexer): tokens = {NAME, NUMBER, PLUS, TIMES, MINUS, DIVIDE, ASSIGN, LPAREN, RPAREN} - ignore = ' \t' + ignore = " \t" # Tokens - NAME = r'[a-zA-Z_][a-zA-Z0-9_]*' - NUMBER = r'\d+' + NAME = r"[a-zA-Z_][a-zA-Z0-9_]*" + NUMBER = r"\d+" # Special symbols - PLUS = r'\+' - MINUS = r'-' - TIMES = r'\*' - DIVIDE = r'/' - ASSIGN = r'=' - LPAREN = r'\(' - RPAREN = r'\)' + PLUS = r"\+" + MINUS = r"-" + TIMES = r"\*" + DIVIDE = r"/" + ASSIGN = r"=" + LPAREN = r"\(" + RPAREN = r"\)" # Ignored pattern - ignore_newline = r'\n+' + ignore_newline = r"\n+" # Extra action for newlines - def ignore_newline(self, t): - self.lineno += t.value.count('\n') + def ignore_newline(self, t) -> None: + self.lineno += t.value.count("\n") - def error(self, t): + def error(self, t) -> None: print(f"Illegal character '{t.value[0]}'") self.index += 1 @@ -45,64 +45,64 @@ class CalcParser(Parser): tokens = CalcLexer.tokens precedence = ( - ('left', PLUS, MINUS), - ('left', TIMES, DIVIDE), - ('right', UMINUS), + ("left", PLUS, MINUS), + ("left", TIMES, DIVIDE), + ("right", UMINUS), ) - def __init__(self): + def __init__(self) -> None: self.names = dict() - @_('NAME ASSIGN expr') - def statement(self, p): + @_("NAME ASSIGN expr") + def statement(self, p) -> None: self.names[p.NAME] = p.expr - @_('expr') + @_("expr") def statement(self, p) -> int: return p.expr - @_('expr PLUS expr') + @_("expr PLUS expr") def expr(self, p): return p.expr0 + p.expr1 - @_('expr MINUS expr') + @_("expr MINUS expr") def expr(self, p): return p.expr0 - p.expr1 - @_('expr TIMES expr') + @_("expr TIMES expr") def expr(self, p): return p.expr0 * p.expr1 - @_('expr DIVIDE expr') + @_("expr DIVIDE expr") def expr(self, p): return p.expr0 / p.expr1 - @_('MINUS expr %prec UMINUS') + @_("MINUS expr %prec UMINUS") def expr(self, p): return -p.expr - @_('LPAREN expr RPAREN') + @_("LPAREN expr RPAREN") def expr(self, p): return p.expr - @_('NUMBER') + @_("NUMBER") def expr(self, p) -> int: return int(p.NUMBER) - @_('NAME') + @_("NAME") def expr(self, p): try: return self.names[p.NAME] except LookupError: - print(f'Undefined name {p.NAME!r}') + print(f"Undefined name {p.NAME!r}") return 0 -if __name__ == '__main__': +if __name__ == "__main__": lexer = CalcLexer() parser = CalcParser() - text = '2 + 2 * 2' + text = "2 + 2 * 2" print(list(lexer.tokenize(text))) # [ # Token(type='NUMBER', value='2', lineno=1, index=0, end=1), @@ -113,23 +113,23 @@ def expr(self, p): # ] value = parser.parse(lexer.tokenize(text)) - print(f'{text!r} = {value}') + print(f"{text!r} = {value}") # '2 + 2 * 2' = 6 print() items = [ - 'a = 2', - 'a = a * 2', - 'b = 2', - 'a + b + 1', + "a = 2", + "a = a * 2", + "b = 2", + "a + b + 1", ] for line in items: value = parser.parse(lexer.tokenize(line)) if value is not None: - print(f'{line!r} = {value}') + print(f"{line!r} = {value}") else: - print(f'{line!r}') + print(f"{line!r}") """ 'a = 2' 'a = a * 2' @@ -141,7 +141,7 @@ def expr(self, p): while True: try: - text = input('calc > ') + text = input("calc > ") except EOFError: break if text: diff --git a/lex_yacc__examples/sly__examples/json_by_goodmami.py b/lex_yacc__examples/sly__examples/json_by_goodmami.py index 1fbbc1cc5..157fd8f85 100644 --- a/lex_yacc__examples/sly__examples/json_by_goodmami.py +++ b/lex_yacc__examples/sly__examples/json_by_goodmami.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/goodmami/python-parsing-benchmarks/blob/611cfc20ca7b61f1f489b0e56f201f6888a5c67b/bench/helpers.py @@ -23,23 +23,23 @@ _json_unesc_re = re.compile(r'\\(["/\\bfnrt]|u[0-9A-Fa-f])') _json_unesc_map = { '"': '"', - '/': '/', - '\\': '\\', - 'b': '\b', - 'f': '\f', - 'n': '\n', - 'r': '\r', - 't': '\t', + "/": "/", + "\\": "\\", + "b": "\b", + "f": "\f", + "n": "\n", + "r": "\r", + "t": "\t", } def _json_unescape(m): c = m.group(1) - if c[0] == 'u': + if c[0] == "u": return chr(int(c[1:], 16)) c2 = _json_unesc_map.get(c) if not c2: - raise ValueError(f'invalid escape sequence: {m.group(0)}') + raise ValueError(f"invalid escape sequence: {m.group(0)}") return c2 @@ -49,11 +49,11 @@ def json_unescape(s): def compile(): class JsonLexer(Lexer): - tokens = { STRING, NUMBER, TRUE, FALSE, NULL } - ignore = ' \t\n\r' - literals = { '{', '}', '[', ']', ':', ',' } + tokens = {STRING, NUMBER, TRUE, FALSE, NULL} + ignore = " \t\n\r" + literals = {"{", "}", "[", "]", ":", ","} - @_(r'-?(0|[1-9][0-9]*)(\.[0-9]+)?([Ee][+-]?[0-9]+)?') + @_(r"-?(0|[1-9][0-9]*)(\.[0-9]+)?([Ee][+-]?[0-9]+)?") def NUMBER(self, t): try: t.value = int(t.value) @@ -66,25 +66,24 @@ def STRING(self, t): t.value = json_unescape(t.value) return t - @_(r'true') + @_(r"true") def TRUE(self, t): t.value = True return t - @_(r'false') + @_(r"false") def FALSE(self, t): t.value = False return t - @_(r'null') + @_(r"null") def NULL(self, t): t.value = None return t - class JsonParser(Parser): tokens = JsonLexer.tokens - start = 'value' + start = "value" @_(r'"{" [ pairs ] "}"') def value(self, p): @@ -112,11 +111,7 @@ def value(self, p): def items(self, p): return [p.value0] + p.value1 - @_('STRING', - 'NUMBER', - 'TRUE', - 'FALSE', - 'NULL') + @_("STRING", "NUMBER", "TRUE", "FALSE", "NULL") def value(self, p): return p[0] @@ -132,7 +127,7 @@ def error(self, p): parse = compile() -if __name__ == '__main__': +if __name__ == "__main__": text = '{"abc": [1, 2.5, 3.0, true, null], "value": "123"}' print(parse(text)) diff --git a/lex_yacc__examples/sly__examples/json_by_hadware.py b/lex_yacc__examples/sly__examples/json_by_hadware.py index 611b5b0a9..8694f3572 100644 --- a/lex_yacc__examples/sly__examples/json_by_hadware.py +++ b/lex_yacc__examples/sly__examples/json_by_hadware.py @@ -5,12 +5,12 @@ class JSONLexer(Lexer): tokens = {"FLOAT", "INTEGER", "STRING"} - literals = {'{', '}', '[', ']', ',', ':'} + literals = {"{", "}", "[", "]", ",", ":"} ignore = " \t\n" @_(r"\".*?\"") def STRING(self, t): - t.value = t.value.strip("\"") + t.value = t.value.strip('"') return t @_(r"\d+\.\d*") @@ -28,8 +28,7 @@ class JSONParser(Parser): tokens = JSONLexer.tokens start = "json" - @_('object', - 'array') + @_("object", "array") def json(self, p): return p[0] @@ -37,7 +36,7 @@ def json(self, p): def object(self, p): return {key: value for key, value in p.members} - @_('pair') + @_("pair") def members(self, p): return [p.pair] @@ -53,7 +52,7 @@ def pair(self, p): def array(self, p): return p.elements - @_('value') + @_("value") def elements(self, p): return [p.value] @@ -61,11 +60,7 @@ def elements(self, p): def elements(self, p): return [p.value] + p.elements - @_('STRING', - 'INTEGER', - 'FLOAT', - 'object', - 'array') + @_("STRING", "INTEGER", "FLOAT", "object", "array") def value(self, p): return p[0] diff --git a/lex_yacc__examples/sly__examples/key=value.py b/lex_yacc__examples/sly__examples/key=value.py index f1fc4c311..5f240181b 100644 --- a/lex_yacc__examples/sly__examples/key=value.py +++ b/lex_yacc__examples/sly__examples/key=value.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install sly @@ -10,21 +10,21 @@ class MyLexer(Lexer): tokens = {NAME, ASSIGN, VALUE} - ignore = ' \t' + ignore = " \t" # Tokens - NAME = r'[a-zA-Z_][a-zA-Z0-9_]*' - ASSIGN = '=' - VALUE = r'.+' + NAME = r"[a-zA-Z_][a-zA-Z0-9_]*" + ASSIGN = "=" + VALUE = r".+" # Ignored pattern - ignore_newline = r'\n+' + ignore_newline = r"\n+" # Extra action for newlines - def ignore_newline(self, t): - self.lineno += t.value.count('\n') + def ignore_newline(self, t) -> None: + self.lineno += t.value.count("\n") - def error(self, t): + def error(self, t) -> None: print(f"Illegal character {t.value[0]!r}") self.index += 1 @@ -32,19 +32,19 @@ def error(self, t): class MyParser(Parser): tokens = MyLexer.tokens - def __init__(self): + def __init__(self) -> None: self.names = dict() - @_('NAME ASSIGN VALUE') - def statement(self, p): + @_("NAME ASSIGN VALUE") + def statement(self, p) -> None: self.names[p.NAME] = p.VALUE -if __name__ == '__main__': +if __name__ == "__main__": lexer = MyLexer() - text = 'abc=123' - print(*list(lexer.tokenize(text)), sep='\n') + text = "abc=123" + print(*list(lexer.tokenize(text)), sep="\n") """ Token(type='NAME', value='abc', lineno=1, index=0, end=3) Token(type='ASSIGN', value='=', lineno=1, index=3, end=4) @@ -54,7 +54,7 @@ def statement(self, p): parser = MyParser() - lines = ['abc=123', 'x = 999', 'y = 111'] + lines = ["abc=123", "x = 999", "y = 111"] for line in lines: tokens = lexer.tokenize(line) parser.parse(tokens) diff --git a/list_split_by_pairs.py b/list_split_by_pairs.py index 727b7112a..a7734a1ba 100644 --- a/list_split_by_pairs.py +++ b/list_split_by_pairs.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" items = [5, 80, 3, 83, 1, 77, 1, 77, 2, 82, 1, 77, 5, 81, 2, 78, 1, 81, 5, 85, 5, 85, 4, 84, 2, 78, 1, 81, 3, 83] diff --git a/lived time.py b/lived time.py index a812fb90e..c54571569 100644 --- a/lived time.py +++ b/lived time.py @@ -1,11 +1,13 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import datetime + + my_bd = datetime.datetime(day=18, month=8, year=1992) my_life = datetime.datetime.today() - my_bd -print('lived time: days = {} <=> seconds = {}'.format(my_life.days, int(my_life.total_seconds()))) +print(f"lived time: days = {my_life.days} <=> seconds = {int(my_life.total_seconds())}") diff --git a/load_and_exec_py_from_url.py b/load_and_exec_py_from_url.py index 552977e1b..591734c0e 100644 --- a/load_and_exec_py_from_url.py +++ b/load_and_exec_py_from_url.py @@ -1,12 +1,13 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from urllib.request import urlopen -url = 'https://raw.githubusercontent.com/gil9red/SimplePyScripts/c4ef2f1636f7d75b87807e858ea1eea6116df773/print_triangle.py' + +url = "https://raw.githubusercontent.com/gil9red/SimplePyScripts/c4ef2f1636f7d75b87807e858ea1eea6116df773/print_triangle.py" with urlopen(url) as f: exec(f.read()) diff --git a/logged_human_time_to_seconds.py b/logged_human_time_to_seconds.py deleted file mode 100644 index 1289cbf02..000000000 --- a/logged_human_time_to_seconds.py +++ /dev/null @@ -1,73 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -__author__ = 'ipetrash' - - -def logged_human_time_to_seconds(human_time: str) -> int: - """Конвертирование человеко-читаемого времени в секунды. - - >>> logged_human_time_to_seconds("1 minute") - 60 - >>> logged_human_time_to_seconds("1 hour") - 3600 - >>> logged_human_time_to_seconds("6 hours, 30 minutes") - 23400 - >>> logged_human_time_to_seconds("1 day, 30 minutes") - 30600 - """ - - # Jira help: - # You can specify a time unit after a time value 'X', such as Xw, Xd, Xh or Xm, to represent weeks (w), - # days (d), hours (h) and minutes (m), respectively. - # If you do not specify a time unit, minute will be assumed. - # Your current conversion rates are 1w = 5d and 1d = 8h. - - total_seconds = 0 - - for part in human_time.split(', '): - value, metric = part.lower().split() - value = int(value) - - if 'minute' in metric: - total_seconds += value * 60 - - elif 'hour' in metric: - total_seconds += value * 60 * 60 - - elif 'day' in metric: - total_seconds += value * 8 * 60 * 60 - - elif 'week' in metric: - total_seconds += value * 5 * 8 * 60 * 60 - - return total_seconds - - -if __name__ == '__main__': - text = """\ -2 hours -1 hour, 30 minutes -4 hours -7 hours -1 day, 1 hour -6 hours, 30 minutes -30 minutes -1 day -1 hour -4 hours, 30 minutes -40 minutes -7 hours, 30 minutes -1 day, 30 minutes -5 hours -5 hours, 30 minutes -1 minute -1 hour, 20 minutes -6 hours -3 hours, 30 minutes -15 minutes -3 hours -""" - - for line in list(set(text.splitlines())): - print(line, logged_human_time_to_seconds(line)) diff --git a/logger_example.py b/logger_example.py deleted file mode 100644 index 3f34cec4b..000000000 --- a/logger_example.py +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -__author__ = 'ipetrash' - - -def get_logger(name, file='log.txt', encoding='utf-8'): - import logging - log = logging.getLogger(name) - log.setLevel(logging.DEBUG) - - formatter = logging.Formatter('[%(asctime)s] %(filename)s:%(lineno)d %(levelname)-8s %(message)s') - - # Simple file handler - # fh = logging.FileHandler(file, encoding=encoding) - # or: - from logging.handlers import RotatingFileHandler - fh = RotatingFileHandler(file, maxBytes=10000000, backupCount=5, encoding=encoding) - fh.setFormatter(formatter) - log.addHandler(fh) - - import sys - sh = logging.StreamHandler(stream=sys.stdout) - sh.setFormatter(formatter) - log.addHandler(sh) - - return log - - -log = get_logger(__file__) - - -if __name__ == '__main__': - log.debug('foo') - log.debug('bar') - log.debug(__file__) diff --git a/logging__examples/console_and_rotating_file_handler.py b/logging__examples/console_and_rotating_file_handler.py new file mode 100644 index 000000000..d074e0874 --- /dev/null +++ b/logging__examples/console_and_rotating_file_handler.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import logging +import sys + +from logging.handlers import RotatingFileHandler + + +def get_logger(name, file="log.txt", encoding="utf-8"): + log = logging.getLogger(name) + log.setLevel(logging.DEBUG) + + formatter = logging.Formatter( + "[%(asctime)s] %(filename)s:%(lineno)d %(levelname)-8s %(message)s" + ) + + # Simple file handler + # fh = logging.FileHandler(file, encoding=encoding) + # or: + fh = RotatingFileHandler( + file, maxBytes=10_000_000, backupCount=5, encoding=encoding + ) + fh.setFormatter(formatter) + log.addHandler(fh) + + sh = logging.StreamHandler(stream=sys.stdout) + sh.setFormatter(formatter) + log.addHandler(sh) + + return log + + +log = get_logger(__file__) + + +if __name__ == "__main__": + log.debug("foo") + log.debug("bar") + log.debug(__file__) diff --git a/logging__examples/log-config.yaml b/logging__examples/log-config.yaml new file mode 100644 index 000000000..b2e5fccfc --- /dev/null +++ b/logging__examples/log-config.yaml @@ -0,0 +1,33 @@ +version: 1 + +formatters: + default: + format: "[%(asctime)s] %(name)-10s %(filename)s[LINE:%(lineno)d] %(levelname)-8s %(message)s" + +handlers: + console: + class: "logging.StreamHandler" + formatter: "default" + stream: "ext://sys.stdout" + + main-file: &main-file + class: "logging.handlers.RotatingFileHandler" + formatter: "default" + filename: "main.log" + encoding: "utf-8" + backupCount: 5 + maxBytes: 10000000 + delay: true + + main2-file: + <<: *main-file + filename: "main2.log" + +loggers: + main: + handlers: ["console", "main-file"] + level: "DEBUG" + + main2: + handlers: ["console", "main2-file"] + level: "INFO" diff --git a/logging__examples/logger_from_dict.py b/logging__examples/logger_from_dict.py new file mode 100644 index 000000000..bf1b9c108 --- /dev/null +++ b/logging__examples/logger_from_dict.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import logging.config +from logging import getLogger +from pathlib import Path +from typing import Any + + +DIR_LOGS: Path = Path(__file__).resolve().parent / "logs" +DIR_LOGS.mkdir(parents=True, exist_ok=True) + + +LOGGING: dict[str, Any] = { + "version": 1, + "formatters": { + "default": { + "format": "[%(asctime)s] %(name)-10s %(filename)s[LINE:%(lineno)d] %(levelname)-8s %(message)s", + } + }, + "handlers": { + "console": { + "class": "logging.StreamHandler", + "stream": "ext://sys.stdout", + "formatter": "default", + }, + "main-file": { + "class": "logging.handlers.RotatingFileHandler", + "filename": "main.log", + "maxBytes": 10_000_000, + "backupCount": 5, + "encoding": "utf-8", + "formatter": "default", + }, + "main2-file": { + "class": "logging.handlers.RotatingFileHandler", + "filename": "main2.log", + "maxBytes": 10_000_000, + "backupCount": 5, + "encoding": "utf-8", + "formatter": "default", + }, + }, + "loggers": { + "main": { + "level": "DEBUG", + "handlers": [ + "console", + "main-file", + ] + }, + "main2": { + "level": "INFO", + "handlers": [ + "console", + "main2-file", + ] + }, + }, +} +for handler in LOGGING["handlers"].values(): + try: + handler["filename"] = DIR_LOGS / handler["filename"] + except KeyError: + pass + + +logging.config.dictConfig(LOGGING) + +log = getLogger("main") +log.debug("DEBUG") +log.info("INFO") + +print() + +log = getLogger("main2") +log.debug("DEBUG") +log.info("INFO") diff --git a/logging__examples/logger_from_yaml.py b/logging__examples/logger_from_yaml.py new file mode 100644 index 000000000..5e9d1b926 --- /dev/null +++ b/logging__examples/logger_from_yaml.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import logging.config +from logging import getLogger +from pathlib import Path +from typing import Any + +# pip install PyYAML +import yaml + + +DIR: Path = Path(__file__).resolve().parent + +DIR_LOGS: Path = DIR / "logs" +DIR_LOGS.mkdir(parents=True, exist_ok=True) + +CONFIG_LOG_FILE_NAME: Path = DIR / "log-config.yaml" + + +LOGGING: dict[str, Any] = yaml.safe_load( + CONFIG_LOG_FILE_NAME.read_text("utf-8") +) +for handler in LOGGING["handlers"].values(): + try: + handler["filename"] = DIR_LOGS / handler["filename"] + except KeyError: + pass + + +logging.config.dictConfig(LOGGING) + +log = getLogger("main") +log.debug("DEBUG") +log.info("INFO") + +print() + +log = getLogger("main2") +log.debug("DEBUG") +log.info("INFO") diff --git a/login_vk.com.py b/login_vk.com.py index f837fb718..cb667f76b 100644 --- a/login_vk.com.py +++ b/login_vk.com.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import sys @@ -18,37 +18,39 @@ QWebSettings.globalSettings().setAttribute(QWebSettings.DeveloperExtrasEnabled, True) # Телефон или емейл -LOGIN = '' -PASSWORD = '' +LOGIN = "" +PASSWORD = "" -URL = 'https://vk.com' +URL = "https://vk.com" -if __name__ == '__main__': - app = QApplication(sys.argv) - view = QWebView() - view.show() +app = QApplication(sys.argv) - view.load(URL) +view = QWebView() +view.show() - loop = QEventLoop() - view.loadFinished.connect(loop.quit) - loop.exec_() +view.load(URL) - doc = view.page().mainFrame().documentElement() +loop = QEventLoop() +view.loadFinished.connect(loop.quit) +loop.exec_() - email = doc.findFirst('#quick_email') - password = doc.findFirst('#quick_pass') - login_button = doc.findFirst('#quick_login_button') +doc = view.page().mainFrame().documentElement() - if email.isNull() or password.isNull() or login_button.isNull(): - raise Exception('Ошибка при авторизации: не найдены поля емейла или пароля, или кнопка "Войти".') +email = doc.findFirst("#quick_email") +password = doc.findFirst("#quick_pass") +login_button = doc.findFirst("#quick_login_button") - # Заполняем поля емейла/телефона и пароля - email.setAttribute('value', LOGIN) - password.setAttribute('value', PASSWORD) +if email.isNull() or password.isNull() or login_button.isNull(): + raise Exception( + 'Ошибка при авторизации: не найдены поля емейла или пароля, или кнопка "Войти".' + ) - # Кликаем на кнопку "Войти" - login_button.evaluateJavaScript('this.click()') +# Заполняем поля емейла/телефона и пароля +email.setAttribute("value", LOGIN) +password.setAttribute("value", PASSWORD) - sys.exit(app.exec_()) +# Кликаем на кнопку "Войти" +login_button.evaluateJavaScript("this.click()") + +sys.exit(app.exec_()) diff --git a/magtu_ru/mgtu_get_students.py b/magtu_ru/mgtu_get_students.py index b2cd556ae..428327d7a 100644 --- a/magtu_ru/mgtu_get_students.py +++ b/magtu_ru/mgtu_get_students.py @@ -1,27 +1,35 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import requests -rs = requests.get('http://magtu.ru/modules/mod_reiting/mobile.php?action=get_all_department') + + +rs = requests.get( + "http://magtu.ru/modules/mod_reiting/mobile.php?action=get_all_department" +) for dep in rs.json(): id_dep = list(dep.keys())[0] name = dep[id_dep] print(id_dep, name) - rs = requests.get('http://magtu.ru/modules/mod_reiting/mobile.php?action=get_spec_by_depart&depart_kod=' + id_dep) + rs = requests.get( + f"http://magtu.ru/modules/mod_reiting/mobile.php?action=get_spec_by_depart&depart_kod={id_dep}" + ) for kaf in rs.json(): id_kaf = list(kaf.keys())[0] name = kaf[id_kaf] - print(' ' + id_kaf, name) + print(" " + id_kaf, name) - rs = requests.get('http://magtu.ru/modules/mod_reiting/mobile.php?action=get_reiting&spec_kod=' + id_kaf) + rs = requests.get( + f"http://magtu.ru/modules/mod_reiting/mobile.php?action=get_reiting&spec_kod={id_kaf}" + ) for i, stud in enumerate(rs.json(), 1): name = stud[0] - print(' {}. {}'.format(i, name)) + print(f" {i}. {name}") print() diff --git a/magtu_ru/mgtu_get_students_gui.py b/magtu_ru/mgtu_get_students_gui.py index 4c9383c69..ee290cb13 100644 --- a/magtu_ru/mgtu_get_students_gui.py +++ b/magtu_ru/mgtu_get_students_gui.py @@ -1,12 +1,11 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import requests - try: from PyQt5.QtGui import * from PyQt5.QtWidgets import * @@ -19,7 +18,7 @@ class Widget(QWidget): - def __init__(self): + def __init__(self) -> None: super().__init__() self.lw_dep = QListWidget() @@ -37,9 +36,11 @@ def __init__(self): self.setLayout(layout) - def fill(self): + def fill(self) -> None: self.lw_dep.clear() - rs = requests.get('http://magtu.ru/modules/mod_reiting/mobile.php?action=get_all_department') + rs = requests.get( + "http://magtu.ru/modules/mod_reiting/mobile.php?action=get_all_department" + ) rs.raise_for_status() for dep in rs.json(): @@ -50,11 +51,13 @@ def fill(self): item.setData(Qt.UserRole, id_dep) self.lw_dep.addItem(item) - def fill_kaf(self, item_dep): + def fill_kaf(self, item_dep) -> None: self.lw_kaf.clear() id_dep = item_dep.data(Qt.UserRole) - rs = requests.get('http://magtu.ru/modules/mod_reiting/mobile.php?action=get_spec_by_depart&depart_kod=' + id_dep) + rs = requests.get( + f"http://magtu.ru/modules/mod_reiting/mobile.php?action=get_spec_by_depart&depart_kod={id_dep}" + ) rs.raise_for_status() for kaf in rs.json(): @@ -65,11 +68,13 @@ def fill_kaf(self, item_dep): item.setData(Qt.UserRole, id_kaf) self.lw_kaf.addItem(item) - def fill_stu(self, item_kaf): + def fill_stu(self, item_kaf) -> None: self.lw_stu.clear() id_kaf = item_kaf.data(Qt.UserRole) - rs = requests.get('http://magtu.ru/modules/mod_reiting/mobile.php?action=get_reiting&spec_kod=' + id_kaf) + rs = requests.get( + f"http://magtu.ru/modules/mod_reiting/mobile.php?action=get_reiting&spec_kod={id_kaf}" + ) rs.raise_for_status() for stud in rs.json(): diff --git a/magtu_ru/raspisanie-konsultatsij-prepodavatelej.py b/magtu_ru/raspisanie-konsultatsij-prepodavatelej.py index b104361d0..9440ad6e8 100644 --- a/magtu_ru/raspisanie-konsultatsij-prepodavatelej.py +++ b/magtu_ru/raspisanie-konsultatsij-prepodavatelej.py @@ -1,36 +1,36 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -url = 'http://www.magtu.ru/student/bakalavriat-spetsialitet-magistratura/raspisanie-konsultatsij-prepodavatelej.html' +from urllib.parse import urljoin import requests -rs = requests.get(url) - from bs4 import BeautifulSoup -root = BeautifulSoup(rs.content, 'html.parser') -from urllib.parse import urljoin -for tag in root.select('[itemprop=articleBody] > *'): +url = "http://www.magtu.ru/student/bakalavriat-spetsialitet-magistratura/raspisanie-konsultatsij-prepodavatelej.html" +rs = requests.get(url) + +root = BeautifulSoup(rs.content, "html.parser") +for tag in root.select("[itemprop=articleBody] > *"): name = tag.name - if name == 'h3': + if name == "h3": print(tag.text.upper()) continue - elif name == 'p': + elif name == "p": print(tag.text) continue - elif name == 'ul': - for li in tag.select('li'): + elif name == "ul": + for li in tag.select("li"): if li.a: - print(' "{}": {}'.format(li.a.text, urljoin(rs.url, li.a['href']))) + print(f' "{li.a.text}": {urljoin(rs.url, li.a["href"])}') else: - print(' "{}"'.format(li.text)) + print(f' "{li.text}"') print() continue diff --git a/magtu_ru/selection_of_specialty.py b/magtu_ru/selection_of_specialty.py index fdba66722..87273d5d1 100644 --- a/magtu_ru/selection_of_specialty.py +++ b/magtu_ru/selection_of_specialty.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """Скрипт получает список специальностей в формате HTML, разбирает таблицу и оформляет ее в JSON""" @@ -15,30 +15,28 @@ def element_to_text_list(el) -> str: - return ', '.join([child.strip() for child in el.strings]) + return ", ".join([child.strip() for child in el.strings]) -url = 'http://magtu.ru/modules/mod_abiturient_helper/tmpl/get_spec.php' +url = "http://magtu.ru/modules/mod_abiturient_helper/tmpl/get_spec.php" headers = { - 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:50.0) Gecko/20100101 Firefox/50.0', - 'X-Requested-With': 'XMLHttpRequest', + "User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:50.0) Gecko/20100101 Firefox/50.0", + "X-Requested-With": "XMLHttpRequest", } # Русский язык и Математика -post_data = { - 'data': '01,02' -} +post_data = {"data": "01,02"} rs = requests.post(url, headers=headers, data=post_data) if not rs.ok or not rs.text: - print('Post запрос не вернул данные таблицы. Возможно, не хватает каких-то данных.') + print("Post запрос не вернул данные таблицы. Возможно, не хватает каких-то данных.") sys.exit() -root = BeautifulSoup(rs.text, 'lxml') +root = BeautifulSoup(rs.text, "lxml") table_rows = [] -for tr in root.select('table tr')[1:]: - td_list = tr.select('td') +for tr in root.select("table tr")[1:]: + td_list = tr.select("td") row_data = { "number": element_to_text_list(td_list[0]), diff --git a/manual_auth_to_github.py b/manual_auth_to_github.py index a88180693..dae2744e0 100644 --- a/manual_auth_to_github.py +++ b/manual_auth_to_github.py @@ -1,42 +1,46 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """Скрипт для авторизации на github вручную""" -LOGIN = '' -PASSWORD = '' +import requests +from lxml import etree + + +LOGIN = "" +PASSWORD = "" -import requests session = requests.Session() -session.headers['User-Agent'] = 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:45.0) Gecko/20100101 Firefox/45.0' +session.headers[ + "User-Agent" +] = "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:45.0) Gecko/20100101 Firefox/45.0" -rs = session.get('https://github.com') +rs = session.get("https://github.com") print(rs) -rs = session.get('https://github.com/login') +rs = session.get("https://github.com/login") print(rs) -from lxml import etree root = etree.HTML(rs.content) input_name_by_value = dict() -for input_tag in root.xpath('//input'): +for input_tag in root.xpath("//input"): try: - input_name_by_value[input_tag.attrib['name']] = input_tag.attrib['value'] + input_name_by_value[input_tag.attrib["name"]] = input_tag.attrib["value"] except KeyError: pass -input_name_by_value['login'] = LOGIN -input_name_by_value['password'] = PASSWORD +input_name_by_value["login"] = LOGIN +input_name_by_value["password"] = PASSWORD -rs = session.post('https://github.com/session', data=input_name_by_value) +rs = session.post("https://github.com/session", data=input_name_by_value) print(rs) -rs = session.get('https://github.com/gil9red/search_in_users_github_gists') +rs = session.get("https://github.com/gil9red/search_in_users_github_gists") print(rs) print(rs.text) diff --git a/markdown__examples/common.py b/markdown__examples/common.py index 933473b62..ef293bab7 100644 --- a/markdown__examples/common.py +++ b/markdown__examples/common.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" EXAMPLE_MARKDOWN = """\ diff --git a/markdown__examples/hello_world.py b/markdown__examples/hello_world.py index c15082bb6..42fe7a160 100644 --- a/markdown__examples/hello_world.py +++ b/markdown__examples/hello_world.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install markdown diff --git a/markdown__examples/pyqt5_gui.py b/markdown__examples/pyqt5_gui.py index d8b08ed9a..237e163bc 100644 --- a/markdown__examples/pyqt5_gui.py +++ b/markdown__examples/pyqt5_gui.py @@ -1,13 +1,21 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from pathlib import Path # pip install pyqt5 -from PyQt5.QtWidgets import QApplication, QWidget, QTextEdit, QLabel, QSplitter, QVBoxLayout, QTabWidget +from PyQt5.QtWidgets import ( + QApplication, + QWidget, + QTextEdit, + QLabel, + QSplitter, + QVBoxLayout, + QTabWidget, +) # pip install markdown import markdown @@ -16,7 +24,7 @@ class MainWindow(QWidget): - def __init__(self): + def __init__(self) -> None: super().__init__() self.setWindowTitle(Path(__file__).stem) @@ -31,15 +39,15 @@ def __init__(self): self.edit_result_markdown.setReadOnly(True) left_side_layout = QVBoxLayout() - left_side_layout.addWidget(QLabel('Markdown:')) + left_side_layout.addWidget(QLabel("Markdown:")) left_side_layout.addWidget(self.edit_markdown) left_side = QWidget() left_side.setLayout(left_side_layout) tab_result = QTabWidget() - tab_result.addTab(self.edit_result_qt, 'Result (Qt)') - tab_result.addTab(self.edit_result_markdown, 'Result (markdown)') + tab_result.addTab(self.edit_result_qt, "Result (Qt)") + tab_result.addTab(self.edit_result_markdown, "Result (markdown)") splitter = QSplitter() splitter.addWidget(left_side) @@ -50,14 +58,14 @@ def __init__(self): self.setLayout(main_layout) - def _on_input_text_markdown(self): + def _on_input_text_markdown(self) -> None: self.edit_result_qt.setMarkdown(self.edit_markdown.toPlainText()) html = markdown.markdown(self.edit_markdown.toPlainText()) self.edit_result_markdown.setHtml(html) -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) mw = MainWindow() diff --git a/markdown__examples/pyqt6_gui.py b/markdown__examples/pyqt6_gui.py index ea74cb6c8..cb029aef6 100644 --- a/markdown__examples/pyqt6_gui.py +++ b/markdown__examples/pyqt6_gui.py @@ -1,13 +1,21 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from pathlib import Path # pip install pyqt6 -from PyQt6.QtWidgets import QApplication, QWidget, QTextEdit, QLabel, QSplitter, QVBoxLayout, QTabWidget +from PyQt6.QtWidgets import ( + QApplication, + QWidget, + QTextEdit, + QLabel, + QSplitter, + QVBoxLayout, + QTabWidget, +) # pip install markdown import markdown @@ -16,7 +24,7 @@ class MainWindow(QWidget): - def __init__(self): + def __init__(self) -> None: super().__init__() self.setWindowTitle(Path(__file__).stem) @@ -31,15 +39,15 @@ def __init__(self): self.edit_result_markdown.setReadOnly(True) left_side_layout = QVBoxLayout() - left_side_layout.addWidget(QLabel('Markdown:')) + left_side_layout.addWidget(QLabel("Markdown:")) left_side_layout.addWidget(self.edit_markdown) left_side = QWidget() left_side.setLayout(left_side_layout) tab_result = QTabWidget() - tab_result.addTab(self.edit_result_qt, 'Result (Qt)') - tab_result.addTab(self.edit_result_markdown, 'Result (markdown)') + tab_result.addTab(self.edit_result_qt, "Result (Qt)") + tab_result.addTab(self.edit_result_markdown, "Result (markdown)") splitter = QSplitter() splitter.addWidget(left_side) @@ -50,14 +58,14 @@ def __init__(self): self.setLayout(main_layout) - def _on_input_text_markdown(self): + def _on_input_text_markdown(self) -> None: self.edit_result_qt.setMarkdown(self.edit_markdown.toPlainText()) html = markdown.markdown(self.edit_markdown.toPlainText()) self.edit_result_markdown.setHtml(html) -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) mw = MainWindow() diff --git a/matplotlib__examples/animate.py b/matplotlib__examples/animate.py index 73430f1aa..6bfe34666 100644 --- a/matplotlib__examples/animate.py +++ b/matplotlib__examples/animate.py @@ -1,31 +1,34 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -import psutil -from datetime import datetime as dt from collections import deque +from datetime import datetime as dt + +import matplotlib.animation as animation import matplotlib.pyplot as plt from matplotlib.dates import date2num -import matplotlib.animation as animation + +import psutil + N = 600 x = deque([date2num(dt.now())], maxlen=N) y = deque([0], maxlen=N) fig, ax = plt.subplots(figsize=(8, 3)) -line, = ax.plot_date(x, y, marker="") +(line,) = ax.plot_date(x, y, marker="") -ax.spines['left'].set_visible(False) -ax.spines['bottom'].set_visible(False) -ax.spines['top'].set_visible(False) -ax.spines['right'].set_visible(False) +ax.spines["left"].set_visible(False) +ax.spines["bottom"].set_visible(False) +ax.spines["top"].set_visible(False) +ax.spines["right"].set_visible(False) def get_data(): - return psutil.cpu_percent(.15) + return psutil.cpu_percent(0.15) def animate(i): @@ -34,8 +37,8 @@ def animate(i): ax.relim(visible_only=True) ax.autoscale_view(True) line.set_data(x, y) - ax.fill_between(x, -0.5, y, color='lightgrey') - return line, + ax.fill_between(x, -0.5, y, color="lightgrey") + return (line,) ani = animation.FuncAnimation(fig, animate, interval=300) diff --git a/matplotlib__examples/examples/exm1.py b/matplotlib__examples/examples/exm1.py index 59655c5ea..fb02671fd 100644 --- a/matplotlib__examples/examples/exm1.py +++ b/matplotlib__examples/examples/exm1.py @@ -1,4 +1,4 @@ -__author__ = 'ipetrash' +__author__ = "ipetrash" # Следующий пример строит график функции f(x) = x / sin(x): @@ -11,7 +11,7 @@ # !!! Импортируем пакет со вспомогательными функциями from matplotlib__examples import mlab -if __name__ == '__main__': +if __name__ == "__main__": # Будем рисовать график этой функции def func(x): """ @@ -19,7 +19,7 @@ def func(x): """ if x == 0: return 1.0 - return math.sin (x) / x + return math.sin(x) / x # Интервал изменения переменной по оси X xmin = -20.0 @@ -29,13 +29,13 @@ def func(x): dx = 0.01 # !!! Создадим список координат по оси X на отрезке [-xmin; xmax], включая концы - xlist = mlab.frange (xmin, xmax, dx) + xlist = mlab.frange(xmin, xmax, dx) # Вычислим значение функции в заданных точках - ylist = [func (x) for x in xlist] + ylist = [func(x) for x in xlist] # !!! Нарисуем одномерный график - pylab.plot (xlist, ylist) + pylab.plot(xlist, ylist) # !!! Покажем окно с нарисованным графиком - pylab.show() \ No newline at end of file + pylab.show() diff --git a/matplotlib__examples/examples/exm10.py b/matplotlib__examples/examples/exm10.py index b3b3df635..c3650f05e 100644 --- a/matplotlib__examples/examples/exm10.py +++ b/matplotlib__examples/examples/exm10.py @@ -1,4 +1,4 @@ -__author__ = 'ipetrash' +__author__ = "ipetrash" # Все классы для работы с трехмерными графиками находятся в пакете mpl_toolkits.mplot3d, @@ -14,8 +14,8 @@ import pylab from mpl_toolkits.mplot3d import Axes3D -if __name__ == '__main__': +if __name__ == "__main__": fig = pylab.figure() Axes3D(fig) - pylab.show() \ No newline at end of file + pylab.show() diff --git a/matplotlib__examples/examples/exm11.py b/matplotlib__examples/examples/exm11.py index 8ed3ec829..e57d4b340 100644 --- a/matplotlib__examples/examples/exm11.py +++ b/matplotlib__examples/examples/exm11.py @@ -1,23 +1,23 @@ -__author__ = 'ipetrash' +__author__ = "ipetrash" +import numpy import pylab from mpl_toolkits.mplot3d import Axes3D -import numpy # Эта функция возвращает три двумерные матрицы: x, y, z. # Координаты x и y лежат в интервале от -10 до 10 с шагом 0.1. -def makeData (): - x = numpy.arange (-10, 10, 0.1) - y = numpy.arange (-10, 10, 0.1) +def makeData(): + x = numpy.arange(-10, 10, 0.1) + y = numpy.arange(-10, 10, 0.1) xgrid, ygrid = numpy.meshgrid(x, y) - zgrid = numpy.sin (xgrid) * numpy.sin (ygrid) / (xgrid * ygrid) + zgrid = numpy.sin(xgrid) * numpy.sin(ygrid) / (xgrid * ygrid) return xgrid, ygrid, zgrid -if __name__ == '__main__': +if __name__ == "__main__": x, y, z = makeData() # Чтобы отобразить наши данные, достаточно вызвать метод plot_surface() @@ -31,4 +31,4 @@ def makeData (): # axes.plot_surface(x, y, z, rstride=5, cstride=5) # Установка шага сетки (при 5, очень мелкая сетка) # axes.plot_surface(x, y, z, rstride=20, cstride=20) # Установка шага сетки (при 20, крупная сетка) - pylab.show() \ No newline at end of file + pylab.show() diff --git a/matplotlib__examples/examples/exm12.py b/matplotlib__examples/examples/exm12.py index c93d45a38..b16136963 100644 --- a/matplotlib__examples/examples/exm12.py +++ b/matplotlib__examples/examples/exm12.py @@ -1,4 +1,4 @@ -__author__ = 'ipetrash' +__author__ = "ipetrash" # Цветовые карты используются, если нужно указать в какие цвета должны окрашиваться @@ -11,23 +11,25 @@ # чтобы создать градиент перехода от синего цвета к красному через белый. +import numpy import pylab + from mpl_toolkits.mplot3d import Axes3D from matplotlib__examples.colors import LinearSegmentedColormap -import numpy + # Эта функция возвращает три двумерные матрицы: x, y, z. # Координаты x и y лежат в интервале от -10 до 10 с шагом 0.1. -def makeData (): - x = numpy.arange (-10, 10, 0.1) - y = numpy.arange (-10, 10, 0.1) +def makeData(): + x = numpy.arange(-10, 10, 0.1) + y = numpy.arange(-10, 10, 0.1) xgrid, ygrid = numpy.meshgrid(x, y) - zgrid = numpy.sin (xgrid) * numpy.sin (ygrid) / (xgrid * ygrid) + zgrid = numpy.sin(xgrid) * numpy.sin(ygrid) / (xgrid * ygrid) return xgrid, ygrid, zgrid -if __name__ == '__main__': +if __name__ == "__main__": x, y, z = makeData() # Чтобы отобразить наши данные, достаточно вызвать метод plot_surface() @@ -54,7 +56,7 @@ def makeData (): # color_map = cm.cmap_d["jet"] # axes.plot_surface(x, y, z, rstride=4, cstride=4, cmap=color_map) - color_map = LinearSegmentedColormap.from_list("red_blue", ['b', 'w', 'r'], 256) + color_map = LinearSegmentedColormap.from_list("red_blue", ["b", "w", "r"], 256) axes.plot_surface(x, y, z, rstride=3, cstride=3, cmap=color_map) - pylab.show() \ No newline at end of file + pylab.show() diff --git a/matplotlib__examples/examples/exm13.py b/matplotlib__examples/examples/exm13.py index 2d410e818..50768bc89 100644 --- a/matplotlib__examples/examples/exm13.py +++ b/matplotlib__examples/examples/exm13.py @@ -1,4 +1,4 @@ -__author__ = 'ipetrash' +__author__ = "ipetrash" import math @@ -9,6 +9,7 @@ # !!! Импортируем пакет со вспомогательными функциями from matplotlib__examples import mlab + # Будем рисовать график этой функции def func(x): """ @@ -19,7 +20,7 @@ def func(x): return math.sin(x) / x -if __name__ == '__main__': +if __name__ == "__main__": # Интервал изменения переменной по оси X xmin = -20.0 xmax = 20.0 @@ -28,10 +29,10 @@ def func(x): dx = 0.2 # !!! Создадим список координат по оси X на отрезке [-xmin; xmax], включая концы - xlist = mlab.frange (xmin, xmax, dx) + xlist = mlab.frange(xmin, xmax, dx) # Вычислим значение функции в заданных точках - ylist = [func (x) for x in xlist] + ylist = [func(x) for x in xlist] # !!! Нарисуем одномерный график с использованием стиля pylab.plot(xlist, ylist, "x") @@ -50,4 +51,4 @@ def func(x): # pylab.plot(xlist, ylist, "-*k") # !!! Покажем окно с нарисованным графиком - pylab.show() \ No newline at end of file + pylab.show() diff --git a/matplotlib__examples/examples/exm14.py b/matplotlib__examples/examples/exm14.py index 25fe00d23..ad26cf7a4 100644 --- a/matplotlib__examples/examples/exm14.py +++ b/matplotlib__examples/examples/exm14.py @@ -1,4 +1,4 @@ -__author__ = 'ipetrash' +__author__ = "ipetrash" import math @@ -9,6 +9,7 @@ # !!! Импортируем пакет со вспомогательными функциями from matplotlib__examples import mlab + # Будем рисовать график этой функции def func(x): """ @@ -19,7 +20,7 @@ def func(x): return math.sin(x) / x -if __name__ == '__main__': +if __name__ == "__main__": # Интервал изменения переменной по оси X xmin = -20.0 xmax = 20.0 @@ -28,10 +29,10 @@ def func(x): dx = 0.2 # !!! Создадим список координат по оси X на отрезке [-xmin; xmax], включая концы - xlist = mlab.frange (xmin, xmax, dx) + xlist = mlab.frange(xmin, xmax, dx) # Вычислим значение функции в заданных точках - ylist = [func (x) for x in xlist] + ylist = [func(x) for x in xlist] # # !!! Нарисуем одномерный график с использованием стиля # pylab.plot(xlist, ylist, "x") @@ -58,8 +59,9 @@ def func(x): # pylab.plot (xlist, ylist, linestyle = "-", marker = "*", color = "k") # Результат будет выглядеть точно также. А, например, следующий код - pylab.plot(xlist, ylist, linestyle="-", marker="o", color="k", - markerfacecolor="#ff22aa") + pylab.plot( + xlist, ylist, linestyle="-", marker="o", color="k", markerfacecolor="#ff22aa" + ) # !!! Покажем окно с нарисованным графиком - pylab.show() \ No newline at end of file + pylab.show() diff --git a/matplotlib__examples/examples/exm15.py b/matplotlib__examples/examples/exm15.py index 3479aff2b..7f45e86fc 100644 --- a/matplotlib__examples/examples/exm15.py +++ b/matplotlib__examples/examples/exm15.py @@ -1,4 +1,4 @@ -__author__ = 'ipetrash' +__author__ = "ipetrash" # Использование библиотеки Matplotlib. Как отобразить легенду @@ -14,17 +14,18 @@ # Импортируем пакет со вспомогательными функциями from matplotlib__examples import mlab + # Будем рисовать график этой функции -def func (x): +def func(x): """ sinc (x) """ if x == 0: return 1.0 - return math.sin (x) / x + return math.sin(x) / x -if __name__ == '__main__': +if __name__ == "__main__": # Интервал изменения переменной по оси X xmin = -20.0 xmax = 20.0 @@ -33,12 +34,11 @@ def func (x): dx = 0.01 # Создадим список координат по оси X на отрезке [-xmin; xmax], включая концы - xlist = mlab.frange (xmin, xmax, dx) + xlist = mlab.frange(xmin, xmax, dx) # Вычислим значение функции в заданных точках - ylist1 = [func (x) for x in xlist] - ylist2 = [func (x * 0.2) for x in xlist] - + ylist1 = [func(x) for x in xlist] + ylist2 = [func(x * 0.2) for x in xlist] ## Первый способ # В качестве параметра функции legend() нужно передать список или кортеж, @@ -49,16 +49,15 @@ def func (x): # виде легенды. # Нарисуем два одномерных графика - pylab.plot (xlist, ylist1, "b-") - pylab.plot (xlist, ylist2, "g--") + pylab.plot(xlist, ylist1, "b-") + pylab.plot(xlist, ylist2, "g--") # !!! Добавим легенду. # !!! Первому графику будет соответствовать надпись "f(x)", # !!! А второму - "f(0.2 * x)" - pylab.legend ( ("f(x)", "f(0.2 * x)") ) + pylab.legend(("f(x)", "f(0.2 * x)")) ## Первый способ - ## Второй способ # Того же самого результата мы можем добиться, если при рисовании графиков # будем использовать дополнительный параметр label, а затем вызовем функцию @@ -73,7 +72,6 @@ def func (x): # метка. ## Второй способ - ## Заголовок легенды # В легенду можно добавить заголовок, для этого в функцию legend() надо # передать дополнительный строковый параметр title со строкой заголовка. @@ -81,6 +79,5 @@ def func (x): # pylab.legend (title = "Sinc") ## Заголовок легенды - # Покажем окно с нарисованным графиком - pylab.show() \ No newline at end of file + pylab.show() diff --git a/matplotlib__examples/examples/exm16.py b/matplotlib__examples/examples/exm16.py index 884b653d6..896a804b7 100644 --- a/matplotlib__examples/examples/exm16.py +++ b/matplotlib__examples/examples/exm16.py @@ -1,4 +1,4 @@ -__author__ = 'ipetrash' +__author__ = "ipetrash" # Использование библиотеки Matplotlib. Как отображать формулы в нотации TeX @@ -20,17 +20,18 @@ # Импортируем пакет со вспомогательными функциями from matplotlib__examples import mlab + # Будем рисовать график этой функции -def func (x): +def func(x): """ sinc (x) """ if x == 0: return 1.0 - return math.sin (x) / x + return math.sin(x) / x -if __name__ == '__main__': +if __name__ == "__main__": # Интервал изменения переменной по оси X xmin = -20.0 xmax = 20.0 @@ -39,18 +40,18 @@ def func (x): dx = 0.01 # Создадим список координат по оиси X на отрезке [-xmin; xmax], включая концы - xlist = mlab.frange (xmin, xmax, dx) + xlist = mlab.frange(xmin, xmax, dx) # Вычислим значение функции в заданных точках - ylist1 = [func (x) for x in xlist] - ylist2 = [func (x * 0.2) for x in xlist] + ylist1 = [func(x) for x in xlist] + ylist2 = [func(x * 0.2) for x in xlist] # !!! Графики имеют метки с формулами в формате TeX - pylab.plot (xlist, ylist1, "b-", label = "$f(x)$") - pylab.plot (xlist, ylist2, "g--", label = "$f(x \cdot 0.2)$") + pylab.plot(xlist, ylist1, "b-", label="$f(x)$") + pylab.plot(xlist, ylist2, "g--", label="$f(x \cdot 0.2)$") # !!! Добавим легенду с заголовком в виде формулы - pylab.legend (title = r"$f(x) = \frac{sin(x)}{x}$") + pylab.legend(title=r"$f(x) = \frac{sin(x)}{x}$") # Покажем окно с нарисованным графиком - pylab.show() \ No newline at end of file + pylab.show() diff --git a/matplotlib__examples/examples/exm17.py b/matplotlib__examples/examples/exm17.py index a31cf5c3e..9d943598b 100644 --- a/matplotlib__examples/examples/exm17.py +++ b/matplotlib__examples/examples/exm17.py @@ -1,4 +1,4 @@ -__author__ = 'ipetrash' +__author__ = "ipetrash" ## Использование библиотеки Matplotlib. Как нарисовать несколько графиков в одном окне @@ -26,16 +26,16 @@ # Будем рисовать график этой функции -def func (x): +def func(x): """ sinc (x) """ if x == 0: return 1.0 - return math.sin (x) / x + return math.sin(x) / x -if __name__ == '__main__': +if __name__ == "__main__": # Интервал изменения переменной по оси X xmin = -20.0 xmax = 20.0 @@ -44,40 +44,40 @@ def func (x): dx = 0.01 # Создадим список координат по оиси X на отрезке [-xmin; xmax], включая концы - xlist = mlab.frange (xmin, xmax, dx) + xlist = mlab.frange(xmin, xmax, dx) # Вычислим значение функции в заданных точках - ylist = [func (x) for x in xlist] + ylist = [func(x) for x in xlist] # !!! Две строки, три столбца. # !!! Текущая ячейка - 1 - pylab.subplot (2, 3, 1) - pylab.plot (xlist, ylist) - pylab.title ("1") + pylab.subplot(2, 3, 1) + pylab.plot(xlist, ylist) + pylab.title("1") # !!! Две строки, три столбца. # !!! Текущая ячейка - 2 - pylab.subplot (2, 3, 2) - pylab.plot (xlist, ylist) - pylab.title ("2") + pylab.subplot(2, 3, 2) + pylab.plot(xlist, ylist) + pylab.title("2") # !!! Две строки, три столбца. # !!! Текущая ячейка - 4 - pylab.subplot (2, 3, 4) - pylab.plot (xlist, ylist) - pylab.title ("4") + pylab.subplot(2, 3, 4) + pylab.plot(xlist, ylist) + pylab.title("4") # !!! Две строки, три столбца. # !!! Текущая ячейка - 5 - pylab.subplot (2, 3, 5) - pylab.plot (xlist, ylist) - pylab.title ("5") + pylab.subplot(2, 3, 5) + pylab.plot(xlist, ylist) + pylab.title("5") # !!! Одна строка, три столбца. # !!! Текущая ячейка - 3 - pylab.subplot (1, 3, 3) - pylab.plot (xlist, ylist) - pylab.title ("3") + pylab.subplot(1, 3, 3) + pylab.plot(xlist, ylist) + pylab.title("3") # Покажем окно с нарисованным графиком - pylab.show() \ No newline at end of file + pylab.show() diff --git a/matplotlib__examples/examples/exm18.py b/matplotlib__examples/examples/exm18.py index a98cd407e..7951b3c31 100644 --- a/matplotlib__examples/examples/exm18.py +++ b/matplotlib__examples/examples/exm18.py @@ -1,4 +1,4 @@ -__author__ = 'ipetrash' +__author__ = "ipetrash" ## Использование библиотеки Matplotlib. Более гибкий способ расположения графиков с @@ -22,33 +22,34 @@ # Импортируем один из пакетов Matplotlib import pylab -if __name__ == '__main__': + +if __name__ == "__main__": # Таблица графиков будет иметь три строки и три столбца (3,3) # Вывод будет осуществляться в ячейку с координатами (0, 0), # то есть 0-ая строка и 0-ой столбец # Оси для графика будут занимать две ячейки по горизонтали (colspan = 2) # и две ячейки по вертикали (rowspan = 2) - pylab.subplot2grid ((3,3), (0, 0), colspan = 2, rowspan = 2) - pylab.title ("Graph1") + pylab.subplot2grid((3, 3), (0, 0), colspan=2, rowspan=2) + pylab.title("Graph1") # Вывод будет осуществляться в ячейку с координатами (0, 2), # то есть 0-ая строка и 2-ой столбец (нумерация начинается с нуля) # Оси для графика будут занимать две ячейки по вертикали (rowspan = 2) - pylab.subplot2grid ((3,3), (0, 2), rowspan = 2) - pylab.title ("Graph2") + pylab.subplot2grid((3, 3), (0, 2), rowspan=2) + pylab.title("Graph2") # Вывод будет осуществляться в ячейку с координатами (2, 0), # то есть 2-ая строка и 0-ой столбец # Оси для графика будут занимать одну ячейку по вертикали и горизонтали # Аналог этого вызова - pylab.subplot (3, 3, 7) - pylab.subplot2grid ((3,3), (2, 0)) - pylab.title ("Graph3") + pylab.subplot2grid((3, 3), (2, 0)) + pylab.title("Graph3") # Вывод будет осуществляться в ячейку с координатами (2, 1), # то есть 2-ая строка и 1-ый столбец # Оси для графика будут занимать две ячейки по горизонтали (colspan = 2) - pylab.subplot2grid ((3,3), (2, 1), colspan = 2) - pylab.title ("Graph4") + pylab.subplot2grid((3, 3), (2, 1), colspan=2) + pylab.title("Graph4") - pylab.show() \ No newline at end of file + pylab.show() diff --git a/matplotlib__examples/examples/exm19.py b/matplotlib__examples/examples/exm19.py index 934118994..a235e2ab9 100644 --- a/matplotlib__examples/examples/exm19.py +++ b/matplotlib__examples/examples/exm19.py @@ -1,4 +1,4 @@ -__author__ = 'ipetrash' +__author__ = "ipetrash" ## Использование библиотеки Matplotlib. Использование класса GridSpec для расположения @@ -20,18 +20,19 @@ import math -import pylab import matplotlib__examples +import pylab + -def plotGraph (): +def plotGraph() -> None: # Будем рисовать график этой функции - def func (x): + def func(x): """ sinc (x) """ if x == 0: return 1.0 - return math.sin (x) / x + return math.sin(x) / x # Интервал изменения переменной по оси X xmin = -20.0 @@ -41,76 +42,76 @@ def func (x): dx = 0.01 # Создадим список координат по оси X на отрезке [-xmin; xmax], включая концы - xlist = matplotlib__examples.mlab.frange (xmin, xmax, dx) + xlist = matplotlib__examples.mlab.frange(xmin, xmax, dx) # Вычислим значение функции в заданных точках - ylist = [func (x) for x in xlist] + ylist = [func(x) for x in xlist] - pylab.plot (xlist, ylist) + pylab.plot(xlist, ylist) -if __name__ == '__main__': +if __name__ == "__main__": # создаем таблицу (сетку) размером 4 x 4 ячеек grid = matplotlib__examples.gridspec.GridSpec(4, 4) # Одномерное индексирование. # 0-ая ячейка, начиная с левого верхнего угла # График занимает одну ячейку - pylab.subplot (grid[0]) - pylab.title ("Graph 1") + pylab.subplot(grid[0]) + pylab.title("Graph 1") plotGraph() # Одномерное индексирование. # 1-ая ячейка, начиная с левого верхнего угла (0-ая строка, 1-ый столбец) # График занимает одну ячейку - pylab.subplot (grid[1]) - pylab.title ("Graph 2") + pylab.subplot(grid[1]) + pylab.title("Graph 2") plotGraph() # Двумерное индексирование. # 1-ая строка, 0-ой столбец # График занимает одну ячейку - pylab.subplot (grid [1, 0]) - pylab.title ("Graph 3") + pylab.subplot(grid[1, 0]) + pylab.title("Graph 3") plotGraph() # Двумерное индексирование. # 0-ая строка. График занимает столбцы от 2 и до конца строки (2:) # График занимает одну строку и два столбца (2-й и 3-й) - pylab.subplot (grid [0, 2:]) - pylab.title ("Graph 4") + pylab.subplot(grid[0, 2:]) + pylab.title("Graph 4") plotGraph() # Двумерное индексирование. # График занимает строки, начиная с 1 и до 2 включительно (1: 3) # График занимает столбцы от 1 и до предпоследней ячейки включительно (1: -1) # График занимает две строки и два столбца - pylab.subplot (grid [1: 3, 1: -1]) - pylab.title ("Graph 5") + pylab.subplot(grid[1:3, 1:-1]) + pylab.title("Graph 5") plotGraph() # Двумерное индексирование. # График занимает строки, начиная со 2 и до конца всех строк (столбца) (2: ) # График занимает один столбец по горизонтали # График занимает две строки и один столбец - pylab.subplot (grid [2:, 0]) - pylab.title ("Graph 6") + pylab.subplot(grid[2:, 0]) + pylab.title("Graph 6") plotGraph() # Двумерное индексирование. # График занимает строки, начиная с 1 и до последней строки включительно (1:) # График занимает один последний столбец (-1) # График занимает две строки и один столбец - pylab.subplot (grid [1:, -1]) - pylab.title ("Graph 7") + pylab.subplot(grid[1:, -1]) + pylab.title("Graph 7") plotGraph() # Двумерное индексирование. # График занимает одну последнюю строку (-1) # График занимает два столбца: 1 и 2 (1: 3) # График занимает одну строку и два столбца - pylab.subplot (grid [-1, 1: 3]) - pylab.title ("Graph 8") + pylab.subplot(grid[-1, 1:3]) + pylab.title("Graph 8") plotGraph() - pylab.show() \ No newline at end of file + pylab.show() diff --git a/matplotlib__examples/examples/exm2.py b/matplotlib__examples/examples/exm2.py index d390fdd40..dd41235f7 100644 --- a/matplotlib__examples/examples/exm2.py +++ b/matplotlib__examples/examples/exm2.py @@ -1,4 +1,4 @@ -__author__ = 'ipetrash' +__author__ = "ipetrash" # Если вызывать функцию plot() несколько раз подряд, то на график @@ -12,15 +12,15 @@ # !!! Импортируем пакет со вспомогательными функциями from matplotlib__examples import mlab -if __name__ == '__main__': +if __name__ == "__main__": # Будем рисовать график этой функции - def func (x): + def func(x): """ sinc (x) """ if x == 0: return 1.0 - return math.sin (x) / x + return math.sin(x) / x # Интервал изменения переменной по оси X xmin = -20.0 @@ -30,15 +30,15 @@ def func (x): dx = 0.01 # !!! Создадим список координат по оиси X на отрезке [-xmin; xmax], включая концы - xlist = mlab.frange (xmin, xmax, dx) + xlist = mlab.frange(xmin, xmax, dx) # Вычислим значение функции в заданных точках - ylist1 = [func (x) for x in xlist] - ylist2 = [func (x * 0.2) for x in xlist] + ylist1 = [func(x) for x in xlist] + ylist2 = [func(x * 0.2) for x in xlist] # !!! Нарисуем одномерные графики - pylab.plot (xlist, ylist1) - pylab.plot (xlist, ylist2) + pylab.plot(xlist, ylist1) + pylab.plot(xlist, ylist2) # !!! Покажем окно с нарисованным графиком - pylab.show() \ No newline at end of file + pylab.show() diff --git a/matplotlib__examples/examples/exm20.py b/matplotlib__examples/examples/exm20.py index 789d67f36..3905c58e2 100644 --- a/matplotlib__examples/examples/exm20.py +++ b/matplotlib__examples/examples/exm20.py @@ -1,4 +1,4 @@ -__author__ = 'ipetrash' +__author__ = "ipetrash" # Использование библиотеки Matplotlib. Как изменять размеры ячеек таблицы при @@ -14,18 +14,19 @@ import math import pylab + import matplotlib__examples -def plotGraph (): +def plotGraph() -> None: # Будем рисовать график этой функции - def func (x): + def func(x): """ sinc (x) """ if x == 0: return 1.0 - return math.sin (x) / x + return math.sin(x) / x # Интервал изменения переменной по оси X xmin = -20.0 @@ -35,15 +36,15 @@ def func (x): dx = 0.01 # Создадим список координат по оси X на отрезке [-xmin; xmax], включая концы - xlist = matplotlib__examples.mlab.frange (xmin, xmax, dx) + xlist = matplotlib__examples.mlab.frange(xmin, xmax, dx) # Вычислим значение функции в заданных точках - ylist = [func (x) for x in xlist] + ylist = [func(x) for x in xlist] - pylab.plot (xlist, ylist) + pylab.plot(xlist, ylist) -if __name__ == '__main__': +if __name__ == "__main__": # Создаем таблицу (сетку) с тремя строками и двумя столбцами rows = 3 cols = 2 @@ -54,13 +55,12 @@ def func (x): # height_ratios - список соотношений высот ячеек # В данном случае высота второй строки будет в 2 раза больше первого, # а высота третьей строки будет в 3 раза больше первой (в 1.5 раза больше второй) - grid = matplotlib__examples.gridspec.GridSpec(rows, - cols, - width_ratios=[1, 1.5], - height_ratios=[1, 2, 3]) + grid = matplotlib__examples.gridspec.GridSpec( + rows, cols, width_ratios=[1, 1.5], height_ratios=[1, 2, 3] + ) - for n in range (rows * cols): - pylab.subplot (grid[n]) + for n in range(rows * cols): + pylab.subplot(grid[n]) plotGraph() - pylab.show() \ No newline at end of file + pylab.show() diff --git a/matplotlib__examples/examples/exm21.py b/matplotlib__examples/examples/exm21.py index 864596c1f..a8cef33cc 100644 --- a/matplotlib__examples/examples/exm21.py +++ b/matplotlib__examples/examples/exm21.py @@ -1,4 +1,4 @@ -__author__ = 'ipetrash' +__author__ = "ipetrash" ## Использование библиотеки Matplotlib. Как рисовать графики в разных окнах @@ -25,16 +25,16 @@ # Будем рисовать график этой функции -def func (x): +def func(x): """ sinc (x) """ if x == 0: return 1.0 - return math.sin (x) / x + return math.sin(x) / x -if __name__ == '__main__': +if __name__ == "__main__": # Интервал изменения переменной по оси X xmin = -20.0 xmax = 20.0 @@ -43,26 +43,26 @@ def func (x): dx = 0.01 # Создадим список координат по оиси X на отрезке [-xmin; xmax], включая концы - xlist = mlab.frange (xmin, xmax, dx) + xlist = mlab.frange(xmin, xmax, dx) # Вычислим значение функции в заданных точках - ylist1 = [func (x) for x in xlist] - ylist2 = [func (x * 0.2) for x in xlist] - ylist3 = [func (x * 2) for x in xlist] + ylist1 = [func(x) for x in xlist] + ylist2 = [func(x * 0.2) for x in xlist] + ylist3 = [func(x * 2) for x in xlist] # !!! Нарисуем график в первом окне - pylab.figure (1) - pylab.plot (xlist, ylist1, label = "f(x)") + pylab.figure(1) + pylab.plot(xlist, ylist1, label="f(x)") # !!! Нарисуем график во втором окне pylab.figure(2) - pylab.plot (xlist, ylist2, label = "f(x * 0.2)") + pylab.plot(xlist, ylist2, label="f(x * 0.2)") pylab.legend() # !!! Нарисуем еще один график в первом окне - pylab.figure (1) - pylab.plot (xlist, ylist3, label = "f(x * 2)") + pylab.figure(1) + pylab.plot(xlist, ylist3, label="f(x * 2)") pylab.legend() # Покажем окна с нарисованными графиками - pylab.show() \ No newline at end of file + pylab.show() diff --git a/matplotlib__examples/examples/exm22.py b/matplotlib__examples/examples/exm22.py index 3963a27aa..6c2af83ff 100644 --- a/matplotlib__examples/examples/exm22.py +++ b/matplotlib__examples/examples/exm22.py @@ -1,4 +1,4 @@ -__author__ = 'ipetrash' +__author__ = "ipetrash" ## Использование библиотеки Matplotlib. Как делать анимированные графики @@ -24,7 +24,7 @@ from matplotlib__examples import mlab -if __name__ == '__main__': +if __name__ == "__main__": # Интервал изменения переменной по оси X xmin = -20.0 xmax = 20.0 @@ -33,25 +33,25 @@ dx = 0.01 # Создадим список координат по оиси X на отрезке [-xmin; xmax], включая концы - xlist = mlab.frange (xmin, xmax, dx) + xlist = mlab.frange(xmin, xmax, dx) # !!! Включаем интерактивный режим pylab.ion() # У нас будет 50 кадров - for n in range (50): + for n in range(50): # Данные для очередного кадра - ylist = [math.sin (x + n / 2.0) for x in xlist] + ylist = [math.sin(x + n / 2.0) for x in xlist] # !!! Очистим график pylab.clf() # Выведем новые данные - pylab.plot (xlist, ylist) + pylab.plot(xlist, ylist) # !!! Нарисуем их # !!! Обратите внимание, что здесь используется функция draw(), а не show() pylab.draw() # !!! Закроем окно, иначе при завершении программы получим ошибку - pylab.close() \ No newline at end of file + pylab.close() diff --git a/matplotlib__examples/examples/exm23_1.py b/matplotlib__examples/examples/exm23_1.py index 4f4844aaf..9f452103f 100644 --- a/matplotlib__examples/examples/exm23_1.py +++ b/matplotlib__examples/examples/exm23_1.py @@ -1,4 +1,4 @@ -__author__ = 'ipetrash' +__author__ = "ipetrash" ## Использование библиотеки Matplotlib. Как изменять формат меток на осях @@ -19,13 +19,13 @@ if __name__ == "__main__": - xvals = numpy.arange (-10.0, 10.1, 0.1) - yvals = numpy.sinc (xvals) + xvals = numpy.arange(-10.0, 10.1, 0.1) + yvals = numpy.sinc(xvals) figure = pylab.figure() - axes = figure.add_subplot (1, 1, 1) + axes = figure.add_subplot(1, 1, 1) - pylab.plot (xvals, yvals) + pylab.plot(xvals, yvals) axes.grid() - pylab.show() \ No newline at end of file + pylab.show() diff --git a/matplotlib__examples/examples/exm23_2.py b/matplotlib__examples/examples/exm23_2.py index f44344052..c4118d280 100644 --- a/matplotlib__examples/examples/exm23_2.py +++ b/matplotlib__examples/examples/exm23_2.py @@ -1,4 +1,4 @@ -__author__ = 'ipetrash' +__author__ = "ipetrash" # Теперь рассмотрим, как можно менять форматирование с помощью метода @@ -21,19 +21,19 @@ if __name__ == "__main__": - xvals = numpy.arange (-10.0, 10.1, 0.1) - yvals = numpy.sinc (xvals) + xvals = numpy.arange(-10.0, 10.1, 0.1) + yvals = numpy.sinc(xvals) figure = pylab.figure() - axes = figure.add_subplot (1, 1, 1) + axes = figure.add_subplot(1, 1, 1) # Создаем форматер formatter = matplotlib__examples.ticker.NullFormatter() # Установка форматера для оси X - axes.xaxis.set_major_formatter (formatter) + axes.xaxis.set_major_formatter(formatter) - pylab.plot (xvals, yvals) + pylab.plot(xvals, yvals) axes.grid() - pylab.show() \ No newline at end of file + pylab.show() diff --git a/matplotlib__examples/examples/exm23_3.py b/matplotlib__examples/examples/exm23_3.py index d4f21431c..4b6da443b 100644 --- a/matplotlib__examples/examples/exm23_3.py +++ b/matplotlib__examples/examples/exm23_3.py @@ -1,4 +1,4 @@ -__author__ = 'ipetrash' +__author__ = "ipetrash" ## FormatStrFormatter @@ -27,20 +27,20 @@ if __name__ == "__main__": - xvals = numpy.arange (-10.0, 10.1, 0.1) - yvals = numpy.sinc (xvals) + xvals = numpy.arange(-10.0, 10.1, 0.1) + yvals = numpy.sinc(xvals) - pylab.rc('font',**{'family':'verdana'}) + pylab.rc("font", **{"family": "verdana"}) figure = pylab.figure() - axes = figure.add_subplot (1, 1, 1) + axes = figure.add_subplot(1, 1, 1) # Создаем форматер - formatter = matplotlib__examples.ticker.FormatStrFormatter ("%.3f") + formatter = matplotlib__examples.ticker.FormatStrFormatter("%.3f") # Установка форматера для оси X - axes.xaxis.set_major_formatter (formatter) + axes.xaxis.set_major_formatter(formatter) - pylab.plot (xvals, yvals) + pylab.plot(xvals, yvals) axes.grid() - pylab.show() \ No newline at end of file + pylab.show() diff --git a/matplotlib__examples/examples/exm23_4.py b/matplotlib__examples/examples/exm23_4.py index a0f200db6..034afcaa3 100644 --- a/matplotlib__examples/examples/exm23_4.py +++ b/matplotlib__examples/examples/exm23_4.py @@ -1,4 +1,4 @@ -__author__ = 'ipetrash' +__author__ = "ipetrash" ## FuncFormatter @@ -28,31 +28,31 @@ import matplotlib__examples.ticker -def funcForFormatter (x, pos): +def funcForFormatter(x, pos) -> str: if x < 0: - return "минус {x}".format (x=abs(x)) + return f"минус {abs(x)}" if x > 0: - return "плюс {x}".format (x=x) + return f"плюс {x}" return "0" if __name__ == "__main__": - xvals = numpy.arange (-10.0, 10.1, 0.1) - yvals = numpy.sinc (xvals) + xvals = numpy.arange(-10.0, 10.1, 0.1) + yvals = numpy.sinc(xvals) - pylab.rc('font',**{'family':'verdana'}) + pylab.rc("font", **{"family": "verdana"}) figure = pylab.figure() - axes = figure.add_subplot (1, 1, 1) + axes = figure.add_subplot(1, 1, 1) # Создаем форматер formatter = matplotlib__examples.ticker.FuncFormatter(funcForFormatter) # Установка форматера для оси X - axes.xaxis.set_major_formatter (formatter) + axes.xaxis.set_major_formatter(formatter) - pylab.plot (xvals, yvals) + pylab.plot(xvals, yvals) axes.grid() - pylab.show() \ No newline at end of file + pylab.show() diff --git a/matplotlib__examples/examples/exm23_5.py b/matplotlib__examples/examples/exm23_5.py index ec5e58528..b056caea7 100644 --- a/matplotlib__examples/examples/exm23_5.py +++ b/matplotlib__examples/examples/exm23_5.py @@ -1,4 +1,4 @@ -__author__ = 'ipetrash' +__author__ = "ipetrash" ## ScalarFormatter @@ -14,17 +14,16 @@ import numpy import pylab -import matplotlib__examples.ticker if __name__ == "__main__": - xvals = numpy.arange (-10.0, 10.1, 0.1) - yvals = numpy.sinc (xvals) * 1e5 + xvals = numpy.arange(-10.0, 10.1, 0.1) + yvals = numpy.sinc(xvals) * 1e5 figure = pylab.figure() - axes = figure.add_subplot (1, 1, 1) + axes = figure.add_subplot(1, 1, 1) - pylab.plot (xvals, yvals) + pylab.plot(xvals, yvals) axes.grid() - pylab.show() \ No newline at end of file + pylab.show() diff --git a/matplotlib__examples/examples/exm23_6.py b/matplotlib__examples/examples/exm23_6.py index 6e70a96c3..82a30583e 100644 --- a/matplotlib__examples/examples/exm23_6.py +++ b/matplotlib__examples/examples/exm23_6.py @@ -1,4 +1,4 @@ -__author__ = 'ipetrash' +__author__ = "ipetrash" ## FixedFormatter @@ -22,20 +22,22 @@ if __name__ == "__main__": - xvals = numpy.arange (-10.0, 10.1, 0.1) - yvals = numpy.sinc (xvals) + xvals = numpy.arange(-10.0, 10.1, 0.1) + yvals = numpy.sinc(xvals) - pylab.rc('font',**{'family':'verdana'}) + pylab.rc("font", **{"family": "verdana"}) figure = pylab.figure() - axes = figure.add_subplot (1, 1, 1) + axes = figure.add_subplot(1, 1, 1) # Создаем форматер - formatter = matplotlib__examples.ticker.FixedFormatter([u"Раз", u"Два", u"Три", u"Четыре", u"Пять"]) + formatter = matplotlib__examples.ticker.FixedFormatter( + ["Раз", "Два", "Три", "Четыре", "Пять"] + ) # Установка форматера для оси X - axes.xaxis.set_major_formatter (formatter) + axes.xaxis.set_major_formatter(formatter) - pylab.plot (xvals, yvals) + pylab.plot(xvals, yvals) axes.grid() - pylab.show() \ No newline at end of file + pylab.show() diff --git a/matplotlib__examples/examples/exm24.py b/matplotlib__examples/examples/exm24.py index af033b352..91b453301 100644 --- a/matplotlib__examples/examples/exm24.py +++ b/matplotlib__examples/examples/exm24.py @@ -1,4 +1,4 @@ -__author__ = 'ipetrash' +__author__ = "ipetrash" ## Использование библиотеки Matplotlib. Как изменять интервал осей @@ -20,16 +20,16 @@ # Будем рисовать график этой функции -def func (x): +def func(x): """ sinc (x) """ if x == 0: return 1.0 - return math.sin (x) / x + return math.sin(x) / x -if __name__ == '__main__': +if __name__ == "__main__": # Интервал изменения переменной по оси X xmin = -50.0 xmax = 50.0 @@ -38,16 +38,16 @@ def func (x): dx = 0.2 # !!! Создадим список координат по оси X на отрезке [-xmin; xmax], включая концы - xlist = mlab.frange (xmin, xmax, dx) + xlist = mlab.frange(xmin, xmax, dx) # Вычислим значение функции в заданных точках - ylist = [func (x) for x in xlist] + ylist = [func(x) for x in xlist] # !!! Нарисуем одномерный график с использованием стиля - pylab.plot (xlist, ylist) + pylab.plot(xlist, ylist) # !!! На графике будет показан только участок от -10 до 30 по оси X - pylab.xlim (-10, 30) + pylab.xlim(-10, 30) # !!! Покажем окно с нарисованным графиком - pylab.show() \ No newline at end of file + pylab.show() diff --git a/matplotlib__examples/examples/exm3.py b/matplotlib__examples/examples/exm3.py index 02636ce3a..e0c25af85 100644 --- a/matplotlib__examples/examples/exm3.py +++ b/matplotlib__examples/examples/exm3.py @@ -1,11 +1,11 @@ -__author__ = 'ipetrash' +__author__ = "ipetrash" # Простой пример рисования графика import pylab -if __name__ == '__main__': - pylab.plot(range(1, 20), [i * i for i in range(1, 20)], 'ro') +if __name__ == "__main__": + pylab.plot(range(1, 20), [i * i for i in range(1, 20)], "ro") # pylab.savefig('example.png') - pylab.show() \ No newline at end of file + pylab.show() diff --git a/matplotlib__examples/examples/exm4.py b/matplotlib__examples/examples/exm4.py index ee5e37a51..54ecf0a23 100644 --- a/matplotlib__examples/examples/exm4.py +++ b/matplotlib__examples/examples/exm4.py @@ -1,4 +1,4 @@ -__author__ = 'ipetrash' +__author__ = "ipetrash" # Для рисования кругового графика используется функция pie() из модуля pylab. @@ -8,11 +8,11 @@ import pylab -if __name__ == '__main__': +if __name__ == "__main__": # Данные для построения графика data = [20.0, 10.0, 5.0, 1.0, 0.5] # Нарисовать круговой график - pylab.pie (data) + pylab.pie(data) - pylab.show() \ No newline at end of file + pylab.show() diff --git a/matplotlib__examples/examples/exm5.py b/matplotlib__examples/examples/exm5.py index fbff62293..401d4771d 100644 --- a/matplotlib__examples/examples/exm5.py +++ b/matplotlib__examples/examples/exm5.py @@ -1,4 +1,4 @@ -__author__ = 'ipetrash' +__author__ = "ipetrash" # По умолчанию создается окно, у которого ширина и высота различны, из-за чего "пирог" @@ -11,17 +11,17 @@ import pylab -if __name__ == '__main__': +if __name__ == "__main__": # Данные для построения графика data = [20.0, 10.0, 5.0, 1.0, 0.5] # Создать новое окно (фигуру) с одинаковыми размерами сторон (6 x 6 дюйма) - pylab.figure(figsize=(6, 6) ) + pylab.figure(figsize=(6, 6)) # Установим размеры осей по горизонтали и вертикали тоже одинаковыми pylab.axes([0.0, 0.0, 1.0, 1.0]) # И снова нарисуем график - pylab.pie (data) + pylab.pie(data) - pylab.show() \ No newline at end of file + pylab.show() diff --git a/matplotlib__examples/examples/exm6.py b/matplotlib__examples/examples/exm6.py index 5eadccc24..1ba8f4ca6 100644 --- a/matplotlib__examples/examples/exm6.py +++ b/matplotlib__examples/examples/exm6.py @@ -1,4 +1,4 @@ -__author__ = 'ipetrash' +__author__ = "ipetrash" # Для того, чтобы добавить текстовые метки к секторам, достаточно передать их список @@ -6,7 +6,7 @@ import pylab -if __name__ == '__main__': +if __name__ == "__main__": # Данные для построения графика data = [20.0, 10.0, 5.0, 1.0, 0.5] @@ -14,12 +14,12 @@ labels = ["data1", "data2", "data3", "data4", "data5"] # Создать новое окно (фигуру) с одинаковыми размерами сторон (6 x 6 дюйма) - pylab.figure(figsize=(6, 6) ) + pylab.figure(figsize=(6, 6)) # Установим размеры осей по горизонтали и вертикали тоже одинаковыми pylab.axes([0.05, 0.05, 0.9, 0.9]) # И снова нарисуем график - pylab.pie (data, labels=labels) + pylab.pie(data, labels=labels) - pylab.show() \ No newline at end of file + pylab.show() diff --git a/matplotlib__examples/examples/exm7.py b/matplotlib__examples/examples/exm7.py index cc4aeb8b9..fe12001ad 100644 --- a/matplotlib__examples/examples/exm7.py +++ b/matplotlib__examples/examples/exm7.py @@ -1,11 +1,11 @@ -__author__ = 'ipetrash' +__author__ = "ipetrash" # Цвета секторов задаются с помощью параметра colors функции pie(). import pylab -if __name__ == '__main__': +if __name__ == "__main__": # Данные для построения графика data = [20.0, 10.0, 5.0, 1.0, 0.5] @@ -16,12 +16,12 @@ colors = ["g", "r", "#FF00BB", "0.5", "y"] # Создать новое окно (фигуру) с одинаковыми размерами сторон (6 x 6 дюйма) - pylab.figure(figsize=(6, 6) ) + pylab.figure(figsize=(6, 6)) # Установим размеры осей по горизонтали и вертикали тоже одинаковыми pylab.axes([0.05, 0.05, 0.9, 0.9]) # И снова нарисуем график - pylab.pie (data, colors=colors, labels=labels) + pylab.pie(data, colors=colors, labels=labels) - pylab.show() \ No newline at end of file + pylab.show() diff --git a/matplotlib__examples/examples/exm8.py b/matplotlib__examples/examples/exm8.py index 28672dd31..0ca842f93 100644 --- a/matplotlib__examples/examples/exm8.py +++ b/matplotlib__examples/examples/exm8.py @@ -1,4 +1,4 @@ -__author__ = 'ipetrash' +__author__ = "ipetrash" # Часто при отображении круговых графиков сектора немного "выдвигают" из центра. @@ -7,7 +7,7 @@ import pylab -if __name__ == '__main__': +if __name__ == "__main__": # Данные для построения графика data = [20.0, 10.0, 5.0, 1.0, 0.5] @@ -21,12 +21,12 @@ explode = [0.02, 0.05, 0.08, 0.08, 0.05] # Создать новое окно (фигуру) с одинаковыми размерами сторон (6 x 6 дюйма) - pylab.figure(figsize=(6, 6) ) + pylab.figure(figsize=(6, 6)) # Установим размеры осей по горизонтали и вертикали тоже одинаковыми pylab.axes([0.1, 0.1, 0.8, 0.8]) # И снова нарисуем график - pylab.pie (data, explode=explode, colors=colors, labels=labels) + pylab.pie(data, explode=explode, colors=colors, labels=labels) - pylab.show() \ No newline at end of file + pylab.show() diff --git a/matplotlib__examples/examples/exm9.py b/matplotlib__examples/examples/exm9.py index 10f0e373c..b682dfa88 100644 --- a/matplotlib__examples/examples/exm9.py +++ b/matplotlib__examples/examples/exm9.py @@ -1,4 +1,4 @@ -__author__ = 'ipetrash' +__author__ = "ipetrash" # Из эстетических соображений к графику можно добавить тень. Для этого в функцию pie() @@ -7,7 +7,7 @@ import pylab -if __name__ == '__main__': +if __name__ == "__main__": # Данные для построения графика data = [20.0, 10.0, 5.0, 1.0, 0.5] @@ -20,12 +20,12 @@ explode = [0.02, 0.05, 0.08, 0.08, 0.05] # Создать новое окно (фигуру) с одинаковыми размерами сторон (6 x 6 дюйма) - pylab.figure(figsize=(6, 6) ) + pylab.figure(figsize=(6, 6)) # Установим размеры осей по горизонтали и вертикали тоже одинаковыми pylab.axes([0.1, 0.1, 0.8, 0.8]) # И снова нарисуем график - pylab.pie (data, explode=explode, colors=colors, labels=labels, shadow=True) + pylab.pie(data, explode=explode, colors=colors, labels=labels, shadow=True) - pylab.show() \ No newline at end of file + pylab.show() diff --git a/matplotlib__examples/price_by_date__plot.py b/matplotlib__examples/price_by_date__plot.py index b5fb68c51..7bd7ad0ab 100644 --- a/matplotlib__examples/price_by_date__plot.py +++ b/matplotlib__examples/price_by_date__plot.py @@ -1,34 +1,70 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' - - -if __name__ == '__main__': - price_by_date = {156802: '2017-04-30', 147719: '2017-04-02', 120172: '2017-01-27', 123787: '2017-02-01', - 127890: '2017-02-11', 131699: '2017-02-16', 146452: '2017-03-27', 148507: '2017-04-09', - 144858: '2017-03-18', 144209: '2017-03-16', 126114: '2017-02-06', 102947: '2017-01-16', - 154404: '2017-04-22', 141733: '2017-03-11', 123249: '2017-01-29', 148008: '2017-04-07', - 112297: '2017-01-19', 120371: '2017-01-28', 119923: '2017-01-25', 127541: '2017-02-10', - 127163: '2017-02-07', 142911: '2017-03-14', 151106: '2017-04-10', 147020: '2017-03-29', - 123598: '2017-01-30', 136784: '2017-03-05', 134737: '2017-02-21', 135507: '2017-02-19', - 135636: '2017-02-20', 136535: '2017-02-27', 123866: '2017-02-04', 143710: '2017-03-15', - 142562: '2017-03-13', 138833: '2017-03-10', 153705: '2017-04-20', 137834: '2017-03-06', - 116076: '2017-01-22', 90386: '2017-01-15', 128239: '2017-02-14', 145136: '2017-03-20', - 146033: '2017-03-22', 124115: '2017-02-05', 135158: '2017-02-17', 145784: '2017-03-21', - 145535: '2017-03-19'} - - # Данные для построения графика - x = list(sorted(price_by_date.keys())) - - from datetime import datetime - get_date = lambda date_str: datetime.strptime(date_str, '%Y-%m-%d') - y = list(get_date(price_by_date[key]) for key in sorted(price_by_date.keys())) - - import pylab - pylab.plot(x, y) - - pylab.xlabel('Price') - pylab.ylabel('Date') - pylab.grid() - pylab.show() +__author__ = "ipetrash" + + +from datetime import datetime +import pylab + + +price_by_date = { + 156802: "2017-04-30", + 147719: "2017-04-02", + 120172: "2017-01-27", + 123787: "2017-02-01", + 127890: "2017-02-11", + 131699: "2017-02-16", + 146452: "2017-03-27", + 148507: "2017-04-09", + 144858: "2017-03-18", + 144209: "2017-03-16", + 126114: "2017-02-06", + 102947: "2017-01-16", + 154404: "2017-04-22", + 141733: "2017-03-11", + 123249: "2017-01-29", + 148008: "2017-04-07", + 112297: "2017-01-19", + 120371: "2017-01-28", + 119923: "2017-01-25", + 127541: "2017-02-10", + 127163: "2017-02-07", + 142911: "2017-03-14", + 151106: "2017-04-10", + 147020: "2017-03-29", + 123598: "2017-01-30", + 136784: "2017-03-05", + 134737: "2017-02-21", + 135507: "2017-02-19", + 135636: "2017-02-20", + 136535: "2017-02-27", + 123866: "2017-02-04", + 143710: "2017-03-15", + 142562: "2017-03-13", + 138833: "2017-03-10", + 153705: "2017-04-20", + 137834: "2017-03-06", + 116076: "2017-01-22", + 90386: "2017-01-15", + 128239: "2017-02-14", + 145136: "2017-03-20", + 146033: "2017-03-22", + 124115: "2017-02-05", + 135158: "2017-02-17", + 145784: "2017-03-21", + 145535: "2017-03-19", +} + +# Данные для построения графика +x = list(sorted(price_by_date.keys())) + +get_date = lambda date_str: datetime.strptime(date_str, "%Y-%m-%d") +y = list(get_date(price_by_date[key]) for key in sorted(price_by_date.keys())) + +pylab.plot(x, y) + +pylab.xlabel("Price") +pylab.ylabel("Date") +pylab.grid() +pylab.show() diff --git a/max_seqs_zeros__itertools.groupby.py b/max_seqs_zeros__itertools.groupby.py index 25022be7f..9ffd7dcf7 100644 --- a/max_seqs_zeros__itertools.groupby.py +++ b/max_seqs_zeros__itertools.groupby.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import itertools @@ -11,13 +11,13 @@ def end_zeros(num: int) -> int: size = 0 for x, items in itertools.groupby(str(num)): - if x == '0': + if x == "0": size = max(size, len(list(items))) return size -if __name__ == '__main__': +if __name__ == "__main__": assert end_zeros(10979000000) == 6 assert end_zeros(1000000) == 6 assert end_zeros(10009) == 3 diff --git a/mechanize__examples/follow_link.py b/mechanize__examples/follow_link.py new file mode 100644 index 000000000..0b9c12732 --- /dev/null +++ b/mechanize__examples/follow_link.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +# pip install mechanize +import mechanize + + +br = mechanize.Browser() +br.open("https://docs.python.org/3/") + +print(f"Title: {br.title()}") +print(f"URL: {br.geturl()}") +""" +Title: 3.11.1 Documentation +URL: https://docs.python.org/3/ +""" + +print() + +br.follow_link(text_regex=r"Tutorial") + +print(f"Title: {br.title()}") +print(f"URL: {br.geturl()}") +""" +Title: The Python Tutorial — Python 3.11.1 documentation +URL: https://docs.python.org/3/tutorial/index.html +""" diff --git a/mechanize__examples/submit_form.py b/mechanize__examples/submit_form.py new file mode 100644 index 000000000..32156515f --- /dev/null +++ b/mechanize__examples/submit_form.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import json + +# pip install mechanize +import mechanize + + +br = mechanize.Browser() +br.open("https://httpbin.org/forms/post") + +print(f"Title: {br.title()}") +print(f"URL: {br.geturl()}") + +rs = br.response() + +print("Headers:") +print(rs.info()) +print() + +print("Response body:") +print(rs.read()) + +print("\n" + "-" * 100 + "\n") + +br.select_form(nr=0) # First form +br["custname"] = "Customer" +br["custtel"] = "+79990001122" +br["custemail"] = "my@example.com" +br["size"] = ["medium"] # radio +br["topping"] = ["bacon", "cheese"] # checkbox +br["delivery"] = "11:15" +br["comments"] = "My comment!" + +rs2 = br.submit() + +print("Headers:") +print(rs2.info()) +print() + +rs2_body = rs2.read() +print("Response body:") +print(rs2_body) + +rs2_json = json.loads(rs2_body) +print(rs2_json["form"]) +# {'comments': 'My comment!', 'custemail': 'my@example.com', 'custname': 'Customer', 'custtel': '+79990001122', +# 'delivery': '11:15', 'size': 'medium', 'topping': ['bacon', 'cheese']} diff --git a/mediawiki__wikipedia.py b/mediawiki__wikipedia.py index 7cef42c82..2f3d25329 100644 --- a/mediawiki__wikipedia.py +++ b/mediawiki__wikipedia.py @@ -1,38 +1,39 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" +from urllib.parse import unquote + # pip install pymediawiki # https://github.com/barrust/mediawiki # http://pymediawiki.readthedocs.io/en/latest/index.html +from mediawiki import MediaWiki -def wiki_search(query: str, lang='ru', unquote_percent_encoded=False) -> str: +def wiki_search(query: str, lang="ru", unquote_percent_encoded=False) -> str: # Default using wikipedia - from mediawiki import MediaWiki wikipedia = MediaWiki(lang=lang) result = wikipedia.opensearch(query, results=1) if not result: - return '' + return "" _, text, url = result[0] if unquote_percent_encoded: - from urllib.parse import unquote url = unquote(url) - return '{} ({})'.format(text, url) + return f"{text} ({url})" -if __name__ == '__main__': - query = 'GitHub' +if __name__ == "__main__": + query = "GitHub" print(wiki_search(query)) - query = 'python' + query = "python" print(wiki_search(query)) - query = 'магнитогорск' + query = "магнитогорск" print(wiki_search(query)) print(wiki_search(query, unquote_percent_encoded=True)) diff --git a/merge_url_params.py b/merge_url_params.py new file mode 100644 index 000000000..d3cac3876 --- /dev/null +++ b/merge_url_params.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from urllib.parse import ParseResult, urlparse, urlencode, parse_qsl + + +def merge_url_params(url: str, params: dict) -> str: + result: ParseResult = urlparse(url) + + current_params = dict(parse_qsl(result.query)) + merged_params = {**current_params, **params} + + new_query = urlencode(merged_params, doseq=True) + return result._replace(query=new_query).geturl() + + +if __name__ == "__main__": + url = "https://examples.com/jobs/search?keywords=engineer" + new_params = {"location": "United States", "keywords": True, "items": [1, 2, 3]} + + assert ( + merge_url_params(url, new_params) + == "https://examples.com/jobs/search?keywords=True&location=United+States&items=1&items=2&items=3" + ) + + url = "https://examples.com/jobs/search" + assert ( + merge_url_params(url, new_params) + == "https://examples.com/jobs/search?location=United+States&keywords=True&items=1&items=2&items=3" + ) diff --git a/metabolism.py b/metabolism.py index ffb22db4b..2dc8969e5 100644 --- a/metabolism.py +++ b/metabolism.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """ @@ -30,7 +30,9 @@ class FactorEnum(enum.Enum): V_19 = 1.9 -def get_1918_for_male(weight_kg: int, height_cm, age: int, factor: FactorEnum = FactorEnum.V_12) -> int: +def get_1918_for_male( + weight_kg: int, height_cm, age: int, factor: FactorEnum = FactorEnum.V_12 +) -> int: """ Формула-Уравнение Харриса-Бенедикта для мужчин. """ @@ -38,7 +40,9 @@ def get_1918_for_male(weight_kg: int, height_cm, age: int, factor: FactorEnum = return int(value * factor.value) -def get_1918_for_female(weight_kg: int, height_cm, age: int, factor: FactorEnum = FactorEnum.V_12) -> int: +def get_1918_for_female( + weight_kg: int, height_cm, age: int, factor: FactorEnum = FactorEnum.V_12 +) -> int: """ Формула-Уравнение Харриса-Бенедикта для женщин. """ @@ -46,7 +50,9 @@ def get_1918_for_female(weight_kg: int, height_cm, age: int, factor: FactorEnum return int(value * factor.value) -def get_1984_for_male(weight_kg: int, height_cm, age: int, factor: FactorEnum = FactorEnum.V_12) -> int: +def get_1984_for_male( + weight_kg: int, height_cm, age: int, factor: FactorEnum = FactorEnum.V_12 +) -> int: """ Пересмотренная Формула-Уравнение Харриса-Бенедикта для мужчин. """ @@ -54,7 +60,9 @@ def get_1984_for_male(weight_kg: int, height_cm, age: int, factor: FactorEnum = return int(value * factor.value) -def get_1984_for_female(weight_kg: int, height_cm, age: int, factor: FactorEnum = FactorEnum.V_12) -> int: +def get_1984_for_female( + weight_kg: int, height_cm, age: int, factor: FactorEnum = FactorEnum.V_12 +) -> int: """ Пересмотренная Формула-Уравнение Харриса-Бенедикта для женщин. """ @@ -62,7 +70,9 @@ def get_1984_for_female(weight_kg: int, height_cm, age: int, factor: FactorEnum return int(value * factor.value) -def get_2005_for_male(weight_kg: int, height_cm, age: int, factor: FactorEnum = FactorEnum.V_12) -> int: +def get_2005_for_male( + weight_kg: int, height_cm, age: int, factor: FactorEnum = FactorEnum.V_12 +) -> int: """ Формула-Уравнение Миффлина-Санкт-Джеора для мужчин. """ @@ -70,7 +80,9 @@ def get_2005_for_male(weight_kg: int, height_cm, age: int, factor: FactorEnum = return int(value * factor.value) -def get_2005_for_female(weight_kg: int, height_cm, age: int, factor: FactorEnum = FactorEnum.V_12) -> int: +def get_2005_for_female( + weight_kg: int, height_cm, age: int, factor: FactorEnum = FactorEnum.V_12 +) -> int: """ Формула-Уравнение Миффлина-Санкт-Джеора для женщин. """ @@ -78,26 +90,26 @@ def get_2005_for_female(weight_kg: int, height_cm, age: int, factor: FactorEnum return int(value * factor.value) -if __name__ == '__main__': +if __name__ == "__main__": weight_kg = 100 height_cm = 190 age = 30 - print('male (1918):', get_1918_for_male(weight_kg, height_cm, age)) - print('female (1918):', get_1918_for_female(weight_kg, height_cm, age)) + print("male (1918):", get_1918_for_male(weight_kg, height_cm, age)) + print("female (1918):", get_1918_for_female(weight_kg, height_cm, age)) # male (1918): 2626 # female (1918): 2199 print() - print('male 1984:', get_1984_for_male(weight_kg, height_cm, age)) - print('female 1984:', get_1984_for_female(weight_kg, height_cm, age)) + print("male 1984:", get_1984_for_male(weight_kg, height_cm, age)) + print("female 1984:", get_1984_for_female(weight_kg, height_cm, age)) # male 1984: 2603 # female 1984: 2197 print() - print('male 2005:', get_2005_for_male(weight_kg, height_cm, age)) - print('female 2005:', get_2005_for_female(weight_kg, height_cm, age)) + print("male 2005:", get_2005_for_male(weight_kg, height_cm, age)) + print("female 2005:", get_2005_for_female(weight_kg, height_cm, age)) # male 2005: 2451 # female 2005: 2251 diff --git a/mock__examples/_test_datetime.py b/mock__examples/_test_datetime.py new file mode 100644 index 000000000..7d7a765ad --- /dev/null +++ b/mock__examples/_test_datetime.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from datetime import datetime + + +def get() -> tuple[datetime, datetime]: + return datetime.now(), datetime.utcnow() diff --git a/mock__examples/datetime_now_utcnow.py b/mock__examples/datetime_now_utcnow.py new file mode 100644 index 000000000..416091557 --- /dev/null +++ b/mock__examples/datetime_now_utcnow.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from unittest.mock import patch, Mock +import datetime + +import _test_datetime + + +def test() -> None: + print("local:", datetime.datetime.now(), datetime.datetime.utcnow()) + print("import:", *_test_datetime.get()) + print() + + +datetime_mock = Mock(wraps=datetime.datetime) +datetime_mock.now.return_value = datetime.datetime(1999, 1, 1) +datetime_mock.utcnow.return_value = datetime.datetime(2000, 1, 1) + +test() + +with ( + patch("datetime.datetime", new=datetime_mock), + patch("_test_datetime.datetime", new=datetime_mock) +): + test() + # 1999-01-01 00:00:00 2000-01-01 00:00:00 + + assert datetime.datetime.now() == datetime.datetime(1999, 1, 1) + assert datetime.datetime.utcnow() == datetime.datetime(2000, 1, 1) + + now_2, utcnow_2 = _test_datetime.get() + assert datetime.datetime.now() == now_2 + assert datetime.datetime.utcnow() == utcnow_2 + +test() diff --git a/module click.py b/module click.py deleted file mode 100644 index 2c5ab50e1..000000000 --- a/module click.py +++ /dev/null @@ -1,194 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -__author__ = 'ipetrash' - - -import click - -# @click.group() -# def greet (): pass -# -# @greet.command () -# def hello ( ** kwargs ): pass -# -# @greet.command () -# def goodbye ( ** kwargs ): pass -# -# if __name__ == '__main__': -# greet () - -# @click.command() -# @click.argument('filename', required=False) -# def touch(filename): -# click.echo('filename=' + str(filename)) -# touch() - -# @click.command() -# @click.argument('src', nargs=-1) -# @click.argument('dst', nargs=1) -# def copy(src, dst): -# for fn in src: -# click.echo('move %s to folder %s' % (fn, dst)) -# -# if __name__ == '__main__': -# copy() - - -# @click.command() -# @click.argument('input', type=click.File('rb')) -# @click.argument('output', type=click.File('wb')) -# def inout(input, output): -# while True: -# chunk = input.read(1024) -# if not chunk: -# break -# output.write(chunk) -# -# inout() - -# -# @click.command() -# @click.argument('f', type=click.Path(exists=True)) -# def touch(f): -# click.echo(click.format_filename(f)) -# -# touch() - -# import os -# for k, v in os.environ.items(): -# print(k + ': ') -# for i, val in enumerate(v.split(';'), 1): -# print(' {}. {}'.format(i, val)) -# quit() - -# @click.command() -# @click.argument('src', envvar='SRC', type=click.File('r')) -# def echo(src): -# click.echo(src.read()) - -# @click.command() -# @click.argument('files', nargs=1, type=int) -# def touch(files): -# print(type(files)) -# click.echo(files) - - -# @click.command() -# @click.option('--value', type=[str, int]) -# def touch(value): -# print(value) -# click.echo('%s=%s' % value) - - -# @click.command() -# @click.option('--message', '-m', multiple=True) -# def touch(message): -# print(type(message), message) -# click.echo('\n'.join(message)) - - -# @click.command() -# @click.option('-m', '--message') -# def touch(message): -# print(type(message), message) -# click.echo('\n'.join(message)) - -# @click.command() -# @click.option('-v', '--verbose', count=True) -# def touch(verbose): -# click.echo('Verbosity: %s' % verbose) - -# @click.command() -# @click.option('-shoot/-no-shoot') -# @click.option('-flag', is_flag=True) -# @click.option('/debug;/no-debug') -# def touch(shoot, flag, debug): -# click.echo('Verbosity: %s %s %s' % (shoot, flag, debug)) - - -# import sys -# -# @click.command() -# @click.option('--upper', 'case', flag_value='upper', default=True) -# @click.option('--lower', 'case', flag_value='lower') -# def touch(case): -# print(case) -# click.echo(getattr(sys.platform, case)()) -# -# touch() - - -# @click.command() -# @click.option('--hash-type', '-hash', type=click.Choice(['md5', 'sha1'])) -# def digest(hash_type): -# click.echo(hash_type) -# -# digest() - -# @click.command() -# @click.option('--name', prompt=True) -# @click.option('--name2', prompt='Your name please') -# def hello(name, name2): -# click.echo('Hello %s-%s!' % (name, name2)) -# -# hello() - - -# @click.command() -# @click.option('--login', prompt='Your login please') -# @click.option('--password', prompt=True, hide_input=True, confirmation_prompt=True) -# # @click.password_option() -# def encrypt(password, login): -# import codecs -# click.echo('Encrypting password to %s %s' % (codecs.encode(password, 'ROT13'), login)) -# -# encrypt() - - -# import os -# -# -# @click.command() -# # @click.option('--username', prompt=True, default=lambda: os.environ.get('JAVA_HOME', '')) -# @click.option('--username', prompt=True, default=lambda: 1) -# @click.option('--username', prompt=True, default=lambda: [1, 2, 3]) -# def hello(username): -# print("Hello,", username) -# -# hello() - - -# def print_version(ctx, param, value): -# if not value or ctx.resilient_parsing: -# return -# click.echo('Version 1.0') -# ctx.exit() -# -# @click.command() -# @click.option('--version', is_flag=True, callback=print_version, expose_value=False, is_eager=True) -# def hello(): -# click.echo('Hello World!') -# -# hello() - - -# def abort_if_false(ctx, param, value): -# if not value: -# ctx.abort() -# -# @click.command() -# @click.option('--yes', is_flag=True, callback=abort_if_false, -# expose_value=False, -# prompt='Are you sure you want to drop the db?') -# def dropdb(): -# click.echo('Dropped all tables!') - - -# @click.command() -# @click.confirmation_option(help='Are you sure you want to drop the db?') -# def dropdb(): -# click.echo('Dropped all tables!') -# -# dropdb() - diff --git a/moving_LNKs_from_non_existent_files.py b/moving_LNKs_from_non_existent_files.py index f255e0ac8..4876bcee8 100644 --- a/moving_LNKs_from_non_existent_files.py +++ b/moving_LNKs_from_non_existent_files.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import shutil @@ -11,23 +11,23 @@ import winshell -DIR = Path(r'~\Desktop\Пройти').expanduser() -DIR_NOT_EXISTENT = DIR / 'Несуществуют' +DIR = Path(r"~\Desktop\Пройти").expanduser() +DIR_NOT_EXISTENT = DIR / "Несуществуют" files = [] -for file_name in DIR.glob('*.lnk'): +for file_name in DIR.glob("*.lnk"): shortcut = winshell.shortcut(str(file_name)) path = Path(shortcut.path) if not path.exists(): files.append(file_name) -print(f'Найдено {len(files)}') +print(f"Найдено {len(files)}") if files: DIR_NOT_EXISTENT.mkdir(parents=True, exist_ok=True) for f in files: - print(f'Выполнено перемещение {f.name} в папку {DIR_NOT_EXISTENT}') + print(f"Выполнено перемещение {f.name} в папку {DIR_NOT_EXISTENT}") new_file = DIR_NOT_EXISTENT / f.name shutil.move(f, new_file) diff --git a/my_torrent_list_from_iknowwhatyoudownload.py b/my_torrent_list_from_iknowwhatyoudownload.py index 96e7a7ede..88caad2f4 100644 --- a/my_torrent_list_from_iknowwhatyoudownload.py +++ b/my_torrent_list_from_iknowwhatyoudownload.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """ @@ -11,31 +11,34 @@ """ +import time + import requests from bs4 import BeautifulSoup -import time def get_my_torrents(append_torrent_size=False): - rs = requests.get('http://iknowwhatyoudownload.com/ru/peer/', headers={'User-Agent': '-'}) - root = BeautifulSoup(rs.content, 'lxml') + rs = requests.get( + "http://iknowwhatyoudownload.com/ru/peer/", headers={"User-Agent": "-"} + ) + root = BeautifulSoup(rs.content, "lxml") # Если нужно вместе с названием передавать и размер торрента if not append_torrent_size: - return [item.text.strip() for item in root.select('.torrent_files > a')] + return [item.text.strip() for item in root.select(".torrent_files > a")] items = list() - for row in root.select('table > tbody > tr'): - name = row.select_one('.name-column').text.strip() - size = row.select_one('.size-column').text.strip() + for row in root.select("table > tbody > tr"): + name = row.select_one(".name-column").text.strip() + size = row.select_one(".size-column").text.strip() items.append((name, size)) return items -if __name__ == '__main__': +if __name__ == "__main__": while True: try: items = get_my_torrents() @@ -45,4 +48,4 @@ def get_my_torrents(append_torrent_size=False): time.sleep(60 * 60 * 12) except Exception as e: - print('Error:', e) + print("Error:", e) diff --git a/namedtuple__typing__example/main.py b/namedtuple__typing__example/main.py index 1d4965687..daec6fe50 100644 --- a/namedtuple__typing__example/main.py +++ b/namedtuple__typing__example/main.py @@ -1,28 +1,26 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from collections import namedtuple +from typing import NamedTuple -Point = namedtuple('Point', ['x', 'y']) +Point = namedtuple("Point", ["x", "y"]) # # TypeError: __new__() missing 2 required positional arguments: 'x' and 'y' # p = Point() # print(p) p = Point(1, 2) -print(p) # Point(x=1, y=2) +print(p) # Point(x=1, y=2) print(p._asdict()) # OrderedDict([('x', 1), ('y', 2)]) print() # SOURCE: https://docs.python.org/3/library/typing.html#typing.NamedTuple -from typing import NamedTuple - - class Point(NamedTuple): x: int = 0 y: int = 0 @@ -32,5 +30,5 @@ class Point(NamedTuple): print(p) # Point(x=0, y=0) p = Point(1, 2) -print(p) # Point(x=1, y=2) +print(p) # Point(x=1, y=2) print(p._asdict()) # OrderedDict([('x', 1), ('y', 2)]) diff --git a/namedtuple_example/namedtuple.py b/namedtuple_example/namedtuple.py index 72ab00839..e41df4050 100644 --- a/namedtuple_example/namedtuple.py +++ b/namedtuple_example/namedtuple.py @@ -1,22 +1,22 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -# TODO: https://docs.python.org/3/library/collections.html#collections.namedtuple_example -# Basic example from collections import namedtuple -Point = namedtuple('Point', ['x', 'y']) + + +Point = namedtuple("Point", ["x", "y"]) p = Point(11, y=22) # instantiate with positional or keyword arguments -print(p[0] + p[1]) # indexable like the plain tuple (11, 22) -x, y = p # unpack like a regular tuple +print(p[0] + p[1]) # indexable like the plain tuple (11, 22) +x, y = p # unpack like a regular tuple print(x, y) -print(p.x + p.y) # fields also accessible by name -print(p) # readable __repr__ with a name=value style +print(p.x + p.y) # fields also accessible by name +print(p) # readable __repr__ with a name=value style print() -XYZPoint = namedtuple('XYZPoint', ['x', 'y', 'z']) +XYZPoint = namedtuple("XYZPoint", ["x", "y", "z"]) xyz = XYZPoint(2, 2, z=5) print(xyz) print("z:", xyz.z) diff --git a/natasha__examples/hello_world.py b/natasha__examples/hello_world.py index 47534b7cc..337be9ca2 100644 --- a/natasha__examples/hello_world.py +++ b/natasha__examples/hello_world.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/natasha/natasha @@ -17,14 +17,14 @@ morph_tagger = NewsMorphTagger(emb) -text = 'Появление ООН было обусловлено целым рядом объективных факторов' +text = "Появление ООН было обусловлено целым рядом объективных факторов" doc = Doc(text) doc.segment(segmenter) doc.tag_morph(morph_tagger) for token in doc.tokens: - print(f'[{token.start}:{token.stop}] {token.pos} {token.text!r}\n{token}\n') + print(f"[{token.start}:{token.stop}] {token.pos} {token.text!r}\n{token}\n") """ [0:9] NOUN 'Появление' @@ -52,16 +52,16 @@ DocToken(start=55, stop=63, text='факторов', pos='NOUN', feats=) """ -print('\n' + '-' * 10 + '\n') +print("\n" + "-" * 10 + "\n") -text = 'Hello World!' +text = "Hello World!" doc = Doc(text) doc.segment(segmenter) doc.tag_morph(morph_tagger) for token in doc.tokens: - print(f'[{token.start}:{token.stop}] {token.pos} {token.text!r}\n{token}\n') + print(f"[{token.start}:{token.stop}] {token.pos} {token.text!r}\n{token}\n") """ [0:5] X 'Hello' diff --git a/neutralize_emoji.py b/neutralize_emoji.py index bade2dbb5..e77a533b1 100644 --- a/neutralize_emoji.py +++ b/neutralize_emoji.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/crinny/gatee/blob/11f78228fbb42dc4e06d180d90974849d4d4e45f/bot/utils/emoji.py#L7 @@ -12,8 +12,7 @@ def neutralize_emoji(character: str) -> str: Remove skin tone and gender modifiers from the emoji. """ return ( - character - .replace("🏻", "") + character.replace("🏻", "") .replace("🏼", "") .replace("🏽", "") .replace("🏾", "") @@ -23,11 +22,11 @@ def neutralize_emoji(character: str) -> str: ) -if __name__ == '__main__': - text = ', '.join(['👨', '👨🏻', '👨🏼', '👨🏽', '👨🏾', '👨🏿']) +if __name__ == "__main__": + text = ", ".join(["👨", "👨🏻", "👨🏼", "👨🏽", "👨🏾", "👨🏿"]) print(neutralize_emoji(text)) # 👨, 👨, 👨, 👨, 👨, 👨 - text = ', '.join(['👩', '👩🏻', '👩🏼', '👩🏽', '👩🏾', '👩🏿']) + text = ", ".join(["👩", "👩🏻", "👩🏼", "👩🏽", "👩🏾", "👩🏿"]) print(neutralize_emoji(text)) # 👩, 👩, 👩, 👩, 👩, 👩 diff --git a/nltk__examples/_add_support_russian.py b/nltk__examples/_add_support_russian.py index 448e1cb06..cd36ad0ad 100644 --- a/nltk__examples/_add_support_russian.py +++ b/nltk__examples/_add_support_russian.py @@ -1,11 +1,11 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install nltk import nltk -nltk.download('punkt') +nltk.download("punkt") diff --git a/nltk__examples/_add_support_russian_stopwords.py b/nltk__examples/_add_support_russian_stopwords.py index 9ac18f5b0..319b3192b 100644 --- a/nltk__examples/_add_support_russian_stopwords.py +++ b/nltk__examples/_add_support_russian_stopwords.py @@ -1,11 +1,11 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install nltk import nltk -nltk.download('stopwords') +nltk.download("stopwords") diff --git a/nltk__examples/nltk_sent_tokenize__with_new_lines.py b/nltk__examples/nltk_sent_tokenize__with_new_lines.py index c06d8b7fc..c28747e24 100644 --- a/nltk__examples/nltk_sent_tokenize__with_new_lines.py +++ b/nltk__examples/nltk_sent_tokenize__with_new_lines.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import re @@ -19,16 +19,16 @@ sentences = [] for line in text.splitlines(): - sentences += nltk.sent_tokenize(line, language='russian') + sentences += nltk.sent_tokenize(line, language="russian") print(*map(repr, sentences)) # 'Согласен.' 'В первое ПОЕ' 'Да вся вторая часть' 'А читеры — подлецы.' -print('-' * 100) +print("-" * 100) -text = text.replace('\n', '. ') -text = re.sub('[.]{2,}', '.', text) +text = text.replace("\n", ". ") +text = re.sub("[.]{2,}", ".", text) -sentences = nltk.sent_tokenize(text, language='russian') +sentences = nltk.sent_tokenize(text, language="russian") print(*map(repr, sentences)) # 'Согласен.' 'В первое ПОЕ.' 'Да вся вторая часть.' 'А читеры — подлецы.' diff --git a/nltk__examples/split_by_sentences.py b/nltk__examples/split_by_sentences.py index 8a5bd40b7..361b5bae7 100644 --- a/nltk__examples/split_by_sentences.py +++ b/nltk__examples/split_by_sentences.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install nltk diff --git a/nltk__examples/split_by_sentences_and_words.py b/nltk__examples/split_by_sentences_and_words.py index 32895af0a..cb7fe956f 100644 --- a/nltk__examples/split_by_sentences_and_words.py +++ b/nltk__examples/split_by_sentences_and_words.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install nltk @@ -20,17 +20,17 @@ А читеры — подлецы. """ -sentences = nltk.sent_tokenize(text, language='russian') +sentences = nltk.sent_tokenize(text, language="russian") for sent in sentences: print(repr(sent)) words = nltk.word_tokenize(sent) - print(f'({len(words)}): {words}') + print(f"({len(words)}): {words}") words_no_punct = [word for word in words if not is_punctuation(word)] - print(f'({len(words_no_punct)}): {words_no_punct}') + print(f"({len(words_no_punct)}): {words_no_punct}") - words_no_punct = [word for word in words if 'PNCT' not in morph.parse(word)[0].tag] - print(f'({len(words_no_punct)}): {words_no_punct}') + words_no_punct = [word for word in words if "PNCT" not in morph.parse(word)[0].tag] + print(f"({len(words_no_punct)}): {words_no_punct}") print() diff --git a/nltk__examples/stopwords_russian.py b/nltk__examples/stopwords_russian.py index e8cedaf97..47513bbb8 100644 --- a/nltk__examples/stopwords_russian.py +++ b/nltk__examples/stopwords_russian.py @@ -1,14 +1,14 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install nltk from nltk.corpus import stopwords -print(stopwords.words('russian')) +print(stopwords.words("russian")) # ['и', 'в', 'во', 'не', 'что', 'он', 'на', 'я', 'с', 'со', 'как', 'а', 'то', 'все', 'она', # 'так', 'его', 'но', 'да', 'ты', 'к', 'у', 'же', 'вы', 'за', 'бы', 'по', 'только', 'ее', # 'мне', 'было', 'вот', 'от', 'меня', 'еще', 'нет', 'о', 'из', 'ему', 'теперь', 'когда', diff --git a/now_UTC_datetime.py b/now_UTC_datetime.py index eaf3fc64c..7017a5840 100644 --- a/now_UTC_datetime.py +++ b/now_UTC_datetime.py @@ -1,9 +1,16 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -import datetime as DT -utc_datetime = DT.datetime.utcnow() -print(utc_datetime.strftime("%d/%m/%Y %H:%M:%S")) +from datetime import datetime, timezone + +utc_datetime_old = datetime.utcnow().replace(microsecond=0) +print(utc_datetime_old) + +utc_datetime = datetime.now(timezone.utc).replace(microsecond=0) +print(utc_datetime) + +assert utc_datetime_old != utc_datetime +assert utc_datetime_old == utc_datetime.replace(tzinfo=None) diff --git a/now_UTC_datetime__previous_365_days.py b/now_UTC_datetime__previous_365_days.py index a4fa9840c..f08b36aff 100644 --- a/now_UTC_datetime__previous_365_days.py +++ b/now_UTC_datetime__previous_365_days.py @@ -1,12 +1,14 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -import datetime as DT -utc_datetime = DT.datetime.utcnow() +from datetime import datetime, timedelta, timezone + + +utc_datetime = datetime.now(timezone.utc) for i in range(365 + 1): - date = utc_datetime - DT.timedelta(days=i) + date = utc_datetime - timedelta(days=i) print(date.strftime("%d/%m/%Y %H:%M:%S")) diff --git a/office__excel__openpyxl__xlwt/draw_image_in_sheet/__init__.py b/office__excel__openpyxl__xlwt/draw_image_in_sheet/__init__.py index 06334aa61..f25732790 100644 --- a/office__excel__openpyxl__xlwt/draw_image_in_sheet/__init__.py +++ b/office__excel__openpyxl__xlwt/draw_image_in_sheet/__init__.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' - +__author__ = "ipetrash" diff --git a/office__excel__openpyxl__xlwt/draw_image_in_sheet/main.py b/office__excel__openpyxl__xlwt/draw_image_in_sheet/main.py index 9cebee2b5..8a34f582c 100644 --- a/office__excel__openpyxl__xlwt/draw_image_in_sheet/main.py +++ b/office__excel__openpyxl__xlwt/draw_image_in_sheet/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import openpyxl @@ -12,7 +12,7 @@ # SOURCE: https://github.com/gil9red/SimplePyScripts/blob/8b69f96d0bd87c10f3d0779ff46b02558c4cca84/excel__openpyxl__xlwt/xlsx__set_row_column_size.py -def set_row_column_size(ws): +def set_row_column_size(ws) -> None: # SOURCE: excel__openpyxl__xlwt\excel\xl\worksheets\sheet1.xml # ws.sheet_format.defaultColWidth = 1.77734375 @@ -34,7 +34,7 @@ def get_pixel_array(img, rgb_hex=False): r, g, b = img.getpixel((x, y)) if rgb_hex: - value = '{:02X}{:02X}{:02X}'.format(r, g, b) + value = f"{r:02X}{g:02X}{b:02X}" row.append(value) else: row.append((r, g, b)) @@ -42,8 +42,8 @@ def get_pixel_array(img, rgb_hex=False): return pixels -def draw_image(ws, img): - img = img.convert('RGB') +def draw_image(ws, img) -> None: + img = img.convert("RGB") # Resize img.thumbnail((250, 250)) @@ -56,7 +56,7 @@ def draw_image(ws, img): cell.fill = PatternFill(fgColor=pixels[i][j], fill_type="solid") -if __name__ == '__main__': +if __name__ == "__main__": wb = openpyxl.Workbook() ws = wb.active @@ -70,4 +70,4 @@ def draw_image(ws, img): ws.title = file_name draw_image(ws, img) - wb.save('excel.xlsx') + wb.save("excel.xlsx") diff --git a/office__excel__openpyxl__xlwt/draw_image_list_in_sheets/main.py b/office__excel__openpyxl__xlwt/draw_image_list_in_sheets/main.py index 8ab6d9824..51c990878 100644 --- a/office__excel__openpyxl__xlwt/draw_image_list_in_sheets/main.py +++ b/office__excel__openpyxl__xlwt/draw_image_list_in_sheets/main.py @@ -1,25 +1,24 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import glob import os +import sys import openpyxl # pip install Pillow from PIL import Image -import sys -sys.path.append('..') - # SOURCE: https://github.com/gil9red/SimplePyScripts/blob/2c4391214de936260926a47440f2bad2d6fb90da/excel__openpyxl__xlwt/draw_image_in_sheet +sys.path.append("..") from draw_image_in_sheet.main import set_row_column_size, get_pixel_array, draw_image -if __name__ == '__main__': +if __name__ == "__main__": wb = openpyxl.Workbook() # Remove default sheet @@ -28,7 +27,7 @@ wb.remove(sheet) # Append images - for file_name in glob.glob('images/*.jpg') + glob.glob('images/*.png'): + for file_name in glob.glob("images/*.jpg") + glob.glob("images/*.png"): title = os.path.basename(file_name) ws = wb.create_sheet(title) @@ -40,4 +39,4 @@ img = Image.open(file_name) draw_image(ws, img) - wb.save('excel.xlsx') + wb.save("excel.xlsx") diff --git a/office__excel__openpyxl__xlwt/find_cells_by_color/main.py b/office__excel__openpyxl__xlwt/find_cells_by_color/main.py index 4e9e5d1e1..93789d6c5 100644 --- a/office__excel__openpyxl__xlwt/find_cells_by_color/main.py +++ b/office__excel__openpyxl__xlwt/find_cells_by_color/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://ru.stackoverflow.com/a/845335/201445 @@ -10,7 +10,7 @@ from openpyxl.worksheet.worksheet import Worksheet -def find_cells_by_color(ws: Worksheet, color: str = '00000000') -> dict: +def find_cells_by_color(ws: Worksheet, color: str = "00000000") -> dict: ret = dict() for row in ws.iter_rows(): for cell in row: @@ -20,19 +20,20 @@ def find_cells_by_color(ws: Worksheet, color: str = '00000000') -> dict: return ret -if __name__ == '__main__': +if __name__ == "__main__": from openpyxl import load_workbook - wb = load_workbook('excel.xlsx') + + wb = load_workbook("excel.xlsx") ws = wb.active - print('background colors for ALL cells:\n') + print("background colors for ALL cells:\n") for row in ws.iter_rows(): for cell in row: - print(f'[{cell.coordinate}]: {cell.fill.fgColor.value}', end=' ') + print(f"[{cell.coordinate}]: {cell.fill.fgColor.value}", end=" ") print() print() - cells = find_cells_by_color(ws, color='FFFFFF00') - print(f'given color has been found in the following cells: {cells}') + cells = find_cells_by_color(ws, color="FFFFFF00") + print(f"given color has been found in the following cells: {cells}") diff --git a/office__excel__openpyxl__xlwt/get_sheets/main.py b/office__excel__openpyxl__xlwt/get_sheets/main.py index 2756b0171..a863e8001 100644 --- a/office__excel__openpyxl__xlwt/get_sheets/main.py +++ b/office__excel__openpyxl__xlwt/get_sheets/main.py @@ -1,13 +1,13 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import openpyxl -wb = openpyxl.load_workbook('excel.xlsx') +wb = openpyxl.load_workbook("excel.xlsx") print(wb.sheetnames) # ['Sheet', 'auto', 'Students', 'Students1'] for name in wb.sheetnames: diff --git a/office__excel__openpyxl__xlwt/hidden_columns.py b/office__excel__openpyxl__xlwt/hidden_columns.py index 192d0648e..43312110b 100644 --- a/office__excel__openpyxl__xlwt/hidden_columns.py +++ b/office__excel__openpyxl__xlwt/hidden_columns.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: http://openpyxl.readthedocs.io/en/stable/usage.html#fold-columns-outline @@ -13,6 +13,6 @@ wb = openpyxl.Workbook() ws = wb.active -ws.column_dimensions.group('B', 'D', hidden=True) +ws.column_dimensions.group("B", "D", hidden=True) -wb.save('excel.xlsx') +wb.save("excel.xlsx") diff --git a/office__excel__openpyxl__xlwt/insert_image/main.py b/office__excel__openpyxl__xlwt/insert_image/main.py index e9268eb8c..bb47534f4 100644 --- a/office__excel__openpyxl__xlwt/insert_image/main.py +++ b/office__excel__openpyxl__xlwt/insert_image/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: http://openpyxl.readthedocs.io/en/stable/usage.html#inserting-an-image @@ -14,12 +14,12 @@ wb = openpyxl.Workbook() ws = wb.active -ws['A1'] = 'You should see three logos below' +ws["A1"] = "You should see three logos below" # Create an image -img = Image('logo.png') +img = Image("logo.png") # Add to worksheet and anchor next to cells -ws.add_image(img, 'A2') +ws.add_image(img, "A2") -wb.save('excel.xlsx') +wb.save("excel.xlsx") diff --git a/office__excel__openpyxl__xlwt/load__or_create__append_new_sheets__xlsx.py b/office__excel__openpyxl__xlwt/load__or_create__append_new_sheets__xlsx.py index ac5bb5947..bd8639d84 100644 --- a/office__excel__openpyxl__xlwt/load__or_create__append_new_sheets__xlsx.py +++ b/office__excel__openpyxl__xlwt/load__or_create__append_new_sheets__xlsx.py @@ -1,20 +1,20 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import openpyxl -columns = ['Name', 'Age', 'Course'] +columns = ["Name", "Age", "Course"] rows = [ - ['Vasya', '16', 1], - ['Anya', '17', 2], - ['Inna', '16', 1], + ["Vasya", "16", 1], + ["Anya", "17", 2], + ["Inna", "16", 1], ] -FILE_NAME = 'excel.xlsx' +FILE_NAME = "excel.xlsx" try: wb = openpyxl.load_workbook(FILE_NAME) @@ -28,7 +28,7 @@ wb.remove(sheet) # Создание нового листа, названия новых листов будут инкрементироваться: Students, Students1, Students2, и т.п. -ws = wb.create_sheet('Students') +ws = wb.create_sheet("Students") for i, value in enumerate(columns, 1): ws.cell(row=1, column=i).value = value @@ -37,4 +37,4 @@ for j, value in enumerate(row, 1): ws.cell(row=i, column=j).value = value -wb.save('excel.xlsx') +wb.save("excel.xlsx") diff --git a/office__excel__openpyxl__xlwt/print_cells/main.py b/office__excel__openpyxl__xlwt/print_cells/main.py index 33c837385..09b1e7a67 100644 --- a/office__excel__openpyxl__xlwt/print_cells/main.py +++ b/office__excel__openpyxl__xlwt/print_cells/main.py @@ -1,13 +1,13 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import openpyxl -path = '../get_sheets/excel.xlsx' +path = "../get_sheets/excel.xlsx" wb = openpyxl.load_workbook(path) sheet = wb.active @@ -15,7 +15,7 @@ for row in range(1, sheet.max_row + 1): for col in range(1, sheet.max_column + 1): cell = sheet.cell(row=row, column=col) - print(cell.value, end=' | ') + print(cell.value, end=" | ") print() """ Language | Text | diff --git a/office__excel__openpyxl__xlwt/pyexcel_xlsx__examples/output.xlsx b/office__excel__openpyxl__xlwt/pyexcel_xlsx__examples/output.xlsx new file mode 100644 index 000000000..36fb9146d Binary files /dev/null and b/office__excel__openpyxl__xlwt/pyexcel_xlsx__examples/output.xlsx differ diff --git a/office__excel__openpyxl__xlwt/pyexcel_xlsx__examples/read.py b/office__excel__openpyxl__xlwt/pyexcel_xlsx__examples/read.py new file mode 100644 index 000000000..3222048c8 --- /dev/null +++ b/office__excel__openpyxl__xlwt/pyexcel_xlsx__examples/read.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import json +from pathlib import Path + +# pip install pyexcel-xlsx +from pyexcel_xlsx import read_data + + +DIR = Path(__file__).resolve().parent +file_name = str(DIR / "output.xlsx") + + +data = read_data(file_name) +print(json.dumps(data, ensure_ascii=False, indent=4)) +""" +{ + "Sheet 1": [ + [ + "Col1", + "Col2", + "Col3" + ], + [ + 1, + 2, + 3 + ], + [ + 4, + 5, + 6 + ] + ], + "Sheet 2": [ + [ + "row 1", + "row 2", + "row 3" + ] + ], + "Страница 3": [ + [ + "Поле:", + "Привет" + ], + [ + "Поле:", + "Мир!" + ] + ] +} +""" diff --git a/office__excel__openpyxl__xlwt/pyexcel_xlsx__examples/write.py b/office__excel__openpyxl__xlwt/pyexcel_xlsx__examples/write.py new file mode 100644 index 000000000..ea4e7b1b0 --- /dev/null +++ b/office__excel__openpyxl__xlwt/pyexcel_xlsx__examples/write.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from pathlib import Path + +# pip install pyexcel-xlsx +from pyexcel_xlsx import save_data + + +DIR = Path(__file__).resolve().parent +file_name = str(DIR / "output.xlsx") + + +data = { + "Sheet 1": [ + ["Col1", "Col2", "Col3"], + [1, 2, 3], + [4, 5, 6] + ], + "Sheet 2": [["row 1"]], +} +data.update({ + "Sheet 2": [ + ["row 1", "row 2", "row 3"] + ] +}) +data.update({ + "Страница 3": [ + ["Поле:", "Привет"], + ["Поле:", "Мир!"] + ] +}) + +save_data(file_name, data) diff --git a/office__excel__openpyxl__xlwt/random_fill_color.py b/office__excel__openpyxl__xlwt/random_fill_color.py index 30d24eb9b..892a93926 100644 --- a/office__excel__openpyxl__xlwt/random_fill_color.py +++ b/office__excel__openpyxl__xlwt/random_fill_color.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import random @@ -12,7 +12,7 @@ # SOURCE: https://github.com/gil9red/SimplePyScripts/blob/8b69f96d0bd87c10f3d0779ff46b02558c4cca84/excel__openpyxl__xlwt/xlsx__set_row_column_size.py -def set_row_column_size(ws: Worksheet): +def set_row_column_size(ws: Worksheet) -> None: # SOURCE: excel__openpyxl__xlwt\excel\xl\worksheets\sheet1.xml # ws.sheet_format.defaultColWidth = 1.77734375 @@ -22,10 +22,10 @@ def set_row_column_size(ws: Worksheet): # SOURCE: https://github.com/gil9red/SimplePyScripts/blob/1c2274804f52004b07601ece535ec74b3f82aa8d/get_random_hex_color.py def get_random_hex_color() -> str: - return ''.join(random.choices('0123456789ABCDEF', k=6)) + return "".join(random.choices("0123456789ABCDEF", k=6)) -def set_fill_color(ws: Worksheet): +def set_fill_color(ws: Worksheet) -> None: size = 250 for i in range(1, size + 1): @@ -43,4 +43,4 @@ def set_fill_color(ws: Worksheet): set_row_column_size(ws) set_fill_color(ws) -wb.save('excel.xlsx') +wb.save("excel.xlsx") diff --git a/office__excel__openpyxl__xlwt/set_cell_color.py b/office__excel__openpyxl__xlwt/set_cell_color.py index 453b24c1f..4dd7a2080 100644 --- a/office__excel__openpyxl__xlwt/set_cell_color.py +++ b/office__excel__openpyxl__xlwt/set_cell_color.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import openpyxl @@ -11,8 +11,8 @@ wb = openpyxl.Workbook() ws = wb.active -ws.cell(row=1, column=1).fill = PatternFill(fgColor='FF0000', fill_type="solid") -ws.cell(row=2, column=1).fill = PatternFill(fgColor='00FF00', fill_type="solid") -ws['A3'].fill = PatternFill(fgColor='0000FF', fill_type="solid") +ws.cell(row=1, column=1).fill = PatternFill(fgColor="FF0000", fill_type="solid") +ws.cell(row=2, column=1).fill = PatternFill(fgColor="00FF00", fill_type="solid") +ws["A3"].fill = PatternFill(fgColor="0000FF", fill_type="solid") -wb.save('excel.xlsx') +wb.save("excel.xlsx") diff --git a/office__excel__openpyxl__xlwt/xls__xlwt__hello_world.py b/office__excel__openpyxl__xlwt/xls__xlwt__hello_world.py index 26a66be22..360aed216 100644 --- a/office__excel__openpyxl__xlwt/xls__xlwt__hello_world.py +++ b/office__excel__openpyxl__xlwt/xls__xlwt__hello_world.py @@ -1,23 +1,23 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install xlwt import xlwt -columns = ['Name', 'Age', 'Course'] +columns = ["Name", "Age", "Course"] rows = [ - ['Vasya', '16', 1], - ['Anya', '17', 2], - ['Inna', '16', 1], + ["Vasya", "16", 1], + ["Anya", "17", 2], + ["Inna", "16", 1], ] wb = xlwt.Workbook() -ws = wb.add_sheet('Students') +ws = wb.add_sheet("Students") for i, column in enumerate(columns): ws.write(0, i, column) @@ -26,4 +26,4 @@ for j, data in enumerate(row): ws.write(i, j, data) -wb.save('excel.xls') +wb.save("excel.xls") diff --git a/office__excel__openpyxl__xlwt/xlsx__auto_size__columns.py b/office__excel__openpyxl__xlwt/xlsx__auto_size__columns.py index 349f14920..651585216 100644 --- a/office__excel__openpyxl__xlwt/xlsx__auto_size__columns.py +++ b/office__excel__openpyxl__xlwt/xlsx__auto_size__columns.py @@ -1,14 +1,14 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import openpyxl from openpyxl.worksheet.worksheet import Worksheet -def set_column_size(ws: Worksheet): +def set_column_size(ws: Worksheet) -> None: dims: dict[str, int] = dict() for row in ws.rows: @@ -23,13 +23,13 @@ def set_column_size(ws: Worksheet): ws.column_dimensions[col].width = value * 1.15 # Append 15% -def fill_sheet(ws: Worksheet): - columns = ['Language', 'Text'] +def fill_sheet(ws: Worksheet) -> None: + columns = ["Language", "Text"] rows = [ - ['python', '-' * 10], - ['java', 'j' * 10], - ['c#', '*' * 5], - ['c++', '0' * 50], + ["python", "-" * 10], + ["java", "j" * 10], + ["c#", "*" * 5], + ["c++", "0" * 50], ] for i, value in enumerate(columns, 1): @@ -48,8 +48,8 @@ def fill_sheet(ws: Worksheet): fill_sheet(ws) # Sheet 2 -ws = wb.create_sheet('set_column_size') +ws = wb.create_sheet("set_column_size") fill_sheet(ws) set_column_size(ws) -wb.save('excel.xlsx') +wb.save("excel.xlsx") diff --git a/office__excel__openpyxl__xlwt/xlsx__formula_cell__SUM_function.py b/office__excel__openpyxl__xlwt/xlsx__formula_cell__SUM_function.py index 479b506dd..980b13b9c 100644 --- a/office__excel__openpyxl__xlwt/xlsx__formula_cell__SUM_function.py +++ b/office__excel__openpyxl__xlwt/xlsx__formula_cell__SUM_function.py @@ -1,19 +1,14 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import openpyxl -columns = ['Language', 'Text'] -rows = [ - ['python', 1], - ['java', 2], - ['c#', 3], - ['Total:', 0] -] +columns = ["Language", "Text"] +rows = [["python", 1], ["java", 2], ["c#", 3], ["Total:", 0]] wb = openpyxl.Workbook() ws = wb.active @@ -27,6 +22,6 @@ cell.value = value # Total: -ws.cell(row=5, column=2).value = '=SUM(B2:B4)' +ws.cell(row=5, column=2).value = "=SUM(B2:B4)" -wb.save('excel.xlsx') +wb.save("excel.xlsx") diff --git a/office__excel__openpyxl__xlwt/xlsx__hyperlink_cell.py b/office__excel__openpyxl__xlwt/xlsx__hyperlink_cell.py index 57c128dcb..8efdf89a2 100644 --- a/office__excel__openpyxl__xlwt/xlsx__hyperlink_cell.py +++ b/office__excel__openpyxl__xlwt/xlsx__hyperlink_cell.py @@ -1,18 +1,18 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from urllib.parse import quote import openpyxl -columns = ['Language', 'Text', 'Hyperlink'] +columns = ["Language", "Text", "Hyperlink"] rows = [ - ['python', 'excel'], - ['java', 'excel'], - ['c#', 'excel'], + ["python", "excel"], + ["java", "excel"], + ["c#", "excel"], ] wb = openpyxl.Workbook() @@ -27,8 +27,9 @@ ws.cell(row=i, column=1).value = lang ws.cell(row=i, column=2).value = text - ws.cell(row=i, column=3).hyperlink = "https://stackoverflow.com/search?q=" + quote(lang + ' ' + text) + url = "https://stackoverflow.com/search?q=" + quote(lang + " " + text) + ws.cell(row=i, column=3).hyperlink = url ws.cell(row=i, column=3).value = "StackOverflow" -wb.save('excel.xlsx') +wb.save("excel.xlsx") diff --git a/office__excel__openpyxl__xlwt/xlsx__hyperlink_cell_to_sheet.py b/office__excel__openpyxl__xlwt/xlsx__hyperlink_cell_to_sheet.py new file mode 100644 index 000000000..43d24fd1f --- /dev/null +++ b/office__excel__openpyxl__xlwt/xlsx__hyperlink_cell_to_sheet.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import openpyxl +from openpyxl.worksheet.hyperlink import Hyperlink + + +wb = openpyxl.Workbook() + +sheet1 = wb.active + +sheet2 = wb.create_sheet(title="Лист 2") + +cell = sheet1.cell(row=1, column=1) +cell.hyperlink = f"#'{sheet2.title}'!K20" +cell.value = "Go" +cell.style = "Hyperlink" + +cell = sheet1.cell(row=2, column=1) +cell.hyperlink = Hyperlink( + location=f"'{sheet2.title}'!K20", + ref="", +) +cell.value = "Go 2" +cell.style = "Hyperlink" + +wb.save("excel.xlsx") diff --git a/office__excel__openpyxl__xlwt/xlsx__hyperlink_cell_to_sheet_for_exists.py b/office__excel__openpyxl__xlwt/xlsx__hyperlink_cell_to_sheet_for_exists.py new file mode 100644 index 000000000..630b1a382 --- /dev/null +++ b/office__excel__openpyxl__xlwt/xlsx__hyperlink_cell_to_sheet_for_exists.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import openpyxl + + +# NOTE: from xlsx__hyperlink_cell_to_sheet.py +filename = "excel.xlsx" + +wb = openpyxl.load_workbook(filename=filename) + +sheet1 = wb["Sheet"] +sheet2 = wb["Лист 2"] + +cell = sheet1.cell(row=5, column=1) +cell.hyperlink = f"#'{sheet2.title}'!B20" +cell.value = "Go 3" +cell.style = "Hyperlink" + +wb.save(filename) diff --git a/office__excel__openpyxl__xlwt/xlsx__merge_cells__and__formula_cell__SUM_function.py b/office__excel__openpyxl__xlwt/xlsx__merge_cells__and__formula_cell__SUM_function.py index deb23923e..e1b83ff07 100644 --- a/office__excel__openpyxl__xlwt/xlsx__merge_cells__and__formula_cell__SUM_function.py +++ b/office__excel__openpyxl__xlwt/xlsx__merge_cells__and__formula_cell__SUM_function.py @@ -1,17 +1,17 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import openpyxl -columns = ['Language', 'Text', 'Total'] +columns = ["Language", "Text", "Total"] rows = [ - ['python', 1], - ['java', 2], - ['c#', 3], + ["python", 1], + ["java", 2], + ["c#", 3], ] wb = openpyxl.Workbook() @@ -25,9 +25,9 @@ cell = ws.cell(row=i, column=j) cell.value = value -ws.merge_cells('C2:C4') +ws.merge_cells("C2:C4") # Total: -ws['C2'].value = '=SUM(B2:B4)' +ws["C2"].value = "=SUM(B2:B4)" -wb.save('excel.xlsx') +wb.save("excel.xlsx") diff --git a/office__excel__openpyxl__xlwt/xlsx__openpyxl__hello_world.py b/office__excel__openpyxl__xlwt/xlsx__openpyxl__hello_world.py index b5ddbd8f2..afba7b9a9 100644 --- a/office__excel__openpyxl__xlwt/xlsx__openpyxl__hello_world.py +++ b/office__excel__openpyxl__xlwt/xlsx__openpyxl__hello_world.py @@ -1,18 +1,18 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install openpyxl import openpyxl -columns = ['Name', 'Age', 'Course'] +columns = ["Name", "Age", "Course"] rows = [ - ['Vasya', '16', 1], - ['Anya', '17', 2], - ['Inna', '16', 1], + ["Vasya", "16", 1], + ["Anya", "17", 2], + ["Inna", "16", 1], ] @@ -23,7 +23,7 @@ sheet = wb[sheet_name] wb.remove(sheet) -ws = wb.create_sheet('Students') +ws = wb.create_sheet("Students") for i, value in enumerate(columns, 1): ws.cell(row=1, column=i).value = value @@ -32,4 +32,4 @@ for j, value in enumerate(row, 1): ws.cell(row=i, column=j).value = value -wb.save('excel.xlsx') +wb.save("excel.xlsx") diff --git a/office__excel__openpyxl__xlwt/xlsx__set_row_column_size.py b/office__excel__openpyxl__xlwt/xlsx__set_row_column_size.py index aeae7bfce..7324565de 100644 --- a/office__excel__openpyxl__xlwt/xlsx__set_row_column_size.py +++ b/office__excel__openpyxl__xlwt/xlsx__set_row_column_size.py @@ -1,14 +1,14 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import openpyxl from openpyxl.worksheet.worksheet import Worksheet -def set_row_column_size(ws: Worksheet): +def set_row_column_size(ws: Worksheet) -> None: # SOURCE: excel__openpyxl__xlwt\excel\xl\worksheets\sheet1.xml # ws.sheet_format.defaultColWidth = 1.77734375 @@ -22,4 +22,4 @@ def set_row_column_size(ws: Worksheet): ws = wb.create_sheet() set_row_column_size(ws) -wb.save('excel.xlsx') +wb.save("excel.xlsx") diff --git a/office__excel__openpyxl__xlwt/xlsx__set_sheet_zoom_scale.py b/office__excel__openpyxl__xlwt/xlsx__set_sheet_zoom_scale.py index 3bdcf411f..2de7489aa 100644 --- a/office__excel__openpyxl__xlwt/xlsx__set_sheet_zoom_scale.py +++ b/office__excel__openpyxl__xlwt/xlsx__set_sheet_zoom_scale.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import openpyxl @@ -10,22 +10,22 @@ wb = openpyxl.Workbook() ws = wb.active -ws.title = '10' +ws.title = "10" ws.sheet_view.zoomScale = 10 -ws = wb.create_sheet('20') +ws = wb.create_sheet("20") ws.sheet_view.zoomScale = 20 -ws = wb.create_sheet('50') +ws = wb.create_sheet("50") ws.sheet_view.zoomScale = 50 -ws = wb.create_sheet('100') +ws = wb.create_sheet("100") # ws.sheet_view.zoomScale = 100 -ws = wb.create_sheet('200') +ws = wb.create_sheet("200") ws.sheet_view.zoomScale = 200 -ws = wb.create_sheet('500') +ws = wb.create_sheet("500") ws.sheet_view.zoomScale = 500 -wb.save('excel.xlsx') +wb.save("excel.xlsx") diff --git a/office__word__doc_docx/counter_of_font.py b/office__word__doc_docx/counter_of_font.py new file mode 100644 index 000000000..8d61973c3 --- /dev/null +++ b/office__word__doc_docx/counter_of_font.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from collections import Counter +from pathlib import Path + +# pip install python-docx==1.1.2 +import docx +from docx.text.run import Run +from docx.text.paragraph import Paragraph + + +def get_font_name_and_size( + doc: docx.Document, + p: Paragraph, + r: Run, +) -> tuple[str | None, int | None]: + def _get_normal_size(size: int | None) -> int | None: + return size // 12700 if size else None + + try: + font_name = r.font.name + except: + font_name = None + + if not font_name: + try: + font_name = r.style.font.name + except: + pass + + if not font_name: + try: + font_name = p.style.font.name + except: + pass + + if not font_name: + try: + font_name = doc.styles["Normal"].font.name + except: + pass + + try: + font_size = r.font.size + except: + font_size = None + + if not font_size: + try: + font_size = r.style.font.size + except: + pass + + if not font_size: + try: + font_size = p.style.font.size + except: + pass + + if not font_size: + try: + font_size = doc.styles["Normal"].font.size + except: + pass + + return font_name, _get_normal_size(font_size) + + +def process(path: Path) -> None: + for file_name in path.glob("*.docx"): + print(file_name) + + try: + doc_file = docx.Document(file_name) + + name_and_size = [] + names = [] + sizes = [] + + for p in doc_file.paragraphs: + for r in p.runs: + name, size = get_font_name_and_size(doc_file, p, r) + name_and_size.append((name, size)) + names.append(name) + sizes.append(size) + + for (name, size), number in Counter(name_and_size).most_common(): + print((name, size), number) + + except Exception as e: + print(e) + + print() + + +if __name__ == "__main__": + path = Path(__file__).parent / "fill_template" + process(path) diff --git a/office__word__doc_docx/create_table__function.py b/office__word__doc_docx/create_table__function.py index 02ec74971..7520e7e0d 100644 --- a/office__word__doc_docx/create_table__function.py +++ b/office__word__doc_docx/create_table__function.py @@ -1,13 +1,13 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from docx import Document -def create_table(document, headers, rows, style='Table Grid'): +def create_table(document, headers, rows, style="Table Grid"): cols_number = len(headers) table = document.add_table(rows=1, cols=cols_number) @@ -27,20 +27,18 @@ def create_table(document, headers, rows, style='Table Grid'): document = Document() -headers = ('№ п/п', 'Наименование параметра', 'Единицы измерения', 'Значение') +headers = ("№ п/п", "Наименование параметра", "Единицы измерения", "Значение") records_table1 = ( - (0, 'Nan', 'Nan', 0), - (1, 'Первая величина', '-/-', 0), - (2, 'Вторая величина', '-/-', 'Базальт'), - (3, 'Третья величина', 'м^2/ч', 0) + (0, "Nan", "Nan", 0), + (1, "Первая величина", "-/-", 0), + (2, "Вторая величина", "-/-", "Базальт"), + (3, "Третья величина", "м^2/ч", 0), ) table1 = create_table(document, headers, records_table1) document.add_paragraph() -rows = [ - [x, x, x * x] for x in range(1, 10) -] -table2 = create_table(document, ('x', 'y', 'x * y'), rows) +rows = [[x, x, x * x] for x in range(1, 10)] +table2 = create_table(document, ("x", "y", "x * y"), rows) document.save("testing.docx") diff --git a/office__word__doc_docx/fill_template/save_style.py b/office__word__doc_docx/fill_template/save_style.py index 50137dd64..d11db0b6d 100644 --- a/office__word__doc_docx/fill_template/save_style.py +++ b/office__word__doc_docx/fill_template/save_style.py @@ -1,17 +1,17 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -import datetime as DT +import datetime as dt # pip install python-docx import docx # SOURCE: https://stackoverflow.com/a/55733040/5909792 -def docx_replace(doc, data): +def docx_replace(doc, data) -> None: paragraphs = list(doc.paragraphs) for t in doc.tables: for row in t.rows: @@ -20,7 +20,8 @@ def docx_replace(doc, data): paragraphs.append(paragraph) for p in paragraphs: for key, val in data.items(): - key_name = '${{{}}}'.format(key) # I'm using placeholders in the form ${PlaceholderName} + # I'm using placeholders in the form ${PlaceholderName} + key_name = "${{{}}}".format(key) if key_name in p.text: inline = p.runs # Replace strings and retain the same style. @@ -34,10 +35,11 @@ def docx_replace(doc, data): found_all = False replace_done = False for i in range(len(inline)): - # case 1: found in single run so short circuit the replace if key_name in inline[i].text and not started: - found_runs.append((i, inline[i].text.find(key_name), len(key_name))) + found_runs.append( + (i, inline[i].text.find(key_name), len(key_name)) + ) text = inline[i].text.replace(key_name, str(val)) inline[i].text = text replace_done = True @@ -49,7 +51,11 @@ def docx_replace(doc, data): continue # case 2: search for partial text, find first run - if key_name[key_index] in inline[i].text and inline[i].text[-1] in key_name and not started: + if ( + key_name[key_index] in inline[i].text + and inline[i].text[-1] in key_name + and not started + ): # check sequence start_index = inline[i].text.find(key_name[key_index]) check_length = len(inline[i].text) @@ -70,7 +76,11 @@ def docx_replace(doc, data): break # case 2: search for partial text, find subsequent run - if key_name[key_index] in inline[i].text and started and not found_all: + if ( + key_name[key_index] in inline[i].text + and started + and not found_all + ): # check sequence chars_found = 0 check_length = len(inline[i].text) @@ -90,21 +100,25 @@ def docx_replace(doc, data): for i, item in enumerate(found_runs): index, start, length = [t for t in item] if i == 0: - text = inline[index].text.replace(inline[index].text[start:start + length], str(val)) + text = inline[index].text.replace( + inline[index].text[start : start + length], str(val) + ) inline[index].text = text else: - text = inline[index].text.replace(inline[index].text[start:start + length], '') + text = inline[index].text.replace( + inline[index].text[start : start + length], "" + ) inline[index].text = text # print(p.text) -if __name__ == '__main__': - from_filename = 'template.docx' - to_filename = 'save_style.docx' +if __name__ == "__main__": + from_filename = "template.docx" + to_filename = "save_style.docx" REPLACING = { - 'title': 'My pretty title!', - 'date_time': DT.datetime.now().strftime('%Y/%m/%d %H:%M:%S'), + "title": "My pretty title!", + "date_time": dt.datetime.now().strftime("%Y/%m/%d %H:%M:%S"), } doc = docx.Document(from_filename) diff --git a/office__word__doc_docx/fill_template/simple.py b/office__word__doc_docx/fill_template/simple.py index f73227886..86fd52b63 100644 --- a/office__word__doc_docx/fill_template/simple.py +++ b/office__word__doc_docx/fill_template/simple.py @@ -1,22 +1,22 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -import datetime as DT +import datetime as dt # pip install python-docx import docx -from_filename = 'template.docx' -to_filename = 'simple.docx' +from_filename = "template.docx" +to_filename = "simple.docx" REPLACING = { - '${title}': 'My pretty title!', - '${date_time}': DT.datetime.now().strftime('%Y/%m/%d %H:%M:%S'), + "${title}": "My pretty title!", + "${date_time}": dt.datetime.now().strftime("%Y/%m/%d %H:%M:%S"), } doc = docx.Document(from_filename) diff --git a/office__word__doc_docx/hello_world/main.py b/office__word__doc_docx/hello_world/main.py index e9b51843e..866e84d79 100644 --- a/office__word__doc_docx/hello_world/main.py +++ b/office__word__doc_docx/hello_world/main.py @@ -1,47 +1,84 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/python-openxml/python-docx # SOURCE: https://python-docx.readthedocs.io/en/latest/ +import os + # pip install python-docx import docx from docx.shared import Inches, Cm, Mm document = docx.Document() -document.add_heading('Document Title', level=0) +document.add_heading("Document Title", level=0) -p = document.add_paragraph('A plain paragraph having some ') -p.add_run('bold').bold = True -p.add_run(' and some ') -p.add_run('italic.').italic = True +p = document.add_paragraph("A plain paragraph having some ") +p.add_run("bold").bold = True +p.add_run(" and some ") +p.add_run("italic.").italic = True -document.add_heading('Heading, level 1', level=1) -document.add_paragraph('Intense quote', style='Intense Quote') -document.add_paragraph('first item in unordered list', style='List Bullet') -document.add_paragraph('first item in ordered list', style='List Number') +document.add_heading("Heading, level 1", level=1) +document.add_paragraph("Intense quote", style="Intense Quote") +document.add_paragraph("first item in unordered list", style="List Bullet") +document.add_paragraph("first item in ordered list", style="List Number") document.add_page_break() # Table -document.add_heading('Table', level=1) +document.add_heading("Table", level=1) -headers = ['id', 'url', 'name', 'short_name', 'birthday', 'job'] +headers = ["id", "url", "name", "short_name", "birthday", "job"] rows = [ - ['#1', 'http://amiller.example.com', 'Andrew Miller', 'amiller', '11 December', 'Testing Engineer'], - ['#2', 'http://ataylor.example.com', 'Anthony Taylor', 'ataylor', '17 July', 'Software Engineer'], - ['#3', 'http://dmoore.example.com', 'Daniel Moore', 'dmoore', '2 March', 'Testing Engineer'], - ['#4', 'http://dsmith.example.com', 'David Smith', 'dsmith', '5 January', 'Testing Engineer'], - ['#5', 'http://awilson.example.com', 'Alexander Wilson', 'awilson', '11 April', 'Software Engineer'] + [ + "#1", + "http://amiller.example.com", + "Andrew Miller", + "amiller", + "11 December", + "Testing Engineer", + ], + [ + "#2", + "http://ataylor.example.com", + "Anthony Taylor", + "ataylor", + "17 July", + "Software Engineer", + ], + [ + "#3", + "http://dmoore.example.com", + "Daniel Moore", + "dmoore", + "2 March", + "Testing Engineer", + ], + [ + "#4", + "http://dsmith.example.com", + "David Smith", + "dsmith", + "5 January", + "Testing Engineer", + ], + [ + "#5", + "http://awilson.example.com", + "Alexander Wilson", + "awilson", + "11 April", + "Software Engineer", + ], ] # Без style='Table Grid' у таблицы не будет выделена решетка -table = document.add_table(rows=1, cols=len(headers), style='Table Grid') +table = document.add_table(rows=1, cols=len(headers), style="Table Grid") heading_cells = table.rows[0].cells for i, value in enumerate(headers): @@ -59,28 +96,27 @@ document.add_page_break() # Image -file_name_image = 'image.jpg' +file_name_image = "image.jpg" -document.add_heading('Image', level=1) +document.add_heading("Image", level=1) -document.add_heading('image__original', level=2) +document.add_heading("image__original", level=2) document.add_picture(file_name_image) -document.add_heading('image__Inches_3', level=2) +document.add_heading("image__Inches_3", level=2) document.add_picture(file_name_image, width=Inches(3.0)) -document.add_heading('image__millimeters_30', level=2) +document.add_heading("image__millimeters_30", level=2) document.add_picture(file_name_image, width=Mm(30)) -document.add_heading('image__centimeters_5', level=2) +document.add_heading("image__centimeters_5", level=2) document.add_picture(file_name_image, width=Cm(5)) document.add_page_break() # Save -file_name_doc = 'word.docx' +file_name_doc = "word.docx" document.save(file_name_doc) # Open file -import os os.startfile(file_name_doc) diff --git a/office__word__doc_docx/hyperlink.py b/office__word__doc_docx/hyperlink.py index 663272f20..7fe12fe83 100644 --- a/office__word__doc_docx/hyperlink.py +++ b/office__word__doc_docx/hyperlink.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install python-docx @@ -9,7 +9,7 @@ # SOURCE: https://github.com/python-openxml/python-docx/issues/74#issuecomment-261169410 -def add_hyperlink(paragraph, url, text, color='1111FF', underline=True): +def add_hyperlink(paragraph, url, text, color="1111FF", underline=True): """ A function that places a hyperlink within a paragraph object. @@ -23,32 +23,37 @@ def add_hyperlink(paragraph, url, text, color='1111FF', underline=True): # This gets access to the document.xml.rels file and gets a new relation id value part = paragraph.part - r_id = part.relate_to(url, docx.opc.constants.RELATIONSHIP_TYPE.HYPERLINK, is_external=True) + r_id = part.relate_to( + url, docx.opc.constants.RELATIONSHIP_TYPE.HYPERLINK, is_external=True + ) # Create the w:hyperlink tag and add needed values - hyperlink = docx.oxml.shared.OxmlElement('w:hyperlink') - hyperlink.set(docx.oxml.shared.qn('r:id'), r_id, ) + hyperlink = docx.oxml.shared.OxmlElement("w:hyperlink") + hyperlink.set( + docx.oxml.shared.qn("r:id"), + r_id, + ) # Create a w:r element - new_run = docx.oxml.shared.OxmlElement('w:r') + new_run = docx.oxml.shared.OxmlElement("w:r") # Create a new w:rPr element - rPr = docx.oxml.shared.OxmlElement('w:rPr') + rPr = docx.oxml.shared.OxmlElement("w:rPr") # Add color if it is given if not color is None: - c = docx.oxml.shared.OxmlElement('w:color') - c.set(docx.oxml.shared.qn('w:val'), color) - rPr.append(c) + c = docx.oxml.shared.OxmlElement("w:color") + c.set(docx.oxml.shared.qn("w:val"), color) + rPr.append(c) # Remove underlining if it is requested if not underline: - u = docx.oxml.shared.OxmlElement('w:u') - u.set(docx.oxml.shared.qn('w:val'), 'none') - rPr.append(u) + u = docx.oxml.shared.OxmlElement("w:u") + u.set(docx.oxml.shared.qn("w:val"), "none") + rPr.append(u) - u = docx.oxml.shared.OxmlElement('w:u') - u.set(docx.oxml.shared.qn('w:val'), 'single') + u = docx.oxml.shared.OxmlElement("w:u") + u.set(docx.oxml.shared.qn("w:val"), "single") rPr.append(u) # Join all the xml elements together add add the required text to the w:r element @@ -61,36 +66,37 @@ def add_hyperlink(paragraph, url, text, color='1111FF', underline=True): return hyperlink -if __name__ == '__main__': +if __name__ == "__main__": document = docx.Document() - document.add_heading('Hyperlink', level=0) + document.add_heading("Hyperlink", level=0) - p = document.add_paragraph('hyperlink: ') - add_hyperlink(p, 'https://google.ru', 'google') + p = document.add_paragraph("hyperlink: ") + add_hyperlink(p, "https://google.ru", "google") - p = document.add_paragraph('hyperlink: ') - add_hyperlink(p, 'https://google.ru', 'google', underline=False) + p = document.add_paragraph("hyperlink: ") + add_hyperlink(p, "https://google.ru", "google", underline=False) - p = document.add_paragraph('hyperlink: ') - add_hyperlink(p, 'https://google.ru', 'google', color='00FF00') + p = document.add_paragraph("hyperlink: ") + add_hyperlink(p, "https://google.ru", "google", color="00FF00") - p = document.add_paragraph('hyperlink: ') - add_hyperlink(p, 'https://google.ru', 'google', color='FF8822') + p = document.add_paragraph("hyperlink: ") + add_hyperlink(p, "https://google.ru", "google", color="FF8822") # Empty line document.add_paragraph() - p = document.add_paragraph('Hello World! -> ') - add_hyperlink(p, 'https://google.ru/search?q=Hello', 'Hello') - p.add_run(' ') - add_hyperlink(p, 'https://google.ru/search?q=World', 'World') - p.add_run(' -> ') - add_hyperlink(p, 'https://google.ru/search?q=Hello World!', 'Hello World!') + p = document.add_paragraph("Hello World! -> ") + add_hyperlink(p, "https://google.ru/search?q=Hello", "Hello") + p.add_run(" ") + add_hyperlink(p, "https://google.ru/search?q=World", "World") + p.add_run(" -> ") + add_hyperlink(p, "https://google.ru/search?q=Hello World!", "Hello World!") # Save - file_name_doc = 'word.docx' + file_name_doc = "word.docx" document.save(file_name_doc) # Open file import os + os.startfile(file_name_doc) diff --git a/office__word__doc_docx/read_docx/read_docx.py b/office__word__doc_docx/read_docx/read_docx.py index 83e6bfa5b..f517e961d 100644 --- a/office__word__doc_docx/read_docx/read_docx.py +++ b/office__word__doc_docx/read_docx/read_docx.py @@ -1,20 +1,25 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" +import re + # pip install python-docx from docx import Document + + document = Document("Обеденное меню 777.docx") # Регулярка для поиска последовательностей пробелов: от двух подряд и более -import re -multi_space_pattern = re.compile(r'[ ]{2,}') +multi_space_pattern = re.compile(r" {2,}") for table in document.tables: for row in table.rows: - name, weight, price = [multi_space_pattern.sub(' ', i.text.strip()) for i in row.cells] + name, weight, price = [ + multi_space_pattern.sub(" ", i.text.strip()) for i in row.cells + ] if name == weight == price or (not weight or not price): print() @@ -22,7 +27,7 @@ print(name) continue - print('{} {} {}'.format(name, weight, price)) + print(f"{name} {weight} {price}") # Таблицы в меню дублируются break diff --git a/office__word__doc_docx/table__hyperlink_cell.py b/office__word__doc_docx/table__hyperlink_cell.py index c27c8a51c..6448fec24 100644 --- a/office__word__doc_docx/table__hyperlink_cell.py +++ b/office__word__doc_docx/table__hyperlink_cell.py @@ -1,33 +1,65 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" +import os + # pip install python-docx import docx from hyperlink import add_hyperlink -headers = ('NAME', 'DESCRIPTION') +headers = ("NAME", "DESCRIPTION") rows = [ - ('php', 'PHP — скриптовый язык программирования общего назначения, активно применяемый для разработки веб-приложений. Используйте эту метку, если у Вас возникли вопросы по применению данного языка или о самом языке.'), - ('javascript', 'JavaScript (не путать с Java) — динамический, интерпретируемый язык со слабой типизацией, обычно используемый для написания скриптов на стороне клиента. Эта метка предназначена для вопросов, связанных с ECMAScript, его различными диалектами и реализациями (за исключением ActionScript). Если нет меток, относящихся к фреймворкам, предполагается, что код в ответах также не должен требовать сторонних библиотек.'), - ('java', 'Java (не путать с JavaScript) — строго типизированный объектно-ориентированный язык программирования. Приложения Java обычно транслируются в специальный байт-код, поэтому они могут работать на любой компьютерной архитектуре ,с помощью виртуальной Java-машины (JVM). Используйте эту метку для вопросов, относящихся к языку Java или инструментам из платформы Java.'), - ('android', 'Android — это операционная система от Google, основанная на ядре Linux, для цифровых устройств: телефонов, планшетов, автомобилей, телевизоров, часов и очков Google Glass. Пожалуйста, используйте специфические для Android метки, например [android-intent], а не просто [intent].'), - ('c#', 'C# (произносится «си шарп») — мультипарадигменный язык программирования, флагманский язык фреймворка .NET. В основе его лежит объектно-ориентированный подход, но он поддерживает элементы функционального программирования, взаимодействие с COM, нативным кодом и скриптовыми языками. Язык и платформа .NET обладают огромной стандартной библиотекой, а также многочисленными фреймворками.'), - ('html', 'Стандартный язык разметки гипертекста, повсеместно применяемый в интернете. Последняя действующая версия спецификации HTML5.1. Как правило, используется совместно с шаблонизатором, серверными и клиентскими скриптами, каскадными таблицами стилей для динамической генерации удобочитаемых веб страниц из информации, хранящейся в БД сервера. '), - ('c++', 'C++ — язык программирования общего назначения, синтаксис которого основан на языке C.'), - ('jquery', 'Популярная JavaScript-библиотека. Метка применяетcя к вопросам использования JQuery и ее дополнений. Должна быть дополнена меткой [javascript].'), - ('css', 'CSS, или каскадные таблицы стилей, — формальный язык описания внешнего вида документа, язык стилей, определяющий отображение HTML-документов.'), - ('mysql', 'Популярная реляционная СУБД, полностью поддерживающая стандартный SQL. Используйте метку только для вопросов, специфических для MySQL, иначе используйте [sql].') + ( + "php", + "PHP — скриптовый язык программирования общего назначения, активно применяемый для разработки веб-приложений. Используйте эту метку, если у Вас возникли вопросы по применению данного языка или о самом языке.", + ), + ( + "javascript", + "JavaScript (не путать с Java) — динамический, интерпретируемый язык со слабой типизацией, обычно используемый для написания скриптов на стороне клиента. Эта метка предназначена для вопросов, связанных с ECMAScript, его различными диалектами и реализациями (за исключением ActionScript). Если нет меток, относящихся к фреймворкам, предполагается, что код в ответах также не должен требовать сторонних библиотек.", + ), + ( + "java", + "Java (не путать с JavaScript) — строго типизированный объектно-ориентированный язык программирования. Приложения Java обычно транслируются в специальный байт-код, поэтому они могут работать на любой компьютерной архитектуре ,с помощью виртуальной Java-машины (JVM). Используйте эту метку для вопросов, относящихся к языку Java или инструментам из платформы Java.", + ), + ( + "android", + "Android — это операционная система от Google, основанная на ядре Linux, для цифровых устройств: телефонов, планшетов, автомобилей, телевизоров, часов и очков Google Glass. Пожалуйста, используйте специфические для Android метки, например [android-intent], а не просто [intent].", + ), + ( + "c#", + "C# (произносится «си шарп») — мультипарадигменный язык программирования, флагманский язык фреймворка .NET. В основе его лежит объектно-ориентированный подход, но он поддерживает элементы функционального программирования, взаимодействие с COM, нативным кодом и скриптовыми языками. Язык и платформа .NET обладают огромной стандартной библиотекой, а также многочисленными фреймворками.", + ), + ( + "html", + "Стандартный язык разметки гипертекста, повсеместно применяемый в интернете. Последняя действующая версия спецификации HTML5.1. Как правило, используется совместно с шаблонизатором, серверными и клиентскими скриптами, каскадными таблицами стилей для динамической генерации удобочитаемых веб страниц из информации, хранящейся в БД сервера. ", + ), + ( + "c++", + "C++ — язык программирования общего назначения, синтаксис которого основан на языке C.", + ), + ( + "jquery", + "Популярная JavaScript-библиотека. Метка применяетcя к вопросам использования JQuery и ее дополнений. Должна быть дополнена меткой [javascript].", + ), + ( + "css", + "CSS, или каскадные таблицы стилей, — формальный язык описания внешнего вида документа, язык стилей, определяющий отображение HTML-документов.", + ), + ( + "mysql", + "Популярная реляционная СУБД, полностью поддерживающая стандартный SQL. Используйте метку только для вопросов, специфических для MySQL, иначе используйте [sql].", + ), ] document = docx.Document() -table = document.add_table(rows=1, cols=len(headers), style='Table Grid') +table = document.add_table(rows=1, cols=len(headers), style="Table Grid") heading_cells = table.rows[0].cells for i, value in enumerate(headers): @@ -42,13 +74,12 @@ p = cells[0].paragraphs[0] - add_hyperlink(p, "https://ru.stackoverflow.com/tags/{}/info".format(name), name) + add_hyperlink(p, f"https://ru.stackoverflow.com/tags/{name}/info", name) cells[1].text = description # Save -file_name_doc = 'word.docx' +file_name_doc = "word.docx" document.save(file_name_doc) # Open file -import os os.startfile(file_name_doc) diff --git a/office__word__doc_docx/table__merge_cells.py b/office__word__doc_docx/table__merge_cells.py index 6e5e3a969..ff316e499 100644 --- a/office__word__doc_docx/table__merge_cells.py +++ b/office__word__doc_docx/table__merge_cells.py @@ -1,9 +1,11 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" +import os + # pip install python-docx import docx @@ -11,14 +13,15 @@ COL_COUNT = 5 ROW_COUNT = 5 -HEADERS = ['TABLE HEADER', '', '', '', ''] +HEADERS = ["TABLE HEADER", "", "", "", ""] ROWS = [ - ['{}x{}'.format(i, j) for j in range(1, COL_COUNT + 1)] for i in range(1, ROW_COUNT + 1) + [f"{i}x{j}" for j in range(1, COL_COUNT + 1)] + for i in range(1, ROW_COUNT + 1) ] def fill_table(document, headers, rows): - table = document.add_table(rows=1, cols=len(headers), style='Table Grid') + table = document.add_table(rows=1, cols=len(headers), style="Table Grid") heading_cells = table.rows[0].cells for i, value in enumerate(headers): @@ -54,9 +57,8 @@ def fill_table(document, headers, rows): table_2.cell(2, 2).merge(table_2.cell(2, 4)) # Save -file_name_doc = 'word.docx' +file_name_doc = "word.docx" document.save(file_name_doc) # Open file -import os os.startfile(file_name_doc) diff --git a/office__word__doc_docx/table__set_size_column.py b/office__word__doc_docx/table__set_size_column.py index 663e61079..26a28347f 100644 --- a/office__word__doc_docx/table__set_size_column.py +++ b/office__word__doc_docx/table__set_size_column.py @@ -1,32 +1,64 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" +import os + # pip install python-docx import docx from docx.shared import Cm -headers = ('NAME', 'DESCRIPTION') +headers = ("NAME", "DESCRIPTION") rows = [ - ('php', 'PHP — скриптовый язык программирования общего назначения, активно применяемый для разработки веб-приложений. Используйте эту метку, если у Вас возникли вопросы по применению данного языка или о самом языке.'), - ('javascript', 'JavaScript (не путать с Java) — динамический, интерпретируемый язык со слабой типизацией, обычно используемый для написания скриптов на стороне клиента. Эта метка предназначена для вопросов, связанных с ECMAScript, его различными диалектами и реализациями (за исключением ActionScript). Если нет меток, относящихся к фреймворкам, предполагается, что код в ответах также не должен требовать сторонних библиотек.'), - ('java', 'Java (не путать с JavaScript) — строго типизированный объектно-ориентированный язык программирования. Приложения Java обычно транслируются в специальный байт-код, поэтому они могут работать на любой компьютерной архитектуре ,с помощью виртуальной Java-машины (JVM). Используйте эту метку для вопросов, относящихся к языку Java или инструментам из платформы Java.'), - ('android', 'Android — это операционная система от Google, основанная на ядре Linux, для цифровых устройств: телефонов, планшетов, автомобилей, телевизоров, часов и очков Google Glass. Пожалуйста, используйте специфические для Android метки, например [android-intent], а не просто [intent].'), - ('c#', 'C# (произносится «си шарп») — мультипарадигменный язык программирования, флагманский язык фреймворка .NET. В основе его лежит объектно-ориентированный подход, но он поддерживает элементы функционального программирования, взаимодействие с COM, нативным кодом и скриптовыми языками. Язык и платформа .NET обладают огромной стандартной библиотекой, а также многочисленными фреймворками.'), - ('html', 'Стандартный язык разметки гипертекста, повсеместно применяемый в интернете. Последняя действующая версия спецификации HTML5.1. Как правило, используется совместно с шаблонизатором, серверными и клиентскими скриптами, каскадными таблицами стилей для динамической генерации удобочитаемых веб страниц из информации, хранящейся в БД сервера. '), - ('c++', 'C++ — язык программирования общего назначения, синтаксис которого основан на языке C.'), - ('jquery', 'Популярная JavaScript-библиотека. Метка применяетcя к вопросам использования JQuery и ее дополнений. Должна быть дополнена меткой [javascript].'), - ('css', 'CSS, или каскадные таблицы стилей, — формальный язык описания внешнего вида документа, язык стилей, определяющий отображение HTML-документов.'), - ('mysql', 'Популярная реляционная СУБД, полностью поддерживающая стандартный SQL. Используйте метку только для вопросов, специфических для MySQL, иначе используйте [sql].') + ( + "php", + "PHP — скриптовый язык программирования общего назначения, активно применяемый для разработки веб-приложений. Используйте эту метку, если у Вас возникли вопросы по применению данного языка или о самом языке.", + ), + ( + "javascript", + "JavaScript (не путать с Java) — динамический, интерпретируемый язык со слабой типизацией, обычно используемый для написания скриптов на стороне клиента. Эта метка предназначена для вопросов, связанных с ECMAScript, его различными диалектами и реализациями (за исключением ActionScript). Если нет меток, относящихся к фреймворкам, предполагается, что код в ответах также не должен требовать сторонних библиотек.", + ), + ( + "java", + "Java (не путать с JavaScript) — строго типизированный объектно-ориентированный язык программирования. Приложения Java обычно транслируются в специальный байт-код, поэтому они могут работать на любой компьютерной архитектуре ,с помощью виртуальной Java-машины (JVM). Используйте эту метку для вопросов, относящихся к языку Java или инструментам из платформы Java.", + ), + ( + "android", + "Android — это операционная система от Google, основанная на ядре Linux, для цифровых устройств: телефонов, планшетов, автомобилей, телевизоров, часов и очков Google Glass. Пожалуйста, используйте специфические для Android метки, например [android-intent], а не просто [intent].", + ), + ( + "c#", + "C# (произносится «си шарп») — мультипарадигменный язык программирования, флагманский язык фреймворка .NET. В основе его лежит объектно-ориентированный подход, но он поддерживает элементы функционального программирования, взаимодействие с COM, нативным кодом и скриптовыми языками. Язык и платформа .NET обладают огромной стандартной библиотекой, а также многочисленными фреймворками.", + ), + ( + "html", + "Стандартный язык разметки гипертекста, повсеместно применяемый в интернете. Последняя действующая версия спецификации HTML5.1. Как правило, используется совместно с шаблонизатором, серверными и клиентскими скриптами, каскадными таблицами стилей для динамической генерации удобочитаемых веб страниц из информации, хранящейся в БД сервера. ", + ), + ( + "c++", + "C++ — язык программирования общего назначения, синтаксис которого основан на языке C.", + ), + ( + "jquery", + "Популярная JavaScript-библиотека. Метка применяетcя к вопросам использования JQuery и ее дополнений. Должна быть дополнена меткой [javascript].", + ), + ( + "css", + "CSS, или каскадные таблицы стилей, — формальный язык описания внешнего вида документа, язык стилей, определяющий отображение HTML-документов.", + ), + ( + "mysql", + "Популярная реляционная СУБД, полностью поддерживающая стандартный SQL. Используйте метку только для вопросов, специфических для MySQL, иначе используйте [sql].", + ), ] document = docx.Document() -table = document.add_table(rows=1, cols=len(headers), style='Table Grid') +table = document.add_table(rows=1, cols=len(headers), style="Table Grid") heading_cells = table.rows[0].cells for i, value in enumerate(headers): @@ -51,9 +83,8 @@ cell.width = size # Save -file_name_doc = 'word.docx' +file_name_doc = "word.docx" document.save(file_name_doc) # Open file -import os os.startfile(file_name_doc) diff --git a/office__word__doc_docx/table__split_cells.py b/office__word__doc_docx/table__split_cells.py index 2e7f44901..841757774 100644 --- a/office__word__doc_docx/table__split_cells.py +++ b/office__word__doc_docx/table__split_cells.py @@ -1,23 +1,22 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" +import os + # pip install python-docx import docx -headers = ['VALUE'] -rows = [ - [''], - [''] -] +headers = ["VALUE"] +rows = [[""], [""]] document = docx.Document() -table = document.add_table(rows=1, cols=len(headers), style='Table Grid') +table = document.add_table(rows=1, cols=len(headers), style="Table Grid") heading_cells = table.rows[0].cells for i, value in enumerate(headers): @@ -34,21 +33,20 @@ # Row 1 sub_table = table.cell(1, 0).add_table(rows=1, cols=3) -sub_table.style = 'Table Grid' +sub_table.style = "Table Grid" for i, col in enumerate(sub_table.row_cells(0), 1): - col.text = str(2 ** i) + col.text = str(2**i) # Row 2 sub_table = table.cell(2, 0).add_table(rows=1, cols=3) for i, col in enumerate(sub_table.row_cells(0), 1): - col.text = str(2 ** i) + col.text = str(2**i) # Save -file_name_doc = 'word.docx' +file_name_doc = "word.docx" document.save(file_name_doc) # Open file -import os os.startfile(file_name_doc) diff --git a/online_anidub_com/get_video_list.py b/online_anidub_com/get_video_list.py index 0f1221e1f..5c29ae269 100644 --- a/online_anidub_com/get_video_list.py +++ b/online_anidub_com/get_video_list.py @@ -1,13 +1,12 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import sys import re -from typing import List from pathlib import Path from bs4 import BeautifulSoup @@ -16,8 +15,9 @@ ROOT_DIR = Path(__file__).resolve().parent -sys.path.append(str(ROOT_DIR.parent / 'using_proxy')) +sys.path.append(str(ROOT_DIR.parent / "using_proxy")) import proxy_requests__upgraded + ProxyRequests = proxy_requests__upgraded.ProxyRequests @@ -27,28 +27,28 @@ def _get_title(el) -> str: title = el.get_text(strip=True) - return re.sub(r'\s{2,}', ' ', title) + return re.sub(r"\s{2,}", " ", title) -def search_video_list(text: str) -> List[str]: - url = 'https://online.anidub.com/index.php?do=search' +def search_video_list(text: str) -> list[str]: + url = "https://online.anidub.com/index.php?do=search" data = { - 'do': 'search', - 'subaction': 'search', - 'search_start': '0', - 'full_search': '0', - 'result_from': '1', - 'story': text, + "do": "search", + "subaction": "search", + "search_start": "0", + "full_search": "0", + "result_from": "1", + "story": text, } headers = { - 'Host': 'online.anidub.com', - 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:77.0) Gecko/20100101 Firefox/77.0', - 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', - 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', - 'Accept-Encoding': 'gzip, deflate', - 'Origin': 'https://online.anidub.com', - 'Referer': 'https://online.anidub.com/index.php?do=search', + "Host": "online.anidub.com", + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:77.0) Gecko/20100101 Firefox/77.0", + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", + "Accept-Language": "ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3", + "Accept-Encoding": "gzip, deflate", + "Origin": "https://online.anidub.com", + "Referer": "https://online.anidub.com/index.php?do=search", } while True: @@ -57,24 +57,33 @@ def search_video_list(text: str) -> List[str]: content = rs.content - if b'Attention Required! | Cloudflare' in content \ - or b'Access denied | online.anidub.com used Cloudflare to restrict access' in content: - DEBUG_LOG and print('Fail! Cloudflare!') + if ( + b"Attention Required! | Cloudflare" in content + or b"Access denied | online.anidub.com used Cloudflare to restrict access" + in content + ): + DEBUG_LOG and print("Fail! Cloudflare!") continue break - root = BeautifulSoup(content, 'html.parser') - return [_get_title(a) for a in root.select('.th-title')] + root = BeautifulSoup(content, "html.parser") + return [_get_title(a) for a in root.select(".th-title")] -if __name__ == '__main__': - text = 'Моя геройская академия' +if __name__ == "__main__": + text = "Моя геройская академия" items = search_video_list(text) - print(f'Items ({len(items)}):') + print(f"Items ({len(items)}):") for x in items: - print(f' {x}') + print(f" {x}") import json - json.dump(items, open('video_list.json', 'w', encoding='utf-8'), ensure_ascii=False, indent=4) + + json.dump( + items, + open("video_list.json", "w", encoding="utf-8"), + ensure_ascii=False, + indent=4, + ) diff --git a/open_tab_webbrowser/open_tab_webbrowser.py b/open_tab_webbrowser/open_tab_webbrowser.py index 5926507d2..da2a80285 100644 --- a/open_tab_webbrowser/open_tab_webbrowser.py +++ b/open_tab_webbrowser/open_tab_webbrowser.py @@ -1,11 +1,11 @@ -__author__ = 'ipetrash' +__author__ = "ipetrash" """Пример открытия вкладки в браузере.""" -if __name__ == '__main__': +if __name__ == "__main__": import webbrowser # Вернет True и откроет вкладку - webbrowser.open_new_tab("https://github.com/gil9red") \ No newline at end of file + webbrowser.open_new_tab("https://github.com/gil9red") diff --git a/opencv (cv2) vs pil (pillow)/main.py b/opencv (cv2) vs pil (pillow)/main.py index 4f5e51d98..9b80cc660 100644 --- a/opencv (cv2) vs pil (pillow)/main.py +++ b/opencv (cv2) vs pil (pillow)/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install opencv-python @@ -9,93 +9,148 @@ import timeit -FILE_NAME = 'input.jpg' +FILE_NAME = "input.jpg" NUMBER = 100 -GLOBALS = { - 'FILE_NAME': FILE_NAME -} +GLOBALS = {"FILE_NAME": FILE_NAME} -print('Read image from file:') -print('PIL', - timeit.timeit("img = Image.open(FILE_NAME)", setup="from PIL import Image", number=NUMBER, globals=GLOBALS) +print("Read image from file:") +print( + "PIL", + timeit.timeit( + "img = Image.open(FILE_NAME)", + setup="from PIL import Image", + number=NUMBER, + globals=GLOBALS, + ), ) -print('CV2', - timeit.timeit("img = cv2.imread(FILE_NAME)", setup="import cv2", number=NUMBER, globals=GLOBALS) +print( + "CV2", + timeit.timeit( + "img = cv2.imread(FILE_NAME)", + setup="import cv2", + number=NUMBER, + globals=GLOBALS, + ), ) print() -print('Read image from file and get image bytes:') -print('PIL', timeit.timeit( - """\ +print("Read image from file and get image bytes:") +print( + "PIL", + timeit.timeit( + """\ img = Image.open(FILE_NAME) bytes_io = io.BytesIO() img.save(bytes_io, format='JPEG') img_data = bytes_io.getvalue() """, - setup="from PIL import Image\nimport io", number=NUMBER, globals=GLOBALS -)) -print('CV2', timeit.timeit( - """\ + setup="from PIL import Image\nimport io", + number=NUMBER, + globals=GLOBALS, + ), +) +print( + "CV2", + timeit.timeit( + """\ img = cv2.imread(FILE_NAME) img_data = cv2.imencode('.jpg', img)[1].tostring() """, - setup="import cv2\nimport io", number=NUMBER, globals=GLOBALS -)) + setup="import cv2\nimport io", + number=NUMBER, + globals=GLOBALS, + ), +) print() -print('Invert:') -print('PIL', timeit.timeit( - """\ +print("Invert:") +print( + "PIL", + timeit.timeit( + """\ img = Image.open(FILE_NAME) img_invert = ImageOps.invert(img) """, - setup="from PIL import Image, ImageOps", number=NUMBER, globals=GLOBALS -)) -print('CV2', timeit.timeit( - """\ + setup="from PIL import Image, ImageOps", + number=NUMBER, + globals=GLOBALS, + ), +) +print( + "CV2", + timeit.timeit( + """\ img = cv2.imread(FILE_NAME) img_invert = cv2.bitwise_not(img) """, - setup="import cv2", number=NUMBER, globals=GLOBALS -)) + setup="import cv2", + number=NUMBER, + globals=GLOBALS, + ), +) print() -print('Grayscale:') -print('PIL ', timeit.timeit( - """\ +print("Grayscale:") +print( + "PIL ", + timeit.timeit( + """\ img = Image.open(FILE_NAME) img_gray = ImageOps.grayscale(img) """, - setup="from PIL import Image, ImageOps", number=NUMBER, globals=GLOBALS -)) -print('CV2_1', timeit.timeit( - """\ + setup="from PIL import Image, ImageOps", + number=NUMBER, + globals=GLOBALS, + ), +) +print( + "CV2_1", + timeit.timeit( + """\ img = cv2.imread(FILE_NAME) img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) """, - setup="import cv2", number=NUMBER, globals=GLOBALS -)) -print('CV2_2', timeit.timeit( - """\ + setup="import cv2", + number=NUMBER, + globals=GLOBALS, + ), +) +print( + "CV2_2", + timeit.timeit( + """\ img_gray = cv2.imread(FILE_NAME, cv2.IMREAD_GRAYSCALE) """, - setup="import cv2", number=NUMBER, globals=GLOBALS -)) + setup="import cv2", + number=NUMBER, + globals=GLOBALS, + ), +) print() -print('GaussianBlur:') -print('PIL', timeit.timeit( - """\ +print("GaussianBlur:") +print( + "PIL", + timeit.timeit( + """\ img = Image.open(FILE_NAME) img_blur = img.filter(ImageFilter.GaussianBlur(2)) """, - setup="from PIL import Image, ImageFilter", number=NUMBER, globals=GLOBALS -)) -print('CV2', timeit.timeit( - """\ + setup="from PIL import Image, ImageFilter", + number=NUMBER, + globals=GLOBALS, + ), +) +print( + "CV2", + timeit.timeit( + """\ img = cv2.imread(FILE_NAME) img_blur = cv2.GaussianBlur(img, (15, 15), 0) """, - setup="import cv2", number=NUMBER, globals=GLOBALS -)) -print() \ No newline at end of file + setup="import cv2", + number=NUMBER, + globals=GLOBALS, + ), +) +print() diff --git a/opencv__Color_Detection_Tool/main.py b/opencv__Color_Detection_Tool/main.py index a96de84d0..2c70c1616 100644 --- a/opencv__Color_Detection_Tool/main.py +++ b/opencv__Color_Detection_Tool/main.py @@ -1,75 +1,109 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: http://itnotesblog.ru/note.php?id=272#sthash.e5tCuHk0.dpbs -from PyQt5 import Qt +import sys +import traceback + +from PyQt5.QtCore import Qt, QFileInfo, QSettings +from PyQt5.QtGui import QImage, qRgb, QColor, QPixmap, QPalette, QPainter, QPen +from PyQt5.QtWidgets import ( + QWidget, + QMessageBox, + QApplication, + QSlider, + QSpinBox, + QFileDialog, + QColorDialog, + QComboBox, + QRadioButton, + QDoubleSpinBox, + QCheckBox, +) from PyQt5 import uic # pip install opencv-python import cv2 + import numpy as np -def log_uncaught_exceptions(ex_cls, ex, tb): - text = '{}: {}:\n'.format(ex_cls.__name__, ex) - import traceback - text += ''.join(traceback.format_tb(tb)) +def log_uncaught_exceptions(ex_cls, ex, tb) -> None: + text = f"{ex_cls.__name__}: {ex}:\n" + text += "".join(traceback.format_tb(tb)) print(text) - Qt.QMessageBox.critical(None, 'Error', text) + QMessageBox.critical(None, "Error", text) sys.exit(1) -import sys sys.excepthook = log_uncaught_exceptions CONFIG_FILE_NAME = "config.ini" -WINDOW_TITLE = 'Color Detection Tool' -GRAY_COLOR_TABLE = [Qt.qRgb(i, i, i) for i in range(256)] +WINDOW_TITLE = "Color Detection Tool" +GRAY_COLOR_TABLE = [qRgb(i, i, i) for i in range(256)] -def numpy_array_to_QImage(numpy_array): +def numpy_array_to_QImage(numpy_array: np.ndarray) -> QImage | None: if numpy_array.dtype != np.uint8: return height, width = numpy_array.shape[:2] if len(numpy_array.shape) == 2: - img = Qt.QImage(numpy_array.data, width, height, numpy_array.strides[0], Qt.QImage.Format_Indexed8) + img = QImage( + numpy_array.data, + width, + height, + numpy_array.strides[0], + QImage.Format_Indexed8, + ) img.setColorTable(GRAY_COLOR_TABLE) return img elif len(numpy_array.shape) == 3: if numpy_array.shape[2] == 3: - img = Qt.QImage(numpy_array.data, width, height, numpy_array.strides[0], Qt.QImage.Format_RGB888) + img = QImage( + numpy_array.data, + width, + height, + numpy_array.strides[0], + QImage.Format_RGB888, + ) return img elif numpy_array.shape[2] == 4: - img = Qt.QImage(numpy_array.data, width, height, numpy_array.strides[0], Qt.QImage.Format_ARGB32) + img = QImage( + numpy_array.data, + width, + height, + numpy_array.strides[0], + QImage.Format_ARGB32, + ) return img -class MainWindow(Qt.QWidget): - def __init__(self): +class MainWindow(QWidget): + def __init__(self) -> None: super().__init__() - uic.loadUi('mainwidget.ui', self) + uic.loadUi("mainwidget.ui", self) self.setWindowTitle(WINDOW_TITLE) - self.cbPenStyle.addItem('Solid', Qt.Qt.SolidLine) - self.cbPenStyle.addItem('Dash', Qt.Qt.DashLine) - self.cbPenStyle.addItem('Dot', Qt.Qt.DotLine) - self.cbPenStyle.addItem('Dash Dot', Qt.Qt.DashDotLine) - self.cbPenStyle.addItem('Dash Dot Dot', Qt.Qt.DashDotDotLine) + self.cbPenStyle.addItem("Solid", Qt.SolidLine) + self.cbPenStyle.addItem("Dash", Qt.DashLine) + self.cbPenStyle.addItem("Dot", Qt.DotLine) + self.cbPenStyle.addItem("Dash Dot", Qt.DashDotLine) + self.cbPenStyle.addItem("Dash Dot Dot", Qt.DashDotDotLine) - self.pen_color = Qt.QColor(Qt.Qt.green) + self.pen_color = QColor(Qt.green) self.last_load_path = "." self.image_source = None @@ -77,11 +111,11 @@ def __init__(self): self.load_settings() - for w in self.findChildren(Qt.QSlider): + for w in self.findChildren(QSlider): w.sliderMoved.connect(self.refresh_HSV) name = w.objectName() - sp = self.findChild(Qt.QSpinBox, "sp" + name[2:]) + sp = self.findChild(QSpinBox, "sp" + name[2:]) if not sp: continue @@ -108,51 +142,55 @@ def __init__(self): def on_load(self): image_filters = "Images (*.jpg *.jpeg *.png *.bmp)" - file_name = Qt.QFileDialog.getOpenFileName(self, "Load image", self.last_load_path, image_filters)[0] + file_name = QFileDialog.getOpenFileName( + self, "Load image", self.last_load_path, image_filters + )[0] if not file_name: return - self.last_load_path = Qt.QFileInfo(file_name).absolutePath() + self.last_load_path = QFileInfo(file_name).absolutePath() # Load image as bytes - with open(file_name, 'rb') as f: + with open(file_name, "rb") as f: img_data = f.read() nparr = np.frombuffer(img_data, np.uint8) self.image_source = cv2.imdecode(nparr, cv2.IMREAD_UNCHANGED) height, width, channels = self.image_source.shape - self.setWindowTitle(WINDOW_TITLE + '. {}x{} ({} channels). {}'.format(width, height, channels, file_name)) + self.setWindowTitle( + f"{WINDOW_TITLE}. {width}x{height} ({channels} channels). {file_name}" + ) # Трансформация BGR->RGB если 3 канала. У картинок с прозрачностью каналов 4 и для них почему if channels == 3: code = cv2.COLOR_BGR2RGB - elif channels == 4: code = cv2.COLOR_BGRA2RGB - else: - raise Exception('Unexpected number of channels: {}'.format(channels)) + raise Exception(f"Unexpected number of channels: {channels}") self.image_source = cv2.cvtColor(self.image_source, code) self.refresh_HSV() - def show_result(self): + def show_result(self) -> None: if not self.result_img: return size = self.lbView.size() - pixmap = Qt.QPixmap.fromImage(self.result_img).scaled(size, Qt.Qt.KeepAspectRatio, Qt.Qt.SmoothTransformation) + pixmap = QPixmap.fromImage(self.result_img).scaled( + size, Qt.KeepAspectRatio, Qt.SmoothTransformation + ) self.lbView.setPixmap(pixmap) - def _update_pen_color(self): + def _update_pen_color(self) -> None: palette = self.pbPenColor.palette() - palette.setColor(Qt.QPalette.Button, self.pen_color) + palette.setColor(QPalette.Button, self.pen_color) self.pbPenColor.setPalette(palette) - def _choose_color(self): - color = Qt.QColorDialog.getColor(self.pen_color) + def _choose_color(self) -> None: + color = QColorDialog.getColor(self.pen_color) if not color.isValid(): return @@ -161,13 +199,13 @@ def _choose_color(self): self._update_pen_color() self.refresh_HSV() - def _draw_contours(self, result_img, contours): + def _draw_contours(self, result_img, contours) -> None: line_size = self.sbPenWidth.value() line_type = self.cbPenStyle.currentData() line_color = self.pen_color - p = Qt.QPainter(result_img) - p.setPen(Qt.QPen(line_color, line_size, line_type)) + p = QPainter(result_img) + p.setPen(QPen(line_color, line_size, line_type)) for c in contours: x, y, width, height = cv2.boundingRect(c) @@ -175,7 +213,7 @@ def _draw_contours(self, result_img, contours): p.end() - def refresh_HSV(self): + def refresh_HSV(self) -> None: hue_from = self.slHueFrom.value() hue_to = max(hue_from, self.slHueTo.value()) @@ -188,17 +226,17 @@ def refresh_HSV(self): hsv_min = hue_from, saturation_from, value_from hsv_max = hue_to, saturation_to, value_to - color_hsv_min = Qt.QColor.fromHsv(*hsv_min) - color_hsv_max = Qt.QColor.fromHsv(*hsv_max) + color_hsv_min = QColor.fromHsv(*hsv_min) + color_hsv_max = QColor.fromHsv(*hsv_max) - self.label_hsv_from_text.setText(', '.join(map(str, hsv_min))) - self.label_hsv_to_text.setText(', '.join(map(str, hsv_max))) + self.label_hsv_from_text.setText(", ".join(map(str, hsv_min))) + self.label_hsv_to_text.setText(", ".join(map(str, hsv_max))) - pixmap = Qt.QPixmap(1, 1) + pixmap = QPixmap(1, 1) pixmap.fill(color_hsv_min) self.lbHsvMin.setPixmap(pixmap) - pixmap = Qt.QPixmap(1, 1) + pixmap = QPixmap(1, 1) pixmap.fill(color_hsv_max) self.lbHsvMax.setPixmap(pixmap) @@ -215,27 +253,23 @@ def refresh_HSV(self): thresholded_image = cv2.inRange( thresholded_image, np.array(hsv_min, np.uint8), - np.array(hsv_max, np.uint8) + np.array(hsv_max, np.uint8), ) # Убираем шум thresholded_image = cv2.erode( - thresholded_image, - cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)) + thresholded_image, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)) ) thresholded_image = cv2.dilate( - thresholded_image, - cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)) + thresholded_image, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)) ) # Замыкаем оставшиеся крупные объекты thresholded_image = cv2.dilate( - thresholded_image, - cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)) + thresholded_image, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)) ) thresholded_image = cv2.erode( - thresholded_image, - cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)) + thresholded_image, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)) ) if self.rbCanny.isChecked(): @@ -243,13 +277,15 @@ def refresh_HSV(self): thresholded_image = cv2.Canny(thresholded_image, 100, 50, 5) if self.rbResult.isChecked(): - mode = cv2.RETR_EXTERNAL if self.chOnlyExternal.isChecked() else cv2.RETR_TREE + mode = ( + cv2.RETR_EXTERNAL + if self.chOnlyExternal.isChecked() + else cv2.RETR_TREE + ) # Находим контуры contours, _ = cv2.findContours( - thresholded_image, - mode, - cv2.CHAIN_APPROX_SIMPLE + thresholded_image, mode, cv2.CHAIN_APPROX_SIMPLE ) result_img = self.image_source.copy() @@ -266,43 +302,43 @@ def refresh_HSV(self): self.show_result() - def save_settings(self): - settings = Qt.QSettings(CONFIG_FILE_NAME, Qt.QSettings.IniFormat) + def save_settings(self) -> None: + settings = QSettings(CONFIG_FILE_NAME, QSettings.IniFormat) settings.setValue(self.objectName(), self.saveGeometry()) # TODO: рефакторинг циклов - for w in self.findChildren(Qt.QComboBox): + for w in self.findChildren(QComboBox): name = w.objectName() settings.setValue(name, w.currentIndex()) - for w in self.findChildren(Qt.QRadioButton): + for w in self.findChildren(QRadioButton): name = w.objectName() settings.setValue(name, int(w.isChecked())) - for w in self.findChildren(Qt.QSlider): + for w in self.findChildren(QSlider): name = w.objectName() settings.setValue(name, w.value()) - for w in self.findChildren(Qt.QDoubleSpinBox): + for w in self.findChildren(QDoubleSpinBox): name = w.objectName() settings.setValue(name, w.value()) - for w in self.findChildren(Qt.QSpinBox): + for w in self.findChildren(QSpinBox): name = w.objectName() settings.setValue(name, w.value()) - for w in self.findChildren(Qt.QCheckBox): + for w in self.findChildren(QCheckBox): name = w.objectName() settings.setValue(name, int(w.isChecked())) - settings.setValue('PenColor', self.pen_color.name()) + settings.setValue("PenColor", self.pen_color.name()) settings.setValue("lastLoadPath", self.last_load_path) - def load_settings(self): - settings = Qt.QSettings(CONFIG_FILE_NAME, Qt.QSettings.IniFormat) + def load_settings(self) -> None: + settings = QSettings(CONFIG_FILE_NAME, QSettings.IniFormat) geometry = settings.value(self.objectName()) if geometry: @@ -310,59 +346,59 @@ def load_settings(self): self.last_load_path = settings.value("lastLoadPath", ".") - self.pen_color = Qt.QColor(settings.value('PenColor', Qt.Qt.green)) + self.pen_color = QColor(settings.value("PenColor", Qt.green)) # TODO: рефакторинг циклов - for w in self.findChildren(Qt.QComboBox): + for w in self.findChildren(QComboBox): name = w.objectName() value = int(settings.value(name, w.currentIndex())) w.setCurrentIndex(value) - for w in self.findChildren(Qt.QRadioButton): + for w in self.findChildren(QRadioButton): name = w.objectName() value = bool(int(settings.value(name, w.isChecked()))) w.setChecked(value) - for w in self.findChildren(Qt.QSpinBox): + for w in self.findChildren(QSpinBox): name = w.objectName() value = int(settings.value(name, w.value())) w.setValue(value) - for w in self.findChildren(Qt.QDoubleSpinBox): + for w in self.findChildren(QDoubleSpinBox): name = w.objectName() value = float(settings.value(name, w.value())) w.setValue(value) - for w in self.findChildren(Qt.QCheckBox): + for w in self.findChildren(QCheckBox): name = w.objectName() value = bool(int(settings.value(name, w.isChecked()))) w.setChecked(value) - for w in self.findChildren(Qt.QSlider): + for w in self.findChildren(QSlider): name = w.objectName() value = int(settings.value(name, w.value())) w.setValue(value) - sp = self.findChild(Qt.QSpinBox, "sp" + name[2:]) + sp = self.findChild(QSpinBox, "sp" + name[2:]) if sp: sp.setMinimum(w.minimum()) sp.setMaximum(w.maximum()) sp.setValue(w.value()) - def resizeEvent(self, e): + def resizeEvent(self, e) -> None: super().resizeEvent(e) self.show_result() - def closeEvent(self, e): + def closeEvent(self, e) -> None: self.save_settings() super().closeEvent(e) -if __name__ == '__main__': - app = Qt.QApplication([]) +if __name__ == "__main__": + app = QApplication([]) mw = MainWindow() mw.show() diff --git a/opencv__examples/Find center of contour/main.py b/opencv__examples/Find center of contour/main.py index bfd4ca705..cc3aaab0b 100644 --- a/opencv__examples/Find center of contour/main.py +++ b/opencv__examples/Find center of contour/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://www.pyimagesearch.com/2016/02/01/opencv-center-of-contour/ @@ -9,8 +9,9 @@ # pip install opencv-python import cv2 -img = cv2.imread('example.jpg') -cv2.imshow('img', img) + +img = cv2.imread("example.jpg") +cv2.imshow("img", img) gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) blurred = cv2.GaussianBlur(gray, (5, 5), 0) @@ -30,13 +31,21 @@ # Draw the contour and center of the shape on the image cv2.drawContours(img, [c], -1, (0, 255, 0), 2) cv2.circle(img, (cX, cY), 7, (255, 255, 255), -1) - cv2.putText(img, "center", (cX - 20, cY - 20), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1) + cv2.putText( + img, + "center", + (cX - 20, cY - 20), + cv2.FONT_HERSHEY_SIMPLEX, + 0.5, + (255, 255, 255), + 1, + ) except ZeroDivisionError: pass # Show the image cv2.imshow("result", img) -cv2.imwrite('output.jpg', img) +cv2.imwrite("output.jpg", img) cv2.waitKey() diff --git a/opencv__examples/Finding books in images/main.py b/opencv__examples/Finding books in images/main.py index ae756c27e..d4dc3703a 100644 --- a/opencv__examples/Finding books in images/main.py +++ b/opencv__examples/Finding books in images/main.py @@ -1,14 +1,16 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://tproger.ru/translations/finding-books-python-opencv/ + # pip install opencv-python import cv2 + # Загрузка изображения image = cv2.imread("example.jpg") # cv2.imshow('image', image) @@ -56,10 +58,18 @@ cX = int(M["m10"] / M["m00"]) cY = int(M["m01"] / M["m00"]) - cv2.putText(image_result, str(total), (cX, cY), cv2.FONT_HERSHEY_SIMPLEX, 1.5, (0, 255, 0), 2) - -print("На картинке {0} книг(и)".format(total)) + cv2.putText( + image_result, + str(total), + (cX, cY), + cv2.FONT_HERSHEY_SIMPLEX, + 1.5, + (0, 255, 0), + 2, + ) + +print(f"На картинке {total} книг(и)") cv2.imwrite("output.jpg", image_result) -cv2.imshow('image_result', image_result) +cv2.imshow("image_result", image_result) cv2.waitKey() diff --git a/opencv__examples/decode_fourcc.py b/opencv__examples/decode_fourcc.py index 3e812021f..1ec804d51 100644 --- a/opencv__examples/decode_fourcc.py +++ b/opencv__examples/decode_fourcc.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" def decode_fourcc(v) -> str: @@ -9,9 +9,9 @@ def decode_fourcc(v) -> str: return "".join(chr((v >> 8 * i) & 0xFF) for i in range(4)) -if __name__ == '__main__': - assert decode_fourcc(828601953) == 'avc1' +if __name__ == "__main__": + assert decode_fourcc(828601953) == "avc1" import cv2 - assert decode_fourcc(cv2.VideoWriter_fourcc(*'XVID')) == 'XVID' - assert decode_fourcc(cv2.VideoWriter_fourcc(*'avc1')) == 'avc1' + assert decode_fourcc(cv2.VideoWriter_fourcc(*"XVID")) == "XVID" + assert decode_fourcc(cv2.VideoWriter_fourcc(*"avc1")) == "avc1" diff --git a/opencv__examples/draw_overlay_current_datetime/main.py b/opencv__examples/draw_overlay_current_datetime/main.py index 38d3d2ede..cec6777b6 100644 --- a/opencv__examples/draw_overlay_current_datetime/main.py +++ b/opencv__examples/draw_overlay_current_datetime/main.py @@ -1,18 +1,21 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -import datetime as DT +import datetime as dt import sys # pip install opencv-python import cv2 + import numpy -def draw_overlay(img: numpy.ndarray, text: str, color_text=(255, 255, 255), max_text_height_percent=5): +def draw_overlay( + img: numpy.ndarray, text: str, color_text=(255, 255, 255), max_text_height_percent=5 +) -> None: h, w, _ = img.shape max_text_height = (h / 100) * max_text_height_percent @@ -33,25 +36,29 @@ def draw_overlay(img: numpy.ndarray, text: str, color_text=(255, 255, 255), max_ text_y = h - text_rect_h // 2 # cv2.LINE_AA -- Anti aliased line - cv2.putText(img, text, (0, text_y), font, scale, color_text, thickness, lineType=cv2.LINE_AA) + cv2.putText( + img, text, (0, text_y), font, scale, color_text, thickness, lineType=cv2.LINE_AA + ) def draw_overlay_current_datetime( - img: numpy.ndarray, datetime_fmt='%d/%m/%y %H:%M:%S', - color_text=(255, 255, 255), max_text_height_percent=5 -): - text = DT.datetime.now().strftime(datetime_fmt) + img: numpy.ndarray, + datetime_fmt="%d/%m/%y %H:%M:%S", + color_text=(255, 255, 255), + max_text_height_percent=5, +) -> None: + text = dt.datetime.now().strftime(datetime_fmt) draw_overlay(img, text, color_text, max_text_height_percent) -if __name__ == '__main__': - img = cv2.imread('../gaussian_blur/example.jpg') +if __name__ == "__main__": + img = cv2.imread("../gaussian_blur/example.jpg") while True: img_overlay = img.copy() draw_overlay_current_datetime(img_overlay) - cv2.imshow('With overlay', img_overlay) + cv2.imshow("With overlay", img_overlay) if cv2.waitKey(25) == 27: # Esc to quit sys.exit() diff --git a/opencv__examples/find_objects/main.py b/opencv__examples/find_objects/main.py index 60c1bce2c..9367e2fdb 100644 --- a/opencv__examples/find_objects/main.py +++ b/opencv__examples/find_objects/main.py @@ -1,13 +1,15 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install opencv-python import cv2 -img = cv2.imread('image.png') -cv2.imshow('img', img) + + +img = cv2.imread("image.png") +cv2.imshow("img", img) gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) @@ -29,7 +31,7 @@ cv2.rectangle(img_with_rect, (x, y), (x + w, y + h), (0, 0, 255)) -cv2.imshow('img_with_rect', img_with_rect) +cv2.imshow("img_with_rect", img_with_rect) # # Draw img_with_rect_rect @@ -47,7 +49,7 @@ cv2.rectangle(img_with_rect_rect, (x, y), (x + w, y + h), (0, 0, 255)) -cv2.imshow('img_with_rect_rect', img_with_rect_rect) +cv2.imshow("img_with_rect_rect", img_with_rect_rect) cv2.waitKey() cv2.destroyAllWindows() diff --git a/opencv__examples/find_objects_by_color__hsv/main.py b/opencv__examples/find_objects_by_color__hsv/main.py index c31fdc80f..aaecb44b1 100644 --- a/opencv__examples/find_objects_by_color__hsv/main.py +++ b/opencv__examples/find_objects_by_color__hsv/main.py @@ -1,11 +1,12 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install opencv-python import cv2 + import numpy as np @@ -14,36 +15,28 @@ def find_contours(image_source_hsv, hsv_min, hsv_max): # Отфильтровываем только то, что нужно, по диапазону цветов thresholded_image = cv2.inRange( - thresholded_image, - np.array(hsv_min, np.uint8), - np.array(hsv_max, np.uint8) + thresholded_image, np.array(hsv_min, np.uint8), np.array(hsv_max, np.uint8) ) # Убираем шум thresholded_image = cv2.erode( - thresholded_image, - cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)) + thresholded_image, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)) ) thresholded_image = cv2.dilate( - thresholded_image, - cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)) + thresholded_image, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)) ) # Замыкаем оставшиеся крупные объекты thresholded_image = cv2.dilate( - thresholded_image, - cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)) + thresholded_image, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)) ) thresholded_image = cv2.erode( - thresholded_image, - cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)) + thresholded_image, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)) ) # Находим контуры contours, _ = cv2.findContours( - thresholded_image, - cv2.RETR_EXTERNAL, - cv2.CHAIN_APPROX_SIMPLE + thresholded_image, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE ) return contours @@ -65,20 +58,20 @@ def draw_rect_contours(img, hsv_min, hsv_max): return img -img = cv2.imread('example.jpg') -cv2.imshow('img', img) +img = cv2.imread("example.jpg") +cv2.imshow("img", img) # Find yellow img_yellow = draw_rect_contours(img, hsv_min=(25, 113, 220), hsv_max=(66, 255, 255)) -cv2.imshow('img_yellow', img_yellow) +cv2.imshow("img_yellow", img_yellow) # Find green img_green = draw_rect_contours(img, hsv_min=(59, 128, 130), hsv_max=(99, 255, 255)) -cv2.imshow('img_green', img_green) +cv2.imshow("img_green", img_green) # Find red img_red = draw_rect_contours(img, hsv_min=(119, 159, 184), hsv_max=(179, 255, 255)) -cv2.imshow('img_red', img_red) +cv2.imshow("img_red", img_red) cv2.waitKey() cv2.destroyAllWindows() diff --git a/opencv__examples/gaussian_blur/main.py b/opencv__examples/gaussian_blur/main.py index 40fb48e5e..9f702f6cd 100644 --- a/opencv__examples/gaussian_blur/main.py +++ b/opencv__examples/gaussian_blur/main.py @@ -1,16 +1,17 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install opencv-python import cv2 -img = cv2.imread('example.jpg') -cv2.imshow('img', img) + +img = cv2.imread("example.jpg") +cv2.imshow("img", img) img_blur = cv2.GaussianBlur(img, (5, 5), 0) -cv2.imshow('img_blur', img_blur) +cv2.imshow("img_blur", img_blur) cv2.waitKey() diff --git a/opencv__examples/gray/main.py b/opencv__examples/gray/main.py index cbc02a822..0e8403ae9 100644 --- a/opencv__examples/gray/main.py +++ b/opencv__examples/gray/main.py @@ -1,20 +1,21 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install opencv-python import cv2 -img = cv2.imread('example.jpg') -cv2.imshow('img', img) + +img = cv2.imread("example.jpg") +cv2.imshow("img", img) # Gray img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) -cv2.imshow('img_gray', img_gray) +cv2.imshow("img_gray", img_gray) -img_2 = cv2.imread('example.jpg', cv2.IMREAD_GRAYSCALE) -cv2.imshow('img_gray_2', img_2) +img_2 = cv2.imread("example.jpg", cv2.IMREAD_GRAYSCALE) +cv2.imshow("img_gray_2", img_2) cv2.waitKey() diff --git a/opencv__examples/hello.py b/opencv__examples/hello.py index 99bd4168b..b5f936443 100644 --- a/opencv__examples/hello.py +++ b/opencv__examples/hello.py @@ -1,15 +1,18 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: http://docs.opencv.org/3.1.0/dc/da5/tutorial_py_drawing_functions.html + # pip install opencv-python import numpy as np + import cv2 + # Create a black image img = np.zeros((512, 512, 3), np.uint8) @@ -25,7 +28,7 @@ cv2.polylines(img, [pts], True, (0, 255, 255)) font = cv2.FONT_HERSHEY_SIMPLEX -cv2.putText(img, 'OpenCV', (10, 500), font, 4, (255, 255, 255), 2, cv2.LINE_AA) +cv2.putText(img, "OpenCV", (10, 500), font, 4, (255, 255, 255), 2, cv2.LINE_AA) cv2.imshow("Image", img) cv2.waitKey(10000) diff --git a/opencv__examples/image_to_bytes/main.py b/opencv__examples/image_to_bytes/main.py index 5f75ebec3..a0be39500 100644 --- a/opencv__examples/image_to_bytes/main.py +++ b/opencv__examples/image_to_bytes/main.py @@ -1,14 +1,15 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install opencv-python import cv2 -img = cv2.imread('example.jpg') + +img = cv2.imread("example.jpg") # cv2.imshow('img', img) -img_data = cv2.imencode('.jpg', img)[1].tostring() +img_data = cv2.imencode(".jpg", img)[1].tostring() print(len(img_data), img_data) diff --git a/opencv__examples/invert/main.py b/opencv__examples/invert/main.py index 2e065e90f..3877c609d 100644 --- a/opencv__examples/invert/main.py +++ b/opencv__examples/invert/main.py @@ -1,23 +1,24 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install opencv-python import cv2 -img = cv2.imread('example.jpg') -cv2.imshow('img', img) + +img = cv2.imread("example.jpg") +cv2.imshow("img", img) # Invert img_invert = cv2.bitwise_not(img) -cv2.imshow('img_invert', img_invert) -cv2.imwrite('img_invert.jpg', img_invert) +cv2.imshow("img_invert", img_invert) +cv2.imwrite("img_invert.jpg", img_invert) # Gray img_invert_gray = cv2.cvtColor(img_invert, cv2.COLOR_BGR2GRAY) -cv2.imshow('img_invert_gray', img_invert_gray) -cv2.imwrite('img_invert_gray.jpg', img_invert_gray) +cv2.imshow("img_invert_gray", img_invert_gray) +cv2.imwrite("img_invert_gray.jpg", img_invert_gray) cv2.waitKey() diff --git a/opencv__examples/pil_image__to__opencv_image.py b/opencv__examples/pil_image__to__opencv_image.py index 377fa7e01..7badecb24 100644 --- a/opencv__examples/pil_image__to__opencv_image.py +++ b/opencv__examples/pil_image__to__opencv_image.py @@ -1,22 +1,23 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -# pip install opencv-python -# pip install Pillow - import pyautogui + +# pip install opencv-python import cv2 + import numpy + pil_image = pyautogui.screenshot(region=(200, 200, 200, 200)) print(pil_image.size) -pil_image.show('pil_image') +pil_image.show("pil_image") opencv_image = cv2.cvtColor(numpy.array(pil_image), cv2.COLOR_RGB2BGR) print(opencv_image.shape[:2]) -cv2.imshow('opencv_image', opencv_image) +cv2.imshow("opencv_image", opencv_image) cv2.waitKey() diff --git a/opencv__examples/remove_contours/main.py b/opencv__examples/remove_contours/main.py index 311fb174b..bc7de737e 100644 --- a/opencv__examples/remove_contours/main.py +++ b/opencv__examples/remove_contours/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://www.pyimagesearch.com/2015/02/09/removing-contours-image-using-python-opencv/ @@ -9,6 +9,7 @@ # pip install opencv-python import cv2 + import numpy as np diff --git a/opencv__examples/screenshot__full.py b/opencv__examples/screenshot__full.py index 49c2f12c8..546e993c6 100644 --- a/opencv__examples/screenshot__full.py +++ b/opencv__examples/screenshot__full.py @@ -1,16 +1,18 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install opencv-python import cv2 + import numpy as np import pyautogui + img = pyautogui.screenshot() img = cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR) -cv2.imshow('img', img) +cv2.imshow("img", img) cv2.waitKey() diff --git a/opencv__examples/screenshot__resize.py b/opencv__examples/screenshot__resize.py index 6daaef996..1ed1dbf02 100644 --- a/opencv__examples/screenshot__resize.py +++ b/opencv__examples/screenshot__resize.py @@ -1,11 +1,12 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install opencv-python import cv2 + import numpy as np import pyautogui @@ -13,6 +14,6 @@ img = pyautogui.screenshot() img = cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR) img = cv2.resize(img, None, fx=0.5, fy=0.5, interpolation=cv2.INTER_CUBIC) -cv2.imshow('img', img) +cv2.imshow("img", img) cv2.waitKey() diff --git a/opencv__examples/screenshots_in_window.py b/opencv__examples/screenshots_in_window.py index 8c12bef7b..9c0229788 100644 --- a/opencv__examples/screenshots_in_window.py +++ b/opencv__examples/screenshots_in_window.py @@ -1,22 +1,24 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://ru.stackoverflow.com/questions/926556/ import threading + import cv2 import numpy as np + from PIL import ImageGrab target_array = None -def go(): +def go() -> None: global target_array while True: if target_array is not None: @@ -28,7 +30,7 @@ def go(): break -if __name__ == '__main__': +if __name__ == "__main__": thread = threading.Thread(target=go) thread.start() diff --git a/opencv__examples/show_edges_Canny/main.py b/opencv__examples/show_edges_Canny/main.py index 2413a32d4..0c204c21a 100644 --- a/opencv__examples/show_edges_Canny/main.py +++ b/opencv__examples/show_edges_Canny/main.py @@ -1,23 +1,24 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install opencv-python import cv2 -img = cv2.imread('example.jpg') -cv2.imshow('img', img) + +img = cv2.imread("example.jpg") +cv2.imshow("img", img) # Edges Canny edges = cv2.Canny(img, 50, 100) -cv2.imshow('Edges Canny', edges) -cv2.imwrite('edges_canny.jpg', edges) +cv2.imshow("Edges Canny", edges) +cv2.imwrite("edges_canny.jpg", edges) # Edges Canny Invert edges_invert = cv2.bitwise_not(edges) -cv2.imshow('Edges Canny Invert', edges_invert) -cv2.imwrite('edges_canny_invert.jpg', edges_invert) +cv2.imshow("Edges Canny Invert", edges_invert) +cv2.imwrite("edges_canny_invert.jpg", edges_invert) cv2.waitKey() diff --git a/opencv__examples/show_fullscreen_window.py b/opencv__examples/show_fullscreen_window.py index 04ceafdbf..ef7738d25 100644 --- a/opencv__examples/show_fullscreen_window.py +++ b/opencv__examples/show_fullscreen_window.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import sys @@ -9,7 +9,7 @@ window_name = "window" -img = cv2.imread('gaussian_blur/example.jpg') +img = cv2.imread("gaussian_blur/example.jpg") while True: cv2.namedWindow(window_name, cv2.WND_PROP_FULLSCREEN) diff --git a/opencv__examples/show_with_pyqt5.py b/opencv__examples/show_with_pyqt5.py index 3e4506550..b91b975a8 100644 --- a/opencv__examples/show_with_pyqt5.py +++ b/opencv__examples/show_with_pyqt5.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import sys @@ -16,12 +16,12 @@ class ThreadOpenCV(QThread): changePixmap = pyqtSignal(QImage) - def __init__(self, source): + def __init__(self, source) -> None: super().__init__() self.source = source - def run(self): + def run(self) -> None: # SOURCE: https://stackoverflow.com/a/44404713/5909792 cap = cv2.VideoCapture(self.source) while True: @@ -31,7 +31,9 @@ def run(self): rgbImage = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) h, w, ch = rgbImage.shape bytesPerLine = ch * w - convertToQtFormat = QImage(rgbImage.data, w, h, bytesPerLine, QImage.Format_RGB888) + convertToQtFormat = QImage( + rgbImage.data, w, h, bytesPerLine, QImage.Format_RGB888 + ) p = convertToQtFormat.scaled(640, 480, Qt.KeepAspectRatio) self.changePixmap.emit(p) @@ -39,13 +41,13 @@ def run(self): class Widget(QWidget): - def __init__(self): + def __init__(self) -> None: super().__init__() self.label_video = QLabel() self.label_video.setMinimumSize(600, 480) - self.pb_play = QPushButton('Play') + self.pb_play = QPushButton("Play") self.pb_play.clicked.connect(self.playVideo) self.thread = ThreadOpenCV("video.mp4") @@ -57,14 +59,14 @@ def __init__(self): self.setLayout(main_layout) - def playVideo(self): + def playVideo(self) -> None: self.thread.start() - def setImage(self, image): + def setImage(self, image) -> None: self.label_video.setPixmap(QPixmap.fromImage(image)) -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication(sys.argv) mw = Widget() diff --git a/ordered_json.py b/ordered_json.py index 017175816..5c9737043 100644 --- a/ordered_json.py +++ b/ordered_json.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://stackoverflow.com/a/6921760/5909792 @@ -10,17 +10,18 @@ import json from collections import OrderedDict + json_data = {"foo": 1, "bar": 2, "abc": {"a": 1, "b": 2, "c": 3}} ordered_json_data = OrderedDict() -ordered_json_data['foo'] = 1 -ordered_json_data['bar'] = 2 -ordered_json_data['abc'] = OrderedDict() -ordered_json_data['abc']['a'] = 1 -ordered_json_data['abc']['b'] = 2 -ordered_json_data['abc']['c'] = 3 +ordered_json_data["foo"] = 1 +ordered_json_data["bar"] = 2 +ordered_json_data["abc"] = OrderedDict() +ordered_json_data["abc"]["a"] = 1 +ordered_json_data["abc"]["b"] = 2 +ordered_json_data["abc"]["c"] = 3 -print('Dumps:') +print("Dumps:") print(json.dumps(json_data, indent=4)) # { @@ -46,7 +47,7 @@ # } print() -print('Loads:') +print("Loads:") data = json.loads(ordered_json_str) print(json.dumps(data, indent=4)) diff --git a/pad__unpad__example.py b/pad__unpad__example.py index ce2422f06..0868339c7 100644 --- a/pad__unpad__example.py +++ b/pad__unpad__example.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" def pad(s: bytes, bs=8) -> bytes: @@ -14,27 +14,28 @@ def unpad(s: bytes) -> bytes: return s[:-pad_size] -if __name__ == '__main__': - data = b'Hello' +if __name__ == "__main__": + data = b"Hello" padded_data = pad(data) - print(padded_data) # b'Hello\x03\x03\x03' - print(unpad(padded_data)) # b'Hello' - print(unpad(padded_data).decode('utf-8')) # Hello + print(padded_data) # b'Hello\x03\x03\x03' + print(unpad(padded_data)) # b'Hello' + print(unpad(padded_data).decode("utf-8")) # Hello assert data == unpad(pad(data)) print() - assert b'123' == unpad(pad(b'123')) - assert b'123' * 9999 == unpad(pad(b'123' * 9999)) - assert b'11111111' == unpad(pad(b'11111111')) - assert b'abcd123' == unpad(pad(b'abcd123')) + assert b"123" == unpad(pad(b"123")) + assert b"123" * 9999 == unpad(pad(b"123" * 9999)) + assert b"11111111" == unpad(pad(b"11111111")) + assert b"abcd123" == unpad(pad(b"abcd123")) - print(unpad(b'12\x02\x02')) # b'12' - print(unpad(b'1\x01')) # b'1' + print(unpad(b"12\x02\x02")) # b'12' + print(unpad(b"1\x01")) # b'1' print() - data = 'Привет!'.encode('utf-8') + data = "Привет!".encode("utf-8") padded_data = pad(data) - print(padded_data) # b'\xd0\x9f\xd1\x80\xd0\xb8\xd0\xb2\xd0\xb5\xd1\x82!\x03\x03\x03' + print(padded_data) + # b'\xd0\x9f\xd1\x80\xd0\xb8\xd0\xb2\xd0\xb5\xd1\x82!\x03\x03\x03' print(unpad(padded_data)) # b'\xd0\x9f\xd1\x80\xd0\xb8\xd0\xb2\xd0\xb5\xd1\x82!' - print(unpad(padded_data).decode('utf-8')) # Привет! + print(unpad(padded_data).decode("utf-8")) # Привет! assert data == unpad(pad(data)) diff --git a/paint with turtle/example.py b/paint with turtle/example.py index b6e92c848..d374a5370 100644 --- a/paint with turtle/example.py +++ b/paint with turtle/example.py @@ -1,11 +1,13 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import turtle -turtle.color('red', 'yellow') + + +turtle.color("red", "yellow") turtle.begin_fill() while True: diff --git a/pandas__examples/Analysis jira/download_jira_log.py b/pandas__examples/Analysis jira/download_jira_log.py index b5b68bbe4..fab52a3f5 100644 --- a/pandas__examples/Analysis jira/download_jira_log.py +++ b/pandas__examples/Analysis jira/download_jira_log.py @@ -1,24 +1,27 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import pathlib -from urllib.parse import urlparse + from datetime import datetime +from urllib.parse import urlparse +from bs4 import BeautifulSoup -URL = 'https://helpdesk.compassluxe.com/sr/jira.issueviews:searchrequest-xml/24381/SearchRequest-24381.xml?tempMax=1000' + +URL = "https://helpdesk.compassluxe.com/sr/jira.issueviews:searchrequest-xml/24381/SearchRequest-24381.xml?tempMax=1000" HEADERS = { - 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:44.0) Gecko/20100101 Firefox/44.0', + "User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:44.0) Gecko/20100101 Firefox/44.0", } # NOTE. Get : openssl pkcs12 -nodes -out key.pem -in file.p12 -PEM_FILE_NAME = 'ipetrash.pem' +PEM_FILE_NAME = "ipetrash.pem" -FILE_NAME = 'jira_log__clear.xml' -FILE_NAME_FULL = 'jira_log.xml' +FILE_NAME = "jira_log__clear.xml" +FILE_NAME_FULL = "jira_log.xml" def generate_file_name(url: str) -> str: @@ -32,63 +35,64 @@ def generate_file_name(url: str) -> str: result = urlparse(url) path = pathlib.Path(result.path) - current_date_str = datetime.now().date().strftime('%Y-%m-%d') + current_date_str = datetime.now().date().strftime("%Y-%m-%d") - return f'{path.name}__{result.query}__{current_date_str}.xml' + return f"{path.name}__{result.query}__{current_date_str}.xml" def clear_jira_xml(xml_content: bytes) -> bytes: - xml_content = xml_content\ - .replace(b'TranzAxis', b'FooBar')\ - .replace(b'compassplus', b'foobar')\ - .replace(b'Compass Plus', b'Foo Bar') + xml_content = ( + xml_content.replace(b"TranzAxis", b"FooBar") + .replace(b"compassplus", b"foobar") + .replace(b"Compass Plus", b"Foo Bar") + ) - from bs4 import BeautifulSoup - root = BeautifulSoup(xml_content, 'html.parser') + root = BeautifulSoup(xml_content, "html.parser") - def mask_tag_content(css_selector, attr=None): + def mask_tag_content(css_selector, attr=None) -> None: for x in root.select(css_selector): - x.string = '...' + x.string = "..." if attr: - x[attr] = '...' + x[attr] = "..." - def delete_tag(css_selector): + def delete_tag(css_selector) -> None: for x in root.select(css_selector): x.decompose() - delete_tag('comments > comment') - delete_tag('attachments > attachment') - delete_tag('customfields') - delete_tag('issuelinks') - delete_tag('subtasks') - - mask_tag_content('[username]', 'username') - mask_tag_content('item > title') - mask_tag_content('item > summary') - mask_tag_content('item > description') - mask_tag_content('item > link') - mask_tag_content('item > key') - mask_tag_content('item > version') - mask_tag_content('item > fixversion') - mask_tag_content('item > component') - mask_tag_content('item > environment') - mask_tag_content('item > project', 'key') + delete_tag("comments > comment") + delete_tag("attachments > attachment") + delete_tag("customfields") + delete_tag("issuelinks") + delete_tag("subtasks") + + mask_tag_content("[username]", "username") + mask_tag_content("item > title") + mask_tag_content("item > summary") + mask_tag_content("item > description") + mask_tag_content("item > link") + mask_tag_content("item > key") + mask_tag_content("item > version") + mask_tag_content("item > fixversion") + mask_tag_content("item > component") + mask_tag_content("item > environment") + mask_tag_content("item > project", "key") # return root.prettify(encoding='utf-8') - return str(root).encode('utf-8') + return str(root).encode("utf-8") -if __name__ == '__main__': +if __name__ == "__main__": import requests + rs = requests.get(URL, headers=HEADERS, cert=PEM_FILE_NAME) print(rs) print(len(rs.text), repr(rs.text[:50])) # file_name = generate_file_name(URL) - with open(FILE_NAME_FULL, mode='wb') as f: + with open(FILE_NAME_FULL, mode="wb") as f: f.write(rs.content) - with open(FILE_NAME, mode='wb') as f: + with open(FILE_NAME, mode="wb") as f: f.write(clear_jira_xml(rs.content)) diff --git a/pandas__examples/Analysis jira/main.py b/pandas__examples/Analysis jira/main.py index 4591e2b11..39207e39c 100644 --- a/pandas__examples/Analysis jira/main.py +++ b/pandas__examples/Analysis jira/main.py @@ -1,21 +1,30 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from datetime import datetime -from download_jira_log import FILE_NAME -data_file = FILE_NAME +import matplotlib.pyplot as plt +from matplotlib.pyplot import FormatStrFormatter + +import pandas as pd from bs4 import BeautifulSoup -root = BeautifulSoup(open(data_file, mode='rb'), 'lxml') + +from download_jira_log import FILE_NAME + + +data_file = FILE_NAME +root = BeautifulSoup(open(data_file, mode="rb"), "lxml") records = [] for resolved in root.select("item > resolved"): # date local is en_US - resolved_date_time = datetime.strptime(resolved.text.strip(), '%a, %d %b %Y %H:%M:%S %z') + resolved_date_time = datetime.strptime( + resolved.text.strip(), "%a, %d %b %Y %H:%M:%S %z" + ) resolved_year = resolved_date_time.year resolved_year_month = datetime(resolved_date_time.year, resolved_date_time.month, 1) @@ -23,39 +32,39 @@ records.sort(key=lambda x: x[0]) -import pandas as pd -df = pd.DataFrame(data=records, columns=['resolved_date_time', 'resolved_year', 'resolved_year_month']) +df = pd.DataFrame( + data=records, columns=["resolved_date_time", "resolved_year", "resolved_year_month"] +) print(df) -print('Total rows:', len(df)) +print("Total rows:", len(df)) -df_month = pd.DataFrame({'count': df.groupby("resolved_year_month").size()}).reset_index() +df_month = pd.DataFrame( + {"count": df.groupby("resolved_year_month").size()} +).reset_index() print(df_month) print() -df_year = pd.DataFrame({'count': df.groupby("resolved_year").size()}).reset_index() +df_year = pd.DataFrame({"count": df.groupby("resolved_year").size()}).reset_index() print(df_year) -import matplotlib.pyplot as plt -from matplotlib.pyplot import FormatStrFormatter - fig = plt.figure(1) -fig.suptitle('Analysis jira', fontsize=14, fontweight='bold') +fig.suptitle("Analysis jira", fontsize=14, fontweight="bold") ax1 = fig.add_subplot(121) -ax1.plot(df_month['resolved_year_month'], df_month['count']) +ax1.plot(df_month["resolved_year_month"], df_month["count"]) ax1.grid() -ax1.set_title('Jira by month') -ax1.set_xlabel('Date') -ax1.set_ylabel('Count') +ax1.set_title("Jira by month") +ax1.set_xlabel("Date") +ax1.set_ylabel("Count") plt.gcf().autofmt_xdate() ax2 = fig.add_subplot(122) -ax2.plot(df_year['resolved_year'], df_year['count']) +ax2.plot(df_year["resolved_year"], df_year["count"]) ax2.ticklabel_format(useOffset=False) -ax2.xaxis.set_major_formatter(FormatStrFormatter('%d')) -ax2.set_title('Jira by year') -ax2.set_xlabel('Date') -ax2.set_ylabel('Count') +ax2.xaxis.set_major_formatter(FormatStrFormatter("%d")) +ax2.set_title("Jira by year") +ax2.set_xlabel("Date") +ax2.set_ylabel("Count") ax2.grid() plt.gcf().autofmt_xdate() diff --git a/pandas__examples/Analysis of account detail (html)/main.py b/pandas__examples/Analysis of account detail (html)/main.py index 9193eabf0..304685cb7 100644 --- a/pandas__examples/Analysis of account detail (html)/main.py +++ b/pandas__examples/Analysis of account detail (html)/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # TODO: сделать детализацию счета и заказать в html/excel @@ -9,11 +9,16 @@ # сделать обработку excel на pandas: Analysis of account detail (excel) import zipfile -with zipfile.ZipFile('Doc_df7c89c378c04e8daf69257ea95d9a2e.zip') as f: - data_file = f.read('Doc_df7c89c378c04e8daf69257ea95d9a2e.html') +from datetime import datetime +import pandas as pd from bs4 import BeautifulSoup -root = BeautifulSoup(data_file, 'lxml') + + +with zipfile.ZipFile("Doc_df7c89c378c04e8daf69257ea95d9a2e.zip") as f: + data_file = f.read("Doc_df7c89c378c04e8daf69257ea95d9a2e.html") + +root = BeautifulSoup(data_file, "lxml") records = [] for tr in root.select("table > tbody > tr"): @@ -27,59 +32,76 @@ # td_list[6].text, # 'Зона направления вызова/номер сессии' td_list[7].text, # 'Услуга' td_list[9].text, # 'Длительность/Объем (мин.:сек.)/(Kb)' - float(td_list[10].text.replace(',', '.')), # 'Стоимость руб. без НДС' + float(td_list[10].text.replace(",", ".")), # 'Стоимость руб. без НДС' ] records.append(record) columns = [ - 'Дата', 'Время', 'GMT', 'Номер', + "Дата", + "Время", + "GMT", + "Номер", # 'Зона вызова', 'Зона направления вызова/номер сессии', - 'Услуга', 'Длительность/Объем (мин.:сек.)/(Kb)', 'Стоимость руб. без НДС' + "Услуга", + "Длительность/Объем (мин.:сек.)/(Kb)", + "Стоимость руб. без НДС", ] -import pandas as pd df = pd.DataFrame(data=records, columns=columns) # print(df) -print('Total rows:', len(df)) +print("Total rows:", len(df)) -df_with_null_price = df[df['Стоимость руб. без НДС'] == 0] -print('With null price:', len(df_with_null_price)) +df_with_null_price = df[df["Стоимость руб. без НДС"] == 0] +print("With null price:", len(df_with_null_price)) -df_with_price = df[df['Стоимость руб. без НДС'] > 0] -print('With price:', len(df_with_price)) +df_with_price = df[df["Стоимость руб. без НДС"] > 0] +print("With price:", len(df_with_price)) -phone_list = sorted(set(df['Номер'].tolist())) -print('\nPhones ({}): {}'.format(len(phone_list), phone_list)) +phone_list = sorted(set(df["Номер"].tolist())) +print(f"\nPhones ({len(phone_list)}): {phone_list}") -print('\nShow target phone:') -df_dns_shop = df[df['Номер'].str.contains('sms:DNS-SHOP')] -print(' DNS-SHOP: number: {}, total price: {}'.format(len(df_dns_shop), df_dns_shop['Стоимость руб. без НДС'].sum())) +print("\nShow target phone:") +df_dns_shop = df[df["Номер"].str.contains("sms:DNS-SHOP")] +print( + f" DNS-SHOP: number: {len(df_dns_shop)}, total price: {df_dns_shop['Стоимость руб. без НДС'].sum()}" +) -df_maginfo = df[df['Номер'].str.contains('sms:Maginfo')] -print(' Maginfo: number: {}, total price: {}'.format(len(df_maginfo), df_maginfo['Стоимость руб. без НДС'].sum())) +df_maginfo = df[df["Номер"].str.contains("sms:Maginfo")] +print( + f" Maginfo: number: {len(df_maginfo)}, total price: {df_maginfo['Стоимость руб. без НДС'].sum()}" +) -df_sms_ru = df[df['Номер'].str.contains('sms:SMS.RU')] -print(' SMS.RU: number: {}, total price: {}'.format(len(df_sms_ru), df_sms_ru['Стоимость руб. без НДС'].sum())) +df_sms_ru = df[df["Номер"].str.contains("sms:SMS.RU")] +print( + f" SMS.RU: number: {len(df_sms_ru)}, total price: {df_sms_ru['Стоимость руб. без НДС'].sum()}" +) -print('\nPrint details (Maginfo):') +print("\nPrint details (Maginfo):") print(df_maginfo.to_string()) # Посчитать сумму за июнь, июль и август -print('\nPrice for months 06, 07 and 08:') -print(' ' + str(df[df['Дата'].str.contains('.06.')]['Стоимость руб. без НДС'].sum())) -print(' ' + str(df[df['Дата'].str.contains('.07.')]['Стоимость руб. без НДС'].sum())) -print(' ' + str(df[df['Дата'].str.contains('.08.')]['Стоимость руб. без НДС'].sum())) - -print('\nPrint details sorting by price (top 10):') -df_with_price_sorted = df_with_price.sort_values(by='Стоимость руб. без НДС', ascending=True).tail(10) +print("\nPrice for months 06, 07 and 08:") +print(" " + str(df[df["Дата"].str.contains(".06.")]["Стоимость руб. без НДС"].sum())) +print(" " + str(df[df["Дата"].str.contains(".07.")]["Стоимость руб. без НДС"].sum())) +print(" " + str(df[df["Дата"].str.contains(".08.")]["Стоимость руб. без НДС"].sum())) + +print("\nPrint details sorting by price (top 10):") +df_with_price_sorted = df_with_price.sort_values( + by="Стоимость руб. без НДС", ascending=True +).tail(10) print(df_with_price_sorted.to_string()) -df_by_phone = df_with_price[df_with_price['Номер'].str.contains('79510000000')] -print('\nPrint total price by phone:', df_by_phone['Стоимость руб. без НДС'].sum()) +df_by_phone = df_with_price[df_with_price["Номер"].str.contains("79510000000")] +print("\nPrint total price by phone:", df_by_phone["Стоимость руб. без НДС"].sum()) -df_months_06_07_08 = df_with_price[df_with_price['Дата'].str.contains('|'.join(['.06.', '.07.', '.08.']))] -print('\nTotal price for months 06, 07 and 08:', df_months_06_07_08['Стоимость руб. без НДС'].sum()) +df_months_06_07_08 = df_with_price[ + df_with_price["Дата"].str.contains("|".join([".06.", ".07.", ".08."])) +] +print( + "\nTotal price for months 06, 07 and 08:", + df_months_06_07_08["Стоимость руб. без НДС"].sum(), +) print(df_months_06_07_08.to_string()) # @@ -87,20 +109,24 @@ # показать. # Таблице нужно поле Итого # -print('\n\nPrint internet info:') -df_internet = df[df['Номер'] == 'internet.mts.ru'] +print("\n\nPrint internet info:") +df_internet = df[df["Номер"] == "internet.mts.ru"] -from datetime import datetime -data_list = sorted(set(df_internet['Дата'].tolist()), key=lambda data: datetime.strptime(data, '%d.%m.%Y')) +data_list = sorted( + set(df_internet["Дата"].tolist()), + key=lambda data: datetime.strptime(data, "%d.%m.%Y"), +) total_mb = 0 for data in data_list: - kb_list = df_internet[df_internet['Дата'] == data]['Длительность/Объем (мин.:сек.)/(Kb)'].tolist() - kb_list = [int(kb.replace('Kb', '')) for kb in kb_list] + kb_list = df_internet[df_internet["Дата"] == data][ + "Длительность/Объем (мин.:сек.)/(Kb)" + ].tolist() + kb_list = [int(kb.replace("Kb", "")) for kb in kb_list] sum_mb = sum(kb_list) // 1024 total_mb += sum_mb - print(data, sum_mb, 'MB') + print(data, sum_mb, "MB") -print('Total:', total_mb, 'MB') +print("Total:", total_mb, "MB") diff --git a/pandas__examples/json_to_csv.py b/pandas__examples/json_to_csv.py index 28905b570..80c4800f0 100644 --- a/pandas__examples/json_to_csv.py +++ b/pandas__examples/json_to_csv.py @@ -1,21 +1,27 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" + + +import pandas as pd # SOURCE: https://ru.stackoverflow.com/a/846931/201445 json_data = { - 'items': [ - {'first_name': 'Оля', 'id': 111111, 'last_name': 'Сущенко'}, - {'first_name': 'Георгий', 'id': 222222, 'last_name': 'Голосов'}, - {'first_name': 'Максим', 'id': 333333, 'home_phone': '+79909999999', 'last_name': 'Тупиченков'} + "items": [ + {"first_name": "Оля", "id": 111111, "last_name": "Сущенко"}, + {"first_name": "Георгий", "id": 222222, "last_name": "Голосов"}, + { + "first_name": "Максим", + "id": 333333, + "home_phone": "+79909999999", + "last_name": "Тупиченков", + }, ] } - -import pandas as pd -df = pd.io.json.json_normalize(json_data['items']) +df = pd.io.json.json_normalize(json_data["items"]) print(df) -df.to_csv('out.csv', index=False) +df.to_csv("out.csv", index=False) diff --git a/pandas__examples/json_to_excel.py b/pandas__examples/json_to_excel.py index 0e3933d11..b4ffe815d 100644 --- a/pandas__examples/json_to_excel.py +++ b/pandas__examples/json_to_excel.py @@ -1,7 +1,11 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" + + +import json +import pandas as pd json_str = """\ @@ -67,10 +71,9 @@ } """ -import json data = json.loads(json_str) # SOURCE: https://ru.stackoverflow.com/questions/671333 -import pandas as pd -df = pd.DataFrame(data['d']['Items']) -df.set_index('CouponCode')['TotalPrizeValue'].reset_index().to_excel('result.xlsx') + +df = pd.DataFrame(data["d"]["Items"]) +df.set_index("CouponCode")["TotalPrizeValue"].reset_index().to_excel("result.xlsx") diff --git a/pandas__examples/print_characters_tekken__who_were_in_all_series.py b/pandas__examples/print_characters_tekken__who_were_in_all_series.py index 75bcb071d..6f8f990af 100644 --- a/pandas__examples/print_characters_tekken__who_were_in_all_series.py +++ b/pandas__examples/print_characters_tekken__who_were_in_all_series.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from table_from__html_by_url__wiki__tekken import get_df @@ -10,8 +10,8 @@ df = get_df() # Столбцы-серии игр: ['Tekken', 'Tekken 2', 'Tekken 3', 'Tekken 4', 'Tekken 5', 'Tekken 6', 'Tekken 7'] -cols = [col for col in df.columns if col.startswith('Tekken')] +cols = [col for col in df.columns if col.startswith("Tekken")] for index, row in df.iterrows(): - if all(row[col] == 'Y' for col in cols): + if all(row[col] == "Y" for col in cols): print(row[0]) diff --git a/pandas__examples/process_txt_two_columns/main.py b/pandas__examples/process_txt_two_columns/main.py index 12d7f9459..2860aac5e 100644 --- a/pandas__examples/process_txt_two_columns/main.py +++ b/pandas__examples/process_txt_two_columns/main.py @@ -1,14 +1,15 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import pandas as pd + df = pd.read_csv("001.txt", sep=":", header=None, dtype="str") for index, row in df.iterrows(): - row[1] = ''.join('1' if x == '0' else '0' for x in row[1].strip()) + row[1] = "".join("1" if x == "0" else "0" for x in row[1].strip()) df.to_csv("002.txt", index=None, header=None, sep=":") diff --git a/pandas__examples/show dataframe in QTableWidget.py b/pandas__examples/show dataframe in QTableWidget.py index ca82d2b8b..05dbc9130 100644 --- a/pandas__examples/show dataframe in QTableWidget.py +++ b/pandas__examples/show dataframe in QTableWidget.py @@ -1,26 +1,26 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -from PyQt5.QtWidgets import * +import pandas as pd def generate_test_data(): records = [ - (1, 'Vasya', 16), - (2, 'Vasya2', 18), - (3, 'Vasya3', 34), - (4, 'Vasya4', 10), - (5, 'Vasya5', 19), + (1, "Vasya", 16), + (2, "Vasya2", 18), + (3, "Vasya3", 34), + (4, "Vasya4", 10), + (5, "Vasya5", 19), ] + return pd.DataFrame(data=records, columns=["ID", "NAME", "AGE"]) - import pandas as pd - return pd.DataFrame(data=records, columns=['ID', 'NAME', 'AGE']) +if __name__ == "__main__": + from PyQt5.QtWidgets import * -if __name__ == '__main__': # Подготовка данных df = generate_test_data() headers = df.columns.values.tolist() diff --git a/pandas__examples/table_from__html_by_url.py b/pandas__examples/table_from__html_by_url.py index d050ad142..2c3da99b3 100644 --- a/pandas__examples/table_from__html_by_url.py +++ b/pandas__examples/table_from__html_by_url.py @@ -1,14 +1,15 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install pandas import pandas as pd -url = 'http://www.gks.ru/bgd/regl/b12_14p/IssWWW.exe/Stg/d01/05-02.htm' + +url = "http://www.gks.ru/bgd/regl/b12_14p/IssWWW.exe/Stg/d01/05-02.htm" df = pd.read_html(url, header=0, index_col=0)[0] print(df) -df.to_excel('table.xlsx') +df.to_excel("table.xlsx") diff --git a/pandas__examples/table_from__html_by_url__wiki__tekken.py b/pandas__examples/table_from__html_by_url__wiki__tekken.py index 7707a3eab..835780c2e 100644 --- a/pandas__examples/table_from__html_by_url__wiki__tekken.py +++ b/pandas__examples/table_from__html_by_url__wiki__tekken.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install pandas @@ -15,21 +15,21 @@ def get_df(): - url = 'https://ru.wikipedia.org/wiki/Список_персонажей_Tekken' + url = "https://ru.wikipedia.org/wiki/Список_персонажей_Tekken" rs = requests.get(url) # Удаление сносок -- из-за них имена персонажей и значения присутствия в конкретной серии показывается # невалидно, например "Акума14", а не "Акума" - root = BeautifulSoup(rs.content, 'html.parser') - for sub in root.select('sup'): + root = BeautifulSoup(rs.content, "html.parser") + for sub in root.select("sup"): sub.decompose() df_list = pd.read_html(str(root), header=0) return df_list[0] -if __name__ == '__main__': +if __name__ == "__main__": df = get_df() print(df) - df.to_excel('tekken_table.xlsx', index=False) + df.to_excel("tekken_table.xlsx", index=False) diff --git a/parse_command_with_phone__doska.ru.py b/parse_command_with_phone__doska.ru.py index cbfe4d54b..362dd0652 100644 --- a/parse_command_with_phone__doska.ru.py +++ b/parse_command_with_phone__doska.ru.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """ @@ -20,21 +20,22 @@ """ -def _ph_dec(g, r, k): - from base64 import b64decode - decode_base64 = b64decode(g).decode('utf-8') +import re +from base64 import b64decode + - import re - g = re.sub(r'%([a-fA-F0-9]{2})', lambda m: chr(int(m.group(1), 16)), decode_base64) +def _ph_dec(g, r, k): + decode_base64 = b64decode(g).decode("utf-8") + g = re.sub(r"%([a-fA-F0-9]{2})", lambda m: chr(int(m.group(1), 16)), decode_base64) n = len(r) d = len(g) - c = '' + c = "" f = 0 while f < d: - q = g[f: f + 1] - p = r[f % n: f % n + 1] + q = g[f : f + 1] + p = r[f % n : f % n + 1] if k == 1: q = ord(q[0]) - ord(p[0]) @@ -60,44 +61,56 @@ def gpzd(data, key): def get_phones_from_command(cmd): phones = list() - cmd = cmd.split('\t') + cmd = cmd.split("\t") for i in cmd: - data = i.split('|') + data = i.split("|") b = gpzd(data[1], data[2]) - phone = _ph_dec(b, 'K0dbVwzGrpLa-wRs2', 2) + phone = _ph_dec(b, "K0dbVwzGrpLa-wRs2", 2) phones.append(phone) return phones -if __name__ == '__main__': - cmd = '1|JTg0JTkxbyU5NnVqJTdDJThGc2drWnR6JTkxWA==|22481814 ' \ - '2|JTg1JTk3cCU5NHZscyU5NHloeVlyJTdCJTk0aXIlN0UlOEUlNjAlODQlOTElN0MlNUR1aWclNjA=|31950540 ' \ - '3|JTg1JTk0cCU5NnVmJTg0JTk2dWhuJTVCdHolOEQlNjA=|44314626' +if __name__ == "__main__": + cmd = ( + "1|JTg0JTkxbyU5NnVqJTdDJThGc2drWnR6JTkxWA==|22481814 " + "2|JTg1JTk3cCU5NHZscyU5NHloeVlyJTdCJTk0aXIlN0UlOEUlNjAlODQlOTElN0MlNUR1aWclNjA=|31950540 " + "3|JTg1JTk0cCU5NnVmJTg0JTk2dWhuJTVCdHolOEQlNjA=|44314626" + ) print(get_phones_from_command(cmd)) # ['-00-00', '11-822-14', '-00-00'] - cmd = '1|JTg3JTkyJTdDJTk2dmZxJTk2c2hwWHQlN0IlOEVmdHglOEUlNUIlODQlN0QlN0NXciVBMWElNjA=|23649509 ' \ - '2|JThBJTk1JTdEJTk0dGtvJTk3cWx3WnJ6bCU5QnV2JTkyJTVFJTg2a3olNUN0bF9k|95446977 ' \ - '3|JTg1JTkybyU5N3pvJTgzJTkycWhsWnUlN0YlOTZf|40841102' + cmd = ( + "1|JTg3JTkyJTdDJTk2dmZxJTk2c2hwWHQlN0IlOEVmdHglOEUlNUIlODQlN0QlN0NXciVBMWElNjA=|23649509 " + "2|JThBJTk1JTdEJTk0dGtvJTk3cWx3WnJ6bCU5QnV2JTkyJTVFJTg2a3olNUN0bF9k|95446977 " + "3|JTg1JTkybyU5N3pvJTgzJTkycWhsWnUlN0YlOTZf|40841102" + ) print(get_phones_from_command(cmd)) # ['94-711-03', '54-829-39', '-00-00'] - cmd = '1|JTg4V2slOTN1ZnYlOTV1aSU3QiU4RXVWdyU5NXglN0J2JTkycSU3QiU5MiU5OXMlN0QlOTFa|56764591 ' \ - '2|JThBJTk1dSU4RXlpcCU5NXFsbiU5NHBabiU4Rnh3cCU5NXd2JTk2JTk3bSU3RCU4RCU1Qw==|96021657 ' \ - '3|JTg4JThGbiU4RnZsJTgzJTkzcWtpWW0lN0IlOTNf|85533937' + cmd = ( + "1|JTg4V2slOTN1ZnYlOTV1aSU3QiU4RXVWdyU5NXglN0J2JTkycSU3QiU5MiU5OXMlN0QlOTFa|56764591 " + "2|JThBJTk1dSU4RXlpcCU5NXFsbiU5NHBabiU4Rnh3cCU5NXd2JTk2JTk3bSU3RCU4RCU1Qw==|96021657 " + "3|JTg4JThGbiU4RnZsJTgzJTkzcWtpWW0lN0IlOTNf|85533937" + ) print(get_phones_from_command(cmd)) # ['60-164-75', '51-632-25', '-00-00'] - cmd = '1|JTg3JTdDJTgwVXklOEV6JTVCbyU5RCU3RV9yJUExell0dXpreGclN0RadCU3RSU4RCU5MHglODB1ZQ==|46551687 ' \ - '2|JTg3ZyU3Q1QlN0MlOEV5JTVFcCU5RCU3Q1clN0RoeSU1RHQlOEQlN0NYJTdEeHlfciU3QiU5RSU4Rnp6bWc=|2094350 ' \ - '3|JTg0JTk2byU5NnVtJTdDJTkycm5sJTVFcCU3RCU4RSU1Qg==|3088581' + cmd = ( + "1|JTg3JTdDJTgwVXklOEV6JTVCbyU5RCU3RV9yJUExell0dXpreGclN0RadCU3RSU4RCU5MHglODB1ZQ==|46551687 " + "2|JTg3ZyU3Q1QlN0MlOEV5JTVFcCU5RCU3Q1clN0RoeSU1RHQlOEQlN0NYJTdEeHlfciU3QiU5RSU4Rnp6bWc=|2094350 " + "3|JTg0JTk2byU5NnVtJTdDJTkycm5sJTVFcCU3RCU4RSU1Qg==|3088581" + ) print(get_phones_from_command(cmd)) # ['49-397-09', '79-935-37', '-00-00'] - cmd = '1|JTg4JTkwayU5M3htJTdFJThGdWtqVnElN0QlOTRa|86770681 ' \ - '2|JTg5JTk2biU5MnZndiU5NndqJThFJTVCcCU3Q2glQTF0JTdDJTkxXyU4N3klN0NYdyU3QmVj|80583093 ' \ - '3|JTg3JTkycSU4RXFtJTdCJTk1d2psJTVDbHYlOTRX|74342308' + cmd = ( + "1|JTg4JTkwayU5M3htJTdFJThGdWtqVnElN0QlOTRa|86770681 " + "2|JTg5JTk2biU5MnZndiU5NndqJThFJTVCcCU3Q2glQTF0JTdDJTkxXyU4N3klN0NYdyU3QmVj|80583093 " + "3|JTg3JTkycSU4RXFtJTdCJTk1d2psJTVDbHYlOTRX|74342308" + ) print(get_phones_from_command(cmd)) # ['-00-00', '50-328-41', '-00-00'] - cmd = '1|JTg3JThFbyU4RXlpJTdGJThFc2poWmwlN0UlOTAlNUI=|67355029 ' \ - '2|JTg5cHclOTB2bnMlOTN1aiU4NFduJTdCJTk2aXF6JTkxXyU4NCU4RCU3Q193JTlFY2M=|80050871 ' \ - '3|JTg4JThFcyU5M3dvJTgwJTk3eWtoJTVFcSU3QyU5NiU1Qw==|84769395' + cmd = ( + "1|JTg3JThFbyU4RXlpJTdGJThFc2poWmwlN0UlOTAlNUI=|67355029 " + "2|JTg5cHclOTB2bnMlOTN1aiU4NFduJTdCJTk2aXF6JTkxXyU4NCU4RCU3Q193JTlFY2M=|80050871 " + "3|JTg4JThFcyU5M3dvJTgwJTk3eWtoJTVFcSU3QyU5NiU1Qw==|84769395" + ) print(get_phones_from_command(cmd)) # ['-00-00', '33-212-58', '-00-00'] diff --git a/parse_en_ru_words/iturizmo.ru_razgovorniki_russko-anglijskij-razgovornik/main.py b/parse_en_ru_words/iturizmo.ru_razgovorniki_russko-anglijskij-razgovornik/main.py deleted file mode 100644 index d6a32d714..000000000 --- a/parse_en_ru_words/iturizmo.ru_razgovorniki_russko-anglijskij-razgovornik/main.py +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -__author__ = 'ipetrash' - - -import requests -rs = requests.get('http://iturizmo.ru/razgovorniki/russko-anglijskij-razgovornik.html') - -from bs4 import BeautifulSoup -root = BeautifulSoup(rs.content, 'html.parser') - -en_ru_items = [] - -for tr in root.select(".entry-content tr"): - td_list = [td.text.strip() for td in tr.select('td')] - if not td_list: - continue - - ru, en = td_list[:2] - en_ru_items.append((en, ru)) - -print(len(en_ru_items), en_ru_items) - -import json -json.dump(en_ru_items, open('en_ru.json', 'w', encoding='utf-8'), indent=4, ensure_ascii=False) diff --git a/parse_games_mail_ru__future_hits.py b/parse_games_mail_ru__future_hits.py deleted file mode 100644 index 163a2aa0e..000000000 --- a/parse_games_mail_ru__future_hits.py +++ /dev/null @@ -1,56 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -__author__ = 'ipetrash' - - -def get_game_list(): - from urllib.parse import urljoin - - import requests - rs = requests.get('https://games.mail.ru/pc/games/future_hits/') - - from bs4 import BeautifulSoup - root = BeautifulSoup(rs.content, 'lxml') - - items = [] - - # Перебор табличек с играми - for item in root.select('.b-pc__entities-item'): - a = item.select_one('.b-pc__entities-title > a') - title = a.text.strip() - - url = urljoin(rs.url, a['href']) - - description = item.select_one('.b-pc__entities-descr').text.strip() - img_url = item.select_one('.b-pc__entities-img')['src'] - release_date = item.select_one('.b-pc__entities-author').text.strip() - - items.append((title, description, release_date, url, img_url)) - - return items - - -if __name__ == '__main__': - game_list = get_game_list() - - def print_list(items): - for i, (title, description, release_date, url, img_url) in enumerate(items, 1): - print('{:2}. "{}" ({}): {} [{}]\n{}\n'.format(i, title, release_date, url, img_url, description)) - - # Full - print_list(game_list) - - # # First 5 - # new_game_list = game_list[:5] - # print_list(new_game_list) - # - # # Sorted by title - # new_game_list = sorted(game_list, key=lambda x: x[0]) - # print_list(new_game_list) - - # # Sorted by year, reverse - # import re - # get_year = lambda text: int(re.search('\d{4}', text).group()) - # new_game_list = sorted(game_list, key=lambda x: get_year(x[2]), reverse=True) - # print_list(new_game_list) diff --git a/parse_jira_logged_time/favicon.ico b/parse_jira_logged_time/favicon.ico deleted file mode 100644 index b5cb32db6..000000000 Binary files a/parse_jira_logged_time/favicon.ico and /dev/null differ diff --git a/parse_jira_logged_time/gui.py b/parse_jira_logged_time/gui.py deleted file mode 100644 index aa9d00d64..000000000 --- a/parse_jira_logged_time/gui.py +++ /dev/null @@ -1,269 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -__author__ = 'ipetrash' - - -import json -import io -import sys -import traceback -import webbrowser - -from contextlib import redirect_stdout -from datetime import datetime - -from PyQt5.Qt import ( - QApplication, QMessageBox, QThread, pyqtSignal, QMainWindow, QPushButton, QCheckBox, QPlainTextEdit, - QVBoxLayout, QHBoxLayout, QTextOption, QTableWidget, QWidget, QSizePolicy, QSplitter, Qt, QTableWidgetItem, - QProgressDialog, QHeaderView, QSystemTrayIcon, QIcon, QEvent, QTimer -) - -from main import ( - DIR, get_rss_jira_log, parse_logged_dict, get_logged_list_by_now_utc_date, get_logged_total_seconds, - get_sorted_logged, seconds_to_str -) - - -def log_uncaught_exceptions(ex_cls, ex, tb): - text = f'{ex_cls.__name__}: {ex}:\n' - text += ''.join(traceback.format_tb(tb)) - - print(text) - QMessageBox.critical(None, 'Error', text) - sys.exit(1) - - -sys.excepthook = log_uncaught_exceptions - - -class RunFuncThread(QThread): - run_finished = pyqtSignal(object) - - def __init__(self, func): - super().__init__() - - self.func = func - - def run(self): - self.run_finished.emit(self.func()) - - -WINDOW_TITLE = 'parse_jira_logged_time' - - -class MainWindow(QMainWindow): - def __init__(self): - super().__init__() - - self.setWindowTitle(WINDOW_TITLE) - - file_name = str(DIR / 'favicon.ico') - icon = QIcon(file_name) - - self.setWindowIcon(icon) - - self.tray = QSystemTrayIcon(icon) - self.tray.setToolTip(self.windowTitle()) - self.tray.activated.connect(self._on_tray_activated) - self.tray.show() - - self.logged_dict = dict() - - self.pb_refresh = QPushButton('REFRESH') - self.pb_refresh.clicked.connect(self.refresh) - - self.cb_show_log = QCheckBox() - self.cb_show_log.setChecked(True) - - self.log = QPlainTextEdit() - self.log.setReadOnly(True) - self.log.setWordWrapMode(QTextOption.NoWrap) - log_font = self.log.font() - log_font.setFamily('Courier New') - self.log.setFont(log_font) - - self.cb_show_log.clicked.connect(self.log.setVisible) - self.log.setVisible(self.cb_show_log.isChecked()) - - header_labels = ['DATE', 'TOTAL LOGGED TIME'] - self.table_logged = QTableWidget() - self.table_logged.setEditTriggers(QTableWidget.NoEditTriggers) - self.table_logged.setSelectionBehavior(QTableWidget.SelectRows) - self.table_logged.setSelectionMode(QTableWidget.SingleSelection) - self.table_logged.setColumnCount(len(header_labels)) - self.table_logged.setHorizontalHeaderLabels(header_labels) - self.table_logged.horizontalHeader().setStretchLastSection(True) - self.table_logged.itemClicked.connect(self._on_table_logged_item_clicked) - - header_labels = ['TIME', 'LOGGED', 'JIRA'] - self.table_logged_info = QTableWidget() - self.table_logged_info.setEditTriggers(QTableWidget.NoEditTriggers) - self.table_logged_info.setSelectionBehavior(QTableWidget.SelectRows) - self.table_logged_info.setSelectionMode(QTableWidget.SingleSelection) - self.table_logged_info.setColumnCount(len(header_labels)) - self.table_logged_info.setHorizontalHeaderLabels(header_labels) - self.table_logged_info.horizontalHeader().setSectionResizeMode(1, QHeaderView.ResizeToContents) - self.table_logged_info.horizontalHeader().setStretchLastSection(True) - self.table_logged_info.itemDoubleClicked.connect(self._on_table_logged_info_item_double_clicked) - - main_layout = QVBoxLayout() - - central_widget = QWidget() - central_widget.setLayout(main_layout) - - self.setCentralWidget(central_widget) - - self.pb_refresh.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)) - - h_layout = QHBoxLayout() - h_layout.addWidget(self.pb_refresh) - h_layout.addWidget(self.cb_show_log) - - layout_table_widget = QVBoxLayout() - layout_table_widget.setContentsMargins(0, 0, 0, 0) - layout_table_widget.addWidget(self.table_logged) - layout_table_widget.addWidget(self.table_logged_info) - - table_widget = QWidget() - table_widget.setLayout(layout_table_widget) - - splitter = QSplitter(Qt.Horizontal) - splitter.addWidget(table_widget) - splitter.addWidget(self.log) - - main_layout.addLayout(h_layout) - main_layout.addWidget(splitter) - - def _fill_tables(self, xml_data: bytes): - buffer_io = io.StringIO() - try: - with redirect_stdout(buffer_io): - print(len(xml_data), repr(xml_data[:50])) - - # Структура документа -- xml - self.logged_dict = parse_logged_dict(xml_data) - print(self.logged_dict) - - if not self.logged_dict: - return - - print(json.dumps(self.logged_dict, indent=4, ensure_ascii=False)) - print() - - logged_list = get_logged_list_by_now_utc_date(self.logged_dict) - - logged_total_seconds = get_logged_total_seconds(logged_list) - logged_total_seconds_str = seconds_to_str(logged_total_seconds) - print('entry_logged_list:', logged_list) - print('today seconds:', logged_total_seconds) - print('today time:', logged_total_seconds_str) - print() - - # Для красоты выводим результат в табличном виде - lines = [] - - # Удаление строк таблицы - while self.table_logged.rowCount(): - self.table_logged.removeRow(0) - - for i, (date_str, logged_list) in enumerate(get_sorted_logged(self.logged_dict)): - total_seconds = get_logged_total_seconds(logged_list) - total_seconds_str = seconds_to_str(total_seconds) - row = date_str, total_seconds_str, total_seconds - lines.append(row) - - self.table_logged.setRowCount(self.table_logged.rowCount() + 1) - - self.table_logged.setItem(i, 0, QTableWidgetItem(date_str)) - - item = QTableWidgetItem(total_seconds_str) - item.setToolTip('Total seconds: {}'.format(total_seconds)) - self.table_logged.setItem(i, 1, item) - - self.table_logged.setCurrentCell(0, 0) - self.table_logged.setFocus() - self._on_table_logged_item_clicked(self.table_logged.currentItem()) - - # Список строк станет списком столбцов, у каждого столбца подсчитается максимальная длина - max_len_columns = [max(map(len, map(str, col))) for col in zip(*lines)] - - # Создание строки форматирования: [30, 14, 5] -> "{:<30} | {:<14} | {:<5}" - my_table_format = ' | '.join('{:<%s}' % max_len for max_len in max_len_columns) - - for line in lines: - print(my_table_format.format(*line)) - - finally: - text = buffer_io.getvalue() - self.log.setPlainText(text) - - print(text) - - def refresh(self): - progress_dialog = QProgressDialog(self) - - thread = RunFuncThread(func=get_rss_jira_log) - thread.run_finished.connect(self._fill_tables) - thread.run_finished.connect(progress_dialog.close) - thread.start() - - progress_dialog.setWindowTitle('Please wait...') - progress_dialog.setLabelText(progress_dialog.windowTitle()) - progress_dialog.setRange(0, 0) - progress_dialog.exec() - - self.setWindowTitle(WINDOW_TITLE + ". Last refresh date: " + datetime.now().strftime('%d/%m/%Y %H:%M:%S')) - - def _on_table_logged_item_clicked(self, item: QTableWidgetItem): - # Удаление строк таблицы - while self.table_logged_info.rowCount(): - self.table_logged_info.removeRow(0) - - row = item.row() - date_str = self.table_logged.item(row, 0).text() - logged_list = self.logged_dict[date_str] - logged_list = reversed(logged_list) - - for i, logged in enumerate(logged_list): - self.table_logged_info.setRowCount(self.table_logged_info.rowCount() + 1) - - self.table_logged_info.setItem(i, 0, QTableWidgetItem(logged['time'])) - self.table_logged_info.setItem(i, 1, QTableWidgetItem(logged['logged_human_time'])) - - item = QTableWidgetItem(logged['jira_id']) - item.setToolTip(logged['jira_title']) - self.table_logged_info.setItem(i, 2, item) - - def _on_table_logged_info_item_double_clicked(self, item: QTableWidgetItem): - row = item.row() - jira_id = self.table_logged_info.item(row, 2).text() - - url = 'https://helpdesk.compassluxe.com/browse/' + jira_id - webbrowser.open(url) - - def _on_tray_activated(self, reason): - self.setVisible(not self.isVisible()) - - if self.isVisible(): - self.showNormal() - self.activateWindow() - - def changeEvent(self, event: QEvent): - if event.type() == QEvent.WindowStateChange: - # Если окно свернули - if self.isMinimized(): - # Прячем окно с панели задач - QTimer.singleShot(0, self.hide) - - -if __name__ == '__main__': - app = QApplication([]) - - mw = MainWindow() - mw.resize(1200, 800) - mw.show() - - mw.refresh() - - app.exec() diff --git a/parse_jira_logged_time/main.py b/parse_jira_logged_time/main.py deleted file mode 100644 index 9161556b1..000000000 --- a/parse_jira_logged_time/main.py +++ /dev/null @@ -1,139 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -__author__ = 'ipetrash' - - -import re -import sys - -from collections import defaultdict -from datetime import datetime, timezone -from pathlib import Path -from typing import Dict, List, Tuple - -import requests -from bs4 import BeautifulSoup - -DIR = Path(__file__).resolve().parent - -sys.path.append(str(DIR.parent)) -from logged_human_time_to_seconds import logged_human_time_to_seconds -from seconds_to_str import seconds_to_str - - -URL = 'https://helpdesk.compassluxe.com/activity?maxResults=100&streams=user+IS+ipetrash&os_authType=basic&title=undefined' -HEADERS = { - 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:44.0) Gecko/20100101 Firefox/44.0', -} - -# NOTE: Get : openssl pkcs12 -nodes -out ipetrash.pem -in ipetrash.p12 -PEM_FILE_NAME = str(DIR / 'ipetrash.pem') - - -# SOURCE: https://stackoverflow.com/a/13287083/5909792 -def utc_to_local(utc_dt: datetime) -> datetime: - return utc_dt.replace(tzinfo=timezone.utc).astimezone(tz=None) - - -def get_rss_jira_log() -> bytes: - rs = requests.get(URL, headers=HEADERS, cert=PEM_FILE_NAME) - rs.raise_for_status() - return rs.content - - -def get_logged_dict(root) -> Dict[str, List[Dict]]: - logged_dict = defaultdict(list) - - for entry in root.select('entry'): - # Ищем в строку с логированием - match = re.search("logged '(.+?)'", entry.text, flags=re.IGNORECASE) - if not match: - continue - - logged_human_time = match.group(1) - logged_seconds = logged_human_time_to_seconds(logged_human_time) - - jira_id = entry.object.title.text.strip() - jira_title = entry.object.summary.text.strip() - - entry_dt = datetime.strptime(entry.published.text, "%Y-%m-%dT%H:%M:%S.%fZ") - - # Переменная entry_dt имеет время в UTC, и желательно его привести в локальное время - entry_dt = utc_to_local(entry_dt) - - entry_date = entry_dt.date() - date_str = entry_date.strftime('%d/%m/%Y') - - logged_dict[date_str].append({ - 'date_time': entry_dt.strftime('%d/%m/%Y %H:%M:%S'), - 'date': entry_dt.strftime('%d/%m/%Y'), - 'time': entry_dt.strftime('%H:%M:%S'), - 'logged_human_time': logged_human_time, - 'logged_seconds': logged_seconds, - 'jira_id': jira_id, - 'jira_title': jira_title, - }) - - return logged_dict - - -def parse_logged_dict(xml_data: bytes) -> Dict[str, List[Dict]]: - # Структура документа -- xml - root = BeautifulSoup(xml_data, 'xml') - - return get_logged_dict(root) - - -def get_sorted_logged(date_str_by_logged_list: Dict, reverse=True) -> List[Tuple[str, List[Dict]]]: - sorted_items = date_str_by_logged_list.items() - sorted_items = sorted(sorted_items, key=lambda x: datetime.strptime(x[0], '%d/%m/%Y'), reverse=reverse) - - return list(sorted_items) - - -def get_logged_list_by_now_utc_date(date_str_by_entry_logged_list: Dict[str, List[Dict]]) -> List[Dict]: - current_utc_date_str = datetime.utcnow().strftime('%d/%m/%Y') - return date_str_by_entry_logged_list.get(current_utc_date_str, []) - - -def get_logged_total_seconds(entry_logged_list: List[Dict]) -> int: - return sum(entry['logged_seconds'] for entry in entry_logged_list) - - -if __name__ == '__main__': - xml_data = get_rss_jira_log() - print(len(xml_data), repr(xml_data[:50])) - - # open('rs.xml', 'wb').write(xml_data) - # xml_data = open('rs.xml', 'rb').read() - - # Структура документа -- xml - logged_dict = parse_logged_dict(xml_data) - print(logged_dict) - - import json - print(json.dumps(logged_dict, indent=4, ensure_ascii=False)) - print() - - logged_list = get_logged_list_by_now_utc_date(logged_dict) - logged_total_seconds = get_logged_total_seconds(logged_list) - print('entry_logged_list:', logged_list) - print('today seconds:', logged_total_seconds) - print('today time:', seconds_to_str(logged_total_seconds)) - print() - - # Для красоты выводим результат в табличном виде - lines = [] - for date_str, logged_list in get_sorted_logged(logged_dict): - total_seconds = get_logged_total_seconds(logged_list) - lines.append((date_str, total_seconds, seconds_to_str(total_seconds))) - - # Список строк станет списком столбцов, у каждого столбца подсчитается максимальная длина - max_len_columns = [max(map(len, map(str, col))) for col in zip(*lines)] - - # Создание строки форматирования: [30, 14, 5] -> "{:<30} | {:<14} | {:<5}" - my_table_format = ' | '.join('{:<%s}' % max_len for max_len in max_len_columns) - - for line in lines: - print(my_table_format.format(*line)) diff --git a/parse_magnet_links.py b/parse_magnet_links.py index 0a04b20ae..1e6a09090 100644 --- a/parse_magnet_links.py +++ b/parse_magnet_links.py @@ -1,29 +1,33 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # https://en.wikipedia.org/wiki/Magnet_URI_scheme # https://ru.wikipedia.org/wiki/Magnet-ссылка + +from urllib.parse import urlparse, parse_qs + + def parse(url: str) -> dict: - from urllib.parse import urlparse, parse_qs result = urlparse(url) return parse_qs(result.query) -if __name__ == '__main__': +if __name__ == "__main__": + import json + links = [ - 'magnet:?xt=urn:btih:19270c27523f428d6b9d23c264e4a0ff57869275&dn=rutor.info_%D0%94%D0%BE%D0%BA%D1%82%D0%BE%D1%80+%D0%A8%D0%B0%D0%BD%D1%81+%2F+Chance+%5B02x01-04+%D0%B8%D0%B7+10%5D+%282017%29+WEB-DLRip+1080p+%7C+Profix+Media&tr=udp://opentor.org:2710&tr=udp://opentor.org:2710&tr=http://retracker.local/announce', - 'magnet:?xt=urn:btih:1ff5a51d2f65e7c2fa200ada0896075543398a73&dn=rutor.info_Heroes+%26+Generals+%5B12.10.17%5D+%282016%29+PC+%7C+Online-only&tr=udp://opentor.org:2710&tr=udp://opentor.org:2710&tr=http://retracker.local/announce', - 'magnet:?xt=urn:btih:08c3a30e88b38ccf6926c073ecd41f983cbf2b20&dn=rutor.info_%D0%A4%D0%B8%D0%B7%D1%80%D1%83%D0%BA+%5B04%D1%8501-10+%D0%B8%D0%B7+21%5D+%282017%29+HDTVRip-AVC+%D0%BE%D1%82+GeneralFilm&tr=udp://opentor.org:2710&tr=udp://opentor.org:2710&tr=http://retracker.local/announce', - 'magnet:?xl=10826029&dn=mediawiki-1.15.1.tar.gz&xt=urn:sha1:XRX2PEFXOOEJFRVUCX6HMZMKS5TWG4K5&as=https%3A%2F%2Freleases.wikimedia.org%2Fmediawiki%2F1.15%2Fmediawiki-1.15.1.tar.gz', + "magnet:?xt=urn:btih:19270c27523f428d6b9d23c264e4a0ff57869275&dn=rutor.info_%D0%94%D0%BE%D0%BA%D1%82%D0%BE%D1%80+%D0%A8%D0%B0%D0%BD%D1%81+%2F+Chance+%5B02x01-04+%D0%B8%D0%B7+10%5D+%282017%29+WEB-DLRip+1080p+%7C+Profix+Media&tr=udp://opentor.org:2710&tr=udp://opentor.org:2710&tr=http://retracker.local/announce", + "magnet:?xt=urn:btih:1ff5a51d2f65e7c2fa200ada0896075543398a73&dn=rutor.info_Heroes+%26+Generals+%5B12.10.17%5D+%282016%29+PC+%7C+Online-only&tr=udp://opentor.org:2710&tr=udp://opentor.org:2710&tr=http://retracker.local/announce", + "magnet:?xt=urn:btih:08c3a30e88b38ccf6926c073ecd41f983cbf2b20&dn=rutor.info_%D0%A4%D0%B8%D0%B7%D1%80%D1%83%D0%BA+%5B04%D1%8501-10+%D0%B8%D0%B7+21%5D+%282017%29+HDTVRip-AVC+%D0%BE%D1%82+GeneralFilm&tr=udp://opentor.org:2710&tr=udp://opentor.org:2710&tr=http://retracker.local/announce", + "magnet:?xl=10826029&dn=mediawiki-1.15.1.tar.gz&xt=urn:sha1:XRX2PEFXOOEJFRVUCX6HMZMKS5TWG4K5&as=https%3A%2F%2Freleases.wikimedia.org%2Fmediawiki%2F1.15%2Fmediawiki-1.15.1.tar.gz", ] - import json pretty = lambda data: json.dumps(data, indent=4, ensure_ascii=False) for i, link in enumerate(links, 1): data = parse(link) - print('{}.\n{}\n'.format(i, pretty(data))) + print(f"{i}.\n{pretty(data)}\n") diff --git a/parse_test_progress.py b/parse_test_progress.py index af2e0d2a0..306f1b0bd 100644 --- a/parse_test_progress.py +++ b/parse_test_progress.py @@ -1,10 +1,13 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -def parse(text, append_test_case_list=True): +import re + + +def parse(text, append_test_case_list=True) -> None: total_value = 0 total_max_value = 0 @@ -14,18 +17,17 @@ def parse(text, append_test_case_list=True): print(line) continue - import re - match = re.search(r'{@} +/ +(\d+)', line) + match = re.search(r"{@} +/ +(\d+)", line) if not match: print(line) continue max_value = int(match.group(1)) - match = re.search('{#}:(.*)$', line) + match = re.search("{#}:(.*)$", line) if match: # Получение списка значений - values = match.group(1).split(',') + values = match.group(1).split(",") # Удаление пустых символов values = map(str.strip, values) @@ -41,21 +43,23 @@ def parse(text, append_test_case_list=True): total_value += value total_max_value += max_value - line = line.replace('{@}', str(value)) - line = line.replace('{#}', "({}%)".format(int(value / max_value * 100))) + line = line.replace("{@}", str(value)) + line = line.replace("{#}", f"({int(value / max_value * 100)}%)") if not append_test_case_list: - index = line.rfind(':') + index = line.rfind(":") if index != -1: line = line[:index] print(line) print() - print("Итого: {} / {} ({}%)".format(total_value, total_max_value, int(total_value / total_max_value * 100))) + print( + f"Итого: {total_value} / {total_max_value} ({int(total_value / total_max_value * 100)}%)" + ) -if __name__ == '__main__': +if __name__ == "__main__": text = """\ Набивка тестов: ATM - VSDC Issuing: {@} / 48 {#}: diff --git a/parse_text_http_headers.py b/parse_text_http_headers.py index bafe092e8..7132c30ee 100644 --- a/parse_text_http_headers.py +++ b/parse_text_http_headers.py @@ -1,21 +1,20 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import re -from typing import Dict -HTTP_HEADER_PATTERN = re.compile(r'([\w-]+): (.*)', flags=re.IGNORECASE) +HTTP_HEADER_PATTERN = re.compile(r"([\w-]+): (.*)", flags=re.IGNORECASE) -def parse(text: str) -> Dict[str, str]: +def parse(text: str) -> dict[str, str]: return dict(HTTP_HEADER_PATTERN.findall(text)) -if __name__ == '__main__': +if __name__ == "__main__": text_http_headers = """ POST /index.php?do=search HTTP/1.1 @@ -39,18 +38,18 @@ def parse(text: str) -> Dict[str, str]: headers = parse(text_http_headers) print(headers) assert headers == { - 'Host': 'online.anidub.com', - 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:77.0) Gecko/20100101 Firefox/77.0', - 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', - 'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3', - 'Accept-Encoding': 'gzip, deflate, br', - 'Content-Type': 'application/x-www-form-urlencoded', - 'Content-Length': '112', - 'Origin': 'https://online.anidub.com', - 'Connection': 'keep-alive', - 'Referer': 'https://online.anidub.com/index.php?do=search', - 'Upgrade-Insecure-Requests': '1', - 'TE': 'Trailers', - 'Pragma': 'no-cache', - 'Cache-Control': 'no-cache' + "Host": "online.anidub.com", + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:77.0) Gecko/20100101 Firefox/77.0", + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", + "Accept-Language": "ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3", + "Accept-Encoding": "gzip, deflate, br", + "Content-Type": "application/x-www-form-urlencoded", + "Content-Length": "112", + "Origin": "https://online.anidub.com", + "Connection": "keep-alive", + "Referer": "https://online.anidub.com/index.php?do=search", + "Upgrade-Insecure-Requests": "1", + "TE": "Trailers", + "Pragma": "no-cache", + "Cache-Control": "no-cache", } diff --git a/parsing_profile_twitter.py b/parsing_profile_twitter.py deleted file mode 100644 index 9a93b2a87..000000000 --- a/parsing_profile_twitter.py +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -__author__ = 'ipetrash' - - -# TODO: from dev_window - -# from PySide.QtWebKit import * -# -# QWebSettings.globalSettings().setAttribute(QWebSettings.DeveloperExtrasEnabled, True) -# -# self.view = QWebView() -# self.setCentralWidget(self.view) -# -# self.view.load('https://twitter.com/Misty_Shadow') -# self.doc = self.view.page().mainFrame().documentElement() -# -# def el_text(el, css_selector): -# return el.findFirst(css_selector).toPlainText() -# -# profile = self.doc.findFirst('.ProfileHeaderCard') -# -# profile_info = { -# 'name': el_text(profile, '.ProfileHeaderCard-name'), -# 'screenname': el_text(profile, '.ProfileHeaderCard-screenname'), -# 'location': el_text(profile, '.ProfileHeaderCard-location'), -# 'url': el_text(profile, '.ProfileHeaderCard-url'), -# 'joinDate': el_text(profile, '.ProfileHeaderCard-joinDate'), -# 'birthdate': el_text(profile, '.ProfileHeaderCard-birthdate'), -# } -# -# text_mess = '' -# -# for k in sorted(profile_info.keys()): -# text_mess += '{}: {}'.format(k, profile_info[k].strip()) + '\n' -# -# print(text_mess) -# -# QMessageBox.information(self, None, text_mess) \ No newline at end of file diff --git a/password_hash.py b/password_hash.py index 2a85fc1b8..f54011003 100644 --- a/password_hash.py +++ b/password_hash.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import hashlib @@ -14,13 +14,19 @@ def get_password_hash(name: str, password: str) -> str: if not password: raise ValueError('"password" is empty!') - data = (name.upper() + "-" + password).encode('UTF-8') + data = (name.upper() + "-" + password).encode("UTF-8") return hashlib.sha256(data).hexdigest().upper() -if __name__ == '__main__': - print(get_password_hash('abc', '123')) - assert get_password_hash('abc', '123') == '4D156D7BC9C38C000AA1E5B06E46E1F40CAFE651F3CABFAE9BAD48803BAF17BF' +if __name__ == "__main__": + print(get_password_hash("abc", "123")) + assert ( + get_password_hash("abc", "123") + == "4D156D7BC9C38C000AA1E5B06E46E1F40CAFE651F3CABFAE9BAD48803BAF17BF" + ) - print(get_password_hash('123', 'abc')) - assert get_password_hash('123', 'abc') == '84A990F85C3CDD4A5FE71B027996AF81E3F87062BE029FAC4C09DDC40B3C42F0' + print(get_password_hash("123", "abc")) + assert ( + get_password_hash("123", "abc") + == "84A990F85C3CDD4A5FE71B027996AF81E3F87062BE029FAC4C09DDC40B3C42F0" + ) diff --git a/pathlib__examples/create_file_in_user_directory.py b/pathlib__examples/create_file_in_user_directory.py index 32fabc505..53f16591d 100644 --- a/pathlib__examples/create_file_in_user_directory.py +++ b/pathlib__examples/create_file_in_user_directory.py @@ -1,20 +1,22 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from pathlib import Path -f = Path('~/test_file.txt').expanduser() + + +f = Path("~/test_file.txt").expanduser() print(f.exists()) # False print(f) # C:\Users\ipetrash\test_file.txt print(f.exists()) # False -print(f.write_text('Hello World', encoding='utf-8')) # 11 +print(f.write_text("Hello World", encoding="utf-8")) # 11 print(f.exists()) # True -print(f.read_text(encoding='utf-8')) # Hello World +print(f.read_text(encoding="utf-8")) # Hello World print(f.unlink()) # None print(f.exists()) # False diff --git a/pathlib__examples/get_filename__without_suffix.py b/pathlib__examples/get_filename__without_suffix.py index 69a42188d..e5a62f038 100644 --- a/pathlib__examples/get_filename__without_suffix.py +++ b/pathlib__examples/get_filename__without_suffix.py @@ -1,19 +1,20 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import pathlib -file_name = 'C:/Users/111/video.mp4' + +file_name = "C:/Users/111/video.mp4" path = pathlib.Path(file_name) print(path.name) # video.mp4 print(path.stem) # video print() -file_name = 'video.mp4' +file_name = "video.mp4" path = pathlib.Path(file_name) print(path.name) # video.mp4 print(path.stem) # video diff --git a/pathlib__examples/get_user_directory.py b/pathlib__examples/get_user_directory.py index 499e27370..25ee45bd3 100644 --- a/pathlib__examples/get_user_directory.py +++ b/pathlib__examples/get_user_directory.py @@ -1,9 +1,11 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from pathlib import Path -print(Path('~').expanduser()) -print(str(Path('~').expanduser())) + + +print(Path("~").expanduser()) +print(str(Path("~").expanduser())) diff --git a/pathlib__examples/print_current_files.py b/pathlib__examples/print_current_files.py index b2052c4a6..2baca32b5 100644 --- a/pathlib__examples/print_current_files.py +++ b/pathlib__examples/print_current_files.py @@ -1,12 +1,14 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import pathlib + + current_dir = pathlib.Path(__file__).parent print(current_dir) -for file_name in current_dir.glob('*.py'): - print(' {}:\n {}\n'.format(file_name, file_name.read_bytes())) +for file_name in current_dir.glob("*.py"): + print(f" {file_name}:\n {file_name.read_bytes()}\n") diff --git a/pathlib__examples/search_and_rename_directory_in_hierarchy.py b/pathlib__examples/search_and_rename_directory_in_hierarchy.py index d36883e8f..7d10927c3 100644 --- a/pathlib__examples/search_and_rename_directory_in_hierarchy.py +++ b/pathlib__examples/search_and_rename_directory_in_hierarchy.py @@ -1,19 +1,20 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import os.path -os.makedirs('./dir1/dir2/dir3/dir4', exist_ok=True) - from pathlib import Path -n = 'dir3' -m = 'new_dir3' + +os.makedirs("./dir1/dir2/dir3/dir4", exist_ok=True) + +n = "dir3" +m = "new_dir3" # Рекурсивный поиск -items = list(Path('.').rglob(n)) +items = list(Path(".").rglob(n)) # Если нашли папку if items: diff --git a/pathlib__examples/search_with_glob.py b/pathlib__examples/search_with_glob.py index 7470c27bc..d99333c86 100644 --- a/pathlib__examples/search_with_glob.py +++ b/pathlib__examples/search_with_glob.py @@ -1,21 +1,21 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import pathlib # Print current folders and files -for file_name in pathlib.Path().glob('*'): +for file_name in pathlib.Path().glob("*"): # print abs path print(file_name.resolve()) print() # Print current folders and files with filter -for file_name in pathlib.Path().glob('get_*'): +for file_name in pathlib.Path().glob("get_*"): # print abs path print(file_name.resolve()) @@ -23,6 +23,6 @@ # for file_name in pathlib.Path('../').rglob('*'): # OR: -for file_name in pathlib.Path('../').glob('**/*'): +for file_name in pathlib.Path("../").glob("**/*"): # print abs path print(file_name.resolve()) diff --git a/pathlib__examples/syntax_path.py b/pathlib__examples/syntax_path.py index 8d1167ca4..c1957a6e2 100644 --- a/pathlib__examples/syntax_path.py +++ b/pathlib__examples/syntax_path.py @@ -1,11 +1,12 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -# C:\Windows\System32\Boot import pathlib -path = pathlib.Path('C:/') / 'Windows' / 'System32' / 'Boot' + + +path = pathlib.Path("C:/") / "Windows" / "System32" / "Boot" print(path) # C:\Windows\System32\Boot diff --git a/pdf/extract_text__PyMuPDF.py b/pdf/extract_text__PyMuPDF.py new file mode 100644 index 000000000..9f655e124 --- /dev/null +++ b/pdf/extract_text__PyMuPDF.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +# pip install PyMuPDF +import fitz + + +with fitz.open("merger__PyPDF2/1.pdf") as doc: + items = [] + for page in doc: + items.append(page.get_text()) + text = "\n".join(items) + +print(text) diff --git a/pdf_merger__PyPDF2/1.pdf b/pdf/merger__PyPDF2/1.pdf similarity index 100% rename from pdf_merger__PyPDF2/1.pdf rename to pdf/merger__PyPDF2/1.pdf diff --git a/pdf_merger__PyPDF2/2.pdf b/pdf/merger__PyPDF2/2.pdf similarity index 100% rename from pdf_merger__PyPDF2/2.pdf rename to pdf/merger__PyPDF2/2.pdf diff --git a/pdf_merger__PyPDF2/3.pdf b/pdf/merger__PyPDF2/3.pdf similarity index 100% rename from pdf_merger__PyPDF2/3.pdf rename to pdf/merger__PyPDF2/3.pdf diff --git a/pdf_merger__PyPDF2/main.py b/pdf/merger__PyPDF2/main.py similarity index 78% rename from pdf_merger__PyPDF2/main.py rename to pdf/merger__PyPDF2/main.py index 0bad6fa63..41f4f5516 100644 --- a/pdf_merger__PyPDF2/main.py +++ b/pdf/merger__PyPDF2/main.py @@ -1,14 +1,14 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install pypdf2 from PyPDF2 import PdfFileMerger -pdfs = ['1.pdf', '2.pdf', '3.pdf'] +pdfs = ["1.pdf", "2.pdf", "3.pdf"] merger = PdfFileMerger() for pdf in pdfs: diff --git a/pdf_merger__PyPDF2/result.pdf b/pdf/merger__PyPDF2/result.pdf similarity index 100% rename from pdf_merger__PyPDF2/result.pdf rename to pdf/merger__PyPDF2/result.pdf diff --git a/peewee__examples/SqliteQueueDatabase/config.py b/peewee__examples/SqliteQueueDatabase/config.py new file mode 100644 index 000000000..0c98459a0 --- /dev/null +++ b/peewee__examples/SqliteQueueDatabase/config.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from pathlib import Path + +DIR: Path = Path(__file__).resolve().parent + +DB_DIR_NAME: Path = DIR / "database" +DB_DIR_NAME.mkdir(parents=True, exist_ok=True) + +DB_FILE_NAME: Path = DB_DIR_NAME / "db.sqlite" diff --git a/peewee__examples/SqliteQueueDatabase/db.py b/peewee__examples/SqliteQueueDatabase/db.py new file mode 100644 index 000000000..2ea9957cd --- /dev/null +++ b/peewee__examples/SqliteQueueDatabase/db.py @@ -0,0 +1,142 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import enum +import sys +import time +from typing import Type, Iterable, Self, Any + +# pip install peewee +from peewee import ( + Model, + TextField, + ForeignKeyField, + CharField, +) +from playhouse.shortcuts import model_to_dict +from playhouse.sqliteq import SqliteQueueDatabase + +from config import DB_FILE_NAME, DIR + +sys.path.append(str(DIR.parent)) +from shorten import shorten + + +# This working with multithreading +# SOURCE: http://docs.peewee-orm.com/en/latest/peewee/playhouse.html#sqliteq +db = SqliteQueueDatabase( + DB_FILE_NAME, + pragmas={ + "foreign_keys": 1, + "journal_mode": "wal", # WAL-mode + "cache_size": -1024 * 64, # 64MB page-cache + }, + use_gevent=False, # Use the standard library "threading" module. + autostart=True, + queue_max_size=64, # Max. # of pending writes that can accumulate. + results_timeout=5.0, # Max. time to wait for query to be executed. +) + + +class BaseModel(Model): + class Meta: + database = db + + def get_new(self) -> Self: + return type(self).get(self._pk_expr()) + + @classmethod + def get_first(cls) -> Self: + return cls.select().first() + + @classmethod + def get_last(cls) -> Self: + return cls.select().order_by(cls.id.desc()).first() + + @classmethod + def get_inherited_models(cls) -> list[Type[Self]]: + return sorted(cls.__subclasses__(), key=lambda x: x.__name__) + + @classmethod + def print_count_of_tables(cls) -> None: + items = [] + for sub_cls in cls.get_inherited_models(): + name = sub_cls.__name__ + count = sub_cls.select().count() + items.append(f"{name}: {count}") + + print(", ".join(items)) + + @classmethod + def count(cls, filters: Iterable = None) -> int: + query = cls.select() + if filters: + query = query.filter(*filters) + return query.count() + + def to_dict(self) -> dict[str, Any]: + return model_to_dict(self, recurse=False) + + def __str__(self) -> str: + fields = [] + for k, field in self._meta.fields.items(): + v = getattr(self, k) + + if isinstance(field, (TextField, CharField)): + if isinstance(v, enum.Enum): + v = v.value + + if v: + v = repr(shorten(v, length=30)) + + elif isinstance(field, ForeignKeyField): + k = f"{k}_id" + if v: + v = v.id + + fields.append(f"{k}={v}") + + return self.__class__.__name__ + "(" + ", ".join(fields) + ")" + + +class Parameter(BaseModel): + name = TextField(primary_key=True) + value = TextField() + description = TextField(null=True) + + @classmethod + def get_by_name(cls, name: str) -> Self | None: + return cls.get_or_none(cls.name == name) + + @classmethod + def add( + cls, + name: str, + value: str, + description: str = None, + ) -> Self: + obj = cls.get_by_name(name) + if obj: + return obj + + return cls.create( + name=name, + value=value, + description=description, + ) + + +db.connect() +db.create_tables(BaseModel.get_inherited_models()) + + +# Задержка в 50мс, чтобы дать время на запуск SqliteQueueDatabase и создание таблиц +# Т.к. в SqliteQueueDatabase запросы на чтение выполняются сразу, а на запись попадают в очередь +time.sleep(0.050) + + +if __name__ == "__main__": + BaseModel.print_count_of_tables() diff --git a/peewee__examples/SqliteQueueDatabase/main.py b/peewee__examples/SqliteQueueDatabase/main.py new file mode 100644 index 000000000..389edd94b --- /dev/null +++ b/peewee__examples/SqliteQueueDatabase/main.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import concurrent.futures +import db + + +def create_parameter(name: str, value: str) -> db.Parameter: + return db.Parameter.add(name, value) + + +with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor: + N = 10000 + + futures = [ + executor.submit(create_parameter, f"name_{i}", f"value_{i}") + for i in range(N) + ] + + items = [] + for future in concurrent.futures.as_completed(futures): + data: db.Parameter = future.result() + items.append(data) + + print("items:", len(items)) + print("count:", db.Parameter.count()) + assert len(items) == N diff --git a/peewee__examples/backup__examples/backup_via_api.py b/peewee__examples/backup__examples/backup_via_api.py new file mode 100644 index 000000000..bde8de1dd --- /dev/null +++ b/peewee__examples/backup__examples/backup_via_api.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import sqlite3 +from pathlib import Path + +from peewee import SqliteDatabase + + +def backup(db: SqliteDatabase, file_name: Path | str) -> None: + dst = sqlite3.connect(file_name) + db.connection().backup(dst) + dst.close() + + +if __name__ == "__main__": + from common import run_test + + run_test( + backup=backup, + file_name_backup=Path(__file__).resolve().name + ".db", + ) diff --git a/peewee__examples/backup__examples/backup_via_vacuum_into.py b/peewee__examples/backup__examples/backup_via_vacuum_into.py new file mode 100644 index 000000000..522700bce --- /dev/null +++ b/peewee__examples/backup__examples/backup_via_vacuum_into.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from pathlib import Path +from peewee import SqliteDatabase + + +def backup(db: SqliteDatabase, file_name: Path | str) -> None: + Path(file_name).unlink(missing_ok=True) + + db.connection().execute("VACUUM INTO ?", (str(file_name),)) + + +if __name__ == "__main__": + from common import run_test + + run_test( + backup=backup, + file_name_backup=Path(__file__).resolve().name + ".db", + ) diff --git a/peewee__examples/backup__examples/common.py b/peewee__examples/backup__examples/common.py new file mode 100644 index 000000000..c1178a22a --- /dev/null +++ b/peewee__examples/backup__examples/common.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import sqlite3 +from pathlib import Path +from typing import Callable + +# pip install peewee +from peewee import Model, SqliteDatabase, TextField + + +db = SqliteDatabase("db.sqlite") + + +class Info(Model): + first_name = TextField() + second_name = TextField() + + class Meta: + database = db + + @classmethod + def fill(cls) -> None: + count = cls.select().count() + for i in range(5): + cls.create( + first_name=f"first_name_{i + count}", + second_name=f"second_name_{i + count}", + ) + + @classmethod + def print(cls) -> None: + for info in cls.select(): + print(info) + + @classmethod + def print_from(cls, file_name: str | Path) -> None: + connection = sqlite3.connect(file_name) + try: + for info in connection.execute(f"SELECT * FROM {cls._meta.table_name}"): + print(info) + except Exception as e: + print(f"[#] {e}") + + def __str__(self) -> str: + return f"Info<#{self.id} first_name={self.first_name!r} second_name={self.second_name!r}>" + + +def run_test( + backup: Callable, + file_name_backup: Path | str, +) -> None: + print("[db backup] Print:") + Info.print_from(file_name_backup) + print() + + Info.fill() + + print("[db] After fill:") + Info.print() + + backup(db, file_name_backup) + + +db.connect() +db.create_tables([Info]) + + +if __name__ == "__main__": + Info.fill() + + Info.print() + """ + Info<#1 first_name='first_name_0' second_name='second_name_0'> + Info<#2 first_name='first_name_1' second_name='second_name_1'> + Info<#3 first_name='first_name_2' second_name='second_name_2'> + Info<#4 first_name='first_name_3' second_name='second_name_3'> + Info<#5 first_name='first_name_4' second_name='second_name_4'> + """ + + Info.print_from("db.sqlite") + """ + (1, 'first_name_0', 'second_name_0') + (2, 'first_name_1', 'second_name_1') + (3, 'first_name_2', 'second_name_2') + (4, 'first_name_3', 'second_name_3') + (5, 'first_name_4', 'second_name_4') + """ diff --git a/peewee__examples/custom_field__EnumField/db_enum_field.py b/peewee__examples/custom_field__EnumField/db_enum_field.py new file mode 100644 index 000000000..ca8c83c1e --- /dev/null +++ b/peewee__examples/custom_field__EnumField/db_enum_field.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import enum +from typing import Type, Any + +from peewee import CharField + + +class EnumField(CharField): + """ + This class enable an Enum like field for Peewee + """ + + def __init__(self, choices: Type[enum.Enum], *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + + self.choices: Type[enum.Enum] = choices + self.max_length: int = 255 + + def db_value(self, value: Any) -> Any: + if value is None: + return + + if isinstance(value, enum.Enum): + return value.value + + return value + + def python_value(self, value: Any) -> Any: + if value is None: + return + + type_value_enum = type(list(self.choices)[0].value) + value_enum = type_value_enum(value) + return self.choices(value_enum) diff --git a/peewee__examples/custom_field__EnumField/main.py b/peewee__examples/custom_field__EnumField/main.py new file mode 100644 index 000000000..06fb225f5 --- /dev/null +++ b/peewee__examples/custom_field__EnumField/main.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import enum + +# pip install peewee +from peewee import SqliteDatabase, Model + +from db_enum_field import EnumField + + +@enum.unique +class TaskRunStatusEnum(enum.StrEnum): + PENDING = enum.auto() + RUNNING = enum.auto() + FINISHED = enum.auto() + STOPPED = enum.auto() + UNKNOWN = enum.auto() + ERROR = enum.auto() + + +db = SqliteDatabase(":memory:", pragmas={"foreign_keys": 1}) + + +class BaseModel(Model): + class Meta: + database = db + + +class TaskRun(BaseModel): + status = EnumField(choices=TaskRunStatusEnum, default=TaskRunStatusEnum.PENDING) + + +db.connect() +db.create_tables([TaskRun]) + + +if __name__ == "__main__": + run1 = TaskRun.create() + run2 = TaskRun.create(status=TaskRunStatusEnum.FINISHED) + + query = TaskRun.select().where(TaskRun.status == TaskRunStatusEnum.FINISHED) + print(query) + print(list(query)) + # SELECT "t1"."id", "t1"."status" FROM "taskrun" AS "t1" WHERE ("t1"."status" = 'finished') + # [] + + print() + + query = TaskRun.select().where(TaskRun.status.contains("ING")) + print(query) + print(list(query)) + # SELECT "t1"."id", "t1"."status" FROM "taskrun" AS "t1" WHERE ("t1"."status" LIKE '%ING%') + # [] diff --git a/peewee__examples/custom_field__ListField/main.py b/peewee__examples/custom_field__ListField/main.py index f9d332e3e..4f430cf86 100644 --- a/peewee__examples/custom_field__ListField/main.py +++ b/peewee__examples/custom_field__ListField/main.py @@ -1,29 +1,29 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import json -from typing import List, Iterable, Optional +from typing import Iterable # pip install peewee -from peewee import * +from peewee import SqliteDatabase, Model, Field, CharField class ListField(Field): - def python_value(self, value: str) -> List: + def python_value(self, value: str) -> list: return json.loads(value) - def db_value(self, value: Optional[Iterable]) -> str: + def db_value(self, value: Iterable | None) -> str: if value is not None: if not isinstance(value, list): - raise Exception('Type must be a list') + raise Exception("Type must be a list") return json.dumps(value, ensure_ascii=False) -db = SqliteDatabase('db.sqlite', pragmas={'foreign_keys': 1}) +db = SqliteDatabase("db.sqlite", pragmas={"foreign_keys": 1}) class BaseModel(Model): @@ -40,7 +40,7 @@ class KeyByList(BaseModel): db.create_tables([KeyByList]) -if __name__ == '__main__': +if __name__ == "__main__": if not KeyByList.get_or_none(key="a"): KeyByList.create(key="a", values=[1, 2, 3]) @@ -67,6 +67,6 @@ class KeyByList(BaseModel): # [1, 2, 3] if not KeyByList.get_or_none(key="d"): - KeyByList.create(key="d", values=list(str(i ** 2) for i in range(1, 10))) + KeyByList.create(key="d", values=list(str(i**2) for i in range(1, 10))) print(KeyByList.get(key="d").values) # ['1', '4', '9', '16', '25', '36', '49', '64', '81'] diff --git a/peewee__examples/custom_func__get_http_status.py b/peewee__examples/custom_func__get_http_status.py new file mode 100644 index 000000000..055b09db0 --- /dev/null +++ b/peewee__examples/custom_func__get_http_status.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import requests +from peewee import SqliteDatabase + + +db = SqliteDatabase(":memory:") + + +@db.func("get_http_status") +def get_http_status(url: str) -> int: + return requests.get(url).status_code + + +db.connect() + +with db.connection() as connect: + print(connect.execute("""SELECT get_http_status("https://ya.ru") """).fetchone()[0]) + # 200 + + print(connect.execute("""SELECT get_http_status("https://ya.ru/404") """).fetchone()[0]) + # 404 diff --git a/peewee__examples/custom_func__redefine_upper.py b/peewee__examples/custom_func__redefine_upper.py new file mode 100644 index 000000000..5fe5d73fd --- /dev/null +++ b/peewee__examples/custom_func__redefine_upper.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from peewee import SqliteDatabase + + +def run_sql(connect) -> None: + print(connect.execute("""SELECT "ы", UPPER("ы") """).fetchone()) + print(connect.execute("""SELECT "s", UPPER("s") """).fetchone()) + print(connect.execute("""SELECT 1 WHERE UPPER("ы") LIKE UPPER("Ы") """).fetchone()) + print(connect.execute("""SELECT 1 WHERE "ы" LIKE "Ы" """).fetchone()) + print(connect.execute("""SELECT 1 WHERE "s" LIKE "S" """).fetchone()) + + +def db_func() -> None: + print("[db_func]") + + db = SqliteDatabase(":memory:") + db.connect() + + with db.connection() as connect: + run_sql(connect) + """ + ('ы', 'ы') + ('s', 'S') + None + None + (1,) + """ + + print() + + @db.func("upper") + def upper(value: str) -> str | None: + if value is None: + return + return value.upper() + + with db.connection() as connect: + run_sql(connect) + """ + ('ы', 'Ы') + ('s', 'S') + (1,) + None + (1,) + """ + + +def db_create_function() -> None: + print("[db_create_function]") + + db = SqliteDatabase(":memory:") + db.connect() + + with db.connection() as connect: + run_sql(connect) + """ + ('ы', 'ы') + ('s', 'S') + None + None + (1,) + """ + + print() + connect.create_function("upper", narg=1, func=str.upper) + + run_sql(connect) + """ + ('ы', 'Ы') + ('s', 'S') + (1,) + None + (1,) + """ + + +db_create_function() + +print() + +db_func() diff --git a/peewee__examples/custom_func__redefine_upper__check_speed.py b/peewee__examples/custom_func__redefine_upper__check_speed.py new file mode 100644 index 000000000..cee35cd06 --- /dev/null +++ b/peewee__examples/custom_func__redefine_upper__check_speed.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +# NOTE: Analog https://github.com/gil9red/SimplePyScripts/blob/7b0238202c6e6b5750ff6909d0a78b7892ee1767/sqlite3__examples/custom_func__redefine_upper__check_speed.py + + +import random + +from timeit import default_timer, timeit +from typing import Type, Iterable, Self, Any + +# pip install peewee +from peewee import ( + SqliteDatabase, + Model, + TextField, + DecimalField, + fn +) +from playhouse.shortcuts import model_to_dict + + +db = SqliteDatabase(":memory:") + + +class BaseModel(Model): + class Meta: + database = db + + @classmethod + def get_inherited_models(cls) -> list[Type[Self]]: + return sorted(cls.__subclasses__(), key=lambda x: x.__name__) + + @classmethod + def count(cls, filters: Iterable = None) -> int: + query = cls.select() + if filters: + query = query.filter(*filters) + return query.count() + + def to_dict(self) -> dict[str, Any]: + return model_to_dict(self, recurse=False) + + +class Stocks(BaseModel): + date = TextField() + trans = TextField() + symbol = TextField() + qty = DecimalField() + price = DecimalField() + + +db.connect() +db.create_tables(BaseModel.get_inherited_models()) + +print("Generate items...") +t = default_timer() +purchases = [ + Stocks( + date=random.choice(["2006-03-28", "2006-04-05", "2006-04-06"]), + trans=random.choice(["SELL", "BUY"]), + symbol=random.choice(["IBM", "MSFT"]), + qty=random.choice([500, 1000, 600]), + price=random.choice([53.00, 45.00, 72.00]), + ) + for _ in range(500_000) +] +print(f"Elapsed {default_timer() - t:.3f} secs") +# Elapsed 2.097 secs + +print() + +print("INSERT INTO...") +t = default_timer() + +with db.atomic(): + Stocks.bulk_create(purchases, batch_size=5_000) + +print(f"Elapsed {default_timer() - t:.3f} secs") +# Elapsed 6.842 secs + +print() + + +def run_test() -> None: + elapsed = timeit( + stmt="Stocks.select().where(fn.UPPER(Stocks.trans).ilike(fn.UPPER('%SELL%'))).count()", + globals=dict(Stocks=Stocks, fn=fn), + number=100, + ) + print(f"Elapsed {elapsed:.3f} secs") + + +print("SELECT COUNT DEFAULT UPPER...") +run_test() +# Elapsed 11.765 secs + +print() + + +@db.func("upper") +def upper(value: str) -> str | None: + if value is None: + return + return value.upper() + + +print("SELECT COUNT PYTHON UPPER...") +run_test() +# Elapsed 47.276 secs diff --git a/peewee__examples/db_peewee_meta_model.py b/peewee__examples/db_peewee_meta_model.py new file mode 100644 index 000000000..6564853ef --- /dev/null +++ b/peewee__examples/db_peewee_meta_model.py @@ -0,0 +1,152 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from enum import Enum +from typing import Type, Any, Iterable + +from peewee import CharField, TextField, ForeignKeyField, Model, Field +from playhouse.shortcuts import model_to_dict + + +def shorten(text: str, length: int = 30, placeholder: str = "...") -> str: + if not text: + return text + + if len(text) > length: + text = text[: length - len(placeholder)] + placeholder + return text + + +class MetaModel(Model): + def get_new(self) -> "MetaModel": + return type(self).get(self._pk_expr()) + + @classmethod + def get_first(cls) -> "MetaModel": + return cls.select().first() + + @classmethod + def get_last(cls) -> "MetaModel": + return cls.select().order_by(cls.id.desc()).first() + + @classmethod + def paginating( + cls, + page: int = 1, + items_per_page: int = 1, + filters: Iterable | None = None, + order_by: Field | None = None, + ) -> list["MetaModel"]: + query = cls.select() + + if filters: + query = query.filter(*filters) + + if order_by: + query = query.order_by(order_by) + + query = query.paginate(page, items_per_page) + return list(query) + + @classmethod + def get_inherited_models(cls) -> list[Type["MetaModel"]]: + return sorted(cls.__subclasses__(), key=lambda x: x.__name__) + + @classmethod + def print_count_of_tables(cls) -> None: + items = [] + for sub_cls in cls.get_inherited_models(): + name = sub_cls.__name__ + count = sub_cls.select().count() + items.append(f"{name}: {count}") + + print(", ".join(items)) + + @classmethod + def count(cls, filters: Iterable = None) -> int: + query = cls.select() + if filters: + query = query.filter(*filters) + return query.count() + + def to_dict(self) -> dict[str, Any]: + return model_to_dict(self, recurse=False) + + def __str__(self) -> str: + fields = [] + for k, field in self._meta.fields.items(): + v = getattr(self, k) + + if isinstance(field, (TextField, CharField)): + if isinstance(v, Enum): + v = v.value + + if v: + v = repr(shorten(v, length=30)) + + elif isinstance(field, ForeignKeyField): + k = f"{k}_id" + if v: + v = v.id + + fields.append(f"{k}={v}") + + return self.__class__.__name__ + "(" + ", ".join(fields) + ")" + + +if __name__ == "__main__": + from peewee import SqliteDatabase + + db = SqliteDatabase(":memory:") + + class BaseModel(MetaModel): + class Meta: + database = db + + class Data(BaseModel): + name = CharField() + value = TextField() + + db.connect() + db.create_tables(BaseModel.get_inherited_models()) + + BaseModel.print_count_of_tables() + # Data: 0 + + data = dict( + a="aAA", + b="Foo", + bar="!!!", + ) + for k, v in data.items(): + Data.create(name=k, value=v) + + BaseModel.print_count_of_tables() + # Data: 3 + + print() + + print(Data.get_first()) + print(Data.get_last()) + # Data(id=1, name='a', value='aAA') + # Data(id=3, name='bar', value='!!!') + + print() + + print(Data.get_last().to_dict()) + # {'id': 3, 'name': 'bar', 'value': '!!!'} + + print() + filters = [ + Data.name.in_(["a", "b"]), + ] + order_by = Data.name.desc() + print(Data.paginating(page=1, filters=filters, order_by=order_by)) + print(Data.paginating(page=2, filters=filters, order_by=order_by)) + print(Data.paginating(page=3, filters=filters, order_by=order_by)) + # [] + # [] + # [] diff --git a/peewee__examples/games_and_genres/main.py b/peewee__examples/games_and_genres/main.py index 85e834429..08b171d27 100644 --- a/peewee__examples/games_and_genres/main.py +++ b/peewee__examples/games_and_genres/main.py @@ -1,16 +1,14 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -from typing import List - # pip install peewee -from peewee import * +from peewee import SqliteDatabase, Model, CharField, TextField, ForeignKeyField -db = SqliteDatabase('games.sqlite', pragmas={'foreign_keys': 1}) +db = SqliteDatabase("games.sqlite", pragmas={"foreign_keys": 1}) class BaseModel(Model): @@ -21,10 +19,10 @@ class Meta: class Game(BaseModel): name = CharField(unique=True) - def get_genres(self) -> List['Genre']: + def get_genres(self) -> list["Genre"]: return [link.genre for link in self.links_to_genres] - def append_genres(self, *genres: List['Genre']): + def append_genres(self, *genres: list["Genre"]) -> None: for genre in genres: GameToGenre.get_or_create(game=self, genre=genre) @@ -33,17 +31,17 @@ class Genre(BaseModel): name = CharField(unique=True) description = TextField(null=True) - def get_games(self) -> List[Game]: + def get_games(self) -> list[Game]: return [link.game for link in self.links_to_games] class GameToGenre(BaseModel): - game = ForeignKeyField(Game, backref='links_to_genres') - genre = ForeignKeyField(Genre, backref='links_to_games') + game = ForeignKeyField(Game, backref="links_to_genres") + genre = ForeignKeyField(Genre, backref="links_to_games") class Meta: indexes = ( - (('game', 'genre'), True), + (("game", "genre"), True), ) @@ -75,30 +73,32 @@ class Meta: game.append_genres(GENRE__ACTION, GENRE__RPG, GENRE__ACTION_ADVENTURE) -if __name__ == '__main__': - print(f'Genre {GENRE__SURVIVAL_HORROR.name!r}:') +if __name__ == "__main__": + print(f"Genre {GENRE__SURVIVAL_HORROR.name!r}:") for game in GENRE__SURVIVAL_HORROR.get_games(): - print(f' {game.name}') + print(f" {game.name}") # Genre 'Survival horror': # Dead Space # Dead Island # Dying Light - print('\n' + '-' * 10 + '\n') + print("\n" + "-" * 10 + "\n") game, _ = Game.get_or_create(name="Dying Light") - print(f'Game {game.name!r}:') + print(f"Game {game.name!r}:") for genre in game.get_genres(): - print(f' {genre.name}: {genre.description!r}') + print(f" {genre.name}: {genre.description!r}") # Game 'Dying Light': # Survival horror: None # RPG: 'Role playing game' # Action: None - print('\n' + '-' * 10 + '\n') + print("\n" + "-" * 10 + "\n") for game in Game.select(): - print(f"{game.name}\n Genres: {', '.join(genre.name for genre in game.get_genres())}") + print( + f"{game.name}\n Genres: {', '.join(genre.name for genre in game.get_genres())}" + ) print() # Dead Space diff --git a/peewee__examples/hello_world/main.py b/peewee__examples/hello_world/main.py index 4636e72ba..c84820266 100644 --- a/peewee__examples/hello_world/main.py +++ b/peewee__examples/hello_world/main.py @@ -1,14 +1,14 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install peewee -from peewee import * +from peewee import Model, SqliteDatabase, TextField, IntegerField -db = SqliteDatabase('my_database.sqlite') +db = SqliteDatabase("my_database.sqlite") class Info(Model): @@ -19,28 +19,33 @@ class Info(Model): class Meta: database = db - def __str__(self): - return f'Info<#{self.id} first_name={self.first_name} second_name={self.second_name} state={self.state}>' + def __str__(self) -> str: + return f"Info<#{self.id} first_name={self.first_name!r} second_name={self.second_name!r} state={self.state}>" db.connect() db.create_tables([Info]) -if __name__ == '__main__': +if __name__ == "__main__": # Вызываем в первый раз, чтобы заполнить таблицу if not Info.select().count(): for i in range(5): - Info.create(first_name="first_name_" + str(i), second_name="second_name_" + str(i), state=i) + Info.create( + first_name="first_name_" + str(i), + second_name="second_name_" + str(i), + state=i, + ) for info in Info.select(): print(info) - # - # Info<#1 first_name=first_name_0 second_name=second_name_0 state=0> - # Info<#2 first_name=first_name_1 second_name=second_name_1 state=1> - # Info<#3 first_name=first_name_2 second_name=second_name_2 state=2> - # Info<#4 first_name=first_name_3 second_name=second_name_3 state=3> - # Info<#5 first_name=first_name_4 second_name=second_name_4 state=4> + """ + Info<#1 first_name='Четный state!' second_name='second_name_0' state=0> + Info<#2 first_name='first_name_1' second_name='second_name_1' state=1> + Info<#3 first_name='Четный state!' second_name='second_name_2' state=2> + Info<#4 first_name='first_name_3' second_name='second_name_3' state=3> + Info<#5 first_name='Четный state!' second_name='second_name_4' state=4> + """ for info in Info.select(): if info.state % 2 == 0: @@ -51,9 +56,10 @@ def __str__(self): for info in Info.select(): print(info) - # - # Info<#1 first_name=Четный state! second_name=second_name_0 state=0> - # Info<#2 first_name=first_name_1 second_name=second_name_1 state=1> - # Info<#3 first_name=Четный state! second_name=second_name_2 state=2> - # Info<#4 first_name=first_name_3 second_name=second_name_3 state=3> - # Info<#5 first_name=Четный state! second_name=second_name_4 state=4> + """ + Info<#1 first_name='Четный state!' second_name='second_name_0' state=0> + Info<#2 first_name='first_name_1' second_name='second_name_1' state=1> + Info<#3 first_name='Четный state!' second_name='second_name_2' state=2> + Info<#4 first_name='first_name_3' second_name='second_name_3' state=3> + Info<#5 first_name='Четный state!' second_name='second_name_4' state=4> + """ diff --git a/peewee__examples/hello_world__diary/main.py b/peewee__examples/hello_world__diary/main.py index e45f93d04..22baaa69d 100644 --- a/peewee__examples/hello_world__diary/main.py +++ b/peewee__examples/hello_world__diary/main.py @@ -1,21 +1,21 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/coleifer/peewee/blob/afdf7b752dcadbf440faaa91a7fb0f403eac9a69/examples/diary.py -from collections import OrderedDict -import datetime as DT +import datetime as dt + from textwrap import shorten # pip install peewee -from peewee import * +from peewee import Model, SqliteDatabase, TextField, DateTimeField -db = SqliteDatabase('my_database.sqlite') +db = SqliteDatabase("my_database.sqlite") class BaseModel(Model): @@ -25,14 +25,14 @@ class Meta: class Diary(BaseModel): content = TextField() - created_date = DateTimeField(default=DT.datetime.now) + created_date = DateTimeField(default=dt.datetime.now) @staticmethod - def print_table(search_text: str = None): + def print_table(search_text: str = None) -> None: """Print all diaries""" - header_fmt = '{:<3} | {:<50} | {:<19}' - row_fmt = '#{id:<3} | {content:<50} | {created_date:%d/%m/%Y %H:%M:%S}' + header_fmt = "{:<3} | {:<50} | {:<19}" + row_fmt = "#{id:<3} | {content:<50} | {created_date:%d/%m/%Y %H:%M:%S}" print(header_fmt.format(*map(str.upper, Diary._meta.fields))) @@ -41,20 +41,24 @@ def print_table(search_text: str = None): query = query.where(Diary.content.contains(search_text)) for diary in query: - print(row_fmt.format( - id=diary.id, - content=shorten(diary.content, width=50, placeholder='...'), - created_date=diary.created_date, - )) + print( + row_fmt.format( + id=diary.id, + content=shorten(diary.content, width=50, placeholder="..."), + created_date=diary.created_date, + ) + ) print() - def __str__(self): - return f'Diary<' \ - f'#{self.id} ' \ - f'content={repr(shorten(self.content, width=50, placeholder="..."))} ' \ - f"created_date='{self.created_date:%d/%m/%Y %H:%M:%S}'" \ - f">" + def __str__(self) -> str: + return ( + f"Diary<" + f"#{self.id} " + f'content={repr(shorten(self.content, width=50, placeholder="..."))} ' + f"created_date='{self.created_date:%d/%m/%Y %H:%M:%S}'" + f">" + ) db.connect() @@ -67,16 +71,16 @@ def __str__(self): Diary.create(content="The quick brown fox jumps over the lazy dog.") -def add_diary(): +def add_diary() -> None: """Add diary""" - data = input('Enter your diary: ').strip() - if data and input('Save diary? [Y/n] ') != 'n': + data = input("Enter your diary: ").strip() + if data and input("Save diary? [Y/n] ") != "n": Diary.create(content=data) - print('Saved successfully.') + print("Saved successfully.") -def view_diaries(search_query=None): +def view_diaries(search_query=None) -> None: """View previous diaries""" query = Diary.select().order_by(Diary.created_date.desc()) @@ -85,42 +89,44 @@ def view_diaries(search_query=None): for diary in query: print() - timestamp = diary.created_date.strftime('%d/%m/%Y %H:%M:%S') + timestamp = diary.created_date.strftime("%d/%m/%Y %H:%M:%S") print(timestamp) - print('=' * len(timestamp)) + print("=" * len(timestamp)) print(diary.content) print() - print('n) next diary') - print('d) delete diary') - print('q) return to main menu') - action = input('Choice? (N/d/q) ').lower().strip() - if action == 'q': + print("n) next diary") + print("d) delete diary") + print("q) return to main menu") + action = input("Choice? (N/d/q) ").lower().strip() + if action == "q": break - elif action == 'd': + elif action == "d": diary.delete_instance() break -def search_diaries(): +def search_diaries() -> None: """Search diaries""" - view_diaries(input('Search query: ')) + view_diaries(input("Search query: ")) -MENU = OrderedDict([ - ('a', add_diary), - ('v', view_diaries), - ('s', search_diaries), - ('p', Diary.print_table), -]) +MENU = dict( + [ + ("a", add_diary), + ("v", view_diaries), + ("s", search_diaries), + ("p", Diary.print_table), + ] +) -def menu_loop(): +def menu_loop() -> None: choice = None - while choice != 'q': + while choice != "q": for key, value in MENU.items(): - print('%s) %s' % (key, value.__doc__)) - choice = input('Action: ').lower().strip() + print("%s) %s" % (key, value.__doc__)) + choice = input("Action: ").lower().strip() if choice in MENU: MENU[choice]() diff --git a/peewee__examples/hello_world__diary/my_database.sqlite b/peewee__examples/hello_world__diary/my_database.sqlite index 73b394087..d6aa5bfd4 100644 Binary files a/peewee__examples/hello_world__diary/my_database.sqlite and b/peewee__examples/hello_world__diary/my_database.sqlite differ diff --git a/peewee__examples/hello_world__diary__encryption_AES_key_for_evere_diary__with_master_key/main.py b/peewee__examples/hello_world__diary__encryption_AES_key_for_evere_diary__with_master_key/main.py index 1623f3e45..a3cb75b15 100644 --- a/peewee__examples/hello_world__diary__encryption_AES_key_for_evere_diary__with_master_key/main.py +++ b/peewee__examples/hello_world__diary__encryption_AES_key_for_evere_diary__with_master_key/main.py @@ -1,27 +1,27 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/coleifer/peewee/blob/afdf7b752dcadbf440faaa91a7fb0f403eac9a69/examples/diary.py -import datetime as DT +import datetime as dt import hashlib import sys from getpass import getpass # pip install peewee -from peewee import * +from peewee import Model, SqliteDatabase, TextField, DateTimeField -sys.path.append('../hello_world__diary__encryption_all_in_one_AES') +sys.path.append("../hello_world__diary__encryption_all_in_one_AES") from utils.security import CryptoAES, AuthenticationError from utils.utils import shorten -db = SqliteDatabase('my_database.sqlite') +db = SqliteDatabase("my_database.sqlite") class BaseModel(Model): @@ -30,7 +30,7 @@ class Meta: # Password: 123 -ENCRYPT_MASTER_KEY = 'A665A45920422F9D417E4867EFDC4FB8A04A1F3FFF1FA07E998E86F7F7A27AE3' +ENCRYPT_MASTER_KEY = "A665A45920422F9D417E4867EFDC4FB8A04A1F3FFF1FA07E998E86F7F7A27AE3" # OR: # ENCRYPT_MASTER_KEY = None @@ -41,25 +41,27 @@ class Meta: # Need password password = getpass() if not password: - print('Required password!') + print("Required password!") sys.exit() - ENCRYPT_MASTER_KEY = hashlib.sha256(bytes(password, 'utf-8')).hexdigest().upper() + ENCRYPT_MASTER_KEY = hashlib.sha256(bytes(password, "utf-8")).hexdigest().upper() class Diary(BaseModel): encrypted_content = TextField() # Содержимое будет шифровано ключом - encrypted_key = TextField() # Ключ будет шифрован мастер ключом - created_date = DateTimeField(default=DT.datetime.now) + encrypted_key = TextField() # Ключ будет шифрован мастер ключом + created_date = DateTimeField(default=dt.datetime.now) @staticmethod - def create_encrypted_content(content: str, master_key: str) -> 'Diary': + def create_encrypted_content(content: str, master_key: str) -> "Diary": key = CryptoAES.get_random_key_hex() encrypted_content = CryptoAES(key).encrypt(content) encrypted_key = CryptoAES(master_key).encrypt(key) - return Diary(encrypted_content=encrypted_content, encrypted_key=encrypted_key).save() + return Diary( + encrypted_content=encrypted_content, encrypted_key=encrypted_key + ).save() def get_content(self, master_key: str) -> str: try: @@ -70,7 +72,7 @@ def get_content(self, master_key: str) -> str: return CryptoAES(key).decrypt(self.encrypted_content) except AuthenticationError as e: - return f'ERROR: {e}' + return f"ERROR: {e}" def get_key(self, master_key: str) -> str: try: @@ -78,26 +80,37 @@ def get_key(self, master_key: str) -> str: return CryptoAES(master_key).decrypt(self.encrypted_key) except AuthenticationError as e: - return f'ERROR: {e}' + return f"ERROR: {e}" @staticmethod - def print_table(master_key: str = ENCRYPT_MASTER_KEY): + def print_table(master_key: str = ENCRYPT_MASTER_KEY) -> None: """Print all diaries""" - header_fmt = '{:<3} | {:<50} | {:<50} | {:<50} | {:<50} | {:<19}' - row_fmt = '#{id:<3} | {encrypted_content:<50} | {content:<50} | {encrypted_key:<50} | {key:<50} | {created_date:%d/%m/%Y %H:%M:%S}' + header_fmt = "{:<3} | {:<50} | {:<50} | {:<50} | {:<50} | {:<19}" + row_fmt = "#{id:<3} | {encrypted_content:<50} | {content:<50} | {encrypted_key:<50} | {key:<50} | {created_date:%d/%m/%Y %H:%M:%S}" - print(header_fmt.format('ID', 'ENCRYPTED_CONTENT', 'CONTENT', 'ENCRYPTED_KEY', 'KEY', 'CREATED_DATE')) + print( + header_fmt.format( + "ID", + "ENCRYPTED_CONTENT", + "CONTENT", + "ENCRYPTED_KEY", + "KEY", + "CREATED_DATE", + ) + ) for diary in Diary.select(): - print(row_fmt.format( - id=diary.id, - encrypted_content=shorten(diary.encrypted_content), - content=shorten(diary.get_content(master_key)), - encrypted_key=shorten(diary.encrypted_key), - key=shorten(diary.get_key(master_key)), - created_date=diary.created_date, - )) + print( + row_fmt.format( + id=diary.id, + encrypted_content=shorten(diary.encrypted_content), + content=shorten(diary.get_content(master_key)), + encrypted_key=shorten(diary.encrypted_key), + key=shorten(diary.get_key(master_key)), + created_date=diary.created_date, + ) + ) print() @@ -108,56 +121,61 @@ def print_table(master_key: str = ENCRYPT_MASTER_KEY): # Вызываем в первый раз, чтобы заполнить таблицу if not Diary.select().count(): - Diary.create_encrypted_content(content="Hello World!", master_key=ENCRYPT_MASTER_KEY) - Diary.create_encrypted_content(content="The quick brown fox jumps over the lazy dog.", master_key=ENCRYPT_MASTER_KEY) + Diary.create_encrypted_content( + content="Hello World!", master_key=ENCRYPT_MASTER_KEY + ) + Diary.create_encrypted_content( + content="The quick brown fox jumps over the lazy dog.", + master_key=ENCRYPT_MASTER_KEY, + ) -def add_diary(master_key: str = ENCRYPT_MASTER_KEY): +def add_diary(master_key: str = ENCRYPT_MASTER_KEY) -> None: """Add diary""" - data = input('Enter your diary: ').strip() - if data and input('Save diary? [Y/n] ') != 'n': + data = input("Enter your diary: ").strip() + if data and input("Save diary? [Y/n] ") != "n": Diary.create_encrypted_content(content=data, master_key=master_key) - print('Saved successfully.') + print("Saved successfully.") print() -def view_diaries(master_key: str = ENCRYPT_MASTER_KEY): +def view_diaries(master_key: str = ENCRYPT_MASTER_KEY) -> None: """View previous diaries""" query = Diary.select().order_by(Diary.created_date.desc()) for diary in query: print() - timestamp = diary.created_date.strftime('%d/%m/%Y %H:%M:%S') + timestamp = diary.created_date.strftime("%d/%m/%Y %H:%M:%S") print(timestamp) - print('=' * len(timestamp)) + print("=" * len(timestamp)) print(diary.encrypted_content) print(diary.get_content(master_key)) print() - print('n) next diary') - print('d) delete diary') - print('q) return to main menu') - action = input('Choice? (N/d/q) ').lower().strip() - if action == 'q': + print("n) next diary") + print("d) delete diary") + print("q) return to main menu") + action = input("Choice? (N/d/q) ").lower().strip() + if action == "q": break - elif action == 'd': + elif action == "d": diary.delete_instance() break MENU = { - 'a': add_diary, - 'v': view_diaries, - 'p': Diary.print_table, + "a": add_diary, + "v": view_diaries, + "p": Diary.print_table, } -def menu_loop(): +def menu_loop() -> None: choice = None - while choice != 'q': + while choice != "q": for key, value in MENU.items(): - print('%s) %s' % (key, value.__doc__)) - choice = input('Action: ').lower().strip() + print("%s) %s" % (key, value.__doc__)) + choice = input("Action: ").lower().strip() if choice in MENU: MENU[choice]() diff --git a/peewee__examples/hello_world__diary__encryption_all_in_one_AES/main.py b/peewee__examples/hello_world__diary__encryption_all_in_one_AES/main.py index b95998296..892ae1c94 100644 --- a/peewee__examples/hello_world__diary__encryption_all_in_one_AES/main.py +++ b/peewee__examples/hello_world__diary__encryption_all_in_one_AES/main.py @@ -1,26 +1,26 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/coleifer/peewee/blob/afdf7b752dcadbf440faaa91a7fb0f403eac9a69/examples/diary.py -import datetime as DT +import datetime as dt import hashlib import sys from getpass import getpass # pip install peewee -from peewee import * +from peewee import Model, SqliteDatabase, TextField, DateTimeField from utils.security import CryptoAES, AuthenticationError from utils.utils import shorten -db = SqliteDatabase('my_database.sqlite') +db = SqliteDatabase("my_database.sqlite") class BaseModel(Model): @@ -29,7 +29,7 @@ class Meta: # Password: 123 -ENCRYPT_KEY = 'A665A45920422F9D417E4867EFDC4FB8A04A1F3FFF1FA07E998E86F7F7A27AE3' +ENCRYPT_KEY = "A665A45920422F9D417E4867EFDC4FB8A04A1F3FFF1FA07E998E86F7F7A27AE3" # OR: # ENCRYPT_KEY = None @@ -40,18 +40,18 @@ class Meta: # Need password password = getpass() if not password: - print('Required password!') + print("Required password!") sys.exit() - ENCRYPT_KEY = hashlib.sha256(bytes(password, 'utf-8')).hexdigest().upper() + ENCRYPT_KEY = hashlib.sha256(bytes(password, "utf-8")).hexdigest().upper() class Diary(BaseModel): encrypted_content = TextField() - created_date = DateTimeField(default=DT.datetime.now) + created_date = DateTimeField(default=dt.datetime.now) @staticmethod - def create_encrypted_content(content: str, key: str) -> 'Diary': + def create_encrypted_content(content: str, key: str) -> "Diary": encrypted_content = CryptoAES(key).encrypt(content) return Diary(encrypted_content=encrypted_content).save() @@ -59,24 +59,26 @@ def get_content(self, key: str) -> str: try: return CryptoAES(key).decrypt(self.encrypted_content) except AuthenticationError as e: - return f'ERROR: {e}' + return f"ERROR: {e}" @staticmethod - def print_table(key: str = ENCRYPT_KEY): + def print_table(key: str = ENCRYPT_KEY) -> None: """Print all diaries""" - header_fmt = '{:<3} | {:<50} | {:<50} | {:<19}' - row_fmt = '#{id:<3} | {encrypted_content:<50} | {content:<50} | {created_date:%d/%m/%Y %H:%M:%S}' + header_fmt = "{:<3} | {:<50} | {:<50} | {:<19}" + row_fmt = "#{id:<3} | {encrypted_content:<50} | {content:<50} | {created_date:%d/%m/%Y %H:%M:%S}" - print(header_fmt.format('ID', 'ENCRYPTED_CONTENT', 'CONTENT', 'CREATED_DATE')) + print(header_fmt.format("ID", "ENCRYPTED_CONTENT", "CONTENT", "CREATED_DATE")) for diary in Diary.select(): - print(row_fmt.format( - id=diary.id, - encrypted_content=shorten(diary.encrypted_content), - content=shorten(diary.get_content(key)), - created_date=diary.created_date, - )) + print( + row_fmt.format( + id=diary.id, + encrypted_content=shorten(diary.encrypted_content), + content=shorten(diary.get_content(key)), + created_date=diary.created_date, + ) + ) print() @@ -88,55 +90,57 @@ def print_table(key: str = ENCRYPT_KEY): # Вызываем в первый раз, чтобы заполнить таблицу if not Diary.select().count(): Diary.create_encrypted_content(content="Hello World!", key=ENCRYPT_KEY) - Diary.create_encrypted_content(content="The quick brown fox jumps over the lazy dog.", key=ENCRYPT_KEY) + Diary.create_encrypted_content( + content="The quick brown fox jumps over the lazy dog.", key=ENCRYPT_KEY + ) -def add_diary(key: str = ENCRYPT_KEY): +def add_diary(key: str = ENCRYPT_KEY) -> None: """Add diary""" - data = input('Enter your diary: ').strip() - if data and input('Save diary? [Y/n] ') != 'n': + data = input("Enter your diary: ").strip() + if data and input("Save diary? [Y/n] ") != "n": Diary.create_encrypted_content(content=data, key=key) - print('Saved successfully.') + print("Saved successfully.") print() -def view_diaries(key: str = ENCRYPT_KEY): +def view_diaries(key: str = ENCRYPT_KEY) -> None: """View previous diaries""" query = Diary.select().order_by(Diary.created_date.desc()) for diary in query: print() - timestamp = diary.created_date.strftime('%d/%m/%Y %H:%M:%S') + timestamp = diary.created_date.strftime("%d/%m/%Y %H:%M:%S") print(timestamp) - print('=' * len(timestamp)) + print("=" * len(timestamp)) print(diary.encrypted_content) print(diary.get_content(key)) print() - print('n) next diary') - print('d) delete diary') - print('q) return to main menu') - action = input('Choice? (N/d/q) ').lower().strip() - if action == 'q': + print("n) next diary") + print("d) delete diary") + print("q) return to main menu") + action = input("Choice? (N/d/q) ").lower().strip() + if action == "q": break - elif action == 'd': + elif action == "d": diary.delete_instance() break MENU = { - 'a': add_diary, - 'v': view_diaries, - 'p': Diary.print_table, + "a": add_diary, + "v": view_diaries, + "p": Diary.print_table, } -def menu_loop(): +def menu_loop() -> None: choice = None - while choice != 'q': + while choice != "q": for key, value in MENU.items(): - print('%s) %s' % (key, value.__doc__)) - choice = input('Action: ').lower().strip() + print("%s) %s" % (key, value.__doc__)) + choice = input("Action: ").lower().strip() if choice in MENU: MENU[choice]() diff --git a/peewee__examples/hello_world__diary__encryption_all_in_one_AES/utils/__init__.py b/peewee__examples/hello_world__diary__encryption_all_in_one_AES/utils/__init__.py index 06334aa61..f25732790 100644 --- a/peewee__examples/hello_world__diary__encryption_all_in_one_AES/utils/__init__.py +++ b/peewee__examples/hello_world__diary__encryption_all_in_one_AES/utils/__init__.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' - +__author__ = "ipetrash" diff --git a/peewee__examples/hello_world__diary__encryption_all_in_one_AES/utils/security.py b/peewee__examples/hello_world__diary__encryption_all_in_one_AES/utils/security.py index 934863819..c91120fb1 100644 --- a/peewee__examples/hello_world__diary__encryption_all_in_one_AES/utils/security.py +++ b/peewee__examples/hello_world__diary__encryption_all_in_one_AES/utils/security.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/gil9red/SimplePyScripts/blob/328be435db9872cfcab35d6df33751006e3e8a64/pycryptodome__examples__AES_DES_RSA/AES_with_password__verify_key.py @@ -19,9 +19,9 @@ class AuthenticationError(Exception): class CryptoAES: - def __init__(self, key: (str, bytes)): + def __init__(self, key: (str, bytes)) -> None: if isinstance(key, str): - key = key.encode('utf-8') + key = key.encode("utf-8") self.key = hashlib.sha256(key).digest() @@ -30,18 +30,22 @@ def get_random_key_hex() -> str: return get_random_bytes(32).hex() def encrypt(self, plain_text: str) -> str: - data = plain_text.encode('utf-8') + data = plain_text.encode("utf-8") cipher = AES.new(self.key, AES.MODE_EAX) cipher_text, tag = cipher.encrypt_and_digest(data) encrypted_data = cipher.nonce + tag + cipher_text - return base64.b64encode(encrypted_data).decode('utf-8') + return base64.b64encode(encrypted_data).decode("utf-8") def decrypt(self, encrypted_text: str) -> str: encrypted_data = base64.b64decode(encrypted_text) - nonce, tag, cipher_text = encrypted_data[:16], encrypted_data[16:32], encrypted_data[32:] + nonce, tag, cipher_text = ( + encrypted_data[:16], + encrypted_data[16:32], + encrypted_data[32:], + ) cipher = AES.new(self.key, AES.MODE_EAX, nonce) try: @@ -49,12 +53,12 @@ def decrypt(self, encrypted_text: str) -> str: except ValueError as e: raise AuthenticationError(e) - return data.decode('utf-8') + return data.decode("utf-8") -if __name__ == '__main__': - text = 'Hello World!' - password = '123' +if __name__ == "__main__": + text = "Hello World!" + password = "123" crypto = CryptoAES(password) encrypted_text = crypto.encrypt(text) @@ -65,6 +69,6 @@ def decrypt(self, encrypted_text: str) -> str: # Decrypt with invalid password try: - CryptoAES('abc').decrypt(encrypted_text) + CryptoAES("abc").decrypt(encrypted_text) except AuthenticationError as e: - assert str(e) == 'MAC check failed' + assert str(e) == "MAC check failed" diff --git a/peewee__examples/hello_world__diary__encryption_all_in_one_AES/utils/utils.py b/peewee__examples/hello_world__diary__encryption_all_in_one_AES/utils/utils.py index 70d753506..6aca51bbd 100644 --- a/peewee__examples/hello_world__diary__encryption_all_in_one_AES/utils/utils.py +++ b/peewee__examples/hello_world__diary__encryption_all_in_one_AES/utils/utils.py @@ -1,12 +1,12 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" def shorten(text: str, width: int = 50) -> str: if len(text) <= width: return text - placeholder = '...' - return text[:width - len(placeholder)] + placeholder + placeholder = "..." + return text[: width - len(placeholder)] + placeholder diff --git a/peewee__examples/persons/main.py b/peewee__examples/persons/main.py index ec993cf75..f949fcab0 100644 --- a/peewee__examples/persons/main.py +++ b/peewee__examples/persons/main.py @@ -1,17 +1,17 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -import datetime as DT +import datetime as dt import json # pip install peewee -from peewee import * +from peewee import SqliteDatabase, Model, CharField, DateField, ForeignKeyField -db = SqliteDatabase('persons.sqlite', pragmas={'foreign_keys': 1}) +db = SqliteDatabase("persons.sqlite", pragmas={"foreign_keys": 1}) class BaseModel(Model): @@ -25,7 +25,7 @@ class Person(BaseModel): class Pet(BaseModel): - owner = ForeignKeyField(Person, backref='pets') + owner = ForeignKeyField(Person, backref="pets") name = CharField() animal_type = CharField() @@ -33,19 +33,21 @@ class Pet(BaseModel): db.connect() db.create_tables([Person, Pet]) -for person_data in json.load(open('persons.json', encoding='utf-8')): - birthday = DT.datetime.strptime(person_data['birthday'], '%Y-%M-%d') - person, _ = Person.get_or_create(name=person_data['name'], birthday=birthday) +for person_data in json.load(open("persons.json", encoding="utf-8")): + birthday = dt.datetime.strptime(person_data["birthday"], "%Y-%M-%d") + person, _ = Person.get_or_create(name=person_data["name"], birthday=birthday) - for pet in person_data['pets']: - Pet.get_or_create(owner=person, name=pet['name'], animal_type=pet['animal_type']) + for pet in person_data["pets"]: + Pet.get_or_create( + owner=person, name=pet["name"], animal_type=pet["animal_type"] + ) for person in Person.select(): - print(f'{person.name} ({person.birthday}). Pets: {person.pets.count()}') + print(f"{person.name} ({person.birthday}). Pets: {person.pets.count()}") for pet in person.pets: - print(f' {pet.name} ({pet.animal_type}). Owner: {pet.owner.name}') + print(f" {pet.name} ({pet.animal_type}). Owner: {pet.owner.name}") print() diff --git a/peewee__examples/serialization__model_to_dict__dict_to_model/main.py b/peewee__examples/serialization__model_to_dict__dict_to_model/main.py index 7cd6864da..85a10a0e5 100644 --- a/peewee__examples/serialization__model_to_dict__dict_to_model/main.py +++ b/peewee__examples/serialization__model_to_dict__dict_to_model/main.py @@ -1,18 +1,18 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -import datetime as DT +import datetime as dt # pip install peewee -from peewee import * +from peewee import SqliteDatabase, Model, CharField, DateField, ForeignKeyField from playhouse.shortcuts import model_to_dict, dict_to_model -db = SqliteDatabase(':memory:', pragmas={'foreign_keys': 1}) +db = SqliteDatabase(":memory:", pragmas={"foreign_keys": 1}) class BaseModel(Model): @@ -24,27 +24,29 @@ class Person(BaseModel): name = CharField() birthday = DateField() - def __str__(self): - return f'Person(id={self.id} name={self.name!r} birthday={self.birthday} ' \ - f'pets={", ".join(p.name for p in self.pets)!r})' + def __str__(self) -> str: + return ( + f"Person(id={self.id} name={self.name!r} birthday={self.birthday} " + f'pets={", ".join(p.name for p in self.pets)!r})' + ) class Pet(BaseModel): - owner = ForeignKeyField(Person, backref='pets') + owner = ForeignKeyField(Person, backref="pets") name = CharField() animal_type = CharField() - def __str__(self): - return f'Pet(id={self.id} name={self.name!r} owner={self.owner.name!r} self.animal_type={self.animal_type!r})' + def __str__(self) -> str: + return f"Pet(id={self.id} name={self.name!r} owner={self.owner.name!r} self.animal_type={self.animal_type!r})" db.connect() db.create_tables([Person, Pet]) -person = Person.create(name='Ivan', birthday=DT.date.today()) +person = Person.create(name="Ivan", birthday=dt.date.today()) -Pet.create(owner=person, name='Oval', animal_type='Dog') -Pet.create(owner=person, name='Bortik', animal_type='Cat') +Pet.create(owner=person, name="Oval", animal_type="Dog") +Pet.create(owner=person, name="Bortik", animal_type="Cat") print(person) # Person(id=1 name='Ivan' birthday=2020-01-09 pets='Oval, Bortik') @@ -63,7 +65,7 @@ def __str__(self): print() # Create another database and import this -db = SqliteDatabase('persons.sqlite', pragmas={'foreign_keys': 1}) +db = SqliteDatabase("persons.sqlite", pragmas={"foreign_keys": 1}) Person._meta.database = db Pet._meta.database = db db.connect() diff --git a/pens_calcs/common.py b/pens_calcs/common.py index c1c00972b..ea8e09133 100644 --- a/pens_calcs/common.py +++ b/pens_calcs/common.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # http://www.pfrf.ru/thm/common/mod/pensCalc/js/params.js @@ -45,7 +45,7 @@ 7: 1.74, 8: 1.9, 9: 2.09, - 10: 2.32 + 10: 2.32, } VSkoef = 1.8 # Коэффициент за 1 год военной службы diff --git a/pens_calcs/number_of_pension_points_for_year.py b/pens_calcs/number_of_pension_points_for_year.py index 9c12bd721..42d0bc365 100644 --- a/pens_calcs/number_of_pension_points_for_year.py +++ b/pens_calcs/number_of_pension_points_for_year.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import common @@ -21,8 +21,10 @@ def number_of_pension_points_for_year(zp: float) -> float: # Зарплата меньше мрот if zp < common.MROT: - raise Exception('Ошибка! Введите зарплату выше, чем минимальный размер оплаты труда в ' - 'Российской Федерации в 2017 году - 7 500 рублей') + raise Exception( + "Ошибка! Введите зарплату выше, чем минимальный размер оплаты труда в " + "Российской Федерации в 2017 году - 7 500 рублей" + ) kpk_trud = zp / common.ZPM * 10 if kpk_trud > 8.26: @@ -31,7 +33,7 @@ def number_of_pension_points_for_year(zp: float) -> float: return round(kpk_trud * 100) / 100 -if __name__ == '__main__': +if __name__ == "__main__": # Сколько пенсионных баллов может быть начислено Вам за 2017 год? # # Введите размер Вашей ежемесячной заработной платы до вычета НДФЛ: diff --git a/pens_calcs/pens_calc.py b/pens_calcs/pens_calc.py deleted file mode 100644 index 6d51873a1..000000000 --- a/pens_calcs/pens_calc.py +++ /dev/null @@ -1,555 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -__author__ = 'ipetrash' - - -import common - - -# TODO: сделать -# http://www.pfrf.ru/thm/common/mod/pensCalc/js/public.js -# TODO: добавить аннотации параметрам и возвращаемому значению - -# # Служба в армии -# yearsInArmy, monthInArmy, daysInArmy - -# # Уход за нетрудоспособными -# careYears, careMonth, careDays - -# # Дети, не более 4х -# TODO: посмотреть к каким полям относится и переименвать соответственно и в формулах их использования -# childrenCount1, childrenVac1 - - -# def pens_calc(gender, birthDate, pensionTarif, -# yearsInArmy, monthInArmy, daysInArmy, -# careYears, careMonth, careDays, -# childrenCount1, childrenVac1, -# retireWorkWithoutPension, -# careerLength, -# revenue, -# SZPeriod -# ): - - - -# var pensionForm = $(".calc-form") -# var persionFormInputs = pensionForm.find("input,select") -# var calcResult = $(".pensionCalcResult") -# var calcResultToday = $(".pensionCalcResultToday") -# var socialPensionWarning = $("#socialPensionWarning") -# var socialPensionWarning2 = $("#socialPensionWarning2") -# var combinationWarning = $("#combinationWarning") -# var enterParamsWarning = $("#enterParamsWarning") -# var enterGenderWarning = $("#enterGenderWarning") -# var enterBYWarning = $("#enterBYWarning") -# var enterBYWarning2 = $("#enterBYWarning2") -# var enterBYWarning3 = $("#enterBYWarning3") -# var alreadyPensioneer = $("#alreadyPensioneer") -# var wrongFee = $("#wrongFee") -# var noTarif = $("#noTarif") -# var ending = $(".ending") -# var newCoefSummSmall = $(".newCoefSummSmall") -# var personOSsmall = $(".personOSsmall") -# -# var armySection = pensionForm.find('.army-section') - -# TODO: возможно будет полезно узнать что скрыто в той секции -# armySection.hide() -# persionFormInputs.filter('[type="radio"][name="noArmy"]').click(function () { -# if($(this).val() == 2) { -# armySection.slideDown(150) -# } -# else { -# armySection.slideUp(150) -# } -# }) - -# var textInputs = persionFormInputs.filter("input[type='text']") -# var prevValues = [] - - -# var careerPlanQuestions = $("div.careerPlanQuestions") -# var careerPlanSwitch = $("input[name='careerPlan']") -# -# function revealCPQuestionsBlocks(switchValue) { -# careerPlanQuestions.hide() -# switch(switchValue) { -# case '1': -# //наемный работник -# careerPlanQuestions.filter(".careerPlan1").show() -# break -# case '2': -# //самозанятый -# careerPlanQuestions.filter(".careerPlan2").show() -# break -# case '3': -# //совмещающий -# careerPlanQuestions.show() -# break -# } -# } -# -# revealCPQuestionsBlocks(careerPlanSwitch.filter(':checked').val()) -# careerPlanSwitch.change(function () { -# revealCPQuestionsBlocks($(this).val()) -# }) - - -# TODO: поле в скрипте не имеет значения, вообще -gender = "1" -# if(gender.length < 1) { -# enterGenderWarning.show() -# $(output_area).slideDown() -# return -# } - -# TODO: поле в скрипте не имеет значения, вообще -birthDate = "1992" -# if(!birthDate.match(/\d{4}/)) { -# enterBYWarning.show() -# $(output_area).slideDown() -# return -# } - -# TODO: узнать что скрывается за полем -# TODO: пусть будет пока 0 -pensionTarif = 0 -# var pensionTarif = persionFormInputs.filter('[name="pensionTarif"]:checked') -# if(pensionTarif.length < 1) { -# noTarif.show() -# $(output_area).slideDown() -# return -# } -# pensionTarif = parseInt(pensionTarif.val()) - -# Общий расчет - -# Служба в армии -yearsInArmy = 1 -monthInArmy = 0 -daysInArmy = 0 - -# Стаж в армии -VS = ((((yearsInArmy * 12) + monthInArmy) / 12 * 365) + daysInArmy) / 365 - -# Коэффициент стажа -VSK = VS * common.VSkoef - -# Уход за нетрудоспособными -careYears = 1 -careMonth = 0 -careDays = 0 - -# Стаж ухода за нетрудоспособными -CR = ((((careYears * 12) + careMonth) / 12 * 365) + careDays) / 365 - -# Коэффициент ухода -CRK = CR * common.CRkoef - -# Дети, не более 4х -childrenCount1 = 1 -if childrenCount1 < 0: - childrenCount1 = 0 - -elif childrenCount1 > 4: - childrenCount1 = 4 - -# TODO: узнать что за childrenVac1 -# TODO: и что за стаж -# Стаж -childrenVac1 = 0 -if childrenVac1 < 0: - childrenVac1 = 0 - -elif childrenVac1 > 1.5: - childrenVac1 = 1.5 - - -# NOTE: только внутри кода объявляется -# Коэффициент -KD = 0.0 - -if childrenCount1 > 0: - if childrenCount1 > 0: - KD += 1.8 - - if childrenCount1 > 1: - KD += 3.6 - - if childrenCount1 > 2: - KD += 5.4 - - if childrenCount1 > 3: - KD += 5.4 - - KD *= childrenVac1 - childrenVac1 *= childrenCount1 - -# Коэффициент за нетрудовые периоды -NK = KD + CRK + VSK - -# Стаж за нетрудовые периоды -NS = childrenVac1 + VS + CR - -# NOTE: сколько после пенсионного возраста собираешься работать неполучая пенсию -retireWorkWithoutPension = 5 -if retireWorkWithoutPension > 10: - retireWorkWithoutPension = 10 - -# Расчеты в зависимости от типа занятости -# TODO: узнать возможные варианты значения -careerPlan = '1' - -# Наемный работник -def calcEmpl(fee: int, careerLength: int, pensionTarif: int): - """ - - :param fee: Зарплата - :param careerLength: Стаж - - # TODO: узнать что за тариф такой - :param pensionTarif: - :return: - """ - - if fee < 0: - fee = 0 - - if fee > common.ZPM: - fee = common.ZPM - - if careerLength < 0: - careerLength = 0 - - # TODO: бросать исключение, узнать какой текст тут скрыт - # if careerLength > 60) { - # enterBYWarning3.show() - # $(output_area).slideDown() - # return false - - # TODO: бросать исключение, узнать какой текст тут скрыт - # # Зарплата меньше мрот - # if careerLength > 0 and fee < common.MROT: - # wrongFee.show() - # $(output_area).slideDown() - # return false - # } - - # Пенсионные коэффициенты за трудовой период - IPKtrud = (fee / common.ZPM) * common.KNPG[pensionTarif] * (careerLength * 10) - - # TODO: Проследить за S и заменить - return { - 'S': careerLength, - 'IPKtrud': IPKtrud, - } - -def calcSZ(SZPeriod: int, revenue: int, pensionTarif: int): - """ - - :param revenue: Годовой доход - :param SZPeriod: Стаж - - # TODO: узнать что за тариф такой - :param pensionTarif: - :return: - """ - - if SZPeriod < 0: - SZPeriod = 0 - - if revenue < 0: - revenue = 0 - - # TODO: разобраться какие значения может иметь pensionTarif - SVGDkoeff = 16 if pensionTarif == 0 else 10 - - # Сумма страховых взносов на страховую пенсию, начисленных исходя из размера годового дохода - if revenue < common.GDmax: - SVGD = (SVGDkoeff * (common.MROT * 0.26 * 12)) / 26 - else: - SVGD = (SVGDkoeff * (common.MROT * 0.26 * 12) + ((revenue - common.GDmax) * 0.01)) / 26 - - # Пенсионные коэффициенты за трудовой период - IPKtrud = (SVGD / common.MSSV) * SZPeriod * 10 - - # TODO: отследить и заменить S - return { - 'S': SZPeriod, - 'IPKtrud': IPKtrud - } - -calcPart = None -calcPartEmpl = None -calcPartSZ = None -IPKtrud = None -S = None - -if careerPlan == '1': - # Наемный работник - calcPart = calcEmpl() - # TODO: добавить проверку, проверить какое значение может возвращаться из функции и как влияет - # возврат значения тут - # - # if(calcPart === false) return false - - # Стаж - S = calcPart.S - - # Пенсионные коэффициенты - IPKtrud = calcPart.IPKtrud - -elif careerPlan == '2': - # Самозанятый - calcPart = calcSZ() - - # Стаж - S = calcPart.S - - # Пенсионные коэффициенты - IPKtrud = calcPart.IPKtrud - -elif careerPlan == '3': - # Совмещающий - calcPartEmpl = calcEmpl() - # TODO: добавить проверку, проверить какое значение может возвращаться из функции и как влияет - # возврат значения тут - # - # if(calcPartEmpl === false) return false - - calcPartSZ = calcSZ() - - # Количество пенсионных коэффициентов свыше максимально установленного значения в год, полученных при совмещённой деятельности - combinePeriod = 0 - # TODO: проверить - # if(combinePeriod.length < 1) { - # combinePeriod = 0 - # persionFormInputs.filter('#combinePeriod').val(combinePeriod) - # } - # TODO: проверить - # if combinePeriod > Math.min(calcPartEmpl.S, calcPartSZ.S)) { - # combinationWarning.show() - # $(output_area).slideDown() - # return false - # } - - # Годовой пенсионный коэффициент, получаемый гражданином в года совмещения деятельности - IPKemp = calcPartEmpl.IPKtrud * combinePeriod / calcPartEmpl.S - IPKsz = calcPartSZ.IPKtrud * combinePeriod / calcPartSZ.S - IPKo = (IPKsz + IPKemp) / combinePeriod - if IPKo > 10: - IPKo = 10 - - IPKis = IPKo * combinePeriod - - # Стаж - S = calcPartEmpl.S + calcPartSZ.S - combinePeriod - - # Пенсионные коэффициенты - IPKtrud = (calcPartSZ.IPKtrud - IPKsz) + (calcPartEmpl.IPKtrud - IPKemp) + IPKis - if combinePeriod == 0: - IPKtrud = calcPartSZ.IPKtrud + calcPartEmpl.IPKtrud - - -# Переходный период для наемных и самозанятых -if careerPlan == '1' or careerPlan == '2': - IPKtrud2015 = 0 - IPKtrud2021 = 0 - - fee = 10000 - revenue = 1 - if revenue < 0: - revenue = 0 - - # Сумма страховых взносов на страховую пенсию, начисленных исходя из размера годового дохода - SVGD = 0 - SVGDkoeff = 16 if pensionTarif == 0 else 10 - - if revenue < common.GDmax: - SVGD = (SVGDkoeff * (common.MROT * 0.26 * 12)) / 26 - else: - SVGD = (SVGDkoeff * (common.MROT * 0.26 * 12) + ((revenue - common.GDmax) * 0.01)) / 26 - - if S > 5: - IPKtrud2021 = IPKtrud * ((S - 5) / S) - - if careerPlan == '1': - IPKtrud2015 = fee / common.ZPM * 10 - - elif careerPlan == '2': - IPKtrud2015 = (SVGD / common.MSSV) * 10 - - elif S < 6: - if careerPlan == '1': - IPKtrud2015 = fee / common.ZPM * 10 - - elif careerPlan == '2': - IPKtrud2015 = (SVGD / common.MSSV) * 10 - - # KNPG = 1 - if pensionTarif == '0': - IPKtrud2015 = (S > 0 ? Math.min(8.26, IPKtrud2015) : 0) + (S > 1 ? Math.min(8.70, IPKtrud2015) : 0) + - (S > 2 ? Math.min(9.13, IPKtrud2015) : 0) + (S > 3 ? Math.min(9.57, IPKtrud2015) : 0) - - # KNPG = 0.625 - else: - IPKtrud2015 = (S > 0 ? Math.min(8.26, IPKtrud2015) : 0) + (S > 1 ? Math.min(5.43, IPKtrud2015) : 0) + - (S > 2 ? Math.min(5.71, IPKtrud2015) : 0) + (S > 3 ? Math.min(5.98, IPKtrud2015) : 0) - - IPKtrud = IPKtrud2015 + IPKtrud2021 - - -# TODO: какое-то обнуление значений в редакторе полей -# if(careerPlan == '1') { -# persionFormInputs.filter('#SZPeriod').val(0) -# persionFormInputs.filter('#revenue').val(0) -# } -# else { -# if(careerPlan == '2') { -# persionFormInputs.filter('#careerLength').val(0) -# persionFormInputs.filter('#fee').val(0) -# } -# } - -# Пенсионные коэффициенты -IPK = (IPKtrud + NK) * common.SPKop[retireWorkWithoutPension] - -# Общий стаж -OS = S + NS - -# # NOTE: Установка значений -# newCoefSummSmall.html((Math.round(IPK * 100) / 100).toString()) -# personOSsmall.html((Math.round(OS * 100) / 100).toString()) - - -var WR = OS.toString().substr(-1) - -ending.html('лет') - -if(WR == 1) { - ending.html('год') -} -else { - if(WR >= 2 && WR <= 4) { - ending.html('года') - } -} - -# пересчёт права выхода на пенсию по стажу (каждому году свой минимальный стаж) -$(output_area).hide() -if(S == 0 && OS < 8) { - socialPensionWarning.show() - $(output_area).slideDown() - return false -} - -if(S == 1 && OS < 9) { - socialPensionWarning.show() - $(output_area).slideDown() - return false -} - -if(S == 2 && OS < 10) { - socialPensionWarning.show() - $(output_area).slideDown() - return false -} - -if(S == 3 && OS < 11) { - socialPensionWarning.show() - $(output_area).slideDown() - return false -} - -if(S == 4 && OS < 12) { - socialPensionWarning.show() - $(output_area).slideDown() - return false -} - -if(S == 5 && OS < 13) { - socialPensionWarning.show() - $(output_area).slideDown() - return false -} - -if(S == 6 && OS < 14) { - socialPensionWarning.show() - $(output_area).slideDown() - return false -} - -if(S == 7 && OS < 15) { - socialPensionWarning.show() - $(output_area).slideDown() - return false -} - -//пересчёт права выхода на пенсию по ИПК (каждому году свой минимальный ИПК) -if(S == 0 && IPK < 11.4) { - socialPensionWarning.show() - $(output_area).slideDown() - return false -} - -if(S == 1 && IPK < 13.8) { - socialPensionWarning.show() - $(output_area).slideDown() - return false -} - -if(S == 2 && IPK < 16.2) { - socialPensionWarning.show() - $(output_area).slideDown() - return false -} - -if(S == 3 && IPK < 18.6) { - socialPensionWarning.show() - $(output_area).slideDown() - return false -} - -if(S == 4 && IPK < 21) { - socialPensionWarning2.show() - $(output_area).slideDown() - return false -} - -if(S == 5 && IPK < 23.4) { - socialPensionWarning.show() - $(output_area).slideDown() - return false -} - -if(S == 6 && IPK < 25.8) { - socialPensionWarning.show() - $(output_area).slideDown() - return false -} - -if(S == 7 && IPK < 28.2) { - socialPensionWarning.show() - $(output_area).slideDown() - return false -} - -if(S == 8 && IPK < 30) { - socialPensionWarning.show() - $(output_area).slideDown() - return false -} - -# Страховая пенсия -SP = (common.FIKS * common.BPKop[retireWorkWithoutPension]) + (IPK * common.CPK) - -var newCoefSummCont = $("#newCoefSumm") -newCoefSummCont.html((Math.round(IPK * 100) / 100).toString()) - -var pensionIPartCont = $("#pensionIPart") -pensionIPartCont.html((Math.round(SP * 100) / 100).toString()) - -var personOSCont = $("#personOS") -personOSCont.html((Math.round(OS * 100) / 100).toString()) diff --git a/percentage_values_in_list.py b/percentage_values_in_list.py index 6d9aba2c6..b485ac97d 100644 --- a/percentage_values_in_list.py +++ b/percentage_values_in_list.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """ @@ -26,10 +26,10 @@ def get_percentage_values(items): """Возвращает список с процентным значением элемента списка.""" sum_items = sum(items) - return ['{:.1f}%'.format(i / sum_items * 100) for i in items] + return [f"{i / sum_items * 100:.1f}%" for i in items] -if __name__ == '__main__': +if __name__ == "__main__": print(get_percentage_values([100, 100])) print(get_percentage_values([100, 100, 200])) print(get_percentage_values([100, 100, 200, 400])) diff --git a/permutations.py b/permutations.py index 326c93870..83c5c183d 100644 --- a/permutations.py +++ b/permutations.py @@ -1,14 +1,15 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from itertools import permutations -items = list(permutations('abc', r=3)) -print(items) # [('a', 'b', 'c'), ('a', 'c', 'b'), ('b', 'a', 'c'), ('b', 'c', 'a'), ('c', 'a', 'b'), ('c', 'b', 'a')] +items = list(permutations("abc", r=3)) +print(items) +# [('a', 'b', 'c'), ('a', 'c', 'b'), ('b', 'a', 'c'), ('b', 'c', 'a'), ('c', 'a', 'b'), ('c', 'b', 'a')] -items = list(permutations('abc', r=2)) +items = list(permutations("abc", r=2)) print(items) # [('a', 'b'), ('a', 'c'), ('b', 'a'), ('b', 'c'), ('c', 'a'), ('c', 'b')] diff --git a/pikabu_ru/download_svg_emotions/main.py b/pikabu_ru/download_svg_emotions/main.py index 6e67703c2..acd5da042 100644 --- a/pikabu_ru/download_svg_emotions/main.py +++ b/pikabu_ru/download_svg_emotions/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from pathlib import Path @@ -15,43 +15,45 @@ DIR = Path(__file__).resolve().parent -DIR_OUT = DIR / 'out' +DIR_OUT = DIR / "out" DIR_OUT.mkdir(parents=True, exist_ok=True) options = Options() -options.add_argument('--headless') +options.add_argument("--headless") driver = webdriver.Firefox(options=options) driver.implicitly_wait(10) try: - driver.get('https://pikabu.ru/') + driver.get("https://pikabu.ru/") print(f'Title: "{driver.title}"') # Содержит иконки в тегах symbol - svg_app_el = driver.find_element(By.CSS_SELECTOR, 'svg.app-svg') + svg_app_el = driver.find_element(By.CSS_SELECTOR, "svg.app-svg") # Содержит предустановленные фигуры. Без него некоторые из иконок-эмоций отображались частично - svg_defs_el = svg_app_el.find_element(By.CSS_SELECTOR, 'defs') - defs = svg_defs_el.get_attribute('outerHTML') + svg_defs_el = svg_app_el.find_element(By.CSS_SELECTOR, "defs") + defs = svg_defs_el.get_attribute("outerHTML") # Перебор и сохранение svg с эмоциями - for svg_symbol_el in svg_app_el.find_elements(By.CSS_SELECTOR, 'symbol'): - el_id = svg_symbol_el.get_attribute('id') - if 'icon--emotions__' in el_id: - svg_symbol = svg_symbol_el.get_attribute('outerHTML') - svg = dedent(f'''\ + for svg_symbol_el in svg_app_el.find_elements(By.CSS_SELECTOR, "symbol"): + el_id = svg_symbol_el.get_attribute("id") + if "icon--emotions__" in el_id: + svg_symbol = svg_symbol_el.get_attribute("outerHTML") + svg = dedent( + f"""\ {defs} {svg_symbol} - ''') - file_name = DIR_OUT / f'{el_id}.svg' - print(f'Saving {file_name} ...') + """ + ) + file_name = DIR_OUT / f"{el_id}.svg" + print(f"Saving {file_name} ...") - file_name.write_text(svg, encoding='utf-8') + file_name.write_text(svg, encoding="utf-8") finally: driver.quit() diff --git a/pikabu_ru/get_profile_rating.py b/pikabu_ru/get_profile_rating.py index 858488e90..645d5ba44 100644 --- a/pikabu_ru/get_profile_rating.py +++ b/pikabu_ru/get_profile_rating.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import requests @@ -9,29 +9,31 @@ session = requests.Session() -session.headers['User-Agent'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:108.0) Gecko/20100101 Firefox/108.0' +session.headers[ + "User-Agent" +] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:108.0) Gecko/20100101 Firefox/108.0" def get_profile_rating(url: str) -> int: rs = session.get(url) rs.raise_for_status() - root = BeautifulSoup(rs.content, 'html.parser') - profile_digital_el = root.select_one('.profile__digital') + root = BeautifulSoup(rs.content, "html.parser") + profile_digital_el = root.select_one(".profile__digital") if not profile_digital_el: raise Exception('Element ".profile__digital" not found!') - rating_str = profile_digital_el['aria-label'] + rating_str = profile_digital_el["aria-label"] # В рейтинге могут быть не цифровые символы, типа пробелов - return int(''.join(c for c in rating_str if c.isdigit())) + return int("".join(c for c in rating_str if c.isdigit())) -if __name__ == '__main__': - url = 'https://pikabu.ru/@RytsarSvezhego' +if __name__ == "__main__": + url = "https://pikabu.ru/@RytsarSvezhego" print(get_profile_rating(url)) # 2016 - url = 'https://pikabu.ru/@tibidohtel' + url = "https://pikabu.ru/@tibidohtel" print(get_profile_rating(url)) # 104563 diff --git a/pikabu_ru/page_interview_fullstack__question_19/main.py b/pikabu_ru/page_interview_fullstack__question_19/main.py index 650524aad..eb9d6407d 100644 --- a/pikabu_ru/page_interview_fullstack__question_19/main.py +++ b/pikabu_ru/page_interview_fullstack__question_19/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://pikabu.ru/page/interview/fullstack/ @@ -17,7 +17,8 @@ # pip install Pillow from PIL import Image -image = Image.open('154800137443598227.png') + +image = Image.open("154800137443598227.png") width, height = image.size pixel = image.load() @@ -30,11 +31,11 @@ # RGBa, alpha-канал нас не интересует, только RGB r, g, b, _ = pixel[x, y] - r = (r + s) & 0xff - g = (g - s) & 0xff - b = (b + s) & 0xff + r = (r + s) & 0xFF + g = (g - s) & 0xFF + b = (b + s) & 0xFF pixel[x, y] = r, g, b -image.save('result.png') +image.save("result.png") diff --git a/pikabu_ru/profile_rating__tracking/db.py b/pikabu_ru/profile_rating__tracking/db.py index 19dfd50e8..d6f5668d0 100644 --- a/pikabu_ru/profile_rating__tracking/db.py +++ b/pikabu_ru/profile_rating__tracking/db.py @@ -1,18 +1,26 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -import datetime as DT +import datetime as dt import shutil import sys from pathlib import Path -from typing import Iterable, Type +from typing import Iterable, Type, TypeVar # pip install peewee -from peewee import Model, SqliteDatabase, TextField, DateTimeField, IntegerField, CharField, ForeignKeyField +from peewee import ( + Model, + SqliteDatabase, + TextField, + DateTimeField, + IntegerField, + CharField, + ForeignKeyField, +) DIR = Path(__file__).resolve().parent @@ -21,21 +29,24 @@ # Absolute file name -DB_FILE_NAME = str(DIR / 'db.sqlite') -DIR_BACKUP = DIR / 'backup' +DB_FILE_NAME = str(DIR / "db.sqlite") +DIR_BACKUP = DIR / "backup" -def db_create_backup(backup_dir=DIR_BACKUP): +def db_create_backup(backup_dir=DIR_BACKUP) -> None: backup_dir.mkdir(parents=True, exist_ok=True) - file_name = str(DT.datetime.today().date()) + '.sqlite' + file_name = str(dt.datetime.today().date()) + ".sqlite" file_name = backup_dir / file_name shutil.copy(DB_FILE_NAME, file_name) # Ensure foreign-key constraints are enforced. -db = SqliteDatabase(DB_FILE_NAME, pragmas={'foreign_keys': 1}) +db = SqliteDatabase(DB_FILE_NAME, pragmas={"foreign_keys": 1}) + + +ChildModel = TypeVar("ChildModel", bound="BaseModel") class BaseModel(Model): @@ -46,30 +57,30 @@ class BaseModel(Model): class Meta: database = db - def get_new(self) -> Type['BaseModel']: + def get_new(self) -> ChildModel: return type(self).get(self._pk_expr()) @classmethod - def get_first(cls) -> Type['BaseModel']: + def get_first(cls) -> ChildModel: return cls.select().first() @classmethod - def get_last(cls) -> Type['BaseModel']: + def get_last(cls) -> ChildModel: return cls.select().order_by(cls.id.desc()).first() @classmethod - def get_inherited_models(cls) -> list[Type['BaseModel']]: + def get_inherited_models(cls) -> list[Type["BaseModel"]]: return sorted(cls.__subclasses__(), key=lambda x: x.__name__) @classmethod - def print_count_of_tables(cls): + def print_count_of_tables(cls) -> None: items = [] for sub_cls in cls.get_inherited_models(): name = sub_cls.__name__ count = sub_cls.select().count() - items.append(f'{name}: {count}') + items.append(f"{name}: {count}") - print(', '.join(items)) + print(", ".join(items)) @classmethod def count(cls, filters: Iterable = None) -> int: @@ -78,7 +89,7 @@ def count(cls, filters: Iterable = None) -> int: query = query.filter(*filters) return query.count() - def __str__(self): + def __str__(self) -> str: fields = [] for k, field in self._meta.fields.items(): v = getattr(self, k) @@ -88,22 +99,22 @@ def __str__(self): v = repr(shorten(v)) elif isinstance(field, ForeignKeyField): - k = f'{k}_id' + k = f"{k}_id" if v: v = v.id - fields.append(f'{k}={v}') + fields.append(f"{k}={v}") - return self.__class__.__name__ + '(' + ', '.join(fields) + ')' + return self.__class__.__name__ + "(" + ", ".join(fields) + ")" class ProfileRating(BaseModel): url = TextField() - date = DateTimeField(default=DT.datetime.now) + date = DateTimeField(default=dt.datetime.now) value = IntegerField() @classmethod - def append(cls, url: str, value: int) -> 'ProfileRating': + def append(cls, url: str, value: int) -> "ProfileRating": return cls.get_or_create(url=url, value=value)[0] @@ -111,6 +122,8 @@ def append(cls, url: str, value: int) -> 'ProfileRating': db.create_tables(BaseModel.get_inherited_models()) -if __name__ == '__main__': +if __name__ == "__main__": BaseModel.print_count_of_tables() print() + + print(ProfileRating.get_last()) diff --git a/pikabu_ru/profile_rating__tracking/main.py b/pikabu_ru/profile_rating__tracking/main.py index 580c59a0e..91b2d3ffb 100644 --- a/pikabu_ru/profile_rating__tracking/main.py +++ b/pikabu_ru/profile_rating__tracking/main.py @@ -1,39 +1,36 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -import datetime as DT +import datetime as dt import sys import traceback from pathlib import Path +# pip install simple-wait +from simple_wait import wait DIR = Path(__file__).resolve().parent - -# Import https://github.com/gil9red/SimplePyScripts/blob/8fa9b9c23d10b5ee7ff0161da997b463f7a861bf/wait/wait.py -sys.path.append(str(DIR.parent.parent / 'wait')) sys.path.append(str(DIR.parent)) -from wait import wait from get_profile_rating import get_profile_rating - from db import ProfileRating, db_create_backup -URL = 'https://pikabu.ru/@RytsarSvezhego' +URL = "https://pikabu.ru/@RytsarSvezhego" while True: - print(f'Started at {DT.datetime.now():%d/%m/%Y %H:%M:%S}\n') + print(f"Started at {dt.datetime.now():%d/%m/%Y %H:%M:%S}\n") db_create_backup() try: value = get_profile_rating(URL) - print(f'URL: {URL}\n {value}\n') + print(f"URL: {URL}\n {value}\n") ProfileRating.append(URL, value) @@ -44,7 +41,7 @@ tb = traceback.format_exc() print(tb) - print('Wait 15 minutes') + print("Wait 15 minutes") wait(minutes=15) print() diff --git a/pikabu_ru/profile_rating__tracking/web.py b/pikabu_ru/profile_rating__tracking/web.py index 7010519e5..ad41cb934 100644 --- a/pikabu_ru/profile_rating__tracking/web.py +++ b/pikabu_ru/profile_rating__tracking/web.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from flask import Flask, render_template @@ -14,9 +14,9 @@ @app.route("/") def index(): items = ProfileRating.select().order_by(ProfileRating.id.desc()) - return render_template('index.html', items=items) + return render_template("index.html", items=items) -if __name__ == '__main__': +if __name__ == "__main__": app.debug = True app.run(port=10017) diff --git a/pil_pillow__examples/blur/main.py b/pil_pillow__examples/blur/main.py index ed8b73d7c..7ccb6bbd1 100644 --- a/pil_pillow__examples/blur/main.py +++ b/pil_pillow__examples/blur/main.py @@ -1,15 +1,16 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install Pillow from PIL import Image, ImageFilter + image_file = "input.jpg" img = Image.open(image_file) img = img.filter(ImageFilter.GaussianBlur(2)) -img.save('output.png') +img.save("output.png") img.show() diff --git a/pil_pillow__examples/bytes_to_image/main.py b/pil_pillow__examples/bytes_to_image/main.py index 945ac776f..7b3192433 100644 --- a/pil_pillow__examples/bytes_to_image/main.py +++ b/pil_pillow__examples/bytes_to_image/main.py @@ -1,17 +1,19 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -with open('input.jpg', 'rb') as f: - data = f.read() - import io -data_io = io.BytesIO(data) # pip install Pillow from PIL import Image + + +with open("input.jpg", "rb") as f: + data = f.read() + +data_io = io.BytesIO(data) img = Image.open(data_io) print(img) diff --git a/pil_pillow__examples/crop.py b/pil_pillow__examples/crop.py index d1fdb421b..224192eff 100644 --- a/pil_pillow__examples/crop.py +++ b/pil_pillow__examples/crop.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install Pillow diff --git a/pil_pillow__examples/crop_groups.py b/pil_pillow__examples/crop_groups.py index f4dcd32af..d48bf3ece 100644 --- a/pil_pillow__examples/crop_groups.py +++ b/pil_pillow__examples/crop_groups.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from pathlib import Path @@ -12,7 +12,7 @@ path = Path(...) -for file_name in path.glob('*.jpg'): +for file_name in path.glob("*.jpg"): img = Image.open(file_name) width, height = img.size diff --git a/pil_pillow__examples/darker_picture/darker_picture.py b/pil_pillow__examples/darker_picture/darker_picture.py index 10fce76e4..4292d4f6a 100644 --- a/pil_pillow__examples/darker_picture/darker_picture.py +++ b/pil_pillow__examples/darker_picture/darker_picture.py @@ -1,14 +1,15 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install Pillow from PIL import Image + image_file = "Collapse all.png" -img = Image.open(image_file).convert('RGBA') +img = Image.open(image_file).convert("RGBA") pixdata = img.load() @@ -16,6 +17,8 @@ for y in range(img.size[1]): for x in range(img.size[0]): if pixdata[x, y][:3] != (255, 255, 255): - pixdata[x, y] = tuple(map(lambda x: x - 50, pixdata[x, y][:3])) + pixdata[x, y][3:] + pixdata[x, y] = ( + tuple(map(lambda x: x - 50, pixdata[x, y][:3])) + pixdata[x, y][3:] + ) img.save("Collapse all black.png") diff --git a/pil_pillow__examples/draw watermark/main.py b/pil_pillow__examples/draw watermark/main.py index 70e747df3..30237ba53 100644 --- a/pil_pillow__examples/draw watermark/main.py +++ b/pil_pillow__examples/draw watermark/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """ @@ -25,6 +25,6 @@ for i in range(0, width, width_text * 2): for j in range(0, height, height_text * 2): - drawer.text((i, j), text, font=font, fill=(0x00, 0xff, 0x00)) + drawer.text((i, j), text, font=font, fill=(0x00, 0xFF, 0x00)) image.show() diff --git a/pil_pillow__examples/draw_progress_on_image/main.py b/pil_pillow__examples/draw_progress_on_image/main.py index ed99db174..d6aea02da 100644 --- a/pil_pillow__examples/draw_progress_on_image/main.py +++ b/pil_pillow__examples/draw_progress_on_image/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install Pillow @@ -33,11 +33,11 @@ def draw_progress(image: Image, percent: int) -> Image: return image -if __name__ == '__main__': +if __name__ == "__main__": image_file = "input.jpg" image = Image.open(image_file) - + for percent in (5, 15, 75, 100): img = draw_progress(image, percent) - img.save(f'output/image_{percent}%.png') + img.save(f"output/image_{percent}%.png") img.show() diff --git a/pil_pillow__examples/draw_times_cyrillic/main.py b/pil_pillow__examples/draw_times_cyrillic/main.py index 944747c5f..c9d42ccdf 100644 --- a/pil_pillow__examples/draw_times_cyrillic/main.py +++ b/pil_pillow__examples/draw_times_cyrillic/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://ru.stackoverflow.com/q/1236348/201445 @@ -17,32 +17,36 @@ def set_text(template, texts): - font_upper = ImageFont.truetype(f"{FONTS}/times.ttf", 64, encoding='utf-8') - font_lower = ImageFont.truetype(f"{FONTS}/times.ttf", 46, encoding='utf-8') + font_upper = ImageFont.truetype(f"{FONTS}/times.ttf", 64, encoding="utf-8") + font_lower = ImageFont.truetype(f"{FONTS}/times.ttf", 46, encoding="utf-8") draw_upper_text = ImageDraw.Draw(template) upper_text_width, _ = draw_upper_text.textsize(texts[0], font_upper) draw_upper_text.text( ( - (template.width-upper_text_width)/2, - template.height-BOTTOM_PADDING+10 + (template.width - upper_text_width) / 2, + template.height - BOTTOM_PADDING + 10, ), - texts[0], fill="white", font=font_upper + texts[0], + fill="white", + font=font_upper, ) draw_lower_text = ImageDraw.Draw(template) lower_text_width, _ = draw_lower_text.textsize(texts[1], font_lower) draw_lower_text.text( ( - (template.width-lower_text_width)/2, - template.height-BOTTOM_PADDING+(PADDING*4)-10 + (template.width - lower_text_width) / 2, + template.height - BOTTOM_PADDING + (PADDING * 4) - 10, ), - texts[1], fill="white", font=font_lower + texts[1], + fill="white", + font=font_lower, ) return template -if __name__ == '__main__': +if __name__ == "__main__": img = Image.new("RGB", (400, 300), "black") set_text(img, ["ПРИВЕТ", "HELLO WORLD!"]) diff --git a/pil_pillow__examples/fast_average_color/main.py b/pil_pillow__examples/fast_average_color/main.py index 27332a422..0fbeb875f 100644 --- a/pil_pillow__examples/fast_average_color/main.py +++ b/pil_pillow__examples/fast_average_color/main.py @@ -1,11 +1,10 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import math -from typing import Tuple from pathlib import Path from PIL import Image @@ -15,16 +14,16 @@ def get_mode_correction(image: Image) -> str: - if image.mode not in ['RGB', 'RGBA']: - return 'RGB' + if image.mode not in ["RGB", "RGBA"]: + return "RGB" return image.mode # SOURCE: https://github.com/fast-average-color/fast-average-color/blob/15561aa55a36702f0b1af3d0ef611a205f8d56b5/src/algorithm/sqrt.js#L3 -def sqrt_algorithm(image: Image) -> Tuple[int, int, int, int]: +def sqrt_algorithm(image: Image) -> tuple[int, int, int, int]: mode = get_mode_correction(image) if mode != image.mode: - image = image.convert('RGB') + image = image.convert("RGB") red_total = 0 green_total = 0 @@ -53,7 +52,7 @@ def sqrt_algorithm(image: Image) -> Tuple[int, int, int, int]: round(math.sqrt(red_total / alpha_total)), round(math.sqrt(green_total / alpha_total)), round(math.sqrt(blue_total / alpha_total)), - round(alpha_total / count) + round(alpha_total / count), ) @@ -71,9 +70,9 @@ def draw_example(image: Image, margin=70) -> Image: return image_output -if __name__ == '__main__': - for file_name in DIR.glob('input/*.*'): +if __name__ == "__main__": + for file_name in DIR.glob("input/*.*"): image = Image.open(file_name) image_output = draw_example(image) # image_output.show() - image_output.save(DIR / 'output' / file_name.name) + image_output.save(DIR / "output" / file_name.name) diff --git a/pil_pillow__examples/fill_0_and_1/main.py b/pil_pillow__examples/fill_0_and_1/main.py index 7a431250c..c72f716dc 100644 --- a/pil_pillow__examples/fill_0_and_1/main.py +++ b/pil_pillow__examples/fill_0_and_1/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import itertools @@ -10,7 +10,7 @@ from PIL import Image, ImageDraw, ImageFont -img = Image.new('RGB', (500, 300), (255, 0, 0)) +img = Image.new("RGB", (500, 300), (255, 0, 0)) width, height = img.size drawer = ImageDraw.Draw(img) @@ -19,7 +19,8 @@ text_0, text_1 = "0", "1" color_0, color_1 = (0, 0, 0), (255, 255, 255) it = itertools.cycle([ - (text_0, color_0), (text_1, color_1) + (text_0, color_0), + (text_1, color_1), ]) width_text_0, height_text_0 = font.getsize(text_0) diff --git a/pil_pillow__examples/flip/flip.py b/pil_pillow__examples/flip/flip.py index 9c38ef45d..7aba1a3a5 100644 --- a/pil_pillow__examples/flip/flip.py +++ b/pil_pillow__examples/flip/flip.py @@ -1,11 +1,13 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install Pillow from PIL import Image, ImageOps -img = Image.open('input.png') + + +img = Image.open("input.png") img = ImageOps.flip(img) img.show() diff --git a/pil_pillow__examples/for_cycle__pixels__array/main.py b/pil_pillow__examples/for_cycle__pixels__array/main.py index ed7f5d2a9..70a89eb4b 100644 --- a/pil_pillow__examples/for_cycle__pixels__array/main.py +++ b/pil_pillow__examples/for_cycle__pixels__array/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install Pillow @@ -21,7 +21,7 @@ def get_pixel_array(img, rgb_hex=False): r, g, b = img.getpixel((x, y)) if rgb_hex: - value = '{:02X}{:02X}{:02X}'.format(r, g, b) + value = f"{r:02X}{g:02X}{b:02X}" row.append(value) else: row.append((r, g, b)) @@ -29,16 +29,16 @@ def get_pixel_array(img, rgb_hex=False): return pixels -if __name__ == '__main__': +if __name__ == "__main__": img = Image.open("input.jpg") print(img) print() pixels = get_pixel_array(img) - print('Rows: {}, cols: {}'.format(len(pixels), len(pixels[0]))) + print(f"Rows: {len(pixels)}, cols: {len(pixels[0])}") print([pixels[0][i] for i in range(2)]) # [(7, 7, 7), (23, 23, 23)] print() pixels = get_pixel_array(img, rgb_hex=True) - print('Rows: {}, cols: {}'.format(len(pixels), len(pixels[0]))) + print(f"Rows: {len(pixels)}, cols: {len(pixels[0])}") print([pixels[0][i] for i in range(2)]) # ['070707', '171717'] diff --git a/pil_pillow__examples/get_image_size/main.py b/pil_pillow__examples/get_image_size/main.py index 96e07cda7..9851cc596 100644 --- a/pil_pillow__examples/get_image_size/main.py +++ b/pil_pillow__examples/get_image_size/main.py @@ -1,12 +1,13 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install Pillow from PIL import Image + img = Image.open("input.jpg") print(img) print(img.size) diff --git a/pil_pillow__examples/get_viewers.py b/pil_pillow__examples/get_viewers.py index 5ed94a7fa..7646fd6b8 100644 --- a/pil_pillow__examples/get_viewers.py +++ b/pil_pillow__examples/get_viewers.py @@ -1,15 +1,15 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PIL.ImageShow import _viewers -print(f'Viewers: {len(_viewers)}') +print(f"Viewers: {len(_viewers)}") for viewer in _viewers: - print(viewer.get_command('123.png')) + print(viewer.get_command("123.png")) """ Viewers: 1 diff --git a/pil_pillow__examples/gray/main.py b/pil_pillow__examples/gray/main.py index a954414bb..c29a90b06 100644 --- a/pil_pillow__examples/gray/main.py +++ b/pil_pillow__examples/gray/main.py @@ -1,15 +1,16 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install Pillow from PIL import Image, ImageOps + image_file = "input.jpg" image = Image.open(image_file) image_gray = ImageOps.grayscale(image) -image_gray.save('image_gray.png') +image_gray.save("image_gray.png") image_gray.show() diff --git a/pil_pillow__examples/image_to_bytes/main.py b/pil_pillow__examples/image_to_bytes/main.py index c3504430f..b1f70bfcf 100644 --- a/pil_pillow__examples/image_to_bytes/main.py +++ b/pil_pillow__examples/image_to_bytes/main.py @@ -1,12 +1,13 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" +import io + # pip install Pillow from PIL import Image -import io img = Image.open("input.jpg") diff --git a/pil_pillow__examples/img_to_grid/main.py b/pil_pillow__examples/img_to_grid/main.py index c5e2fcf09..9a0d01864 100644 --- a/pil_pillow__examples/img_to_grid/main.py +++ b/pil_pillow__examples/img_to_grid/main.py @@ -1,18 +1,24 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install Pillow from PIL import Image + + image = Image.open("input.jpg") width, height = image.size ROW_COUNT = 3 COLUMN_COUNT = 5 -grid_image = Image.new("RGB", (width * COLUMN_COUNT, height * ROW_COUNT), (255, 255, 255)) +grid_image = Image.new( + "RGB", + (width * COLUMN_COUNT, height * ROW_COUNT), + (255, 255, 255), +) for row in range(ROW_COUNT): y = row * width @@ -22,4 +28,4 @@ grid_image.paste(image, (x, y)) grid_image.show() -grid_image.save('output.jpg') +grid_image.save("output.jpg") diff --git a/pil_pillow__examples/invert/main.py b/pil_pillow__examples/invert/main.py index fc9481503..c3c409f00 100644 --- a/pil_pillow__examples/invert/main.py +++ b/pil_pillow__examples/invert/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """Инвертирование цвета картинки""" @@ -12,21 +12,21 @@ def invert(image): - if image.mode == 'RGBA': + if image.mode == "RGBA": r, g, b, a = image.split() - rgb_image = Image.merge('RGB', (r, g, b)) + rgb_image = Image.merge("RGB", (r, g, b)) inverted_image = ImageOps.invert(rgb_image) r2, g2, b2 = inverted_image.split() - return Image.merge('RGBA', (r2, g2, b2, a)) + return Image.merge("RGBA", (r2, g2, b2, a)) else: return ImageOps.invert(image) -if __name__ == '__main__': +if __name__ == "__main__": image_file = "input.jpg" image = Image.open(image_file) image_invert = invert(image) - image_invert.save('image_invert.png') + image_invert.save("image_invert.png") image_invert.show() diff --git a/pil_pillow__examples/invert_gray/main.py b/pil_pillow__examples/invert_gray/main.py index 3ee1191b2..6ddb9c4f1 100644 --- a/pil_pillow__examples/invert_gray/main.py +++ b/pil_pillow__examples/invert_gray/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install Pillow @@ -9,22 +9,22 @@ def invert(image): - if image.mode == 'RGBA': + if image.mode == "RGBA": r, g, b, a = image.split() - rgb_image = Image.merge('RGB', (r, g, b)) + rgb_image = Image.merge("RGB", (r, g, b)) inverted_image = ImageOps.invert(rgb_image) r2, g2, b2 = inverted_image.split() - return Image.merge('RGBA', (r2, g2, b2, a)) + return Image.merge("RGBA", (r2, g2, b2, a)) else: return ImageOps.invert(image) -if __name__ == '__main__': +if __name__ == "__main__": image_file = "input.jpg" image = Image.open(image_file) image_invert = invert(image) image_invert_gray = ImageOps.grayscale(image_invert) - image_invert_gray.save('image_invert_gray.png') + image_invert_gray.save("image_invert_gray.png") image_invert_gray.show() diff --git "a/pil_pillow__examples/jpeg_quality=1__\321\210\320\260\320\272\320\260\320\273\320\270\321\201\321\202\321\213\320\265_\321\204\320\276\321\202\320\272\320\270__jackal_jpg/main.py" "b/pil_pillow__examples/jpeg_quality=1__\321\210\320\260\320\272\320\260\320\273\320\270\321\201\321\202\321\213\320\265_\321\204\320\276\321\202\320\272\320\270__jackal_jpg/main.py" index b9d9ff20b..6abde7363 100644 --- "a/pil_pillow__examples/jpeg_quality=1__\321\210\320\260\320\272\320\260\320\273\320\270\321\201\321\202\321\213\320\265_\321\204\320\276\321\202\320\272\320\270__jackal_jpg/main.py" +++ "b/pil_pillow__examples/jpeg_quality=1__\321\210\320\260\320\272\320\260\320\273\320\270\321\201\321\202\321\213\320\265_\321\204\320\276\321\202\320\272\320\270__jackal_jpg/main.py" @@ -1,9 +1,11 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PIL import Image -img = Image.open('input.jpg') -img.save('output.jpg', format='JPEG', quality=1) + + +img = Image.open("input.jpg") +img.save("output.jpg", format="JPEG", quality=1) diff --git a/pil_pillow__examples/pil_image__to__qimage_qpixmap__qt/main.py b/pil_pillow__examples/pil_image__to__qimage_qpixmap__qt/main.py index a52ba908c..1c1e97c64 100644 --- a/pil_pillow__examples/pil_image__to__qimage_qpixmap__qt/main.py +++ b/pil_pillow__examples/pil_image__to__qimage_qpixmap__qt/main.py @@ -1,13 +1,15 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install Pillow from PIL import Image + from PyQt5 import Qt + image_file = "input.jpg" image = Image.open(image_file) diff --git a/pil_pillow__examples/pixelate_image/main.py b/pil_pillow__examples/pixelate_image/main.py index 14d7bdf2d..ea9fa95e6 100644 --- a/pil_pillow__examples/pixelate_image/main.py +++ b/pil_pillow__examples/pixelate_image/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://gist.github.com/danyshaanan/6754465 @@ -17,8 +17,12 @@ def pixelate(image, pixel_size=9, draw_margin=True): margin_color = (0, 0, 0) - image = image.resize((image.size[0] // pixel_size, image.size[1] // pixel_size), Image.NEAREST) - image = image.resize((image.size[0] * pixel_size, image.size[1] * pixel_size), Image.NEAREST) + image = image.resize( + (image.size[0] // pixel_size, image.size[1] // pixel_size), Image.NEAREST + ) + image = image.resize( + (image.size[0] * pixel_size, image.size[1] * pixel_size), Image.NEAREST + ) pixel = image.load() # Draw black margin between pixels @@ -26,25 +30,25 @@ def pixelate(image, pixel_size=9, draw_margin=True): for i in range(0, image.size[0], pixel_size): for j in range(0, image.size[1], pixel_size): for r in range(pixel_size): - pixel[i+r, j] = margin_color - pixel[i, j+r] = margin_color + pixel[i + r, j] = margin_color + pixel[i, j + r] = margin_color return image -if __name__ == '__main__': - image = Image.open('image/input.jpg').convert('RGB') +if __name__ == "__main__": + image = Image.open("image/input.jpg").convert("RGB") # image.show() image_pixelate = pixelate(image, draw_margin=False) - image_pixelate.save('image/output_no_margin.jpg') + image_pixelate.save("image/output_no_margin.jpg") # image_pixelate.show() image_pixelate = pixelate(image) - image_pixelate.save('image/output.jpg') + image_pixelate.save("image/output.jpg") # image_pixelate.show() for size in (16, 32, 48): image_pixelate = pixelate(image, pixel_size=size) - image_pixelate.save('image/output_{}.jpg'.format(size)) + image_pixelate.save(f"image/output_{size}.jpg") # image_pixelate.show() diff --git a/pil_pillow__examples/png2jpg/main.py b/pil_pillow__examples/png2jpg/main.py index 9cfc0673a..d0acd602d 100644 --- a/pil_pillow__examples/png2jpg/main.py +++ b/pil_pillow__examples/png2jpg/main.py @@ -1,13 +1,14 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" + + +from PIL import Image file_name = "input.png" -new_file_name = file_name[:-3] + 'jpg' +new_file_name = file_name[:-3] + "jpg" -from PIL import Image img = Image.open(file_name) - img.save(new_file_name, optimize=True, quality=100) diff --git a/pil_pillow__examples/qimage_qpixmap__to__pil_image/main.py b/pil_pillow__examples/qimage_qpixmap__to__pil_image/main.py index a3a1fe53e..48e8cd089 100644 --- a/pil_pillow__examples/qimage_qpixmap__to__pil_image/main.py +++ b/pil_pillow__examples/qimage_qpixmap__to__pil_image/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install Pillow @@ -10,6 +10,7 @@ # pip install pyqt5 from PyQt5.Qt import QImage, QPixmap, QApplication + image_file = "input.jpg" app = QApplication([]) diff --git a/pil_pillow__examples/replace_rgb_by_alpha/main.py b/pil_pillow__examples/replace_rgb_by_alpha/main.py index 3e346db51..a9ff418d5 100644 --- a/pil_pillow__examples/replace_rgb_by_alpha/main.py +++ b/pil_pillow__examples/replace_rgb_by_alpha/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install Pillow @@ -9,7 +9,7 @@ image_file = "input.png" -img = Image.open(image_file).convert('RGBA') +img = Image.open(image_file).convert("RGBA") pixdata = img.load() diff --git a/pil_pillow__examples/resize_width_or_height_with_keep_ratio/input.jpg b/pil_pillow__examples/resize_width_or_height_with_keep_ratio/input.jpg new file mode 100644 index 000000000..2b7999a0f Binary files /dev/null and b/pil_pillow__examples/resize_width_or_height_with_keep_ratio/input.jpg differ diff --git a/pil_pillow__examples/resize_width_or_height_with_keep_ratio/main.py b/pil_pillow__examples/resize_width_or_height_with_keep_ratio/main.py new file mode 100644 index 000000000..52e049336 --- /dev/null +++ b/pil_pillow__examples/resize_width_or_height_with_keep_ratio/main.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +# pip install Pillow +from PIL import Image +from PIL.Image import Resampling + + +def _resize_img( + img: Image, + width: int, + height: int, + resample: Resampling | None = None, +) -> Image: + img_resized = img.resize(size=(width, height), resample=resample) + img_resized.format = img.format # NOTE: resize reset format + return img_resized + + +def resize_height_img( + img: Image, + height: int, + resample: Resampling | None = None, +) -> Image: + """Resize by height, keep ratio.""" + return _resize_img(img, img.width * height // img.height, height, resample) + + +def resize_width_img( + img: Image, + width: int, + resample: Resampling | None = None, +) -> Image: + """Resize by width, keep ratio.""" + return _resize_img(img, width, img.height * width // img.width, resample) + + +if __name__ == "__main__": + file_name = "input.jpg" + img = Image.open(file_name) + print(img.size) + # (660, 438) + + resample = Resampling.LANCZOS + + img_height_300 = resize_height_img(img, height=300, resample=resample) + img_height_300.save("output_height=300.jpg") + print(img_height_300.size) + # (452, 300) + + img_width_300 = resize_width_img(img, width=300, resample=resample) + img_width_300.save("output_width=300.jpg") + print(img_width_300.size) + # (300, 199) diff --git a/pil_pillow__examples/resize_width_or_height_with_keep_ratio/output_height=300.jpg b/pil_pillow__examples/resize_width_or_height_with_keep_ratio/output_height=300.jpg new file mode 100644 index 000000000..fb4a062a2 Binary files /dev/null and b/pil_pillow__examples/resize_width_or_height_with_keep_ratio/output_height=300.jpg differ diff --git a/pil_pillow__examples/resize_width_or_height_with_keep_ratio/output_width=300.jpg b/pil_pillow__examples/resize_width_or_height_with_keep_ratio/output_width=300.jpg new file mode 100644 index 000000000..3381d2a90 Binary files /dev/null and b/pil_pillow__examples/resize_width_or_height_with_keep_ratio/output_width=300.jpg differ diff --git a/pil_pillow__examples/rounded_rectangle.py b/pil_pillow__examples/rounded_rectangle.py index ad836f2c7..57784bfa4 100644 --- a/pil_pillow__examples/rounded_rectangle.py +++ b/pil_pillow__examples/rounded_rectangle.py @@ -1,38 +1,46 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/szabgab/slides/blob/914d15bb55cb0e335afcb72b6af0fa2aa9ed356f/python/examples/pil/draw_rectangle_with_rounded_corners.py -# TODO: append alpha +from PIL import Image, ImageDraw -from PIL import Image, ImageDraw +Color = float | tuple[int, ...] | str -def round_corner(radius, fill): +def round_corner(radius: int, fill: Color) -> Image: """Draw a round corner""" - corner = Image.new('RGB', (radius, radius), (0, 0, 0, 0)) + corner = Image.new("RGBA", (radius, radius), (0, 0, 0, 0)) + draw = ImageDraw.Draw(corner) - draw.pieslice((0, 0, radius * 2, radius * 2), 180, 270, fill=fill) + draw.pieslice((0, 0, radius * 2, radius * 2), start=180, end=270, fill=fill) + return corner -def round_rectangle(size, radius, fill): +def round_rectangle(width: int, height: int, radius: int, fill: Color) -> Image: """Draw a rounded rectangle""" - width, height = size - rectangle = Image.new('RGBA', size, fill) + + rectangle = Image.new("RGBA", (width, height), fill) corner = round_corner(radius, fill) + rectangle.paste(corner, (0, 0)) - rectangle.paste(corner.rotate(90), (0, height - radius)) # Rotate the corner and paste it + + # Rotate the corner and paste it + rectangle.paste(corner.rotate(90), (0, height - radius)) + rectangle.paste(corner.rotate(180), (width - radius, height - radius)) rectangle.paste(corner.rotate(270), (width - radius, 0)) + return rectangle -img = round_rectangle((500, 500), 50, "yellow") -img.show() -img.save('img.png') +if __name__ == "__main__": + img = round_rectangle(width=500, height=500, radius=50, fill="yellow") + img.show() + img.save("img.png") diff --git a/pil_pillow__examples/set_transparent_pixels/main.py b/pil_pillow__examples/set_transparent_pixels/main.py new file mode 100644 index 000000000..491a8f6a2 --- /dev/null +++ b/pil_pillow__examples/set_transparent_pixels/main.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from PIL import Image + + +file_name = "../for_cycle__pixels__array/input.jpg" + +img = Image.open(file_name).convert("RGBA") +pixels = img.load() +width, height = img.size + +for i in range(width): + for j in range(height): + if (j + i) % 2 == 0: + pixels[i, j] = 0, 0, 0, 0 + +img.save("output.png") diff --git a/pil_pillow__examples/simple_draw__hello_world/main.py b/pil_pillow__examples/simple_draw__hello_world/main.py index 26a9a4d30..ba888f3d7 100644 --- a/pil_pillow__examples/simple_draw__hello_world/main.py +++ b/pil_pillow__examples/simple_draw__hello_world/main.py @@ -1,17 +1,18 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install Pillow from PIL import Image, ImageDraw, ImageFont + image = Image.open("images.jpg") font = ImageFont.truetype("arial.ttf", 25) drawer = ImageDraw.Draw(image) -drawer.text((50, 100), "Hello World!\nПривет мир!", font=font, fill='black') +drawer.text((50, 100), "Hello World!\nПривет мир!", font=font, fill="black") -image.save('new_img.jpg') +image.save("new_img.jpg") image.show() diff --git a/pil_pillow__examples/square_to_circle__avatar/main.py b/pil_pillow__examples/square_to_circle__avatar/main.py index 796e98f89..7ad249805 100644 --- a/pil_pillow__examples/square_to_circle__avatar/main.py +++ b/pil_pillow__examples/square_to_circle__avatar/main.py @@ -1,18 +1,19 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PIL import Image, ImageDraw -img = Image.open('input.jpg') + +img = Image.open("input.jpg") bigsize = img.size[0] * 3, img.size[1] * 3 -mask = Image.new('L', bigsize, 0) +mask = Image.new("L", bigsize, 0) draw = ImageDraw.Draw(mask) draw.ellipse((0, 0) + bigsize, fill=255) mask = mask.resize(img.size, Image.ANTIALIAS) img.putalpha(mask) -img.save('output.png') +img.save("output.png") diff --git a/pil_pillow__examples/text_to_pixels/main.py b/pil_pillow__examples/text_to_pixels/main.py index 6f3309b45..959090869 100644 --- a/pil_pillow__examples/text_to_pixels/main.py +++ b/pil_pillow__examples/text_to_pixels/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PIL import Image, ImageDraw @@ -46,7 +46,7 @@ img_size = s break -assert img_size != -1, "Неудалось подобрать размер изображения" +assert img_size != -1, "Не удалось подобрать размер изображения" image = Image.new("RGBA", (img_size, img_size), (0, 0, 0, 0)) draw = ImageDraw.Draw(image) @@ -61,4 +61,4 @@ color = colors[pos] draw.point((x, y), fill=(color, 255, 255)) -image.save('result.png') +image.save("result.png") diff --git a/pil_pillow__examples/thumbnails/main.py b/pil_pillow__examples/thumbnails/main.py index ba5910aeb..668ba6756 100644 --- a/pil_pillow__examples/thumbnails/main.py +++ b/pil_pillow__examples/thumbnails/main.py @@ -1,17 +1,18 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install Pillow from PIL import Image + image_file = "input.jpg" img = Image.open(image_file) size = 128, 128 img.thumbnail(size) -img.save('output_thumbnail.png') +img.save("output_thumbnail.png") img.show() diff --git a/pipette/main.py b/pipette/main.py index addd5d231..ad58972e9 100644 --- a/pipette/main.py +++ b/pipette/main.py @@ -1,23 +1,32 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://habrahabr.ru/post/346146/ +import time + # pip install pyautogui import pyautogui + from graphics import GraphWin, Circle, Point, Entry, color_rgb -import time -win = GraphWin("pipetka", 200, 200, autoflush=True) # создаем графическую форму размером 200х200 и элементы на ней + +# Создаем графическую форму размером 200х200 и элементы на ней +win = GraphWin( + "pipetka", 200, 200, autoflush=True +) x, y = pyautogui.position() # получаем в x, y координаты мыши r, g, b = pyautogui.pixel(x, y) # получаем в r, g, b цвет ColorDot = Circle(Point(100, 100), 25) # создаем точку, отображающую цвет -ColorDot.setFill(color_rgb(r, g, b)) # устанавливает ей заливку из ранее полученных цветов +# Устанавливает ей заливку из ранее полученных цветов +ColorDot.setFill( + color_rgb(r, g, b) +) ColorDot.draw(win) # рисуем на форме win RGBtext = Entry(Point(win.getWidth() / 2, 25), 10) # создаем RGB вывод @@ -37,7 +46,7 @@ ColorDot.setFill(color_rgb(r, g, b)) # Обновляем цвет RGBtext.setText(pyautogui.pixel(x, y)) # Обновляем RGB RGBstring.setText(color_rgb(r, g, b)) # Обновляем web цвет - Coordstring.setText('{} x {}'.format(x, y)) # Обновляем координаты + Coordstring.setText(f"{x} x {y}") # Обновляем координаты if win.isClosed(): break diff --git a/pixelate_image_gui/main.py b/pixelate_image_gui/main.py index 718acb70f..cd121acd3 100644 --- a/pixelate_image_gui/main.py +++ b/pixelate_image_gui/main.py @@ -1,20 +1,28 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import os -from PyQt5 import Qt +import sys +import traceback + from PIL import Image +from PyQt5 import Qt + # SOURCE: https://github.com/gil9red/SimplePyScripts/blob/1c1ad83de7c0f0bd02d9926fe141da4ba5b92720/pil_example/pixelate_image/main.py def pixelate(image, pixel_size=9, draw_margin=True): margin_color = (0, 0, 0) - image = image.resize((image.size[0] // pixel_size, image.size[1] // pixel_size), Image.NEAREST) - image = image.resize((image.size[0] * pixel_size, image.size[1] * pixel_size), Image.NEAREST) + image = image.resize( + (image.size[0] // pixel_size, image.size[1] // pixel_size), Image.NEAREST + ) + image = image.resize( + (image.size[0] * pixel_size, image.size[1] * pixel_size), Image.NEAREST + ) pixel = image.load() # Draw black margin between pixels @@ -22,36 +30,34 @@ def pixelate(image, pixel_size=9, draw_margin=True): for i in range(0, image.size[0], pixel_size): for j in range(0, image.size[1], pixel_size): for r in range(pixel_size): - pixel[i+r, j] = margin_color - pixel[i, j+r] = margin_color + pixel[i + r, j] = margin_color + pixel[i, j + r] = margin_color return image -def log_uncaught_exceptions(ex_cls, ex, tb): - text = '{}: {}:\n'.format(ex_cls.__name__, ex) - import traceback - text += ''.join(traceback.format_tb(tb)) +def log_uncaught_exceptions(ex_cls, ex, tb) -> None: + text = f"{ex_cls.__name__}: {ex}:\n" + text += "".join(traceback.format_tb(tb)) print(text) - Qt.QMessageBox.critical(None, 'Error', text) + Qt.QMessageBox.critical(None, "Error", text) sys.exit(1) -import sys sys.excepthook = log_uncaught_exceptions -WINDOW_TITLE = 'Pixelate' +WINDOW_TITLE = "Pixelate" class MainWindow(Qt.QWidget): - def __init__(self): + def __init__(self) -> None: super().__init__() self.setWindowTitle(WINDOW_TITLE) - self.last_load_path = '.' + self.last_load_path = "." self.file_name = None self.image_source = None self.image_result = None @@ -59,20 +65,20 @@ def __init__(self): self.lb_image_view = Qt.QLabel() self.lb_image_view.setAlignment(Qt.Qt.AlignCenter) - self.pb_load_image = Qt.QPushButton('Load') + self.pb_load_image = Qt.QPushButton("Load") self.pb_load_image.clicked.connect(self.load) - self.pb_save_as = Qt.QPushButton('Save as...') + self.pb_save_as = Qt.QPushButton("Save as...") self.pb_save_as.clicked.connect(self.save_as) - self.rb_original = Qt.QRadioButton('Original') + self.rb_original = Qt.QRadioButton("Original") self.rb_original.clicked.connect(self._do_pixelate) - self.rb_result = Qt.QRadioButton('Result') + self.rb_result = Qt.QRadioButton("Result") self.rb_result.setChecked(True) self.rb_result.clicked.connect(self._do_pixelate) - self.cb_draw_margin = Qt.QCheckBox('Draw margin') + self.cb_draw_margin = Qt.QCheckBox("Draw margin") self.cb_draw_margin.setChecked(True) self.cb_draw_margin.clicked.connect(self._do_pixelate) @@ -81,7 +87,9 @@ def __init__(self): self.sl_pixel_size.setValue(9) self.sb_pixel_size = Qt.QSpinBox() - self.sb_pixel_size.setRange(self.sl_pixel_size.minimum(), self.sl_pixel_size.maximum()) + self.sb_pixel_size.setRange( + self.sl_pixel_size.minimum(), self.sl_pixel_size.maximum() + ) self.sb_pixel_size.setValue(self.sl_pixel_size.value()) self.sl_pixel_size.sliderMoved.connect(self.sb_pixel_size.setValue) @@ -101,7 +109,7 @@ def __init__(self): layout_button.addWidget(self.cb_draw_margin) layout_button_2 = Qt.QHBoxLayout() - layout_button_2.addWidget(Qt.QLabel('Pixel size:')) + layout_button_2.addWidget(Qt.QLabel("Pixel size:")) layout_button_2.addWidget(self.sl_pixel_size) layout_button_2.addWidget(self.sb_pixel_size) @@ -117,30 +125,34 @@ def __init__(self): self._do_pixelate() - def load(self): + def load(self) -> None: image_filters = "Images (*.jpg *.jpeg *.png *.bmp)" - self.file_name = Qt.QFileDialog.getOpenFileName(self, "Load image", self.last_load_path, image_filters)[0] + self.file_name = Qt.QFileDialog.getOpenFileName( + self, "Load image", self.last_load_path, image_filters + )[0] if not self.file_name: return self.last_load_path = Qt.QFileInfo(self.file_name).absolutePath() - self.image_source = Image.open(self.file_name).convert('RGB') + self.image_source = Image.open(self.file_name).convert("RGB") self._do_pixelate() - def save_as(self): + def save_as(self) -> None: image_filters = "Images (*.jpg *.jpeg *.png *.bmp)" # C:\Images\img.png -> C:\Images\img_pixelate.png - file_name = '_pixelate'.join(os.path.splitext(self.file_name)) + file_name = "_pixelate".join(os.path.splitext(self.file_name)) - file_name = Qt.QFileDialog.getSaveFileName(self, "Save as image", file_name, image_filters)[0] + file_name = Qt.QFileDialog.getSaveFileName( + self, "Save as image", file_name, image_filters + )[0] if not file_name: return self.image_result.save(file_name) - def _do_pixelate(self): + def _do_pixelate(self) -> None: # Виджеты будут доступны только если стоит флаг на Result и картинка загружена ok = self.rb_result.isChecked() and self.image_source is not None self.pb_save_as.setEnabled(ok) @@ -157,7 +169,7 @@ def _do_pixelate(self): self.show_result() - def show_result(self): + def show_result(self) -> None: image_result = self.image_result if self.rb_result.isChecked() else self.image_source if not image_result: return @@ -165,20 +177,22 @@ def show_result(self): image_result = image_result.toqpixmap() size = self.lb_image_view.size() - pixmap = image_result.scaled(size, Qt.Qt.KeepAspectRatio, Qt.Qt.SmoothTransformation) + pixmap = image_result.scaled( + size, Qt.Qt.KeepAspectRatio, Qt.Qt.SmoothTransformation + ) self.lb_image_view.setPixmap(pixmap) height, width = self.image_source.size - title = WINDOW_TITLE + '. {}x{} ({}x{}). {}'.format(width, height, size.width(), size.height(), self.file_name) + title = f"{WINDOW_TITLE}. {width}x{height} ({size.width()}x{size.height()}). {self.file_name}" self.setWindowTitle(title) - def resizeEvent(self, e): + def resizeEvent(self, e) -> None: super().resizeEvent(e) self.show_result() -if __name__ == '__main__': +if __name__ == "__main__": app = Qt.QApplication([]) mw = MainWindow() diff --git a/play mp3/pyglet_example.py b/play mp3/pyglet_example.py index 48c44a09e..7ee7eeb93 100644 --- a/play mp3/pyglet_example.py +++ b/play mp3/pyglet_example.py @@ -1,7 +1,11 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" + + +import os +import pyglet # # SIMPLE EXAMPLE @@ -11,10 +15,8 @@ # pyglet.app.run() -def play(file_name): - import pyglet - import os - dll_file_name = os.path.join(os.path.dirname(__file__), 'avbin') +def play(file_name) -> None: + dll_file_name = os.path.join(os.path.dirname(__file__), "avbin") pyglet.lib.load_library(dll_file_name) player = pyglet.media.Player() @@ -23,7 +25,7 @@ def play(file_name): player.play() - def update(dt): + def update(dt) -> None: if not player.playing: # Отпишем функцию, иначе при повторном вызове, иначе # будет двойной вызов при следующем воспроизведении @@ -35,5 +37,5 @@ def update(dt): pyglet.app.run() -if __name__ == '__main__': - play(r'speak.mp3') +if __name__ == "__main__": + play(r"speak.mp3") diff --git a/play_audio/play.py b/play_audio/play.py index 4bed644be..7fc5567e8 100644 --- a/play_audio/play.py +++ b/play_audio/play.py @@ -1,17 +1,18 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -if __name__ == '__main__': - import sys +import os +import sys +import play_audio + + +if __name__ == "__main__": if len(sys.argv) > 1: - import play_audio play_audio.play(sys.argv[1]) - else: - import os file_name = os.path.basename(sys.argv[0]) - print('usage: {} [-h] audio_file_name'.format(file_name)) + print(f"usage: {file_name} [-h] audio_file_name") diff --git a/play_audio/play_audio.py b/play_audio/play_audio.py index 3211fcf9f..a6df2a2f7 100644 --- a/play_audio/play_audio.py +++ b/play_audio/play_audio.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import sys @@ -21,7 +21,7 @@ Phonon.createPath(audio, output) -def play(file_name): +def play(file_name) -> None: audio.setCurrentSource(Phonon.MediaSource(file_name)) audio.play() diff --git a/play_mp3_with_mp3play_module/play.py b/play_mp3_with_mp3play_module/play.py index ce2835749..10ac62ca3 100644 --- a/play_mp3_with_mp3play_module/play.py +++ b/play_mp3_with_mp3play_module/play.py @@ -1,23 +1,24 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" +import time + # https://pypi.python.org/pypi/mp3play/ +import mp3play -def play(filename): - import mp3play +def play(filename) -> None: clip = mp3play.load(filename) clip.play() - import time time.sleep(min(30, clip.seconds())) clip.stop() -if __name__ == '__main__': +if __name__ == "__main__": # TODO: не работает: print 'Error %s for "%s": %s' % (str(err), txt, buf) # ^SyntaxError: invalid syntax - play(r'speak.mp3') + play(r"speak.mp3") diff --git a/play_sound__pyqt/main.py b/play_sound__pyqt/main.py index 8c3899bb5..8229d0c1d 100644 --- a/play_sound__pyqt/main.py +++ b/play_sound__pyqt/main.py @@ -1,12 +1,15 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" +import os + +from multiprocessing import Process + from PyQt5.QtCore import QCoreApplication, QUrl, QTimer from PyQt5.QtMultimedia import QMediaPlaylist, QMediaContent, QMediaPlayer -import os def play(file_name: str): @@ -24,7 +27,7 @@ def play(file_name: str): player.setPlaylist(playlist) player.play() - def tick(): + def tick() -> None: if player.state() == QMediaPlayer.StoppedState: app.quit() @@ -35,24 +38,25 @@ def tick(): app.exec() -def play_async(file_name: str): +def play_async(file_name: str) -> None: # from threading import Thread # thread = Thread(target=play, args=(file_name,)) # thread.start() - from multiprocessing import Process process = Process(target=play, args=(file_name,)) process.start() -if __name__ == '__main__': - file_name = r'..\speak\play_mp3\speak_male.mp3' - +if __name__ == "__main__": + file_name = r"..\speak\play_mp3\speak_male.mp3" play(file_name) import time + play_async(file_name) time.sleep(0.2) + play_async(file_name) time.sleep(0.2) + play_async(file_name) diff --git a/play_sound__pyqt/play.py b/play_sound__pyqt/play.py index dddc6af95..75e100d02 100644 --- a/play_sound__pyqt/play.py +++ b/play_sound__pyqt/play.py @@ -1,17 +1,18 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -if __name__ == '__main__': - import sys +import os +import sys +from main import play + + +if __name__ == "__main__": if len(sys.argv) > 1: - from main import play play(sys.argv[1]) - else: - import os file_name = os.path.basename(sys.argv[0]) - print('usage: {} [-h] audio_file_name'.format(file_name)) + print(f"usage: {file_name} [-h] audio_file_name") diff --git a/play_url_mp3/play_url_mp3.py b/play_url_mp3/play_url_mp3.py index cd49e75fe..8a27d6685 100644 --- a/play_url_mp3/play_url_mp3.py +++ b/play_url_mp3/play_url_mp3.py @@ -1,35 +1,36 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -from urllib.request import urlretrieve -from urllib.parse import urlparse - import os import sys +from urllib.request import urlretrieve +from urllib.parse import urlparse + from PySide.QtGui import * from PySide.phonon import Phonon -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication(sys.argv) audio = Phonon.MediaObject() - audio.setCurrentSource(Phonon.MediaSource('')) + audio.setCurrentSource(Phonon.MediaSource("")) output = Phonon.AudioOutput(Phonon.MusicCategory) Phonon.createPath(audio, output) widget = QWidget() - widget.setWindowTitle('Simple url audio player') + widget.setWindowTitle("Simple url audio player") line_edit_url = QLineEdit() - line_edit_url.setText('https://www.dropbox.com/sh/a4bwzhn0s584u73/AACgDRii1rtCmoL3mXEwlXDga/CallYourName.mp3?dl=1') - + line_edit_url.setText( + "https://www.dropbox.com/sh/a4bwzhn0s584u73/AACgDRii1rtCmoL3mXEwlXDga/CallYourName.mp3?dl=1" + ) - def play(): + def play() -> None: url = line_edit_url.text() # Выдираем из url имя файла @@ -40,17 +41,15 @@ def play(): audio.setCurrentSource(Phonon.MediaSource(file_name)) audio.play() - - play_url = QPushButton('Play') + play_url = QPushButton("Play") play_url.clicked.connect(play) layout = QHBoxLayout() - layout.addWidget(QLabel('Url audio:')) + layout.addWidget(QLabel("Url audio:")) layout.addWidget(line_edit_url) layout.addWidget(play_url) widget.setLayout(layout) widget.show() - sys.exit(app.exec_()) diff --git a/play_url_mp3/setup.py b/play_url_mp3/setup.py index ef4a9cdf4..4b92e25f0 100644 --- a/play_url_mp3/setup.py +++ b/play_url_mp3/setup.py @@ -9,12 +9,11 @@ from cx_Freeze import setup, Executable -executables = [ - Executable('play_url_mp3.py') -] +executables = [Executable("play_url_mp3.py")] -setup(name='play_url_mp3', - version='0.1', - description='play_url_mp3.py', - executables=executables - ) +setup( + name="play_url_mp3", + version="0.1", + description="play_url_mp3.py", + executables=executables, +) diff --git a/playwright__examples/README.md b/playwright__examples/README.md new file mode 100644 index 000000000..b22502b53 --- /dev/null +++ b/playwright__examples/README.md @@ -0,0 +1,15 @@ +Install: +``` +pip install playwright==1.52.0 +``` + +Download browsers: +``` +playwright install +``` + +Download browsers (proxy): +``` +set HTTPS_PROXY=https://: +playwright install +``` diff --git a/playwright__examples/__init__.py b/playwright__examples/__init__.py new file mode 100644 index 000000000..f6a8eac64 --- /dev/null +++ b/playwright__examples/__init__.py @@ -0,0 +1,5 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + diff --git a/playwright__examples/get_cookies.py b/playwright__examples/get_cookies.py new file mode 100644 index 000000000..9af43b8ba --- /dev/null +++ b/playwright__examples/get_cookies.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import time +from playwright.sync_api import sync_playwright + + +with sync_playwright() as p: + for browser_type in [p.chromium, p.firefox, p.webkit]: + browser = browser_type.launch() + page = browser.new_page() + + print(browser_type.name.upper()) + + print("Cookies:", page.context.cookies()) + + for k, v in [ + ("name", 123), + ("foo", "bar"), + ("browser", browser_type.name), + ]: + page.goto(f"https://httpbin.org/cookies/set/{k}/{v}") + time.sleep(1) + + rs = page.goto("https://httpbin.org/cookies") + print("From API:", rs.json()) + + cookies = page.context.cookies() + print(f"Cookies ({len(cookies)}):") + for cookie in cookies: + print(f" {cookie}") + + print() + + browser.close() +""" +CHROMIUM +Cookies: [] +From API: {'cookies': {'browser': 'chromium', 'foo': 'bar', 'name': '123'}} +Cookies (3): + {'name': 'name', 'value': '123', 'domain': 'httpbin.org', 'path': '/', 'expires': -1, 'httpOnly': False, 'secure': False, 'sameSite': 'Lax'} + {'name': 'foo', 'value': 'bar', 'domain': 'httpbin.org', 'path': '/', 'expires': -1, 'httpOnly': False, 'secure': False, 'sameSite': 'Lax'} + {'name': 'browser', 'value': 'chromium', 'domain': 'httpbin.org', 'path': '/', 'expires': -1, 'httpOnly': False, 'secure': False, 'sameSite': 'Lax'} + +FIREFOX +Cookies: [] +From API: {'cookies': {'browser': 'firefox', 'foo': 'bar', 'name': '123'}} +Cookies (3): + {'name': 'name', 'value': '123', 'domain': 'httpbin.org', 'path': '/', 'expires': -1, 'httpOnly': False, 'secure': False, 'sameSite': 'None'} + {'name': 'foo', 'value': 'bar', 'domain': 'httpbin.org', 'path': '/', 'expires': -1, 'httpOnly': False, 'secure': False, 'sameSite': 'None'} + {'name': 'browser', 'value': 'firefox', 'domain': 'httpbin.org', 'path': '/', 'expires': -1, 'httpOnly': False, 'secure': False, 'sameSite': 'None'} + +WEBKIT +Cookies: [] +From API: {'cookies': {}} +Cookies (0): + +""" diff --git a/playwright__examples/get_page_text.py b/playwright__examples/get_page_text.py new file mode 100644 index 000000000..a7e103d4a --- /dev/null +++ b/playwright__examples/get_page_text.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from playwright.sync_api import sync_playwright + + +with sync_playwright() as p: + browser = p.webkit.launch() + page = browser.new_page() + + rs = page.goto("https://httpbin.org/") + print(rs.text()) + + browser.close() diff --git a/playwright__examples/get_screenshot.py b/playwright__examples/get_screenshot.py new file mode 100644 index 000000000..edd891a28 --- /dev/null +++ b/playwright__examples/get_screenshot.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from pathlib import Path +from playwright.sync_api import sync_playwright + +DIR: Path = Path(__file__).resolve().parent +DIR_SCREENSHOTS: Path = DIR / "screenshots" +DIR_SCREENSHOTS.mkdir(parents=True, exist_ok=True) + + +with sync_playwright() as p: + for browser_type in [p.chromium, p.firefox, p.webkit]: + print(browser_type.name) + + browser = browser_type.launch() + page = browser.new_page() + + page.goto("https://github.com/gil9red") + + img_date: bytes = page.screenshot() + print(f"img_date: {img_date[:50]}...") + + path_page: Path = DIR_SCREENSHOTS / f"{browser_type.name}_page.png" + page.screenshot(path=path_page) + print(f"path_page: {path_page}") + + path_full_page: Path = DIR_SCREENSHOTS / f"{browser_type.name}_full_page.png" + page.screenshot(path=path_full_page, full_page=True) + print(f"path_full_page: {path_full_page}") + + print() diff --git a/playwright__examples/get_user_agents.py b/playwright__examples/get_user_agents.py new file mode 100644 index 000000000..2b657fb5b --- /dev/null +++ b/playwright__examples/get_user_agents.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from playwright.sync_api import sync_playwright + + +with sync_playwright() as p: + for browser_type in [p.chromium, p.firefox, p.webkit]: + browser = browser_type.launch() + page = browser.new_page() + + print(browser_type.name.upper()) + print("Local:", page.evaluate("navigator.userAgent")) + + context = browser.new_context(user_agent="Abc 123") + print("Set local:", context.new_page().evaluate("navigator.userAgent")) + + rs = page.goto("https://httpbin.org/user-agent") + print("Remote:", rs.json()["user-agent"]) + + print() + + browser.close() +""" +CHROMIUM +Local: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/131.0.6778.33 Safari/537.36 +Set local: Abc 123 +Remote: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/131.0.6778.33 Safari/537.36 + +FIREFOX +Local: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:132.0) Gecko/20100101 Firefox/132.0 +Set local: Abc 123 +Remote: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:132.0) Gecko/20100101 Firefox/132.0 + +WEBKIT +Local: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.2 Safari/605.1.15 +Set local: Abc 123 +Remote: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.2 Safari/605.1.15 +""" diff --git a/playwright__examples/human_automation/__init__.py b/playwright__examples/human_automation/__init__.py new file mode 100644 index 000000000..3c994a1db --- /dev/null +++ b/playwright__examples/human_automation/__init__.py @@ -0,0 +1,4 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" diff --git a/playwright__examples/human_automation/examples/__init__.py b/playwright__examples/human_automation/examples/__init__.py new file mode 100644 index 000000000..3c994a1db --- /dev/null +++ b/playwright__examples/human_automation/examples/__init__.py @@ -0,0 +1,4 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" diff --git a/playwright__examples/human_automation/examples/github.com_gil9red.py b/playwright__examples/human_automation/examples/github.com_gil9red.py new file mode 100644 index 000000000..dfb200a0f --- /dev/null +++ b/playwright__examples/human_automation/examples/github.com_gil9red.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +# playwright>=1.61.0 +from playwright.sync_api import sync_playwright + +from human_automation.human_automation import HumanAutomation + +URL: str = "https://github.com/gil9red" + + +with sync_playwright() as p: + browser = p.chromium.launch(headless=False) + page = browser.new_page() + + human_automation = HumanAutomation(page) + + page.goto(URL, wait_until="domcontentloaded") + + part_url: str = "tab=repositories" + + def click_on_repositories() -> None: + link_locator = page.locator(f'nav a[href *= "{part_url}"]').first + human_automation.click(link_locator) + + human_automation.ensure_change_url( + action=click_on_repositories, + is_ok_url=lambda url: part_url in url, + ) + + print("\n") + human_automation.wait(300, 600) + + part_url: str = "/SimplePyScripts" + + def find_repository() -> None: + find_selector = 'input[placeholder="Find a repository…"]' + find_locator = page.locator(find_selector) + human_automation.type_text(find_locator, "SimplePyScripts") + + link_selector = ( + f'#user-repositories-list a[itemprop *= "name"][href *= "{part_url}"]' + ) + locator = page.locator(link_selector).first + + human_automation.click(locator) + + human_automation.ensure_change_url( + action=find_repository, + is_ok_url=lambda url: part_url in url, + ) + + human_automation.wait(300, 600) + print("\n") + + human_automation.move_mouse_to_center() + + # Step 1: Go /playwright__examples + part_url: str = "/playwright__examples" + + def click_on_step_1() -> None: + dir_locator = page.get_by_role("link", name=part_url[1:]).first + human_automation.click(dir_locator) + + human_automation.ensure_change_url( + action=click_on_step_1, + is_ok_url=lambda url: part_url in url, + ) + + print("\n") + human_automation.wait(300, 600) + + # Step 2: Go /playwright__examples/screenshots + part_url: str = f"{part_url}/screenshots" + + def click_on_step_2() -> None: + dir_locator = page.locator(f'a[href$="{part_url}"]:visible').first + human_automation.click(dir_locator) + + human_automation.ensure_change_url( + action=click_on_step_2, + is_ok_url=lambda url: url.endswith(part_url), + ) + + print("\n") + human_automation.wait(300, 600) + + # Step 3: Go /playwright__examples/screenshots/stealth + part_url: str = f"{part_url}/stealth" + + def click_on_step_3() -> None: + dir_locator = page.locator(f'a[href$="{part_url}"]:visible').first + human_automation.click(dir_locator) + + human_automation.ensure_change_url( + action=click_on_step_3, + is_ok_url=lambda url: part_url in url, + ) + + print("\n") + human_automation.wait(300, 600) + + # Step 4: Go /playwright__examples/screenshots/stealth/stealth_firefox_page.png + part_url: str = f"{part_url}/stealth_firefox_page.png" + + def click_on_step_4() -> None: + dir_locator = page.locator(f'a[href$="{part_url}"]:visible').first + human_automation.click(dir_locator) + + human_automation.ensure_change_url( + action=click_on_step_4, + is_ok_url=lambda url: part_url in url, + ) + + print("\n") + human_automation.wait(300, 600) + + # Download /playwright__examples/screenshots/stealth/stealth_firefox_page.png + img_locator = page.locator(f'img[src *= "{part_url}"]').first + print(img_locator) + + img_url: str = img_locator.get_attribute("src") + full_url: str = page.evaluate(f"new URL('{img_url}', window.location.href).href") + print(full_url) + + response = page.request.get(full_url) + img_bytes: bytes = response.body() + print(f"img_bytes ({len(img_bytes)} bytes): {img_bytes[:50]}...") + + page.wait_for_timeout(2_000) diff --git a/playwright__examples/human_automation/examples/store_steampowered_com.py b/playwright__examples/human_automation/examples/store_steampowered_com.py new file mode 100644 index 000000000..f5c093484 --- /dev/null +++ b/playwright__examples/human_automation/examples/store_steampowered_com.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +# playwright>=1.61.0 +from playwright.sync_api import sync_playwright + +from human_automation.human_automation import HumanAutomation + +URL: str = "https://store.steampowered.com/" + + +with sync_playwright() as p: + browser = p.chromium.launch(headless=False) + page = browser.new_page() + + human_automation = HumanAutomation(page) + + page.goto(URL, wait_until="domcontentloaded") + + categories_locator = page.get_by_label("Меню магазина").locator( + "button > div:has-text('Категории')" + ) + print(categories_locator) + human_automation.click(categories_locator) + + # View all tags + all_tags_locator = page.locator("a:has-text('Просмотреть все метки')") + print(all_tags_locator) + human_automation.click(all_tags_locator) + + # Free To Play + free_to_play_locator = page.get_by_role("button", name="Бесплатная игра") + print(categories_locator) + human_automation.click(free_to_play_locator) + + go_to_free_to_play_locator = page.get_by_text("Просмотреть все") + print(go_to_free_to_play_locator) + human_automation.click(go_to_free_to_play_locator) + + filters_locator = page.locator("//div[text()='Фильтры']/..").first + print(filters_locator) + human_automation.move_to(filters_locator) + + genres_frame_locator = filters_locator.locator("//div[text()='Жанры']/../..").first + print(genres_frame_locator) + human_automation.move_to(genres_frame_locator) + + genres_locator = genres_frame_locator.locator("//div[text()='Жанры']") + print(genres_locator) + human_automation.click(genres_locator) + + genres_all_locator = genres_frame_locator.locator("//div[text()='Показать больше']") + print(genres_all_locator) + human_automation.click(genres_all_locator) + + genre_shooter_locator = genres_frame_locator.locator("//div/a[text()='Шутер']") + print(genre_shooter_locator) + human_automation.click(genre_shooter_locator) + + sub_genres_frame_locator = filters_locator.locator( + "//div[text()='Поджанры']/../.." + ).first + print(sub_genres_frame_locator) + human_automation.move_to(sub_genres_frame_locator) + + genres_locator = sub_genres_frame_locator.locator("//div[text()='Поджанры']") + print(genres_locator) + human_automation.click(genres_locator) + + sub_genres_all_locator = sub_genres_frame_locator.locator( + "//div[text()='Показать больше']" + ) + print(sub_genres_all_locator) + human_automation.click(sub_genres_all_locator) + + fps_locator = sub_genres_frame_locator.locator( + "//div/a[text()='Шутер от первого лица']" + ) + print(fps_locator) + human_automation.click(fps_locator) + + human_automation.click(page.get_by_role("button", name="Показать больше")) + + human_automation.move_to( + page.locator("a[href *= '/app/'][role='button']:has(img[alt]):visible").first + ) + human_automation.wait(2_000, 4_000) + + for locator in page.locator( + "a[href *= '/app/'][role='button']:has(img[alt]):visible" + ).all(): + print(locator) + + name: str = locator.locator("img[alt]").get_attribute("alt") + URL: str = locator.get_attribute("href") + print(f"{name!r}: {URL}") + + human_automation.move_to(locator) + + print() + + page.wait_for_timeout(5_000) diff --git a/playwright__examples/human_automation/examples/translate_google_ru.py b/playwright__examples/human_automation/examples/translate_google_ru.py new file mode 100644 index 000000000..f3ad0c2bb --- /dev/null +++ b/playwright__examples/human_automation/examples/translate_google_ru.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from textwrap import dedent + +# playwright>=1.61.0 +from playwright.sync_api import Page, sync_playwright + +from human_automation.human_automation import HumanAutomation + + +def get_text(page: Page, lang: str) -> str: + return "\n".join( + span.text_content().strip() + for span in page.locator(f'span[lang="{lang}"] > span > span').all() + ) + + +URL: str = "https://translate.google.ru/?sl=ru&tl=en&op=translate" + + +with sync_playwright() as p: + browser = p.chromium.launch(headless=False) + page = browser.new_page() + + human_automation = HumanAutomation(page) + + page.goto(URL, wait_until="domcontentloaded") + + input_locator = page.locator('textarea[aria-label="Исходный текст"]') + + human_automation.type_text( + input_locator, + text=dedent(""" + Съешь же ещё этих мягких + французских булок, + да выпей же чаю + """).strip(), + ) + + page.wait_for_timeout(2_000) + + text: str = get_text(page, lang="en") + print(f"Text ({len(text)}):\n{text}") + + page.wait_for_timeout(2_000) + + human_automation.click('button[aria-label *= "Обратный перевод"]:visible') + + page.wait_for_timeout(2_000) + + human_automation.type_text( + input_locator, + text=dedent(""" + The quick brown fox jumps + over the lazy dog + """).strip(), + ) + + page.wait_for_timeout(2_000) + + text: str = get_text(page, lang="ru") + print(f"Text ({len(text)}):\n{text}") + + page.wait_for_timeout(5_000) diff --git a/playwright__examples/human_automation/human_automation.py b/playwright__examples/human_automation/human_automation.py new file mode 100644 index 000000000..85c5bcd23 --- /dev/null +++ b/playwright__examples/human_automation/human_automation.py @@ -0,0 +1,421 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import functools +import logging +import time +import random +import re +import string + +from typing import Any, Callable, TypedDict + +# playwright>=1.61.0 +from playwright.sync_api import Page, Locator, TimeoutError + +# playwright-stealth>=2.0.3 +from playwright_stealth import Stealth + +# python-ghost-cursor==0.1.1 +from python_ghost_cursor.playwright_sync import create_cursor +from python_ghost_cursor.playwright_sync._spoof import GhostCursor + +type Point = TypedDict("Point", {"x": int, "y": int}) + + +log = logging.getLogger(__name__) +logging.basicConfig(level=logging.INFO) + + +def log_action[**P, R](func: Callable[P, R]) -> Callable[P, R]: + @functools.wraps(func) + def wrapper(*args: P.args, **kwargs: P.kwargs) -> R: + func_name: str = func.__name__ + + locator: Any | None = kwargs.get("locator") + if not locator and len(args) > 1: + locator = args[1] + if not isinstance(locator, str | Locator): + locator = None + locator_str: str = f" to: {locator}" if locator else "" + + log.debug(f"Started {func_name}{locator_str}") + + start_time: float = time.perf_counter() + try: + result: R = func(*args, **kwargs) + + execution_time_ms: int = int((time.perf_counter() - start_time) * 1000) + log.debug(f"Finished {func_name}{locator_str} ({execution_time_ms}ms)") + return result + + except Exception as e: + execution_time_ms: int = int((time.perf_counter() - start_time) * 1000) + log.error( + f"Failed {func_name}{locator_str} after {execution_time_ms}ms. Error: {e}" + ) + raise e + + return wrapper + + +stealth = Stealth() + + +def get_center_page(page: Page) -> Point: + # Get the size of the current browser window + viewport: dict[str, int] | None = page.viewport_size + + base_x: int + base_y: int + if viewport: + base_x = viewport["width"] // 2 + base_y = viewport["height"] // 2 + else: + base_x, base_y = 640, 360 + + offset_x: int = random.randint(-40, 40) + offset_y: int = random.randint(-40, 40) + + target_x: int = base_x + offset_x + target_y: int = base_y + offset_y + + return {"x": target_x, "y": target_y} + + +def inject_cursor(page: Page) -> None: + page.add_init_script(""" + (() => { + const initCursor = () => { + const box = document.createElement('div'); + box.id = 'playwright-fake-cursor'; + box.style.position = 'fixed'; + box.style.top = '0'; + box.style.left = '0'; + box.style.width = '14px'; + box.style.height = '14px'; + box.style.background = 'rgba(255, 0, 0, 0.7)'; + box.style.border = '2px solid white'; + box.style.borderRadius = '50%'; + box.style.pointerEvents = 'none'; + box.style.zIndex = '999999'; + box.style.transform = 'translate(-50%, -50%)'; + document.body.appendChild(box); + + document.addEventListener('mousemove', (e) => { + box.style.left = e.clientX + 'px'; + box.style.top = e.clientY + 'px'; + }); + }; + + // If the DOM has already been loaded (unlikely for init_script, but useful for safety) + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', initCursor); + } else { + initCursor(); + } + })(); + """) + + +TYPO_CHAR_SETS: dict[str, str] = { + "eng_lower": string.ascii_lowercase, + "eng_upper": string.ascii_uppercase, + "rus_lower": "абвгдеёжзийклмнопрстуфхцчшщъыьэюя", + "rus_upper": "АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ", + "digits": string.digits, + "punctuation": string.punctuation, +} + +WAIT_MS: tuple[int, int] = 100, 250 + + +class HumanAutomation: + page: Page + cursor: GhostCursor + typo_char_sets: dict[str, str] + + def __init__( + self, + page: Page, + typo_char_sets: dict[str, str] | None = None, + ) -> None: + if typo_char_sets is None: + typo_char_sets = TYPO_CHAR_SETS + + self.page = page + self.cursor = create_cursor(page, start=get_center_page(page)) + self.typo_char_sets = typo_char_sets + + stealth.apply_stealth_sync(page) + inject_cursor(page) + + @log_action + def wait(self, min_time_ms: int, max_time_ms: int) -> None: + time_ms: float = random.randint(min_time_ms, max_time_ms) + self.page.wait_for_timeout(time_ms) + + @log_action + def move_to( + self, + locator: Locator | str, + wait_ms: tuple[int, int] = WAIT_MS, + ) -> None: + if isinstance(locator, str): + locator = self.page.locator(locator) + + self.scroll_to_element(locator) + + self.cursor.move(locator) + self.wait(*wait_ms) + + @log_action + def click( + self, + locator: Locator | str, + wait_ms: tuple[int, int] = WAIT_MS, + ) -> None: + if isinstance(locator, str): + locator = self.page.locator(locator) + + # NOTE: move_to is not suitable here - the cursor moves twice + # It internally calls cursor.move, which moves the cursor + # click internally calls cursor.click, which also calls cursor.move + self.scroll_to_element(locator) + + # NOTE: If this is a link, removing the 'target' attribute will prevent opening in a new tab + locator.evaluate("el => el.removeAttribute('target')") + + self.cursor.click(locator) + self.wait(*wait_ms) + + @log_action + def scroll_to_element( + self, + locator: Locator | str, + margin: int = 100, + wait_ms: tuple[int, int] = WAIT_MS, + ) -> None: + """ + Simulates human scrolling. + If the element is fully visible on the screen, no scrolling occurs. + If the element is hidden (at the top or bottom), it will smoothly scroll closer to the center. + """ + + if isinstance(locator, str): + locator = self.page.locator(locator) + + while True: + box: dict[str, float] | None + try: + box = locator.bounding_box(timeout=100) + except TimeoutError: + box = None + + log.debug(f"Box: {box}") + if not box: + # If the element is not in the DOM at all, scroll down randomly + self.page.mouse.wheel(0, random.randint(250, 450)) + self.wait(*wait_ms) + continue + + viewport_height: int = self.page.viewport_size["height"] + log.debug(f"Viewport height: {viewport_height}") + + # Find the boundaries of the element relative to the screen + element_top = box["y"] + element_height = box["height"] + element_bottom = element_top + element_height + + is_top_visible = element_top >= margin + is_bottom_within_viewport = element_bottom <= (viewport_height - margin) + is_too_large = element_height > (viewport_height - (margin * 2)) + is_bottom_visible = is_bottom_within_viewport or is_too_large + if is_top_visible and is_bottom_visible: # The element is perfectly visible + break + + # If the element is not on the screen, calculate the distance to center for scrolling + element_center_y = element_top + (element_height / 2) + screen_center_y = viewport_height / 2 + distance_to_center = element_center_y - screen_center_y + + # If we are already very close to the center, stop + if abs(distance_to_center) < random.randint(40, 80): + break + + scroll_step: int + if distance_to_center > 0: + # Scroll down (target below screen) + scroll_step = random.randint(150, 350) + else: + # Scroll up (target above screen) + scroll_step = random.randint(-350, -150) + + self.page.mouse.wheel(0, scroll_step) + self.wait(*wait_ms) + + self.wait(*wait_ms) + + def _typo_effect( + self, + char: str, + typo_chance: float, + wait_ms: tuple[int, int] = WAIT_MS, + ) -> None: + # Logic for random typo (don't break spaces) + if char == " " or random.random() >= typo_chance: + return + + pool = "" + for s_set in self.typo_char_sets.values(): + if char in s_set: + pool = s_set + break + + # If the symbol is rare and is not in the pools, we create a default list (letters + numbers) + if not pool: + pool: str = self.typo_char_sets["eng_lower"] + self.typo_char_sets["digits"] + + valid_choices: list[str] = [c for c in pool if c != char] + if not valid_choices: + log.warning(f"No valid choices found for character {char!r}.") + return + + wrong_char: str = random.choice(valid_choices) + + self.page.keyboard.type(wrong_char) + self.wait(60, 140) + self.wait(180, 350) # Pause for recognizing the error + self.page.keyboard.press("Backspace") + self.wait(*wait_ms) + + @log_action + def type_text( + self, + locator: Locator | str, + text: str, + typo_chance: float = 0.07, + max_attempts: int = 3, + strict_validation: bool = False, + wait_ms: tuple[int, int] = WAIT_MS, + ) -> bool: + """ + Types text like a human: with unique pauses, random typos, + deletion of them through Backspace and final validation of the input value. + """ + + if isinstance(locator, str): + locator = self.page.locator(locator) + + log.info(f"Typing text ({len(text)}): {text!r}") + + for attempt in range(1, max_attempts + 1): + # Click on the field to bring focus + self.click(locator) + + # Realistically clear the field through keyboard emulation of Ctrl+A -> Backspace + self.page.keyboard.down("Control") + self.page.keyboard.press("a") + self.page.keyboard.up("Control") + self.page.keyboard.press("Backspace") + self.wait(*wait_ms) + + # Typing character by character + for char in text: + self._typo_effect(char, typo_chance=typo_chance, wait_ms=wait_ms) + + # Type correct character + self.page.keyboard.type(char) + + # Typing rhythm (delay between keys) + min_time_ms: int = 50 + max_time_ms: int = 160 + # 4% chance that the person thought about a word in the middle of it + if random.random() < 0.04: + min_time_ms += 350 + max_time_ms += 700 + self.wait(min_time_ms, max_time_ms) + + # Validate the actually typed text in the field + actual_text: str = locator.input_value() or "" + + is_valid: bool + if strict_validation: + is_valid = actual_text == text + else: + + def _remove_all_spaces(text: str) -> str: + return re.sub(r"\s+", "", text) + + is_valid = _remove_all_spaces(actual_text) == _remove_all_spaces(text) + + if is_valid: + return True + + log.warning(f"Expected text {text!r}, but got {actual_text!r}.") + + self.wait(400, 800) + + raise RuntimeError( + f"Failed to correctly type {text!r} after {max_attempts} attempts." + ) + + @log_action + def move_mouse_to_center(self) -> None: + """ + Smoothly moves the mouse cursor to a random area around the center of the screen. + Takes: page (a Playwright page object) + """ + + # Move the cursor along a human-like trajectory + point: Point = get_center_page(self.page) + self.cursor.move_to(point) + + # Simulate a small pause after movement (200-600 ms) + self.wait(200, 600) + + @log_action + def ensure_change_url( + self, + action: Callable[[], None], + is_ok_url: Callable[[str], bool], + max_attempts: int = 3, + ) -> None: + prev_url: str = self.page.url + log.info(f"Current URL: {prev_url!r}") + + attempt: int = 0 + while True: + # If the URL substring is found and the URL has changed + if is_ok_url(self.page.url) and prev_url != self.page.url: + break + + try: + action() + + log.info("Waiting for url load") + self.page.wait_for_url( + is_ok_url, timeout=5_000, wait_until="domcontentloaded" + ) + log.info(f"Page changed! New URL: {self.page.url!r}") + + break + + except Exception: + log.exception("Error:") + log.info("Trying to move the cursor and retry") + attempt += 1 + self.cursor.move_to( + { + "x": 10 + random.randint(-10, 10), + "y": 500 + random.randint(-100, 100), + } + ) + if attempt == max_attempts: + log.info(f"Loading {prev_url!r}") + attempt = 0 + self.page.goto(prev_url) + self.wait(4_000, 5_000) diff --git a/playwright__examples/human_automation/screenshots/github.com_gil9red.gif b/playwright__examples/human_automation/screenshots/github.com_gil9red.gif new file mode 100644 index 000000000..14df4e414 Binary files /dev/null and b/playwright__examples/human_automation/screenshots/github.com_gil9red.gif differ diff --git a/playwright__examples/human_automation/screenshots/store_steampowered_com.gif b/playwright__examples/human_automation/screenshots/store_steampowered_com.gif new file mode 100644 index 000000000..eaa969fa6 Binary files /dev/null and b/playwright__examples/human_automation/screenshots/store_steampowered_com.gif differ diff --git a/playwright__examples/human_automation/screenshots/translate_google_ru.gif b/playwright__examples/human_automation/screenshots/translate_google_ru.gif new file mode 100644 index 000000000..7d25d9d64 Binary files /dev/null and b/playwright__examples/human_automation/screenshots/translate_google_ru.gif differ diff --git a/playwright__examples/parse_with_bs4.py b/playwright__examples/parse_with_bs4.py new file mode 100644 index 000000000..4e21a8a67 --- /dev/null +++ b/playwright__examples/parse_with_bs4.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from bs4 import BeautifulSoup +from playwright.sync_api import sync_playwright + + +with sync_playwright() as p: + browser = p.firefox.launch() + page = browser.new_page() + + rs = page.goto("https://httpbin.org/") + text = rs.text() + browser.close() + + soup = BeautifulSoup(text, "html.parser") + print(soup.select_one("a.github-corner")["href"]) + print(soup.select_one(".description").get_text(strip=True, separator="\n")) + """ + https://github.com/requests/httpbin + A simple HTTP Request & Response Service. + Run locally: + $ docker run -p 80:80 kennethreitz/httpbin + """ diff --git a/playwright__examples/print_executable_path.py b/playwright__examples/print_executable_path.py new file mode 100644 index 000000000..fb81bd4be --- /dev/null +++ b/playwright__examples/print_executable_path.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from playwright.sync_api import sync_playwright + + +with sync_playwright() as p: + for browser_type in [p.chromium, p.firefox, p.webkit]: + print(browser_type.name, browser_type.executable_path) +r""" +chromium C:\Users\ipetrash\AppData\Local\ms-playwright\chromium-1148\chrome-win\chrome.exe +firefox C:\Users\ipetrash\AppData\Local\ms-playwright\firefox-1466\firefox\firefox.exe +webkit C:\Users\ipetrash\AppData\Local\ms-playwright\webkit-2104\Playwright.exe +""" diff --git a/playwright__examples/screenshots/chromium_full_page.png b/playwright__examples/screenshots/chromium_full_page.png new file mode 100644 index 000000000..547400daa Binary files /dev/null and b/playwright__examples/screenshots/chromium_full_page.png differ diff --git a/playwright__examples/screenshots/chromium_page.png b/playwright__examples/screenshots/chromium_page.png new file mode 100644 index 000000000..632b26e62 Binary files /dev/null and b/playwright__examples/screenshots/chromium_page.png differ diff --git a/playwright__examples/screenshots/firefox_full_page.png b/playwright__examples/screenshots/firefox_full_page.png new file mode 100644 index 000000000..5a2ffe6b0 Binary files /dev/null and b/playwright__examples/screenshots/firefox_full_page.png differ diff --git a/playwright__examples/screenshots/firefox_page.png b/playwright__examples/screenshots/firefox_page.png new file mode 100644 index 000000000..611bc0d79 Binary files /dev/null and b/playwright__examples/screenshots/firefox_page.png differ diff --git a/playwright__examples/screenshots/stealth/normal_chromium_page.png b/playwright__examples/screenshots/stealth/normal_chromium_page.png new file mode 100644 index 000000000..0e2e6c809 Binary files /dev/null and b/playwright__examples/screenshots/stealth/normal_chromium_page.png differ diff --git a/playwright__examples/screenshots/stealth/normal_firefox_page.png b/playwright__examples/screenshots/stealth/normal_firefox_page.png new file mode 100644 index 000000000..f25c69aa1 Binary files /dev/null and b/playwright__examples/screenshots/stealth/normal_firefox_page.png differ diff --git a/playwright__examples/screenshots/stealth/normal_webkit_page.png b/playwright__examples/screenshots/stealth/normal_webkit_page.png new file mode 100644 index 000000000..53f89ea02 Binary files /dev/null and b/playwright__examples/screenshots/stealth/normal_webkit_page.png differ diff --git a/playwright__examples/screenshots/stealth/stealth_chromium_page.png b/playwright__examples/screenshots/stealth/stealth_chromium_page.png new file mode 100644 index 000000000..36d09711d Binary files /dev/null and b/playwright__examples/screenshots/stealth/stealth_chromium_page.png differ diff --git a/playwright__examples/screenshots/stealth/stealth_firefox_page.png b/playwright__examples/screenshots/stealth/stealth_firefox_page.png new file mode 100644 index 000000000..976fe5b9b Binary files /dev/null and b/playwright__examples/screenshots/stealth/stealth_firefox_page.png differ diff --git a/playwright__examples/screenshots/stealth/stealth_webkit_page.png b/playwright__examples/screenshots/stealth/stealth_webkit_page.png new file mode 100644 index 000000000..0c92edfc2 Binary files /dev/null and b/playwright__examples/screenshots/stealth/stealth_webkit_page.png differ diff --git a/playwright__examples/screenshots/webkit_full_page.png b/playwright__examples/screenshots/webkit_full_page.png new file mode 100644 index 000000000..5cd17dee8 Binary files /dev/null and b/playwright__examples/screenshots/webkit_full_page.png differ diff --git a/playwright__examples/screenshots/webkit_page.png b/playwright__examples/screenshots/webkit_page.png new file mode 100644 index 000000000..68cc4d083 Binary files /dev/null and b/playwright__examples/screenshots/webkit_page.png differ diff --git a/playwright__examples/stealth.py b/playwright__examples/stealth.py new file mode 100644 index 000000000..395e6ba4b --- /dev/null +++ b/playwright__examples/stealth.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from pathlib import Path + +from playwright.sync_api import sync_playwright +from playwright_stealth import Stealth + +DIR: Path = Path(__file__).resolve().parent +DIR_SCREENSHOTS: Path = DIR / "screenshots" / "stealth" +DIR_SCREENSHOTS.mkdir(parents=True, exist_ok=True) + +URL: str = "https://bot.sannysoft.com/" + +stealth = Stealth() + + +with sync_playwright() as p: + for browser_type in [p.chromium, p.firefox, p.webkit]: + print(browser_type.name) + + browser = browser_type.launch() + page = browser.new_page() + + page.goto(URL) + print("navigator.webdriver (default):", page.evaluate("navigator.webdriver")) + + normal_screenshot_path: Path = ( + DIR_SCREENSHOTS / f"normal_{browser_type.name}_page.png" + ) + page.screenshot(path=normal_screenshot_path) + + stealth.apply_stealth_sync(page) + page.goto(URL) + print("navigator.webdriver (stealth):", page.evaluate("navigator.webdriver")) + + stealth_screenshot_path: Path = ( + DIR_SCREENSHOTS / f"stealth_{browser_type.name}_page.png" + ) + page.screenshot(path=stealth_screenshot_path) + + browser.close() # NOTE: Close in loop + print() + +""" +chromium +navigator.webdriver (default): True +navigator.webdriver (stealth): False + +firefox +navigator.webdriver (default): True +navigator.webdriver (stealth): False + +webkit +navigator.webdriver (default): True +navigator.webdriver (stealth): False +""" diff --git a/plural_days.py b/plural_days.py index d3ce2d3be..66dbba1f9 100644 --- a/plural_days.py +++ b/plural_days.py @@ -1,11 +1,11 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" def plural_days(n: int) -> str: - days = ['день', 'дня', 'дней'] + days = ["день", "дня", "дней"] if n % 10 == 1 and n % 100 != 11: p = 0 @@ -14,9 +14,9 @@ def plural_days(n: int) -> str: else: p = 2 - return f'{n} {days[p]}' + return f"{n} {days[p]}" -if __name__ == '__main__': +if __name__ == "__main__": for i in range(100 + 1): print(plural_days(i)) diff --git a/png2svg.py b/png2svg.py index 2613b31ba..3b5745afc 100644 --- a/png2svg.py +++ b/png2svg.py @@ -1,13 +1,12 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import os.path import sys - try: from PyQt5.QtWidgets import QApplication from PyQt5.QtGui import QPainter, QImage @@ -26,23 +25,21 @@ from PySide.QtSvg import QSvgGenerator -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication(sys.argv) - file_name = r'C:\Users\ipetrash\Desktop\explorer.png' + file_name = r"C:\Users\ipetrash\Desktop\explorer.png" im = QImage() im.load(file_name) w, h = im.size().width(), im.size().height() gen = QSvgGenerator() - gen.setFileName(os.path.splitext(file_name)[0] + '.svg') + gen.setFileName(os.path.splitext(file_name)[0] + ".svg") gen.setSize(im.size()) gen.setViewBox(QRect(0, 0, w, h)) painter = QPainter() painter.begin(gen) - painter.drawImage(QPointF(0, 0), - im, - QRectF(0, 0, w, h)) + painter.drawImage(QPointF(0, 0), im, QRectF(0, 0, w, h)) painter.end() diff --git a/pretty_html/pretty_html.py b/pretty_html/pretty_html.py index 0163ea195..e2207e771 100644 --- a/pretty_html/pretty_html.py +++ b/pretty_html/pretty_html.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import re @@ -10,11 +10,11 @@ # SOURCE: https://stackoverflow.com/a/15513483/5909792 orig_prettify = BeautifulSoup.prettify -r = re.compile(r'^(\s*)', re.MULTILINE) +r = re.compile(r"^(\s*)", re.MULTILINE) def prettify(self, encoding=None, formatter="minimal", indent_width=4): - return r.sub(r'\1' * indent_width, orig_prettify(self, encoding, formatter)) + return r.sub(r"\1" * indent_width, orig_prettify(self, encoding, formatter)) BeautifulSoup.prettify = prettify @@ -26,17 +26,17 @@ def process_html_string(text: str) -> str: """ - start = text.index('<') - end = text.rindex('>') - return text[start:end + 1] + start = text.index("<") + end = text.rindex(">") + return text[start : end + 1] def pretty_html(text: str) -> str: text = process_html_string(text) - root = BeautifulSoup(text, 'html.parser') + root = BeautifulSoup(text, "html.parser") return root.prettify() -if __name__ == '__main__': - text = '

' +if __name__ == "__main__": + text = "

" print(pretty_html(text)) diff --git a/pretty_html/pretty_html_gui.py b/pretty_html/pretty_html_gui.py index 184374e6c..97317a60a 100644 --- a/pretty_html/pretty_html_gui.py +++ b/pretty_html/pretty_html_gui.py @@ -1,44 +1,75 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -from os.path import split as path_split -import traceback import sys +import traceback + +from os.path import split as path_split try: from PyQt5.QtWidgets import ( - QWidget, QVBoxLayout, QPlainTextEdit, QLabel, QSizePolicy, QPushButton, - QSplitter, QHBoxLayout, QMessageBox, QErrorMessage, QTextEdit, QApplication + QWidget, + QVBoxLayout, + QPlainTextEdit, + QLabel, + QSizePolicy, + QPushButton, + QSplitter, + QHBoxLayout, + QMessageBox, + QErrorMessage, + QTextEdit, + QApplication, ) from PyQt5.QtCore import Qt except: try: from PyQt4.QtGui import ( - QWidget, QVBoxLayout, QPlainTextEdit, QLabel, QSizePolicy, QPushButton, - QSplitter, QHBoxLayout, QMessageBox, QErrorMessage, QTextEdit, QApplication + QWidget, + QVBoxLayout, + QPlainTextEdit, + QLabel, + QSizePolicy, + QPushButton, + QSplitter, + QHBoxLayout, + QMessageBox, + QErrorMessage, + QTextEdit, + QApplication, ) from PyQt4.QtCore import Qt except: from PySide.QtGui import ( - QWidget, QVBoxLayout, QPlainTextEdit, QLabel, QSizePolicy, QPushButton, - QSplitter, QHBoxLayout, QMessageBox, QErrorMessage, QTextEdit, QApplication + QWidget, + QVBoxLayout, + QPlainTextEdit, + QLabel, + QSizePolicy, + QPushButton, + QSplitter, + QHBoxLayout, + QMessageBox, + QErrorMessage, + QTextEdit, + QApplication, ) from PySide.QtCore import Qt from pretty_html import pretty_html -def log_uncaught_exceptions(ex_cls, ex, tb): - text = '{}: {}:\n'.format(ex_cls.__name__, ex) - text += ''.join(traceback.format_tb(tb)) +def log_uncaught_exceptions(ex_cls, ex, tb) -> None: + text = f"{ex_cls.__name__}: {ex}:\n" + text += "".join(traceback.format_tb(tb)) print(text) - QMessageBox.critical(None, 'Error', text) + QMessageBox.critical(None, "Error", text) sys.exit(1) @@ -46,7 +77,7 @@ def log_uncaught_exceptions(ex_cls, ex, tb): class MainWindow(QWidget): - def __init__(self): + def __init__(self) -> None: super().__init__() self.setWindowTitle(path_split(__file__)[1]) @@ -61,9 +92,9 @@ def __init__(self): self.label_error.setTextInteractionFlags(Qt.TextSelectableByMouse) self.label_error.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) - self.button_detail_error = QPushButton('...') + self.button_detail_error = QPushButton("...") self.button_detail_error.setFixedSize(20, 20) - self.button_detail_error.setToolTip('Detail error') + self.button_detail_error.setToolTip("Detail error") self.button_detail_error.hide() self.last_error_message = None @@ -87,7 +118,7 @@ def __init__(self): self.setLayout(layout) - def input_text_changed(self): + def input_text_changed(self) -> None: self.label_error.clear() self.button_detail_error.hide() @@ -110,13 +141,13 @@ def input_text_changed(self): self.last_detail_error_message = str(tb) self.button_detail_error.show() - self.label_error.setText('Error: ' + self.last_error_message) + self.label_error.setText("Error: " + self.last_error_message) - def show_detail_error_message(self): - message = self.last_error_message + '\n\n' + self.last_detail_error_message + def show_detail_error_message(self) -> None: + message = self.last_error_message + "\n\n" + self.last_detail_error_message mb = QErrorMessage() - mb.setWindowTitle('Error') + mb.setWindowTitle("Error") # Сообщение ошибки содержит отступы, символы-переходы на следующую строку, # которые поломаются при вставке через QErrorMessage.showMessage, и нет возможности # выбрать тип текста, то делаем такой хак. @@ -125,7 +156,7 @@ def show_detail_error_message(self): mb.exec_() -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) mw = MainWindow() @@ -133,9 +164,9 @@ def show_detail_error_message(self): mw.show() mw.text_edit_input.setPlainText( - 'Example' - '

This is an example of a simple HTML page with one paragraph.

' - '' + "Example" + "

This is an example of a simple HTML page with one paragraph.

" + "" ) app.exec_() diff --git a/pretty_money_format.py b/pretty_money_format.py index 380068b12..da8159729 100644 --- a/pretty_money_format.py +++ b/pretty_money_format.py @@ -1,28 +1,29 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" + + +import re def pretty_money_format(money) -> str: money = str(money) + money = re.sub(r"[^.,\d]", "", money) - import re - money = re.sub(r'[^.,\d]', '', money) - - if money.count('.') + money.count(',') > 1: - raise Exception('Invalid money format: "{}".'.format(money)) + if money.count(".") + money.count(",") > 1: + raise Exception(f'Invalid money format: "{money}".') - money_sep = '' - if money.count('.'): - money_sep = '.' - elif money.count(','): - money_sep = ',' + money_sep = "" + if money.count("."): + money_sep = "." + elif money.count(","): + money_sep = "," if money_sep: major, minor = money.split(money_sep) else: - major, minor = money, '' + major, minor = money, "" if len(major) > 3: new_major = list() @@ -36,29 +37,29 @@ def pretty_money_format(money) -> str: if i > 0 and i % 3 == 0: new_major.append(" ") - major = ''.join(reversed(new_major)) + major = "".join(reversed(new_major)) money = major + money_sep + minor return money.strip() -if __name__ == '__main__': - assert pretty_money_format(1000) == '1 000' - assert pretty_money_format(1000.) == '1 000.0' - assert pretty_money_format(1000000) == '1 000 000' - assert pretty_money_format(10000.56) == '10 000.56' - assert pretty_money_format(1000000.0) == '1 000 000.0' - assert pretty_money_format(100000) == '100 000' - - assert pretty_money_format('1000') == '1 000' - assert pretty_money_format('1000.') == '1 000.' - assert pretty_money_format('1000000') == '1 000 000' - assert pretty_money_format('10000.50') == '10 000.50' - assert pretty_money_format('1000000.0') == '1 000 000.0' - assert pretty_money_format('100000') == '100 000' - - assert pretty_money_format('10 00') == '1 000' - assert pretty_money_format('100 00 00') == '1 000 000' - assert pretty_money_format('1 0000.50$') == '10 000.50' - assert pretty_money_format('100 00 00.0') == '1 000 000.0' - assert pretty_money_format('100!000#') == '100 000' +if __name__ == "__main__": + assert pretty_money_format(1000) == "1 000" + assert pretty_money_format(1000.0) == "1 000.0" + assert pretty_money_format(1000000) == "1 000 000" + assert pretty_money_format(10000.56) == "10 000.56" + assert pretty_money_format(1000000.0) == "1 000 000.0" + assert pretty_money_format(100000) == "100 000" + + assert pretty_money_format("1000") == "1 000" + assert pretty_money_format("1000.") == "1 000." + assert pretty_money_format("1000000") == "1 000 000" + assert pretty_money_format("10000.50") == "10 000.50" + assert pretty_money_format("1000000.0") == "1 000 000.0" + assert pretty_money_format("100000") == "100 000" + + assert pretty_money_format("10 00") == "1 000" + assert pretty_money_format("100 00 00") == "1 000 000" + assert pretty_money_format("1 0000.50$") == "10 000.50" + assert pretty_money_format("100 00 00.0") == "1 000 000.0" + assert pretty_money_format("100!000#") == "100 000" diff --git a/pretty_xml/pretty_xml.py b/pretty_xml/pretty_xml.py index 21a00e7a2..76f734fe2 100644 --- a/pretty_xml/pretty_xml.py +++ b/pretty_xml/pretty_xml.py @@ -1,42 +1,67 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -def process_xml_string(xml_string): +from xml.dom.minidom import parseString +from lxml import etree + + +def process_xml_string(xml_string: str) -> str: """ Функция из текста выдирает строку с xml -- она должна начинаться на < и заканчиваться > """ - start = xml_string.index('<') - end = xml_string.rindex('>') - return xml_string[start:end + 1] + start = xml_string.index("<") + end = xml_string.rindex(">") + return xml_string[start : end + 1] -def pretty_xml_minidom(xml_string, ind=2): +def pretty_xml_minidom(xml_string: str, indent: int = 4) -> str: """Функция принимает строку xml и выводит xml с отступами.""" - from xml.dom.minidom import parseString + xml_string: str = process_xml_string(xml_string) - xml_string = process_xml_string(xml_string) - xml_utf8 = parseString(xml_string).toprettyxml(indent=' ' * ind, encoding='utf-8') - return xml_utf8.decode('utf-8') + xml_utf8 = parseString(xml_string).toprettyxml( + indent=" " * indent, + encoding="utf-8", + ) + return xml_utf8.decode("utf-8") -def pretty_xml_lxml(xml_string): +def pretty_xml_lxml(xml_string: str) -> str: """Функция принимает строку xml и выводит xml с отступами.""" - from lxml import etree + xml_string: str = process_xml_string(xml_string) + + root = etree.fromstring( + text=xml_string, + parser=etree.XMLParser( + remove_blank_text=True, + # NOTE: Для обработки больших XML + huge_tree=True, + ), + ) - xml_string = process_xml_string(xml_string) - root = etree.fromstring(xml_string) - return etree.tostring(root, pretty_print=True, encoding='utf-8').decode('utf-8') + return etree.tostring( + root, + pretty_print=True, + encoding="utf-8", + ).decode("utf-8") -if __name__ == '__main__': - xml = '' +if __name__ == "__main__": + xml = """ + Простой хлеб + МукаДрожжиТёплая вода + + """ + + # TODO: Плохо работает print(pretty_xml_minidom(xml)) + print(pretty_xml_lxml(xml)) diff --git a/pretty_xml/pretty_xml_gui.py b/pretty_xml/pretty_xml_gui.py index 864b6817e..9ca28ee36 100644 --- a/pretty_xml/pretty_xml_gui.py +++ b/pretty_xml/pretty_xml_gui.py @@ -1,12 +1,14 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -from os.path import split as path_split +import sys import traceback +from pathlib import Path + # TODO: избавиться от `import *` try: @@ -23,31 +25,28 @@ from PySide.QtGui import * from PySide.QtCore import Qt +from pretty_xml import pretty_xml_lxml as to_pretty_xml -def log_uncaught_exceptions(ex_cls, ex, tb): - text = '{}: {}:\n'.format(ex_cls.__name__, ex) - import traceback - text += ''.join(traceback.format_tb(tb)) + +def log_uncaught_exceptions(ex_cls, ex, tb) -> None: + text = f"{ex_cls.__name__}: {ex}:\n" + text += "".join(traceback.format_tb(tb)) print(text) - QMessageBox.critical(None, 'Error', text) + QMessageBox.critical(None, "Error", text) sys.exit(1) -import sys sys.excepthook = log_uncaught_exceptions -from pretty_xml import pretty_xml_minidom as to_pretty_xml - - class MainWindow(QWidget): - def __init__(self): + def __init__(self) -> None: super().__init__() - self.setWindowTitle(path_split(__file__)[1]) + self.setWindowTitle(Path(__file__).name) - layout = QVBoxLayout() + layout = QVBoxLayout(self) self.text_edit_input = QPlainTextEdit() self.text_edit_output = QPlainTextEdit() @@ -57,13 +56,13 @@ def __init__(self): self.label_error.setTextInteractionFlags(Qt.TextSelectableByMouse) self.label_error.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) - self.button_detail_error = QPushButton('...') + self.button_detail_error = QPushButton("...") self.button_detail_error.setFixedSize(20, 20) - self.button_detail_error.setToolTip('Detail error') + self.button_detail_error.setToolTip("Detail error") self.button_detail_error.hide() - self.last_error_message = None - self.last_detail_error_message = None + self.last_error_message = "" + self.last_detail_error_message = "" self.button_detail_error.clicked.connect(self.show_detail_error_message) self.text_edit_input.textChanged.connect(self.input_text_changed) @@ -81,14 +80,12 @@ def __init__(self): layout.addLayout(layout_error) - self.setLayout(layout) - - def input_text_changed(self): + def input_text_changed(self) -> None: self.label_error.clear() self.button_detail_error.hide() - self.last_error_message = None - self.last_detail_error_message = None + self.last_error_message = "" + self.last_detail_error_message = "" try: text = self.text_edit_input.toPlainText() @@ -106,13 +103,13 @@ def input_text_changed(self): self.last_detail_error_message = str(tb) self.button_detail_error.show() - self.label_error.setText('Error: ' + self.last_error_message) + self.label_error.setText("Error: " + self.last_error_message) - def show_detail_error_message(self): - message = self.last_error_message + '\n\n' + self.last_detail_error_message + def show_detail_error_message(self) -> None: + message = self.last_error_message + "\n\n" + self.last_detail_error_message mb = QErrorMessage() - mb.setWindowTitle('Error') + mb.setWindowTitle("Error") # Сообщение ошибки содержит отступы, символы-переходы на следующую строку, # которые поломаются при вставке через QErrorMessage.showMessage, и нет возможности # выбрать тип текста, то делаем такой хак. @@ -121,11 +118,20 @@ def show_detail_error_message(self): mb.exec_() -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) mw = MainWindow() mw.resize(650, 500) mw.show() + mw.text_edit_input.setPlainText( + """ + Простой хлеб + МукаДрожжиТёплая вода + + """ + ) + app.exec_() diff --git a/prevent_multiple_instances__mutex.py b/prevent_multiple_instances__mutex.py index 33c1c6fa5..c8bb31791 100644 --- a/prevent_multiple_instances__mutex.py +++ b/prevent_multiple_instances__mutex.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import sys @@ -15,7 +15,7 @@ # Prevent multiple instances mutex = win32event.CreateMutex(None, 1, "MY_MUTEX_PYTHON") if win32api.GetLastError() == winerror.ERROR_ALREADY_EXISTS: - print('Already running!') + print("Already running!") mutex = None sys.exit(0) diff --git a/prime_numbers.py b/prime_numbers.py index 45789a853..c7b162426 100644 --- a/prime_numbers.py +++ b/prime_numbers.py @@ -1,25 +1,28 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://stackoverflow.com/a/3035188/5909792 -def get_prime_numbers(n: int) -> list: - """ Returns a list of primes < n """ +def get_prime_numbers(n: int) -> list[int]: + """Returns a list of primes < n""" + sieve = [True] * n - for i in range(3, int(n**0.5)+1, 2): + for i in range(3, int(n**0.5) + 1, 2): if sieve[i]: - sieve[i*i::2*i] = [False] * ((n-i*i-1)//(2*i)+1) + sieve[i * i : : 2 * i] = [False] * ((n - i * i - 1) // (2 * i) + 1) return [2] + [i for i in range(3, n, 2) if sieve[i]] -if __name__ == '__main__': +if __name__ == "__main__": assert get_prime_numbers(10) == [2, 3, 5, 7] - assert get_prime_numbers(100) == [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, - 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97] + assert get_prime_numbers(100) == [ + 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, + 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97 + ] print(sum(get_prime_numbers(2_000_000))) assert sum(get_prime_numbers(2_000_000)) == 142913828922 diff --git a/print_System_Info.py b/print_System_Info.py index fd7eb83ed..2ddd03ac5 100644 --- a/print_System_Info.py +++ b/print_System_Info.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/Pure-L0G1C/FleX/blob/da8f30f9204a65df57063ed74b3e79a2a79a7bfc/payload/modules/sysinfo.py @@ -12,11 +12,11 @@ system_info = { - 'System': system(), - 'Release': release(), - 'Version': version(), - 'Machine': machine(), - 'Username': getuser(), - 'Architecture': architecture()[0] + "System": system(), + "Release": release(), + "Version": version(), + "Machine": machine(), + "Username": getuser(), + "Architecture": architecture()[0], } print(system_info) diff --git a/print__hprof_or_big_size_file.py b/print__hprof_or_big_size_file.py index 001869340..13ac6acd4 100644 --- a/print__hprof_or_big_size_file.py +++ b/print__hprof_or_big_size_file.py @@ -1,26 +1,26 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" +from multiprocessing.dummy import Pool as ThreadPool from os import walk from os.path import join, getsize from timeit import default_timer -from multiprocessing.dummy import Pool as ThreadPool from human_byte_size import sizeof_fmt -DIRS = [r'C:\DEV__TX', r'C:\DEV__OPTT', r'C:\DEV__RADIX'] +DIRS = [r"C:\DEV__TX", r"C:\DEV__OPTT", r"C:\DEV__RADIX"] def is_hprof_and_more_1GB(file_name: str) -> bool: - return '.hprof' in file_name and getsize(file_name) >= 1_000_000_000 # 1 GB + return ".hprof" in file_name and getsize(file_name) >= 1_000_000_000 # 1 GB def find_files_by_dir(root_dir: str, match_func) -> list: - print(f'Start check: {root_dir!r}') + print(f"Start check: {root_dir!r}") items = [] t = default_timer() @@ -34,7 +34,7 @@ def find_files_by_dir(root_dir: str, match_func) -> list: print(text) items.append(text) - print(f'Finish check: {root_dir!r}. Elapsed: {default_timer() - t:.3f} secs') + print(f"Finish check: {root_dir!r}. Elapsed: {default_timer() - t:.3f} secs") return items @@ -43,15 +43,17 @@ def find_files_by_dirs(dirs: list, match_func=is_hprof_and_more_1GB) -> list: items = [] with ThreadPool() as pool: - for result in pool.map(lambda root_dir: find_files_by_dir(root_dir, match_func), dirs): + for result in pool.map( + lambda root_dir: find_files_by_dir(root_dir, match_func), dirs + ): items += result return items -if __name__ == '__main__': +if __name__ == "__main__": items = find_files_by_dirs(DIRS) print() - print(f'Result ({len(items)}):') - print('\n'.join(items)) + print(f"Result ({len(items)}):") + print("\n".join(items)) diff --git a/print__hprof_or_big_size_file__notify_with_MessageBox.py b/print__hprof_or_big_size_file__notify_with_MessageBox.py index cfa257acd..a07b65b56 100644 --- a/print__hprof_or_big_size_file__notify_with_MessageBox.py +++ b/print__hprof_or_big_size_file__notify_with_MessageBox.py @@ -1,12 +1,13 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" +import re import time + from pathlib import Path -import re from PyQt5.QtWidgets import QApplication, QMessageBox from PyQt5.QtCore import Qt @@ -14,7 +15,7 @@ from print__hprof_or_big_size_file import find_files_by_dirs, DIRS -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) while True: @@ -22,11 +23,13 @@ if not result: continue - text = f'Files .hprof ({len(result)}):\n' + '\n'.join(result) + text = f"Files .hprof ({len(result)}):\n" + "\n".join(result) - msg_box = QMessageBox(QMessageBox.Information, 'Found .hprof!', text) + msg_box = QMessageBox(QMessageBox.Information, "Found .hprof!", text) msg_box.setTextInteractionFlags(Qt.TextSelectableByMouse) - remove_all_files_button = msg_box.addButton("Remove all files", QMessageBox.DestructiveRole) + remove_all_files_button = msg_box.addButton( + "Remove all files", QMessageBox.DestructiveRole + ) msg_box.addButton(QMessageBox.Ok) msg_box.exec() @@ -40,6 +43,8 @@ try: Path(file_name).unlink() except Exception as e: - QMessageBox.warning(None, "Warning", f"Error while removed {file_name!r}: {e}") + QMessageBox.warning( + None, "Warning", f"Error while removed {file_name!r}: {e}" + ) time.sleep(5 * 60 * 60) diff --git a/print__win__netstat/main.py b/print__win__netstat/main.py index 197ca5f78..89f572b5a 100644 --- a/print__win__netstat/main.py +++ b/print__win__netstat/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: Чтобы узнать кто какие занял/прослушивает порты @@ -11,7 +11,7 @@ # SOURCE: https://github.com/gil9red/SimplePyScripts/blob/c24fb90f1c6b792ebd77e2f916ef5ec7d741c494/ascii_table__simple_pretty__rjust.py -def print_pretty_table(data, cell_sep=' | '): +def print_pretty_table(data, cell_sep=" | "): rows = len(data) cols = len(data[0]) @@ -32,27 +32,27 @@ def print_pretty_table(data, cell_sep=' | '): # Append header separate if not header_ok: - print(' + '.join('-' * width for width in col_width)) + print(" + ".join("-" * width for width in col_width)) header_ok = True def get_raw_data() -> str: - cmd = 'netstat -ano -p tcp' + cmd = "netstat -ano -p tcp" return check_output(cmd, universal_newlines=True).strip() -def get_data() -> (list, list): +def get_data() -> tuple[list, list]: text = get_raw_data() - lines = text.split('\n')[3:] + lines = text.split("\n")[3:] - headers = ['Proto', 'Local Address', 'Foreign Address', 'State', 'PID'] + headers = ["Proto", "Local Address", "Foreign Address", "State", "PID"] return headers, [line.split() for line in lines] -if __name__ == '__main__': +if __name__ == "__main__": print(get_raw_data()) - print('\n\n') + print("\n\n") headers, rows = get_data() diff --git a/print__win__wmi/print__baseboard__info.py b/print__win__wmi/print__baseboard__info.py index 29309a6c5..3788cb4ca 100644 --- a/print__win__wmi/print__baseboard__info.py +++ b/print__win__wmi/print__baseboard__info.py @@ -1,29 +1,29 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -from subprocess import check_output import xml.etree.ElementTree as ET +from subprocess import check_output def get_baseboard_info() -> dict: # Motherboard model - cmd = 'wmic baseboard get product, Manufacturer, version, serialnumber /format:RAWXML' + cmd = "wmic baseboard get product, Manufacturer, version, serialnumber /format:RAWXML" text = check_output(cmd) root = ET.fromstring(text) result = dict() - for x in root.iter('PROPERTY'): - key = x.attrib['NAME'] - result[key] = x.find('VALUE').text + for x in root.iter("PROPERTY"): + key = x.attrib["NAME"] + result[key] = x.find("VALUE").text return result -if __name__ == '__main__': +if __name__ == "__main__": info = get_baseboard_info() print(info) - print(info['Product']) + print(info["Product"]) diff --git "a/print__\320\223 \320\225 \320\235 \320\230 \320\220 \320\233 \320\254 \320\235 \320\236 \320\225 \321\204\320\276\321\202\320\276.py" "b/print__\320\223 \320\225 \320\235 \320\230 \320\220 \320\233 \320\254 \320\235 \320\236 \320\225 \321\204\320\276\321\202\320\276.py" index b5f03b99a..6f6965d78 100644 --- "a/print__\320\223 \320\225 \320\235 \320\230 \320\220 \320\233 \320\254 \320\235 \320\236 \320\225 \321\204\320\276\321\202\320\276.py" +++ "b/print__\320\223 \320\225 \320\235 \320\230 \320\220 \320\233 \320\254 \320\235 \320\236 \320\225 \321\204\320\276\321\202\320\276.py" @@ -1,19 +1,19 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -text = 'Г Е Н И А Л Ь Н О Е фото ' +text = "Г Е Н И А Л Ь Н О Е фото " length = len(text) for start in range(length + 1): - new_text = '' + new_text = "" for i in range(length): j = (start + i) % length new_text += text[j] - if new_text[0] != ' ': + if new_text[0] != " ": print(new_text) # Г Е Н И А Л Ь Н О Е фото diff --git a/print_ascii_art.py b/print_ascii_art.py index 9bd0e15dd..c5f0ca33c 100644 --- a/print_ascii_art.py +++ b/print_ascii_art.py @@ -1,10 +1,10 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -art = '''\ +art = """\ ..sSs$$$$$$b. .$$$$$$$$$$$$$$$. .$$$$$$$$$$$$$$$$$$$$$b. @@ -68,6 +68,6 @@ $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ dp $ -''' +""" print(art) diff --git a/print_attr_from_object.py b/print_attr_from_object.py index 7815dfae0..fae7fd4ba 100644 --- a/print_attr_from_object.py +++ b/print_attr_from_object.py @@ -1,12 +1,12 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" obj = 1 -print(list(filter(lambda x: not x.startswith('_'), dir(obj)))) -for i in list(filter(lambda x: not x.startswith('_'), dir(obj))): - print(i, ':', getattr(obj, i)) +print(list(filter(lambda x: not x.startswith("_"), dir(obj)))) +for i in list(filter(lambda x: not x.startswith("_"), dir(obj))): + print(i, ":", getattr(obj, i)) print() diff --git a/print_class_name.py b/print_class_name.py index 9400ec27b..945a046c6 100644 --- a/print_class_name.py +++ b/print_class_name.py @@ -1,19 +1,21 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" class A: pass + a = A() print(A.__name__) # A print(a.__class__.__name__) # A class B: - def __init__(self): + def __init__(self) -> None: print(self.__class__.__name__) # B + B() diff --git a/print_connections__radixware_explorer.py b/print_connections__radixware_explorer.py deleted file mode 100644 index e5547bc68..000000000 --- a/print_connections__radixware_explorer.py +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -__author__ = 'ipetrash' - - -FILE_NAME = r'%APPDATA%\radixware.org\explorer\connections.xml' - -import os -ABS_FILE_NAME = os.path.expandvars(FILE_NAME) - - -with open(ABS_FILE_NAME, 'rb') as f: - from bs4 import BeautifulSoup - root = BeautifulSoup(f.read(), 'html.parser') - - -def get_tag_text(tag): - return '' if tag is None else tag.text - - -headers = ['NAME', 'ID', 'COMMENT', 'USERNAME', 'STATIONNAME', 'INITIALADDRESS', 'LANGUAGE', - 'COUNTRY', 'EXPLORERROOTID', 'TRACELEVEL'] -rows = [] - -for connection in root.select('connection'): - rows.append([ - connection['name'], - connection['id'], - get_tag_text(connection.comment), - get_tag_text(connection.username), - get_tag_text(connection.stationname), - get_tag_text(connection.initialaddress), - get_tag_text(connection.language), - get_tag_text(connection.country), - get_tag_text(connection.explorerrootid), - get_tag_text(connection.tracelevel), - ]) - - -# pip install tabulate -from tabulate import tabulate -print(tabulate(rows, headers=headers, tablefmt="grid")) diff --git a/print_exif/main.py b/print_exif/main.py index 9beb97b26..b45e4113a 100644 --- a/print_exif/main.py +++ b/print_exif/main.py @@ -1,25 +1,29 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" + + +import base64 + +# pip install exifread +import exifread def get_exif_tags(file_object_or_file_name, as_category=True): if type(file_object_or_file_name) == str: # Open image file for reading (binary mode) - file_object_or_file_name = open(file_object_or_file_name, mode='rb') + file_object_or_file_name = open(file_object_or_file_name, mode="rb") # Return Exif tags - # pip install exifread - import exifread tags = exifread.process_file(file_object_or_file_name) tags_by_value = dict() if not tags: - print('Not tags') + print("Not tags") return tags_by_value - print('Tags ({}):'.format(len(tags))) + print(f"Tags ({len(tags)}):") for tag, value in tags.items(): # Process value @@ -28,9 +32,9 @@ def get_exif_tags(file_object_or_file_name, as_category=True): try: # If last 2 items equals [0, 0] if value.values[-2:] == [0, 0]: - value = bytes(value.values[:-2]).decode('utf-16') + value = bytes(value.values[:-2]).decode("utf-16") else: - value = bytes(value.values).decode('utf-16') + value = bytes(value.values).decode("utf-16") except: value = str(value.values) @@ -42,18 +46,17 @@ def get_exif_tags(file_object_or_file_name, as_category=True): except: # Example tag JPEGThumbnail if type(value) == bytes: - import base64 value = base64.b64encode(value).decode() - print(' "{}": {}'.format(tag, value)) + print(f' "{tag}": {value}') if not as_category: tags_by_value[tag] = value else: # Fill categories_by_tag - if ' ' in tag: - category, sub_tag = tag.split(' ', maxsplit=1) + if " " in tag: + category, sub_tag = tag.split(" ", maxsplit=1) if category not in tags_by_value: tags_by_value[category] = dict() @@ -68,10 +71,11 @@ def get_exif_tags(file_object_or_file_name, as_category=True): return tags_by_value -if __name__ == '__main__': +if __name__ == "__main__": import json - print('TAGS_BY_VALUE:') - f = open('exif_this.jpg', mode='rb') + + print("TAGS_BY_VALUE:") + f = open("exif_this.jpg", mode="rb") tags_by_value = get_exif_tags(f, as_category=False) print(json.dumps(tags_by_value, sort_keys=True, ensure_ascii=False, indent=4)) # @@ -91,9 +95,9 @@ def get_exif_tags(file_object_or_file_name, as_category=True): # "Image XPTitle": "print_exif" # } - print('\n\n') - print('CATEGORIES_BY_TAG:') - f = open('exif_this.jpg', mode='rb') + print("\n\n") + print("CATEGORIES_BY_TAG:") + f = open("exif_this.jpg", mode="rb") categories_by_tag = get_exif_tags(f) print(json.dumps(categories_by_tag, sort_keys=True, ensure_ascii=False, indent=4)) # diff --git a/print_list_subdirectories_size.py b/print_list_subdirectories_size.py index b95ec9591..f513ccac2 100644 --- a/print_list_subdirectories_size.py +++ b/print_list_subdirectories_size.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import os @@ -10,14 +10,14 @@ from human_byte_size import sizeof_fmt -# Словарь нужен чтобы помнить размер папки. Когда итератор дойдет до родительской папки +# Словарь нужен, чтобы помнить размер папки. Когда итератор дойдет до родительской папки # в словаре уже будут размер вложенных папок dir_sizes = dict() -for root, dirs, files in os.walk('.', topdown=False): +for root, dirs, files in os.walk(".", topdown=False): size = sum(getsize(join(root, f)) for f in files) size += sum(dir_sizes[join(root, d)] for d in dirs) dir_sizes[root] = size for path, total_size in sorted(dir_sizes.items(), key=lambda x: x[0]): - print('{} : {} ({})'.format(path, sizeof_fmt(total_size), total_size)) + print(f"{path} : {sizeof_fmt(total_size)} ({total_size})") diff --git a/print_paths_from_LNKs_of_directory_by_specific_Disc.py b/print_paths_from_LNKs_of_directory_by_specific_Disc.py index fce17a400..7dd065fdf 100644 --- a/print_paths_from_LNKs_of_directory_by_specific_Disc.py +++ b/print_paths_from_LNKs_of_directory_by_specific_Disc.py @@ -1,17 +1,17 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -from glob import iglob import os +from glob import iglob # pip install winshell import winshell -path_desktop_lnk = os.path.expanduser(r'~\Desktop\Пройти\*.lnk') +path_desktop_lnk = os.path.expanduser(r"~\Desktop\Пройти\*.lnk") need_disc = "E:" paths = [] diff --git a/print_sizes_of_directory_LNKs_desktop.py b/print_sizes_of_directory_LNKs_desktop.py index 57ebfa597..e0f98f865 100644 --- a/print_sizes_of_directory_LNKs_desktop.py +++ b/print_sizes_of_directory_LNKs_desktop.py @@ -1,12 +1,13 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" +import os + from collections import defaultdict from glob import iglob -import os # pip install winshell import winshell @@ -15,10 +16,10 @@ # SOURCE: https://github.com/gil9red/SimplePyScripts/blob/b6ac435ee171e48ed35044e8e61e199de641a6e7/get_dir_total_size__using_glob.py -def get_dir_total_size(dir_name: str) -> (int, str): +def get_dir_total_size(dir_name: str) -> tuple[int, str]: total_size = 0 - for file_name in iglob(dir_name + '/**/*', recursive=True): + for file_name in iglob(dir_name + "/**/*", recursive=True): try: if os.path.isfile(file_name): total_size += os.path.getsize(file_name) @@ -31,7 +32,7 @@ def get_dir_total_size(dir_name: str) -> (int, str): disc_by_number = defaultdict(int) -path_desktop_lnk = os.path.expanduser(r'~\Desktop\Пройдено\*.lnk') +path_desktop_lnk = os.path.expanduser(r"~\Desktop\Пройдено\*.lnk") paths = [] @@ -39,7 +40,7 @@ def get_dir_total_size(dir_name: str) -> (int, str): shortcut = winshell.shortcut(file_name) path = shortcut.path - if path.endswith('.exe') and os.path.isfile(path): + if path.endswith(".exe") and os.path.isfile(path): path = os.path.dirname(path) paths.append(path) @@ -53,7 +54,7 @@ def get_dir_total_size(dir_name: str) -> (int, str): for file_name in paths: size, size_str = get_dir_total_size(file_name) - print(f'{size:<15} {size_str:10} {file_name}') + print(f"{size:<15} {size_str:10} {file_name}") total_size += size disc = file_name[0] @@ -63,22 +64,25 @@ def get_dir_total_size(dir_name: str) -> (int, str): disc_by_total_items[disc].append((size, size_str, file_name)) print() -print(f'Total size: {total_size} {sizeof_fmt(total_size)}') +print(f"Total size: {total_size} {sizeof_fmt(total_size)}") for disc in sorted(total_size_by_disc): size = total_size_by_disc[disc] - print(f' {disc} {size:<15} {sizeof_fmt(size)}') + print(f" {disc} {size:<15} {sizeof_fmt(size)}") print() -print('Top all:') -for size, size_str, file_name in sorted(total_items, key=lambda x: x[0], reverse=True)[:5]: - print(f' {size:<15} bytes {size_str:10} {file_name}') +print("Top all:") +items = sorted(total_items, key=lambda x: x[0], reverse=True) +for size, size_str, file_name in items[:5]: + print(f" {size:<15} bytes {size_str:10} {file_name}") print() for disc, total_items in disc_by_total_items.items(): - print(f'Top of {disc}:') - for size, size_str, file_name in sorted(total_items, key=lambda x: x[0], reverse=True)[:3]: - print(f' {size:<15} bytes {size_str:10} {file_name}') + print(f"Top of {disc}:") + for size, size_str, file_name in sorted( + total_items, key=lambda x: x[0], reverse=True + )[:3]: + print(f" {size:<15} bytes {size_str:10} {file_name}") print() diff --git a/print_sql_db_tables__using_sqlalchemy.py b/print_sql_db_tables__using_sqlalchemy.py index 0e44c469c..31a1e9930 100644 --- a/print_sql_db_tables__using_sqlalchemy.py +++ b/print_sql_db_tables__using_sqlalchemy.py @@ -1,29 +1,31 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -if __name__ == '__main__': - import os - DIR = os.path.dirname(__file__) - DB_FILE_NAME = 'sqlite:///' + os.path.join(DIR, 'database') +import os - # Создаем базу, включаем логирование и автообновление подключения каждые 2 часа (7200 секунд) - from sqlalchemy import create_engine - engine = create_engine( - DB_FILE_NAME, - # echo=True, - pool_recycle=7200 - ) +from sqlalchemy import create_engine +from sqlalchemy.engine import reflection - from sqlalchemy.engine import reflection - inspector = reflection.Inspector.from_engine(engine) - table_names = inspector.get_table_names() - for table in table_names: - columns = [column_info['name'] for column_info in inspector.get_columns(table)] - print('{}: {}'.format(table, ', '.join(columns))) +DIR = os.path.dirname(__file__) +DB_FILE_NAME = "sqlite:///" + os.path.join(DIR, "database") - for row in engine.execute("select * from {}".format(table)): - print(row.values()) +# Создаем базу, включаем логирование и автообновление подключения каждые 2 часа (7200 секунд) +engine = create_engine( + DB_FILE_NAME, + # echo=True, + pool_recycle=7200, +) + +inspector = reflection.Inspector.from_engine(engine) + +table_names = inspector.get_table_names() +for table in table_names: + columns = [column_info["name"] for column_info in inspector.get_columns(table)] + print(f"{table}: {', '.join(columns)}") + + for row in engine.execute(f"select * from {table}"): + print(row.values()) diff --git a/profile__example.py b/profile__example.py index d932e9203..22b38634e 100644 --- a/profile__example.py +++ b/profile__example.py @@ -1,31 +1,31 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -# LOOK: 1000 0.016 0.000 0.016 0.000 profile__example.py:14(_has) +import profile -def foo(number=100): - def _inc_i(): +def foo(number=100) -> None: + def _inc_i() -> None: nonlocal i i += 1 - def _has(value, items): + def _has(value, items) -> bool: return value in items - items = ['1234567890' for _ in range(number)] + items = ["1234567890" for _ in range(number)] print(items) i = 0 for i in range(number): _inc_i() - _has('5', items) + _has("5", items) print(i) -import profile -profile.run(r'foo(1000)') +profile.run(r"foo(1000)") +# LOOK: 1000 0.016 0.000 0.016 0.000 profile__example.py:14(_has) diff --git a/programmer_day.py b/programmer_day.py index 9601fe536..6a6785d1f 100644 --- a/programmer_day.py +++ b/programmer_day.py @@ -1,20 +1,22 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -from datetime import datetime, timedelta -day_of_year = int(datetime.today().strftime('%j')) -print('day_of_year:', day_of_year) +from datetime import date, timedelta -last_programmer_day = 256 - day_of_year -print('last_programmer_day:', last_programmer_day) -if last_programmer_day >= 0: - programmer_day = (datetime.today() + timedelta(days=last_programmer_day)) - print('\nday of programmer_day:', programmer_day.strftime('%j')) - print('programmer_day:', programmer_day) +day_of_year: int = int(date.today().strftime("%j")) +print(f"Current day of year: {day_of_year}") + +last_programmer_day: int = 256 - day_of_year +print(f"Until Programmer's day: {last_programmer_day}") +if last_programmer_day >= 0: + programmer_day = date.today() + timedelta(days=last_programmer_day) + print() + print(f"Day of programmer day: {programmer_day:%j}") + print(f"Programmer day: {programmer_day}") else: - print('Has already passed') + print("Has already passed") diff --git a/proxy.py__examples/ca_certificate.py b/proxy.py__examples/ca_certificate.py index 4578c9d69..878b916fb 100644 --- a/proxy.py__examples/ca_certificate.py +++ b/proxy.py__examples/ca_certificate.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # NOTE: Analog https://github.com/abhinavsingh/proxy.py/blob/754d5a35d28347716dfaefea91418826148ed903/Makefile#L60 @@ -11,22 +11,30 @@ from config import CA_KEY_FILE_PATH, CA_CERT_FILE_PATH, CA_SIGNING_KEY_FILE_PATH -def main(): +def main() -> None: # Generate CA key - os.system(f'python -m proxy.common.pki gen_private_key --private-key-path {CA_KEY_FILE_PATH}') - os.system(f'python -m proxy.common.pki remove_passphrase --private-key-path {CA_KEY_FILE_PATH}') + os.system( + f"python -m proxy.common.pki gen_private_key --private-key-path {CA_KEY_FILE_PATH}" + ) + os.system( + f"python -m proxy.common.pki remove_passphrase --private-key-path {CA_KEY_FILE_PATH}" + ) # Generate CA certificate os.system( - f'python -m proxy.common.pki gen_public_key --private-key-path {CA_KEY_FILE_PATH} ' - f'--public-key-path {CA_CERT_FILE_PATH}' + f"python -m proxy.common.pki gen_public_key --private-key-path {CA_KEY_FILE_PATH} " + f"--public-key-path {CA_CERT_FILE_PATH}" ) # Generate key that will be used to generate domain certificates on the fly # Generated certificates are then signed with CA certificate / key generated above - os.system(f'python -m proxy.common.pki gen_private_key --private-key-path {CA_SIGNING_KEY_FILE_PATH}') - os.system(f'python -m proxy.common.pki remove_passphrase --private-key-path {CA_SIGNING_KEY_FILE_PATH}') + os.system( + f"python -m proxy.common.pki gen_private_key --private-key-path {CA_SIGNING_KEY_FILE_PATH}" + ) + os.system( + f"python -m proxy.common.pki remove_passphrase --private-key-path {CA_SIGNING_KEY_FILE_PATH}" + ) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/proxy.py__examples/config.py b/proxy.py__examples/config.py index a3cd80774..682255cb5 100644 --- a/proxy.py__examples/config.py +++ b/proxy.py__examples/config.py @@ -1,10 +1,10 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # TODO: support pathlib -CA_KEY_FILE_PATH = 'ca-key.pem' -CA_CERT_FILE_PATH = 'ca-cert.pem' -CA_SIGNING_KEY_FILE_PATH = 'ca-signing-key.pem' +CA_KEY_FILE_PATH = "ca-key.pem" +CA_CERT_FILE_PATH = "ca-cert.pem" +CA_SIGNING_KEY_FILE_PATH = "ca-signing-key.pem" diff --git a/proxy.py__examples/hello_world.py b/proxy.py__examples/hello_world.py index 2bb3d8570..0a1ecc404 100644 --- a/proxy.py__examples/hello_world.py +++ b/proxy.py__examples/hello_world.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/abhinavsingh/proxy.py @@ -9,8 +9,6 @@ import ipaddress -from typing import Optional - # pip install --upgrade proxy.py import proxy from proxy.common.utils import bytes_ @@ -21,13 +19,13 @@ # NOTE: Analog https://github.com/gil9red/SimplePyScripts/blob/3d8afdec07c2cfbc94fcb3fd792721e1e8565a42/http.server__examples/simple_http_proxy_server/main.py#L18 class AddHeadersPlugin(HttpProxyBasePlugin): - def before_upstream_connection(self, request: HttpParser) -> Optional[HttpParser]: + def before_upstream_connection(self, request: HttpParser) -> HttpParser | None: return request - def handle_client_request(self, request: HttpParser) -> Optional[HttpParser]: + def handle_client_request(self, request: HttpParser) -> HttpParser | None: # NOTE: Add custom header - request.add_header(b'x-my-proxy', b'hell yeah!') - request.add_header(b'x-my-client-ip', bytes_(self.client.addr[0])) + request.add_header(b"x-my-proxy", b"hell yeah!") + request.add_header(b"x-my-client-ip", bytes_(self.client.addr[0])) return request @@ -38,19 +36,19 @@ def on_upstream_connection_close(self) -> None: pass -def main(*args, **kwargs): +def main(*args, **kwargs) -> None: proxy.main( *args, - hostname=ipaddress.ip_address('127.0.0.1'), + hostname=ipaddress.ip_address("127.0.0.1"), port=33333, plugins=[ - 'proxy.plugin.CacheResponsesPlugin', # Adding plugin v1 - FilterByUpstreamHostPlugin, # Adding plugin v2 - AddHeadersPlugin, # Adding custom plugin + "proxy.plugin.CacheResponsesPlugin", # Adding plugin v1 + FilterByUpstreamHostPlugin, # Adding plugin v2 + AddHeadersPlugin, # Adding custom plugin ], **kwargs ) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/proxy.py__examples/hello_world__with_TLS_HTTPS_interception.py b/proxy.py__examples/hello_world__with_TLS_HTTPS_interception.py index 378086684..65c26694f 100644 --- a/proxy.py__examples/hello_world__with_TLS_HTTPS_interception.py +++ b/proxy.py__examples/hello_world__with_TLS_HTTPS_interception.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/abhinavsingh/proxy.py#tls-interception @@ -22,7 +22,7 @@ ca_certificate.main() -if __name__ == '__main__': +if __name__ == "__main__": main( ca_key_file=CA_KEY_FILE_PATH, ca_cert_file=CA_CERT_FILE_PATH, diff --git a/proxy.py__examples/test.py b/proxy.py__examples/test.py index 0fe92ef67..cb39e7c5f 100644 --- a/proxy.py__examples/test.py +++ b/proxy.py__examples/test.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import os.path @@ -20,13 +20,13 @@ proxies = { - "http": 'localhost:33333', - "https": 'localhost:33333', + "http": "localhost:33333", + "https": "localhost:33333", } # NOTE: When error "ssl.SSLError: [X509: KEY_VALUES_MISMATCH] key values mismatch (_ssl.c:3845)" # try remove all files from "C:\Users\\.proxy\certificates" -for url in ['http://httpbin.org/headers', 'https://httpbin.org/headers']: +for url in ["http://httpbin.org/headers", "https://httpbin.org/headers"]: print(url) # NOTE: The "verify" parameter is needed for HTTPS interception @@ -38,9 +38,9 @@ DEBUG_LOG and print(rs.headers) DEBUG_LOG and print(rs.content) - for header, value in rs.json()['headers'].items(): - if header.lower().startswith('x-my'): - print(f'{header}: {value!r}') + for header, value in rs.json()["headers"].items(): + if header.lower().startswith("x-my"): + print(f"{header}: {value!r}") print() diff --git a/psutil_example/cpu_info.py b/psutil_example/cpu_info.py index ff4921e88..4c734f2a0 100644 --- a/psutil_example/cpu_info.py +++ b/psutil_example/cpu_info.py @@ -1,32 +1,33 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install psutil import psutil -print('CPU info:', psutil.cpu_times()) + +print("CPU info:", psutil.cpu_times()) print() for _ in range(3): info = psutil.cpu_percent(interval=1) - print('CPU percent (interval=1, percpu=False):', info) + print("CPU percent (interval=1, percpu=False):", info) print() for _ in range(3): info = psutil.cpu_percent(interval=1, percpu=True) - print('CPU percent (interval=1, percpu=True):', info) + print("CPU percent (interval=1, percpu=True):", info) print() for _ in range(3): info = psutil.cpu_times_percent(interval=1, percpu=False) - print('CPU times percent (interval=1, percpu=False):', info) + print("CPU times percent (interval=1, percpu=False):", info) print() -print('Logical CPUs:', psutil.cpu_count()) -print('Physical CPUs:', psutil.cpu_count(logical=False)) +print("Logical CPUs:", psutil.cpu_count()) +print("Physical CPUs:", psutil.cpu_count(logical=False)) print() -print('CPU stats:', psutil.cpu_stats()) -print('CPU freq:', psutil.cpu_freq()) +print("CPU stats:", psutil.cpu_stats()) +print("CPU freq:", psutil.cpu_freq()) diff --git a/psutil_example/disc_used_DIFF/common.py b/psutil_example/disc_used_DIFF/common.py index 05e254563..2be79686a 100644 --- a/psutil_example/disc_used_DIFF/common.py +++ b/psutil_example/disc_used_DIFF/common.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import json @@ -13,31 +13,31 @@ # Absolute file name -FILE_NAME_SNAPSHOT = str(pathlib.Path(__file__).resolve().parent / 'snapshot.json') +FILE_NAME_SNAPSHOT = str(pathlib.Path(__file__).resolve().parent / "snapshot.json") def sizeof_fmt(num: Union[int, float], with_sign=False) -> str: if with_sign: - sign = '+' if num >= 0 else '-' + sign = "+" if num >= 0 else "-" else: - sign = '' + sign = "" if num == 0: - sign = '' + sign = "" num = abs(num) - for x in ['bytes', 'KB', 'MB', 'GB']: + for x in ["bytes", "KB", "MB", "GB"]: if num < 1024.0: return "%s%3.1f %s" % (sign, num, x) num /= 1024.0 - return "%s%3.1f %s" % (sign, num, 'TB') + return "%s%3.1f %s" % (sign, num, "TB") def get_disc_list() -> List[str]: - return [disk.device for disk in psutil.disk_partitions() if 'fixed' in disk.opts] + return [disk.device for disk in psutil.disk_partitions() if "fixed" in disk.opts] def get_human_sizes(items: List[int], with_sign) -> List[str]: @@ -58,18 +58,18 @@ def get_disc_total_used(disc_list: List[str]) -> str: return sizeof_fmt(sum(psutil.disk_usage(disk).used for disk in disc_list)) -def write_snapshot_raw_disc_used(disc_list: List[str] = None): - with open(FILE_NAME_SNAPSHOT, 'w', encoding='utf-8') as f: +def write_snapshot_raw_disc_used(disc_list: List[str] = None) -> None: + with open(FILE_NAME_SNAPSHOT, "w", encoding="utf-8") as f: items = get_disc_used(disc_list, only_raw=True) - print('Write snapshot with:', items) + print("Write snapshot with:", items) json.dump(items, f) def load_snapshot_raw_disc_used() -> List[int]: - with open(FILE_NAME_SNAPSHOT, encoding='utf-8') as f: + with open(FILE_NAME_SNAPSHOT, encoding="utf-8") as f: items = json.load(f) - print('Read snapshot:', items) + print("Read snapshot:", items) return items diff --git a/psutil_example/disc_used_DIFF/create_disc_snapshot.py b/psutil_example/disc_used_DIFF/create_disc_snapshot.py index 4d662ae9c..c483e1a6b 100644 --- a/psutil_example/disc_used_DIFF/create_disc_snapshot.py +++ b/psutil_example/disc_used_DIFF/create_disc_snapshot.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from common import write_snapshot_raw_disc_used diff --git a/psutil_example/disc_used_DIFF/main.py b/psutil_example/disc_used_DIFF/main.py index eb7d460fe..9a54e6967 100644 --- a/psutil_example/disc_used_DIFF/main.py +++ b/psutil_example/disc_used_DIFF/main.py @@ -1,26 +1,32 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install tabulate from tabulate import tabulate -from common import get_disc_list, get_disc_used, get_human_sizes, get_row_for, load_snapshot_raw_disc_used +from common import ( + get_disc_list, + get_disc_used, + get_human_sizes, + get_row_for, + load_snapshot_raw_disc_used, +) disc_list = get_disc_list() disc_list_raw = get_disc_used(disc_list, only_raw=True) disc_list_snapshot_raw = load_snapshot_raw_disc_used() -disc_list_diff_raw = [b-a for a, b in zip(disc_list_snapshot_raw, disc_list_raw)] +disc_list_diff_raw = [b - a for a, b in zip(disc_list_snapshot_raw, disc_list_raw)] -headers = [''] + disc_list + ['TOTAL USED'] +headers = [""] + disc_list + ["TOTAL USED"] rows = [ - get_row_for('', disc_list_snapshot_raw), - get_row_for('', disc_list_raw), - get_row_for('', disc_list_diff_raw, with_sign=True), + get_row_for("", disc_list_snapshot_raw), + get_row_for("", disc_list_raw), + get_row_for("", disc_list_diff_raw, with_sign=True), ] print() diff --git a/psutil_example/disk_info.py b/psutil_example/disk_info.py index ac529aa17..ca824ff52 100644 --- a/psutil_example/disk_info.py +++ b/psutil_example/disk_info.py @@ -1,53 +1,43 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -import sys -from pathlib import Path +# pip install humanize +from humanize import naturalsize as sizeof_fmt # pip install psutil import psutil -sys.path.append(str(Path(__file__).resolve().parent.parent)) -from human_byte_size import sizeof_fmt +# pip install tabulate +from tabulate import tabulate -print('Disk partitions:') +print("Disk partitions:") for disk in psutil.disk_partitions(): - print(' {}'.format(disk)) + print(f" {disk}") print() -print('Disk usage:') -for disk in filter(lambda x: 'fixed' in x.opts, psutil.disk_partitions()): +print("Disk usage:") +for disk in filter(lambda x: "fixed" in x.opts, psutil.disk_partitions()): info = psutil.disk_usage(disk.device) - print(' {} {}'.format(disk.device, info)) - print(' {} free of {}'.format(sizeof_fmt(info.free), sizeof_fmt(info.total))) + print(f" {disk.device} {info}") + print(f" {sizeof_fmt(info.free)} free of {sizeof_fmt(info.total)}") print() print() -print('Disk io (input/output) total sum counters:') -print(' {}'.format(psutil.disk_io_counters())) +print("Disk io (input/output) total sum counters:") +print(f" {psutil.disk_io_counters()}") physical_drive_by_info = list(psutil.disk_io_counters(True).items()) print() -print('Physical drive io (input/output) counters ({}):'.format(len(physical_drive_by_info))) -# -# for drive, info in physical_drive_by_info: -# print(' {}: {}'.format(drive, info)) -# # -# # OR: -# # -headers = ('drive',) + physical_drive_by_info[0][1]._fields +print(f"Physical drive io (input/output) counters ({len(physical_drive_by_info)}):") + +headers = ("drive",) + physical_drive_by_info[0][1]._fields headers = [header.upper() for header in headers] rows = [(drive,) + tuple(info) for drive, info in physical_drive_by_info] - -# pip install tabulate -from tabulate import tabulate print(tabulate(rows, headers=headers, tablefmt="grid")) -print() -print() diff --git a/psutil_example/get_battery_status.py b/psutil_example/get_battery_status.py index ea2fd6797..5116944d0 100644 --- a/psutil_example/get_battery_status.py +++ b/psutil_example/get_battery_status.py @@ -1,10 +1,12 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install psutil import psutil + + battery = psutil.sensors_battery() print(battery.percent) diff --git a/psutil_example/get_win_services.py b/psutil_example/get_win_services.py index d27b7343a..26361ecb1 100644 --- a/psutil_example/get_win_services.py +++ b/psutil_example/get_win_services.py @@ -1,30 +1,30 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install psutil import psutil from psutil._pswindows import WindowsService -from typing import List - -def get_win_services() -> List[WindowsService]: +def get_win_services() -> list[WindowsService]: return list(psutil.win_service_iter()) -if __name__ == '__main__': +if __name__ == "__main__": win_service_list = get_win_services() - print(f'Win service list ({len(win_service_list)}):') + print(f"Win service list ({len(win_service_list)}):") for service in win_service_list: - title = f'{service.name()!r} ({service.display_name()})' - path = f'Pid={service.pid()}, name={service.name()!r}, display_name={service.display_name()!r}, ' \ - f'status={service.status()!r}, start_type={service.start_type()!r}' - print('Title:', title) - print('Path:', path) - print('Status:', service.status()) - print('binpath:', service.binpath()) + title = f"{service.name()!r} ({service.display_name()})" + path = ( + f"Pid={service.pid()}, name={service.name()!r}, display_name={service.display_name()!r}, " + f"status={service.status()!r}, start_type={service.start_type()!r}" + ) + print("Title:", title) + print("Path:", path) + print("Status:", service.status()) + print("binpath:", service.binpath()) print() diff --git a/psutil_example/hello_world.py b/psutil_example/hello_world.py index 6a1f82e01..01a6e6232 100644 --- a/psutil_example/hello_world.py +++ b/psutil_example/hello_world.py @@ -1,9 +1,11 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install psutil import psutil + + psutil.test() diff --git a/psutil_example/kill_process_by_name.py b/psutil_example/kill_process_by_name.py index e06ffad92..889c8f93a 100644 --- a/psutil_example/kill_process_by_name.py +++ b/psutil_example/kill_process_by_name.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install psutil @@ -9,5 +9,5 @@ for process in psutil.process_iter(): - if process.name() == 'calc.exe': + if process.name() == "calc.exe": process.kill() diff --git a/psutil_example/loop_kill_process_by_name.py b/psutil_example/loop_kill_process_by_name.py index d5e5c27f4..d23f99ea2 100644 --- a/psutil_example/loop_kill_process_by_name.py +++ b/psutil_example/loop_kill_process_by_name.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import time @@ -12,7 +12,7 @@ while True: for process in psutil.process_iter(): - if process.name() == 'calc.exe': + if process.name() == "calc.exe": process.kill() # Wait 5 secs diff --git a/psutil_example/memory_info.py b/psutil_example/memory_info.py index f3579be09..ac1744368 100644 --- a/psutil_example/memory_info.py +++ b/psutil_example/memory_info.py @@ -1,10 +1,12 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install psutil import psutil -print('System memory:', psutil.virtual_memory()) -print('System swap memory:', psutil.swap_memory()) + + +print("System memory:", psutil.virtual_memory()) +print("System swap memory:", psutil.swap_memory()) diff --git a/psutil_example/network_info/net_connections.py b/psutil_example/network_info/net_connections.py index 86e7535fc..bdc25cd6d 100644 --- a/psutil_example/network_info/net_connections.py +++ b/psutil_example/network_info/net_connections.py @@ -1,14 +1,18 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install psutil import psutil +# pip install tabulate +from tabulate import tabulate + + net_connections = psutil.net_connections() -print('Net connections ({}):'.format(len(net_connections))) +print(f"Net connections ({len(net_connections)}):") # Sort by pid net_connections.sort(key=lambda x: x.pid) @@ -23,7 +27,4 @@ headers = [header.upper() for header in headers] rows = [tuple(info) for info in net_connections] - - # pip install tabulate - from tabulate import tabulate print(tabulate(rows, headers=headers, tablefmt="grid", showindex=True)) diff --git a/psutil_example/network_info/net_interface_by_address_list.py b/psutil_example/network_info/net_interface_by_address_list.py index bf797234a..356ae34d2 100644 --- a/psutil_example/network_info/net_interface_by_address_list.py +++ b/psutil_example/network_info/net_interface_by_address_list.py @@ -1,14 +1,18 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install psutil import psutil +# pip install tabulate +from tabulate import tabulate + + net_interface_by_address_list = list(psutil.net_if_addrs().items()) -print('Net interface by address list ({}):'.format(len(net_interface_by_address_list))) +print(f"Net interface by address list ({len(net_interface_by_address_list)}):") # Sort by name interface net_interface_by_address_list.sort(key=lambda x: x[0]) @@ -23,15 +27,13 @@ # # OR: # # if net_interface_by_address_list: - headers = ('name',) + net_interface_by_address_list[0][1][0]._fields + headers = ("name",) + net_interface_by_address_list[0][1][0]._fields headers = [header.upper() for header in headers] rows = [] for name, address_list in net_interface_by_address_list: for i, address in enumerate(address_list): - rows.append((name if i == 0 else '',) + tuple(address)) + rows.append((name if i == 0 else "",) + tuple(address)) - # pip install tabulate - from tabulate import tabulate print(tabulate(rows, headers=headers, tablefmt="grid")) diff --git a/psutil_example/network_info/net_interface_list.py b/psutil_example/network_info/net_interface_list.py index 561c964ac..88f2f7b75 100644 --- a/psutil_example/network_info/net_interface_list.py +++ b/psutil_example/network_info/net_interface_list.py @@ -1,14 +1,18 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install psutil import psutil +# pip install tabulate +from tabulate import tabulate + + net_interface_list = list(psutil.net_io_counters(pernic=True).items()) -print('Net interface list ({}):'.format(len(net_interface_list))) +print(f"Net interface list ({len(net_interface_list)}):") # Sort by name interface net_interface_list.sort(key=lambda x: x[0]) @@ -20,11 +24,8 @@ # # if net_interface_list: info_fields = net_interface_list[0][1]._fields - headers = ('name',) + info_fields + headers = ("name",) + info_fields headers = [header.upper() for header in headers] rows = [(interface,) + tuple(info) for interface, info in net_interface_list] - - # pip install tabulate - from tabulate import tabulate print(tabulate(rows, headers=headers, tablefmt="grid")) diff --git a/psutil_example/network_info/network_interface_card_by_info.py b/psutil_example/network_info/network_interface_card_by_info.py index de795cefd..c4b0b8c73 100644 --- a/psutil_example/network_info/network_interface_card_by_info.py +++ b/psutil_example/network_info/network_interface_card_by_info.py @@ -1,14 +1,18 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install psutil import psutil +# pip install tabulate +from tabulate import tabulate + + network_interface_card_by_info = list(psutil.net_if_stats().items()) -print('Network interface card by info ({}):'.format(len(network_interface_card_by_info))) +print(f"Network interface card by info ({len(network_interface_card_by_info)}):") # Sort by name interface network_interface_card_by_info.sort(key=lambda x: x[0]) @@ -20,11 +24,8 @@ # # if network_interface_card_by_info: - headers = ('name',) + network_interface_card_by_info[0][1]._fields + headers = ("name",) + network_interface_card_by_info[0][1]._fields headers = [header.upper() for header in headers] rows = [(name,) + tuple(info) for name, info in network_interface_card_by_info] - - # pip install tabulate - from tabulate import tabulate print(tabulate(rows, headers=headers, tablefmt="grid")) diff --git a/psutil_example/on_new_process_handler.py b/psutil_example/on_new_process_handler.py index 5d6be6934..b2092c618 100644 --- a/psutil_example/on_new_process_handler.py +++ b/psutil_example/on_new_process_handler.py @@ -1,37 +1,37 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from threading import Thread from time import sleep -from typing import Callable, List +from typing import Callable # pip install psutil import psutil -ListenerFunc = Callable[[List], None] +ListenerFunc = Callable[[list], None] class OnNewProcessHandler(Thread): - def __init__(self, timeout_secs=1.0): + def __init__(self, timeout_secs=1.0) -> None: super().__init__() self._process = dict() - self._listeners: List[ListenerFunc] = [] + self._listeners: list[ListenerFunc] = [] self._timeout_secs = timeout_secs - def add_listener(self, listener: ListenerFunc): + def add_listener(self, listener: ListenerFunc) -> None: if listener not in self._listeners: self._listeners.append(listener) - def remove_listener(self, listener: ListenerFunc): + def remove_listener(self, listener: ListenerFunc) -> None: if listener in self._listeners: self._listeners.remove(listener) - def remove_all_listener(self): + def remove_all_listener(self) -> None: self._listeners.clear() @staticmethod @@ -40,15 +40,15 @@ def get_process() -> dict: for proc in psutil.process_iter(): try: - pinfo = proc.as_dict(attrs=['pid', 'name', 'exe']) - items[pinfo['pid']] = pinfo + pinfo = proc.as_dict(attrs=["pid", "name", "exe"]) + items[pinfo["pid"]] = pinfo except psutil.NoSuchProcess: continue return items - def run(self): + def run(self) -> None: while True: current_process = self.get_process() @@ -69,18 +69,18 @@ def run(self): sleep(self._timeout_secs) -if __name__ == '__main__': - def print_listener(process_list): +if __name__ == "__main__": + + def print_listener(process_list) -> None: result = [] for p in process_list: result.append(f"{p['name']} (pid={p['pid']})") - print('-' * 10 + '\n' + '\n'.join(result) + '\n' + '-' * 10) - + print("-" * 10 + "\n" + "\n".join(result) + "\n" + "-" * 10) handler = OnNewProcessHandler() handler.add_listener(print_listener) handler.start() sleep(2) - handler.add_listener(lambda x: print(f'New process: {len(x)}')) + handler.add_listener(lambda x: print(f"New process: {len(x)}")) diff --git a/psutil_example/other_system_info.py b/psutil_example/other_system_info.py index 6105dc634..0df75294a 100644 --- a/psutil_example/other_system_info.py +++ b/psutil_example/other_system_info.py @@ -1,16 +1,19 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" +from datetime import datetime + # pip install psutil import psutil -print('Users:', psutil.users()) + + +print("Users:", psutil.users()) print() boot_time = psutil.boot_time() -print('System boot time (timestamp):', boot_time) +print("System boot time (timestamp):", boot_time) -from datetime import datetime -print('System boot time:', datetime.fromtimestamp(boot_time)) +print("System boot time:", datetime.fromtimestamp(boot_time)) diff --git a/psutil_example/print_all_webservers/get_info_html.py b/psutil_example/print_all_webservers/get_info_html.py new file mode 100644 index 000000000..476373f37 --- /dev/null +++ b/psutil_example/print_all_webservers/get_info_html.py @@ -0,0 +1,165 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import os +import tempfile + +from dataclasses import dataclass + +# pip install psutil==6.1.0 +import psutil + + +@dataclass +class Process: + pid: int + name: str + path: str + cwd: str + ports: str + + +TITLE = "Список запущенных серверов" + + +def _get_processes() -> list[Process]: + processes = [] + for proc in psutil.process_iter(): + try: + connections = [ + c + for c in proc.net_connections() + if c.status == psutil.CONN_LISTEN and c.laddr + ] + if not connections: + continue + + ports = sorted(set(c.laddr.port for c in connections)) + processes.append( + Process( + pid=proc.pid, + name=proc.name(), + path=" ".join(os.path.normpath(x) for x in proc.cmdline()), + cwd=proc.cwd(), + ports=", ".join(map(str, ports)), + ) + ) + + except psutil.AccessDenied: + pass + + processes.sort(key=lambda p: p.name) + + return processes + + +def get_info_html() -> str: + template = """ + + + + + {{ title }} + + + + + + + + + + + + + + + + + + {{ table_rows }} + +
{{ title }}
#PIDНазваниеПорт(ы)ПутьCwd
+ + + """ + + table_rows: list[str] = [ + f""" + + {i} + {p.pid} + {p.name} +
{p.ports}
+
{p.path}
+
{p.cwd}
+ + """ + for i, p in enumerate(_get_processes(), 1) + ] + + return template.replace( + "{{ title }}", TITLE + ).replace( + "{{ table_rows }}", "\n".join(table_rows) + ) + + +def open_html_file() -> None: + with tempfile.NamedTemporaryFile( + mode="w", + encoding="utf-8", + prefix="webservers-", + suffix=".html", + delete=False, + ) as fp: + fp.write(get_info_html()) + path: str = fp.name + + os.startfile(path) + + +if __name__ == "__main__": + open_html_file() diff --git a/psutil_example/print_all_webservers/web.py b/psutil_example/print_all_webservers/web.py index 114811c60..0687ee765 100644 --- a/psutil_example/print_all_webservers/web.py +++ b/psutil_example/print_all_webservers/web.py @@ -1,130 +1,45 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -from http.server import BaseHTTPRequestHandler, HTTPServer import os -from urllib.parse import urlsplit - -# pip install psutil -import psutil - - -TITLE = "Список запущенных серверов" -HEADERS = ["#", "PID", "Название", "Порт(ы)", "Путь"] - -def _get_processes() -> list: - processes = [] - for proc in psutil.process_iter(): - try: - connections = [c for c in proc.connections() if c.status == psutil.CONN_LISTEN and c.laddr] - if not connections: - continue - - ports = sorted(set(c.laddr.port for c in connections)) - processes.append({ - 'pid': proc.pid, - 'name': proc.name(), - 'path': ' '.join(os.path.normpath(x) for x in proc.cmdline()), - 'ports': ', '.join(map(str, ports)), - }) - - except psutil.AccessDenied: - pass - - processes.sort(key=lambda p: int(''.join(c for c in p['ports'] if c.isdigit()))) +from http.server import BaseHTTPRequestHandler, HTTPServer +from urllib.parse import urlsplit - return processes +from get_info_html import get_info_html class HttpProcessor(BaseHTTPRequestHandler): - def do_GET(self): + def do_GET(self) -> None: o = urlsplit(self.path) # Only index - if o.path != '/': + if o.path != "/": self.send_error(404) return - table_rows = [] - for i, p in enumerate(_get_processes(), 1): - table_rows.append(f''' - - {i} - {p['pid']} - {p['name']} - {p['ports']} - {p['path']} - - ''') - - text = """ - - - - - {{ title }} - - - - - - - - - - - - {{ headers }} - - {{ table_rows }} - -
{{ title }}
- - - """ \ - .replace('{{ title }}', TITLE) \ - .replace('{{ headers }}', ''.join(f'{x}' for x in HEADERS)) \ - .replace('{{ table_rows }}', ''.join(table_rows)) + text = get_info_html() self.send_response(200) - self.send_header('Content-Type', 'text/html; charset=utf-8') - self.send_header('Connection', 'close') + self.send_header("Content-Type", "text/html; charset=utf-8") + self.send_header("Connection", "close") self.end_headers() - self.wfile.write(text.encode('utf-8')) + self.wfile.write(text.encode("utf-8")) -def run(server_class=HTTPServer, handler_class=HttpProcessor, port=8080): - print(f'HTTP server running on http://127.0.0.1:{port}') +def run(server_class=HTTPServer, handler_class=HttpProcessor, port=8080) -> None: + print(f"HTTP server running on http://127.0.0.1:{port}") - server_address = ('', port) + server_address = ("", port) httpd = server_class(server_address, handler_class) httpd.serve_forever() -if __name__ == '__main__': - run(port=10013) +if __name__ == "__main__": + run( + port=int(os.environ.get("PORT", 50012)), + ) diff --git a/psutil_example/print_current_python_webservers/web.py b/psutil_example/print_current_python_webservers/web.py index f9d176cb0..9b36cc0db 100644 --- a/psutil_example/print_current_python_webservers/web.py +++ b/psutil_example/print_current_python_webservers/web.py @@ -1,70 +1,78 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -from http.server import BaseHTTPRequestHandler, HTTPServer import os + +from http.server import BaseHTTPRequestHandler, HTTPServer from urllib.parse import urlsplit -# pip install psutil +# pip install psutil==6.1.0 import psutil -NEED_PROCESS = ['python.exe', 'pythonw.exe'] +NEED_PROCESS = ["python.exe", "pythonw.exe"] TITLE = "Список запущенных серверов на python" HEADERS = ["#", "PID", "Порт(ы)", "Путь"] -def _get_processes() -> list: +def _port_to_tag_a(port: int) -> str: + return f'{port}' + + +def _get_processes() -> list[dict[str, str | int]]: processes = [] for proc in psutil.process_iter(): if not proc.is_running() or proc.name().lower() not in NEED_PROCESS: continue - connections = [c for c in proc.connections() if c.status == psutil.CONN_LISTEN and c.laddr] + connections = [ + c for c in proc.net_connections() if c.status == psutil.CONN_LISTEN and c.laddr + ] if not connections: continue ports = sorted(set(c.laddr.port for c in connections)) - processes.append({ - 'pid': proc.pid, - 'name': proc.name(), - 'path': os.path.normpath(proc.cmdline()[1]), - 'ports': ', '.join(map(_port_to_tag_a, ports)), - }) + processes.append( + { + "pid": proc.pid, + "name": proc.name(), + "path": " ".join(proc.cmdline()[1:]), + "ports": ", ".join(map(_port_to_tag_a, ports)), + } + ) - processes.sort(key=lambda p: int(''.join(c for c in p['ports'] if c.isdigit()))) + processes.sort(key=lambda p: int("".join(c for c in p["ports"] if c.isdigit()))) return processes -def _port_to_tag_a(port: int) -> str: - return f'{port}' - - class HttpProcessor(BaseHTTPRequestHandler): - def do_GET(self): + def do_GET(self) -> None: o = urlsplit(self.path) # Only index - if o.path != '/': + if o.path != "/": self.send_error(404) return table_rows = [] for i, p in enumerate(_get_processes(), 1): - table_rows.append(f''' + table_rows.append( + f""" {i} {p['pid']} {p['ports']} {p['path']} - ''') + """ + ) - text = """ + text = ( + """ @@ -108,26 +116,28 @@ def do_GET(self): - """ \ - .replace('{{ title }}', TITLE) \ - .replace('{{ headers }}', ''.join(f'{x}' for x in HEADERS)) \ - .replace('{{ table_rows }}', ''.join(table_rows)) + """.replace( + "{{ title }}", TITLE + ) + .replace("{{ headers }}", "".join(f"{x}" for x in HEADERS)) + .replace("{{ table_rows }}", "".join(table_rows)) + ) self.send_response(200) - self.send_header('Content-Type', 'text/html; charset=utf-8') - self.send_header('Connection', 'close') + self.send_header("Content-Type", "text/html; charset=utf-8") + self.send_header("Connection", "close") self.end_headers() - self.wfile.write(text.encode('utf-8')) + self.wfile.write(text.encode("utf-8")) -def run(server_class=HTTPServer, handler_class=HttpProcessor, port=8080): - print(f'HTTP server running on http://127.0.0.1:{port}') +def run(server_class=HTTPServer, handler_class=HttpProcessor, port=8080) -> None: + print(f"HTTP server running on http://127.0.0.1:{port}") - server_address = ('', port) + server_address = ("", port) httpd = server_class(server_address, handler_class) httpd.serve_forever() -if __name__ == '__main__': +if __name__ == "__main__": run(port=10012) diff --git a/psutil_example/print_process_memory/print_process_by_memory.py b/psutil_example/print_process_memory/print_process_by_memory.py index 57dec5fbc..c859bf762 100644 --- a/psutil_example/print_process_memory/print_process_by_memory.py +++ b/psutil_example/print_process_memory/print_process_by_memory.py @@ -1,19 +1,16 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -import sys -from pathlib import Path - from collections import defaultdict # pip install psutil import psutil -sys.path.append(str(Path(__file__).resolve().parent.parent)) -from human_byte_size import sizeof_fmt +# pip install humanize +from humanize import naturalsize as sizeof_fmt column_width = defaultdict(int) @@ -21,15 +18,15 @@ for p in psutil.process_iter(): memory = p.memory_info().rss - cols = p.name(), str(memory) + ' bytes', sizeof_fmt(memory) + cols = p.name(), str(memory) + " bytes", sizeof_fmt(memory) process_list.append(cols) for i, x in enumerate(cols): column_width[i] = max(column_width[i], len(x)) # Sort by memory size -process_list.sort(key=lambda x: int(x[1].split(' ')[0]), reverse=True) +process_list.sort(key=lambda x: int(x[1].split(" ")[0]), reverse=True) for p in process_list: row = [x.rjust(column_width[i]) for i, x in enumerate(p)] - print(' | '.join(row)) + print(" | ".join(row)) diff --git a/psutil_example/print_process_memory/print_process_by_memory__simple.py b/psutil_example/print_process_memory/print_process_by_memory__simple.py index 188aface3..17c2ae6b8 100644 --- a/psutil_example/print_process_memory/print_process_by_memory__simple.py +++ b/psutil_example/print_process_memory/print_process_by_memory__simple.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install psutil diff --git a/psutil_example/print_process_memory/print_process_by_memory__simple__only_firefox.py b/psutil_example/print_process_memory/print_process_by_memory__simple__only_firefox.py index 06d8b8444..b4cefbbf1 100644 --- a/psutil_example/print_process_memory/print_process_by_memory__simple__only_firefox.py +++ b/psutil_example/print_process_memory/print_process_by_memory__simple__only_firefox.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install psutil @@ -10,7 +10,7 @@ for p in psutil.process_iter(): name = p.name() - if name.lower() != 'firefox.exe': + if name.lower() != "firefox.exe": continue print(name, p.memory_info().rss) diff --git a/psutil_example/print_process_memory/print_process_by_memory__simple__only_firefox_total.py b/psutil_example/print_process_memory/print_process_by_memory__simple__only_firefox_total.py index 2503e9d16..cf47d0986 100644 --- a/psutil_example/print_process_memory/print_process_by_memory__simple__only_firefox_total.py +++ b/psutil_example/print_process_memory/print_process_by_memory__simple__only_firefox_total.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install psutil @@ -12,7 +12,7 @@ for p in psutil.process_iter(): name = p.name() - if name.lower() != 'firefox.exe': + if name.lower() != "firefox.exe": continue memory = p.memory_info().rss @@ -20,9 +20,11 @@ print(name, memory) -print(f''' +print( + f""" Total: {total} bytes {total // 1024} KB {total // 1024 // 1024} MB {total // 1024 // 1024 // 1024} GB -''') +""" +) diff --git a/psutil_example/process_management/analog_subprocess_Popen.py b/psutil_example/process_management/analog_subprocess_Popen.py index ffaa915de..8f8f6f77f 100644 --- a/psutil_example/process_management/analog_subprocess_Popen.py +++ b/psutil_example/process_management/analog_subprocess_Popen.py @@ -1,13 +1,14 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install psutil import psutil from subprocess import PIPE -p = psutil.Popen(['python', "-c", "print('hello')\nprint('world!')"], stdout=PIPE) + +p = psutil.Popen(["python", "-c", "print('hello')\nprint('world!')"], stdout=PIPE) print(p.name()) print(p.username()) print(p.communicate()) diff --git a/psutil_example/process_management/current_process_detail_info.py b/psutil_example/process_management/current_process_detail_info.py index 55ff0101e..a7ac6bbb8 100644 --- a/psutil_example/process_management/current_process_detail_info.py +++ b/psutil_example/process_management/current_process_detail_info.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from multiprocessing import current_process diff --git a/psutil_example/process_management/on_terminate__callback.py b/psutil_example/process_management/on_terminate__callback.py index 712109595..36cffe170 100644 --- a/psutil_example/process_management/on_terminate__callback.py +++ b/psutil_example/process_management/on_terminate__callback.py @@ -1,17 +1,17 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # Run 2 process and wait terminate +import threading +import time -def func(time_life=4): - import time - i = 1 - import threading +def func(time_life=4) -> None: + i = 1 while i <= time_life: print(threading.current_thread(), i) @@ -20,8 +20,9 @@ def func(time_life=4): time.sleep(1) -if __name__ == '__main__': +if __name__ == "__main__": from multiprocessing import Process + p1 = Process(target=func, args=(5,)) p1.start() @@ -30,18 +31,21 @@ def func(time_life=4): # pip install psutil import psutil + process1 = psutil.Process(p1.pid) - print('process1 is_running:', process1.is_running()) + print("process1 is_running:", process1.is_running()) process2 = psutil.Process(p2.pid) - print('process2 is_running:', process2.is_running()) + print("process2 is_running:", process2.is_running()) print() - def on_terminate(proc): - print('Process "{}" terminated'.format(proc)) + def on_terminate(proc) -> None: + print(f'Process "{proc}" terminated') # waits for multiple processes to terminate - gone, alive = psutil.wait_procs([process1, process2], timeout=30, callback=on_terminate) + gone, alive = psutil.wait_procs( + [process1, process2], timeout=30, callback=on_terminate + ) print() - print('gone:', gone) - print('alive:', alive) + print("gone:", gone) + print("alive:", alive) diff --git a/psutil_example/process_management/process_control.py b/psutil_example/process_management/process_control.py index eea6d2052..66961ef30 100644 --- a/psutil_example/process_management/process_control.py +++ b/psutil_example/process_management/process_control.py @@ -1,13 +1,14 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -def func(): - import time - import threading +import time +import threading + +def func() -> None: i = 1 while True: @@ -17,37 +18,39 @@ def func(): time.sleep(1) -if __name__ == '__main__': +if __name__ == "__main__": from multiprocessing import Process + p = Process(target=func) p.start() print(p.pid) # pip install psutil import psutil + process = psutil.Process(p.pid) - print('is_running:', process.is_running()) + print("is_running:", process.is_running()) - print('Info:') + print("Info:") from process_detail_info import print_info + print_info(p.pid) - print('\n') + print("\n") - import time time.sleep(4) - print('suspend on 3 seconds') + print("suspend on 3 seconds") process.suspend() time.sleep(3) - print('resume') + print("resume") process.resume() - print('terminate after 5 seconds') + print("terminate after 5 seconds") time.sleep(5) process.terminate() - print('terminate') + print("terminate") result_code = process.wait(timeout=3) - print('result_code:', result_code) + print("result_code:", result_code) diff --git a/psutil_example/process_management/process_detail_info.py b/psutil_example/process_management/process_detail_info.py index aeaf2f7c2..b2ff6649b 100644 --- a/psutil_example/process_management/process_detail_info.py +++ b/psutil_example/process_management/process_detail_info.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # NOTE: Need run as admin @@ -10,52 +10,52 @@ import psutil -def print_info(pid): +def print_info(pid) -> None: process = psutil.Process(pid) - print('Process:', process) - - print('name:', process.name()) - print('as_dict:', process.as_dict()) - print('is_running:', process.is_running()) - - print('exe:', process.exe()) - print('cwd:', process.cwd()) - print('cmdline:', process.cmdline()) - print('pid:', process.pid) - print('ppid:', process.ppid()) - print('parent:', process.parent()) - print('children:', process.children()) - print('status:', process.status()) - print('username:', process.username()) - print('create_time:', process.create_time()) + print("Process:", process) + + print("name:", process.name()) + print("as_dict:", process.as_dict()) + print("is_running:", process.is_running()) + + print("exe:", process.exe()) + print("cwd:", process.cwd()) + print("cmdline:", process.cmdline()) + print("pid:", process.pid) + print("ppid:", process.ppid()) + print("parent:", process.parent()) + print("children:", process.children()) + print("status:", process.status()) + print("username:", process.username()) + print("create_time:", process.create_time()) # If POSIX: # print('uids:', process.uids()) # print('gids:', process.gids()) - print('cpu_times:', process.cpu_times()) - print('cpu_percent:', process.cpu_percent(interval=1.0)) - print('cpu_affinity:', process.cpu_affinity()) - print('cpu_affinity:', process.cpu_affinity([0, 1])) + print("cpu_times:", process.cpu_times()) + print("cpu_percent:", process.cpu_percent(interval=1.0)) + print("cpu_affinity:", process.cpu_affinity()) + print("cpu_affinity:", process.cpu_affinity([0, 1])) # If Linux, FreeBSD, SunOS # print('cpu_num:', process.cpu_num()) - print('memory_info:', process.memory_info()) - print('memory_full_info:', process.memory_full_info()) - print('memory_percent:', process.memory_percent()) - print('memory_maps:', process.memory_maps()) - print('io_counters:', process.io_counters()) - print('open_files:', process.open_files()) - print('connections:', process.connections()) - print('num_threads:', process.num_threads()) + print("memory_info:", process.memory_info()) + print("memory_full_info:", process.memory_full_info()) + print("memory_percent:", process.memory_percent()) + print("memory_maps:", process.memory_maps()) + print("io_counters:", process.io_counters()) + print("open_files:", process.open_files()) + print("connections:", process.connections()) + print("num_threads:", process.num_threads()) # If POSIX # print('num_fds:', process.num_fds()) - print('threads:', process.threads()) - print('num_ctx_switches:', process.num_ctx_switches()) - print('nice/priority get:', process.nice()) + print("threads:", process.threads()) + print("num_ctx_switches:", process.num_ctx_switches()) + print("nice/priority get:", process.nice()) # Error: OSError: [WinError 87] The parameter is incorrect # print('nice/priority set:', process.nice(10)) @@ -64,23 +64,24 @@ def print_info(pid): # Error: AttributeError: module 'psutil' has no attribute 'IOPRIO_CLASS_IDLE' # print('ionice set:', process.ionice(psutil.IOPRIO_CLASS_IDLE)) - print('ionice get:', process.ionice()) + print("ionice get:", process.ionice()) # If Linux # print('rlimit set:', process.rlimit(psutil.RLIMIT_NOFILE, (5, 5))) # print('rlimit get:', process.rlimit(psutil.RLIMIT_NOFILE)) # If Linux, OSX and Windows - print('environ:', process.environ()) + print("environ:", process.environ()) -if __name__ == '__main__': - print('Random process info') +if __name__ == "__main__": + print("Random process info") process_pid_list = psutil.pids() import random + pid = random.choice(process_pid_list) - print('Pid:', pid) + print("Pid:", pid) print_info(pid) diff --git a/psutil_example/process_management/process_list_info.py b/psutil_example/process_management/process_list_info.py index 1b8d0a78e..22b8e3162 100644 --- a/psutil_example/process_management/process_list_info.py +++ b/psutil_example/process_management/process_list_info.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """Получение списка запущенных процессов.""" @@ -13,7 +13,7 @@ for proc in psutil.process_iter(): try: - pinfo = proc.as_dict(attrs=['pid', 'name']) + pinfo = proc.as_dict(attrs=["pid", "name"]) except psutil.NoSuchProcess: continue diff --git a/psutil_example/process_management/process_pid_list.py b/psutil_example/process_management/process_pid_list.py index 13d59670e..fe5b7caf1 100644 --- a/psutil_example/process_management/process_pid_list.py +++ b/psutil_example/process_management/process_pid_list.py @@ -1,11 +1,11 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install psutil import psutil process_pid_list = psutil.pids() -print('Process pid list ({}): {}'.format(len(process_pid_list), process_pid_list)) +print(f"Process pid list ({len(process_pid_list)}): {process_pid_list}") diff --git a/psutil_example/process_management/random_process_info.py b/psutil_example/process_management/random_process_info.py index c5224eddc..1c4a997dc 100644 --- a/psutil_example/process_management/random_process_info.py +++ b/psutil_example/process_management/random_process_info.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import random @@ -9,11 +9,11 @@ # pip install psutil import psutil -print('Random process info') +print("Random process info") process_pid_list = psutil.pids() pid = random.choice(process_pid_list) -print('Pid:', pid) +print("Pid:", pid) process = psutil.Process(pid) -print('Process:', process) +print("Process:", process) diff --git a/psutil_example/python_processes_started_more_than_an_hour_ago.py b/psutil_example/python_processes_started_more_than_an_hour_ago.py index ef38f2262..e8be19fe4 100644 --- a/psutil_example/python_processes_started_more_than_an_hour_ago.py +++ b/psutil_example/python_processes_started_more_than_an_hour_ago.py @@ -1,20 +1,20 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -import datetime as DT +import datetime as dt # pip install psutil import psutil for proc in psutil.process_iter(): - if not proc.name().startswith('python'): + if not proc.name().startswith("python"): continue secs = proc.create_time() - started = DT.datetime.fromtimestamp(secs) - if (DT.datetime.now() - started) >= DT.timedelta(hours=1): + started = dt.datetime.fromtimestamp(secs) + if (dt.datetime.now() - started) >= dt.timedelta(hours=1): print(proc) diff --git a/psutil_example/sensors_info.py b/psutil_example/sensors_info.py index d5cce15e7..91fd5e487 100644 --- a/psutil_example/sensors_info.py +++ b/psutil_example/sensors_info.py @@ -1,13 +1,15 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # Only Linux # pip install psutil import psutil + + print(psutil.sensors_temperatures()) print(psutil.sensors_fans()) print(psutil.sensors_battery()) diff --git a/psutil_example/windows_services.py b/psutil_example/windows_services.py index fb6fae644..2d45453a1 100644 --- a/psutil_example/windows_services.py +++ b/psutil_example/windows_services.py @@ -1,14 +1,18 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" +# pip install tabulate +from tabulate import tabulate + # pip install psutil import psutil + win_service_list = list(psutil.win_service_iter()) -print(f'Win service list ({len(win_service_list)}):') +print(f"Win service list ({len(win_service_list)}):") win_service_list.sort(key=lambda x: x.status()) @@ -18,7 +22,16 @@ # # OR: # # if win_service_list: - headers = ['name', 'display_name', 'status', 'start_type', 'username', 'pid', 'binpath', 'description'] + headers = [ + "name", + "display_name", + "status", + "start_type", + "username", + "pid", + "binpath", + "description", + ] rows = [] for win_service in win_service_list: @@ -27,15 +40,14 @@ rows.append(row) headers = [header.upper() for header in headers] - - # pip install tabulate - from tabulate import tabulate print(tabulate(rows, headers=headers, tablefmt="grid", showindex=True)) print() print() + name = win_service_list[0].name() -print(f'Windows service info: {name}') +print(f"Windows service info: {name}") + win_service = psutil.win_service_get(name) -print(' win_service:', win_service) -print(' win_service info:', win_service.as_dict()) +print(" win_service:", win_service) +print(" win_service info:", win_service.as_dict()) diff --git a/public_key_cryptography.py b/public_key_cryptography.py index f39a28fe0..07aa24b82 100644 --- a/public_key_cryptography.py +++ b/public_key_cryptography.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # https://ru.wikipedia.org/wiki/Криптосистема_с_открытым_ключом @@ -9,19 +9,19 @@ # Разбор примера шифрования с помощью справочника: https://ru.wikipedia.org/wiki/Криптосистема_с_открытым_ключом REFERENCE_GUIDE_NAME_NUM = { - 'Королёв': '5643452', - 'Орехов': '3572651', - 'Рузаева': '4673956', - 'Осипов': '3517289', - 'Батурин': '7755628', - 'Кирсанова': '1235267', - 'Арсеньева': '8492746', + "Королёв": "5643452", + "Орехов": "3572651", + "Рузаева": "4673956", + "Осипов": "3517289", + "Батурин": "7755628", + "Кирсанова": "1235267", + "Арсеньева": "8492746", } # Обратный словарь -- ключом будет число, а значением имя REFERENCE_GUIDE_NUM_NAME = {v: k for k, v in REFERENCE_GUIDE_NAME_NUM.items()} -MESS = 'коробка' +MESS = "коробка" def encrypt(mess): @@ -32,19 +32,20 @@ def encrypt(mess): encrypt_key = sorted(filter(lambda x: x[0].lower() == c, keys))[0] crypto_text_list.append(REFERENCE_GUIDE_NAME_NUM[encrypt_key]) - return '@'.join(crypto_text_list) + return "@".join(crypto_text_list) def decrypt(encrypt_mess): - crypto_num_list = encrypt_mess.split('@') - mess = '' + crypto_num_list = encrypt_mess.split("@") + mess = "" for num in crypto_num_list: mess += REFERENCE_GUIDE_NUM_NAME[num][0].lower() return mess + encrypt_mess = encrypt(MESS) -print('Encrypt: {} -> {}.'.format(MESS, encrypt_mess)) -print('Decrypt: {} -> {}'.format(encrypt_mess, decrypt(encrypt_mess))) +print(f"Encrypt: {MESS} -> {encrypt_mess}.") +print(f"Decrypt: {encrypt_mess} -> {decrypt(encrypt_mess)}") diff --git a/pyDes__examples/hello_world.py b/pyDes__examples/hello_world.py index f5c675a27..6d7f42db7 100644 --- a/pyDes__examples/hello_world.py +++ b/pyDes__examples/hello_world.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/toddw-as/pyDes @@ -10,11 +10,13 @@ # pip install pyDes import pyDes -data = b"Hello World!" +data = b"Hello World!" # def __init__(self, key, mode=ECB, IV=None, pad=None, padmode=PAD_NORMAL): -k = pyDes.des(b"DESCRYPT", pyDes.CBC, b"\0\0\0\0\0\0\0\0", pad=None, padmode=pyDes.PAD_PKCS5) +k = pyDes.des( + b"DESCRYPT", pyDes.CBC, b"\0\0\0\0\0\0\0\0", pad=None, padmode=pyDes.PAD_PKCS5 +) d = k.encrypt(data) print("Encrypted: %r" % d) diff --git a/pyautogui__keyboard__examples/10_second_macro.py b/pyautogui__keyboard__examples/10_second_macro.py index c8b0e1278..031ab1fcd 100644 --- a/pyautogui__keyboard__examples/10_second_macro.py +++ b/pyautogui__keyboard__examples/10_second_macro.py @@ -5,9 +5,11 @@ # SOURCE: https://github.com/boppreh/keyboard/blob/master/examples/10_second_macro.py +import time + # pip install keyboard import keyboard -import time + keyboard.start_recording() time.sleep(10) diff --git a/pyautogui__keyboard__examples/Frozen_Throne__auto_select_local_map_and_ai/main.py b/pyautogui__keyboard__examples/Frozen_Throne__auto_select_local_map_and_ai/main.py index abc1b74ce..f595ff49f 100644 --- a/pyautogui__keyboard__examples/Frozen_Throne__auto_select_local_map_and_ai/main.py +++ b/pyautogui__keyboard__examples/Frozen_Throne__auto_select_local_map_and_ai/main.py @@ -1,34 +1,35 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # NOTE: Frozen Throne, 2560x1440 import time + # import winsound # pip install pyautogui import pyautogui -LOCAL_NETWORK_BUTTON = 'images/local_network_button.png' -NEW_GAME_BUTTON = 'images/new_game_button.png' -NEW_GAME_BUTTON_SELECT_MAP = 'images/new_game_button_select_map.png' +LOCAL_NETWORK_BUTTON = "images/local_network_button.png" +NEW_GAME_BUTTON = "images/new_game_button.png" +NEW_GAME_BUTTON_SELECT_MAP = "images/new_game_button_select_map.png" -OPEN_COMBO_BOX_SENTINEL = 'images/open_combo_box_sentinel.png' -OPEN_COMBO_BOX_SCOURGE = 'images/open_combo_box_scourge.png' +OPEN_COMBO_BOX_SENTINEL = "images/open_combo_box_sentinel.png" +OPEN_COMBO_BOX_SCOURGE = "images/open_combo_box_scourge.png" SELECT_AI_MENU_SENTINEL = "images/select_ai_menu_sentinel.png" SELECT_AI_MENU_SCOURGE = "images/select_ai_menu_scourge.png" AI_EASY = "images/ai_easy.png" -def go_local_network(): +def go_local_network() -> bool: pos = pyautogui.locateCenterOnScreen(LOCAL_NETWORK_BUTTON) - print('LOCAL_NETWORK_BUTTON:', pos) + print("LOCAL_NETWORK_BUTTON:", pos) if pos: pyautogui.click(pos) @@ -40,9 +41,9 @@ def go_local_network(): return False -def go_new_game(): +def go_new_game() -> bool: pos = pyautogui.locateCenterOnScreen(NEW_GAME_BUTTON) - print('NEW_GAME_BUTTON:', pos) + print("NEW_GAME_BUTTON:", pos) if pos: pyautogui.click(pos) @@ -54,9 +55,9 @@ def go_new_game(): return False -def go_select_map(): +def go_select_map() -> bool: pos = pyautogui.locateCenterOnScreen(NEW_GAME_BUTTON_SELECT_MAP) - print('NEW_GAME_BUTTON_SELECT_MAP:', pos) + print("NEW_GAME_BUTTON_SELECT_MAP:", pos) if pos: pyautogui.click(pos) @@ -68,7 +69,7 @@ def go_select_map(): return False -def bot_says(): +def bot_says() -> None: text = """\ Bot say: -aremnpakulsc @@ -77,10 +78,10 @@ def bot_says(): for line in text.splitlines(): pyautogui.typewrite(line) - pyautogui.typewrite(['enter']) + pyautogui.typewrite(["enter"]) -def select_sentinel_and_scourge(coords_sentinel, coords_scourge): +def select_sentinel_and_scourge(coords_sentinel, coords_scourge) -> None: for rect in coords_sentinel + coords_scourge: pos = pyautogui.center(rect) @@ -93,14 +94,16 @@ def select_sentinel_and_scourge(coords_sentinel, coords_scourge): # Ждем появления меню pos_menu_sentinel = pyautogui.locateCenterOnScreen(SELECT_AI_MENU_SENTINEL) pos_menu_scourge = pyautogui.locateCenterOnScreen(SELECT_AI_MENU_SCOURGE) - print(f'SELECT_AI_MENU_SENTINEL: {pos_menu_sentinel}, SELECT_AI_MENU_SCOURGE: {pos_menu_scourge}') + print( + f"SELECT_AI_MENU_SENTINEL: {pos_menu_sentinel}, SELECT_AI_MENU_SCOURGE: {pos_menu_scourge}" + ) if pos_menu_sentinel or pos_menu_scourge: break # Выбираем игрока pos = pyautogui.locateCenterOnScreen(AI_EASY) - print('AI_EASY:', pos) + print("AI_EASY:", pos) if pos: pyautogui.moveTo(pos) or time.sleep(0.3) pyautogui.click(pos) @@ -108,7 +111,7 @@ def select_sentinel_and_scourge(coords_sentinel, coords_scourge): time.sleep(0.3) -if __name__ == '__main__': +if __name__ == "__main__": while True: # Кликаем на Локальную игру while not go_local_network(): @@ -133,7 +136,9 @@ def select_sentinel_and_scourge(coords_sentinel, coords_scourge): while True: coords_sentinel = list(pyautogui.locateAllOnScreen(OPEN_COMBO_BOX_SENTINEL)) coords_scourge = list(pyautogui.locateAllOnScreen(OPEN_COMBO_BOX_SCOURGE)) - print(f'coords_sentinel: {coords_sentinel}, coords_scourge: {coords_scourge}') + print( + f"coords_sentinel: {coords_sentinel}, coords_scourge: {coords_scourge}" + ) if coords_sentinel and coords_scourge: # winsound.Beep(1000, duration=10) diff --git a/pyautogui__keyboard__examples/auto_input_text__repo_example.py b/pyautogui__keyboard__examples/auto_input_text__repo_example.py index 97263406c..5beef36cf 100644 --- a/pyautogui__keyboard__examples/auto_input_text__repo_example.py +++ b/pyautogui__keyboard__examples/auto_input_text__repo_example.py @@ -1,29 +1,31 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install keyboard import keyboard -keyboard.press_and_release('tab, tab, tab') -keyboard.write('The quick brown fox jumps over the lazy dog.') + +keyboard.press_and_release("tab, tab, tab") + +keyboard.write("The quick brown fox jumps over the lazy dog.") # Press PAGE UP then PAGE DOWN to type "foobar". -keyboard.add_hotkey('page up, page down', lambda: keyboard.write('foobar')) +keyboard.add_hotkey("page up, page down", lambda: keyboard.write("foobar")) # Blocks until you press esc. -keyboard.wait('esc') +keyboard.wait("esc") # Record events until 'esc' is pressed. -recorded = keyboard.record(until='esc') +recorded = keyboard.record(until="esc") # Then replay back at three times the speed. keyboard.play(recorded, speed_factor=3) # Type @@ then press space to replace with abbreviation. -keyboard.add_abbreviation('@@', 'my.long.email@example.com') +keyboard.add_abbreviation("@@", "my.long.email@example.com") # Block forever. keyboard.wait() diff --git a/pyautogui__keyboard__examples/auto_replace_input_text.py b/pyautogui__keyboard__examples/auto_replace_input_text.py index 516b0f4ff..f2aeeaa91 100644 --- a/pyautogui__keyboard__examples/auto_replace_input_text.py +++ b/pyautogui__keyboard__examples/auto_replace_input_text.py @@ -1,14 +1,15 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install keyboard import keyboard + # Type fuck then press space to replace with abbreviation. -keyboard.add_abbreviation('fuck', 'Furnification under consult of King') +keyboard.add_abbreviation("fuck", "Furnification under consult of King") # Block forever. keyboard.wait() diff --git a/pyautogui__keyboard__examples/autoclick_in_game__pyautoit.py b/pyautogui__keyboard__examples/autoclick_in_game__pyautoit.py new file mode 100644 index 000000000..b618b0b83 --- /dev/null +++ b/pyautogui__keyboard__examples/autoclick_in_game__pyautoit.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import time +import os +import winsound + +from threading import Thread + +# pip install PyAutoIt==0.6.5 +import autoit + +# pip install keyboard==0.13.5 +import keyboard + + +DATA = { + "START": False, +} + +HOTKEY: str = os.environ.get("HOTKEY", "Ctrl+Alt+Space") + + +def change_start() -> None: + DATA["START"] = not DATA["START"] + print("START:", DATA["START"]) + + winsound.Beep(1000, duration=50) + + +def process_auto_click() -> None: + while True: + if not DATA["START"]: + time.sleep(0.100) + continue + + # Симуляция атаки + if DATA["START"]: + autoit.mouse_click() + + time.sleep(0.070) + + +keyboard.add_hotkey(HOTKEY, change_start) + +# Запуск потока для автоатаки +thread_auto_click = Thread(target=process_auto_click) +thread_auto_click.start() + + +print(f"Press {HOTKEY} for starting/stopping") + +while True: + if not DATA["START"]: + time.sleep(0.100) + continue diff --git a/pyautogui__keyboard__examples/customizable_hotkey.py b/pyautogui__keyboard__examples/customizable_hotkey.py index be71e8953..28da7ef28 100644 --- a/pyautogui__keyboard__examples/customizable_hotkey.py +++ b/pyautogui__keyboard__examples/customizable_hotkey.py @@ -8,16 +8,18 @@ # pip install keyboard import keyboard -print('Press and release your desired shortcut: ') + +print("Press and release your desired shortcut: ") shortcut = keyboard.read_shortcut() -print('Shortcut selected:', shortcut) +print("Shortcut selected:", shortcut) -def on_triggered(): +def on_triggered() -> None: print("Triggered!") + keyboard.add_hotkey(shortcut, on_triggered) print("Press ESC to stop.") -keyboard.wait('esc') +keyboard.wait("esc") diff --git a/pyautogui__keyboard__examples/hotkey.py b/pyautogui__keyboard__examples/hotkey.py index d1cdbc4a1..71d22ad30 100644 --- a/pyautogui__keyboard__examples/hotkey.py +++ b/pyautogui__keyboard__examples/hotkey.py @@ -1,13 +1,15 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install keyboard import keyboard -keyboard.add_hotkey('shift', lambda: keyboard.write('on')) -keyboard.add_hotkey('shift', lambda: keyboard.write('off'), trigger_on_release=True) + + +keyboard.add_hotkey("shift", lambda: keyboard.write("on")) +keyboard.add_hotkey("shift", lambda: keyboard.write("off"), trigger_on_release=True) # Block forever. keyboard.wait() diff --git a/pyautogui__keyboard__examples/hotkey__hello_world.py b/pyautogui__keyboard__examples/hotkey__hello_world.py index 0f43941fe..dd6599df4 100644 --- a/pyautogui__keyboard__examples/hotkey__hello_world.py +++ b/pyautogui__keyboard__examples/hotkey__hello_world.py @@ -1,18 +1,18 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install keyboard import keyboard -def foo(): - print('World') +def foo() -> None: + print("World") -keyboard.add_hotkey('Ctrl + 1', lambda: print('Hello')) -keyboard.add_hotkey('Ctrl + 2', foo) +keyboard.add_hotkey("Ctrl + 1", lambda: print("Hello")) +keyboard.add_hotkey("Ctrl + 2", foo) -keyboard.wait('Ctrl + Q') +keyboard.wait("Ctrl + Q") diff --git a/pyautogui__keyboard__examples/hotkey__minimize_current_windows__Alt_Space_N.py b/pyautogui__keyboard__examples/hotkey__minimize_current_windows__Alt_Space_N.py index fe6a508e4..210c99773 100644 --- a/pyautogui__keyboard__examples/hotkey__minimize_current_windows__Alt_Space_N.py +++ b/pyautogui__keyboard__examples/hotkey__minimize_current_windows__Alt_Space_N.py @@ -1,12 +1,14 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install keyboard import keyboard -keyboard.add_hotkey('Win + PageDown', lambda: keyboard.send('Alt + Space + N')) + + +keyboard.add_hotkey("Win + PageDown", lambda: keyboard.send("Alt + Space + N")) # Block forever. keyboard.wait() diff --git a/pyautogui__keyboard__examples/hotkey__mouse___down_up.py b/pyautogui__keyboard__examples/hotkey__mouse___down_up.py index 2e65922eb..8ca58e968 100644 --- a/pyautogui__keyboard__examples/hotkey__mouse___down_up.py +++ b/pyautogui__keyboard__examples/hotkey__mouse___down_up.py @@ -1,16 +1,16 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' - - -import pyautogui +__author__ = "ipetrash" # pip install keyboard import keyboard -keyboard.add_hotkey('Ctrl + 1', pyautogui.mouseDown) -keyboard.add_hotkey('Ctrl + 2', pyautogui.mouseUp) -keyboard.wait('Ctrl + Q') +import pyautogui + + +keyboard.add_hotkey("Ctrl + 1", pyautogui.mouseDown) +keyboard.add_hotkey("Ctrl + 2", pyautogui.mouseUp) +keyboard.wait("Ctrl + Q") diff --git a/pyautogui__keyboard__examples/hotkey__mouse___down_up__sound_beep.py b/pyautogui__keyboard__examples/hotkey__mouse___down_up__sound_beep.py index 27e3741ef..90f540165 100644 --- a/pyautogui__keyboard__examples/hotkey__mouse___down_up__sound_beep.py +++ b/pyautogui__keyboard__examples/hotkey__mouse___down_up__sound_beep.py @@ -1,15 +1,27 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -import pyautogui import winsound -# pip install keyboard +# pip install keyboard==0.13.5 import keyboard -keyboard.add_hotkey('Ctrl + 1', lambda: pyautogui.mouseDown() or winsound.Beep(1000, duration=50)) -keyboard.add_hotkey('Ctrl + 2', lambda: pyautogui.mouseUp() or winsound.Beep(1000, duration=50)) -keyboard.wait('Ctrl + Q') +# pip install pyautogui==0.9.54 +import pyautogui + + +def beep() -> None: + winsound.Beep(1000, duration=50) + + +keyboard.add_hotkey( + "Ctrl + 1", lambda: pyautogui.mouseDown() or beep() +) +keyboard.add_hotkey( + "Ctrl + 2", lambda: pyautogui.mouseUp() or beep() +) + +keyboard.wait("Ctrl + Q") diff --git a/pyautogui__keyboard__examples/hotkey__start_pause_quit.py b/pyautogui__keyboard__examples/hotkey__start_pause_quit.py index e4ee1d20a..a326f9091 100644 --- a/pyautogui__keyboard__examples/hotkey__start_pause_quit.py +++ b/pyautogui__keyboard__examples/hotkey__start_pause_quit.py @@ -1,51 +1,54 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -RUN_COMBINATION = 'Ctrl+Shift+R' -QUIT_COMBINATION = 'Ctrl+Shift+Q' -AUTO_ATTACK_COMBINATION = 'Ctrl+Shift+Space' +import time +import os + +import keyboard + + +RUN_COMBINATION = "Ctrl+Shift+R" +QUIT_COMBINATION = "Ctrl+Shift+Q" +AUTO_ATTACK_COMBINATION = "Ctrl+Shift+Space" BOT_DATA = { - 'START': False, - 'AUTO_ATTACK': False, + "START": False, + "AUTO_ATTACK": False, } -def change_start(): - BOT_DATA['START'] = not BOT_DATA['START'] - print('START:', BOT_DATA['START']) +def change_start() -> None: + BOT_DATA["START"] = not BOT_DATA["START"] + print("START:", BOT_DATA["START"]) -def change_auto_attack(): - BOT_DATA['AUTO_ATTACK'] = not BOT_DATA['AUTO_ATTACK'] - print('AUTO_ATTACK:', BOT_DATA['AUTO_ATTACK']) +def change_auto_attack() -> None: + BOT_DATA["AUTO_ATTACK"] = not BOT_DATA["AUTO_ATTACK"] + print("AUTO_ATTACK:", BOT_DATA["AUTO_ATTACK"]) -print('Press "{}" for RUN / PAUSE'.format(RUN_COMBINATION)) -print('Press "{}" for QUIT'.format(QUIT_COMBINATION)) -print('Press "{}" for AUTO_ATTACK'.format(AUTO_ATTACK_COMBINATION)) +print(f'Press "{RUN_COMBINATION}" for RUN / PAUSE') +print(f'Press "{QUIT_COMBINATION}" for QUIT') +print(f'Press "{AUTO_ATTACK_COMBINATION}" for AUTO_ATTACK') -import time -import os -import keyboard -keyboard.add_hotkey(QUIT_COMBINATION, lambda: print('Quit by Escape') or os._exit(0)) +keyboard.add_hotkey(QUIT_COMBINATION, lambda: print("Quit by Escape") or os._exit(0)) keyboard.add_hotkey(AUTO_ATTACK_COMBINATION, change_auto_attack) keyboard.add_hotkey(RUN_COMBINATION, change_start) -print('Start') +print("Start") i = 1 while True: - if not BOT_DATA['START']: + if not BOT_DATA["START"]: time.sleep(0.1) continue - print(i, 'AUTO_ATTACK:', BOT_DATA['AUTO_ATTACK']) + print(i, "AUTO_ATTACK:", BOT_DATA["AUTO_ATTACK"]) time.sleep(1) i += 1 diff --git a/pyautogui__keyboard__examples/hotkey_change_state.py b/pyautogui__keyboard__examples/hotkey_change_state.py index 519cb8414..de9ea7729 100644 --- a/pyautogui__keyboard__examples/hotkey_change_state.py +++ b/pyautogui__keyboard__examples/hotkey_change_state.py @@ -1,41 +1,44 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -RUN_COMBINATION = 'Ctrl+Shift+R' -QUIT_COMBINATION = 'Ctrl+Shift+T' -AUTO_ATTACK_COMBINATION = 'Ctrl+Shift+Space' +import time +import os + +import keyboard + + +RUN_COMBINATION = "Ctrl+Shift+R" +QUIT_COMBINATION = "Ctrl+Shift+T" +AUTO_ATTACK_COMBINATION = "Ctrl+Shift+Space" BOT_DATA = { - 'AUTO_ATTACK': False, + "AUTO_ATTACK": False, } -def change_auto_attack(): - BOT_DATA['AUTO_ATTACK'] = not BOT_DATA['AUTO_ATTACK'] - print('AUTO_ATTACK:', BOT_DATA['AUTO_ATTACK']) +def change_auto_attack() -> None: + BOT_DATA["AUTO_ATTACK"] = not BOT_DATA["AUTO_ATTACK"] + print("AUTO_ATTACK:", BOT_DATA["AUTO_ATTACK"]) -print('Press "{}" for RUN'.format(RUN_COMBINATION)) -print('Press "{}" for QUIT'.format(QUIT_COMBINATION)) -print('Press "{}" for AUTO_ATTACK'.format(AUTO_ATTACK_COMBINATION)) +print(f'Press "{RUN_COMBINATION}" for RUN') +print(f'Press "{QUIT_COMBINATION}" for QUIT') +print(f'Press "{AUTO_ATTACK_COMBINATION}" for AUTO_ATTACK') -import time -import os -import keyboard -keyboard.add_hotkey(QUIT_COMBINATION, lambda: print('Quit by Escape') or os._exit(0)) +keyboard.add_hotkey(QUIT_COMBINATION, lambda: print("Quit by Escape") or os._exit(0)) keyboard.add_hotkey(AUTO_ATTACK_COMBINATION, change_auto_attack) keyboard.wait(RUN_COMBINATION) -print('Start') +print("Start") i = 1 while True: - print(i, 'AUTO_ATTACK:', BOT_DATA['AUTO_ATTACK']) + print(i, "AUTO_ATTACK:", BOT_DATA["AUTO_ATTACK"]) time.sleep(1) i += 1 diff --git a/pyautogui__keyboard__examples/hotkey_change_state__multi_thread.py b/pyautogui__keyboard__examples/hotkey_change_state__multi_thread.py index cfb2de25d..bf4264347 100644 --- a/pyautogui__keyboard__examples/hotkey_change_state__multi_thread.py +++ b/pyautogui__keyboard__examples/hotkey_change_state__multi_thread.py @@ -1,50 +1,52 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import time import threading +import os + +import keyboard -RUN_COMBINATION = 'Ctrl+Shift+R' -QUIT_COMBINATION = 'Ctrl+Shift+T' -AUTO_ATTACK_COMBINATION = 'Ctrl+Shift+Space' +RUN_COMBINATION = "Ctrl+Shift+R" +QUIT_COMBINATION = "Ctrl+Shift+T" +AUTO_ATTACK_COMBINATION = "Ctrl+Shift+Space" BOT_DATA = { - 'AUTO_ATTACK': False, + "AUTO_ATTACK": False, } -def change_auto_attack(): - BOT_DATA['AUTO_ATTACK'] = not BOT_DATA['AUTO_ATTACK'] - print('AUTO_ATTACK:', BOT_DATA['AUTO_ATTACK']) +def change_auto_attack() -> None: + BOT_DATA["AUTO_ATTACK"] = not BOT_DATA["AUTO_ATTACK"] + print("AUTO_ATTACK:", BOT_DATA["AUTO_ATTACK"]) -print('Press "{}" for RUN'.format(RUN_COMBINATION)) -print('Press "{}" for QUIT'.format(QUIT_COMBINATION)) -print('Press "{}" for AUTO_ATTACK'.format(AUTO_ATTACK_COMBINATION)) +print(f'Press "{RUN_COMBINATION}" for RUN') +print(f'Press "{QUIT_COMBINATION}" for QUIT') +print(f'Press "{AUTO_ATTACK_COMBINATION}" for AUTO_ATTACK') -import time -import os -import keyboard -keyboard.add_hotkey(QUIT_COMBINATION, lambda: print('Quit by Escape') or os._exit(0)) + +keyboard.add_hotkey(QUIT_COMBINATION, lambda: print("Quit by Escape") or os._exit(0)) keyboard.add_hotkey(AUTO_ATTACK_COMBINATION, change_auto_attack) keyboard.wait(RUN_COMBINATION) -print('Start') +print("Start") -def process_auto_attack(): +def process_auto_attack() -> None: i = 1 while True: - print(i, 'AUTO_ATTACK:', BOT_DATA['AUTO_ATTACK']) + print(i, "AUTO_ATTACK:", BOT_DATA["AUTO_ATTACK"]) time.sleep(1) i += 1 + thread_auto_attack = threading.Thread(target=process_auto_attack) thread_auto_attack.start() diff --git a/pyautogui__keyboard__examples/input__hello_world.py b/pyautogui__keyboard__examples/input__hello_world.py index 1b9caa5fa..b6595328a 100644 --- a/pyautogui__keyboard__examples/input__hello_world.py +++ b/pyautogui__keyboard__examples/input__hello_world.py @@ -1,10 +1,12 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install keyboard import keyboard -keyboard.write('Hello') -keyboard.write(' world!', delay=0.2) + + +keyboard.write("Hello") +keyboard.write(" world!", delay=0.2) diff --git a/pyautogui__keyboard__examples/input__hello_world__by_hotkey.py b/pyautogui__keyboard__examples/input__hello_world__by_hotkey.py index e0a29d584..8040eb6fa 100644 --- a/pyautogui__keyboard__examples/input__hello_world__by_hotkey.py +++ b/pyautogui__keyboard__examples/input__hello_world__by_hotkey.py @@ -1,12 +1,17 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install keyboard import keyboard -keyboard.add_hotkey('Shift + Home', lambda: keyboard.write('Hello') or keyboard.write(' world!', delay=0.3)) + + +keyboard.add_hotkey( + "Shift + Home", + lambda: keyboard.write("Hello") or keyboard.write(" world!", delay=0.3), +) # Block forever. keyboard.wait() diff --git a/pyautogui__keyboard__examples/input_and_delete__typewrite_press_keyDown_keyUp.py b/pyautogui__keyboard__examples/input_and_delete__typewrite_press_keyDown_keyUp.py index e7fec5fcc..640268450 100644 --- a/pyautogui__keyboard__examples/input_and_delete__typewrite_press_keyDown_keyUp.py +++ b/pyautogui__keyboard__examples/input_and_delete__typewrite_press_keyDown_keyUp.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: http://pyautogui.readthedocs.io/en/latest/keyboard.html#the-press-keydown-and-keyup-functions @@ -11,18 +11,18 @@ import pyautogui -TEXT = 'Hello World!' +TEXT = "Hello World!" # Input text pyautogui.typewrite(TEXT) # Select text -pyautogui.keyDown('shift') # Hold down the shift key +pyautogui.keyDown("shift") # Hold down the shift key for _ in TEXT: - pyautogui.press('left') # Press the left arrow key + pyautogui.press("left") # Press the left arrow key -pyautogui.keyUp('shift') # Release the shift key +pyautogui.keyUp("shift") # Release the shift key -pyautogui.press('delete') +pyautogui.press("delete") diff --git a/pyautogui__keyboard__examples/keyboard.send__ctrl+v.py b/pyautogui__keyboard__examples/keyboard.send__ctrl+v.py index 2afb4fc23..04d7784a4 100644 --- a/pyautogui__keyboard__examples/keyboard.send__ctrl+v.py +++ b/pyautogui__keyboard__examples/keyboard.send__ctrl+v.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/boppreh/keyboard#keyboard.send @@ -9,4 +9,5 @@ # pip install keyboard import keyboard + keyboard.send("Ctrl+V") diff --git a/pyautogui__keyboard__examples/keyboard_hook.py b/pyautogui__keyboard__examples/keyboard_hook.py index 9f91aefb4..2bc6174c0 100644 --- a/pyautogui__keyboard__examples/keyboard_hook.py +++ b/pyautogui__keyboard__examples/keyboard_hook.py @@ -1,14 +1,14 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install keyboard import keyboard -def print_pressed_keys(e): +def print_pressed_keys(e) -> None: print(e, e.event_type, e.name) diff --git a/pyautogui__keyboard__examples/locate_center_on_screen.py b/pyautogui__keyboard__examples/locate_center_on_screen.py index da80a4f9e..0719d62fe 100644 --- a/pyautogui__keyboard__examples/locate_center_on_screen.py +++ b/pyautogui__keyboard__examples/locate_center_on_screen.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import pyautogui @@ -16,8 +16,8 @@ def locate_center_on_screen(needle_image, screenshot_image=None): return pyautogui.locateCenterOnScreen(needle_image) -if __name__ == '__main__': - needle_image = '' +if __name__ == "__main__": + needle_image = "" import time diff --git a/pyautogui__keyboard__examples/loop_key_press__quit_by_press_Escape.py b/pyautogui__keyboard__examples/loop_key_press__quit_by_press_Escape.py index 0c4a3b5e9..f3bac70f6 100644 --- a/pyautogui__keyboard__examples/loop_key_press__quit_by_press_Escape.py +++ b/pyautogui__keyboard__examples/loop_key_press__quit_by_press_Escape.py @@ -1,24 +1,27 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -QUIT_COMBINATION = 'Esc' -print('Press "{}" for QUIT'.format(QUIT_COMBINATION)) - +import time import os -import keyboard -keyboard.add_hotkey(QUIT_COMBINATION, lambda: print('Quit by Escape') or os._exit(0)) -import time +import keyboard import pyautogui + +QUIT_COMBINATION = "Esc" +print(f'Press "{QUIT_COMBINATION}" for QUIT') + + +keyboard.add_hotkey(QUIT_COMBINATION, lambda: print("Quit by Escape") or os._exit(0)) + time.sleep(5) i = 0 while True: - pyautogui.press('pgdn') + pyautogui.press("pgdn") i += 1 print(i) diff --git a/pyautogui__keyboard__examples/message_box/alert.py b/pyautogui__keyboard__examples/message_box/alert.py index d9ace4589..6e9f1cc64 100644 --- a/pyautogui__keyboard__examples/message_box/alert.py +++ b/pyautogui__keyboard__examples/message_box/alert.py @@ -1,9 +1,11 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from pyautogui import alert -button = alert(text='My Text', title='My Title', button='OK') + + +button = alert(text="My Text", title="My Title", button="OK") print(button) diff --git a/pyautogui__keyboard__examples/message_box/confirm.py b/pyautogui__keyboard__examples/message_box/confirm.py index 8f3f88442..1c2c8c9f8 100644 --- a/pyautogui__keyboard__examples/message_box/confirm.py +++ b/pyautogui__keyboard__examples/message_box/confirm.py @@ -1,9 +1,11 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from pyautogui import confirm -button = confirm(text='My Text', title='My Title', buttons=['OK', 'Cancel']) + + +button = confirm(text="My Text", title="My Title", buttons=["OK", "Cancel"]) print(button) diff --git a/pyautogui__keyboard__examples/message_box/password.py b/pyautogui__keyboard__examples/message_box/password.py index 52cfdcaa8..e8794e0d0 100644 --- a/pyautogui__keyboard__examples/message_box/password.py +++ b/pyautogui__keyboard__examples/message_box/password.py @@ -1,12 +1,14 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from pyautogui import password -result = password(text='My Text', title='My Title', default='Default Text', mask='*') + + +result = password(text="My Text", title="My Title", default="Default Text", mask="*") print(result) -result = password(text='My Text', title='My Title', default='Default Text', mask='?') +result = password(text="My Text", title="My Title", default="Default Text", mask="?") print(result) diff --git a/pyautogui__keyboard__examples/message_box/prompt.py b/pyautogui__keyboard__examples/message_box/prompt.py index cfa67c035..026545a88 100644 --- a/pyautogui__keyboard__examples/message_box/prompt.py +++ b/pyautogui__keyboard__examples/message_box/prompt.py @@ -1,9 +1,11 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from pyautogui import prompt -result = prompt(text='My Text', title='My Title', default='Default Text') + + +result = prompt(text="My Text", title="My Title", default="Default Text") print(result) diff --git a/pyautogui__keyboard__examples/move_mouse_on_center.py b/pyautogui__keyboard__examples/move_mouse_on_center.py index 4e17f3bf7..fa0f598e1 100644 --- a/pyautogui__keyboard__examples/move_mouse_on_center.py +++ b/pyautogui__keyboard__examples/move_mouse_on_center.py @@ -1,9 +1,11 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import pyautogui + + screenWidth, screenHeight = pyautogui.size() pyautogui.moveTo(screenWidth / 2, screenHeight / 2) diff --git a/pyautogui__keyboard__examples/press_key_random_direction__pyautogui.typewrite.py b/pyautogui__keyboard__examples/press_key_random_direction__pyautogui.typewrite.py index 88d30c557..913f9b723 100644 --- a/pyautogui__keyboard__examples/press_key_random_direction__pyautogui.typewrite.py +++ b/pyautogui__keyboard__examples/press_key_random_direction__pyautogui.typewrite.py @@ -1,15 +1,15 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" +import random import pyautogui -# left up down right -directions = ['left', 'up', 'down', 'right'] -import random +# left up down right +directions = ["left", "up", "down", "right"] while True: direction = random.choice(directions) diff --git a/pyautogui__keyboard__examples/pressed_keys.py b/pyautogui__keyboard__examples/pressed_keys.py index b2488c6a2..520de3dcb 100644 --- a/pyautogui__keyboard__examples/pressed_keys.py +++ b/pyautogui__keyboard__examples/pressed_keys.py @@ -14,11 +14,11 @@ import keyboard -def print_pressed_keys(e): - line = ', '.join(str(code) for code in keyboard._pressed_events) +def print_pressed_keys(e) -> None: + line = ", ".join(str(code) for code in keyboard._pressed_events) # '\r' and end='' overwrites the previous line. # ' ' * 40 prints 40 spaces at the end to ensure the previous line is cleared. - print('\r' + line + ' ' * 40, end='') + print("\r" + line + " " * 40, end="") keyboard.hook(print_pressed_keys) diff --git a/pyautogui__keyboard__examples/pyautogui__ctrl+v.py b/pyautogui__keyboard__examples/pyautogui__ctrl+v.py index ef1ce5b92..42aae0e97 100644 --- a/pyautogui__keyboard__examples/pyautogui__ctrl+v.py +++ b/pyautogui__keyboard__examples/pyautogui__ctrl+v.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://pyautogui.readthedocs.io/en/latest/cheatsheet.html#keyboard-functions @@ -10,10 +10,11 @@ # pip install pyautogui import pyautogui + # Variant 1 pyautogui.hotkey("Ctrl", "V") # Variant 2 -pyautogui.keyDown('Ctrl') -pyautogui.press('V') -pyautogui.keyUp('Ctrl') +pyautogui.keyDown("Ctrl") +pyautogui.press("V") +pyautogui.keyUp("Ctrl") diff --git a/pyautogui__keyboard__examples/pyautogui_typewrite__hello_world.py b/pyautogui__keyboard__examples/pyautogui_typewrite__hello_world.py index 6e43a4f64..aa4710550 100644 --- a/pyautogui__keyboard__examples/pyautogui_typewrite__hello_world.py +++ b/pyautogui__keyboard__examples/pyautogui_typewrite__hello_world.py @@ -1,16 +1,17 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install pyautogui import pyautogui + # Prints out "Hello world!" instantly -pyautogui.typewrite('Hello world!') +pyautogui.typewrite("Hello world!") -pyautogui.press('enter') +pyautogui.press("enter") # Prints out "Hello world!" with a quarter second delay after each character -pyautogui.typewrite('Hello world!', interval=0.25) +pyautogui.typewrite("Hello world!", interval=0.25) diff --git a/pyautogui__keyboard__examples/quit_by_global_press_Escape.py b/pyautogui__keyboard__examples/quit_by_global_press_Escape.py index 88f79e9b7..ecdad9882 100644 --- a/pyautogui__keyboard__examples/quit_by_global_press_Escape.py +++ b/pyautogui__keyboard__examples/quit_by_global_press_Escape.py @@ -1,17 +1,20 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -QUIT_COMBINATION = 'Esc' -print('Press "{}" for QUIT'.format(QUIT_COMBINATION)) - import os +import time + import keyboard -keyboard.add_hotkey(QUIT_COMBINATION, lambda: print('Quit by Escape') or os._exit(0)) -import time + +QUIT_COMBINATION = "Esc" +print(f'Press "{QUIT_COMBINATION}" for QUIT') + + +keyboard.add_hotkey(QUIT_COMBINATION, lambda: print("Quit by Escape") or os._exit(0)) i = 0 while True: diff --git a/pyautogui__keyboard__examples/screenshot.py b/pyautogui__keyboard__examples/screenshot.py index 509ca7860..42af1e3dc 100644 --- a/pyautogui__keyboard__examples/screenshot.py +++ b/pyautogui__keyboard__examples/screenshot.py @@ -1,12 +1,14 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import pyautogui -im1 = pyautogui.screenshot() -print(im1) -im2 = pyautogui.screenshot('my_screenshot.png') # save file -print(im2) + +img1 = pyautogui.screenshot() +print(img1) + +img2 = pyautogui.screenshot("my_screenshot.png") # save file +print(img2) diff --git a/pyautogui__keyboard__examples/screenshot__pil_image__to__opencv_image.py b/pyautogui__keyboard__examples/screenshot__pil_image__to__opencv_image.py index 64d91d121..adacc32c8 100644 --- a/pyautogui__keyboard__examples/screenshot__pil_image__to__opencv_image.py +++ b/pyautogui__keyboard__examples/screenshot__pil_image__to__opencv_image.py @@ -1,21 +1,20 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -# from PIL import Image - -import pyautogui import cv2 import numpy +import pyautogui + pil_image = pyautogui.screenshot(region=(200, 200, 200, 200)) print(pil_image.size) -pil_image.show('pil_image') +pil_image.show("pil_image") opencv_image = cv2.cvtColor(numpy.array(pil_image), cv2.COLOR_RGB2BGR) print(opencv_image.shape[:2]) -cv2.imshow('opencv_image', opencv_image) +cv2.imshow("opencv_image", opencv_image) cv2.waitKey() diff --git a/pyautogui__keyboard__examples/screenshots_by_cycle.py b/pyautogui__keyboard__examples/screenshots_by_cycle.py index 9a13ad10d..81d6a4ce0 100644 --- a/pyautogui__keyboard__examples/screenshots_by_cycle.py +++ b/pyautogui__keyboard__examples/screenshots_by_cycle.py @@ -1,11 +1,12 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -import datetime as DT +import datetime as dt import time + from pathlib import Path import pyautogui @@ -13,12 +14,12 @@ DIR = Path(__file__).resolve().parent -DIR_SCREENSHOTS = DIR / 'screenshots' +DIR_SCREENSHOTS = DIR / "screenshots" DIR_SCREENSHOTS.mkdir(parents=True, exist_ok=True) while True: - file_name = str(DIR_SCREENSHOTS / f'{DT.datetime.now():%Y-%m-%d_%H%M%S.%f}.png') + file_name = str(DIR_SCREENSHOTS / f"{dt.datetime.now():%Y-%m-%d_%H%M%S.%f}.png") print(file_name) pyautogui.screenshot(file_name) # Save file diff --git a/pyautogui__keyboard__examples/telegram_smile_clicker/main.py b/pyautogui__keyboard__examples/telegram_smile_clicker/main.py index 04b131836..7a221651e 100644 --- a/pyautogui__keyboard__examples/telegram_smile_clicker/main.py +++ b/pyautogui__keyboard__examples/telegram_smile_clicker/main.py @@ -1,35 +1,38 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -def go(): - OPEN_SMILE_MENU = 'elements/open_smile_menu.png' - GROUP_SMILE = 'elements/group_smile.png' - CLICK_SMILE = 'elements/click_smile.png' - SEND = 'elements/send.png' +import logging +import sys +import time + +# OpenCv -- for performance +# pip install opencv-python +# +# pip install pyautogui +import pyautogui + + +def go() -> None: + OPEN_SMILE_MENU = "elements/open_smile_menu.png" + GROUP_SMILE = "elements/group_smile.png" + CLICK_SMILE = "elements/click_smile.png" + SEND = "elements/send.png" def get_logger(): - import logging - log = logging.getLogger('telegram_smile_clicker') + log = logging.getLogger("telegram_smile_clicker") log.setLevel(logging.DEBUG) - import sys sh = logging.StreamHandler(stream=sys.stdout) - sh.setFormatter(logging.Formatter('[%(asctime)s] %(message)s')) + sh.setFormatter(logging.Formatter("[%(asctime)s] %(message)s")) log.addHandler(sh) return log log = get_logger() - log_it = lambda pos, filename: log.debug('{} [{}]'.format(pos, filename)) - - # OpenCv -- for performance - # pip install opencv-python - - # pip install pyautogui - import pyautogui + log_it = lambda pos, filename: log.debug(f"{pos} [{filename}]") # Ищем меню с смайлами pos = pyautogui.locateCenterOnScreen(OPEN_SMILE_MENU) @@ -41,7 +44,6 @@ def get_logger(): pyautogui.moveTo(pos) # Ожидаем - import time time.sleep(1) # Ищем наш смайл в меню @@ -80,7 +82,7 @@ def get_logger(): pyautogui.click(pos) -if __name__ == '__main__': +if __name__ == "__main__": # import profile # profile.run('go()') diff --git a/pyautogui__keyboard__examples/wait_and_draw__hotkey__dragRel.py b/pyautogui__keyboard__examples/wait_and_draw__hotkey__dragRel.py index db1e27933..daaa48ec3 100644 --- a/pyautogui__keyboard__examples/wait_and_draw__hotkey__dragRel.py +++ b/pyautogui__keyboard__examples/wait_and_draw__hotkey__dragRel.py @@ -1,30 +1,33 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -RUN_COMBINATION = 'Ctrl+Shift+R' -QUIT_COMBINATION = 'Esc' - -print('Press "{}" for RUN'.format(RUN_COMBINATION)) -print('Press "{}" for QUIT'.format(QUIT_COMBINATION)) - import os -import keyboard -keyboard.add_hotkey(QUIT_COMBINATION, lambda: print('Quit by Escape') or os._exit(0)) -keyboard.wait(RUN_COMBINATION) +import keyboard import pyautogui + +RUN_COMBINATION = "Ctrl+Shift+R" +QUIT_COMBINATION = "Esc" + +print(f'Press "{RUN_COMBINATION}" for RUN') +print(f'Press "{QUIT_COMBINATION}" for QUIT') + + +keyboard.add_hotkey(QUIT_COMBINATION, lambda: print("Quit by Escape") or os._exit(0)) +keyboard.wait(RUN_COMBINATION) + indent = 2 duration = 0.1 distance = 300 while distance > 0: - pyautogui.dragRel(distance, 0, duration) # move right + pyautogui.dragRel(distance, 0, duration) # move right distance -= indent - pyautogui.dragRel(0, distance, duration) # move down + pyautogui.dragRel(0, distance, duration) # move down pyautogui.dragRel(-distance, 0, duration) # move left distance -= indent pyautogui.dragRel(0, -distance, duration) # move up diff --git a/pyautogui__keyboard__examples/win_calc_clicker/main.py b/pyautogui__keyboard__examples/win_calc_clicker/main.py index 80bd5ac7a..812b850f0 100644 --- a/pyautogui__keyboard__examples/win_calc_clicker/main.py +++ b/pyautogui__keyboard__examples/win_calc_clicker/main.py @@ -1,32 +1,36 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" + + +import os +import time + +# OpenCv -- for performance +# pip install opencv-python +# +# pip install pyautogui +import pyautogui BUTTONS = { - '+': 'buttons/add.png', - '-': 'buttons/sub.png', - '/': 'buttons/div.png', - '*': 'buttons/mul.png', - '=': 'buttons/equal.png', + "+": "buttons/add.png", + "-": "buttons/sub.png", + "/": "buttons/div.png", + "*": "buttons/mul.png", + "=": "buttons/equal.png", } for i in range(10): - BUTTONS[str(i)] = 'buttons/{}.png'.format(i) + BUTTONS[str(i)] = f"buttons/{i}.png" CACHE_POS_BUTTON = dict() -def go(expression): - # OpenCv -- for performance - # pip install opencv-python - - # pip install pyautogui - import pyautogui - +def go(expression) -> None: for x in expression: if x not in BUTTONS: - print('Not found: "{}"'.format(x)) + print(f'Not found: "{x}"') continue if x in CACHE_POS_BUTTON: @@ -43,16 +47,13 @@ def go(expression): pyautogui.click(pos) -def show_test_calc(): - import os - os.startfile('calc.exe') - - import time +def show_test_calc() -> None: + os.startfile("calc.exe") time.sleep(1) -if __name__ == '__main__': +if __name__ == "__main__": show_test_calc() - expression = '1234 * 222 + 3214 = ' + expression = "1234 * 222 + 3214 = " go(expression) diff --git a/pyautogui__keyboard__examples/win_calc_clicker/main__PI_clicker_monster.py b/pyautogui__keyboard__examples/win_calc_clicker/main__PI_clicker_monster.py index ccc4ef5a7..407699b56 100644 --- a/pyautogui__keyboard__examples/win_calc_clicker/main__PI_clicker_monster.py +++ b/pyautogui__keyboard__examples/win_calc_clicker/main__PI_clicker_monster.py @@ -1,12 +1,16 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from main import go, show_test_calc + + show_test_calc() -expression = '3,14159265358979323846264338327950288419716939937510582097494459230781640628620' \ - '8998628034825342117067982148086513282306647093844609550582231725359408128481117' +expression = ( + "3,14159265358979323846264338327950288419716939937510582097494459230781640628620" + "8998628034825342117067982148086513282306647093844609550582231725359408128481117" +) go(expression) diff --git a/pyautogui__keyboard__examples/win_calc_clicker/main__locateAllOnScreen.py b/pyautogui__keyboard__examples/win_calc_clicker/main__locateAllOnScreen.py index 3bd1b841e..9694f9048 100644 --- a/pyautogui__keyboard__examples/win_calc_clicker/main__locateAllOnScreen.py +++ b/pyautogui__keyboard__examples/win_calc_clicker/main__locateAllOnScreen.py @@ -1,43 +1,47 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" +# OpenCv -- for performance +# pip install opencv-python +# +# pip install pyautogui +import pyautogui + from main import show_test_calc + + show_test_calc() BUTTONS = { - '+': 'buttons/add.png', - '-': 'buttons/sub.png', - '/': 'buttons/div.png', - '*': 'buttons/mul.png', - '=': 'buttons/equal.png', + "+": "buttons/add.png", + "-": "buttons/sub.png", + "/": "buttons/div.png", + "*": "buttons/mul.png", + "=": "buttons/equal.png", } for i in range(10): - BUTTONS[str(i)] = 'buttons/{}.png'.format(i) + BUTTONS[str(i)] = f"buttons/{i}.png" cache_pos_button = dict() -# OpenCv -- for performance -# pip install opencv-python - -# pip install pyautogui -import pyautogui - -expression = '1234 * 222 + 3214 = ' +expression = "1234 * 222 + 3214 = " for x in expression: if x not in BUTTONS: - print('Not found: "{}"'.format(x)) + print(f'Not found: "{x}"') continue if x in cache_pos_button: pos_list = cache_pos_button[x] else: file_name = BUTTONS[x] - pos_list = [pyautogui.center(pos) for pos in pyautogui.locateAllOnScreen(BUTTONS[x])] + pos_list = [ + pyautogui.center(pos) for pos in pyautogui.locateAllOnScreen(BUTTONS[x]) + ] if not pos_list: continue diff --git a/pycountry__examples/get_ru_country_by_alpha2.py b/pycountry__examples/get_ru_country_by_alpha2.py index 92055786b..2c5b83f5f 100644 --- a/pycountry__examples/get_ru_country_by_alpha2.py +++ b/pycountry__examples/get_ru_country_by_alpha2.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import gettext @@ -18,7 +18,7 @@ def get_country(code): return _(ru.name) -if __name__ == '__main__': +if __name__ == "__main__": print(get_country("RU")) # Российская Федерация diff --git a/pycryptodome__examples__AES_DES_RSA/AES_with_password__verify_key.py b/pycryptodome__examples__AES_DES_RSA/AES_with_password__verify_key.py index d7151cb31..1c3a930a1 100644 --- a/pycryptodome__examples__AES_DES_RSA/AES_with_password__verify_key.py +++ b/pycryptodome__examples__AES_DES_RSA/AES_with_password__verify_key.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import base64 @@ -18,22 +18,26 @@ class AuthenticationError(Exception): class CryptoAES: - def __init__(self, key: str): - self.key = hashlib.sha256(key.encode('utf-8')).digest() + def __init__(self, key: str) -> None: + self.key = hashlib.sha256(key.encode("utf-8")).digest() def encrypt(self, plain_text: str) -> str: - data = plain_text.encode('utf-8') + data = plain_text.encode("utf-8") cipher = AES.new(self.key, AES.MODE_EAX) cipher_text, tag = cipher.encrypt_and_digest(data) encrypted_data = cipher.nonce + tag + cipher_text - return base64.b64encode(encrypted_data).decode('utf-8') + return base64.b64encode(encrypted_data).decode("utf-8") def decrypt(self, encrypted_text: str) -> str: encrypted_data = base64.b64decode(encrypted_text) - nonce, tag, cipher_text = encrypted_data[:16], encrypted_data[16:32], encrypted_data[32:] + nonce, tag, cipher_text = ( + encrypted_data[:16], + encrypted_data[16:32], + encrypted_data[32:], + ) cipher = AES.new(self.key, AES.MODE_EAX, nonce) try: @@ -41,12 +45,12 @@ def decrypt(self, encrypted_text: str) -> str: except ValueError as e: raise AuthenticationError(e) - return data.decode('utf-8') + return data.decode("utf-8") -if __name__ == '__main__': - text = 'Hello World!' - password = '123' +if __name__ == "__main__": + text = "Hello World!" + password = "123" crypto = CryptoAES(password) encrypted_text = crypto.encrypt(text) @@ -57,6 +61,6 @@ def decrypt(self, encrypted_text: str) -> str: # Decrypt with invalid password try: - CryptoAES('abc').decrypt(encrypted_text) + CryptoAES("abc").decrypt(encrypted_text) except AuthenticationError as e: - assert str(e) == 'MAC check failed' + assert str(e) == "MAC check failed" diff --git a/pycryptodome__examples__AES_DES_RSA/RSA__examples/export_import_keys__with__encrypt_decrypt/main.py b/pycryptodome__examples__AES_DES_RSA/RSA__examples/export_import_keys__with__encrypt_decrypt/main.py index 64b0613b4..eddc432a6 100644 --- a/pycryptodome__examples__AES_DES_RSA/RSA__examples/export_import_keys__with__encrypt_decrypt/main.py +++ b/pycryptodome__examples__AES_DES_RSA/RSA__examples/export_import_keys__with__encrypt_decrypt/main.py @@ -1,23 +1,24 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # Для импорта rsa import sys -sys.path.append('..') + +sys.path.append("..") import rsa -FILE_NAME_PUBLIC_KEY = 'public.pem' -FILE_NAME_PRIVATE_KEY = 'private.pem' +FILE_NAME_PUBLIC_KEY = "public.pem" +FILE_NAME_PRIVATE_KEY = "private.pem" -with open(FILE_NAME_PUBLIC_KEY, 'rb') as f: +with open(FILE_NAME_PUBLIC_KEY, "rb") as f: public_pem = f.read() -with open(FILE_NAME_PRIVATE_KEY, 'rb') as f: +with open(FILE_NAME_PRIVATE_KEY, "rb") as f: private_pem = f.read() if public_pem and private_pem: @@ -25,27 +26,29 @@ private_key = rsa.import_key(private_pem) else: - print(f'The {repr(FILE_NAME_PUBLIC_KEY)} or {repr(FILE_NAME_PRIVATE_KEY)} files are empty! ' - f'Performing key generation!') + print( + f"The {repr(FILE_NAME_PUBLIC_KEY)} or {repr(FILE_NAME_PRIVATE_KEY)} files are empty! " + f"Performing key generation!" + ) public_key, private_key = rsa.new_keys(key_size=2048) - print('Key generation completed successfully!') + print("Key generation completed successfully!") - with open(FILE_NAME_PUBLIC_KEY, 'wb') as f: - f.write(public_key.exportKey('PEM')) + with open(FILE_NAME_PUBLIC_KEY, "wb") as f: + f.write(public_key.exportKey("PEM")) - with open(FILE_NAME_PRIVATE_KEY, 'wb') as f: - f.write(private_key.exportKey('PEM')) + with open(FILE_NAME_PRIVATE_KEY, "wb") as f: + f.write(private_key.exportKey("PEM")) - print('Successfully save generated keys to files!') + print("Successfully save generated keys to files!") print() -print('private:', private_key.exportKey('PEM')) -print('public:', public_key.exportKey('PEM')) +print("private:", private_key.exportKey("PEM")) +print("public:", public_key.exportKey("PEM")) print() -text = 'Hello World!'.encode('utf-8') +text = "Hello World!".encode("utf-8") encrypted = rsa.encrypt(text, public_key) print(encrypted) diff --git "a/pycryptodome__examples__AES_DES_RSA/RSA__examples/ge\320\277enerate_public_key_and_private_key/main.py" "b/pycryptodome__examples__AES_DES_RSA/RSA__examples/ge\320\277enerate_public_key_and_private_key/main.py" index 0e364e905..0512d7a76 100644 --- "a/pycryptodome__examples__AES_DES_RSA/RSA__examples/ge\320\277enerate_public_key_and_private_key/main.py" +++ "b/pycryptodome__examples__AES_DES_RSA/RSA__examples/ge\320\277enerate_public_key_and_private_key/main.py" @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/Legrandin/pycryptodome/blob/29810b96cefc900138523203f027a179a15ceffe/Doc/src/examples.rst#generate-public-key-and-private-key @@ -10,8 +10,6 @@ # pip install pycryptodome # OR: # pip install pycryptodomex - - from Crypto.PublicKey import RSA diff --git "a/pycryptodome__examples__AES_DES_RSA/RSA__examples/ge\320\277enerate_public_key_and_private_key__passphrase_and_scrypt/main.py" "b/pycryptodome__examples__AES_DES_RSA/RSA__examples/ge\320\277enerate_public_key_and_private_key__passphrase_and_scrypt/main.py" index cc84ee883..b7050dda3 100644 --- "a/pycryptodome__examples__AES_DES_RSA/RSA__examples/ge\320\277enerate_public_key_and_private_key__passphrase_and_scrypt/main.py" +++ "b/pycryptodome__examples__AES_DES_RSA/RSA__examples/ge\320\277enerate_public_key_and_private_key__passphrase_and_scrypt/main.py" @@ -1,21 +1,21 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install pycryptodome # OR: # pip install pycryptodomex - - from Crypto.PublicKey import RSA SECRET_CODE = "Unguessable" key = RSA.generate(2048) -encrypted_key = key.export_key(passphrase=SECRET_CODE, pkcs=8, protection="scryptAndAES128-CBC") +encrypted_key = key.export_key( + passphrase=SECRET_CODE, pkcs=8, protection="scryptAndAES128-CBC" +) with open("rsa_key.bin", "wb") as f: f.write(encrypted_key) diff --git "a/pycryptodome__examples__AES_DES_RSA/RSA__examples/ge\320\277enerate_public_key_and_private_key__with__passphrase/main.py" "b/pycryptodome__examples__AES_DES_RSA/RSA__examples/ge\320\277enerate_public_key_and_private_key__with__passphrase/main.py" index fe016325b..20b31880d 100644 --- "a/pycryptodome__examples__AES_DES_RSA/RSA__examples/ge\320\277enerate_public_key_and_private_key__with__passphrase/main.py" +++ "b/pycryptodome__examples__AES_DES_RSA/RSA__examples/ge\320\277enerate_public_key_and_private_key__with__passphrase/main.py" @@ -1,14 +1,12 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install pycryptodome # OR: # pip install pycryptodomex - - from Crypto.PublicKey import RSA diff --git a/pycryptodome__examples__AES_DES_RSA/RSA__examples/main.py b/pycryptodome__examples__AES_DES_RSA/RSA__examples/main.py index 72fb9faf6..197e4dde8 100644 --- a/pycryptodome__examples__AES_DES_RSA/RSA__examples/main.py +++ b/pycryptodome__examples__AES_DES_RSA/RSA__examples/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://stackoverflow.com/a/42856051/5909792 @@ -12,12 +12,12 @@ import rsa -msg1 = "Hello Tony, I am Jarvis!".encode('utf-8') -msg2 = "Hello Toni, I am Jarvis!".encode('utf-8') +msg1 = "Hello Tony, I am Jarvis!".encode("utf-8") +msg2 = "Hello Toni, I am Jarvis!".encode("utf-8") public, private = rsa.new_keys(key_size=2048) -print('private:', private.exportKey('PEM')) -print('public:', public.exportKey('PEM')) +print("private:", private.exportKey("PEM")) +print("public:", public.exportKey("PEM")) print() # Encrypt-Decrypt: private -> private @@ -39,7 +39,7 @@ print("Verify:", verify) # False # Encrypt-Decrypt: private -> private -print('\n') +print("\n") # Encrypt-Decrypt: public -> private encrypted = b64encode(rsa.encrypt(msg1, public)) diff --git a/pycryptodome__examples__AES_DES_RSA/RSA__examples/read_private_key_and_get_public/main.py b/pycryptodome__examples__AES_DES_RSA/RSA__examples/read_private_key_and_get_public/main.py index 78a60d081..88984e6fd 100644 --- a/pycryptodome__examples__AES_DES_RSA/RSA__examples/read_private_key_and_get_public/main.py +++ b/pycryptodome__examples__AES_DES_RSA/RSA__examples/read_private_key_and_get_public/main.py @@ -1,14 +1,12 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install pycryptodome # OR: # pip install pycryptodomex - - from Crypto.PublicKey import RSA diff --git a/pycryptodome__examples__AES_DES_RSA/RSA__examples/read_private_key_and_get_public/main__use_file.py b/pycryptodome__examples__AES_DES_RSA/RSA__examples/read_private_key_and_get_public/main__use_file.py index a75143b8e..d1ba5eeec 100644 --- a/pycryptodome__examples__AES_DES_RSA/RSA__examples/read_private_key_and_get_public/main__use_file.py +++ b/pycryptodome__examples__AES_DES_RSA/RSA__examples/read_private_key_and_get_public/main__use_file.py @@ -1,21 +1,21 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install pycryptodome # OR: # pip install pycryptodomex - - from Crypto.PublicKey import RSA SECRET_CODE = "Unguessable" key = RSA.generate(2048) -encrypted_key = key.export_key(passphrase=SECRET_CODE, pkcs=8, protection="scryptAndAES128-CBC") +encrypted_key = key.export_key( + passphrase=SECRET_CODE, pkcs=8, protection="scryptAndAES128-CBC" +) with open("rsa_key.bin", "wb") as f: f.write(encrypted_key) diff --git a/pycryptodome__examples__AES_DES_RSA/hello_world__AES_pkcs7_ECB.py b/pycryptodome__examples__AES_DES_RSA/hello_world__AES_pkcs7_ECB.py index 8e7d1c046..dc6768e73 100644 --- a/pycryptodome__examples__AES_DES_RSA/hello_world__AES_pkcs7_ECB.py +++ b/pycryptodome__examples__AES_DES_RSA/hello_world__AES_pkcs7_ECB.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install pycryptodome @@ -12,15 +12,16 @@ # pip install pkcs7 from pkcs7 import PKCS7Encoder -key = '12647863128053333215894456187814' + +key = "12647863128053333215894456187814" mode = AES.MODE_ECB -text = 'Hello World!' +text = "Hello World!" encoder = PKCS7Encoder() pad_text = encoder.encode(text) -print(pad_text) # "Hello World!" -print(bytes(pad_text, 'utf-8')) # b'Hello World!\x04\x04\x04\x04' +print(pad_text) # "Hello World!" +print(bytes(pad_text, "utf-8")) # b'Hello World!\x04\x04\x04\x04' encryptor = AES.new(key, mode) @@ -32,7 +33,7 @@ decrypt_text = encryptor.decrypt(cipher) print(decrypt_text) # b'Hello World!\x04\x04\x04\x04' -decrypt_text = str(decrypt_text, 'utf-8') +decrypt_text = str(decrypt_text, "utf-8") unpad_text = encoder.decode(decrypt_text) print(unpad_text) # Hello World! diff --git a/pycryptodome__examples__AES_DES_RSA/hello_world__DES.py b/pycryptodome__examples__AES_DES_RSA/hello_world__DES.py index 910f3db02..e57c1f305 100644 --- a/pycryptodome__examples__AES_DES_RSA/hello_world__DES.py +++ b/pycryptodome__examples__AES_DES_RSA/hello_world__DES.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/Legrandin/pycryptodome @@ -10,8 +10,6 @@ # pip install pycryptodome # OR: # pip install pycryptodomex - - from Crypto.Cipher import DES @@ -26,11 +24,11 @@ def unpad(s: bytes) -> bytes: return s[:-pad_size] -key = b'abcdefgh' +key = b"abcdefgh" des = DES.new(key, DES.MODE_ECB) -text = b'Hello World!' +text = b"Hello World!" padded_text = pad(text) encrypted_text = des.encrypt(padded_text) diff --git a/pycryptodome__examples__AES_DES_RSA/info_security.py b/pycryptodome__examples__AES_DES_RSA/info_security.py index c6477c143..4af65909b 100644 --- a/pycryptodome__examples__AES_DES_RSA/info_security.py +++ b/pycryptodome__examples__AES_DES_RSA/info_security.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import base64 @@ -14,26 +14,26 @@ from Crypto import Random -AES_key = '' +AES_key = "" # SOURCE: https://github.com/maldevel/gdog/blob/a3a47b17231d0ee3a2d0fa8ac986f7485f3f6b82/gdog.py#L68 class InfoSecurity: - def __init__(self, key: str): + def __init__(self, key: str) -> None: self.bs = 32 self.key = hashlib.sha256(key.encode()).digest() def encrypt(self, plain_text: str) -> str: - raw = self._pad(plain_text.encode('utf-8')) + raw = self._pad(plain_text.encode("utf-8")) iv = Random.new().read(AES.block_size) cipher = AES.new(self.key, AES.MODE_CBC, iv) - return base64.b64encode(iv + cipher.encrypt(raw)).decode('utf-8') + return base64.b64encode(iv + cipher.encrypt(raw)).decode("utf-8") def decrypt(self, cipher_text: str) -> str: enc = base64.b64decode(cipher_text) - iv = enc[:AES.block_size] + iv = enc[: AES.block_size] cipher = AES.new(self.key, AES.MODE_CBC, iv) - return self._unpad(cipher.decrypt(enc[AES.block_size:])).decode('utf-8') + return self._unpad(cipher.decrypt(enc[AES.block_size :])).decode("utf-8") # SOURCE: https://github.com/gil9red/SimplePyScripts/blob/82810f8397907679316107f135a5b1dd1fcca516/pad__unpad__example.py#L7 def _pad(self, s: bytes) -> bytes: @@ -46,15 +46,15 @@ def _unpad(s: bytes) -> bytes: return s[:-pad_size] -if __name__ == '__main__': +if __name__ == "__main__": info_sec = InfoSecurity(AES_key) - text = 'Hello World!' + text = "Hello World!" cipher_text = info_sec.encrypt(text) print(cipher_text) assert info_sec.decrypt(cipher_text) == text - text = 'Привет Мир!' + text = "Привет Мир!" cipher_text = info_sec.encrypt(text) print(cipher_text) assert info_sec.decrypt(cipher_text) == text diff --git a/pygame__examples/about_joystick.py b/pygame__examples/about_joystick.py index a7f2fe86f..5e9864c45 100644 --- a/pygame__examples/about_joystick.py +++ b/pygame__examples/about_joystick.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://www.pygame.org/docs/ref/joystick.html @@ -9,6 +9,7 @@ import pygame + # Define some colors BLACK = (0, 0, 0) WHITE = (255, 255, 255) @@ -18,24 +19,24 @@ # It has nothing to do with the joysticks, just outputting the # information. class TextPrint: - def __init__(self): + def __init__(self) -> None: self.reset() self.font = pygame.font.Font(None, 20) - def print(self, screen, textString): + def print(self, screen, textString) -> None: textBitmap = self.font.render(textString, True, BLACK) screen.blit(textBitmap, [self.x, self.y]) self.y += self.line_height - def reset(self): + def reset(self) -> None: self.x = 10 self.y = 10 self.line_height = 15 - def indent(self): + def indent(self) -> None: self.x += 10 - def unindent(self): + def unindent(self) -> None: self.x -= 10 @@ -81,7 +82,7 @@ def unindent(self): # Get count of joysticks joystick_count = pygame.joystick.get_count() - textPrint.print(screen, "Number of joysticks: {}".format(joystick_count)) + textPrint.print(screen, f"Number of joysticks: {joystick_count}") textPrint.indent() # For each joystick: @@ -89,42 +90,42 @@ def unindent(self): joystick = pygame.joystick.Joystick(i) joystick.init() - textPrint.print(screen, "Joystick {}".format(i)) + textPrint.print(screen, f"Joystick {i}") textPrint.indent() # Get the name from the OS for the controller/joystick name = joystick.get_name() - textPrint.print(screen, "Joystick name: {}".format(name)) + textPrint.print(screen, f"Joystick name: {name}") # Usually axis run in pairs, up/down for one, and left/right for # the other. axes = joystick.get_numaxes() - textPrint.print(screen, "Number of axes: {}".format(axes)) + textPrint.print(screen, f"Number of axes: {axes}") textPrint.indent() for i in range(axes): axis = joystick.get_axis(i) - textPrint.print(screen, "Axis {} value: {:>6.3f}".format(i, axis)) + textPrint.print(screen, f"Axis {i} value: {axis:>6.3f}") textPrint.unindent() buttons = joystick.get_numbuttons() - textPrint.print(screen, "Number of buttons: {}".format(buttons)) + textPrint.print(screen, f"Number of buttons: {buttons}") textPrint.indent() for i in range(buttons): button = joystick.get_button(i) - textPrint.print(screen, "Button {:>2} value: {}".format(i, button)) + textPrint.print(screen, f"Button {i:>2} value: {button}") textPrint.unindent() # Hat switch. All or nothing for direction, not like joysticks. # Value comes back in an array. hats = joystick.get_numhats() - textPrint.print(screen, "Number of hats: {}".format(hats)) + textPrint.print(screen, f"Number of hats: {hats}") textPrint.indent() for i in range(hats): hat = joystick.get_hat(i) - textPrint.print(screen, "Hat {} value: {}".format(i, str(hat))) + textPrint.print(screen, f"Hat {i} value: {str(hat)}") textPrint.unindent() textPrint.unindent() diff --git a/pygame__examples/buttons__using_pygametext.py b/pygame__examples/buttons__using_pygametext.py index 6452c7012..abe72750f 100644 --- a/pygame__examples/buttons__using_pygametext.py +++ b/pygame__examples/buttons__using_pygametext.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import sys @@ -11,6 +11,7 @@ import pygame import pygametext + running = True pygame.init() @@ -20,17 +21,23 @@ pgt = pygametext.PGT(screen) # Define pygametext object. -pgt.button(10, 10, 100, 50, (255, 0, 0), "Hello!", (0, 0, 0), print, "Hello World!", 0) # Add pgt Button -pgt.button(120, 10, 100, 50, (255, 255, 0), "Bye bye", (0, 0, 0), print, "Goodbye World!", 0) # Add pgt Button +pgt.button( + 10, 10, 100, 50, (255, 0, 0), "Hello!", (0, 0, 0), print, "Hello World!", 0 +) # Add pgt Button +pgt.button( + 120, 10, 100, 50, (255, 255, 0), "Bye bye", (0, 0, 0), print, "Goodbye World!", 0 +) # Add pgt Button pgt.text(10, 70, "Simple pygametext example.", (0, 120, 0), 20, 0) # Add pgt Text -def update(): # Update & Eventd +def update() -> None: # Update & Eventd global running events = pygame.event.get() - pgt.update(events, 0) # Update all pgt elements from layer 0. Takes events arg to process some elements. + pgt.update( + events, 0 + ) # Update all pgt elements from layer 0. Takes events arg to process some elements. for event in events: if event.type == pygame.QUIT: @@ -39,7 +46,7 @@ def update(): # Update & Eventd sys.exit() -def draw(): +def draw() -> None: screen.fill((255, 255, 255)) # Clear screen pgt.draw() # Draw all pgt elements from layer 0 diff --git a/pygame__examples/circle_collision.py b/pygame__examples/circle_collision.py index 436fbca9d..83290096a 100644 --- a/pygame__examples/circle_collision.py +++ b/pygame__examples/circle_collision.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import sys @@ -10,12 +10,12 @@ import pygame -BLACK = ( 0, 0, 0) +BLACK = (0, 0, 0) WHITE = (255, 255, 255) class Ball(pygame.sprite.Sprite): - def __init__(self, size, pos=(0, 0), color=WHITE): + def __init__(self, size, pos=(0, 0), color=WHITE) -> None: super().__init__() self.image = pygame.Surface([size, size], pygame.SRCALPHA) diff --git a/pygame__examples/complex_balls_with_physics.py b/pygame__examples/complex_balls_with_physics.py index 45ae742f9..f6fc2c93a 100644 --- a/pygame__examples/complex_balls_with_physics.py +++ b/pygame__examples/complex_balls_with_physics.py @@ -1,12 +1,11 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import math import random -from typing import Tuple, Optional # SOURCE: http://archive.petercollingridge.co.uk/book/export/html/6 import pygame @@ -19,7 +18,7 @@ gravity = (math.pi, 0.002) -def add_vectors(angle1, length1, angle2, length2) -> Tuple[float, float]: +def add_vectors(angle1, length1, angle2, length2) -> tuple[float, float]: x = math.sin(angle1) * length1 + math.sin(angle2) * length2 y = math.cos(angle1) * length1 + math.cos(angle2) * length2 @@ -29,7 +28,7 @@ def add_vectors(angle1, length1, angle2, length2) -> Tuple[float, float]: return angle, length -def collide(p1, p2): +def collide(p1, p2) -> None: dx = p1.x - p2.x dy = p1.y - p2.y @@ -39,12 +38,16 @@ def collide(p1, p2): total_mass = p1.mass + p2.mass (p1.angle, p1.speed) = add_vectors( - p1.angle, p1.speed * (p1.mass - p2.mass) / total_mass, - angle, 2 * p2.speed * p2.mass / total_mass + p1.angle, + p1.speed * (p1.mass - p2.mass) / total_mass, + angle, + 2 * p2.speed * p2.mass / total_mass, ) (p2.angle, p2.speed) = add_vectors( - p2.angle, p2.speed * (p2.mass - p1.mass) / total_mass, - angle + math.pi, 2 * p1.speed * p1.mass / total_mass + p2.angle, + p2.speed * (p2.mass - p1.mass) / total_mass, + angle + math.pi, + 2 * p1.speed * p1.mass / total_mass, ) p1.speed *= elasticity p2.speed *= elasticity @@ -57,7 +60,7 @@ def collide(p1, p2): class Particle: - def __init__(self, x, y, size, mass=1): + def __init__(self, x, y, size, mass=1) -> None: self.x = x self.y = y self.size = size @@ -68,27 +71,25 @@ def __init__(self, x, y, size, mass=1): self.mass = mass self.drag = (self.mass / (self.mass + mass_of_air)) ** self.size - def display(self): + def display(self) -> None: pygame.draw.circle( - screen, self.colour, - (int(self.x), int(self.y)), - self.size, self.thickness + screen, self.colour, (int(self.x), int(self.y)), self.size, self.thickness ) - def move(self): + def move(self) -> None: self.x += math.sin(self.angle) * self.speed self.y -= math.cos(self.angle) * self.speed self.speed *= self.drag - def bounce(self): + def bounce(self) -> None: if self.x > width - self.size: self.x = 2 * (width - self.size) - self.x - self.angle = - self.angle + self.angle = -self.angle self.speed *= elasticity elif self.x < self.size: self.x = 2 * self.size - self.x - self.angle = - self.angle + self.angle = -self.angle self.speed *= elasticity if self.y > height - self.size: @@ -102,20 +103,20 @@ def bounce(self): self.speed *= elasticity -def find_particle(particles, x, y) -> Optional[Particle]: +def find_particle(particles, x, y) -> Particle | None: for p in particles: if math.hypot(p.x - x, p.y - y) <= p.size: return p return None -def get_rand_color() -> Tuple[int, int, int]: +def get_rand_color() -> tuple[int, int, int]: return random.randint(0, 255), random.randint(0, 255), random.randint(0, 255) clock = pygame.time.Clock() screen = pygame.display.set_mode((width, height)) -pygame.display.set_caption('Tutorial 9') +pygame.display.set_caption("Tutorial 9") number_of_particles = 5 my_particles = [] @@ -126,7 +127,7 @@ def get_rand_color() -> Tuple[int, int, int]: x = random.randint(size, width - size) y = random.randint(size, height - size) - particle = Particle(x, y, size, density * size ** 2) + particle = Particle(x, y, size, density * size**2) particle.colour = get_rand_color() particle.speed = random.random() particle.angle = random.uniform(0, math.pi * 2) @@ -157,7 +158,7 @@ def get_rand_color() -> Tuple[int, int, int]: for i, particle in enumerate(my_particles): particle.move() particle.bounce() - for particle2 in my_particles[i + 1:]: + for particle2 in my_particles[i + 1 :]: collide(particle, particle2) particle.display() diff --git a/pygame__examples/is_point_in_triangle.py b/pygame__examples/is_point_in_triangle.py index 8adc9f947..911a0aeae 100644 --- a/pygame__examples/is_point_in_triangle.py +++ b/pygame__examples/is_point_in_triangle.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from dataclasses import dataclass @@ -10,7 +10,7 @@ import pygame -BLACK = ( 0, 0, 0) +BLACK = (0, 0, 0) WHITE = (255, 255, 255) @@ -27,8 +27,12 @@ def get_triangle_area(a: Point, b: Point, c: Point) -> float: def is_point_in_triangle(a: Point, b: Point, c: Point, p: Point) -> bool: tr_area = get_triangle_area(a, b, c) # Площадь основного треугольника - tr_area2 = get_triangle_area(a, b, p) # Площади треугольника, образованного из 2 точек основного - tr_area3 = get_triangle_area(a, p, c) # и точки, которая проверяется на принадлежность + tr_area2 = get_triangle_area( + a, b, p + ) # Площади треугольника, образованного из 2 точек основного + tr_area3 = get_triangle_area( + a, p, c + ) # и точки, которая проверяется на принадлежность tr_area4 = get_triangle_area(b, p, c) # к треугольнику # Если площади образованных треугольников равны, то точка в треугольнике diff --git a/pygame__examples/move_circle.py b/pygame__examples/move_circle.py index e9ffd57fa..e79999859 100644 --- a/pygame__examples/move_circle.py +++ b/pygame__examples/move_circle.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install pygame @@ -58,6 +58,6 @@ if is_pressed[pygame.K_DOWN]: y += step - pygame.display.set_caption("move_circle [{} fps]".format(int(clock.get_fps()))) + pygame.display.set_caption(f"move_circle [{int(clock.get_fps())} fps]") clock.tick(FPS) diff --git a/pygame__examples/move_rect.py b/pygame__examples/move_rect.py index 48da911de..040bffe9f 100644 --- a/pygame__examples/move_rect.py +++ b/pygame__examples/move_rect.py @@ -1,13 +1,15 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: http://www.nerdparadise.com/programming/pygame/part1 # pip install pygame import pygame + + pygame.init() screen = pygame.display.set_mode((400, 300)) @@ -53,7 +55,7 @@ pygame.display.flip() - pygame.display.set_caption("move_rect [{} fps]".format(int(clock.get_fps()))) + pygame.display.set_caption(f"move_rect [{int(clock.get_fps())} fps]") # will block execution until 1/60 seconds have passed # since the previous time clock.tick was called. diff --git a/pygame__examples/move_rect__with_joystick__hat_axis_button.py b/pygame__examples/move_rect__with_joystick__hat_axis_button.py index 89f576cd0..afbe15f66 100644 --- a/pygame__examples/move_rect__with_joystick__hat_axis_button.py +++ b/pygame__examples/move_rect__with_joystick__hat_axis_button.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: http://www.nerdparadise.com/programming/pygame/part1 @@ -85,7 +85,7 @@ pygame.display.flip() - pygame.display.set_caption("move_rect [{} fps]".format(int(clock.get_fps()))) + pygame.display.set_caption(f"move_rect [{int(clock.get_fps())} fps]") # will block execution until 1/60 seconds have passed # since the previous time clock.tick was called. diff --git a/pygame__examples/multiline_text__using_ptext/invisible_text.py b/pygame__examples/multiline_text__using_ptext/invisible_text.py index c0e6c394b..6dab8fb77 100644 --- a/pygame__examples/multiline_text__using_ptext/invisible_text.py +++ b/pygame__examples/multiline_text__using_ptext/invisible_text.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import sys @@ -12,7 +12,7 @@ import ptext -BLACK = ( 0, 0, 0) +BLACK = (0, 0, 0) WHITE = (255, 255, 255) diff --git a/pygame__examples/on_press_lctrl+backspace__event.key+key.get_mods.py b/pygame__examples/on_press_lctrl+backspace__event.key+key.get_mods.py index 02bfaf0dc..790bb8938 100644 --- a/pygame__examples/on_press_lctrl+backspace__event.key+key.get_mods.py +++ b/pygame__examples/on_press_lctrl+backspace__event.key+key.get_mods.py @@ -1,15 +1,15 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import pygame -import pygame.locals + pygame.init() -screen = pygame.display.set_mode((300,200)) +screen = pygame.display.set_mode((300, 200)) running = True @@ -20,7 +20,10 @@ elif event.type == pygame.KEYDOWN: if event.key == pygame.K_ESCAPE: running = False - elif event.key == pygame.K_BACKSPACE and pygame.key.get_mods() & pygame.KMOD_LCTRL: + elif ( + event.key == pygame.K_BACKSPACE + and pygame.key.get_mods() & pygame.KMOD_LCTRL + ): print("pressed: LCTRL + BACKSPACE") pygame.quit() diff --git a/pygame__examples/on_press_lctrl+backspace__key_get_pressed.py b/pygame__examples/on_press_lctrl+backspace__key_get_pressed.py index 2cb988349..bc09d0c56 100644 --- a/pygame__examples/on_press_lctrl+backspace__key_get_pressed.py +++ b/pygame__examples/on_press_lctrl+backspace__key_get_pressed.py @@ -1,11 +1,11 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import pygame -import pygame.locals + pygame.init() diff --git a/pygame__examples/rect_collision.py b/pygame__examples/rect_collision.py index 5710a365f..3d3270ff4 100644 --- a/pygame__examples/rect_collision.py +++ b/pygame__examples/rect_collision.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import sys diff --git a/pygame__examples/simple_balls.py b/pygame__examples/simple_balls.py index e66f48e91..2295a3d57 100644 --- a/pygame__examples/simple_balls.py +++ b/pygame__examples/simple_balls.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from random import randint @@ -11,7 +11,7 @@ class Ball: - def __init__(self, x, y, r, v_x, v_y, color): + def __init__(self, x, y, r, v_x, v_y, color) -> None: self.x = x self.y = y self.r = r @@ -19,11 +19,11 @@ def __init__(self, x, y, r, v_x, v_y, color): self.v_y = v_y self.color = color - def update(self): + def update(self) -> None: self.x += self.v_x self.y += self.v_y - def draw(self, screen): + def draw(self, screen) -> None: pygame.draw.circle(screen, self.color, self.center, self.r) # Нарисуем поверх первого, прозрачный второй с границей (параметр width) @@ -51,7 +51,14 @@ def right(self): class Game: - def __init__(self, width: int, height: int, caption='Balls!', background_color=(255, 255, 255), frame_rate=60): + def __init__( + self, + width: int, + height: int, + caption="Balls!", + background_color=(255, 255, 255), + frame_rate=60, + ) -> None: self.width = width self.height = height self.frame_rate = frame_rate @@ -70,10 +77,12 @@ def __init__(self, width: int, height: int, caption='Balls!', background_color=( self.update_caption() - def update_caption(self): - pygame.display.set_caption("{} [{} fps]".format(self.caption, int(self.clock.get_fps()))) + def update_caption(self) -> None: + pygame.display.set_caption( + f"{self.caption} [{int(self.clock.get_fps())} fps]" + ) - def handle_events(self): + def handle_events(self) -> None: # Получение всех событий for event in pygame.event.get(): # Проверка события "Выход" @@ -85,7 +94,7 @@ def handle_events(self): if is_pressed[pygame.K_ESCAPE]: self.game_active = False - def run(self): + def run(self) -> None: self.game_active = True while self.game_active: @@ -112,7 +121,7 @@ def run(self): self.clock.tick(self.frame_rate) - def append_random_ball(self): + def append_random_ball(self) -> None: def get_random_vector(): pos = 0, 0 # Если pos равен (0, 0), пересчитываем значения, т.к. шарик должен двигаться @@ -134,8 +143,8 @@ def get_random_color(): self.balls.append(ball) -if __name__ == '__main__': - WIDTH = 600 # ширина экрана +if __name__ == "__main__": + WIDTH = 600 # ширина экрана HEIGHT = 600 # высота экрана BALL_NUMBER = 100 diff --git a/pyglet_example/main.py b/pyglet_example/main.py index b5436f639..648220488 100644 --- a/pyglet_example/main.py +++ b/pyglet_example/main.py @@ -1,34 +1,35 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import pyglet from pyglet.gl import * + # Optional audio outputs (Linux examples): # pyglet.options['audio'] = ('alsa', 'openal', 'silent') key = pyglet.window.key class main(pyglet.window.Window): - def __init__ (self): - super(main, self).__init__(800, 800, fullscreen = False) + def __init__(self) -> None: + super(main, self).__init__(800, 800, fullscreen=False) self.x, self.y = 0, 0 - self.bg = pyglet.sprite.Sprite(pyglet.image.load('background.jpg')) + self.bg = pyglet.sprite.Sprite(pyglet.image.load("background.jpg")) self.sprites = {} self.player = pyglet.media.Player() self.alive = 1 - def on_draw(self): + def on_draw(self) -> None: self.render() - def on_close(self): + def on_close(self) -> None: self.alive = 0 - def on_key_press(self, symbol, modifiers): + def on_key_press(self, symbol, modifiers) -> None: # Do something when a key is pressed? # Pause the audio for instance? # use `if symbol == key.SPACE: ...` @@ -37,13 +38,13 @@ def on_key_press(self, symbol, modifiers): # You could also do a standard input() call and enter a string # on the command line. if symbol == key.ENTER: - self.player.queue(pyglet.media.load('speak.mp3', streaming = False)) + self.player.queue(pyglet.media.load("speak.mp3", streaming=False)) if not self.player.playing: self.player.play() - if symbol == key.ESCAPE: # [ESC] + if symbol == key.ESCAPE: # [ESC] self.alive = 0 - def render(self): + def render(self) -> None: self.clear() self.bg.draw() @@ -54,7 +55,7 @@ def render(self): self.flip() - def run(self): + def run(self) -> None: while self.alive == 1: self.render() @@ -63,7 +64,7 @@ def run(self): # but is required for the GUI to not freeze # event = self.dispatch_events() - self.player.delete() # Free resources. (Not really needed but as an example) + self.player.delete() # Free resources. (Not really needed but as an example) x = main() diff --git a/pyglet_example/using_thread.py b/pyglet_example/using_thread.py index 849763cf6..a5af84548 100644 --- a/pyglet_example/using_thread.py +++ b/pyglet_example/using_thread.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from threading import Thread @@ -10,8 +10,8 @@ import pyglet -def play_song(): - song = pyglet.media.load('speak.mp3') +def play_song() -> None: + song = pyglet.media.load("speak.mp3") song.play() pyglet.app.run() diff --git a/pymem__examples/hello_world.py b/pymem__examples/hello_world.py index c28ba8720..86455892d 100644 --- a/pymem__examples/hello_world.py +++ b/pymem__examples/hello_world.py @@ -1,18 +1,18 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install pymem from pymem import Pymem -pm = Pymem('notepad.exe') -print('Process id: %s' % pm.process_id) +pm = Pymem("notepad.exe") +print("Process id: %s" % pm.process_id) address = pm.allocate(10) -print('Allocated address: %s' % address) +print("Allocated address: %s" % address) pm.write_int(address, 1337) value = pm.read_int(address) -print('Allocated value: %s' % value) +print("Allocated value: %s" % value) pm.free(address) diff --git a/pymem__examples/inject_python_interpreter.py b/pymem__examples/inject_python_interpreter.py index cae468827..2d214f26a 100644 --- a/pymem__examples/inject_python_interpreter.py +++ b/pymem__examples/inject_python_interpreter.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import os @@ -43,16 +43,16 @@ # RuntimeError: Could not allocate memory for shellcode -notepad = subprocess.Popen(['notepad.exe']) +notepad = subprocess.Popen(["notepad.exe"]) -pm = Pymem('notepad.exe') +pm = Pymem("notepad.exe") pm.inject_python_interpreter() -filepath = os.path.join(os.path.abspath('.'), 'pymem_injection.txt') +filepath = os.path.join(os.path.abspath("."), "pymem_injection.txt") filepath = filepath.replace("\\", "\\\\") -shellcode = """ -f = open("{}", "w+") +shellcode = f""" +f = open("{filepath}", "w+") f.write("pymem_injection") f.close() -""".format(filepath) +""" pm.inject_python_shellcode(shellcode) notepad.kill() diff --git a/pymem__examples/listing_process_modules.py b/pymem__examples/listing_process_modules.py index 7876d272d..23d089d7e 100644 --- a/pymem__examples/listing_process_modules.py +++ b/pymem__examples/listing_process_modules.py @@ -1,13 +1,13 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import pymem -pm = pymem.Pymem('python.exe') +pm = pymem.Pymem("python.exe") for module in pm.list_modules(): print(module.name) diff --git "a/pymorphy2__examples/c\320\276\320\263\320\273\320\260\321\201\320\276\320\262\320\260\320\275\320\270\320\265_\321\201\320\273\320\276\320\262_\321\201_\321\207\320\270\321\201\320\273\320\270\321\202\320\265\320\273\321\214\320\275\321\213\320\274\320\270.py" "b/pymorphy2__examples/c\320\276\320\263\320\273\320\260\321\201\320\276\320\262\320\260\320\275\320\270\320\265_\321\201\320\273\320\276\320\262_\321\201_\321\207\320\270\321\201\320\273\320\270\321\202\320\265\320\273\321\214\320\275\321\213\320\274\320\270.py" index 73981ee04..30693d05a 100644 --- "a/pymorphy2__examples/c\320\276\320\263\320\273\320\260\321\201\320\276\320\262\320\260\320\275\320\270\320\265_\321\201\320\273\320\276\320\262_\321\201_\321\207\320\270\321\201\320\273\320\270\321\202\320\265\320\273\321\214\320\275\321\213\320\274\320\270.py" +++ "b/pymorphy2__examples/c\320\276\320\263\320\273\320\260\321\201\320\276\320\262\320\260\320\275\320\270\320\265_\321\201\320\273\320\276\320\262_\321\201_\321\207\320\270\321\201\320\273\320\270\321\202\320\265\320\273\321\214\320\275\321\213\320\274\320\270.py" @@ -1,45 +1,38 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -# Для импортирования ascii_table__simple_pretty__format.py -import sys -sys.path.append('..') - -from ascii_table__simple_pretty__format import print_pretty_table - # pip install pymorphy2 import pymorphy2 +# pip install tabulate +from tabulate import tabulate + morph = pymorphy2.MorphAnalyzer() -words = [ - 'программист', - 'ёлка', - 'фрукт', - 'игр', - 'барсук' -] +words = ["программист", "ёлка", "фрукт", "игр", "барсук"] parsed_words = [morph.parse(word)[0] for word in words] -rows = [words] +rows = [] for i in range(5 + 1): row = [] for parsed_word in parsed_words: word = parsed_word.make_agree_with_number(i).word - row.append(f'{i} {word}') + row.append(f"{i} {word}") rows.append(row) -print_pretty_table(rows, align='<') -# программист | ёлка | фрукт | игр | барсук -# ----------------+--------+-----------+--------+----------- -# 0 программистов | 0 ёлок | 0 фруктов | 0 игр | 0 барсуков -# 1 программист | 1 ёлка | 1 фрукт | 1 игры | 1 барсук -# 2 программиста | 2 ёлки | 2 фрукта | 2 игр | 2 барсука -# 3 программиста | 3 ёлки | 3 фрукта | 3 игр | 3 барсука -# 4 программиста | 4 ёлки | 4 фрукта | 4 игр | 4 барсука -# 5 программистов | 5 ёлок | 5 фруктов | 5 игр | 5 барсуков +print(tabulate(rows, headers=words, tablefmt="orgtbl")) +""" +| программист | ёлка | фрукт | игр | барсук | +|-----------------+--------+-----------+--------+------------| +| 0 программистов | 0 ёлок | 0 фруктов | 0 игр | 0 барсуков | +| 1 программист | 1 ёлка | 1 фрукт | 1 игры | 1 барсук | +| 2 программиста | 2 ёлки | 2 фрукта | 2 игр | 2 барсука | +| 3 программиста | 3 ёлки | 3 фрукта | 3 игр | 3 барсука | +| 4 программиста | 4 ёлки | 4 фрукта | 4 игр | 4 барсука | +| 5 программистов | 5 ёлок | 5 фруктов | 5 игр | 5 барсуков | +""" diff --git a/pymorphy2__examples/find_gender_by_first_name.py b/pymorphy2__examples/find_gender_by_first_name.py index 3e88ba131..44d59d310 100644 --- a/pymorphy2__examples/find_gender_by_first_name.py +++ b/pymorphy2__examples/find_gender_by_first_name.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install pymorphy2 @@ -9,15 +9,23 @@ name_list = [ - 'Константин', 'Виктор', 'Любовь', 'Дамир', 'Венера', - 'Таисия', 'Алёна', 'Евгений', 'Егор', 'Никита' + "Константин", + "Виктор", + "Любовь", + "Дамир", + "Венера", + "Таисия", + "Алёна", + "Евгений", + "Егор", + "Никита", ] morph = pymorphy2.MorphAnalyzer() for name in name_list: parsed_word = morph.parse(name)[0] - print('{:<15} {}'.format(name, parsed_word.tag.gender)) + print(f"{name:<15} {parsed_word.tag.gender}") # Константин masc # Виктор masc diff --git a/pymorphy2__examples/get_tokens.py b/pymorphy2__examples/get_tokens.py index 746223e2b..518e1ec40 100644 --- a/pymorphy2__examples/get_tokens.py +++ b/pymorphy2__examples/get_tokens.py @@ -1,27 +1,26 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -from typing import List - # pip install pymorphy2 import pymorphy2 from pymorphy2.tokenizers import simple_word_tokenize + morph = pymorphy2.MorphAnalyzer() -def get_tokens(text: str, ignore_punctuations=False) -> List[pymorphy2.analyzer.Parse]: +def get_tokens(text: str, ignore_punctuations=False) -> list[pymorphy2.analyzer.Parse]: tokens = [morph.parse(word)[0] for word in simple_word_tokenize(text)] if ignore_punctuations: - tokens = [token for token in tokens if 'PNCT' not in token.tag] + tokens = [token for token in tokens if "PNCT" not in token.tag] return tokens -if __name__ == '__main__': - text = 'чё за дом! ни конфет, ни печенек, пришлось жрать СОЛЁНЫЙ огурец ((' +if __name__ == "__main__": + text = "чё за дом! ни конфет, ни печенек, пришлось жрать СОЛЁНЫЙ огурец ((" for token in get_tokens(text, ignore_punctuations=True): print(token.word, token.normal_form) diff --git a/pymorphy2__examples/normal_form.py b/pymorphy2__examples/normal_form.py index a0ab96666..ac64bcbca 100644 --- a/pymorphy2__examples/normal_form.py +++ b/pymorphy2__examples/normal_form.py @@ -1,11 +1,13 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install pymorphy2 import pymorphy2 + + morph = pymorphy2.MorphAnalyzer() @@ -13,7 +15,7 @@ def get_normal_form(word: str) -> str: return morph.parse(word)[0].normal_form -if __name__ == '__main__': - print(get_normal_form('ИВАНОВА')) # иванов - print(get_normal_form('Иванова')) # иванов - print(get_normal_form('Иванов')) # иванов +if __name__ == "__main__": + print(get_normal_form("ИВАНОВА")) # иванов + print(get_normal_form("Иванова")) # иванов + print(get_normal_form("Иванов")) # иванов diff --git a/pymorphy2__examples/parse_word.py b/pymorphy2__examples/parse_word.py index d79e7f831..b28bea875 100644 --- a/pymorphy2__examples/parse_word.py +++ b/pymorphy2__examples/parse_word.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install pymorphy2 @@ -10,9 +10,7 @@ morph = pymorphy2.MorphAnalyzer() -words = [ - "Думала", "Подумала", "Согласен" -] +words = ["Думала", "Подумала", "Согласен"] for word in words: print(repr(word)) diff --git "a/pymorphy2__examples/\320\276\320\277\321\200\320\265\320\264\320\265\320\273\320\265\320\275\320\270\320\265_\320\272\320\276\320\274\320\274\320\265\320\275\321\202\320\260\321\200\320\270\320\265\320\262_\320\266\320\265\320\275\321\211\320\270\320\275/main.py" "b/pymorphy2__examples/\320\276\320\277\321\200\320\265\320\264\320\265\320\273\320\265\320\275\320\270\320\265_\320\272\320\276\320\274\320\274\320\265\320\275\321\202\320\260\321\200\320\270\320\265\320\262_\320\266\320\265\320\275\321\211\320\270\320\275/main.py" index 6acb45ef5..682f49b82 100644 --- "a/pymorphy2__examples/\320\276\320\277\321\200\320\265\320\264\320\265\320\273\320\265\320\275\320\270\320\265_\320\272\320\276\320\274\320\274\320\265\320\275\321\202\320\260\321\200\320\270\320\265\320\262_\320\266\320\265\320\275\321\211\320\270\320\275/main.py" +++ "b/pymorphy2__examples/\320\276\320\277\321\200\320\265\320\264\320\265\320\273\320\265\320\275\320\270\320\265_\320\272\320\276\320\274\320\274\320\265\320\275\321\202\320\260\321\200\320\270\320\265\320\262_\320\266\320\265\320\275\321\211\320\270\320\275/main.py" @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # pip install nltk @@ -19,35 +19,35 @@ def is_ADJS_sing_femn(parsed: pymorphy2.analyzer.Parse) -> bool: """ Имя прилагательное (краткое) + единственное число + женский род """ - return {'ADJS', 'sing', 'femn'} in parsed.tag + return {"ADJS", "sing", "femn"} in parsed.tag def is_ADJS_sing_masc(parsed: pymorphy2.analyzer.Parse) -> bool: """ Имя прилагательное (краткое) + единственное число + мужской род """ - return {'ADJS', 'sing', 'masc'} in parsed.tag + return {"ADJS", "sing", "masc"} in parsed.tag def is_VERB_sing_femn(parsed: pymorphy2.analyzer.Parse) -> bool: """ Глагол (личная форма) + единственное число + женский род """ - return {'VERB', 'sing', 'femn'} in parsed.tag + return {"VERB", "sing", "femn"} in parsed.tag def is_VERB_sing_masc(parsed: pymorphy2.analyzer.Parse) -> bool: """ Глагол (личная форма) + единственное число + мужской род """ - return {'VERB', 'sing', 'masc'} in parsed.tag + return {"VERB", "sing", "masc"} in parsed.tag def is_NPRO_1per_sing(parsed: pymorphy2.analyzer.Parse) -> bool: """ Местоимение-существительное + 1 лицо + единственное число """ - return {'NPRO', '1per', 'sing'} in parsed.tag + return {"NPRO", "1per", "sing"} in parsed.tag LOG_DEBUG = False @@ -58,11 +58,11 @@ def is_NPRO_1per_sing(parsed: pymorphy2.analyzer.Parse) -> bool: def is_femn(text: str) -> bool: for line in text.splitlines(): - LOG_DEBUG and print(f'[#] Comment: {line!r}') + LOG_DEBUG and print(f"[#] Comment: {line!r}") # Перебор предложений - for sent in nltk.sent_tokenize(line, language='russian'): - LOG_DEBUG and print(f'[#] Comment part: {sent!r}') + for sent in nltk.sent_tokenize(line, language="russian"): + LOG_DEBUG and print(f"[#] Comment part: {sent!r}") words = nltk.word_tokenize(sent) parsed_words = [morph.parse(word)[0] for word in words] @@ -76,11 +76,11 @@ def is_femn(text: str) -> bool: has_NPRO_1per_sing = False - LOG_DEBUG and print(f'[#] ({len(words)}): {words}') - LOG_DEBUG and print(f'[#] ({len(parsed_words)}):') + LOG_DEBUG and print(f"[#] ({len(words)}): {words}") + LOG_DEBUG and print(f"[#] ({len(parsed_words)}):") for parsed in parsed_words: - LOG_DEBUG and print(f'[#] {parsed.word} - {str(parsed.tag)!r}') + LOG_DEBUG and print(f"[#] {parsed.word} - {str(parsed.tag)!r}") if is_NPRO_1per_sing(parsed): has_NPRO_1per_sing = True @@ -99,26 +99,28 @@ def is_femn(text: str) -> bool: return False -if __name__ == '__main__': +if __name__ == "__main__": import json - with open('comments.json', encoding='utf-8') as f: + with open("comments.json", encoding="utf-8") as f: data = json.load(f) - comments = [(x['text'], x['expected']) for x in data] + comments = [(x["text"], x["expected"]) for x in data] matches = 0 total = len(comments) for i, (text, expected) in enumerate(comments, 1): has_female = is_femn(text) - match = has_female == (expected == 'female') + match = has_female == (expected == "female") matches += match - print(f"{i}. {text!r}" - f"\n [{'+' if match else '-'}] " - f"Expected={expected}, " - f"actual={'female' if has_female else 'male'}") - print('-' * 100) + print( + f"{i}. {text!r}" + f"\n [{'+' if match else '-'}] " + f"Expected={expected}, " + f"actual={'female' if has_female else 'male'}" + ) + print("-" * 100) - print(f'Total: {matches} / {total}') + print(f"Total: {matches} / {total}") diff --git a/pynput__examples/console_board_game.py b/pynput__examples/console_board_game.py new file mode 100644 index 000000000..0bc7ec743 --- /dev/null +++ b/pynput__examples/console_board_game.py @@ -0,0 +1,169 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from dataclasses import dataclass +from random import randint + +import time +import os + +# pip install pynput-1.8.1 +from pynput import keyboard + + +BOARD_TEMPLATE = """ +############################## +#G # +######## ####### ####### # +# GG # # +# GG # # G G G G G G # # +# GG # ################ # +# GG # # +# # ###### ### #### # +# #GG # # # #GG# # +# #GG # # @ # +############################## +""".strip() + + +def clear() -> None: + os.system("clear" if os.name == "posix" else "cls") + + +@dataclass +class GameObject: + pos_x: int = 0 + pos_y: int = 0 + + icon: str = " " + + +@dataclass +class Wall(GameObject): + icon: str = "#" + + +@dataclass +class Gold(GameObject): + value: int = 0 + icon: str = "G" + + +@dataclass +class Hero(GameObject): + gold: int = 0 + icon: str = "@" + + +class Game: + def __init__(self) -> None: + self.hero: Hero = Hero() + self.board: list[list[GameObject]] = [] + self.is_active: bool = False + + self.listener = keyboard.Listener(on_release=self._on_release) + + def _on_release(self, key) -> None: + self.logic(key) + + def do_step(self) -> bool: + self.draw() + + return self.is_active + + def logic(self, key) -> None: + pos_x, pos_y = self.hero.pos_x, self.hero.pos_y + + if key == keyboard.Key.esc: + self.finish() + return + + elif key == keyboard.KeyCode.from_char("w") or key == keyboard.Key.up: + self.hero.pos_y -= 1 + + elif key == keyboard.KeyCode.from_char("s") or key == keyboard.Key.down: + self.hero.pos_y += 1 + + elif key == keyboard.KeyCode.from_char("d") or key == keyboard.Key.right: + self.hero.pos_x += 1 + + elif key == keyboard.KeyCode.from_char("a") or key == keyboard.Key.left: + self.hero.pos_x -= 1 + + if (pos_x, pos_y) != (self.hero.pos_x, self.hero.pos_y): + game_object: GameObject = self.board[self.hero.pos_y][self.hero.pos_x] + + if isinstance(game_object, Wall): # Если уперлись в стену + self.hero.pos_x, self.hero.pos_y = pos_x, pos_y + return + + if isinstance(game_object, Gold): + self.hero.gold += game_object.value + + self.board[self.hero.pos_y][self.hero.pos_x] = self.hero # Рисуем героя + self.board[pos_y][pos_x] = GameObject( + pos_x, pos_y + ) # Старое место стало пустым + + def draw(self) -> None: + clear() + + self.draw_board() + self.draw_game_info() + + def draw_board(self) -> None: + for row in self.board: + print("".join(game_object.icon for game_object in row)) + + def draw_game_info(self) -> None: + print( + f"Hero: position: {self.hero.pos_x}x{self.hero.pos_y}, gold: {self.hero.gold}" + ) + + def start(self, board_template: str) -> None: + self.is_active = True + self.board.clear() + self.listener.start() + + for y, lines in enumerate(board_template.splitlines()): + self.board.append([]) + + for x, value in enumerate(lines): + match value: + case "@": + self.hero.pos_x = x + self.hero.pos_y = y + game_object = self.hero + + case "G": + game_object = Gold( + pos_x=x, + pos_y=y, + value=randint(1, 100), + ) + + case "#": + game_object = Wall(pos_x=x, pos_y=y) + + case _: + game_object = GameObject( + pos_x=x, + pos_y=y, + ) + + self.board[-1].append(game_object) + + def finish(self) -> None: + self.is_active = False + self.listener.stop() + + +if __name__ == "__main__": + game = Game() + game.start(board_template=BOARD_TEMPLATE) + + while game.do_step(): + time.sleep(0.100) diff --git a/pynput__examples/pynput__global_hot_key.py b/pynput__examples/global_hot_key.py similarity index 55% rename from pynput__examples/pynput__global_hot_key.py rename to pynput__examples/global_hot_key.py index 75b02f25f..fe4c26a16 100644 --- a/pynput__examples/pynput__global_hot_key.py +++ b/pynput__examples/global_hot_key.py @@ -1,21 +1,23 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -# pip install pynput +# pip install pynput-1.8.1 from pynput.keyboard import Key, Listener +# pip install pyscreenshot +import pyscreenshot as ImageGrab -def on_release(key): + +def on_release(key) -> None: if key == Key.f7: - print('screenshot') + print("screenshot") # Fullscreen - import pyscreenshot as ImageGrab im = ImageGrab.grab() - im.save('screenshot.png') + im.save("screenshot.png") im.show() diff --git a/pynput__examples/hello_world.py b/pynput__examples/key_listener.py similarity index 62% rename from pynput__examples/hello_world.py rename to pynput__examples/key_listener.py index fe6cadc79..e408ff0fa 100644 --- a/pynput__examples/hello_world.py +++ b/pynput__examples/key_listener.py @@ -1,24 +1,25 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -# pip install pynput +# pip install pynput-1.8.1 from pynput.keyboard import Key, Listener -def on_press(key): - print('{0} pressed'.format(key)) +def on_press(key) -> None: + print(f"{key} pressed") -def on_release(key): - print('{0} release'.format(key)) +def on_release(key) -> bool | None: + print(f"{key} release") if key == Key.esc: # Stop listener return False + # Collect events until released with Listener(on_press=on_press, on_release=on_release) as listener: listener.join() diff --git a/pynput__examples/media_stop.py b/pynput__examples/media_stop.py new file mode 100644 index 000000000..404ae32fd --- /dev/null +++ b/pynput__examples/media_stop.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +# pip install pynput-1.8.1 +from pynput.keyboard import Key, Controller + + +keyboard = Controller() + +print("Attempting to stop media playback using pynput...") + +keyboard.press(Key.media_stop) +keyboard.release(Key.media_stop) + +print("Media stop command sent via pynput.") diff --git a/pynput__examples/pynput__key_listener.py b/pynput__examples/pynput__key_listener.py deleted file mode 100644 index 5f9972ea2..000000000 --- a/pynput__examples/pynput__key_listener.py +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -__author__ = 'ipetrash' - - -def on_press(key): - print('on_press', str(key), key, type(key)) - - -def on_release(key): - print('on_release', str(key), key, type(key)) - - -# pip install pynput -from pynput import keyboard -with keyboard.Listener(on_press=on_press, on_release=on_release) as listener: - listener.join() diff --git a/pyqrcode__examples/hello_world.py b/pyqrcode__examples/hello_world.py deleted file mode 100644 index 317736d8e..000000000 --- a/pyqrcode__examples/hello_world.py +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -__author__ = 'ipetrash' - - -# SOURCE: https://pythonhosted.org/PyQRCode/ - - -# pip install pyqrcode -# pip install pypng -import pyqrcode - - -qr_code = pyqrcode.create('http://uca.edu') -qr_code.png('img.png') - -qr_code = pyqrcode.create('Hello World!') -qr_code.png('img_hello.png') - -qr_code.png('img_hello__scale=6.png', scale=6) diff --git a/pyscreeze__examples/win_calc_clicker/README.md b/pyscreeze__examples/win_calc_clicker/README.md new file mode 100644 index 000000000..de669a9bf --- /dev/null +++ b/pyscreeze__examples/win_calc_clicker/README.md @@ -0,0 +1,2 @@ +## main.py +![](screenshots/screenshot.gif) diff --git a/pyscreeze__examples/win_calc_clicker/buttons/+.png b/pyscreeze__examples/win_calc_clicker/buttons/+.png new file mode 100644 index 000000000..084c9c285 Binary files /dev/null and b/pyscreeze__examples/win_calc_clicker/buttons/+.png differ diff --git a/pyscreeze__examples/win_calc_clicker/buttons/0.png b/pyscreeze__examples/win_calc_clicker/buttons/0.png new file mode 100644 index 000000000..12e8b4f96 Binary files /dev/null and b/pyscreeze__examples/win_calc_clicker/buttons/0.png differ diff --git a/pyscreeze__examples/win_calc_clicker/buttons/1.png b/pyscreeze__examples/win_calc_clicker/buttons/1.png new file mode 100644 index 000000000..63521ac03 Binary files /dev/null and b/pyscreeze__examples/win_calc_clicker/buttons/1.png differ diff --git a/pyscreeze__examples/win_calc_clicker/buttons/2.png b/pyscreeze__examples/win_calc_clicker/buttons/2.png new file mode 100644 index 000000000..a4b2586f3 Binary files /dev/null and b/pyscreeze__examples/win_calc_clicker/buttons/2.png differ diff --git a/pyscreeze__examples/win_calc_clicker/buttons/3.png b/pyscreeze__examples/win_calc_clicker/buttons/3.png new file mode 100644 index 000000000..ded0748f5 Binary files /dev/null and b/pyscreeze__examples/win_calc_clicker/buttons/3.png differ diff --git a/pyscreeze__examples/win_calc_clicker/buttons/4.png b/pyscreeze__examples/win_calc_clicker/buttons/4.png new file mode 100644 index 000000000..87c7d85b2 Binary files /dev/null and b/pyscreeze__examples/win_calc_clicker/buttons/4.png differ diff --git a/pyscreeze__examples/win_calc_clicker/buttons/5.png b/pyscreeze__examples/win_calc_clicker/buttons/5.png new file mode 100644 index 000000000..fc7991e11 Binary files /dev/null and b/pyscreeze__examples/win_calc_clicker/buttons/5.png differ diff --git a/pyscreeze__examples/win_calc_clicker/buttons/6.png b/pyscreeze__examples/win_calc_clicker/buttons/6.png new file mode 100644 index 000000000..22936758e Binary files /dev/null and b/pyscreeze__examples/win_calc_clicker/buttons/6.png differ diff --git a/pyscreeze__examples/win_calc_clicker/buttons/7.png b/pyscreeze__examples/win_calc_clicker/buttons/7.png new file mode 100644 index 000000000..483623193 Binary files /dev/null and b/pyscreeze__examples/win_calc_clicker/buttons/7.png differ diff --git a/pyscreeze__examples/win_calc_clicker/buttons/8.png b/pyscreeze__examples/win_calc_clicker/buttons/8.png new file mode 100644 index 000000000..188390ada Binary files /dev/null and b/pyscreeze__examples/win_calc_clicker/buttons/8.png differ diff --git a/pyscreeze__examples/win_calc_clicker/buttons/9.png b/pyscreeze__examples/win_calc_clicker/buttons/9.png new file mode 100644 index 000000000..ab5ba3774 Binary files /dev/null and b/pyscreeze__examples/win_calc_clicker/buttons/9.png differ diff --git a/pyscreeze__examples/win_calc_clicker/buttons/=.png b/pyscreeze__examples/win_calc_clicker/buttons/=.png new file mode 100644 index 000000000..69088492a Binary files /dev/null and b/pyscreeze__examples/win_calc_clicker/buttons/=.png differ diff --git a/pyscreeze__examples/win_calc_clicker/main.py b/pyscreeze__examples/win_calc_clicker/main.py new file mode 100644 index 000000000..a522ead8a --- /dev/null +++ b/pyscreeze__examples/win_calc_clicker/main.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import os +import time +import win32api +import win32con + +from pathlib import Path + +# pip install pyscreeze +import pyscreeze + + +DIR = Path(__file__).parent.resolve() +DIR_BUTTONS = DIR / "buttons" + + +def click(x, y, sleep_secs: float = 0.01) -> None: + win32api.SetCursorPos((x, y)) + win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN, x, y, 0, 0) + win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP, x, y, 0, 0) + + time.sleep(sleep_secs) + + +BUTTON_BY_POSITION = dict() + + +def init_button_positions(grayscale: bool = True) -> None: + BUTTON_BY_POSITION.clear() + + for path in DIR_BUTTONS.glob("*.png"): + button = path.stem + + position = pyscreeze.locateCenterOnScreen(str(path), grayscale=grayscale) + if not position: + print(f"Position of button {button!r} not found!") + continue + + BUTTON_BY_POSITION[button] = position + + +def go(expression: str) -> None: + init_button_positions() + + for button in expression: + if not button.strip(): + continue + + if button not in BUTTON_BY_POSITION: + print(f"Unknown button {button!r}!") + continue + + x, y = BUTTON_BY_POSITION[button] + print(f"Click on {button!r} at {x}x{y}") + + click(x, y) + + +def show_test_calc() -> None: + os.startfile("calc.exe") + time.sleep(1) + + +if __name__ == "__main__": + show_test_calc() + + expression = "1234 + 222 + 3214 + 1111 + 1234567890 + 222 = " + go(expression) diff --git a/pyscreeze__examples/win_calc_clicker/screenshots/screenshot.gif b/pyscreeze__examples/win_calc_clicker/screenshots/screenshot.gif new file mode 100644 index 000000000..a7c1b0f92 Binary files /dev/null and b/pyscreeze__examples/win_calc_clicker/screenshots/screenshot.gif differ diff --git a/pysimplegui__examples/generation_random.py b/pysimplegui__examples/generation_random.py index 79cd59736..414e2973f 100644 --- a/pysimplegui__examples/generation_random.py +++ b/pysimplegui__examples/generation_random.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from random import randint @@ -10,20 +10,20 @@ import PySimpleGUI as sg -sg.theme('SystemDefault') +sg.theme("SystemDefault") layout = [ - [sg.Text('Number of digits'), sg.Input(default_text='5', key='ND')], - [sg.Text('Minimum'), sg.Input(default_text='1', key='Min')], - [sg.Text('Maximum'), sg.Input(default_text='9', key='Max')], - [sg.Text(' ', key='OUT')], - [sg.Button('Generate'), sg.Button('Clear')] + [sg.Text("Number of digits"), sg.Input(default_text="5", key="ND")], + [sg.Text("Minimum"), sg.Input(default_text="1", key="Min")], + [sg.Text("Maximum"), sg.Input(default_text="9", key="Max")], + [sg.Text(" ", key="OUT")], + [sg.Button("Generate", key="button_generate"), sg.Button("Clear")], ] -window = sg.Window('Generator', layout) +window = sg.Window("Generator", layout) def generate(number: int, min_number: int, max_number: int) -> str: - return ''.join(str(randint(min_number, max_number)) for _ in range(number)) + return "".join(str(randint(min_number, max_number)) for _ in range(number)) while True: @@ -31,14 +31,18 @@ def generate(number: int, min_number: int, max_number: int) -> str: if event is None or event == sg.WIN_CLOSED: break - if event == 'Generate': - number, min_number, max_number = int(values['ND']), int(values['Min']), int(values['Max']) + if event == "button_generate": + number, min_number, max_number = ( + int(values["ND"]), + int(values["Min"]), + int(values["Max"]), + ) text = generate(number, min_number, max_number) print(text) - window['OUT'].update(f'Result: {text}') + window["OUT"].update(f"Result: {text}") - if event == 'Clear': - window.find_element('ND').update('') - window.find_element('Min').update('') - window.find_element('Max').update('') - window.find_element('OUT').update('') + if event == "Clear": + window.find_element("ND").update("") + window.find_element("Min").update("") + window.find_element("Max").update("") + window.find_element("OUT").update("") diff --git a/pytesseract__examples__image_to_text/GTA/main.py b/pytesseract__examples__image_to_text/GTA/main.py index 6868c0d18..54791cd2b 100644 --- a/pytesseract__examples__image_to_text/GTA/main.py +++ b/pytesseract__examples__image_to_text/GTA/main.py @@ -1,22 +1,24 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -# pip install pillow -from PIL import Image - # pip install pytesseract # tesseract.exe from https://github.com/UB-Mannheim/tesseract/wiki import pytesseract -pytesseract.pytesseract.tesseract_cmd = r'C:\Program Files\Tesseract-OCR\tesseract.exe' + +# pip install pillow +from PIL import Image + + +pytesseract.pytesseract.tesseract_cmd = r"C:\Program Files\Tesseract-OCR\tesseract.exe" -file_name = 'RuFjPBg.png' +file_name = "RuFjPBg.png" img = Image.open(file_name) -text = pytesseract.image_to_string(img, lang='eng') +text = pytesseract.image_to_string(img, lang="eng") print(repr(text)) # '.\nCheat activated' diff --git a/pytesseract__examples__image_to_text/hello_world.py b/pytesseract__examples__image_to_text/hello_world.py index 4cfec19f7..722288cc7 100644 --- a/pytesseract__examples__image_to_text/hello_world.py +++ b/pytesseract__examples__image_to_text/hello_world.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/madmaze/pytesseract @@ -9,20 +9,22 @@ import re -# pip install pillow -from PIL import Image - # pip install pytesseract # Tesseract.exe from https://github.com/UB-Mannheim/tesseract/wiki import pytesseract -pytesseract.pytesseract.tesseract_cmd = r'C:\Program Files\Tesseract-OCR\tesseract.exe' + +# pip install pillow +from PIL import Image + + +pytesseract.pytesseract.tesseract_cmd = r"C:\Program Files\Tesseract-OCR\tesseract.exe" # Simple image to string -img = Image.open('test.jpg') -text = pytesseract.image_to_string(img, lang='eng') +img = Image.open("test.jpg") +text = pytesseract.image_to_string(img, lang="eng") -text = re.sub(r'(\s){2,}', '\1', text) +text = re.sub(r"(\s){2,}", "\1", text) print(text) # At this Time. two Great Empires struggled # for Dominion over Ivalice:Archadia in the East. Rozarria. the West. diff --git a/pytesseract__examples__image_to_text/hello_world__with__googletrans.py b/pytesseract__examples__image_to_text/hello_world__with__googletrans.py index 2dbbb9d36..8e2085af1 100644 --- a/pytesseract__examples__image_to_text/hello_world__with__googletrans.py +++ b/pytesseract__examples__image_to_text/hello_world__with__googletrans.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/madmaze/pytesseract @@ -9,23 +9,25 @@ import re +# pip install pytesseract +# Tesseract.exe from https://github.com/UB-Mannheim/tesseract/wiki +import pytesseract + # pip install googletrans from googletrans import Translator # pip install pillow from PIL import Image -# pip install pytesseract -# Tesseract.exe from https://github.com/UB-Mannheim/tesseract/wiki -import pytesseract -pytesseract.pytesseract.tesseract_cmd = r'C:\Program Files\Tesseract-OCR\tesseract.exe' + +pytesseract.pytesseract.tesseract_cmd = r"C:\Program Files\Tesseract-OCR\tesseract.exe" # Simple image to string -img = Image.open('test.jpg') -text = pytesseract.image_to_string(img, lang='eng') +img = Image.open("test.jpg") +text = pytesseract.image_to_string(img, lang="eng") -text = re.sub(r'(\s){2,}', '\1', text) +text = re.sub(r"(\s){2,}", "\1", text) print(text) # At this Time. two Great Empires struggled # for Dominion over Ivalice:Archadia in the East. Rozarria. the West. @@ -35,17 +37,13 @@ # tolled the Destruction of the greater # part of Dalmasca’'s f orces. -print('', '-' * 100, '', sep='\n') +print("", "-" * 100, "", sep="\n") -from_lang = 'en' -to_lang = 'ru' +from_lang = "en" +to_lang = "ru" translator = Translator() -translation = translator.translate( - text, - src=from_lang, - dest=to_lang -).text +translation = translator.translate(text, src=from_lang, dest=to_lang).text print(translation) # В это время. два Великие Империи боролись # для Dominion над Ивалисом: Archadia на Востоке. Rozarria. Запад. diff --git a/python_implementation.py b/python_implementation.py index dfa994cba..659662411 100644 --- a/python_implementation.py +++ b/python_implementation.py @@ -1,8 +1,11 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import platform + + print(platform.python_implementation()) +# CPython diff --git a/python_interpreter_info.py b/python_interpreter_info.py index 29363df55..8a2406dd9 100644 --- a/python_interpreter_info.py +++ b/python_interpreter_info.py @@ -1,13 +1,15 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import platform -print('Implementation:', platform.python_implementation()) -print('Version:', platform.python_version()) -print('Compiler:', platform.python_compiler()) - import sys -print('Executable:', sys.executable) + + +print("Implementation:", platform.python_implementation()) +print("Version:", platform.python_version()) +print("Compiler:", platform.python_compiler()) + +print("Executable:", sys.executable) diff --git a/python_object_to_json.py b/python_object_to_json.py index b0b7a9bfe..339d4b0dc 100644 --- a/python_object_to_json.py +++ b/python_object_to_json.py @@ -1,16 +1,16 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" class SubField: - def __init__(self): + def __init__(self) -> None: self.flag = True class Field: - def __init__(self, tag1, tag2, sub_field_flag=True): + def __init__(self, tag1, tag2, sub_field_flag=True) -> None: self.tag1 = tag1 self.tag2 = tag2 @@ -20,17 +20,17 @@ def __init__(self, tag1, tag2, sub_field_flag=True): class Object: a = 0 - b = '123' + b = "123" - def __init__(self): + def __init__(self) -> None: self.c = 3 self.items = [1, 2, 3, 4] self.maps = { - 'is': True, - 'not': 0, + "is": True, + "not": 0, } - self.field = Field('abc', 'tag2') + self.field = Field("abc", "tag2") self.field_2 = Field(777, False, sub_field_flag=False) @@ -39,11 +39,11 @@ def object_to_dict(object): fields.update(object.__class__.__dict__) fields.update(object.__dict__) - fields = dict(filter(lambda x: not x[0].startswith('_'), fields.items())) + fields = dict(filter(lambda x: not x[0].startswith("_"), fields.items())) new_fields = dict() for k, v in fields.items(): - if hasattr(v, '__dict__'): + if hasattr(v, "__dict__"): v = object_to_dict(v) new_fields[k] = v @@ -51,11 +51,12 @@ def object_to_dict(object): return new_fields -if __name__ == '__main__': +if __name__ == "__main__": obj = Object() fields = object_to_dict(obj) print(fields) print() import json + print(json.dumps(fields, ensure_ascii=False, indent=4, sort_keys=True)) diff --git a/pythonnet__examples/OpenHardwareMonitorLib__Sensor_Temperature/main.py b/pythonnet__examples/OpenHardwareMonitorLib__Sensor_Temperature/main.py index e67b33ea0..c30092c25 100644 --- a/pythonnet__examples/OpenHardwareMonitorLib__Sensor_Temperature/main.py +++ b/pythonnet__examples/OpenHardwareMonitorLib__Sensor_Temperature/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # NOTE: Run as administrator @@ -12,22 +12,41 @@ # pip install pythonnet import clr +from OpenHardwareMonitor import Hardware + openhardwaremonitor_hwtypes = [ - 'Mainboard', 'SuperIO', 'CPU', 'RAM', 'GpuNvidia', 'GpuAti', 'TBalancer', 'Heatmaster', 'HDD' + "Mainboard", + "SuperIO", + "CPU", + "RAM", + "GpuNvidia", + "GpuAti", + "TBalancer", + "Heatmaster", + "HDD", ] openhardwaremonitor_sensortypes = [ - 'Voltage', 'Clock', 'Temperature', 'Load', 'Fan', 'Flow', 'Control', 'Level', - 'Factor', 'Power', 'Data', 'SmallData' + "Voltage", + "Clock", + "Temperature", + "Load", + "Fan", + "Flow", + "Control", + "Level", + "Factor", + "Power", + "Data", + "SmallData", ] def initialize_openhardwaremonitor(): DIR = os.path.abspath(os.path.dirname(__file__)) - dll_file_name = DIR + R'\OpenHardwareMonitorLib.dll' + dll_file_name = DIR + R"\OpenHardwareMonitorLib.dll" clr.AddReference(dll_file_name) - from OpenHardwareMonitor import Hardware handle = Hardware.Computer() handle.MainboardEnabled = True handle.CPUEnabled = True @@ -38,7 +57,7 @@ def initialize_openhardwaremonitor(): return handle -def fetch_stats(handle): +def fetch_stats(handle) -> None: for i in handle.Hardware: i.Update() @@ -51,15 +70,17 @@ def fetch_stats(handle): parse_sensor(subsensor) -def parse_sensor(sensor): +def parse_sensor(sensor) -> None: if sensor.Value is None: return # If SensorType is Temperature - if sensor.SensorType == openhardwaremonitor_sensortypes.index('Temperature'): + if sensor.SensorType == openhardwaremonitor_sensortypes.index("Temperature"): type_name = openhardwaremonitor_hwtypes[sensor.Hardware.HardwareType] - print(f" {type_name}. {sensor.Hardware.Name!r} " - f"Temperature Sensor #{sensor.Index} {sensor.Name} - {sensor.Value}°C") + print( + f" {type_name}. {sensor.Hardware.Name!r} " + f"Temperature Sensor #{sensor.Index} {sensor.Name} - {sensor.Value}°C" + ) if __name__ == "__main__": diff --git a/pytube__examples/main.py b/pytube__examples/main.py index a71e2b421..5edc52193 100644 --- a/pytube__examples/main.py +++ b/pytube__examples/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import sys @@ -10,20 +10,20 @@ from pytube import YouTube -url = 'https://www.youtube.com/watch?v=90KZnwrVMgY' +url = "https://www.youtube.com/watch?v=90KZnwrVMgY" yt = YouTube(url) -print(f'Download video {yt.title!r}: {url}') +print(f"Download video {yt.title!r}: {url}") # Атрибут progressive=True нужен, чтобы получить поток с видео и аудио в одном файле -streams = yt.streams\ - .filter(progressive=True, file_extension='mp4', resolution='720p')\ - .order_by('resolution') +streams = yt.streams.filter( + progressive=True, file_extension="mp4", resolution="720p" +).order_by("resolution") if not streams: - print('Not found mp4 with 720p!') + print("Not found mp4 with 720p!") sys.exit() video = streams[-1] -print('Stream url:', video.url) +print("Stream url:", video.url) video.download() diff --git a/pytz_vs_zoneinfo__examples/dt_with_tz_to_utc__pytz.py b/pytz_vs_zoneinfo__examples/dt_with_tz_to_utc__pytz.py new file mode 100644 index 000000000..c10204f9a --- /dev/null +++ b/pytz_vs_zoneinfo__examples/dt_with_tz_to_utc__pytz.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from datetime import datetime + +# pip install pytz==2025.2 +import pytz + + +tz_moscow = pytz.timezone("Europe/Moscow") +tz_etc_0300 = pytz.timezone("Etc/GMT-3") +tz_offset_0300 = pytz.FixedOffset(offset=3 * 60) +tz_offset_neg0300 = pytz.FixedOffset(offset=-3 * 60) +tz_utc = pytz.timezone("UTC") + +dt = datetime(year=2025, month=1, day=1, hour=12, minute=0, second=0) +print(dt) +# 2025-01-01 12:00:00 + +print() + +dt_moscow = dt.replace(tzinfo=tz_moscow) +print(dt_moscow) +print(dt_moscow.astimezone(tz_utc).replace(tzinfo=None)) +# 2025-01-01 12:00:00+02:30 +# 2025-01-01 09:30:00 + +print() + +dt_etc_0300 = dt.replace(tzinfo=tz_etc_0300) +print(dt_etc_0300) +print(dt_etc_0300.astimezone(tz_utc).replace(tzinfo=None)) +# 2025-01-01 12:00:00+03:00 +# 2025-01-01 09:00:00 + +print() + +dt_offset_0300 = dt.replace(tzinfo=tz_offset_0300) +print(dt_offset_0300) +print(dt_offset_0300.astimezone(tz_utc).replace(tzinfo=None)) +# 2025-01-01 12:00:00+03:00 +# 2025-01-01 09:00:00 + +print() + +dt_offset_neg0300 = dt.replace(tzinfo=tz_offset_neg0300) +print(dt_offset_neg0300) +print(dt_offset_neg0300.astimezone(tz_utc).replace(tzinfo=None)) +# 2025-01-01 12:00:00-03:00 +# 2025-01-01 15:00:00 + +print() + +dt_utc = dt.replace(tzinfo=tz_utc) +print(dt_utc) +print(dt_utc.astimezone(tz_utc).replace(tzinfo=None)) +# 2025-01-01 12:00:00+00:00 +# 2025-01-01 12:00:00 diff --git a/pytz_vs_zoneinfo__examples/dt_with_tz_to_utc__zoneinfo.py b/pytz_vs_zoneinfo__examples/dt_with_tz_to_utc__zoneinfo.py new file mode 100644 index 000000000..e5e761c72 --- /dev/null +++ b/pytz_vs_zoneinfo__examples/dt_with_tz_to_utc__zoneinfo.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import zoneinfo +from datetime import datetime, timedelta, timezone + + +tz_moscow = zoneinfo.ZoneInfo("Europe/Moscow") +tz_etc_0300 = zoneinfo.ZoneInfo("Etc/GMT-3") +tz_offset_0300 = timezone(offset=timedelta(minutes=3 * 60)) +tz_offset_neg0300 = timezone(offset=timedelta(minutes=-3 * 60)) +tz_utc = zoneinfo.ZoneInfo("UTC") + +dt = datetime(year=2025, month=1, day=1, hour=12, minute=0, second=0) +print(dt) +# 2025-01-01 12:00:00 + +print() + +dt_moscow = dt.replace(tzinfo=tz_moscow) +print(dt_moscow) +print(dt_moscow.astimezone(tz_utc).replace(tzinfo=None)) +# 2025-01-01 12:00:00+03:00 +# 2025-01-01 09:00:00 + +print() + +dt_etc_0300 = dt.replace(tzinfo=tz_etc_0300) +print(dt_etc_0300) +print(dt_etc_0300.astimezone(tz_utc).replace(tzinfo=None)) +# 2025-01-01 12:00:00+03:00 +# 2025-01-01 09:00:00 + +print() + +dt_offset_0300 = dt.replace(tzinfo=tz_offset_0300) +print(dt_offset_0300) +print(dt_offset_0300.astimezone(tz_utc).replace(tzinfo=None)) +# 2025-01-01 12:00:00+03:00 +# 2025-01-01 09:00:00 + +print() + +dt_offset_neg0300 = dt.replace(tzinfo=tz_offset_neg0300) +print(dt_offset_neg0300) +print(dt_offset_neg0300.astimezone(tz_utc).replace(tzinfo=None)) +# 2025-01-01 12:00:00-03:00 +# 2025-01-01 15:00:00 + +print() + +dt_utc = dt.replace(tzinfo=tz_utc) +print(dt_utc) +print(dt_utc.astimezone(tz_utc).replace(tzinfo=None)) +# 2025-01-01 12:00:00+00:00 +# 2025-01-01 12:00:00 diff --git a/pytz_vs_zoneinfo__examples/get_offset__pytz.py b/pytz_vs_zoneinfo__examples/get_offset__pytz.py new file mode 100644 index 000000000..2bee60209 --- /dev/null +++ b/pytz_vs_zoneinfo__examples/get_offset__pytz.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from datetime import datetime + +# pip install pytz==2025.2 +import pytz + + +tz_moscow = pytz.timezone("Europe/Moscow") +print(tz_moscow) +# Europe/Moscow + +tz_etc_0300 = pytz.timezone("Etc/GMT-3") +print(tz_etc_0300) +# Etc/GMT-3 + +tz_offset_0300 = pytz.FixedOffset(offset=3 * 60) +print(tz_offset_0300) +# pytz.FixedOffset(180) + +tz_utc = pytz.timezone("UTC") +print(tz_utc) +# UTC + +print() + +dt = datetime(year=2025, month=1, day=1, hour=12, minute=0, second=0) + +print(dt.astimezone(tz_moscow).strftime("%z")) +print(dt.astimezone(tz_etc_0300).strftime("%z")) +print(dt.astimezone(tz_offset_0300).strftime("%z")) +print(dt.astimezone(tz_utc).strftime("%z")) +# +0300 +# +0300 +# +0300 +# +0000 diff --git a/pytz_vs_zoneinfo__examples/get_offset__zoneinfo.py b/pytz_vs_zoneinfo__examples/get_offset__zoneinfo.py new file mode 100644 index 000000000..7c4f3c6f3 --- /dev/null +++ b/pytz_vs_zoneinfo__examples/get_offset__zoneinfo.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import zoneinfo +from datetime import datetime, timedelta, timezone + + +tz_moscow = zoneinfo.ZoneInfo("Europe/Moscow") +print(tz_moscow) +# Europe/Moscow + +tz_etc_0300 = zoneinfo.ZoneInfo("Etc/GMT-3") +print(tz_etc_0300) +# Etc/GMT-3 + +tz_offset_0300 = timezone(offset=timedelta(minutes=3 * 60)) +print(tz_offset_0300) +# pytz.FixedOffset(180) + +tz_utc = zoneinfo.ZoneInfo("UTC") +print(tz_utc) +# UTC + +print() + +dt = datetime(year=2025, month=1, day=1, hour=12, minute=0, second=0) + +print(dt.astimezone(tz_moscow).strftime("%z")) +print(dt.astimezone(tz_etc_0300).strftime("%z")) +print(dt.astimezone(tz_offset_0300).strftime("%z")) +print(dt.astimezone(tz_utc).strftime("%z")) +# +0300 +# +0300 +# +0300 +# +0000 diff --git a/pytz_vs_zoneinfo__examples/get_timezones__pytz.py b/pytz_vs_zoneinfo__examples/get_timezones__pytz.py new file mode 100644 index 000000000..4cc985eac --- /dev/null +++ b/pytz_vs_zoneinfo__examples/get_timezones__pytz.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +# pip install pytz==2025.2 +import pytz + + +timezones: list[str] = pytz.all_timezones +print(len(timezones), timezones) +# 594 ['Africa/Abidjan', 'Africa/Accra', 'Africa/Addis_Ababa', ..., 'W-SU', 'WET', 'Zulu'] + +timezones: list[str] = pytz.common_timezones +print(len(timezones), timezones) +# 439 ['Africa/Abidjan', 'Africa/Accra', 'Africa/Addis_Ababa', ..., 'US/Mountain', 'US/Pacific', 'UTC'] diff --git a/pytz_vs_zoneinfo__examples/get_timezones__zoneinfo.py b/pytz_vs_zoneinfo__examples/get_timezones__zoneinfo.py new file mode 100644 index 000000000..a7cc79dc6 --- /dev/null +++ b/pytz_vs_zoneinfo__examples/get_timezones__zoneinfo.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import zoneinfo + + +timezones: list[str] = sorted(zoneinfo.available_timezones()) +print(len(timezones), timezones) +# 597 ['Africa/Abidjan', 'Africa/Accra', 'Africa/Addis_Ababa', ..., 'W-SU', 'WET', 'Zulu'] diff --git a/pytz_vs_zoneinfo__examples/get_tz__pytz.py b/pytz_vs_zoneinfo__examples/get_tz__pytz.py new file mode 100644 index 000000000..18165a422 --- /dev/null +++ b/pytz_vs_zoneinfo__examples/get_tz__pytz.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +# pip install pytz==2025.2 +import pytz + + +tz_moscow = pytz.timezone("Europe/Moscow") +print(tz_moscow) +# Europe/Moscow + +tz_utc = pytz.timezone("UTC") +print(tz_utc) +# UTC + +try: + tz = pytz.timezone("INVALID") + raise Exception() +except pytz.UnknownTimeZoneError as e: + print(e) +# 'INVALID' + +try: + tz = pytz.timezone(None) + raise Exception() +except pytz.UnknownTimeZoneError as e: + print(e) +# None diff --git a/pytz_vs_zoneinfo__examples/get_tz__zoneinfo.py b/pytz_vs_zoneinfo__examples/get_tz__zoneinfo.py new file mode 100644 index 000000000..c6fe4b826 --- /dev/null +++ b/pytz_vs_zoneinfo__examples/get_tz__zoneinfo.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import zoneinfo +from datetime import datetime, timedelta, timezone + + +tz_moscow = zoneinfo.ZoneInfo("Europe/Moscow") +print(tz_moscow) +# Europe/Moscow + +tz_utc = zoneinfo.ZoneInfo("UTC") +print(tz_utc) +# UTC + +try: + tz = zoneinfo.ZoneInfo("INVALID") + raise Exception() +except zoneinfo.ZoneInfoNotFoundError as e: + print(e) +# 'No time zone found with key INVALID' + +try: + tz = zoneinfo.ZoneInfo(None) + raise Exception() +except TypeError as e: + print(e) +# expected str, bytes or os.PathLike object, not NoneType diff --git a/pytz_vs_zoneinfo__examples/get_tz_by_country__pytz.py b/pytz_vs_zoneinfo__examples/get_tz_by_country__pytz.py new file mode 100644 index 000000000..6937b5366 --- /dev/null +++ b/pytz_vs_zoneinfo__examples/get_tz_by_country__pytz.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +# pip install pytz==2025.2 +import pytz + + +ru_timezones: list[str] = pytz.country_timezones["ru"] +print(len(ru_timezones)) +# 26 + +for tz in ru_timezones: + print(tz) +""" +Europe/Kaliningrad +Europe/Moscow +Europe/Kirov +Europe/Volgograd +Europe/Astrakhan +Europe/Saratov +Europe/Ulyanovsk +Europe/Samara +Asia/Yekaterinburg +Asia/Omsk +Asia/Novosibirsk +Asia/Barnaul +Asia/Tomsk +Asia/Novokuznetsk +Asia/Krasnoyarsk +Asia/Irkutsk +Asia/Chita +Asia/Yakutsk +Asia/Khandyga +Asia/Vladivostok +Asia/Ust-Nera +Asia/Magadan +Asia/Sakhalin +Asia/Srednekolymsk +Asia/Kamchatka +Asia/Anadyr +""" diff --git a/pytz_vs_zoneinfo__examples/get_tz_from_offset__pytz.py b/pytz_vs_zoneinfo__examples/get_tz_from_offset__pytz.py new file mode 100644 index 000000000..f673cb92b --- /dev/null +++ b/pytz_vs_zoneinfo__examples/get_tz_from_offset__pytz.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import re +from datetime import datetime, tzinfo, timezone + +# pip install pytz==2025.2 +import pytz + + +PATTERN_TZ_OFFSET = re.compile(r"(?P[+-])(?P\d{2}):?(?P\d{2})") + + +def get_tz(value: str) -> tzinfo: + m = PATTERN_TZ_OFFSET.search(value) + if not m: + raise Exception(f"Invalid time zone: {value!r}") + + sign: str = m.group("sign") + hour: int = int(m.group("hour")) + minute: int = int(m.group("minute")) + + total_minutes = hour * 60 + minute + if sign == "-": + total_minutes = -total_minutes + + return pytz.FixedOffset(offset=total_minutes) + + +if __name__ == "__main__": + now_utc = datetime.now(timezone.utc) + + test_cases = [ + (pytz.timezone("Etc/GMT-3"), "+0300"), + (pytz.FixedOffset(offset=3 * 60), "+0300"), + (get_tz("+0300"), "+0300"), + (get_tz("+03:00"), "+0300"), + (get_tz("-0300"), "-0300") + ] + + for tz, expected in test_cases: + result = now_utc.astimezone(tz).strftime("%z") + print(result) + assert result == expected diff --git a/pytz_vs_zoneinfo__examples/get_tz_from_offset__zoneinfo.py b/pytz_vs_zoneinfo__examples/get_tz_from_offset__zoneinfo.py new file mode 100644 index 000000000..d213e598f --- /dev/null +++ b/pytz_vs_zoneinfo__examples/get_tz_from_offset__zoneinfo.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import re +from datetime import datetime, timedelta, timezone, tzinfo + + +PATTERN_TZ_OFFSET = re.compile(r"(?P[+-])(?P\d{2}):?(?P\d{2})") + + +def get_tz(value: str) -> tzinfo: + m = PATTERN_TZ_OFFSET.search(value) + if not m: + raise Exception(f"Invalid time zone: {value!r}") + + sign: str = m.group("sign") + hour: int = int(m.group("hour")) + minute: int = int(m.group("minute")) + + total_minutes = hour * 60 + minute + if sign == "-": + total_minutes = -total_minutes + + return timezone(offset=timedelta(minutes=total_minutes)) + + +if __name__ == "__main__": + import zoneinfo + + now_utc = datetime.now(timezone.utc) + + test_cases = [ + (zoneinfo.ZoneInfo("Etc/GMT-3"), "+0300"), + (timezone(offset=timedelta(minutes=3 * 60)), "+0300"), + (get_tz("+0300"), "+0300"), + (get_tz("+03:00"), "+0300"), + (get_tz("-0300"), "-0300") + ] + + for tz, expected in test_cases: + result = now_utc.astimezone(tz).strftime("%z") + print(result) + assert result == expected diff --git a/pytz_vs_zoneinfo__examples/utc_plus_tz__pytz.py b/pytz_vs_zoneinfo__examples/utc_plus_tz__pytz.py new file mode 100644 index 000000000..b62df7409 --- /dev/null +++ b/pytz_vs_zoneinfo__examples/utc_plus_tz__pytz.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from datetime import datetime + +# pip install pytz==2025.2 +import pytz + + +tz_moscow = pytz.timezone("Europe/Moscow") +tz_etc_0300 = pytz.timezone("Etc/GMT-3") +tz_offset_0300 = pytz.FixedOffset(offset=3 * 60) +tz_utc = pytz.timezone("UTC") + +dt = datetime(year=2025, month=1, day=1, hour=12, minute=0, second=0) +print(dt) +# 2025-01-01 12:00:00 + +print() + +print(tz_moscow.fromutc(dt)) +print(tz_etc_0300.fromutc(dt)) +print(tz_offset_0300.fromutc(tz_offset_0300.localize(dt))) +print(tz_utc.fromutc(tz_utc.localize(dt))) +# 2025-01-01 15:00:00+03:00 +# 2025-01-01 15:00:00+03:00 +# 2025-01-01 15:00:00+03:00 +# 2025-01-01 12:00:00+00:00 diff --git a/pytz_vs_zoneinfo__examples/utc_plus_tz__zoneinfo.py b/pytz_vs_zoneinfo__examples/utc_plus_tz__zoneinfo.py new file mode 100644 index 000000000..73ad0f6d1 --- /dev/null +++ b/pytz_vs_zoneinfo__examples/utc_plus_tz__zoneinfo.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import zoneinfo +from datetime import datetime, timedelta, timezone + + +tz_moscow = zoneinfo.ZoneInfo("Europe/Moscow") +tz_etc_0300 = zoneinfo.ZoneInfo("Etc/GMT-3") +tz_offset_0300 = timezone(offset=timedelta(minutes=3 * 60)) +tz_utc = zoneinfo.ZoneInfo("UTC") + +dt_utc = datetime(year=2025, month=1, day=1, hour=12, minute=0, second=0, tzinfo=tz_utc) +print(dt_utc) +# 2025-01-01 12:00:00+00:00 + +assert dt_utc == datetime.fromisoformat("2025-01-01 12:00:00").replace(tzinfo=dt_utc.tzinfo) +assert dt_utc == datetime.fromisoformat("2025-01-01 12:00:00").replace(tzinfo=timezone.utc) + +print() + +print(dt_utc.astimezone(tz_moscow)) +print(dt_utc.astimezone(tz_etc_0300)) +print(dt_utc.astimezone(tz_offset_0300)) +print(dt_utc.astimezone(tz_utc)) +# 2025-01-01 15:00:00+03:00 +# 2025-01-01 15:00:00+03:00 +# 2025-01-01 15:00:00+03:00 +# 2025-01-01 12:00:00+00:00 + +print("\n" + "-" * 10 + "\n") + +dt = datetime(year=2025, month=1, day=1, hour=12, minute=0, second=0) +print(dt) +# 2025-01-01 12:00:00 + +print() + +print(tz_moscow.fromutc(dt.replace(tzinfo=tz_moscow))) +print(tz_etc_0300.fromutc(dt.replace(tzinfo=tz_etc_0300))) +print(tz_offset_0300.fromutc(dt.replace(tzinfo=tz_offset_0300))) +print(tz_utc.fromutc(dt.replace(tzinfo=tz_utc))) +# 2025-01-01 15:00:00+03:00 +# 2025-01-01 15:00:00+03:00 +# 2025-01-01 15:00:00+03:00 +# 2025-01-01 12:00:00+00:00 diff --git a/qbittorrent_examples/about_version.py b/qbittorrent_examples/about_version.py index 2fe3c6757..466279d99 100644 --- a/qbittorrent_examples/about_version.py +++ b/qbittorrent_examples/about_version.py @@ -1,11 +1,12 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from common import get_client + qb = get_client() -print('Get qBittorrent version:', qb.qbittorrent_version) -print('Get WEB API version:', qb.api_version) +print("Get qBittorrent version:", qb.qbittorrent_version) +print("Get WEB API version:", qb.api_version) diff --git a/qbittorrent_examples/check_changes_in_torrent.py b/qbittorrent_examples/check_changes_in_torrent.py index a62124426..d6b80645e 100644 --- a/qbittorrent_examples/check_changes_in_torrent.py +++ b/qbittorrent_examples/check_changes_in_torrent.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """ @@ -10,6 +10,21 @@ """ +import time +import traceback +import re + +from collections import defaultdict +from datetime import datetime + +import requests +import effbot_bencode + +from bs4 import BeautifulSoup + +# pip install simple-wait +from simple_wait import wait + def get_rutor_torrent_download_info(torrent_url): """ @@ -17,27 +32,23 @@ def get_rutor_torrent_download_info(torrent_url): """ - import requests rs = requests.get(torrent_url) + root = BeautifulSoup(rs.content, "lxml") - from bs4 import BeautifulSoup - root = BeautifulSoup(rs.content, 'lxml') - - magnet_url = root.select_one('#download > a[href^="magnet"]')['href'] + magnet_url = root.select_one('#download > a[href^="magnet"]')["href"] # For get info hash from magnet url - import re - match = re.compile(r'btih:([abcdef\d]+?)&', flags=re.IGNORECASE).search(magnet_url) + match = re.compile(r"btih:([abcdef\d]+?)&", flags=re.IGNORECASE).search(magnet_url) if match: info_hash = match.group(1) - return torrent_url.replace('/torrent/', '/download/'), magnet_url, info_hash - + return torrent_url.replace("/torrent/", "/download/"), magnet_url, info_hash -def remove_previous_torrent_from_qbittorrent(qb, new_info_hash): - info_hash_by_name_dict = {torrent['hash']: torrent['name'] for torrent in qb.torrents()} - from collections import defaultdict +def remove_previous_torrent_from_qbittorrent(qb, new_info_hash) -> None: + info_hash_by_name_dict = { + torrent["hash"]: torrent["name"] for torrent in qb.torrents() + } name_by_info_hash_list_dict = defaultdict(list) for info_hash, name in info_hash_by_name_dict.items(): @@ -56,60 +67,61 @@ def remove_previous_torrent_from_qbittorrent(qb, new_info_hash): # Remove previous torrents if info_hash_list: - print('Удаление предыдущих раздач этого торрента: {}'.format(info_hash_list)) + print( + f"Удаление предыдущих раздач этого торрента: {info_hash_list}" + ) qb.delete(info_hash_list) else: print("Предыдущие закачки не найдены") - -# Import https://github.com/gil9red/SimplePyScripts/blob/8fa9b9c23d10b5ee7ff0161da997b463f7a861bf/wait/wait.py -import sys -sys.path.append('../wait') - -from wait import wait - -if __name__ == '__main__': +if __name__ == "__main__": from config import API_ID, TO from common import get_client qb = get_client() - torrent_url = 'http://anti-tor.org/torrent/544942' + torrent_url = "http://anti-tor.org/torrent/544942" last_info_hash = None last_torrent_files = list() while True: try: - from datetime import datetime today = datetime.today() - torrent_file_url, _, info_hash = get_rutor_torrent_download_info(torrent_url) - print('{}: Проверка {}: {} / {}'.format(today, torrent_url, torrent_file_url, info_hash)) + torrent_file_url, _, info_hash = get_rutor_torrent_download_info( + torrent_url + ) + print( + f"{today}: Проверка {torrent_url}: {torrent_file_url} / {info_hash}" + ) if qb.get_torrent(info_hash): - print('Торрент {} уже есть в списке раздачи'.format(info_hash)) + print(f"Торрент {info_hash} уже есть в списке раздачи") else: if info_hash != last_info_hash: - import requests - data = requests.get(torrent_file_url).content.decode('latin1') - - import effbot_bencode + data = requests.get(torrent_file_url).content.decode("latin1") torrent = effbot_bencode.decode(data) - files = ["/".join(file["path"]) for file in torrent["info"]["files"]] + files = [ + "/".join(file["path"]) for file in torrent["info"]["files"] + ] # Использование множеств, чтобы узнать разницу списков, т.е. какие файлы были добавлены # А чтобы узнать какие были удалены: list(set(last_torrent_files) - set(files)) new_files = list(set(files) - set(last_torrent_files)) if last_info_hash is None: - print("Добавление торрента с {} файлами: {}".format(len(new_files), new_files)) + print( + f"Добавление торрента с {len(new_files)} файлами: {new_files}" + ) else: - print('Торрент изменился, пора его перекачивать') - print("Добавлено {} файлов: {}".format(len(new_files), new_files)) + print("Торрент изменился, пора его перекачивать") + print( + f"Добавлено {len(new_files)} файлов: {new_files}" + ) last_info_hash = info_hash last_torrent_files = files @@ -119,22 +131,21 @@ def remove_previous_torrent_from_qbittorrent(qb, new_info_hash): qb.download_from_link(torrent_file_url) # Отправляю смс на номер - url = 'https://sms.ru/sms/send?api_id={api_id}&to={to}&text={text}'.format( + url = "https://sms.ru/sms/send?api_id={api_id}&to={to}&text={text}".format( api_id=API_ID, to=TO, - text="Вышла новая серия '{}'".format(torrent['info']['name']) + text=f"Вышла новая серия '{torrent['info']['name']}'", ) rs = requests.get(url) print(rs.text) # Даем 5 секунд на добавление торрента в клиент - import time time.sleep(5) remove_previous_torrent_from_qbittorrent(qb, info_hash) else: - print('Изменений нет') + print("Изменений нет") print() @@ -142,12 +153,10 @@ def remove_previous_torrent_from_qbittorrent(qb, new_info_hash): wait(hours=3) except Exception: - import traceback - print('Ошибка:') + print("Ошибка:") print(traceback.format_exc()) - print('Через 1 минуту попробую снова...') + print("Через 1 минуту попробую снова...") - # Wait 1 minute before next attempt - import time - time.sleep(60) + # Wait before next attempt + wait(minutes=1) diff --git a/qbittorrent_examples/common.py b/qbittorrent_examples/common.py index ddc95e8e8..6691e6592 100644 --- a/qbittorrent_examples/common.py +++ b/qbittorrent_examples/common.py @@ -1,12 +1,11 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -import sys -from typing import List, Dict -from pathlib import Path +# pip install humanize +from humanize import naturalsize as sizeof_fmt # pip install tabulate from tabulate import tabulate @@ -16,11 +15,8 @@ from config import IP_HOST, USER, PASSWORD -sys.path.append(str(Path(__file__).resolve().parent.parent)) -from human_byte_size import sizeof_fmt - -def print_table(rows: List[List[str]], headers: List[str], show_index=True): +def print_table(rows: list[list[str]], headers: list[str], show_index=True) -> None: if show_index: show_index = range(1, len(rows) + 1) @@ -28,23 +24,28 @@ def print_table(rows: List[List[str]], headers: List[str], show_index=True): print(text) -def print_files_table(files: List[Dict]): - rows = [(file['name'], sizeof_fmt(file['size'])) for file in sorted(files, key=lambda x: x['name'])] - headers = ['#', 'File Name', 'Size'] +def print_files_table(files: list[dict]) -> None: + rows = [ + (file["name"], sizeof_fmt(file["size"])) + for file in sorted(files, key=lambda x: x["name"]) + ] + headers = ["#", "File Name", "Size"] print_table(rows, headers) -def print_torrents(torrents: List[Dict]): +def print_torrents(torrents: list[dict]) -> None: total_size = 0 for i, torrent in enumerate(torrents, 1): - torrent_size = torrent['total_size'] + torrent_size = torrent["total_size"] total_size += torrent_size print(f"{i:3}. {torrent['name']} ({sizeof_fmt(torrent_size)})") print() - print(f'Total torrents: {len(torrents)}, total size: {sizeof_fmt(total_size)} ({total_size} bytes)') + print( + f"Total torrents: {len(torrents)}, total size: {sizeof_fmt(total_size)} ({total_size} bytes)" + ) def get_client() -> Client: diff --git a/qbittorrent_examples/config.py b/qbittorrent_examples/config.py index 9d2ee95d9..657fe9bde 100644 --- a/qbittorrent_examples/config.py +++ b/qbittorrent_examples/config.py @@ -1,13 +1,13 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -IP_HOST = 'http://127.0.0.1:8080/' -USER = 'admin' -PASSWORD = '' +IP_HOST = "http://127.0.0.1:8080/" +USER = "admin" +PASSWORD = "" # Для отправки смс -API_ID = '' -TO = '' +API_ID = "" +TO = "" diff --git a/qbittorrent_examples/effbot_bencode.py b/qbittorrent_examples/effbot_bencode.py index b6cea7858..3777e710b 100644 --- a/qbittorrent_examples/effbot_bencode.py +++ b/qbittorrent_examples/effbot_bencode.py @@ -17,7 +17,7 @@ def tokenize(text, match=re.compile("([idel])|(\d+):|(-?\d+)").match): i = m.end() if m.lastindex == 2: yield "s" - yield text[i:i+int(s)] + yield text[i : i + int(s)] i = i + int(s) else: yield s @@ -50,7 +50,7 @@ def decode(text): try: src = tokenize(text) data = decode_item(src.__next__, next(src)) - for token in src: # look for more tokens + for token in src: # look for more tokens raise SyntaxError("trailing junk") except (AttributeError, ValueError, StopIteration): raise SyntaxError("syntax error") diff --git a/qbittorrent_examples/get_info_random_torrent.py b/qbittorrent_examples/get_info_random_torrent.py index 5a43b603b..295f6b9f0 100644 --- a/qbittorrent_examples/get_info_random_torrent.py +++ b/qbittorrent_examples/get_info_random_torrent.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import random @@ -13,7 +13,7 @@ print(f"{torrent['name']} ({sizeof_fmt(torrent['total_size'])})") print() -files = qb.get_torrent_files(torrent['hash']) -print(f'Files ({len(files)}):') +files = qb.get_torrent_files(torrent["hash"]) +print(f"Files ({len(files)}):") print_files_table(files) diff --git a/qbittorrent_examples/get_top5_torrent_by_size.py b/qbittorrent_examples/get_top5_torrent_by_size.py index a4468e863..5b89fb8d7 100644 --- a/qbittorrent_examples/get_top5_torrent_by_size.py +++ b/qbittorrent_examples/get_top5_torrent_by_size.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from common import sizeof_fmt, get_client @@ -11,15 +11,19 @@ torrents = qb.torrents() -torrents_max_top5 = sorted(torrents, key=lambda x: x['total_size'], reverse=True)[:5] -torrents_min_top5 = sorted(torrents, key=lambda x: x['total_size'])[:5] +torrents_max_top5 = sorted(torrents, key=lambda x: x["total_size"], reverse=True)[:5] +torrents_min_top5 = sorted(torrents, key=lambda x: x["total_size"])[:5] -print('Max top5:') +print("Max top5:") for i, torrent in enumerate(torrents_max_top5, 1): - print(' {}. {} ({})'.format(i, torrent['name'], sizeof_fmt(torrent['total_size']))) + print( + f" {i}. {torrent['name']} ({sizeof_fmt(torrent['total_size'])})" + ) print() -print('Min top5:') +print("Min top5:") for i, torrent in enumerate(torrents_min_top5, 1): - print(' {}. {} ({})'.format(i, torrent['name'], sizeof_fmt(torrent['total_size']))) + print( + f" {i}. {torrent['name']} ({sizeof_fmt(torrent['total_size'])})" + ) diff --git a/qbittorrent_examples/get_top5_torrent_by_size__use_api_sort_limit.py b/qbittorrent_examples/get_top5_torrent_by_size__use_api_sort_limit.py index 44fef4851..c47a2f3aa 100644 --- a/qbittorrent_examples/get_top5_torrent_by_size__use_api_sort_limit.py +++ b/qbittorrent_examples/get_top5_torrent_by_size__use_api_sort_limit.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from common import sizeof_fmt, get_client @@ -9,15 +9,19 @@ qb = get_client() -torrents_max_top5 = qb.torrents(sort='size', reverse='true', limit='5') -torrents_min_top5 = qb.torrents(sort='size', limit='5') +torrents_max_top5 = qb.torrents(sort="size", reverse="true", limit="5") +torrents_min_top5 = qb.torrents(sort="size", limit="5") -print('Max top5:') +print("Max top5:") for i, torrent in enumerate(torrents_max_top5, 1): - print(' {}. {} ({})'.format(i, torrent['name'], sizeof_fmt(torrent['total_size']))) + print( + f" {i}. {torrent['name']} ({sizeof_fmt(torrent['total_size'])})" + ) print() -print('Min top5:') +print("Min top5:") for i, torrent in enumerate(torrents_min_top5, 1): - print(' {}. {} ({})'.format(i, torrent['name'], sizeof_fmt(torrent['total_size']))) + print( + f" {i}. {torrent['name']} ({sizeof_fmt(torrent['total_size'])})" + ) diff --git a/qbittorrent_examples/get_top_torrent_by_files_number.py b/qbittorrent_examples/get_top_torrent_by_files_number.py index 93481442e..6b7963568 100644 --- a/qbittorrent_examples/get_top_torrent_by_files_number.py +++ b/qbittorrent_examples/get_top_torrent_by_files_number.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from common import sizeof_fmt, get_client, print_files_table @@ -10,12 +10,12 @@ qb = get_client() torrents = qb.torrents() -torrent = max(torrents, key=lambda x: len(qb.get_torrent_files(x['hash']))) +torrent = max(torrents, key=lambda x: len(qb.get_torrent_files(x["hash"]))) -print('{} ({})'.format(torrent['name'], sizeof_fmt(torrent['total_size']))) +print(f"{torrent['name']} ({sizeof_fmt(torrent['total_size'])})") print() -files = qb.get_torrent_files(torrent['hash']) -print('Files ({}):'.format(len(files))) +files = qb.get_torrent_files(torrent["hash"]) +print(f"Files ({len(files)}):") print_files_table(files) diff --git a/qbittorrent_examples/print_current__save_path__of_torrents.py b/qbittorrent_examples/print_current__save_path__of_torrents.py index 08103cda9..cb134c5f3 100644 --- a/qbittorrent_examples/print_current__save_path__of_torrents.py +++ b/qbittorrent_examples/print_current__save_path__of_torrents.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from collections import Counter @@ -10,10 +10,10 @@ qb = get_client() -items = [torrent['save_path'] for torrent in qb.torrents()] +items = [torrent["save_path"] for torrent in qb.torrents()] counter = Counter(items) -print('Save path:', sorted(counter.keys())) +print("Save path:", sorted(counter.keys())) for name, number in counter.items(): - print(f'{name!r}: {number}') + print(f"{name!r}: {number}") diff --git a/qbittorrent_examples/print_not_torrent_content_on_saved_paths.py b/qbittorrent_examples/print_not_torrent_content_on_saved_paths.py index e12257a8e..79b7fc0ca 100644 --- a/qbittorrent_examples/print_not_torrent_content_on_saved_paths.py +++ b/qbittorrent_examples/print_not_torrent_content_on_saved_paths.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/qbittorrent/qBittorrent/blob/ccd8f3e0f1447c0f88fedb975b52e2691722d6e9/src/base/bittorrent/torrenthandle.cpp#L302 @@ -10,6 +10,7 @@ from glob import glob, escape as glob_escape from os.path import join, normpath, getsize, isfile from os import listdir + from common import get_client, sizeof_fmt @@ -17,9 +18,9 @@ save_path_current_paths = [] -for save_path in set(torrent['save_path'] for torrent in qb.torrents()): +for save_path in set(torrent["save_path"] for torrent in qb.torrents()): for file_name in listdir(save_path): - if file_name == '.unwanted': + if file_name == ".unwanted": continue path = normpath(join(save_path, file_name)) @@ -29,20 +30,20 @@ torrent_content_path_list = [] -for torrent in qb.torrents(sort='name'): +for torrent in qb.torrents(sort="name"): # Нужны только те торренты, что скачаны (хоть частично) - if torrent['downloaded'] == 0: + if torrent["downloaded"] == 0: continue - files = qb.get_torrent_files(torrent['hash']) + files = qb.get_torrent_files(torrent["hash"]) - save_path = torrent['save_path'] - first_file_path = files[0]['name'] + save_path = torrent["save_path"] + first_file_path = files[0]["name"] # Найдем первый разделите папки - index = first_file_path.find('/') + index = first_file_path.find("/") if index == -1: - index = first_file_path.find('\\') + index = first_file_path.find("\\") if index != -1: # Путь до папки торрента @@ -67,12 +68,12 @@ size = getsize(path) else: # Найдем все файлы в папке и подпапках и подсчитаем их суммарный размер - sub_files = filter(isfile, glob(glob_escape(path) + '/**', recursive=True)) + sub_files = filter(isfile, glob(glob_escape(path) + "/**", recursive=True)) size = sum(getsize(file_name) for file_name in sub_files) total_size += size - print(f'{path} ({size} / {sizeof_fmt(size)})') + print(f"{path} ({size} / {sizeof_fmt(size)})") print() -print(f'Total size: {total_size} / {sizeof_fmt(total_size)}') +print(f"Total size: {total_size} / {sizeof_fmt(total_size)}") diff --git a/qbittorrent_examples/print_torrent_comment.py b/qbittorrent_examples/print_torrent_comment.py index ffa968c26..2bae74324 100644 --- a/qbittorrent_examples/print_torrent_comment.py +++ b/qbittorrent_examples/print_torrent_comment.py @@ -1,14 +1,14 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from common import get_client def get_comment(torrent): - return qb.get_torrent(torrent['hash'])['comment'] + return qb.get_torrent(torrent["hash"])["comment"] qb = get_client() @@ -21,4 +21,4 @@ def get_comment(torrent): comment_list.append(comment) comment_list.sort() -print('\n'.join(comment_list)) +print("\n".join(comment_list)) diff --git a/qbittorrent_examples/print_torrent_created_by.py b/qbittorrent_examples/print_torrent_created_by.py index db6b0ab77..8a724f973 100644 --- a/qbittorrent_examples/print_torrent_created_by.py +++ b/qbittorrent_examples/print_torrent_created_by.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from common import get_client @@ -12,8 +12,8 @@ created_by_list = set() for torrent in qb.torrents(): - created_by = qb.get_torrent(torrent['hash'])['created_by'] + created_by = qb.get_torrent(torrent["hash"])["created_by"] if created_by: created_by_list.add(created_by) -print('\n'.join(created_by_list)) +print("\n".join(created_by_list)) diff --git a/qbittorrent_examples/print_torrents_by_specific_Disc.py b/qbittorrent_examples/print_torrents_by_specific_Disc.py index 7a9a75d46..3f687bb13 100644 --- a/qbittorrent_examples/print_torrents_by_specific_Disc.py +++ b/qbittorrent_examples/print_torrents_by_specific_Disc.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from common import get_client, print_torrents @@ -9,5 +9,5 @@ qb = get_client() need_disc = "E:" -torrents = [x for x in qb.torrents() if x['save_path'].startswith(need_disc)] +torrents = [x for x in qb.torrents() if x["save_path"].startswith(need_disc)] print_torrents(torrents) diff --git a/qbittorrent_examples/print_total_size.py b/qbittorrent_examples/print_total_size.py index db0d9e616..e92d04f41 100644 --- a/qbittorrent_examples/print_total_size.py +++ b/qbittorrent_examples/print_total_size.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from common import get_client, sizeof_fmt @@ -10,7 +10,7 @@ qb = get_client() torrents = qb.torrents() -total_size = sum(torrent['total_size'] for torrent in torrents) +total_size = sum(torrent["total_size"] for torrent in torrents) -print(f'Total torrents: {len(torrents)}') -print(f'Total size: {sizeof_fmt(total_size)} ({total_size} bytes)') +print(f"Total torrents: {len(torrents)}") +print(f"Total size: {sizeof_fmt(total_size)} ({total_size} bytes)") diff --git a/qbittorrent_examples/send_api.py b/qbittorrent_examples/send_api.py index e41ab8017..43f354e4d 100644 --- a/qbittorrent_examples/send_api.py +++ b/qbittorrent_examples/send_api.py @@ -1,10 +1,12 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import requests -rs = requests.get('http://127.0.0.1:8080/api/v2/torrents/info') + + +rs = requests.get("http://127.0.0.1:8080/api/v2/torrents/info") print(rs) print(rs.text) diff --git a/qbittorrent_examples/shutdown.py b/qbittorrent_examples/shutdown.py index b7019406b..ff659ad6d 100644 --- a/qbittorrent_examples/shutdown.py +++ b/qbittorrent_examples/shutdown.py @@ -1,10 +1,11 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from common import get_client + qb = get_client() qb.shutdown() diff --git a/qbittorrent_examples/torrent_list.py b/qbittorrent_examples/torrent_list.py index f2eb9f9a5..4f9b1b8a9 100644 --- a/qbittorrent_examples/torrent_list.py +++ b/qbittorrent_examples/torrent_list.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/qbittorrent/qbittorrent/wiki/WebUI-API-Documentation#get-torrent-list diff --git a/qbittorrent_examples/torrent_list__sort_by_added_on.py b/qbittorrent_examples/torrent_list__sort_by_added_on.py index 842c6e810..d6ffe6d7b 100644 --- a/qbittorrent_examples/torrent_list__sort_by_added_on.py +++ b/qbittorrent_examples/torrent_list__sort_by_added_on.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/qbittorrent/qbittorrent/wiki/WebUI-API-Documentation#get-torrent-list @@ -11,5 +11,5 @@ qb = get_client() -torrents = qb.torrents(sort='added_on', reverse='true') +torrents = qb.torrents(sort="added_on", reverse="true") print_torrents(torrents) diff --git a/qbittorrent_examples/torrent_list__sort_by_name.py b/qbittorrent_examples/torrent_list__sort_by_name.py index f3cb95e0a..34aeeabaa 100644 --- a/qbittorrent_examples/torrent_list__sort_by_name.py +++ b/qbittorrent_examples/torrent_list__sort_by_name.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/qbittorrent/qbittorrent/wiki/WebUI-API-Documentation#get-torrent-list @@ -12,7 +12,7 @@ qb = get_client() -torrents = qb.torrents(sort='name') +torrents = qb.torrents(sort="name") # Desc / Reverse: # torrents = qb.torrents(sort='name', reverse='true') diff --git a/qbittorrent_examples/torrent_list__table_gui_pyqt.py b/qbittorrent_examples/torrent_list__table_gui_pyqt.py index 0e6c3b35a..9b0c01f7b 100644 --- a/qbittorrent_examples/torrent_list__table_gui_pyqt.py +++ b/qbittorrent_examples/torrent_list__table_gui_pyqt.py @@ -1,33 +1,33 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/qbittorrent/qbittorrent/wiki/WebUI-API-Documentation#get-torrent-list -from common import get_client - from PyQt5.Qt import * +from common import get_client + class TorrentInfoWidget(QTableWidget): - def __init__(self): + def __init__(self) -> None: super().__init__() self.setEditTriggers(QTableWidget.NoEditTriggers) self.setSelectionBehavior(QTableWidget.SelectRows) self.setSelectionMode(QTableWidget.SingleSelection) - headers = ['KEY', 'VALUE'] + headers = ["KEY", "VALUE"] self.setColumnCount(len(headers)) self.setHorizontalHeaderLabels(headers) self.horizontalHeader().setStretchLastSection(True) self.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeToContents) - def fill(self, torrent_details: dict): + def fill(self, torrent_details: dict) -> None: while self.rowCount(): self.removeRow(0) @@ -40,7 +40,7 @@ def fill(self, torrent_details: dict): class MainWindow(QMainWindow): - def __init__(self): + def __init__(self) -> None: super().__init__() self.table_torrent = QTableWidget() @@ -57,7 +57,7 @@ def __init__(self): self.setCentralWidget(splitter) - def fill_table(self): + def fill_table(self) -> None: qb = get_client() torrents = qb.torrents() if not torrents: @@ -80,7 +80,7 @@ def fill_table(self): self.table_torrent.setItem(row, column, item) - def fill_torrent_info(self): + def fill_torrent_info(self) -> None: items = self.table_torrent.selectedItems() if not items: return @@ -88,12 +88,12 @@ def fill_torrent_info(self): torrent = items[0].data(Qt.UserRole) qb = get_client() - torrent_details = qb.get_torrent(torrent['hash']) + torrent_details = qb.get_torrent(torrent["hash"]) self.torrent_info_widget.fill(torrent_details) -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) mw = MainWindow() diff --git a/qbittorrent_examples/torrent_list__with_filter.py b/qbittorrent_examples/torrent_list__with_filter.py index b11e888ea..09bc98b9d 100644 --- a/qbittorrent_examples/torrent_list__with_filter.py +++ b/qbittorrent_examples/torrent_list__with_filter.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/qbittorrent/qbittorrent/wiki/WebUI-API-Documentation#get-torrent-list @@ -11,5 +11,5 @@ qb = get_client() -torrents = qb.torrents(filter='downloading') +torrents = qb.torrents(filter="downloading") print_torrents(torrents) diff --git a/qbittorrent_examples/torrent_list__with_filter_by_name.py b/qbittorrent_examples/torrent_list__with_filter_by_name.py index 6d527f511..2ac0e4b4c 100644 --- a/qbittorrent_examples/torrent_list__with_filter_by_name.py +++ b/qbittorrent_examples/torrent_list__with_filter_by_name.py @@ -1,19 +1,19 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from common import get_client, print_torrents -def get_torrents(qb, search_name='', **filters) -> list: +def get_torrents(qb, search_name="", **filters) -> list: def match(name: str) -> bool: return search_name.lower() in name.lower() - return [torrent for torrent in qb.torrents(**filters) if match(torrent['name'])] + return [torrent for torrent in qb.torrents(**filters) if match(torrent["name"])] qb = get_client() -torrents = get_torrents(qb, search_name='.mkv') +torrents = get_torrents(qb, search_name=".mkv") print_torrents(torrents) diff --git a/qbittorrent_examples/torrent_list__with_filter_plus_sort_by_added_on.py b/qbittorrent_examples/torrent_list__with_filter_plus_sort_by_added_on.py index ef7e2b087..f8c036311 100644 --- a/qbittorrent_examples/torrent_list__with_filter_plus_sort_by_added_on.py +++ b/qbittorrent_examples/torrent_list__with_filter_plus_sort_by_added_on.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/qbittorrent/qbittorrent/wiki/WebUI-API-Documentation#get-torrent-list @@ -11,5 +11,5 @@ qb = get_client() -torrents = qb.torrents(filter='downloading', sort='added_on', reverse='true') +torrents = qb.torrents(filter="downloading", sort="added_on", reverse="true") print_torrents(torrents) diff --git a/pyqrcode__examples/db_sqlite3.py b/qrcode/pyqrcode__examples/db_sqlite3.py similarity index 80% rename from pyqrcode__examples/db_sqlite3.py rename to qrcode/pyqrcode__examples/db_sqlite3.py index 3a714ca4c..806d1bf7e 100644 --- a/pyqrcode__examples/db_sqlite3.py +++ b/qrcode/pyqrcode__examples/db_sqlite3.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import sqlite3 @@ -16,7 +16,7 @@ print(items) # ('Version: 3.29.0', '2020-07-20') -text = '\n'.join(items) +text = "\n".join(items) qr_code = pyqrcode.create(text) -qr_code.png('sqlite3.png', scale=6) +qr_code.png("sqlite3.png", scale=6) diff --git a/qrcode/pyqrcode__examples/hello_world.py b/qrcode/pyqrcode__examples/hello_world.py new file mode 100644 index 000000000..d2e5aee67 --- /dev/null +++ b/qrcode/pyqrcode__examples/hello_world.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +# SOURCE: https://pythonhosted.org/PyQRCode/ + + +# pip install pyqrcode +# pip install pypng +import pyqrcode + + +qr_code = pyqrcode.create("http://uca.edu") +qr_code.png("img.png") + +qr_code = pyqrcode.create("Hello World!") +qr_code.png("img_hello.png") + +qr_code.png("img_hello__scale=6.png", scale=6) diff --git a/qrcode__generate/README.md b/qrcode/qrcode__generate/README.md similarity index 100% rename from qrcode__generate/README.md rename to qrcode/qrcode__generate/README.md diff --git a/qrcode__generate/main.py b/qrcode/qrcode__generate/main.py similarity index 68% rename from qrcode__generate/main.py rename to qrcode/qrcode__generate/main.py index bb224e762..1d98b762e 100644 --- a/qrcode__generate/main.py +++ b/qrcode/qrcode__generate/main.py @@ -1,28 +1,30 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://github.com/lincolnloop/python-qrcode -text = 'Hello World!' +# In memory +import io # pip install qrcode import qrcode -img = qrcode.make(text) -# In memory -import io + +text = "Hello World!" + +img = qrcode.make(text) io_data = io.BytesIO() -img.save(io_data, 'png') +img.save(io_data, "png") data = io_data.getvalue() print(data) # In file -img.save('qr_code_1.png') +img.save("qr_code_1.png") # # In file, version 2: -with open('qr_code_2.png', 'wb') as f: +with open("qr_code_2.png", "wb") as f: f.write(data) diff --git a/qrcode__generate/qr_code_1.png b/qrcode/qrcode__generate/qr_code_1.png similarity index 100% rename from qrcode__generate/qr_code_1.png rename to qrcode/qrcode__generate/qr_code_1.png diff --git a/qrcode__generate/qr_code_2.png b/qrcode/qrcode__generate/qr_code_2.png similarity index 100% rename from qrcode__generate/qr_code_2.png rename to qrcode/qrcode__generate/qr_code_2.png diff --git a/qrcode__reader/main.py b/qrcode/qrcode__reader/main.py similarity index 74% rename from qrcode__reader/main.py rename to qrcode/qrcode__reader/main.py index f02951d38..dc67f7e7c 100644 --- a/qrcode__reader/main.py +++ b/qrcode/qrcode__reader/main.py @@ -1,38 +1,40 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -# pip install git+https://github.com/ewino/qreader.git +import io +from urllib.request import urlopen + +import requests # SOURCE: https://github.com/tomerfiliba/reedsolomon/blob/master/reedsolo.py # pip install reedsolo +# +# pip install git+https://github.com/ewino/qreader.git import qreader # FROM FILE -data = qreader.read('../qrcode__generate/qr_code_1.png') +data = qreader.read("../qrcode__generate/qr_code_1.png") print(data) # Hello World! # FROM URL -url = 'https://upload.wikimedia.org/wikipedia/commons/8/8f/Qr-2.png' -from urllib.request import urlopen +url = "https://upload.wikimedia.org/wikipedia/commons/8/8f/Qr-2.png" data = qreader.read(urlopen(url)) print(data) # "Version 2" -url = 'https://upload.wikimedia.org/wikipedia/commons/e/eb/QR-%D0%BA%D0%BE%D0%B4.png' +url = "https://upload.wikimedia.org/wikipedia/commons/e/eb/QR-%D0%BA%D0%BE%D0%B4.png" data = qreader.read(urlopen(url)) print(data) # http://ru.wikipedia.org/wiki/QR_Code # FROM URL as bytes in file-like object -import requests rs = requests.get(url) image_data = rs.content -import io bytes_io = io.BytesIO(image_data) data = qreader.read(bytes_io) diff --git a/qrcode__reader/reedsolo.py b/qrcode/qrcode__reader/reedsolo.py similarity index 100% rename from qrcode__reader/reedsolo.py rename to qrcode/qrcode__reader/reedsolo.py diff --git a/qt__pyqt__pyside__pyqode/Auto_widget_placement__using_FlowLayout_QThread.py b/qt__pyqt__pyside__pyqode/Auto_widget_placement__using_FlowLayout_QThread.py index cd54ca99c..ccb4e9403 100644 --- a/qt__pyqt__pyside__pyqode/Auto_widget_placement__using_FlowLayout_QThread.py +++ b/qt__pyqt__pyside__pyqode/Auto_widget_placement__using_FlowLayout_QThread.py @@ -2,14 +2,23 @@ # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -import time import random +import sys +import time +import traceback from PyQt5.QtWidgets import ( - QWidget, QMessageBox, QVBoxLayout, QApplication, QLabel, QPushButton, QScrollArea, QSizePolicy + QWidget, + QMessageBox, + QVBoxLayout, + QApplication, + QLabel, + QPushButton, + QScrollArea, + QSizePolicy, ) from PyQt5.QtGui import QPainter, QImage, QPixmap from PyQt5.QtCore import Qt, QThread, pyqtSignal @@ -18,18 +27,15 @@ # Для отлова всех исключений, которые в слотах Qt могут "затеряться" и привести к тихому падению -def log_uncaught_exceptions(ex_cls, ex, tb): - text = '{}: {}:\n'.format(ex_cls.__name__, ex) +def log_uncaught_exceptions(ex_cls, ex, tb) -> None: + text = f"{ex_cls.__name__}: {ex}:\n" + text += "".join(traceback.format_tb(tb)) - import traceback - text += ''.join(traceback.format_tb(tb)) - - print('Error: ', text) - QMessageBox.critical(None, 'Error', text) + print("Error: ", text) + QMessageBox.critical(None, "Error", text) sys.exit(1) -import sys sys.excepthook = log_uncaught_exceptions @@ -42,12 +48,12 @@ def seconds_to_str(seconds: int) -> str: class ImgThread(QThread): new_img = pyqtSignal(QImage) - def __init__(self, life_time_seconds=60): + def __init__(self, life_time_seconds=60) -> None: super().__init__() self.life_time_seconds = life_time_seconds - def run(self): + def run(self) -> None: painter = QPainter() painter.setRenderHint(QPainter.Antialiasing) @@ -71,7 +77,9 @@ def run(self): try: # Алгоритм изменения размера текста взят из http://stackoverflow.com/a/2204501 # Для текущего пришлось немного адаптировать - factor = img.width() / painter.fontMetrics().width(text) / 1.5 # Ширина текста 2/3 от ширины + factor = ( + img.width() / painter.fontMetrics().width(text) / 1.5 + ) # Ширина текста 2/3 от ширины if factor < 1 or factor > 1.25: f = painter.font() point_size = f.pointSizeF() * factor @@ -92,7 +100,7 @@ def run(self): class ImgWidget(QWidget): closed = pyqtSignal() - def __init__(self, life_time_seconds=60): + def __init__(self, life_time_seconds=60) -> None: super().__init__() self._thread = ImgThread(life_time_seconds) @@ -108,26 +116,26 @@ def __init__(self, life_time_seconds=60): self.setLayout(layout) - def _on_new_img(self, img: QImage): + def _on_new_img(self, img: QImage) -> None: if not img: return self.setFixedSize(img.size()) self._label.setPixmap(QPixmap.fromImage(img)) - def closeEvent(self, event): + def closeEvent(self, event) -> None: self.closed.emit() super().closeEvent(event) class Window(QWidget): - def __init__(self): + def __init__(self) -> None: super().__init__() - self.setWindowTitle(__file__.split('/')[-1]) + self.setWindowTitle(__file__.split("/")[-1]) - button = QPushButton('Add') + button = QPushButton("Add") button.clicked.connect(self._on_push_add) self.items_layout = FlowLayout() @@ -144,14 +152,14 @@ def __init__(self): self.setLayout(main_layout) - def _on_push_add(self): + def _on_push_add(self) -> None: w = ImgWidget(life_time_seconds=random.randint(10, 100)) w.closed.connect(lambda w=w: self.items_layout.removeWidget(w)) self.items_layout.addWidget(w) -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) mw = Window() diff --git a/qt__pyqt__pyside__pyqode/CompletingTextEdit__QTextEdit_QCompleter.py b/qt__pyqt__pyside__pyqode/CompletingTextEdit__QTextEdit_QCompleter.py index e53204811..17fd1fad0 100644 --- a/qt__pyqt__pyside__pyqode/CompletingTextEdit__QTextEdit_QCompleter.py +++ b/qt__pyqt__pyside__pyqode/CompletingTextEdit__QTextEdit_QCompleter.py @@ -1,30 +1,37 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" +import sys +import traceback + from PyQt5.QtCore import QFile, QStringListModel, Qt from PyQt5.QtGui import QCursor, QKeySequence, QTextCursor -from PyQt5.QtWidgets import QApplication, QCompleter, QMainWindow, QTextEdit, QMessageBox +from PyQt5.QtWidgets import ( + QApplication, + QCompleter, + QMainWindow, + QTextEdit, + QMessageBox, +) -def log_uncaught_exceptions(ex_cls, ex, tb): - text = '{}: {}:\n'.format(ex_cls.__name__, ex) - import traceback - text += ''.join(traceback.format_tb(tb)) +def log_uncaught_exceptions(ex_cls, ex, tb) -> None: + text = f"{ex_cls.__name__}: {ex}:\n" + text += "".join(traceback.format_tb(tb)) print(text) - QMessageBox.critical(None, 'Error', text) + QMessageBox.critical(None, "Error", text) sys.exit(1) -import sys sys.excepthook = log_uncaught_exceptions class CompletingTextEdit(QTextEdit): - def __init__(self, parent=None): + def __init__(self, parent=None) -> None: super().__init__(parent) self.completerSequence = QKeySequence("Ctrl+Space") @@ -33,7 +40,7 @@ def __init__(self, parent=None): self._completer.setWidget(self) self._completer.activated.connect(self.insertCompletion) - def insertCompletion(self, completion): + def insertCompletion(self, completion) -> None: if self._completer.widget() is not self: return @@ -50,7 +57,7 @@ def textUnderCursor(self): return tc.selectedText() - def loadFromFile(self, fileName): + def loadFromFile(self, fileName) -> None: f = QFile(fileName) if not f.open(QFile.ReadOnly): model = QStringListModel() @@ -63,7 +70,7 @@ def loadFromFile(self, fileName): line = f.readLine().trimmed() if line.length() != 0: try: - line = str(line, encoding='utf-8') + line = str(line, encoding="utf-8") except TypeError: line = str(line) @@ -74,14 +81,20 @@ def loadFromFile(self, fileName): model = QStringListModel(words) self._completer.setModel(model) - def loadFromList(self, words): + def loadFromList(self, words) -> None: model = QStringListModel(words) self._completer.setModel(model) - def keyPressEvent(self, e): + def keyPressEvent(self, e) -> None: if self._completer.popup().isVisible(): # The following keys are forwarded by the completer to the widget. - if e.key() in (Qt.Key_Enter, Qt.Key_Return, Qt.Key_Escape, Qt.Key_Tab, Qt.Key_Backtab): + if e.key() in ( + Qt.Key_Enter, + Qt.Key_Return, + Qt.Key_Escape, + Qt.Key_Tab, + Qt.Key_Backtab, + ): e.ignore() # Let the completer do default behavior. return @@ -102,7 +115,12 @@ def keyPressEvent(self, e): hasModifier = (e.modifiers() != Qt.NoModifier) and not ctrlOrShift completionPrefix = self.textUnderCursor() - if not isShortcut and (hasModifier or not e.text() or len(completionPrefix) < 3 or e.text()[-1] in eow): + if not isShortcut and ( + hasModifier + or not e.text() + or len(completionPrefix) < 3 + or e.text()[-1] in eow + ): self._completer.popup().hide() return @@ -113,33 +131,38 @@ def keyPressEvent(self, e): ) cr = self.cursorRect() - cr.setWidth(self._completer.popup().sizeHintForColumn(0) + - self._completer.popup().verticalScrollBar().sizeHint().width()) + cr.setWidth( + self._completer.popup().sizeHintForColumn(0) + + self._completer.popup().verticalScrollBar().sizeHint().width() + ) self._completer.complete(cr) class MainWindow(QMainWindow): - def __init__(self, parent=None): + def __init__(self, parent=None) -> None: super().__init__(parent) self.setWindowTitle("Completer") self.textEdit = CompletingTextEdit() - self.textEdit.loadFromList(['dog', 'cat', 'carry', 'python', 'собака', 'foobar']) + self.textEdit.loadFromList( + ["dog", "cat", "carry", "python", "собака", "foobar", "editor", "egg", "foo", "bar", "tester"] + ) # OR: # self.textEdit.loadFromFile('wordlist.txt') self.textEdit.setPlainText( "This TextEdit provides autocompletions for words that have " "more than 3 characters. You can trigger autocompletion " - "using %s\n\n" % self.textEdit.completerSequence.toString(QKeySequence.NativeText)) + "using %s\n\n" + % self.textEdit.completerSequence.toString(QKeySequence.NativeText) + ) self.setCentralWidget(self.textEdit) -if __name__ == '__main__': - import sys +if __name__ == "__main__": app = QApplication(sys.argv) mw = MainWindow() diff --git a/qt__pyqt__pyside__pyqode/EgoDialog.py b/qt__pyqt__pyside__pyqode/EgoDialog.py index 41aedb943..d4bbf7b9a 100644 --- a/qt__pyqt__pyside__pyqode/EgoDialog.py +++ b/qt__pyqt__pyside__pyqode/EgoDialog.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PyQt5.QtWidgets import QApplication, QMessageBox, QWidget @@ -10,7 +10,7 @@ class EgoDialog(QWidget): - def __init__(self): + def __init__(self) -> None: super().__init__() self.setWindowFlag(Qt.FramelessWindowHint) @@ -29,19 +29,19 @@ def exec(title: str, text: str) -> bool: return ok == QMessageBox.Yes - def paintEvent(self, event): + def paintEvent(self, event) -> None: painter = QPainter(self) painter.setBrush(QColor(0, 0, 0, 127)) painter.drawRect(self.rect()) -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) - ok = EgoDialog.exec('Question', 'Question?') + ok = EgoDialog.exec("Question", "Question?") if ok: - print('User select Ok') + print("User select Ok") else: - print('User select No') + print("User select No") # app.exec() diff --git a/qt__pyqt__pyside__pyqode/ElidedLabel.py b/qt__pyqt__pyside__pyqode/ElidedLabel.py index 277df1ceb..5b1bdf5ae 100644 --- a/qt__pyqt__pyside__pyqode/ElidedLabel.py +++ b/qt__pyqt__pyside__pyqode/ElidedLabel.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PyQt5.QtWidgets import QLabel @@ -10,7 +10,12 @@ class ElidedLabel(QLabel): - def paintEvent(self, event): + def __init__(self, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) + + self.setMinimumWidth(50) + + def paintEvent(self, event) -> None: painter = QPainter(self) metrics = QFontMetrics(self.font()) @@ -19,14 +24,14 @@ def paintEvent(self, event): painter.drawText(self.rect(), self.alignment(), elided_text) -if __name__ == '__main__': +if __name__ == "__main__": from PyQt5.QtWidgets import QApplication from PyQt5.QtGui import QFont app = QApplication([]) - mw = ElidedLabel('abc123' * 100) - mw.setFont(QFont('Arial', 20)) + mw = ElidedLabel("abc123" * 100) + mw.setFont(QFont("Arial", 20)) mw.resize(100, 100) mw.show() diff --git a/qt__pyqt__pyside__pyqode/FlowLayout.py b/qt__pyqt__pyside__pyqode/FlowLayout.py index 990c2bae3..c7cf4eb52 100644 --- a/qt__pyqt__pyside__pyqode/FlowLayout.py +++ b/qt__pyqt__pyside__pyqode/FlowLayout.py @@ -76,8 +76,12 @@ def doLayout(self, rect, testOnly): for item in self.itemList: wid = item.widget() - spaceX = self.spacing() + wid.style().layoutSpacing(QSizePolicy.PushButton, QSizePolicy.PushButton, Qt.Horizontal) - spaceY = self.spacing() + wid.style().layoutSpacing(QSizePolicy.PushButton, QSizePolicy.PushButton, Qt.Vertical) + spaceX = self.spacing() + wid.style().layoutSpacing( + QSizePolicy.PushButton, QSizePolicy.PushButton, Qt.Horizontal + ) + spaceY = self.spacing() + wid.style().layoutSpacing( + QSizePolicy.PushButton, QSizePolicy.PushButton, Qt.Vertical + ) nextX = x + item.sizeHint().width() + spaceX if nextX - spaceX > rect.right() and lineHeight > 0: x = rect.x() @@ -94,13 +98,12 @@ def doLayout(self, rect, testOnly): return y + lineHeight - rect.y() -if __name__ == '__main__': +if __name__ == "__main__": import sys - from PyQt5.QtWidgets import QWidget, QPushButton, QApplication class Window(QWidget): - def __init__(self): + def __init__(self) -> None: super().__init__() flowLayout = FlowLayout() diff --git a/qt__pyqt__pyside__pyqode/GIPHY__gif/config.py b/qt__pyqt__pyside__pyqode/GIPHY__gif/config.py index bd0ee287e..ff9c1f653 100644 --- a/qt__pyqt__pyside__pyqode/GIPHY__gif/config.py +++ b/qt__pyqt__pyside__pyqode/GIPHY__gif/config.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import os @@ -9,10 +9,10 @@ DIR = Path(__file__).resolve().parent -TOKEN_FILE_NAME = DIR / 'TOKEN.txt' +TOKEN_FILE_NAME = DIR / "TOKEN.txt" # SOURCE: https://developers.giphy.com/dashboard/ -GIPHY_API_KEY = os.environ.get('TOKEN') or TOKEN_FILE_NAME.read_text('utf-8').strip() +GIPHY_API_KEY = os.environ.get("TOKEN") or TOKEN_FILE_NAME.read_text("utf-8").strip() -TEMP_DIR = DIR / 'temp' +TEMP_DIR = DIR / "temp" TEMP_DIR.mkdir(exist_ok=True) diff --git a/qt__pyqt__pyside__pyqode/GIPHY__gif/main.py b/qt__pyqt__pyside__pyqode/GIPHY__gif/main.py index 49a5ed9ef..114f44686 100644 --- a/qt__pyqt__pyside__pyqode/GIPHY__gif/main.py +++ b/qt__pyqt__pyside__pyqode/GIPHY__gif/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://ru.stackoverflow.com/q/1134473/201445 @@ -14,8 +14,18 @@ import requests from PyQt5.QtWidgets import ( - QApplication, QMainWindow, QVBoxLayout, QHBoxLayout, QFrame, QMessageBox, - QLineEdit, QPushButton, QLabel, QScrollArea, QWidget, QGridLayout + QApplication, + QMainWindow, + QVBoxLayout, + QHBoxLayout, + QFrame, + QMessageBox, + QLineEdit, + QPushButton, + QLabel, + QScrollArea, + QWidget, + QGridLayout, ) from PyQt5.QtCore import QThread, pyqtSignal, Qt from PyQt5.QtGui import QMovie @@ -23,12 +33,12 @@ from config import GIPHY_API_KEY, TEMP_DIR -def log_uncaught_exceptions(ex_cls, ex, tb): - text = '{}: {}:\n'.format(ex_cls.__name__, ex) - text += ''.join(traceback.format_tb(tb)) +def log_uncaught_exceptions(ex_cls, ex, tb) -> None: + text = f"{ex_cls.__name__}: {ex}:\n" + text += "".join(traceback.format_tb(tb)) print(text) - QMessageBox.critical(None, 'Error', text) + QMessageBox.critical(None, "Error", text) sys.exit(1) @@ -36,23 +46,23 @@ def log_uncaught_exceptions(ex_cls, ex, tb): class SearchGifThread(QThread): - SITE_URL = 'https://api.giphy.com/v1' + SITE_URL = "https://api.giphy.com/v1" about_add_gif = pyqtSignal(dict, int, int) - def __init__(self, name_gif=None): + def __init__(self, name_gif=None) -> None: super().__init__() self.name_gif = name_gif def get_gif(self) -> dict: - url = f'{self.SITE_URL}/gifs/search?api_key={GIPHY_API_KEY}&q={self.name_gif}' + url = f"{self.SITE_URL}/gifs/search?api_key={GIPHY_API_KEY}&q={self.name_gif}" try: rs = requests.get(url) rs.raise_for_status() - data = rs.json()['data'] + data = rs.json()["data"] if data: return data @@ -61,38 +71,43 @@ def get_gif(self) -> dict: print(e) # TODO: emit 'not found' to MainWindow - return {'error': True} + return {"error": True} def _process_gif(self, img: dict, index: int) -> dict: - url_gif = img['images']['fixed_width']['url'] + url_gif = img["images"]["fixed_width"]["url"] image_rs = requests.get(url_gif) image_rs.raise_for_status() file_name = TEMP_DIR / f"img{index}.gif" - with open(file_name, 'wb') as f: + with open(file_name, "wb") as f: f.write(image_rs.content) - data = {'row': index // 3, 'col': index % 3, 'error': None, 'file_name': str(file_name)} + data = { + "row": index // 3, + "col": index % 3, + "error": None, + "file_name": str(file_name), + } return data - def run(self): + def run(self) -> None: data = self.get_gif() - if 'error' in data: + if "error" in data: # TODO: emit error to MainWindow return for i, img in enumerate(data): data_gif = self._process_gif(img, i) - self.about_add_gif.emit(data_gif, i+1, len(data)) + self.about_add_gif.emit(data_gif, i + 1, len(data)) time.sleep(1) class MainWindow(QMainWindow): - def __init__(self): + def __init__(self) -> None: super().__init__() - self.title = 'Gif Manager' + self.title = "Gif Manager" self.setWindowTitle(self.title) self.search_gif_thread = SearchGifThread() @@ -102,12 +117,12 @@ def __init__(self): self.init_ui() - def init_ui(self): + def init_ui(self) -> None: self.gif_edit = QLineEdit() self.gif_edit.returnPressed.connect(self.search_gif) - self.gif_edit.setPlaceholderText('Enter name gif') + self.gif_edit.setPlaceholderText("Enter name gif") - gif_search_button = QPushButton('Search gif') + gif_search_button = QPushButton("Search gif") gif_search_button.clicked.connect(self.search_gif) gif_data_layout = QHBoxLayout() @@ -140,15 +155,15 @@ def init_ui(self): root_layout.addWidget(self.info_label) root_layout.addWidget(self.scroll) - def on_finish(self): + def on_finish(self) -> None: self.gif_data_frame.setEnabled(True) - def on_start(self): + def on_start(self) -> None: self.info_label.hide() self.scroll.show() self.gif_data_frame.setEnabled(False) - def search_gif(self): + def search_gif(self) -> None: # TODO: replace on QListWidget for i in range(self.gifs_layout.count()): self.gifs_layout.itemAt(i).widget().deleteLater() @@ -156,10 +171,10 @@ def search_gif(self): self.search_gif_thread.name_gif = self.gif_edit.text() self.search_gif_thread.start() - def add_gif(self, data: dict, num: int, total: int): - self.setWindowTitle(f'{self.title}. {num} / {total}') + def add_gif(self, data: dict, num: int, total: int) -> None: + self.setWindowTitle(f"{self.title}. {num} / {total}") - if data['error']: + if data["error"]: self.gif_data_frame.setEnabled(True) self.scroll.hide() self.info_label.show() @@ -183,6 +198,6 @@ def add_gif(self, data: dict, num: int, total: int): mw.resize(800, 600) mw.show() - mw.gif_edit.setText('cat') + mw.gif_edit.setText("cat") sys.exit(app.exec_()) diff --git a/qt__pyqt__pyside__pyqode/Good_text_foreground_color_for_a_given_background_color.py b/qt__pyqt__pyside__pyqode/Good_text_foreground_color_for_a_given_background_color.py index 2af77002d..56467789b 100644 --- a/qt__pyqt__pyside__pyqode/Good_text_foreground_color_for_a_given_background_color.py +++ b/qt__pyqt__pyside__pyqode/Good_text_foreground_color_for_a_given_background_color.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PyQt5.QtGui import QColor diff --git a/qt__pyqt__pyside__pyqode/ImageLabel__QGraphicsView/main.py b/qt__pyqt__pyside__pyqode/ImageLabel__QGraphicsView/main.py index eb4e208f8..db0860632 100644 --- a/qt__pyqt__pyside__pyqode/ImageLabel__QGraphicsView/main.py +++ b/qt__pyqt__pyside__pyqode/ImageLabel__QGraphicsView/main.py @@ -1,38 +1,43 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -from PyQt5.QtWidgets import QApplication, QGraphicsView, QGraphicsScene, QGraphicsPixmapItem +from PyQt5.QtWidgets import ( + QApplication, + QGraphicsView, + QGraphicsScene, + QGraphicsPixmapItem, +) from PyQt5.QtGui import QPixmap from PyQt5.QtCore import Qt class ImageLabel(QGraphicsView): - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) self.setScene(QGraphicsScene()) self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) - def setImage(self, filename: str): + def setImage(self, filename: str) -> None: self.setPixmap(QPixmap(filename)) - def setPixmap(self, pixmap: QPixmap): + def setPixmap(self, pixmap: QPixmap) -> None: item = QGraphicsPixmapItem(pixmap) item.setTransformationMode(Qt.SmoothTransformation) self.scene().addItem(item) - def resizeEvent(self, event): + def resizeEvent(self, event) -> None: super().resizeEvent(event) rect = self.scene().itemsBoundingRect() self.fitInView(rect, Qt.KeepAspectRatio) -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) screenshot = app.primaryScreen().grabWindow(QApplication.desktop().winId()) diff --git a/qt__pyqt__pyside__pyqode/Is_IP__QRegExpValidator/IP_Validator.py b/qt__pyqt__pyside__pyqode/Is_IP__QRegExpValidator/IP_Validator.py index f999c1b98..702592e7d 100644 --- a/qt__pyqt__pyside__pyqode/Is_IP__QRegExpValidator/IP_Validator.py +++ b/qt__pyqt__pyside__pyqode/Is_IP__QRegExpValidator/IP_Validator.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PyQt5.QtGui import QRegularExpressionValidator diff --git a/qt__pyqt__pyside__pyqode/Is_IP__QRegExpValidator/is_ip.py b/qt__pyqt__pyside__pyqode/Is_IP__QRegExpValidator/is_ip.py index 634fa3a6e..a2f49e2fc 100644 --- a/qt__pyqt__pyside__pyqode/Is_IP__QRegExpValidator/is_ip.py +++ b/qt__pyqt__pyside__pyqode/Is_IP__QRegExpValidator/is_ip.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import re @@ -9,17 +9,21 @@ # SOURCE: https://stackoverflow.com/a/23166561/5909792 IP_RANGE = "(?:[0-1]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])" -IP_REGEXP = fr'^{IP_RANGE}\.{IP_RANGE}\.{IP_RANGE}\.{IP_RANGE}$' +IP_REGEXP = rf"^{IP_RANGE}\.{IP_RANGE}\.{IP_RANGE}\.{IP_RANGE}$" def is_ip(ip: str) -> bool: return bool(re.match(IP_REGEXP, ip)) -if __name__ == '__main__': +if __name__ == "__main__": tests = [ - ('127.0.0.1', True), ('100.100.100.100', True), ('0.0.0.0', True), ('300.300.300.300', False), - ('300.0.0.1', False), ('255.255.255.255', True) + ("127.0.0.1", True), + ("100.100.100.100", True), + ("0.0.0.0", True), + ("300.300.300.300", False), + ("300.0.0.1", False), + ("255.255.255.255", True), ] for ip, expected in tests: diff --git a/qt__pyqt__pyside__pyqode/Is_IP__QRegExpValidator/main.py b/qt__pyqt__pyside__pyqode/Is_IP__QRegExpValidator/main.py index b7c01b9a0..e8c03e338 100644 --- a/qt__pyqt__pyside__pyqode/Is_IP__QRegExpValidator/main.py +++ b/qt__pyqt__pyside__pyqode/Is_IP__QRegExpValidator/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PyQt5.QtWidgets import QLineEdit, QApplication @@ -9,7 +9,7 @@ from IP_Validator import get_ip_validator -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) mw = QLineEdit() diff --git a/qt__pyqt__pyside__pyqode/OID__QValidator.py b/qt__pyqt__pyside__pyqode/OID__QValidator.py index ad1eff961..cfdd10e50 100644 --- a/qt__pyqt__pyside__pyqode/OID__QValidator.py +++ b/qt__pyqt__pyside__pyqode/OID__QValidator.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PyQt5.QtWidgets import QApplication, QLineEdit @@ -12,8 +12,8 @@ app = QApplication([]) mw = QLineEdit() -mw.setText('1.3.6.1.2.1.25.1.1.0') -mw.setValidator(QRegExpValidator(QRegExp(r'\d+(\.\d+)+'))) +mw.setText("1.3.6.1.2.1.25.1.1.0") +mw.setValidator(QRegExpValidator(QRegExp(r"\d+(\.\d+)+"))) mw.show() app.exec() diff --git a/qt__pyqt__pyside__pyqode/One_ball__in_outer_circle_area__collision_with_mouse__QPainter/main.py b/qt__pyqt__pyside__pyqode/One_ball__in_outer_circle_area__collision_with_mouse__QPainter/main.py index b2c0036fe..1b5cf743b 100644 --- a/qt__pyqt__pyside__pyqode/One_ball__in_outer_circle_area__collision_with_mouse__QPainter/main.py +++ b/qt__pyqt__pyside__pyqode/One_ball__in_outer_circle_area__collision_with_mouse__QPainter/main.py @@ -1,32 +1,59 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://ru.stackoverflow.com/a/970882/201445 # SOURCE: https://github.com/gil9red/SimplePyScripts/blob/d50de0f237d778f7d69ee75e328503f8c8344743/qt__pyqt__pyside__pyqode/QWebEngineView__one_ball__in_outer_circle_area__collision_with_mouse/index.html +import traceback +import sys + +from dataclasses import dataclass from pathlib import Path from timeit import default_timer -from PyQt5.Qt import QApplication, QWidget, QPainter, QTimer, Qt, QPoint, QPen +from PyQt5.QtCore import QTimer, Qt, QPointF +from PyQt5.QtGui import QPainter, QPen +from PyQt5.QtWidgets import QApplication, QMessageBox, QWidget + + +def log_uncaught_exceptions(ex_cls, ex, tb) -> None: + text = f"{ex_cls.__name__}: {ex}\n" + text += "".join(traceback.format_tb(tb)) + print(text) + + if QApplication.instance(): + msg_box = QMessageBox( + QMessageBox.Critical, + "Error", + f"Error: {ex}", + parent=None, + ) + msg_box.setDetailedText(text) + msg_box.setStandardButtons(QMessageBox.Ok) + msg_box.exec() + + +sys.excepthook = log_uncaught_exceptions +@dataclass class Ball: - r = 50 # Радиус шарика - x = 0 # Координата по х центра шарика - y = 0 # Координата по y центра шарика - speed = 0 # Скорость движения - dir_x = 0 # Компонент x вектора движения шарика - dir_y = 0 # Компонент y вектора движения шарика - damp = 10 # Скорость уменьшения скорости движения (сопротивление) - collision = False # Признак коллизии с внешним кругом - speed_after_collision = 300 # Скорость движения шарика после столкновения + r: float = 50 # Радиус шарика + x: float = 0 # Координата по х центра шарика + y: float = 0 # Координата по y центра шарика + speed: float = 0 # Скорость движения + dir_x: float = 0 # Компонент x вектора движения шарика + dir_y: float = 0 # Компонент y вектора движения шарика + damp: float = 10 # Скорость уменьшения скорости движения (сопротивление) + collision: bool = False # Признак коллизии с внешним кругом + speed_after_collision: float = 300 # Скорость движения шарика после столкновения # Функция, которая проверяет наличие коллизии шарика с внешним кругом - def hit_outer_circle_check(self, outer_circle: int): + def hit_outer_circle_check(self, outer_circle: int) -> None: dr = outer_circle - self.r # Разница радиусов # По теореме пифагора проверяем выход за пределы круга (коллизию) @@ -69,7 +96,7 @@ def hit_outer_circle_check(self, outer_circle: int): self.collision = False # Функция проверки коллизии шарика и мышки - def hit_mouse_check(self, x, y): + def hit_mouse_check(self, x, y) -> None: # Если есть коллизия с внешним кругом игнорируем мышку if self.collision: return @@ -93,7 +120,7 @@ def hit_mouse_check(self, x, y): # Тут осуществляется передвижение # dt - кол-во секунд с прошлого обсчета - def do_move(self, dt): + def do_move(self, dt: float) -> None: # К текущей координате прибавляем вектор скорости помноженный # на значение скорости помноженные на прошедшее время self.x += self.dir_x * self.speed * dt @@ -104,17 +131,15 @@ def do_move(self, dt): class MainWindow(QWidget): - def __init__(self): + def __init__(self) -> None: super().__init__() - title = Path(__file__).parent.name - self.setWindowTitle(title) - - timeout = 1000 // 60 + timeout: int = 1000 // 60 # Таймер обновления движения и обработки столкновения шариков self.timer = QTimer() self.timer.timeout.connect(self.tick) + self.timer.timeout.connect(self.update_window_title) self.timer.start(timeout) # NOTE: Перерисовку помести в tick, но этот вариант с отдельным таймером тоже @@ -124,19 +149,26 @@ def __init__(self): # self.timer_render.timeout.connect(self.update) # self.timer_render.start(timeout) - self.outer_circle = 195 + self.outer_circle: int = 195 self.ball = Ball() - self.mouse_center_x = 0 - self.mouse_center_y = 0 + self.mouse_center_x: int = 0 + self.mouse_center_y: int = 0 # Используется, чтобы в независимости от количества вызовов # tick скорость шарика была одинаковая - self.t = 0 + self.t: float = 0 self.setMouseTracking(True) - def tick(self): + self.update_window_title() + + def update_window_title(self) -> None: + name = Path(__file__).parent.name + title = f"{name}. Speed: {self.ball.speed}" + self.setWindowTitle(title) + + def tick(self) -> None: # Считаем сколько времени прошло с прошлого обсчета dt = default_timer() - self.t @@ -148,11 +180,11 @@ def tick(self): self.update() - def mouseMoveEvent(self, event): + def mouseMoveEvent(self, event) -> None: self.mouse_center_x = event.pos().x() - (self.width() / 2) self.mouse_center_y = event.pos().y() - (self.height() / 2) - def paintEvent(self, event): + def paintEvent(self, event) -> None: painter = QPainter(self) painter.setRenderHint(QPainter.HighQualityAntialiasing) @@ -161,11 +193,11 @@ def paintEvent(self, event): painter.translate(self.width() / 2, self.height() / 2) - painter.drawEllipse(QPoint(0, 0), self.outer_circle, self.outer_circle) - painter.drawEllipse(QPoint(self.ball.x, self.ball.y), self.ball.r, self.ball.r) + painter.drawEllipse(QPointF(0, 0), self.outer_circle, self.outer_circle) + painter.drawEllipse(QPointF(self.ball.x, self.ball.y), self.ball.r, self.ball.r) -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) mw = MainWindow() diff --git a/qt__pyqt__pyside__pyqode/One_ball__in_outer_circle_area__collision_with_mouse__QWebEngineView/main.py b/qt__pyqt__pyside__pyqode/One_ball__in_outer_circle_area__collision_with_mouse__QWebEngineView/main.py index 8abe15c2c..e3ef79230 100644 --- a/qt__pyqt__pyside__pyqode/One_ball__in_outer_circle_area__collision_with_mouse__QWebEngineView/main.py +++ b/qt__pyqt__pyside__pyqode/One_ball__in_outer_circle_area__collision_with_mouse__QWebEngineView/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://ru.stackoverflow.com/a/970882/201445 @@ -13,8 +13,8 @@ from PyQt5.QtWebEngineWidgets import QWebEngineView -if __name__ == '__main__': - with open('index.html', encoding='utf-8') as f: +if __name__ == "__main__": + with open("index.html", encoding="utf-8") as f: html = f.read() title = Path(__file__).parent.name @@ -27,4 +27,3 @@ view.show() app.exec() - diff --git a/qt__pyqt__pyside__pyqode/PyQt6_screens_size.py b/qt__pyqt__pyside__pyqode/PyQt6_screens_size.py new file mode 100644 index 000000000..ec7232233 --- /dev/null +++ b/qt__pyqt__pyside__pyqode/PyQt6_screens_size.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from PyQt6.QtWidgets import QApplication +from PyQt6.QtGui import QGuiApplication + + +app = QApplication([]) +for screen in QApplication.screens(): + print(screen.geometry(), screen.size()) +""" +PyQt6.QtCore.QRect(0, 0, 1920, 1080) PyQt6.QtCore.QSize(1920, 1080) +PyQt6.QtCore.QRect(-3840, 0, 1536, 864) PyQt6.QtCore.QSize(1536, 864) +PyQt6.QtCore.QRect(-1920, 0, 1920, 1080) PyQt6.QtCore.QSize(1920, 1080) +""" + +app = None +print() + +app = QGuiApplication([]) +for screen in QGuiApplication.screens(): + print(screen.geometry(), screen.size()) +""" +PyQt6.QtCore.QRect(0, 0, 1920, 1080) PyQt6.QtCore.QSize(1920, 1080) +PyQt6.QtCore.QRect(-3840, 0, 1536, 864) PyQt6.QtCore.QSize(1536, 864) +PyQt6.QtCore.QRect(-1920, 0, 1920, 1080) PyQt6.QtCore.QSize(1920, 1080) +""" diff --git a/qt__pyqt__pyside__pyqode/PySide_examples/draw_table/draw_table.py b/qt__pyqt__pyside__pyqode/PySide_examples/draw_table/draw_table.py index a8621542b..03c97bf5c 100644 --- a/qt__pyqt__pyside__pyqode/PySide_examples/draw_table/draw_table.py +++ b/qt__pyqt__pyside__pyqode/PySide_examples/draw_table/draw_table.py @@ -1,12 +1,11 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -from random import randint import sys - +from random import randint from PyQt5.QtWidgets import * from PyQt5.QtGui import * @@ -14,9 +13,9 @@ class Widget(QWidget): - def __init__(self): + def __init__(self) -> None: super().__init__() - self.setWindowTitle('Widget') + self.setWindowTitle("Widget") self.cell_size = 20 self.matrix_size = 9 @@ -35,7 +34,7 @@ def __init__(self): self.setMouseTracking(True) - def resizeEvent(self, event): + def resizeEvent(self, event) -> None: super().resizeEvent(event) w, h = event.size().width(), event.size().height() @@ -43,7 +42,7 @@ def resizeEvent(self, event): self.cell_size = min_size // self.matrix_size - def mouseMoveEvent(self, event): + def mouseMoveEvent(self, event) -> None: super().mouseMoveEvent(event) pos = event.pos() @@ -53,13 +52,16 @@ def mouseMoveEvent(self, event): self.update() - def paintEvent(self, event): + def paintEvent(self, event) -> None: super().paintEvent(event) painter = QPainter(self) # Если индекс ячейки под курсором валидный - if 0 <= self.x_highlight_cell < self.matrix_size and 0 <= self.y_highlight_cell < self.matrix_size: + if ( + 0 <= self.x_highlight_cell < self.matrix_size + and 0 <= self.y_highlight_cell < self.matrix_size + ): # Выделение всего столбца и строки пересекающих ячейку под курсором painter.save() painter.setBrush(Qt.lightGray) @@ -70,7 +72,7 @@ def paintEvent(self, event): i * self.cell_size, self.y_highlight_cell * self.cell_size, self.cell_size, - self.cell_size + self.cell_size, ) # Выделение столбца @@ -79,7 +81,7 @@ def paintEvent(self, event): self.x_highlight_cell * self.cell_size, j * self.cell_size, self.cell_size, - self.cell_size + self.cell_size, ) painter.restore() @@ -91,7 +93,7 @@ def paintEvent(self, event): self.x_highlight_cell * self.cell_size, self.y_highlight_cell * self.cell_size, self.cell_size, - self.cell_size + self.cell_size, ) painter.restore() @@ -131,7 +133,7 @@ def paintEvent(self, event): x2 += self.cell_size -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication(sys.argv) w = Widget() diff --git a/qt__pyqt__pyside__pyqode/PySide_examples/draw_table/setup.py b/qt__pyqt__pyside__pyqode/PySide_examples/draw_table/setup.py index 882963664..7f6db329a 100644 --- a/qt__pyqt__pyside__pyqode/PySide_examples/draw_table/setup.py +++ b/qt__pyqt__pyside__pyqode/PySide_examples/draw_table/setup.py @@ -9,12 +9,8 @@ from cx_Freeze import setup, Executable -executables = [ - Executable('draw_table.py') -] +executables = [Executable("draw_table.py")] -setup(name='draw_table', - version='0.1', - description='draw_table', - executables=executables - ) +setup( + name="draw_table", version="0.1", description="draw_table", executables=executables +) diff --git a/qt__pyqt__pyside__pyqode/PySide_examples/e1.py b/qt__pyqt__pyside__pyqode/PySide_examples/e1.py index 600ecf9c9..6d43f7ab5 100644 --- a/qt__pyqt__pyside__pyqode/PySide_examples/e1.py +++ b/qt__pyqt__pyside__pyqode/PySide_examples/e1.py @@ -1,17 +1,18 @@ -__author__ = 'ipetrash' +__author__ = "ipetrash" # Простой пример использования модуля PySide from PySide.QtGui import * -# from PySide.QtCore import * -import sys -if __name__ == '__main__': + +if __name__ == "__main__": + import sys + app = QApplication(sys.argv) - def says(): + def says() -> None: print("Hello, Python!") label = QLabel("Hello, PySize!") @@ -33,4 +34,4 @@ def says(): window.setLayout(layout) window.show() - sys.exit(app.exec_()) \ No newline at end of file + sys.exit(app.exec_()) diff --git a/qt__pyqt__pyside__pyqode/PySide_examples/e2.py b/qt__pyqt__pyside__pyqode/PySide_examples/e2.py index bdf84542b..5d60a5f05 100644 --- a/qt__pyqt__pyside__pyqode/PySide_examples/e2.py +++ b/qt__pyqt__pyside__pyqode/PySide_examples/e2.py @@ -1,4 +1,4 @@ -__author__ = 'ipetrash' +__author__ = "ipetrash" # Пример показывает как можно создать простое окно с небольшим функционалом, @@ -11,7 +11,7 @@ class SimpleWindow(QtGui.QWidget): """Просто окно""" - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) self.setWindowTitle("Простое окно") @@ -30,18 +30,18 @@ def __init__(self, *args, **kwargs): self.setLayout(layout_main) - def slot_add_log(self): + def slot_add_log(self) -> None: text = self.le_mess.text() if text: self.lw_log.addItem(text) -import sys +if __name__ == "__main__": + import sys -if __name__ == '__main__': app = QtGui.QApplication(sys.argv) w = SimpleWindow() w.show() - sys.exit(app.exec_()) \ No newline at end of file + sys.exit(app.exec_()) diff --git a/qt__pyqt__pyside__pyqode/PySide_examples/e3.py b/qt__pyqt__pyside__pyqode/PySide_examples/e3.py index d756ac811..5bdfbe286 100644 --- a/qt__pyqt__pyside__pyqode/PySide_examples/e3.py +++ b/qt__pyqt__pyside__pyqode/PySide_examples/e3.py @@ -1,11 +1,11 @@ -__author__ = 'ipetrash' +__author__ = "ipetrash" from PySide.QtGui import * class MainWindow(QMainWindow): - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) self.editor = QTextEdit() self.setCentralWidget(self.editor) @@ -19,25 +19,25 @@ def __init__(self, *args, **kwargs): self.edit_menu = self.menuBar().addMenu("Правка") self.edit_menu.aboutToShow.connect(self.update_edit_menu) - def update_edit_menu(self): + def update_edit_menu(self) -> None: self.edit_menu.clear() actions = self.editor.createStandardContextMenu().actions() self.edit_menu.addActions(actions) - def slot_save_as(self): + def slot_save_as(self) -> None: file_name = QFileDialog.getSaveFileName()[0] if file_name: - with open(file_name, 'w') as f: - f.write(self.editor.toPlainText()) + with open(file_name, "w") as f: + f.write(self.editor.toPlainText()) -if __name__ == '__main__': +if __name__ == "__main__": import sys app = QApplication(sys.argv) - app.setApplicationName('myWindow') + app.setApplicationName("myWindow") w = MainWindow() w.show() - sys.exit(app.exec_()) \ No newline at end of file + sys.exit(app.exec_()) diff --git a/qt__pyqt__pyside__pyqode/PySide_examples/e4.py b/qt__pyqt__pyside__pyqode/PySide_examples/e4.py index 17b03bb5c..736d1b344 100644 --- a/qt__pyqt__pyside__pyqode/PySide_examples/e4.py +++ b/qt__pyqt__pyside__pyqode/PySide_examples/e4.py @@ -1,51 +1,55 @@ -__author__ = 'ipetrash' +__author__ = "ipetrash" +import time +from threading import Thread + from grab import Grab -import sys from PySide.QtGui import * -from threading import Thread -import time def get_confucius_quotes(): g = Grab() # http://ru.wikiquote.org/wiki/Конфуций - url = "http://ru.wikiquote.org/wiki/%D0%9A%D0%BE%D0%BD%D1%84%D1%83%D1%86%D0%B8%D0%B9" + url = ( + "http://ru.wikiquote.org/wiki/%D0%9A%D0%BE%D0%BD%D1%84%D1%83%D1%86%D0%B8%D0%B9" + ) g.go(url) quotes = list() - for el in g.doc.select('//h2/following-sibling::ul/li'): + for el in g.doc.select("//h2/following-sibling::ul/li"): quotes.append(el.text()) return quotes class MyThread(Thread): - def __init__(self, log): + def __init__(self, log) -> None: super().__init__() self.log = log - def run(self): + def run(self) -> None: for i, quote in enumerate(get_confucius_quotes(), 1): - self.log.append('{}. "{}"\n'.format(i, quote)) + self.log.append(f'{i}. "{quote}"\n') time.sleep(0.1) # задержка каждые 100 миллисекунд class Window(QMainWindow): - def __init__(self): + def __init__(self) -> None: super().__init__() self.te_quotes = QTextEdit() self.te_quotes.setReadOnly(True) self.setCentralWidget(self.te_quotes) - def slot_refresh(self): + def slot_refresh(self) -> None: self.te_quotes.clear() thread = MyThread(self.te_quotes) thread.start() -if __name__ == '__main__': +if __name__ == "__main__": + import sys + app = QApplication(sys.argv) w = Window() diff --git a/qt__pyqt__pyside__pyqode/PySide_examples/infinite_input.py b/qt__pyqt__pyside__pyqode/PySide_examples/infinite_input.py index ecb58f731..8390f5d6b 100644 --- a/qt__pyqt__pyside__pyqode/PySide_examples/infinite_input.py +++ b/qt__pyqt__pyside__pyqode/PySide_examples/infinite_input.py @@ -1,17 +1,15 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -import sys - from PySide.QtGui import * from PySide.QtCore import * class Widget(QWidget): - def __init__(self): + def __init__(self) -> None: super().__init__() self.layout = QVBoxLayout() @@ -21,14 +19,14 @@ def __init__(self): self._add_line_edit() - def _add_line_edit(self): + def _add_line_edit(self) -> None: line_edit = QLineEdit() line_edit.textEdited.connect(self._text_edited) self.layout.addWidget(line_edit) self.line_edit_list.append(line_edit) - def _text_edited(self, text): + def _text_edited(self, text) -> None: if not self.line_edit_list: return @@ -36,7 +34,9 @@ def _text_edited(self, text): self._add_line_edit() -if __name__ == '__main__': +if __name__ == "__main__": + import sys + app = QApplication(sys.argv) w = Widget() diff --git a/qt__pyqt__pyside__pyqode/QApplication_keyboardModifiers.py b/qt__pyqt__pyside__pyqode/QApplication_keyboardModifiers.py index ef79d74f1..c10d47b94 100644 --- a/qt__pyqt__pyside__pyqode/QApplication_keyboardModifiers.py +++ b/qt__pyqt__pyside__pyqode/QApplication_keyboardModifiers.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PyQt5.QtWidgets import QWidget, QApplication, QVBoxLayout, QPushButton @@ -9,10 +9,10 @@ class Window(QWidget): - def __init__(self): + def __init__(self) -> None: super().__init__() - self.button = QPushButton('Test') + self.button = QPushButton("Test") self.button.clicked.connect(self.on_clicked) main_layout = QVBoxLayout() @@ -20,13 +20,13 @@ def __init__(self): self.setLayout(main_layout) - def foo(self): - print('foo') + def foo(self) -> None: + print("foo") - def bar(self): - print('bar') + def bar(self) -> None: + print("bar") - def on_clicked(self): + def on_clicked(self) -> None: modifiers = QApplication.keyboardModifiers() if modifiers == Qt.AltModifier: @@ -35,7 +35,7 @@ def on_clicked(self): self.foo() -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) mw = Window() diff --git a/qt__pyqt__pyside__pyqode/QApplication_show_screens.py b/qt__pyqt__pyside__pyqode/QApplication_show_screens.py index b8be5b4ac..0c21d0bf2 100644 --- a/qt__pyqt__pyside__pyqode/QApplication_show_screens.py +++ b/qt__pyqt__pyside__pyqode/QApplication_show_screens.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PyQt5.QtWidgets import QApplication @@ -10,8 +10,7 @@ app = QApplication([]) screens = app.screens() -print(f'Screens ({len(screens)}):') +print(f"Screens ({len(screens)}):") for screen in screens: - print(f' {screen.availableGeometry()} {screen.availableSize()}') - + print(f" {screen.availableGeometry()} {screen.availableSize()}") diff --git a/qt__pyqt__pyside__pyqode/QAxContainer_Word_Application.py b/qt__pyqt__pyside__pyqode/QAxContainer_Word_Application.py index 2236a70c6..69357fd5a 100644 --- a/qt__pyqt__pyside__pyqode/QAxContainer_Word_Application.py +++ b/qt__pyqt__pyside__pyqode/QAxContainer_Word_Application.py @@ -1,25 +1,30 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import sys from PyQt5.QAxContainer import QAxWidget from PyQt5.QtWidgets import ( - QWidget, QGridLayout, QPushButton, QFileDialog, QMessageBox, QApplication + QWidget, + QGridLayout, + QPushButton, + QFileDialog, + QMessageBox, + QApplication, ) class Window(QWidget): - def __init__(self): + def __init__(self) -> None: super().__init__() - self.setWindowTitle('Document Word.Application') + self.setWindowTitle("Document Word.Application") self.axWidget = QAxWidget(self) - self.buttonOpen = QPushButton('Open') + self.buttonOpen = QPushButton("Open") self.buttonOpen.clicked.connect(self.handleOpen) layout = QGridLayout(self) @@ -28,26 +33,24 @@ def __init__(self): def handleOpen(self): path, _ = QFileDialog.getOpenFileName( - self, 'Выберите файл word', '', 'word(*.docx *.doc)' + self, "Выберите файл word", "", "word(*.docx *.doc)" ) if not path: return - return self.openOffice(path, 'Word.Application') + return self.openOffice(path, "Word.Application") def openOffice(self, path, app): self.axWidget.clear() if not self.axWidget.setControl(app): - return QMessageBox.critical(self, 'Ошибка', f'Нет установки {app!r}') + return QMessageBox.critical(self, "Ошибка", f"Нет установки {app!r}") - self.axWidget.dynamicCall( - 'SetVisible (bool Visible)', 'false' - ) - self.axWidget.setProperty('DisplayAlerts', False) + self.axWidget.dynamicCall("SetVisible (bool Visible)", "false") + self.axWidget.setProperty("DisplayAlerts", False) self.axWidget.setControl(path) -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication(sys.argv) mw = Window() diff --git a/qt__pyqt__pyside__pyqode/QFileSystemWatcher__examples/main.py b/qt__pyqt__pyside__pyqode/QFileSystemWatcher__examples/main.py index b2d06df0d..2c0192947 100644 --- a/qt__pyqt__pyside__pyqode/QFileSystemWatcher__examples/main.py +++ b/qt__pyqt__pyside__pyqode/QFileSystemWatcher__examples/main.py @@ -1,10 +1,10 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -import datetime as DT +import datetime as dt from pathlib import Path from PyQt5.QtCore import QCoreApplication, QFileSystemWatcher @@ -17,13 +17,11 @@ watcher = QFileSystemWatcher([current_dir]) watcher.directoryChanged.connect( lambda directory: print( - f'[{DT.datetime.now():%d/%m/%Y %H:%M:%S}] Directory: {directory!r}' + f"[{dt.datetime.now():%d/%m/%Y %H:%M:%S}] Directory: {directory!r}" ) ) watcher.fileChanged.connect( - lambda file: print( - f'[{DT.datetime.now():%d/%m/%Y %H:%M:%S}] File: {file!r}' - ) + lambda file: print(f"[{dt.datetime.now():%d/%m/%Y %H:%M:%S}] File: {file!r}") ) app.exec_() diff --git a/qt__pyqt__pyside__pyqode/QImageReader.supportedImageFormats.py b/qt__pyqt__pyside__pyqode/QImageReader.supportedImageFormats.py index cc5d7e465..fa1511990 100644 --- a/qt__pyqt__pyside__pyqode/QImageReader.supportedImageFormats.py +++ b/qt__pyqt__pyside__pyqode/QImageReader.supportedImageFormats.py @@ -1,12 +1,11 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PyQt5.QtGui import QImageReader -print( - list(map(lambda x: x.data().decode(), QImageReader.supportedImageFormats())) -) + +print(list(map(lambda x: x.data().decode(), QImageReader.supportedImageFormats()))) # ['bmp', 'cur', 'gif', 'icns', 'ico', 'jpeg', 'jpg', 'pbm', 'pgm', 'png', 'ppm', 'svg', 'svgz', 'tga', 'tif', 'tiff', 'wbmp', 'webp', 'xbm', 'xpm'] diff --git a/qt__pyqt__pyside__pyqode/QLabel__enter_leave__with_pixmap/main.py b/qt__pyqt__pyside__pyqode/QLabel__enter_leave__with_pixmap/main.py index 503552ec1..24b062c28 100644 --- a/qt__pyqt__pyside__pyqode/QLabel__enter_leave__with_pixmap/main.py +++ b/qt__pyqt__pyside__pyqode/QLabel__enter_leave__with_pixmap/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PyQt5.QtWidgets import QApplication, QLabel @@ -9,23 +9,23 @@ class Label(QLabel): - def __init__(self): + def __init__(self) -> None: super().__init__() - self.pixmap = QPixmap('52rQ3.png') + self.pixmap = QPixmap("52rQ3.png") self.pixmap_leave = self.pixmap.copy(0, 0, 26, 26) self.pixmap_enter = self.pixmap.copy(26, 0, 52, 26) self.setPixmap(self.pixmap_leave) - def enterEvent(self, event): + def enterEvent(self, event) -> None: self.setPixmap(self.pixmap_enter) - def leaveEvent(self, event): + def leaveEvent(self, event) -> None: self.setPixmap(self.pixmap_leave) -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) mw = Label() diff --git a/qt__pyqt__pyside__pyqode/QLabel_bold_italic_text/use__QFont.py b/qt__pyqt__pyside__pyqode/QLabel_bold_italic_text/use__QFont.py index 3ec556b50..d2b09cff9 100644 --- a/qt__pyqt__pyside__pyqode/QLabel_bold_italic_text/use__QFont.py +++ b/qt__pyqt__pyside__pyqode/QLabel_bold_italic_text/use__QFont.py @@ -1,15 +1,16 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PyQt5.Qt import QApplication, QLabel, QVBoxLayout, QWidget + app = QApplication([]) label_1 = QLabel() -label_1.setText('Hello World!!!') +label_1.setText("Hello World!!!") font = label_1.font() font.setItalic(True) @@ -17,7 +18,7 @@ label_1.setFont(font) label_2 = QLabel() -label_2.setText('nothing...') +label_2.setText("nothing...") layout = QVBoxLayout() layout.addWidget(label_1) diff --git a/qt__pyqt__pyside__pyqode/QLabel_bold_italic_text/use__html.py b/qt__pyqt__pyside__pyqode/QLabel_bold_italic_text/use__html.py index 2f214642b..897b864f6 100644 --- a/qt__pyqt__pyside__pyqode/QLabel_bold_italic_text/use__html.py +++ b/qt__pyqt__pyside__pyqode/QLabel_bold_italic_text/use__html.py @@ -1,18 +1,19 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PyQt5.Qt import QApplication, QLabel, QVBoxLayout, QWidget + app = QApplication([]) label_1 = QLabel() -label_1.setText('Hello World!!!') +label_1.setText("Hello World!!!") label_2 = QLabel() -label_2.setText('nothing...') +label_2.setText("nothing...") layout = QVBoxLayout() layout.addWidget(label_1) diff --git a/qt__pyqt__pyside__pyqode/QLabel_bold_italic_text/use__qss__qt_style_sheet__by_object_name.py b/qt__pyqt__pyside__pyqode/QLabel_bold_italic_text/use__qss__qt_style_sheet__by_object_name.py index 2856d3e7e..749dbe34a 100644 --- a/qt__pyqt__pyside__pyqode/QLabel_bold_italic_text/use__qss__qt_style_sheet__by_object_name.py +++ b/qt__pyqt__pyside__pyqode/QLabel_bold_italic_text/use__qss__qt_style_sheet__by_object_name.py @@ -1,30 +1,33 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PyQt5.Qt import QApplication, QLabel, QVBoxLayout, QWidget + app = QApplication([]) -app.setStyleSheet(''' +app.setStyleSheet( + """ #welcome { font: bold italic } #label_foo_bar { font: italic; color: green; } -''') +""" +) label_1 = QLabel() -label_1.setObjectName('welcome') -label_1.setText('Hello World!!!') +label_1.setObjectName("welcome") +label_1.setText("Hello World!!!") label_2 = QLabel() -label_2.setText('nothing...') +label_2.setText("nothing...") label_3 = QLabel() -label_3.setObjectName('label_foo_bar') -label_3.setText('FooBar') +label_3.setObjectName("label_foo_bar") +label_3.setText("FooBar") layout = QVBoxLayout() layout.addWidget(label_1) diff --git a/qt__pyqt__pyside__pyqode/QLabel_bold_italic_text/use__qss__qt_style_sheet__global.py b/qt__pyqt__pyside__pyqode/QLabel_bold_italic_text/use__qss__qt_style_sheet__global.py index ddbf73edb..096fd41ca 100644 --- a/qt__pyqt__pyside__pyqode/QLabel_bold_italic_text/use__qss__qt_style_sheet__global.py +++ b/qt__pyqt__pyside__pyqode/QLabel_bold_italic_text/use__qss__qt_style_sheet__global.py @@ -1,19 +1,20 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PyQt5.Qt import QApplication, QLabel, QVBoxLayout, QWidget + app = QApplication([]) -app.setStyleSheet('QLabel { font: bold italic }') +app.setStyleSheet("QLabel { font: bold italic }") label_1 = QLabel() -label_1.setText('Hello World!!!') +label_1.setText("Hello World!!!") label_2 = QLabel() -label_2.setText('nothing...') +label_2.setText("nothing...") layout = QVBoxLayout() layout.addWidget(label_1) diff --git a/qt__pyqt__pyside__pyqode/QLabel_bold_italic_text/use__qss__qt_style_sheet__local.py b/qt__pyqt__pyside__pyqode/QLabel_bold_italic_text/use__qss__qt_style_sheet__local.py index c8beffea6..4ff050f43 100644 --- a/qt__pyqt__pyside__pyqode/QLabel_bold_italic_text/use__qss__qt_style_sheet__local.py +++ b/qt__pyqt__pyside__pyqode/QLabel_bold_italic_text/use__qss__qt_style_sheet__local.py @@ -1,23 +1,26 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PyQt5.Qt import QApplication, QLabel, QVBoxLayout, QWidget + app = QApplication([]) label_1 = QLabel() -label_1.setText('Hello World!!!') -label_1.setStyleSheet(""" +label_1.setText("Hello World!!!") +label_1.setStyleSheet( + """ font: bold italic; color: green; background-color: black; -""") +""" +) label_2 = QLabel() -label_2.setText('nothing...') +label_2.setText("nothing...") layout = QVBoxLayout() layout.addWidget(label_1) diff --git a/qt__pyqt__pyside__pyqode/QLabel_setText__in_thread__QThread.py b/qt__pyqt__pyside__pyqode/QLabel_setText__in_thread__QThread.py index 45766a589..8fbbf55c1 100644 --- a/qt__pyqt__pyside__pyqode/QLabel_setText__in_thread__QThread.py +++ b/qt__pyqt__pyside__pyqode/QLabel_setText__in_thread__QThread.py @@ -1,23 +1,22 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import time - from PyQt5.Qt import QMainWindow, QLabel, QFont, QApplication, Qt, QThread class MainWindow(QMainWindow): - def __init__(self): + def __init__(self) -> None: super().__init__() self.label = QLabel() self.label.setAlignment(Qt.AlignCenter) - self.label.setFont(QFont('Courier', 25)) + self.label.setFont(QFont("Courier", 25)) - def loop(): + def loop() -> None: i = 0 while True: @@ -27,7 +26,7 @@ def loop(): time.sleep(1) class Thread(QThread): - def run(self): + def run(self) -> None: loop() self.thread = Thread() @@ -36,7 +35,7 @@ def run(self): self.setCentralWidget(self.label) -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) mw = MainWindow() diff --git a/qt__pyqt__pyside__pyqode/QLabel_setText__in_thread__threading.py b/qt__pyqt__pyside__pyqode/QLabel_setText__in_thread__threading.py index 75e644223..80d27525b 100644 --- a/qt__pyqt__pyside__pyqode/QLabel_setText__in_thread__threading.py +++ b/qt__pyqt__pyside__pyqode/QLabel_setText__in_thread__threading.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import threading @@ -11,14 +11,14 @@ class MainWindow(QMainWindow): - def __init__(self): + def __init__(self) -> None: super().__init__() self.label = QLabel() self.label.setAlignment(Qt.AlignCenter) - self.label.setFont(QFont('Courier', 25)) + self.label.setFont(QFont("Courier", 25)) - def loop(): + def loop() -> None: i = 0 while True: @@ -33,7 +33,7 @@ def loop(): self.setCentralWidget(self.label) -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) mw = MainWindow() diff --git a/qt__pyqt__pyside__pyqode/QLibraryInfo_location.py b/qt__pyqt__pyside__pyqode/QLibraryInfo_location.py new file mode 100644 index 000000000..9d6ebb392 --- /dev/null +++ b/qt__pyqt__pyside__pyqode/QLibraryInfo_location.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from PyQt5.QtCore import QLibraryInfo + + +print(QLibraryInfo.location(QLibraryInfo.TranslationsPath)) +print(QLibraryInfo.location(QLibraryInfo.PluginsPath)) +print(QLibraryInfo.location(QLibraryInfo.ExamplesPath)) +""" +C:/Users/ipetrash/AppData/Local/Programs/Python/Python310/lib/site-packages/PyQt5/Qt5/translations +C:/Users/ipetrash/AppData/Local/Programs/Python/Python310/lib/site-packages/PyQt5/Qt5/plugins +C:/Users/ipetrash/AppData/Local/Programs/Python/Python310/lib/site-packages/PyQt5/Qt5/examples +""" diff --git a/qt__pyqt__pyside__pyqode/QLineEdit__mask.py b/qt__pyqt__pyside__pyqode/QLineEdit__mask.py index 934aad3f4..934c4f95c 100644 --- a/qt__pyqt__pyside__pyqode/QLineEdit__mask.py +++ b/qt__pyqt__pyside__pyqode/QLineEdit__mask.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://doc.qt.io/qt-5/qlineedit.html#inputMask-prop @@ -11,7 +11,7 @@ class MainWindow(QWidget): - def __init__(self): + def __init__(self) -> None: super().__init__() self.mask_qt = QLineEdit("+7([000])[000]-[0000]") @@ -25,14 +25,14 @@ def __init__(self): self.setLayout(layout) - def _on_changed_mask(self, mask): + def _on_changed_mask(self, mask) -> None: text = self.test_input.text() self.test_input.setInputMask(mask) self.test_input.setText(text) -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) mw = MainWindow() diff --git a/qt__pyqt__pyside__pyqode/QListWidget_with_custom_widgets.py b/qt__pyqt__pyside__pyqode/QListWidget_with_custom_widgets.py index 5473ae443..9bf014214 100644 --- a/qt__pyqt__pyside__pyqode/QListWidget_with_custom_widgets.py +++ b/qt__pyqt__pyside__pyqode/QListWidget_with_custom_widgets.py @@ -1,38 +1,53 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -import datetime as DT +import datetime as dt -from PyQt5.QtWidgets import QApplication, QListWidget, QListWidgetItem, QFrame, QFormLayout, QLabel +from PyQt5.QtWidgets import ( + QApplication, + QListWidget, + QListWidgetItem, + QFrame, + QFormLayout, + QLabel, +) from PyQt5.QtCore import Qt class ListItem(QFrame): - def __init__(self, host: str, port: int, status: str = 'Not checked!', last_check_time: DT.datetime = None): + def __init__( + self, + host: str, + port: int, + status: str = "Not checked!", + last_check_time: dt.datetime = None, + ) -> None: super().__init__() if not last_check_time: - last_check_time = DT.datetime.now() + last_check_time = dt.datetime.now() self.setFrameShape(QFrame.Box) self.setFrameShadow(QFrame.Plain) - self.label_address = QLabel(f'{host}:{port}') + self.label_address = QLabel(f"{host}:{port}") self.label_status = QLabel(status) - self.label_last_check_time = QLabel(last_check_time.strftime('%d/%m/%Y %H:%M:%S')) + self.label_last_check_time = QLabel( + last_check_time.strftime("%d/%m/%Y %H:%M:%S") + ) main_layout = QFormLayout(self) - main_layout.addRow('Address:', self.label_address) - main_layout.addRow('Status:', self.label_status) - main_layout.addRow('Last check time:', self.label_last_check_time) + main_layout.addRow("Address:", self.label_address) + main_layout.addRow("Status:", self.label_status) + main_layout.addRow("Last check time:", self.label_last_check_time) self.setFixedHeight(self.sizeHint().height()) -def add_item(list_widget: QListWidget, host: str, port: int): +def add_item(list_widget: QListWidget, host: str, port: int) -> None: widget = ListItem(host, port) item = QListWidgetItem() @@ -43,7 +58,7 @@ def add_item(list_widget: QListWidget, host: str, port: int): list_widget.setItemWidget(item, widget) -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) list_widgets = QListWidget() @@ -52,10 +67,10 @@ def add_item(list_widget: QListWidget, host: str, port: int): list_widgets.itemClicked.connect(lambda item: print(item.data(Qt.UserRole))) - add_item(list_widgets, '127.0.0.1', 161) - add_item(list_widgets, '192.168.0.0', 161) + add_item(list_widgets, "127.0.0.1", 161) + add_item(list_widgets, "192.168.0.0", 161) for i in range(1, 10 + 1): - add_item(list_widgets, '127.0.0.1', 161 + i) + add_item(list_widgets, "127.0.0.1", 161 + i) list_widgets.show() diff --git a/qt__pyqt__pyside__pyqode/QML__examples/hello_text.qml b/qt__pyqt__pyside__pyqode/QML__examples/hello_text.qml new file mode 100644 index 000000000..001a12cf1 --- /dev/null +++ b/qt__pyqt__pyside__pyqode/QML__examples/hello_text.qml @@ -0,0 +1,23 @@ +import QtQuick 2.3 + +Rectangle { + width: 360 + height: 360 + + Text { + id: helloText + objectName: "helloText" + + text: qsTr("Click me!") + anchors.centerIn: parent + font.pointSize: 24 + font.bold: true + + MouseArea { + anchors.fill: parent + onClicked: { + helloText.text = qsTr("Hello World!"); + } + } + } +} \ No newline at end of file diff --git a/qt__pyqt__pyside__pyqode/QML__examples/hello_text_window.qml b/qt__pyqt__pyside__pyqode/QML__examples/hello_text_window.qml new file mode 100644 index 000000000..7daed8dc9 --- /dev/null +++ b/qt__pyqt__pyside__pyqode/QML__examples/hello_text_window.qml @@ -0,0 +1,23 @@ +import QtQuick 2.3 +import QtQuick.Window 2.0 + +Window { + visible: true + width: 640 + height: 480 + title: qsTr("Hello World") //The method qsTr() is used for translations from one language to other. + + Text { + id: helloText + text: qsTr("Click me!") + anchors.centerIn: parent + font.pointSize: 24; font.bold: true + + MouseArea { + anchors.fill: parent + onClicked: { + helloText.text = "Hello World!"; + } + } + } +} \ No newline at end of file diff --git a/qt__pyqt__pyside__pyqode/QML__examples/hello_world.py b/qt__pyqt__pyside__pyqode/QML__examples/hello_world.py new file mode 100644 index 000000000..ebeb67d04 --- /dev/null +++ b/qt__pyqt__pyside__pyqode/QML__examples/hello_world.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from pathlib import Path + +from PyQt5.QtGui import QGuiApplication +from PyQt5.QtQuick import QQuickView +from PyQt5.QtCore import QUrl + + +app = QGuiApplication([]) + +qml_file = Path(__file__).parent.resolve() / "hello_text.qml" +url_qml_file = QUrl.fromLocalFile(str(qml_file)) + +view = QQuickView() +view.setTitle("Hello World!") +view.setSource(url_qml_file) +view.show() + +app.exec() diff --git a/qt__pyqt__pyside__pyqode/QML__examples/hello_world_window.py b/qt__pyqt__pyside__pyqode/QML__examples/hello_world_window.py new file mode 100644 index 000000000..64ffdd123 --- /dev/null +++ b/qt__pyqt__pyside__pyqode/QML__examples/hello_world_window.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from pathlib import Path + +from PyQt5.QtGui import QGuiApplication # NOTE: For only QML, QGuiApplication is enough +from PyQt5.QtQml import QQmlApplicationEngine +from PyQt5.QtCore import QUrl + + +app = QGuiApplication([]) + +qml_file_window = Path(__file__).parent.resolve() / "hello_text_window.qml" +url_qml_file_window = QUrl.fromLocalFile(str(qml_file_window)) + +engine = QQmlApplicationEngine(url_qml_file_window) + +app.exec() diff --git a/qt__pyqt__pyside__pyqode/QML__examples/hello_world_with_widgets.py b/qt__pyqt__pyside__pyqode/QML__examples/hello_world_with_widgets.py new file mode 100644 index 000000000..00fe3caf7 --- /dev/null +++ b/qt__pyqt__pyside__pyqode/QML__examples/hello_world_with_widgets.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from datetime import datetime +from pathlib import Path + +from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QVBoxLayout +from PyQt5.QtQuick import QQuickView, QQuickItem +from PyQt5.QtCore import QUrl + + +QML_FILE = Path(__file__).parent.resolve() / "hello_text.qml" +URL_QML_FILE = QUrl.fromLocalFile(str(QML_FILE)) + + +class MainWindow(QWidget): + def __init__(self) -> None: + super().__init__() + + self.setWindowTitle("Hello World! (QML + Qt)") + + self.view = QQuickView() + self.view.setSource(URL_QML_FILE) + + pb_click = QPushButton("Click me!") + pb_click.clicked.connect(self._on_clicked) + + main_layout = QVBoxLayout(self) + main_layout.addWidget(QWidget.createWindowContainer(self.view)) + main_layout.addWidget(pb_click) + + def _on_clicked(self) -> None: + qml_hello_text = self.view.rootObject().findChild(QQuickItem, "helloText") + qml_hello_text.setProperty("text", f"Now: {datetime.now().time():%H:%M:%S}") + + +if __name__ == "__main__": + app = QApplication([]) + + view = QQuickView() + view.setTitle("Hello World! (single)") + view.setSource(URL_QML_FILE) + view.show() + + main_window = MainWindow() + main_window.resize(500, 500) + main_window.show() + + app.exec() diff --git a/qt__pyqt__pyside__pyqode/QMdiSubWindow__hello_world.py b/qt__pyqt__pyside__pyqode/QMdiSubWindow__hello_world.py index 7a5b0acc8..8584615d1 100644 --- a/qt__pyqt__pyside__pyqode/QMdiSubWindow__hello_world.py +++ b/qt__pyqt__pyside__pyqode/QMdiSubWindow__hello_world.py @@ -1,21 +1,28 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -from PyQt5.QtWidgets import QApplication, QMainWindow, QMdiSubWindow, QMdiArea, QTextEdit, QPushButton +from PyQt5.QtWidgets import ( + QApplication, + QMainWindow, + QMdiSubWindow, + QMdiArea, + QTextEdit, + QPushButton, +) class MainWindow(QMainWindow): - def __init__(self): + def __init__(self) -> None: super().__init__() self.sub_window_1 = QMdiSubWindow() self.sub_window_1.setWidget(QTextEdit("

Hello World!

")) self.sub_window_2 = QMdiSubWindow() - self.sub_window_2.setWidget(QPushButton('Click!')) + self.sub_window_2.setWidget(QPushButton("Click!")) self.mdi_area = QMdiArea() self.mdi_area.addSubWindow(self.sub_window_1) @@ -24,7 +31,7 @@ def __init__(self): self.setCentralWidget(self.mdi_area) -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) mw = MainWindow() diff --git a/qt__pyqt__pyside__pyqode/QPainter_setOpacity/main.py b/qt__pyqt__pyside__pyqode/QPainter_setOpacity/main.py index 164ad59da..1db1bf643 100644 --- a/qt__pyqt__pyside__pyqode/QPainter_setOpacity/main.py +++ b/qt__pyqt__pyside__pyqode/QPainter_setOpacity/main.py @@ -1,14 +1,14 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PyQt5.QtGui import QPainter, QImage from PyQt5.QtCore import Qt -img = QImage(r'video-game-192x192.png') +img = QImage(r"video-game-192x192.png") img2 = QImage(img.size(), QImage.Format_ARGB32) img2.fill(Qt.transparent) @@ -16,4 +16,4 @@ p.setOpacity(0.85) p.drawImage(img.rect(), img) -img2.save(r'video-game-192x192__transparent.png') +img2.save(r"video-game-192x192__transparent.png") diff --git a/qt__pyqt__pyside__pyqode/QPixmap_createHeuristicMask/main.py b/qt__pyqt__pyside__pyqode/QPixmap_createHeuristicMask/main.py index 465796e78..e31020d56 100644 --- a/qt__pyqt__pyside__pyqode/QPixmap_createHeuristicMask/main.py +++ b/qt__pyqt__pyside__pyqode/QPixmap_createHeuristicMask/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PyQt5.QtGui import QGuiApplication, QPixmap @@ -12,4 +12,4 @@ pixmap = QPixmap("cat.jpg") pixmap.setMask(pixmap.createHeuristicMask(Qt.transparent)) -pixmap.save('cat.png') +pixmap.save("cat.png") diff --git a/qt__pyqt__pyside__pyqode/QTextEdit__with_font_size_panel.py b/qt__pyqt__pyside__pyqode/QTextEdit__with_font_size_panel.py index 22be23e1e..512d88dc0 100644 --- a/qt__pyqt__pyside__pyqode/QTextEdit__with_font_size_panel.py +++ b/qt__pyqt__pyside__pyqode/QTextEdit__with_font_size_panel.py @@ -1,21 +1,21 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PyQt5 import Qt class MainWindow(Qt.QMainWindow): - def __init__(self): + def __init__(self) -> None: super().__init__() self.sb_font_size = Qt.QSpinBox() self.sb_font_size.setRange(5, 40) self.sb_font_size.valueChanged.connect(self._on_font_size_changed) - self.text_edit = Qt.QTextEdit('Test this!') + self.text_edit = Qt.QTextEdit("Test this!") layout = Qt.QVBoxLayout() layout.addWidget(self.sb_font_size) @@ -26,13 +26,13 @@ def __init__(self): self.setCentralWidget(central_widget) - def _on_font_size_changed(self, value): + def _on_font_size_changed(self, value) -> None: text_char_format = Qt.QTextCharFormat() text_char_format.setFontPointSize(value) self.merge_format_on_word_or_selection(text_char_format) - def merge_format_on_word_or_selection(self, text_char_format): + def merge_format_on_word_or_selection(self, text_char_format) -> None: cursor = self.text_edit.textCursor() if not cursor.hasSelection(): cursor.select(Qt.QTextCursor.WordUnderCursor) @@ -40,7 +40,7 @@ def merge_format_on_word_or_selection(self, text_char_format): cursor.mergeCharFormat(text_char_format) -if __name__ == '__main__': +if __name__ == "__main__": app = Qt.QApplication([]) mw = MainWindow() diff --git a/qt__pyqt__pyside__pyqode/QThreadPool__QRunnable.py b/qt__pyqt__pyside__pyqode/QThreadPool__QRunnable.py index 7e82d541a..af48f9ec5 100644 --- a/qt__pyqt__pyside__pyqode/QThreadPool__QRunnable.py +++ b/qt__pyqt__pyside__pyqode/QThreadPool__QRunnable.py @@ -1,7 +1,8 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" + import threading import time @@ -9,16 +10,17 @@ from PyQt5.QtCore import QThreadPool, QRunnable + lock = threading.Lock() class HelloWorldTask(QRunnable): - def __init__(self, idx: int): + def __init__(self, idx: int) -> None: super().__init__() self.idx = idx - def run(self): + def run(self) -> None: with lock: print(f"[{self.idx}] Hello world from thread: {threading.current_thread()}") diff --git a/qt__pyqt__pyside__pyqode/QThread_with_pause_and_resume__QWaitCondition.py b/qt__pyqt__pyside__pyqode/QThread_with_pause_and_resume__QWaitCondition.py index 73d0a4870..405f622ee 100644 --- a/qt__pyqt__pyside__pyqode/QThread_with_pause_and_resume__QWaitCondition.py +++ b/qt__pyqt__pyside__pyqode/QThread_with_pause_and_resume__QWaitCondition.py @@ -1,20 +1,25 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import sys import time -from typing import List - -from PyQt5.QtWidgets import QApplication, QWidget, QGridLayout, QComboBox, QPushButton, QLabel +from PyQt5.QtWidgets import ( + QApplication, + QWidget, + QGridLayout, + QComboBox, + QPushButton, + QLabel, +) from PyQt5.QtCore import QThread, QTimer, QWaitCondition, QMutex class Thread(QThread): - def __init__(self, parent=None): + def __init__(self, parent=None) -> None: super().__init__(parent) self._is_pause = False @@ -25,14 +30,14 @@ def __init__(self, parent=None): def is_pause(self) -> bool: return self._is_pause - def pause(self): + def pause(self) -> None: self._is_pause = True - def resume(self): + def resume(self) -> None: self._is_pause = False self.condition.wakeAll() - def run(self): + def run(self) -> None: while True: self.mutex.lock() if self._is_pause: @@ -43,7 +48,7 @@ def run(self): class MainWindow(QWidget): - def __init__(self): + def __init__(self) -> None: super().__init__() self.combobox_thread = QComboBox() @@ -51,19 +56,19 @@ def __init__(self): self.label_result = QLabel() - self.button_add = QPushButton('Add') + self.button_add = QPushButton("Add") self.button_add.clicked.connect(self.add) - self.button_pause = QPushButton('Pause') + self.button_pause = QPushButton("Pause") self.button_pause.clicked.connect(self.pause) - self.button_pause_all = QPushButton('Pause all') + self.button_pause_all = QPushButton("Pause all") self.button_pause_all.clicked.connect(self.pause_all) - self.button_resume = QPushButton('Resume') + self.button_resume = QPushButton("Resume") self.button_resume.clicked.connect(self.resume) - self.button_resume_all = QPushButton('Resume all') + self.button_resume_all = QPushButton("Resume all") self.button_resume_all.clicked.connect(self.resume_all) layout = QGridLayout(self) @@ -82,9 +87,9 @@ def __init__(self): self._update_states() - def _update_states(self): + def _update_states(self) -> None: threads_num = self.combobox_thread.count() - self.setWindowTitle(f'Threads: {threads_num}') + self.setWindowTitle(f"Threads: {threads_num}") idx = self.combobox_thread.currentIndex() ok = idx != -1 @@ -94,8 +99,8 @@ def _update_states(self): if ok: title = self.combobox_thread.itemText(idx) - self.button_pause.setText(f'Pause - {title!r}') - self.button_resume.setText(f'Resume - {title!r}') + self.button_pause.setText(f"Pause - {title!r}") + self.button_resume.setText(f"Resume - {title!r}") self.button_pause_all.setEnabled(threads_num) self.button_resume_all.setEnabled(threads_num) @@ -103,48 +108,50 @@ def _update_states(self): def get_thread(self, idx: int) -> Thread: return self.combobox_thread.itemData(idx) - def get_all_thread(self) -> List[Thread]: + def get_all_thread(self) -> list[Thread]: return [self.get_thread(i) for i in range(self.combobox_thread.count())] - def update_info(self): + def update_info(self) -> None: total = sum(thread.sum for thread in self.get_all_thread()) self.label_result.setText(f"SUM: {total}") - def add(self): + def add(self) -> None: thread = Thread(self) thread.start() - self.combobox_thread.addItem(f'Thread #{self.combobox_thread.count() + 1}', thread) + self.combobox_thread.addItem( + f"Thread #{self.combobox_thread.count() + 1}", thread + ) self._update_states() - def pause(self): + def pause(self) -> None: thread = self.combobox_thread.currentData() if thread: thread.pause() self._update_states() - def pause_all(self): + def pause_all(self) -> None: for thread in self.get_all_thread(): thread.pause() self._update_states() - def resume(self): + def resume(self) -> None: thread = self.combobox_thread.currentData() if thread: thread.resume() self._update_states() - def resume_all(self): + def resume_all(self) -> None: for thread in self.get_all_thread(): thread.resume() self._update_states() -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication(sys.argv) mw = MainWindow() diff --git a/qt__pyqt__pyside__pyqode/QThread_with_pause_and_resume__QWaitCondition_with_QTableWidget.py b/qt__pyqt__pyside__pyqode/QThread_with_pause_and_resume__QWaitCondition_with_QTableWidget.py index 6d8c07847..228b77fce 100644 --- a/qt__pyqt__pyside__pyqode/QThread_with_pause_and_resume__QWaitCondition_with_QTableWidget.py +++ b/qt__pyqt__pyside__pyqode/QThread_with_pause_and_resume__QWaitCondition_with_QTableWidget.py @@ -1,31 +1,37 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -import datetime as DT +import datetime as dt import sys import time -from typing import List - from PyQt5.QtWidgets import ( - QApplication, QWidget, QVBoxLayout, QHBoxLayout, QGridLayout, - QTableWidget, QTableWidgetItem, QPushButton, QLabel, QCheckBox + QApplication, + QWidget, + QVBoxLayout, + QHBoxLayout, + QGridLayout, + QTableWidget, + QTableWidgetItem, + QPushButton, + QLabel, + QCheckBox, ) from PyQt5.QtCore import QThread, QTimer, QWaitCondition, QMutex, pyqtSignal, Qt def get_pretty_int(num: int) -> str: - return f'{num:,}'.replace(',', ' ') + return f"{num:,}".replace(",", " ") class Thread(QThread): about_pause = pyqtSignal(bool) about_sum = pyqtSignal(int) - def __init__(self, parent=None): + def __init__(self, parent=None) -> None: super().__init__(parent) self._is_pause = False @@ -38,14 +44,14 @@ def sum(self) -> int: return self._sum @sum.setter - def sum(self, value: int): + def sum(self, value: int) -> None: self._sum = value self.about_sum.emit(value) def get_state(self) -> str: - return 'PAUSED' if self._is_pause else 'WORKING' + return "PAUSED" if self._is_pause else "WORKING" - def set_pause(self, pause: bool): + def set_pause(self, pause: bool) -> None: self._is_pause = pause self.about_pause.emit(pause) @@ -55,13 +61,13 @@ def set_pause(self, pause: bool): def is_pause(self) -> bool: return self._is_pause - def pause(self): + def pause(self) -> None: self.set_pause(True) - def resume(self): + def resume(self) -> None: self.set_pause(False) - def run(self): + def run(self) -> None: while True: self.mutex.lock() if self._is_pause: @@ -72,12 +78,12 @@ def run(self): class MainWindow(QWidget): - headers = ['NAME', 'STATE', 'SUM'] + headers = ["NAME", "STATE", "SUM"] - def __init__(self): + def __init__(self) -> None: super().__init__() - self.started: DT.datetime = None + self.started: dt.datetime = None self._sum = 0 self.table_thread = QTableWidget() @@ -88,26 +94,28 @@ def __init__(self): self.table_thread.setSelectionBehavior(QTableWidget.SelectRows) self.table_thread.setSelectionMode(QTableWidget.SingleSelection) self.table_thread.setMinimumHeight(250) - self.table_thread.selectionModel().currentRowChanged.connect(self._update_states) + self.table_thread.selectionModel().currentRowChanged.connect( + self._update_states + ) self.label_result = QLabel() - self.button_add = QPushButton('Add') + self.button_add = QPushButton("Add") self.button_add.clicked.connect(self.add) - self.button_pause = QPushButton('Pause') + self.button_pause = QPushButton("Pause") self.button_pause.clicked.connect(self.pause) - self.button_pause_all = QPushButton('Pause all') + self.button_pause_all = QPushButton("Pause all") self.button_pause_all.clicked.connect(self.pause_all) - self.button_resume = QPushButton('Resume') + self.button_resume = QPushButton("Resume") self.button_resume.clicked.connect(self.resume) - self.button_resume_all = QPushButton('Resume all') + self.button_resume_all = QPushButton("Resume all") self.button_resume_all.clicked.connect(self.resume_all) - self.cb_reset_sum = QCheckBox('Reset sum') + self.cb_reset_sum = QCheckBox("Reset sum") self.cb_reset_sum.setChecked(True) left_layout = QVBoxLayout() @@ -133,15 +141,15 @@ def __init__(self): self._update_states() - def _update_window_title(self): + def _update_window_title(self) -> None: threads_num = self.table_thread.rowCount() if self.started: - elapsed = str(DT.datetime.now() - self.started).split('.')[0] - self.setWindowTitle(f'Threads: {threads_num}. Elapsed: {elapsed}') + elapsed = str(dt.datetime.now() - self.started).split(".")[0] + self.setWindowTitle(f"Threads: {threads_num}. Elapsed: {elapsed}") else: - self.setWindowTitle(f'Threads: {threads_num}') + self.setWindowTitle(f"Threads: {threads_num}") - def _update_states(self): + def _update_states(self) -> None: self._update_window_title() row = self.table_thread.currentRow() @@ -152,8 +160,8 @@ def _update_states(self): if ok: title = self.table_thread.item(row, 0).text() - self.button_pause.setText(f'Pause - {title!r}') - self.button_resume.setText(f'Resume - {title!r}') + self.button_pause.setText(f"Pause - {title!r}") + self.button_resume.setText(f"Resume - {title!r}") threads_num = self.table_thread.rowCount() self.button_pause_all.setEnabled(threads_num) @@ -162,10 +170,10 @@ def _update_states(self): def get_thread(self, row: int) -> Thread: return self.table_thread.item(row, 0).data(Qt.UserRole) - def get_all_thread(self) -> List[Thread]: + def get_all_thread(self) -> list[Thread]: return [self.get_thread(row) for row in range(self.table_thread.rowCount())] - def update_info(self): + def update_info(self) -> None: self._update_window_title() if self.cb_reset_sum.isChecked(): @@ -174,24 +182,28 @@ def update_info(self): self._sum += sum(thread.sum for thread in self.get_all_thread()) self.label_result.setText(f"TOTAL SUM: {get_pretty_int(self._sum)}") - def _on_thread_changed(self, thread: Thread): + def _on_thread_changed(self, thread: Thread) -> None: for row in range(self.table_thread.rowCount()): if thread == self.get_thread(row): self.table_thread.item(row, 1).setText(thread.get_state()) self.table_thread.item(row, 2).setText(get_pretty_int(thread.sum)) - def add(self): + def add(self) -> None: if not self.started: - self.started = DT.datetime.now() + self.started = dt.datetime.now() thread = Thread(self) - thread.about_sum.connect(lambda _, thread=thread: self._on_thread_changed(thread)) - thread.about_pause.connect(lambda _, thread=thread: self._on_thread_changed(thread)) + thread.about_sum.connect( + lambda _, thread=thread: self._on_thread_changed(thread) + ) + thread.about_pause.connect( + lambda _, thread=thread: self._on_thread_changed(thread) + ) row = self.table_thread.rowCount() self.table_thread.setRowCount(row + 1) - title = f'Thread #{row + 1}' + title = f"Thread #{row + 1}" item_title = QTableWidgetItem(title) item_title.setData(Qt.UserRole, thread) @@ -206,7 +218,7 @@ def add(self): self._update_states() - def pause(self): + def pause(self) -> None: item = self.table_thread.currentItem() if item: thread = self.get_thread(item.row()) @@ -214,13 +226,13 @@ def pause(self): self._update_states() - def pause_all(self): + def pause_all(self) -> None: for thread in self.get_all_thread(): thread.pause() self._update_states() - def resume(self): + def resume(self) -> None: item = self.table_thread.currentItem() if item: thread = self.get_thread(item.row()) @@ -228,14 +240,14 @@ def resume(self): self._update_states() - def resume_all(self): + def resume_all(self) -> None: for thread in self.get_all_thread(): thread.resume() self._update_states() -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication(sys.argv) mw = MainWindow() diff --git a/qt__pyqt__pyside__pyqode/QWebEngineView__load__blank_url.py b/qt__pyqt__pyside__pyqode/QWebEngineView__load__blank_url.py index cb4b1f682..6f7f69feb 100644 --- a/qt__pyqt__pyside__pyqode/QWebEngineView__load__blank_url.py +++ b/qt__pyqt__pyside__pyqode/QWebEngineView__load__blank_url.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://ru.stackoverflow.com/a/971228/201445 @@ -18,24 +18,26 @@ def createWindow(self, _type): page.urlChanged.connect(self.on_url_changed) return page - def on_url_changed(self, url: QUrl): + def on_url_changed(self, url: QUrl) -> None: page = self.sender() self.setUrl(url) page.deleteLater() class MainWindow(QMainWindow): - def __init__(self): + def __init__(self) -> None: super().__init__() self.browser = QWebEngineView() self.browser.setPage(WebEnginePage(self.browser)) - self.browser.load(QUrl("https://www.w3schools.com/tags/tryit.asp?filename=tryhtml_a_target")) + self.browser.load( + QUrl("https://www.w3schools.com/tags/tryit.asp?filename=tryhtml_a_target") + ) self.setCentralWidget(self.browser) -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) mw = MainWindow() diff --git a/qt__pyqt__pyside__pyqode/QWebEngine__append_custom_javascript__jQuery/main.py b/qt__pyqt__pyside__pyqode/QWebEngine__append_custom_javascript__jQuery/main.py index 3fd41c545..3da012a4c 100644 --- a/qt__pyqt__pyside__pyqode/QWebEngine__append_custom_javascript__jQuery/main.py +++ b/qt__pyqt__pyside__pyqode/QWebEngine__append_custom_javascript__jQuery/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PyQt5.QtCore import QUrl @@ -12,7 +12,7 @@ app = QApplication([]) -with open('js/jquery-3.1.1.min.js') as f: +with open("js/jquery-3.1.1.min.js") as f: jquery_text = f.read() jquery_text += "\nvar qt = { 'jQuery': jQuery.noConflict(true) };" @@ -20,7 +20,7 @@ view = QWebEngineView() -def _on_load_finished(ok: bool): +def _on_load_finished(ok: bool) -> None: view.page().runJavaScript(jquery_text) # Test jQuery @@ -29,7 +29,7 @@ def _on_load_finished(ok: bool): view.loadFinished.connect(_on_load_finished) -view.load(QUrl('https://store.steampowered.com/')) +view.load(QUrl("https://store.steampowered.com/")) view.show() diff --git a/qt__pyqt__pyside__pyqode/QWebEngine__append_custom_javascript__jQuery__with_select_color/main.py b/qt__pyqt__pyside__pyqode/QWebEngine__append_custom_javascript__jQuery__with_select_color/main.py index 98b0247fd..6018c88a7 100644 --- a/qt__pyqt__pyside__pyqode/QWebEngine__append_custom_javascript__jQuery__with_select_color/main.py +++ b/qt__pyqt__pyside__pyqode/QWebEngine__append_custom_javascript__jQuery__with_select_color/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PyQt5.QtCore import QUrl, Qt @@ -10,41 +10,42 @@ class MainWindow(QMainWindow): - def __init__(self): + def __init__(self) -> None: super().__init__() self.view = QWebEngineView() self.view.loadFinished.connect(self._on_load_finished) - with open('../QWebEngine__append_custom_javascript__jQuery/js/jquery-3.1.1.min.js') as f: + file_name = "../QWebEngine__append_custom_javascript__jQuery/js/jquery-3.1.1.min.js" + with open(file_name) as f: self.jquery_text = f.read() self.jquery_text += "\nvar qt = { 'jQuery': jQuery.noConflict(true) };" - tool_bar = self.addToolBar('General') - action = tool_bar.addAction('Change background-color') + tool_bar = self.addToolBar("General") + action = tool_bar.addAction("Change background-color") action.triggered.connect(self._change_background_color) self.setCentralWidget(self.view) - def _on_load_finished(self, ok: bool): + def _on_load_finished(self, ok: bool) -> None: self.view.page().runJavaScript(self.jquery_text) - def _change_background_color(self): + def _change_background_color(self) -> None: color = QColorDialog.getColor(Qt.yellow) if not color.isValid(): return - code = "qt.jQuery('a').css('background-color', '{}');".format(color.name()) + code = f"qt.jQuery('a').css('background-color', '{color.name()}');" self.view.page().runJavaScript(code) -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) mw = MainWindow() mw.resize(400, 400) mw.show() - mw.view.load(QUrl('https://store.steampowered.com/')) + mw.view.load(QUrl("https://store.steampowered.com/")) app.exec() diff --git a/qt__pyqt__pyside__pyqode/QWebEngine__fill_form_login__auth__github_com/main.py b/qt__pyqt__pyside__pyqode/QWebEngine__fill_form_login__auth__github_com/main.py index f0e597de5..e5e174f41 100644 --- a/qt__pyqt__pyside__pyqode/QWebEngine__fill_form_login__auth__github_com/main.py +++ b/qt__pyqt__pyside__pyqode/QWebEngine__fill_form_login__auth__github_com/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PyQt5.QtCore import QUrl @@ -9,11 +9,12 @@ from PyQt5.QtWebEngineWidgets import QWebEngineView -LOGIN = '' -PASSWORD = '' +LOGIN = "" +PASSWORD = "" -with open('../QWebEngine__append_custom_javascript__jQuery/js/jquery-3.1.1.min.js') as f: +file_name = "../QWebEngine__append_custom_javascript__jQuery/js/jquery-3.1.1.min.js" +with open(file_name) as f: jquery_text = f.read() jquery_text += "\nvar qt = { 'jQuery': jQuery.noConflict(true) };" @@ -21,31 +22,35 @@ app = QApplication([]) view = QWebEngineView() -view.load(QUrl('https://github.com/login')) +view.load(QUrl("https://github.com/login")) -def _on_load_finished(ok: bool): +def _on_load_finished(ok: bool) -> None: page = view.page() url = page.url().toString() print(url) - if not url.endswith('login'): + if not url.endswith("login"): return page.runJavaScript(jquery_text) - page.runJavaScript(f""" + page.runJavaScript( + f""" qt.jQuery('#login_field').val('{LOGIN}'); qt.jQuery('#password').val('{PASSWORD}'); qt.jQuery('input[name="commit"]').click(); - """) + """ + ) print() -view.loadProgress.connect(lambda value: view.setWindowTitle('{} ({}%)'.format(view.url().toString(), value))) +view.loadProgress.connect( + lambda value: view.setWindowTitle(f"{view.url().toString()} ({value}%)") +) view.loadFinished.connect(_on_load_finished) mw = QMainWindow() diff --git a/qt__pyqt__pyside__pyqode/QWebEngine__handler_console_log_javascript/main.py b/qt__pyqt__pyside__pyqode/QWebEngine__handler_console_log_javascript/main.py index 2080e7ead..ca05eaeb8 100644 --- a/qt__pyqt__pyside__pyqode/QWebEngine__handler_console_log_javascript/main.py +++ b/qt__pyqt__pyside__pyqode/QWebEngine__handler_console_log_javascript/main.py @@ -1,43 +1,54 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" +import sys + from PyQt5.QtCore import QUrl from PyQt5.QtWidgets import QApplication, QMainWindow from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEnginePage -import sys - class MyWebEnginePage(QWebEnginePage): - def javaScriptConsoleMessage(self, level: 'JavaScriptConsoleMessage', message: str, line_number: int, source_id: str): - print(f'javascript_console_message: {level}, {message}, {line_number}, {source_id}', file=sys.stderr) + def javaScriptConsoleMessage( + self, + level: "JavaScriptConsoleMessage", + message: str, + line_number: int, + source_id: str, + ) -> None: + print( + f"javascript_console_message: {level}, {message}, {line_number}, {source_id}", + file=sys.stderr, + ) app = QApplication([]) page = MyWebEnginePage() -page.load(QUrl('https://ya.ru')) +page.load(QUrl("https://ya.ru")) view = QWebEngineView() view.setPage(page) -def _on_load_finished(ok: bool): +def _on_load_finished(ok: bool) -> None: page = view.page() print(page.url().toString()) - page.runJavaScript('console.log(document.title)') - page.runJavaScript('console.log(this)') + page.runJavaScript("console.log(document.title)") + page.runJavaScript("console.log(this)") page.runJavaScript('console.log("Hello World!")') - page.runJavaScript('console.log(2 + 2 * 2)') + page.runJavaScript("console.log(2 + 2 * 2)") print() -view.loadProgress.connect(lambda value: view.setWindowTitle('{} ({}%)'.format(view.url().toString(), value))) +view.loadProgress.connect( + lambda value: view.setWindowTitle(f"{view.url().toString()} ({value}%)") +) view.loadFinished.connect(_on_load_finished) mw = QMainWindow() diff --git a/qt__pyqt__pyside__pyqode/QWebEngine__runJavaScript__sync__click_on_element/main.py b/qt__pyqt__pyside__pyqode/QWebEngine__runJavaScript__sync__click_on_element/main.py index 8115f3373..a3a1287be 100644 --- a/qt__pyqt__pyside__pyqode/QWebEngine__runJavaScript__sync__click_on_element/main.py +++ b/qt__pyqt__pyside__pyqode/QWebEngine__runJavaScript__sync__click_on_element/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PyQt5.QtCore import QUrl, QEventLoop @@ -12,12 +12,10 @@ def run_js_code(page: QWebEnginePage, code: str) -> object: loop = QEventLoop() - result_value = { - 'value': None - } + result_value = {"value": None} - def _on_callback(result: object): - result_value['value'] = result + def _on_callback(result: object) -> None: + result_value["value"] = result loop.quit() @@ -25,10 +23,11 @@ def _on_callback(result: object): loop.exec() - return result_value['value'] + return result_value["value"] -with open('../QWebEngine__append_custom_javascript__jQuery/js/jquery-3.1.1.min.js') as f: +file_name = "../QWebEngine__append_custom_javascript__jQuery/js/jquery-3.1.1.min.js" +with open(file_name) as f: jquery_text = f.read() jquery_text += "\nvar qt = { 'jQuery': jQuery.noConflict(true) };" @@ -36,17 +35,17 @@ def _on_callback(result: object): app = QApplication([]) view = QWebEngineView() -view.load(QUrl('https://гибдд.рф/request_main')) +view.load(QUrl("https://гибдд.рф/request_main")) -def _on_load_finished(ok: bool): +def _on_load_finished(ok: bool) -> None: page = view.page() print(page.url().toString()) page.runJavaScript(jquery_text) result = run_js_code(page, "document.title") - print('run_java_script:', result) + print("run_java_script:", result) # Клик на флажок "С информацией ознакомлен" run_js_code(page, """qt.jQuery('input[name="agree"]').click();""") @@ -57,7 +56,9 @@ def _on_load_finished(ok: bool): print() -view.loadProgress.connect(lambda value: view.setWindowTitle('{} ({}%)'.format(view.url().toString(), value))) +view.loadProgress.connect( + lambda value: view.setWindowTitle(f"{view.url().toString()} ({value}%)") +) view.loadFinished.connect(_on_load_finished) mw = QMainWindow() diff --git a/qt__pyqt__pyside__pyqode/QWidget_setMask__frameless_window/main.py b/qt__pyqt__pyside__pyqode/QWidget_setMask__frameless_window/main.py index 255b9b8a9..05d200bd9 100644 --- a/qt__pyqt__pyside__pyqode/QWidget_setMask__frameless_window/main.py +++ b/qt__pyqt__pyside__pyqode/QWidget_setMask__frameless_window/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PyQt5.Qt import QApplication, QPixmap, QLabel diff --git a/qt__pyqt__pyside__pyqode/QWidget_with_QMenuBar.py b/qt__pyqt__pyside__pyqode/QWidget_with_QMenuBar.py index 5b9f99bdb..130249ff8 100644 --- a/qt__pyqt__pyside__pyqode/QWidget_with_QMenuBar.py +++ b/qt__pyqt__pyside__pyqode/QWidget_with_QMenuBar.py @@ -1,31 +1,31 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PyQt5 import Qt class MainWindow(Qt.QWidget): - def __init__(self, parent=None): + def __init__(self, parent=None) -> None: super().__init__(parent) menu_bar = Qt.QMenuBar() - menu_file = menu_bar.addMenu('File') - action_exit = menu_file.addAction('Exit') + menu_file = menu_bar.addMenu("File") + action_exit = menu_file.addAction("Exit") action_exit.triggered.connect(self.close) layout = Qt.QVBoxLayout() - layout.addWidget(Qt.QLabel('Test')) - layout.addWidget(Qt.QPushButton('Click!')) + layout.addWidget(Qt.QLabel("Test")) + layout.addWidget(Qt.QPushButton("Click!")) layout.setMenuBar(menu_bar) self.setLayout(layout) -if __name__ == '__main__': +if __name__ == "__main__": app = Qt.QApplication([]) mw = MainWindow() diff --git a/qt__pyqt__pyside__pyqode/QtWebKit_example/parse_myscore_ru.py b/qt__pyqt__pyside__pyqode/QtWebKit_example/parse_myscore_ru.py index f4d66695e..841364e42 100644 --- a/qt__pyqt__pyside__pyqode/QtWebKit_example/parse_myscore_ru.py +++ b/qt__pyqt__pyside__pyqode/QtWebKit_example/parse_myscore_ru.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import sys @@ -12,17 +12,19 @@ from PySide.QtNetwork import * -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication(sys.argv) # Чтобы не было проблем запуска компов с прокси: QNetworkProxyFactory.setUseSystemConfiguration(True) - QWebSettings.globalSettings().setAttribute(QWebSettings.DeveloperExtrasEnabled, True) + QWebSettings.globalSettings().setAttribute( + QWebSettings.DeveloperExtrasEnabled, True + ) view = QWebView() # view.show() - view.load('http://www.myscore.ru/match/nqD8D0j4/#match-statistics;0') + view.load("http://www.myscore.ru/match/nqD8D0j4/#match-statistics;0") # Ждем пока прогрузится страница loop = QEventLoop() @@ -35,8 +37,8 @@ print(doc.findFirst("#statistics-2-statistic a").toPlainText()) table = doc.findFirst("#tab-statistics-0-statistic .parts") - for tr in table.findAll('tr'): - l, text, r = tr.toPlainText().split('\t') + for tr in table.findAll("tr"): + l, text, r = tr.toPlainText().split("\t") print(l, text, r) # sys.exit(app.exec_()) diff --git a/qt__pyqt__pyside__pyqode/RotatedButton__QGraphicsScene.py b/qt__pyqt__pyside__pyqode/RotatedButton__QGraphicsScene.py index 875a53607..9ecac1a74 100644 --- a/qt__pyqt__pyside__pyqode/RotatedButton__QGraphicsScene.py +++ b/qt__pyqt__pyside__pyqode/RotatedButton__QGraphicsScene.py @@ -2,19 +2,27 @@ # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import sys -from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QGraphicsScene, \ - QGraphicsView, QGraphicsLinearLayout, QGraphicsWidget, QHBoxLayout +from PyQt5.QtWidgets import ( + QApplication, + QWidget, + QPushButton, + QGraphicsScene, + QGraphicsView, + QGraphicsLinearLayout, + QGraphicsWidget, + QHBoxLayout, +) from PyQt5.QtGui import QPainter from PyQt5.QtCore import Qt class Window(QWidget): - def __init__(self): + def __init__(self) -> None: super().__init__() self.scene = QGraphicsScene() @@ -56,7 +64,7 @@ def __init__(self): scene_layout.addItem(proxy_rb3) -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication(sys.argv) w = Window() diff --git a/qt__pyqt__pyside__pyqode/RunFuncThread.py b/qt__pyqt__pyside__pyqode/RunFuncThread.py index ae35408b0..7329a61cf 100644 --- a/qt__pyqt__pyside__pyqode/RunFuncThread.py +++ b/qt__pyqt__pyside__pyqode/RunFuncThread.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PyQt5.QtCore import QThread, pyqtSignal @@ -10,17 +10,18 @@ class RunFuncThread(QThread): run_finished = pyqtSignal(object) - def __init__(self, func): + def __init__(self, func) -> None: super().__init__() self.func = func - def run(self): + def run(self) -> None: self.run_finished.emit(self.func()) -if __name__ == '__main__': +if __name__ == "__main__": from PyQt5.QtCore import QCoreApplication + app = QCoreApplication([]) thread = RunFuncThread(func=lambda: 2 + 2 * 2) diff --git a/qt__pyqt__pyside__pyqode/SwitchButton__example/from_QAbstractButton/switch_button.py b/qt__pyqt__pyside__pyqode/SwitchButton__example/from_QAbstractButton/switch_button.py index 6b3498d72..7d3cbacd3 100644 --- a/qt__pyqt__pyside__pyqode/SwitchButton__example/from_QAbstractButton/switch_button.py +++ b/qt__pyqt__pyside__pyqode/SwitchButton__example/from_QAbstractButton/switch_button.py @@ -1,19 +1,25 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://stackoverflow.com/a/51825815/5909792 -from PyQt5.QtWidgets import QAbstractButton, QApplication, QHBoxLayout, QSizePolicy, QWidget +from PyQt5.QtWidgets import ( + QAbstractButton, + QApplication, + QHBoxLayout, + QSizePolicy, + QWidget, +) from PyQt5.QtCore import QPropertyAnimation, QRectF, QSize, Qt, pyqtProperty from PyQt5.QtGui import QPainter class Switch(QAbstractButton): - def __init__(self, parent=None, track_radius=10, thumb_radius=8): + def __init__(self, parent=None, track_radius=10, thumb_radius=8) -> None: super().__init__(parent=parent) self.setCheckable(True) self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) @@ -44,8 +50,8 @@ def __init__(self, parent=None, track_radius=10, thumb_radius=8): False: palette.dark().color(), } self._thumb_text = { - True: '', - False: '', + True: "", + False: "", } self._track_opacity = 0.5 else: @@ -62,8 +68,8 @@ def __init__(self, parent=None, track_radius=10, thumb_radius=8): False: palette.dark().color(), } self._thumb_text = { - True: '✔', - False: '✕', + True: "✔", + False: "✕", } self._track_opacity = 1 @@ -72,7 +78,7 @@ def offset(self): return self._offset @offset.setter - def offset(self, value): + def offset(self, value) -> None: self._offset = value self.update() @@ -82,15 +88,15 @@ def sizeHint(self): # pylint: disable=invalid-name 2 * self._track_radius + 2 * self._margin, ) - def setChecked(self, checked): + def setChecked(self, checked) -> None: super().setChecked(checked) self.offset = self._end_offset[checked]() - def resizeEvent(self, event): + def resizeEvent(self, event) -> None: super().resizeEvent(event) self.offset = self._end_offset[self.isChecked()]() - def paintEvent(self, event): # pylint: disable=invalid-name, unused-argument + def paintEvent(self, event) -> None: # pylint: disable=invalid-name, unused-argument p = QPainter(self) p.setRenderHint(QPainter.Antialiasing, True) p.setPen(Qt.NoPen) @@ -141,29 +147,29 @@ def paintEvent(self, event): # pylint: disable=invalid-name, unused-argument self._thumb_text[self.isChecked()], ) - def mouseReleaseEvent(self, event): # pylint: disable=invalid-name + def mouseReleaseEvent(self, event) -> None: # pylint: disable=invalid-name super().mouseReleaseEvent(event) if event.button() == Qt.LeftButton: - anim = QPropertyAnimation(self, b'offset', self) + anim = QPropertyAnimation(self, b"offset", self) anim.setDuration(120) anim.setStartValue(self.offset) anim.setEndValue(self._end_offset[self.isChecked()]()) anim.start() - def enterEvent(self, event): # pylint: disable=invalid-name + def enterEvent(self, event) -> None: # pylint: disable=invalid-name self.setCursor(Qt.PointingHandCursor) super().enterEvent(event) -def main(): +def main() -> None: app = QApplication([]) # Thumb size < track size (Gitlab style) s1 = Switch() - s1.toggled.connect(lambda c: print('toggled', c)) - s1.clicked.connect(lambda c: print('clicked', c)) - s1.pressed.connect(lambda: print('pressed')) - s1.released.connect(lambda: print('released')) + s1.toggled.connect(lambda c: print("toggled", c)) + s1.clicked.connect(lambda c: print("clicked", c)) + s1.pressed.connect(lambda: print("pressed")) + s1.released.connect(lambda: print("released")) s2 = Switch() s2.setEnabled(False) @@ -184,5 +190,5 @@ def main(): app.exec() -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/qt__pyqt__pyside__pyqode/SwitchButton__example/from_QAbstractButton/test.py b/qt__pyqt__pyside__pyqode/SwitchButton__example/from_QAbstractButton/test.py index e9a94844c..2a9dfecad 100644 --- a/qt__pyqt__pyside__pyqode/SwitchButton__example/from_QAbstractButton/test.py +++ b/qt__pyqt__pyside__pyqode/SwitchButton__example/from_QAbstractButton/test.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QLabel @@ -9,20 +9,24 @@ class MainWindow(QWidget): - def __init__(self): + def __init__(self) -> None: super().__init__() switch_btn1 = Switch() - label_switch_btn1 = QLabel(f'Checked: {switch_btn1.isChecked()}') - switch_btn1.clicked.connect(lambda checked: label_switch_btn1.setText( - f'Checked: {switch_btn1.isChecked()}' - )) + label_switch_btn1 = QLabel(f"Checked: {switch_btn1.isChecked()}") + switch_btn1.clicked.connect( + lambda checked: label_switch_btn1.setText( + f"Checked: {switch_btn1.isChecked()}" + ) + ) switch_btn2 = Switch() - label_switch_btn2 = QLabel(f'Checked: {switch_btn2.isChecked()}') - switch_btn2.clicked.connect(lambda checked: label_switch_btn2.setText( - f'Checked: {switch_btn2.isChecked()}' - )) + label_switch_btn2 = QLabel(f"Checked: {switch_btn2.isChecked()}") + switch_btn2.clicked.connect( + lambda checked: label_switch_btn2.setText( + f"Checked: {switch_btn2.isChecked()}" + ) + ) main_layout = QVBoxLayout(self) main_layout.addWidget(switch_btn1) @@ -32,7 +36,7 @@ def __init__(self): main_layout.addWidget(label_switch_btn2) -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) mw = MainWindow() diff --git a/qt__pyqt__pyside__pyqode/SwitchButton__example/from_QCheckBox/switch_button.py b/qt__pyqt__pyside__pyqode/SwitchButton__example/from_QCheckBox/switch_button.py index 601cdfa03..1db714f21 100644 --- a/qt__pyqt__pyside__pyqode/SwitchButton__example/from_QCheckBox/switch_button.py +++ b/qt__pyqt__pyside__pyqode/SwitchButton__example/from_QCheckBox/switch_button.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://ru.stackoverflow.com/a/1355594/201445 @@ -11,19 +11,26 @@ from PyQt5.QtWidgets import QCheckBox from PyQt5.QtGui import QPainter, QColor -from PyQt5.QtCore import Qt, QEasingCurve, QPropertyAnimation, pyqtProperty, QPoint, QRect +from PyQt5.QtCore import ( + Qt, + QEasingCurve, + QPropertyAnimation, + pyqtProperty, + QPoint, + QRect, +) class ToggleButton(QCheckBox): def __init__( - self, - width=70, - height=40, - bg_color="#777", - circle_color="#DDD", - active_color="#00BCff", - animation_curve=QEasingCurve.OutBounce, - ): + self, + width=70, + height=40, + bg_color="#777", + circle_color="#DDD", + active_color="#00BCff", + animation_curve=QEasingCurve.OutBounce, + ) -> None: super().__init__() self.setFixedSize(width, height) @@ -47,11 +54,11 @@ def circle_position(self) -> int: return self._circle_position @circle_position.setter - def circle_position(self, pos: int): + def circle_position(self, pos: int) -> None: self._circle_position = pos self.update() - def start_transition(self, value): + def start_transition(self, value) -> None: self.animation.setStartValue(self.circle_position) if value: self.animation.setEndValue(self.width() - self._circle_size) @@ -62,7 +69,7 @@ def start_transition(self, value): def hitButton(self, pos: QPoint) -> bool: return self.contentsRect().contains(pos) - def paintEvent(self, e): + def paintEvent(self, e) -> None: p = QPainter(self) p.setRenderHint(QPainter.Antialiasing) @@ -72,24 +79,25 @@ def paintEvent(self, e): p.setBrush(QColor(self._active_color if self.isChecked() else self._bg_color)) p.drawRoundedRect( - 0, 0, - rect.width(), - self.height(), - self.height() / 2, - self.height() / 2 + 0, 0, rect.width(), self.height(), self.height() / 2, self.height() / 2 ) p.setBrush(QColor(self._circle_color)) x, y = self._circle_position, self._circle_margin - p.drawEllipse(x, y, self._circle_size - self._circle_margin, self._circle_size - self._circle_margin) + p.drawEllipse( + x, + y, + self._circle_size - self._circle_margin, + self._circle_size - self._circle_margin, + ) -if __name__ == '__main__': +if __name__ == "__main__": from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout class MainWindow(QWidget): - def __init__(self): + def __init__(self) -> None: super().__init__() self.setWindowTitle("Анимация кнопки переключения") @@ -103,4 +111,3 @@ def __init__(self): w.resize(500, 500) w.show() sys.exit(app.exec_()) - diff --git a/qt__pyqt__pyside__pyqode/SwitchButton__example/from_QCheckBox/test.py b/qt__pyqt__pyside__pyqode/SwitchButton__example/from_QCheckBox/test.py index 230f36053..90a7515ac 100644 --- a/qt__pyqt__pyside__pyqode/SwitchButton__example/from_QCheckBox/test.py +++ b/qt__pyqt__pyside__pyqode/SwitchButton__example/from_QCheckBox/test.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QLabel @@ -9,20 +9,24 @@ class MainWindow(QWidget): - def __init__(self): + def __init__(self) -> None: super().__init__() switch_btn1 = ToggleButton() - label_switch_btn1 = QLabel(f'Checked: {switch_btn1.isChecked()}') - switch_btn1.clicked.connect(lambda checked: label_switch_btn1.setText( - f'Checked: {switch_btn1.isChecked()}' - )) + label_switch_btn1 = QLabel(f"Checked: {switch_btn1.isChecked()}") + switch_btn1.clicked.connect( + lambda checked: label_switch_btn1.setText( + f"Checked: {switch_btn1.isChecked()}" + ) + ) switch_btn2 = ToggleButton(width=200) - label_switch_btn2 = QLabel(f'Checked: {switch_btn2.isChecked()}') - switch_btn2.clicked.connect(lambda checked: label_switch_btn2.setText( - f'Checked: {switch_btn2.isChecked()}' - )) + label_switch_btn2 = QLabel(f"Checked: {switch_btn2.isChecked()}") + switch_btn2.clicked.connect( + lambda checked: label_switch_btn2.setText( + f"Checked: {switch_btn2.isChecked()}" + ) + ) main_layout = QVBoxLayout(self) main_layout.addWidget(switch_btn1) @@ -32,7 +36,7 @@ def __init__(self): main_layout.addWidget(label_switch_btn2) -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) mw = MainWindow() diff --git a/qt__pyqt__pyside__pyqode/SwitchButton__example/from_QWidget/switch_button.py b/qt__pyqt__pyside__pyqode/SwitchButton__example/from_QWidget/switch_button.py index 6d1433094..d48da89c2 100644 --- a/qt__pyqt__pyside__pyqode/SwitchButton__example/from_QWidget/switch_button.py +++ b/qt__pyqt__pyside__pyqode/SwitchButton__example/from_QWidget/switch_button.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://stackoverflow.com/a/51023362/5909792 @@ -13,23 +13,27 @@ class SwitchButton(QtWidgets.QWidget): clicked = QtCore.pyqtSignal(bool) - def __init__(self, parent=None, w1="Yes", l1=12, w2="No", l2=33, width=60): + def __init__(self, parent=None, w1="Yes", l1=12, w2="No", l2=33, width=60) -> None: super(SwitchButton, self).__init__(parent) self.setWindowFlags(QtCore.Qt.FramelessWindowHint) self.setAttribute(QtCore.Qt.WA_TranslucentBackground) self.__labeloff = QtWidgets.QLabel(self) self.__labeloff.setText(w2) - self.__labeloff.setStyleSheet("""color: rgb(120, 120, 120); font-weight: bold;""") - self.__background = Background(self) + self.__labeloff.setStyleSheet( + """color: rgb(120, 120, 120); font-weight: bold;""" + ) + self.__background = Background(self) self.__labelon = QtWidgets.QLabel(self) self.__labelon.setText(w1) - self.__labelon.setStyleSheet("""color: rgb(255, 255, 255); font-weight: bold;""") - self.__circle = Circle(self) - self.__circlemove = None + self.__labelon.setStyleSheet( + """color: rgb(255, 255, 255); font-weight: bold;""" + ) + self.__circle = Circle(self) + self.__circlemove = None self.__ellipsemove = None - self.__enabled = True - self.__duration = 100 - self.__value = False + self.__enabled = True + self.__duration = 100 + self.__value = False self.setFixedSize(width, 24) self.__background.resize(20, 20) @@ -44,10 +48,10 @@ def isChecked(self) -> bool: def valueText(self) -> str: return self.__labelon.text() if self.isChecked() else self.__labeloff.text() - def setDuration(self, time): + def setDuration(self, time) -> None: self.__duration = time - def mousePressEvent(self, event): + def mousePressEvent(self, event) -> None: if not self.__enabled: return @@ -58,16 +62,16 @@ def mousePressEvent(self, event): self.__ellipsemove.setDuration(self.__duration) xs = 2 - y = 2 - xf = self.width()-22 + y = 2 + xf = self.width() - 22 hback = 20 isize = QtCore.QSize(hback, hback) - bsize = QtCore.QSize(self.width()-4, hback) + bsize = QtCore.QSize(self.width() - 4, hback) if self.__value: xf = 2 - xs = self.width()-22 + xs = self.width() - 22 bsize = QtCore.QSize(hback, hback) - isize = QtCore.QSize(self.width()-4, hback) + isize = QtCore.QSize(self.width() - 4, hback) self.__circlemove.setStartValue(QtCore.QPoint(xs, y)) self.__circlemove.setEndValue(QtCore.QPoint(xf, y)) @@ -81,7 +85,7 @@ def mousePressEvent(self, event): self.clicked.emit(self.isChecked()) - def paintEvent(self, event): + def paintEvent(self, event) -> None: s = self.size() qp = QtGui.QPainter() qp.begin(self) @@ -96,7 +100,7 @@ def paintEvent(self, event): lg.setColorAt(0.82, QtGui.QColor(255, 255, 255, 255)) lg.setColorAt(1, QtGui.QColor(210, 210, 210, 255)) qp.setBrush(lg) - qp.drawRoundedRect(1, 1, s.width()-2, s.height()-2, 10, 10) + qp.drawRoundedRect(1, 1, s.width() - 2, s.height() - 2, 10, 10) qp.setBrush(QtGui.QColor(210, 210, 210)) qp.drawRoundedRect(2, 2, s.width() - 4, s.height() - 4, 10, 10) @@ -121,12 +125,12 @@ def paintEvent(self, event): class Circle(QtWidgets.QWidget): - def __init__(self, parent=None): + def __init__(self, parent=None) -> None: super(Circle, self).__init__(parent) self.__enabled = True self.setFixedSize(20, 20) - def paintEvent(self, event): + def paintEvent(self, event) -> None: s = self.size() qp = QtGui.QPainter() qp.begin(self) @@ -139,19 +143,19 @@ def paintEvent(self, event): rg.setColorAt(0.6, QtGui.QColor(255, 255, 255)) rg.setColorAt(1, QtGui.QColor(205, 205, 205)) qp.setBrush(QtGui.QBrush(rg)) - qp.drawEllipse(1,1, 18, 18) + qp.drawEllipse(1, 1, 18, 18) qp.setBrush(QtGui.QColor(210, 210, 210)) qp.drawEllipse(2, 2, 16, 16) if self.__enabled: - lg = QtGui.QLinearGradient(3, 18,20, 4) + lg = QtGui.QLinearGradient(3, 18, 20, 4) lg.setColorAt(0, QtGui.QColor(255, 255, 255, 255)) lg.setColorAt(0.55, QtGui.QColor(230, 230, 230, 255)) lg.setColorAt(0.72, QtGui.QColor(255, 255, 255, 255)) lg.setColorAt(1, QtGui.QColor(255, 255, 255, 255)) qp.setBrush(lg) - qp.drawEllipse(3,3, 14, 14) + qp.drawEllipse(3, 3, 14, 14) else: lg = QtGui.QLinearGradient(3, 18, 20, 4) lg.setColorAt(0, QtGui.QColor(230, 230, 230)) @@ -164,19 +168,19 @@ def paintEvent(self, event): class Background(QtWidgets.QWidget): - def __init__(self, parent=None): + def __init__(self, parent=None) -> None: super(Background, self).__init__(parent) self.__enabled = True self.setFixedHeight(20) - def paintEvent(self, event): + def paintEvent(self, event) -> None: s = self.size() qp = QtGui.QPainter() qp.begin(self) qp.setRenderHint(QtGui.QPainter.Antialiasing, True) pen = QtGui.QPen(QtCore.Qt.NoPen) qp.setPen(pen) - qp.setBrush(QtGui.QColor(154,205,50)) + qp.setBrush(QtGui.QColor(154, 205, 50)) if self.__enabled: qp.setBrush(QtGui.QColor(154, 190, 50)) qp.drawRoundedRect(0, 0, s.width(), s.height(), 10, 10) diff --git a/qt__pyqt__pyside__pyqode/SwitchButton__example/from_QWidget/test.py b/qt__pyqt__pyside__pyqode/SwitchButton__example/from_QWidget/test.py index 162297b55..33534f734 100644 --- a/qt__pyqt__pyside__pyqode/SwitchButton__example/from_QWidget/test.py +++ b/qt__pyqt__pyside__pyqode/SwitchButton__example/from_QWidget/test.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QLabel @@ -9,20 +9,28 @@ class MainWindow(QWidget): - def __init__(self): + def __init__(self) -> None: super().__init__() switch_btn1 = SwitchButton(self, "On", 15, "Off", 31, width=60) - label_switch_btn1 = QLabel(f'Checked: {switch_btn1.isChecked()}, text: {switch_btn1.valueText()}') - switch_btn1.clicked.connect(lambda checked: label_switch_btn1.setText( - f'Checked: {switch_btn1.isChecked()}, text: {switch_btn1.valueText()}' - )) + label_switch_btn1 = QLabel( + f"Checked: {switch_btn1.isChecked()}, text: {switch_btn1.valueText()}" + ) + switch_btn1.clicked.connect( + lambda checked: label_switch_btn1.setText( + f"Checked: {switch_btn1.isChecked()}, text: {switch_btn1.valueText()}" + ) + ) switch_btn2 = SwitchButton(self, "Вкл", 15, "Откл", 31, width=120) - label_switch_btn2 = QLabel(f'Checked: {switch_btn2.isChecked()}, text: {switch_btn2.valueText()}') - switch_btn2.clicked.connect(lambda checked: label_switch_btn2.setText( - f'Checked: {switch_btn2.isChecked()}, text: {switch_btn2.valueText()}' - )) + label_switch_btn2 = QLabel( + f"Checked: {switch_btn2.isChecked()}, text: {switch_btn2.valueText()}" + ) + switch_btn2.clicked.connect( + lambda checked: label_switch_btn2.setText( + f"Checked: {switch_btn2.isChecked()}, text: {switch_btn2.valueText()}" + ) + ) main_layout = QVBoxLayout(self) main_layout.addWidget(switch_btn1) @@ -32,7 +40,7 @@ def __init__(self): main_layout.addWidget(label_switch_btn2) -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) mw = MainWindow() diff --git a/qt__pyqt__pyside__pyqode/ToolBarArea/main.py b/qt__pyqt__pyside__pyqode/ToolBarArea/main.py index b1ed9c8a3..d7f4625c3 100644 --- a/qt__pyqt__pyside__pyqode/ToolBarArea/main.py +++ b/qt__pyqt__pyside__pyqode/ToolBarArea/main.py @@ -1,43 +1,43 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PyQt5 import Qt class Widget(Qt.QMainWindow): - def __init__(self): + def __init__(self) -> None: super().__init__() - self.setWindowTitle('ToolBarArea') + self.setWindowTitle("ToolBarArea") - self.toolbar = self.addToolBar('Panel2') - self.toolbar.addAction('1') - self.toolbar.addAction('2') - self.toolbar.addAction('3') + self.toolbar = self.addToolBar("Panel2") + self.toolbar.addAction("1") + self.toolbar.addAction("2") + self.toolbar.addAction("3") - self.toolbar2 = self.addToolBar('Panel1') + self.toolbar2 = self.addToolBar("Panel1") self.addToolBar(Qt.Qt.LeftToolBarArea, self.toolbar2) - self.toolbar2.addAction('a') - self.toolbar2.addAction('b') - self.toolbar2.addAction('c') + self.toolbar2.addAction("a") + self.toolbar2.addAction("b") + self.toolbar2.addAction("c") - self.toolbar3 = Qt.QToolBar('Panel3') + self.toolbar3 = Qt.QToolBar("Panel3") self.addToolBar(Qt.Qt.RightToolBarArea, self.toolbar3) - self.toolbar3.addAction('x') - self.toolbar3.addAction('y') - self.toolbar3.addAction('z') + self.toolbar3.addAction("x") + self.toolbar3.addAction("y") + self.toolbar3.addAction("z") - self.toolbar4 = Qt.QToolBar('Panel4') + self.toolbar4 = Qt.QToolBar("Panel4") self.addToolBar(Qt.Qt.BottomToolBarArea, self.toolbar4) - self.toolbar4.addAction('+') - self.toolbar4.addAction('-') - self.toolbar4.addAction('=') + self.toolbar4.addAction("+") + self.toolbar4.addAction("-") + self.toolbar4.addAction("=") -if __name__ == '__main__': +if __name__ == "__main__": app = Qt.QApplication([]) w = Widget() diff --git a/qt__pyqt__pyside__pyqode/URLView__download_and_show_image_on_label/async__QNetworkAccessManager.py b/qt__pyqt__pyside__pyqode/URLView__download_and_show_image_on_label/async__QNetworkAccessManager.py index 8e1c4a706..3a584fbc8 100644 --- a/qt__pyqt__pyside__pyqode/URLView__download_and_show_image_on_label/async__QNetworkAccessManager.py +++ b/qt__pyqt__pyside__pyqode/URLView__download_and_show_image_on_label/async__QNetworkAccessManager.py @@ -1,20 +1,20 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PyQt5 import Qt class URLView(Qt.QWidget): - def __init__(self, parent=None): + def __init__(self, parent=None) -> None: super().__init__(parent) layout = Qt.QVBoxLayout(self) self.urlEdit = Qt.QLineEdit() - self.urlEdit.setText('https://www.python.org/static/img/python-logo.png') + self.urlEdit.setText("https://www.python.org/static/img/python-logo.png") layout.addWidget(self.urlEdit) self.imageLabel = Qt.QLabel("No image") @@ -29,20 +29,20 @@ def __init__(self, parent=None): self.nam = Qt.QNetworkAccessManager() self.nam.finished.connect(self.finish_request) - def on_load(self): + def on_load(self) -> None: print("Load image") url = self.urlEdit.text() self.nam.get(Qt.QNetworkRequest(Qt.QUrl(url))) - def finish_request(self, reply): + def finish_request(self, reply) -> None: img = Qt.QPixmap() img.loadFromData(reply.readAll()) self.imageLabel.setPixmap(img) -if __name__ == '__main__': +if __name__ == "__main__": app = Qt.QApplication([]) w = URLView() w.show() diff --git a/qt__pyqt__pyside__pyqode/URLView__download_and_show_image_on_label/async__QThread.py b/qt__pyqt__pyside__pyqode/URLView__download_and_show_image_on_label/async__QThread.py index 6502cd07c..bf3431aad 100644 --- a/qt__pyqt__pyside__pyqode/URLView__download_and_show_image_on_label/async__QThread.py +++ b/qt__pyqt__pyside__pyqode/URLView__download_and_show_image_on_label/async__QThread.py @@ -1,17 +1,17 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -from PyQt5 import Qt from urllib import request +from PyQt5 import Qt class LoadImageThread(Qt.QThread): about_new_image = Qt.pyqtSignal(Qt.QPixmap) - def run(self): + def run(self) -> None: data = request.urlopen(self.url).read() pixmap = Qt.QPixmap() pixmap.loadFromData(data) @@ -21,13 +21,13 @@ def run(self): class URLView(Qt.QWidget): - def __init__(self, parent=None): + def __init__(self, parent=None) -> None: super().__init__(parent) layout = Qt.QVBoxLayout(self) self.urlEdit = Qt.QLineEdit() - self.urlEdit.setText('https://www.python.org/static/img/python-logo.png') + self.urlEdit.setText("https://www.python.org/static/img/python-logo.png") layout.addWidget(self.urlEdit) self.imageLabel = Qt.QLabel("No image") @@ -47,7 +47,7 @@ def __init__(self, parent=None): self.thread.started.connect(lambda: self.loadButton.setEnabled(False)) self.thread.finished.connect(lambda: self.loadButton.setEnabled(True)) - def on_load(self): + def on_load(self) -> None: print("Load image") # Запускаем поток @@ -55,7 +55,7 @@ def on_load(self): self.thread.start() -if __name__ == '__main__': +if __name__ == "__main__": app = Qt.QApplication([]) w = URLView() w.show() diff --git a/qt__pyqt__pyside__pyqode/URLView__download_and_show_image_on_label/sync.py b/qt__pyqt__pyside__pyqode/URLView__download_and_show_image_on_label/sync.py index 6547604f6..8c12217f3 100644 --- a/qt__pyqt__pyside__pyqode/URLView__download_and_show_image_on_label/sync.py +++ b/qt__pyqt__pyside__pyqode/URLView__download_and_show_image_on_label/sync.py @@ -1,22 +1,21 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -from PyQt5 import Qt from urllib import request +from PyQt5 import Qt class URLView(Qt.QWidget): - - def __init__(self, parent=None): + def __init__(self, parent=None) -> None: super().__init__(parent) layout = Qt.QVBoxLayout(self) self.urlEdit = Qt.QLineEdit() - self.urlEdit.setText('https://www.python.org/static/img/python-logo.png') + self.urlEdit.setText("https://www.python.org/static/img/python-logo.png") layout.addWidget(self.urlEdit) self.imageLabel = Qt.QLabel("No image") @@ -28,7 +27,7 @@ def __init__(self, parent=None): self.loadButton.clicked.connect(self.on_load) - def on_load(self): + def on_load(self) -> None: print("Load image") url = self.urlEdit.text() @@ -38,7 +37,7 @@ def on_load(self): self.imageLabel.setPixmap(pixmap) -if __name__ == '__main__': +if __name__ == "__main__": app = Qt.QApplication([]) w = URLView() w.show() diff --git a/qt__pyqt__pyside__pyqode/advanced_list_widget.py b/qt__pyqt__pyside__pyqode/advanced_list_widget.py new file mode 100644 index 000000000..4da454915 --- /dev/null +++ b/qt__pyqt__pyside__pyqode/advanced_list_widget.py @@ -0,0 +1,120 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from PyQt5.QtCore import Qt +from PyQt5.QtWidgets import QListWidgetItem, QListWidget, QWidget, QVBoxLayout, QToolBar + + +class AdvancedListWidget(QWidget): + def __init__(self) -> None: + super().__init__() + + self.list_widget = QListWidget() + self.list_widget.itemSelectionChanged.connect(self._update_states) + + tool_bar = QToolBar() + self.action_append = tool_bar.addAction("➕", self._append) + self.action_remove = tool_bar.addAction("➖", self._remove) + self.action_move_up = tool_bar.addAction("🔼", self._move_up) + self.action_move_down = tool_bar.addAction("🔽", self._move_down) + + main_layout = QVBoxLayout(self) + main_layout.setContentsMargins(0, 0, 0, 0) + main_layout.setSpacing(0) + main_layout.addWidget(tool_bar) + main_layout.addWidget(self.list_widget) + + self._update_states() + + def _update_states(self) -> None: + current_row: int = self.list_widget.currentRow() + + is_selected: bool = current_row != -1 + self.action_remove.setVisible(is_selected) + + self.action_move_up.setVisible(is_selected and current_row > 0) + self.action_move_down.setVisible(is_selected and current_row < self.count() - 1) + + def _append(self) -> None: + item = self.append("New item") + + self.list_widget.setCurrentItem(item) + self.list_widget.editItem(item) + + def _remove(self) -> None: + current_row: int = self.list_widget.currentRow() + if current_row == -1: + return + + self.list_widget.takeItem(current_row) + + def _move_item(self, from_row: int, to_row: int) -> None: + item = self.list_widget.takeItem(from_row) + self.list_widget.insertItem(to_row, item) + self.list_widget.setCurrentItem(item) + + def _move_up(self) -> None: + current_row: int = self.list_widget.currentRow() + if current_row < 1: + return + + self._move_item(current_row, current_row - 1) + + def _move_down(self) -> None: + current_row: int = self.list_widget.currentRow() + if current_row != -1 and current_row >= self.count() - 1: + return + + self._move_item(current_row, current_row + 1) + + def set_items(self, items: list[str]) -> None: + self.clear() + self.append_all(items) + + def append(self, text: str) -> QListWidgetItem: + item = QListWidgetItem(text) + item.setFlags(item.flags() | Qt.ItemIsEditable) + + self.list_widget.addItem(item) + + return item + + def append_all(self, items: list[str]) -> None: + for text in items: + self.append(text) + + def clear(self) -> None: + self.list_widget.clear() + + def count(self) -> int: + return self.list_widget.count() + + def get(self, i: int) -> str: + return self.list_widget.item(i).text() + + def items(self) -> list[str]: + return [self.get(i) for i in range(self.count())] + + +if __name__ == "__main__": + from PyQt5.QtWidgets import QApplication + + app = QApplication([]) + + mw = AdvancedListWidget() + mw.set_items( + [ + "FOO", + "BAR", + "GO", + "ABC", + "123", + "!!!", + ] + ) + mw.show() + + app.exec() diff --git a/qt__pyqt__pyside__pyqode/advanced_list_widget_pyqt6.py b/qt__pyqt__pyside__pyqode/advanced_list_widget_pyqt6.py new file mode 100644 index 000000000..75cee8a60 --- /dev/null +++ b/qt__pyqt__pyside__pyqode/advanced_list_widget_pyqt6.py @@ -0,0 +1,120 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from PyQt6.QtCore import Qt +from PyQt6.QtWidgets import QListWidgetItem, QListWidget, QWidget, QVBoxLayout, QToolBar + + +class AdvancedListWidget(QWidget): + def __init__(self): + super().__init__() + + self.list_widget = QListWidget() + self.list_widget.itemSelectionChanged.connect(self._update_states) + + tool_bar = QToolBar() + self.action_append = tool_bar.addAction("➕", self._append) + self.action_remove = tool_bar.addAction("➖", self._remove) + self.action_move_up = tool_bar.addAction("🔼", self._move_up) + self.action_move_down = tool_bar.addAction("🔽", self._move_down) + + main_layout = QVBoxLayout(self) + main_layout.setContentsMargins(0, 0, 0, 0) + main_layout.setSpacing(0) + main_layout.addWidget(tool_bar) + main_layout.addWidget(self.list_widget) + + self._update_states() + + def _update_states(self): + current_row: int = self.list_widget.currentRow() + + is_selected: bool = current_row != -1 + self.action_remove.setVisible(is_selected) + + self.action_move_up.setVisible(is_selected and current_row > 0) + self.action_move_down.setVisible(is_selected and current_row < self.count() - 1) + + def _append(self): + item = self.append("New item") + + self.list_widget.setCurrentItem(item) + self.list_widget.editItem(item) + + def _remove(self): + current_row: int = self.list_widget.currentRow() + if current_row == -1: + return + + self.list_widget.takeItem(current_row) + + def _move_item(self, from_row: int, to_row: int): + item = self.list_widget.takeItem(from_row) + self.list_widget.insertItem(to_row, item) + self.list_widget.setCurrentItem(item) + + def _move_up(self): + current_row: int = self.list_widget.currentRow() + if current_row < 1: + return + + self._move_item(current_row, current_row - 1) + + def _move_down(self): + current_row: int = self.list_widget.currentRow() + if current_row != -1 and current_row >= self.count() - 1: + return + + self._move_item(current_row, current_row + 1) + + def set_items(self, items: list[str]): + self.clear() + self.append_all(items) + + def append(self, text: str) -> QListWidgetItem: + item = QListWidgetItem(text) + item.setFlags(item.flags() | Qt.ItemFlag.ItemIsEditable) + + self.list_widget.addItem(item) + + return item + + def append_all(self, items: list[str]): + for text in items: + self.append(text) + + def clear(self): + return self.list_widget.clear() + + def count(self) -> int: + return self.list_widget.count() + + def get(self, i: int) -> str: + return self.list_widget.item(i).text() + + def items(self) -> list[str]: + return [self.get(i) for i in range(self.count())] + + +if __name__ == "__main__": + from PyQt6.QtWidgets import QApplication + + app = QApplication([]) + + mw = AdvancedListWidget() + mw.set_items( + [ + "FOO", + "BAR", + "GO", + "ABC", + "123", + "!!!", + ] + ) + mw.show() + + app.exec() diff --git a/qt__pyqt__pyside__pyqode/animate_move_and_rotate_rect_draw_dynamic_QPainter_QTimer.py b/qt__pyqt__pyside__pyqode/animate_move_and_rotate_rect_draw_dynamic_QPainter_QTimer.py index 414c6724f..dd7ca64ea 100644 --- a/qt__pyqt__pyside__pyqode/animate_move_and_rotate_rect_draw_dynamic_QPainter_QTimer.py +++ b/qt__pyqt__pyside__pyqode/animate_move_and_rotate_rect_draw_dynamic_QPainter_QTimer.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import math @@ -12,7 +12,7 @@ class MainWindow(QWidget): - def __init__(self): + def __init__(self) -> None: super().__init__() self.angle = 0 @@ -24,7 +24,7 @@ def __init__(self): self.timer.timeout.connect(self._do_tick) self.timer.start() - def _do_tick(self): + def _do_tick(self) -> None: self.angle += 5 self.x += 5 @@ -35,7 +35,7 @@ def _do_tick(self): # Перерисование окна self.update() - def paintEvent(self, event): + def paintEvent(self, event) -> None: painter = QPainter(self) painter.setRenderHint(QPainter.Antialiasing) @@ -48,7 +48,10 @@ def paintEvent(self, event): y = y0 + math.sin(x * frequency) * amplitude / 2 + amplitude / 2 triangle_points = [ - QPointF(0, 0), QPointF(0, 50), QPointF(50, 50), QPointF(50, 0) + QPointF(0, 0), + QPointF(0, 50), + QPointF(50, 50), + QPointF(50, 0), ] for p in triangle_points: p.setX(p.x() + x) @@ -67,10 +70,12 @@ def paintEvent(self, event): p1 = p2 painter.restore() - transform = QTransform() \ - .translate(center.x(), center.y())\ - .rotate(self.angle)\ + transform = ( + QTransform() + .translate(center.x(), center.y()) + .rotate(self.angle) .translate(-center.x(), -center.y()) + ) p = transform.map(polygon) @@ -78,7 +83,7 @@ def paintEvent(self, event): painter.drawPolygon(p) -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) mw = MainWindow() diff --git a/qt__pyqt__pyside__pyqode/animate_move_and_rotate_triangle_QPolygonF_QPainter_QTimer.py b/qt__pyqt__pyside__pyqode/animate_move_and_rotate_triangle_QPolygonF_QPainter_QTimer.py index 60aa71918..27e1c4c38 100644 --- a/qt__pyqt__pyside__pyqode/animate_move_and_rotate_triangle_QPolygonF_QPainter_QTimer.py +++ b/qt__pyqt__pyside__pyqode/animate_move_and_rotate_triangle_QPolygonF_QPainter_QTimer.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import math @@ -12,7 +12,7 @@ class MainWindow(QWidget): - def __init__(self): + def __init__(self) -> None: super().__init__() self.angle = 0 @@ -23,7 +23,7 @@ def __init__(self): self.timer.timeout.connect(self._do_tick) self.timer.start() - def _do_tick(self): + def _do_tick(self) -> None: self.angle += 5 self.x += 5 @@ -33,7 +33,7 @@ def _do_tick(self): # Перерисование окна self.update() - def paintEvent(self, event): + def paintEvent(self, event) -> None: painter = QPainter(self) painter.setRenderHint(QPainter.Antialiasing) @@ -58,9 +58,7 @@ def paintEvent(self, event): x = self.x y = y0 + math.sin(x * frequency) * amplitude / 2 + amplitude / 2 - triangle_points = [ - QPointF(0, 0), QPointF(0, 50), QPointF(50, 50) - ] + triangle_points = [QPointF(0, 0), QPointF(0, 50), QPointF(50, 50)] for p in triangle_points: p.setX(p.x() + x) p.setY(p.y() + y) @@ -68,16 +66,18 @@ def paintEvent(self, event): p = QPolygonF(triangle_points) center = p.boundingRect().center() - transform = QTransform() \ - .translate(center.x(), center.y())\ - .rotate(self.angle)\ + transform = ( + QTransform() + .translate(center.x(), center.y()) + .rotate(self.angle) .translate(-center.x(), -center.y()) + ) p = transform.map(p) painter.drawPolygon(p) -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) mw = MainWindow() diff --git a/qt__pyqt__pyside__pyqode/animate_move_and_rotate_triangle_draw_dynamic_QPolygonF_QPainter_QTimer.py b/qt__pyqt__pyside__pyqode/animate_move_and_rotate_triangle_draw_dynamic_QPolygonF_QPainter_QTimer.py index bb5d054b4..05f066d5e 100644 --- a/qt__pyqt__pyside__pyqode/animate_move_and_rotate_triangle_draw_dynamic_QPolygonF_QPainter_QTimer.py +++ b/qt__pyqt__pyside__pyqode/animate_move_and_rotate_triangle_draw_dynamic_QPolygonF_QPainter_QTimer.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import math @@ -12,7 +12,7 @@ class MainWindow(QWidget): - def __init__(self): + def __init__(self) -> None: super().__init__() self.angle = 0 @@ -24,7 +24,7 @@ def __init__(self): self.timer.timeout.connect(self._do_tick) self.timer.start() - def _do_tick(self): + def _do_tick(self) -> None: self.angle += 5 self.x += 5 @@ -35,7 +35,7 @@ def _do_tick(self): # Перерисование окна self.update() - def paintEvent(self, event): + def paintEvent(self, event) -> None: painter = QPainter(self) painter.setRenderHint(QPainter.Antialiasing) @@ -47,9 +47,7 @@ def paintEvent(self, event): x = self.x y = y0 + math.sin(x * frequency) * amplitude / 2 + amplitude / 2 - triangle_points = [ - QPointF(0, 0), QPointF(0, 50), QPointF(50, 50) - ] + triangle_points = [QPointF(0, 0), QPointF(0, 50), QPointF(50, 50)] for p in triangle_points: p.setX(p.x() + x) p.setY(p.y() + y) @@ -67,10 +65,12 @@ def paintEvent(self, event): p1 = p2 painter.restore() - transform = QTransform() \ - .translate(center.x(), center.y())\ - .rotate(self.angle)\ + transform = ( + QTransform() + .translate(center.x(), center.y()) + .rotate(self.angle) .translate(-center.x(), -center.y()) + ) p = transform.map(polygon) @@ -78,7 +78,7 @@ def paintEvent(self, event): painter.drawPolygon(p) -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) mw = MainWindow() diff --git a/qt__pyqt__pyside__pyqode/animate_rotate_triangle_QPainterPath_QPainter_QTimer.py b/qt__pyqt__pyside__pyqode/animate_rotate_triangle_QPainterPath_QPainter_QTimer.py index 05aa6c47f..38ad62cf4 100644 --- a/qt__pyqt__pyside__pyqode/animate_rotate_triangle_QPainterPath_QPainter_QTimer.py +++ b/qt__pyqt__pyside__pyqode/animate_rotate_triangle_QPainterPath_QPainter_QTimer.py @@ -1,16 +1,16 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PyQt5.QtWidgets import QApplication, QWidget -from PyQt5.QtGui import QPainter, QPainterPath, QTransform, QPolygonF +from PyQt5.QtGui import QPainter, QPainterPath, QTransform from PyQt5.QtCore import QPointF, Qt, QRectF, QTimer class MainWindow(QWidget): - def __init__(self): + def __init__(self) -> None: super().__init__() self.angle = 0 @@ -20,13 +20,13 @@ def __init__(self): self.timer.timeout.connect(self._do_tick) self.timer.start() - def _do_tick(self): + def _do_tick(self) -> None: self.angle += 5 # Перерисования окна self.update() - def paintEvent(self, event): + def paintEvent(self, event) -> None: painter = QPainter(self) painter.setRenderHint(QPainter.Antialiasing) @@ -46,17 +46,19 @@ def paintEvent(self, event): path.setElementPositionAt(i, point.x(), point.y()) center = rect.center() - transform = QTransform()\ - .translate(center.x() + x, center.y() + y)\ - .rotate(self.angle)\ + transform = ( + QTransform() + .translate(center.x() + x, center.y() + y) + .rotate(self.angle) .translate(-center.x() - x, -center.y() - y) + ) path = transform.map(path) painter.fillPath(path, Qt.blue) -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) mw = MainWindow() diff --git a/qt__pyqt__pyside__pyqode/animate_rotate_triangle_QPolygonF_QPainter_QTimer.py b/qt__pyqt__pyside__pyqode/animate_rotate_triangle_QPolygonF_QPainter_QTimer.py index 476b3aa89..5749c31fc 100644 --- a/qt__pyqt__pyside__pyqode/animate_rotate_triangle_QPolygonF_QPainter_QTimer.py +++ b/qt__pyqt__pyside__pyqode/animate_rotate_triangle_QPolygonF_QPainter_QTimer.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PyQt5.QtWidgets import QApplication, QWidget @@ -10,7 +10,7 @@ class MainWindow(QWidget): - def __init__(self): + def __init__(self) -> None: super().__init__() self.angle = 0 @@ -20,13 +20,13 @@ def __init__(self): self.timer.timeout.connect(self._do_tick) self.timer.start() - def _do_tick(self): + def _do_tick(self) -> None: self.angle += 5 # Перерисования окна self.update() - def paintEvent(self, event): + def paintEvent(self, event) -> None: painter = QPainter(self) painter.setRenderHint(QPainter.Antialiasing) @@ -34,9 +34,7 @@ def paintEvent(self, event): painter.setBrush(Qt.blue) - triangle_points = [ - QPointF(0, 0), QPointF(0, 50), QPointF(50, 50) - ] + triangle_points = [QPointF(0, 0), QPointF(0, 50), QPointF(50, 50)] for p in triangle_points: p.setX(p.x() + x) p.setY(p.y() + y) @@ -44,16 +42,18 @@ def paintEvent(self, event): p = QPolygonF(triangle_points) center = p.boundingRect().center() - transform = QTransform() \ - .translate(center.x(), center.y())\ - .rotate(self.angle)\ + transform = ( + QTransform() + .translate(center.x(), center.y()) + .rotate(self.angle) .translate(-center.x(), -center.y()) + ) p = transform.map(p) painter.drawPolygon(p) -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) mw = MainWindow() diff --git a/qt__pyqt__pyside__pyqode/append_custom_QStatusBar__with_icon/main.py b/qt__pyqt__pyside__pyqode/append_custom_QStatusBar__with_icon/main.py index 9efdfca20..43224f8e8 100644 --- a/qt__pyqt__pyside__pyqode/append_custom_QStatusBar__with_icon/main.py +++ b/qt__pyqt__pyside__pyqode/append_custom_QStatusBar__with_icon/main.py @@ -1,21 +1,21 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PyQt5 import Qt class Widget(Qt.QWidget): - def __init__(self): + def __init__(self) -> None: super().__init__() self.indicator = Qt.QLabel() self.indicator.hide() self.indicator.setFixedSize(32, 32) self.indicator.setScaledContents(True) - self.indicator.setPixmap(Qt.QPixmap('light-bulb-icon_34400.png')) + self.indicator.setPixmap(Qt.QPixmap("light-bulb-icon_34400.png")) self.line_edit = Qt.QLineEdit() self.line_edit.textEdited.connect(self._on_text_edited) @@ -25,14 +25,17 @@ def __init__(self): self.status_bar.setSizeGripEnabled(False) # Убираем снизу-справа уголок main_layout = Qt.QVBoxLayout() - main_layout.setContentsMargins(5, 5, 5, 0) # Сверху, слева и справа отступ 5, внизу его нет + # Сверху, слева и справа отступ 5, внизу его нет + main_layout.setContentsMargins( + 5, 5, 5, 0 + ) main_layout.addWidget(self.line_edit) main_layout.addStretch() main_layout.addWidget(self.status_bar) self.setLayout(main_layout) - def _on_text_edited(self, text): + def _on_text_edited(self, text) -> None: # Показываем сообщение на 2 секунды self.status_bar.showMessage(text, msecs=2000) @@ -43,7 +46,7 @@ def _on_text_edited(self, text): Qt.QTimer.singleShot(2000, self.indicator.hide) -if __name__ == '__main__': +if __name__ == "__main__": app = Qt.QApplication([]) w = Widget() diff --git a/qt__pyqt__pyside__pyqode/autowidth__QLineEdit.py b/qt__pyqt__pyside__pyqode/autowidth__QLineEdit.py index 77eb3a201..cda5582f3 100644 --- a/qt__pyqt__pyside__pyqode/autowidth__QLineEdit.py +++ b/qt__pyqt__pyside__pyqode/autowidth__QLineEdit.py @@ -1,14 +1,14 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PyQt5 import Qt class Widget(Qt.QWidget): - def __init__(self): + def __init__(self) -> None: super().__init__() self.line_edit = Qt.QLineEdit() @@ -19,13 +19,13 @@ def __init__(self): self.setLayout(layout) - def on_text_changed(self, text): + def on_text_changed(self, text) -> None: # Рассчитываем ширину текст по шрифту width = self.line_edit.fontMetrics().width(text) self.line_edit.setMinimumWidth(width) -if __name__ == '__main__': +if __name__ == "__main__": app = Qt.QApplication([]) mw = Widget() diff --git a/qt__pyqt__pyside__pyqode/bar_chart__QtChart.py b/qt__pyqt__pyside__pyqode/bar_chart__QtChart.py index 609467bc7..064c10a6a 100644 --- a/qt__pyqt__pyside__pyqode/bar_chart__QtChart.py +++ b/qt__pyqt__pyside__pyqode/bar_chart__QtChart.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import random @@ -12,7 +12,7 @@ class MainWidow(QMainWindow): - def __init__(self): + def __init__(self) -> None: super().__init__() series = self.append_series() @@ -48,6 +48,7 @@ def append_series(self): if __name__ == "__main__": import sys + app = QApplication(sys.argv) mw = MainWidow() diff --git a/qt__pyqt__pyside__pyqode/buttons_setShortcut.py b/qt__pyqt__pyside__pyqode/buttons_setShortcut.py index 458a5746c..d2150a9cc 100644 --- a/qt__pyqt__pyside__pyqode/buttons_setShortcut.py +++ b/qt__pyqt__pyside__pyqode/buttons_setShortcut.py @@ -1,23 +1,23 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PyQt5.QtWidgets import QApplication, QWidget, QHBoxLayout, QPushButton, QLabel class MainWindow(QWidget): - def __init__(self): + def __init__(self) -> None: super().__init__() label_result = QLabel() - button_left = QPushButton('Left') + button_left = QPushButton("Left") button_left.setShortcut("Ctrl+Alt+Left") button_left.clicked.connect(lambda: label_result.setText("left")) - button_right = QPushButton('Right') + button_right = QPushButton("Right") button_right.setShortcut("Ctrl+Alt+Right") button_right.clicked.connect(lambda: label_result.setText("right")) @@ -27,7 +27,7 @@ def __init__(self): main_layout.addWidget(button_right) -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) mw = MainWindow() diff --git a/qt__pyqt__pyside__pyqode/chart_line__QtChart.py b/qt__pyqt__pyside__pyqode/chart_line__QtChart.py index 020e2e975..1035e2e11 100644 --- a/qt__pyqt__pyside__pyqode/chart_line__QtChart.py +++ b/qt__pyqt__pyside__pyqode/chart_line__QtChart.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PyQt5.QtWidgets import QApplication, QMainWindow @@ -10,7 +10,7 @@ class MainWindow(QMainWindow): - def __init__(self): + def __init__(self) -> None: super().__init__() series = QLineSeries() @@ -34,7 +34,7 @@ def __init__(self): self.setCentralWidget(chart_view) -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) mw = MainWindow() diff --git a/qt__pyqt__pyside__pyqode/chart_line__dark_theme__QtChart.py b/qt__pyqt__pyside__pyqode/chart_line__dark_theme__QtChart.py index 666007d6f..62e431635 100644 --- a/qt__pyqt__pyside__pyqode/chart_line__dark_theme__QtChart.py +++ b/qt__pyqt__pyside__pyqode/chart_line__dark_theme__QtChart.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PyQt5.QtWidgets import QApplication, QMainWindow @@ -10,7 +10,7 @@ class MainWindow(QMainWindow): - def __init__(self): + def __init__(self) -> None: super().__init__() series = QLineSeries() @@ -35,7 +35,7 @@ def __init__(self): self.setCentralWidget(chart_view) -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) mw = MainWindow() diff --git a/qt__pyqt__pyside__pyqode/chart_line__datetime_axis__using_QDateTime__QtChart.py b/qt__pyqt__pyside__pyqode/chart_line__datetime_axis__using_QDateTime__QtChart.py index 837eb065e..0c5545dcd 100644 --- a/qt__pyqt__pyside__pyqode/chart_line__datetime_axis__using_QDateTime__QtChart.py +++ b/qt__pyqt__pyside__pyqode/chart_line__datetime_axis__using_QDateTime__QtChart.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://doc.qt.io/qt-5/qtcharts-datetimeaxis-example.html @@ -10,7 +10,6 @@ from PyQt5.QtWidgets import QApplication, QMainWindow from PyQt5.QtGui import QPainter from PyQt5.QtChart import QChart, QChartView, QLineSeries, QDateTimeAxis, QValueAxis - from PyQt5.QtCore import QDateTime, Qt @@ -25,7 +24,7 @@ class MainWindow(QMainWindow): - def __init__(self): + def __init__(self) -> None: super().__init__() series = QLineSeries() @@ -43,12 +42,12 @@ def __init__(self): axisX = QDateTimeAxis() axisX.setFormat("dd/MM/yyyy") - axisX.setTitleText('Date') + axisX.setTitleText("Date") chart.addAxis(axisX, Qt.AlignBottom) series.attachAxis(axisX) axisY = QValueAxis() - axisY.setTitleText('Value') + axisY.setTitleText("Value") chart.addAxis(axisY, Qt.AlignLeft) series.attachAxis(axisY) @@ -59,7 +58,7 @@ def __init__(self): self.setCentralWidget(chart_view) -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) mw = MainWindow() diff --git a/qt__pyqt__pyside__pyqode/chart_line__datetime_axis__using_datetime__QtChart.py b/qt__pyqt__pyside__pyqode/chart_line__datetime_axis__using_datetime__QtChart.py index f8aa9f212..e29a2278e 100644 --- a/qt__pyqt__pyside__pyqode/chart_line__datetime_axis__using_datetime__QtChart.py +++ b/qt__pyqt__pyside__pyqode/chart_line__datetime_axis__using_datetime__QtChart.py @@ -1,19 +1,17 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://doc.qt.io/qt-5/qtcharts-datetimeaxis-example.html -import datetime as DT - +import datetime as dt from PyQt5.QtWidgets import QApplication, QMainWindow from PyQt5.QtGui import QPainter from PyQt5.QtChart import QChart, QChartView, QLineSeries, QDateTimeAxis, QValueAxis - from PyQt5.QtCore import Qt @@ -28,13 +26,13 @@ class MainWindow(QMainWindow): - def __init__(self): + def __init__(self) -> None: super().__init__() series = QLineSeries() for date, value in DATA: - date_value = DT.datetime(*date).timestamp() * 1000 + date_value = dt.datetime(*date).timestamp() * 1000 series.append(date_value, value) chart = QChart() @@ -46,13 +44,13 @@ def __init__(self): axisX = QDateTimeAxis() axisX.setFormat("dd/MM/yyyy") - axisX.setTitleText('Date') + axisX.setTitleText("Date") # axisX.setTickCount(10) chart.addAxis(axisX, Qt.AlignBottom) series.attachAxis(axisX) axisY = QValueAxis() - axisY.setTitleText('Value') + axisY.setTitleText("Value") chart.addAxis(axisY, Qt.AlignLeft) series.attachAxis(axisY) @@ -63,7 +61,7 @@ def __init__(self): self.setCentralWidget(chart_view) -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) mw = MainWindow() diff --git a/qt__pyqt__pyside__pyqode/chart_line__no_margin__QtChart.py b/qt__pyqt__pyside__pyqode/chart_line__no_margin__QtChart.py index 9c9219212..59aa609b7 100644 --- a/qt__pyqt__pyside__pyqode/chart_line__no_margin__QtChart.py +++ b/qt__pyqt__pyside__pyqode/chart_line__no_margin__QtChart.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PyQt5.QtWidgets import QApplication @@ -31,7 +31,7 @@ def create_QChartView() -> QChartView: return chart_view -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) mw = create_QChartView() diff --git a/qt__pyqt__pyside__pyqode/chart_line__show_all_theme__QtChart.py b/qt__pyqt__pyside__pyqode/chart_line__show_all_theme__QtChart.py index da5c51836..906164059 100644 --- a/qt__pyqt__pyside__pyqode/chart_line__show_all_theme__QtChart.py +++ b/qt__pyqt__pyside__pyqode/chart_line__show_all_theme__QtChart.py @@ -1,18 +1,16 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -from typing import List, Tuple - from PyQt5.QtWidgets import QApplication, QMainWindow, QTabWidget from PyQt5.QtGui import QPainter from PyQt5.QtChart import QChart, QChartView, QLineSeries -def get_themes() -> List[Tuple[str, QChart.ChartTheme]]: - NAME = 'ChartTheme' +def get_themes() -> list[tuple[str, QChart.ChartTheme]]: + NAME = "ChartTheme" themes = [] for theme_name in dir(QChart): @@ -20,7 +18,7 @@ def get_themes() -> List[Tuple[str, QChart.ChartTheme]]: continue theme = getattr(QChart, theme_name) - theme_name = theme_name.replace(NAME, '') + theme_name = theme_name.replace(NAME, "") themes.append((theme_name, theme)) @@ -29,13 +27,13 @@ def get_themes() -> List[Tuple[str, QChart.ChartTheme]]: class MainWindow(QMainWindow): - def __init__(self): + def __init__(self) -> None: super().__init__() tab_widget = QTabWidget() chart_view = self.create_QChartView() - tab_widget.addTab(chart_view, '') + tab_widget.addTab(chart_view, "") for name, theme in get_themes(): chart_view = self.create_QChartView() @@ -67,7 +65,7 @@ def create_QChartView(self) -> QChartView: return chart_view -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) mw = MainWindow() diff --git a/qt__pyqt__pyside__pyqode/chart_line__show_point_marks__QtChart.py b/qt__pyqt__pyside__pyqode/chart_line__show_point_marks__QtChart.py index b8f0f56a9..75a63f16a 100644 --- a/qt__pyqt__pyside__pyqode/chart_line__show_point_marks__QtChart.py +++ b/qt__pyqt__pyside__pyqode/chart_line__show_point_marks__QtChart.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PyQt5.QtWidgets import QApplication, QMainWindow @@ -10,7 +10,7 @@ class MainWindow(QMainWindow): - def __init__(self): + def __init__(self) -> None: super().__init__() series = QLineSeries() @@ -37,7 +37,7 @@ def __init__(self): self.setCentralWidget(chart_view) -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) mw = MainWindow() diff --git a/qt__pyqt__pyside__pyqode/chart_line__show_tooltip_on_point_marks__QtChart.py b/qt__pyqt__pyside__pyqode/chart_line__show_tooltip_on_point_marks__QtChart.py index 81277edde..ed1edad19 100644 --- a/qt__pyqt__pyside__pyqode/chart_line__show_tooltip_on_point_marks__QtChart.py +++ b/qt__pyqt__pyside__pyqode/chart_line__show_tooltip_on_point_marks__QtChart.py @@ -1,21 +1,19 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import math from PyQt5.QtWidgets import QApplication -from PyQt5.QtGui import QPainter -from PyQt5.QtCore import QRectF, QPoint, QSizeF -from PyQt5.QtChart import QChart, QChartView, QLineSeries +from PyQt5.QtChart import QChart, QLineSeries from chart_line__show_tooltip_on_series__QtChart import Callout, ChartViewToolTips class MainWindow(ChartViewToolTips): - def __init__(self): + def __init__(self) -> None: super().__init__() series = QLineSeries() @@ -40,7 +38,7 @@ def __init__(self): self.setChart(self._chart) - def show_series_tooltip(self, point, state: bool): + def show_series_tooltip(self, point, state: bool) -> None: if not self._tooltip: self._tooltip = Callout(self._chart) @@ -53,7 +51,7 @@ def show_series_tooltip(self, point, state: bool): + (p.y() - point.y()) * (p.y() - point.y()) ) if current_distance < distance: - self._tooltip.setText("X: {}\nY: {}".format(p.x(), p.y())) + self._tooltip.setText(f"X: {p.x()}\nY: {p.y()}") self._tooltip.setAnchor(p) self._tooltip.setZValue(11) self._tooltip.updateGeometry() @@ -62,7 +60,7 @@ def show_series_tooltip(self, point, state: bool): self._tooltip.hide() -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) mw = MainWindow() diff --git a/qt__pyqt__pyside__pyqode/chart_line__show_tooltip_on_point_marks__check_distance_by_pos__QtChart.py b/qt__pyqt__pyside__pyqode/chart_line__show_tooltip_on_point_marks__check_distance_by_pos__QtChart.py index fd900bfa7..81aa59b79 100644 --- a/qt__pyqt__pyside__pyqode/chart_line__show_tooltip_on_point_marks__check_distance_by_pos__QtChart.py +++ b/qt__pyqt__pyside__pyqode/chart_line__show_tooltip_on_point_marks__check_distance_by_pos__QtChart.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import math @@ -13,7 +13,7 @@ class MainWindow(ChartViewToolTips): - def __init__(self): + def __init__(self) -> None: super().__init__() series = QLineSeries() @@ -38,7 +38,7 @@ def __init__(self): self.setChart(self._chart) - def show_series_tooltip(self, point, state: bool): + def show_series_tooltip(self, point, state: bool) -> None: # value -> pos point = self._chart.mapToPosition(point) @@ -56,7 +56,7 @@ def show_series_tooltip(self, point, state: bool): + (p.y() - point.y()) * (p.y() - point.y()) ) if current_distance < distance: - self._tooltip.setText("X: {}\nY: {}".format(p.x(), p.y())) + self._tooltip.setText(f"X: {p.x()}\nY: {p.y()}") self._tooltip.setAnchor(p_value) self._tooltip.setZValue(11) self._tooltip.updateGeometry() @@ -65,7 +65,7 @@ def show_series_tooltip(self, point, state: bool): self._tooltip.hide() -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) mw = MainWindow() diff --git a/qt__pyqt__pyside__pyqode/chart_line__show_tooltip_on_series__QtChart.py b/qt__pyqt__pyside__pyqode/chart_line__show_tooltip_on_series__QtChart.py index d31d99f11..9faf17c25 100644 --- a/qt__pyqt__pyside__pyqode/chart_line__show_tooltip_on_series__QtChart.py +++ b/qt__pyqt__pyside__pyqode/chart_line__show_tooltip_on_series__QtChart.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PyQt5.QtWidgets import QApplication, QGraphicsItem, QGraphicsSceneMouseEvent @@ -12,7 +12,7 @@ # SOURCE: https://doc-snapshots.qt.io/qt5-5.12/qtcharts-callout-callout-h.html class Callout(QGraphicsItem): - def __init__(self, chart: QChart, parent=None): + def __init__(self, chart: QChart, parent=None) -> None: super().__init__(parent) self.hide() @@ -20,26 +20,26 @@ def __init__(self, chart: QChart, parent=None): self._chart = chart self._chart.scene().addItem(self) - self._text = '' + self._text = "" self._textRect = QRectF() self._rect = QRectF() self._anchor = QPointF() self._font = QFont() - def setText(self, text: str): + def setText(self, text: str) -> None: self._text = text metrics = QFontMetrics(self._font) - self._textRect = QRectF(metrics.boundingRect( - QRect(0, 0, 150, 150), Qt.AlignLeft, self._text - )) + self._textRect = QRectF( + metrics.boundingRect(QRect(0, 0, 150, 150), Qt.AlignLeft, self._text) + ) self._textRect.translate(5, 5) self.prepareGeometryChange() self._rect = QRectF(self._textRect.adjusted(-5, -5, 5, 5)) - def setAnchor(self, point: QPointF): + def setAnchor(self, point: QPointF) -> None: self._anchor = point - def updateGeometry(self): + def updateGeometry(self) -> None: self.prepareGeometryChange() self.setPos(self._chart.mapToPosition(self._anchor) + QPoint(10, -50)) @@ -70,42 +70,77 @@ def boundingRect(self): rect.setBottom(max(self._rect.bottom(), anchor.y())) return rect - def paint(self, painter: QPainter, option, widget=None): + def paint(self, painter: QPainter, option, widget=None) -> None: path = QPainterPath() path.addRoundedRect(self._rect, 5, 5) - + anchor = self.mapFromParent(self._chart.mapToPosition(self._anchor)) if not self._rect.contains(anchor): point1 = QPointF() point2 = QPointF() - + # establish the position of the anchor point in relation to self._rect above = anchor.y() <= self._rect.top() - aboveCenter = anchor.y() > self._rect.top() and anchor.y() <= self._rect.center().y() - belowCenter = anchor.y() > self._rect.center().y() and anchor.y() <= self._rect.bottom() + aboveCenter = ( + anchor.y() > self._rect.top() and anchor.y() <= self._rect.center().y() + ) + belowCenter = ( + anchor.y() > self._rect.center().y() + and anchor.y() <= self._rect.bottom() + ) below = anchor.y() > self._rect.bottom() - + onLeft = anchor.x() <= self._rect.left() - leftOfCenter = anchor.x() > self._rect.left() and anchor.x() <= self._rect.center().x() - rightOfCenter = anchor.x() > self._rect.center().x() and anchor.x() <= self._rect.right() + leftOfCenter = ( + anchor.x() > self._rect.left() and anchor.x() <= self._rect.center().x() + ) + rightOfCenter = ( + anchor.x() > self._rect.center().x() + and anchor.x() <= self._rect.right() + ) onRight = anchor.x() > self._rect.right() - + # get the nearest self._rect corner. x = (onRight + rightOfCenter) * self._rect.width() y = (below + belowCenter) * self._rect.height() - cornerCase = (above and onLeft) or (above and onRight) or (below and onLeft) or (below and onRight) + cornerCase = ( + (above and onLeft) + or (above and onRight) + or (below and onLeft) + or (below and onRight) + ) vertical = abs(anchor.x() - x) > abs(anchor.y() - y) - - x1 = x + leftOfCenter * 10 - rightOfCenter * 20 + cornerCase * (not vertical) * (onLeft * 10 - onRight * 20) - y1 = y + aboveCenter * 10 - belowCenter * 20 + cornerCase * vertical * (above * 10 - below * 20) + + x1 = ( + x + + leftOfCenter * 10 + - rightOfCenter * 20 + + cornerCase * (not vertical) * (onLeft * 10 - onRight * 20) + ) + y1 = ( + y + + aboveCenter * 10 + - belowCenter * 20 + + cornerCase * vertical * (above * 10 - below * 20) + ) point1.setX(x1) point1.setY(y1) - - x2 = x + leftOfCenter * 20 - rightOfCenter * 10 + cornerCase * (not vertical) * (onLeft * 20 - onRight * 10) - y2 = y + aboveCenter * 20 - belowCenter * 10 + cornerCase * vertical * (above * 20 - below * 10) + + x2 = ( + x + + leftOfCenter * 20 + - rightOfCenter * 10 + + cornerCase * (not vertical) * (onLeft * 20 - onRight * 10) + ) + y2 = ( + y + + aboveCenter * 20 + - belowCenter * 10 + + cornerCase * vertical * (above * 20 - below * 10) + ) point2.setX(x2) point2.setY(y2) - + path.moveTo(point1) path.lineTo(anchor) path.lineTo(point2) @@ -116,19 +151,21 @@ def paint(self, painter: QPainter, option, widget=None): painter.drawPath(path) painter.drawText(self._textRect, self._text) - def mousePressEvent(self, event: QGraphicsSceneMouseEvent): + def mousePressEvent(self, event: QGraphicsSceneMouseEvent) -> None: event.setAccepted(True) - def mouseMoveEvent(self, event: QGraphicsSceneMouseEvent): + def mouseMoveEvent(self, event: QGraphicsSceneMouseEvent) -> None: if event.buttons() & Qt.LeftButton: - self.setPos(self.mapToParent(event.pos() - event.buttonDownPos(Qt.LeftButton))) + self.setPos( + self.mapToParent(event.pos() - event.buttonDownPos(Qt.LeftButton)) + ) event.setAccepted(True) else: event.setAccepted(False) class ChartViewToolTips(QChartView): - def __init__(self): + def __init__(self) -> None: super().__init__() self.setRenderHint(QPainter.Antialiasing) @@ -137,7 +174,7 @@ def __init__(self): self._callout_font_family = None self._callouts = [] - def clear_all_tooltips(self): + def clear_all_tooltips(self) -> None: if self._tooltip: self.scene().removeItem(self._tooltip) @@ -152,7 +189,7 @@ def _add_Callout(self) -> Callout: return callout - def show_series_tooltip(self, point, state: bool): + def show_series_tooltip(self, point, state: bool) -> None: if not self.chart(): return @@ -160,7 +197,7 @@ def show_series_tooltip(self, point, state: bool): self._tooltip = self._add_Callout() if state: - self._tooltip.setText("X: {} \nY: {}".format(point.x(), point.y())) + self._tooltip.setText(f"X: {point.x()} \nY: {point.y()}") self._tooltip.setAnchor(point) self._tooltip.setZValue(11) self._tooltip.updateGeometry() @@ -168,7 +205,7 @@ def show_series_tooltip(self, point, state: bool): else: self._tooltip.hide() - def keepCallout(self, point): + def keepCallout(self, point) -> None: if not self.chart() or not self._tooltip: return @@ -177,7 +214,7 @@ def keepCallout(self, point): self._tooltip = self._add_Callout() - def mouseReleaseEvent(self, event): + def mouseReleaseEvent(self, event) -> None: if self.chart(): pos = event.pos() point = self.chart().mapToValue(pos) @@ -185,7 +222,7 @@ def mouseReleaseEvent(self, event): super().mouseReleaseEvent(event) - def resizeEvent(self, event): + def resizeEvent(self, event) -> None: if self.scene(): size = QSizeF(event.size()) @@ -199,7 +236,7 @@ def resizeEvent(self, event): class MainWindow(ChartViewToolTips): - def __init__(self): + def __init__(self) -> None: super().__init__() series = QLineSeries() @@ -225,7 +262,7 @@ def __init__(self): self.setChart(self._chart) -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) mw = MainWindow() diff --git a/qt__pyqt__pyside__pyqode/check_image_on_transparents/main.py b/qt__pyqt__pyside__pyqode/check_image_on_transparents/main.py index 473101a01..c79b07e5c 100644 --- a/qt__pyqt__pyside__pyqode/check_image_on_transparents/main.py +++ b/qt__pyqt__pyside__pyqode/check_image_on_transparents/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PyQt5.QtWidgets import QApplication, QWidget @@ -9,17 +9,17 @@ from PyQt5.QtCore import Qt, QRect -FILE_NAME = 'emoji-video-game-512x512.png' +FILE_NAME = "emoji-video-game-512x512.png" class Widget(QWidget): - def __init__(self): + def __init__(self) -> None: super().__init__() self.img = QPixmap() self.img.load(FILE_NAME) - def paintEvent(self, event): + def paintEvent(self, event) -> None: painter = QPainter(self) painter.setBrush(Qt.darkGreen) @@ -32,7 +32,7 @@ def paintEvent(self, event): painter.drawPixmap(img_rect.topLeft(), self.img) -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) mw = Widget() diff --git a/qt__pyqt__pyside__pyqode/check_urls.py b/qt__pyqt__pyside__pyqode/check_urls.py index 73515889f..28ad3b224 100644 --- a/qt__pyqt__pyside__pyqode/check_urls.py +++ b/qt__pyqt__pyside__pyqode/check_urls.py @@ -1,29 +1,35 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import traceback import sys -from typing import List +import requests from PyQt5.QtWidgets import ( - QApplication, QMainWindow, QTableWidget, QTableWidgetItem, QMessageBox, QPushButton, - QHBoxLayout, QVBoxLayout, QWidget, QPlainTextEdit + QApplication, + QMainWindow, + QTableWidget, + QTableWidgetItem, + QMessageBox, + QPushButton, + QHBoxLayout, + QVBoxLayout, + QWidget, + QPlainTextEdit, ) from PyQt5.QtCore import QThread, pyqtSignal, Qt -import requests - -def log_uncaught_exceptions(ex_cls, ex, tb): - text = '{}: {}:\n'.format(ex_cls.__name__, ex) - text += ''.join(traceback.format_tb(tb)) +def log_uncaught_exceptions(ex_cls, ex, tb) -> None: + text = f"{ex_cls.__name__}: {ex}:\n" + text += "".join(traceback.format_tb(tb)) print(text) - QMessageBox.critical(None, 'Error', text) + QMessageBox.critical(None, "Error", text) sys.exit() @@ -31,18 +37,20 @@ def log_uncaught_exceptions(ex_cls, ex, tb): session = requests.session() -session.headers['User-Agent'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:84.0) Gecko/20100101 Firefox/84.0' +session.headers[ + "User-Agent" +] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:84.0) Gecko/20100101 Firefox/84.0" class CheckUrlThread(QThread): about_check_url = pyqtSignal(str, str) - def __init__(self, urls: List[str] = None): + def __init__(self, urls: list[str] = None) -> None: super().__init__() self.urls = urls if urls else [] - def run(self): + def run(self) -> None: for url in self.urls: try: rs = session.get(url) @@ -58,7 +66,7 @@ def run(self): class MainWindow(QMainWindow): - def __init__(self): + def __init__(self) -> None: super().__init__() self.urls = QPlainTextEdit() @@ -72,7 +80,7 @@ def __init__(self): self.result_table.horizontalHeader().setStretchLastSection(True) self.result_table.horizontalHeader().resizeSection(0, 200) - self.pb_check = QPushButton('Check') + self.pb_check = QPushButton("Check") self.pb_check.clicked.connect(self._on_click_check) layout = QHBoxLayout() @@ -92,7 +100,7 @@ def __init__(self): self.thread.started.connect(lambda: self.pb_check.setEnabled(False)) self.thread.finished.connect(lambda: self.pb_check.setEnabled(True)) - def _on_click_check(self): + def _on_click_check(self) -> None: urls = self.urls.toPlainText().strip().splitlines() self.result_table.clearContents() @@ -108,7 +116,7 @@ def _on_click_check(self): self.thread.urls = urls self.thread.start() - def _on_about_check_url(self, url: str, code: str): + def _on_about_check_url(self, url: str, code: str) -> None: for item in self.result_table.findItems(url, Qt.MatchCaseSensitive): row = item.row() self.result_table.item(row, 1).setText(code) @@ -117,13 +125,15 @@ def _on_about_check_url(self, url: str, code: str): if __name__ == "__main__": app = QApplication([]) - text = '\n'.join([ - 'https://ru.stackoverflow.com', - 'https://ru.stackoverflow.com/questions/893436/', - 'https://google.com', - 'http://ya.ru', - 'http://not_found_site.123', - ]) + text = "\n".join( + [ + "https://ru.stackoverflow.com", + "https://ru.stackoverflow.com/questions/893436/", + "https://google.com", + "http://ya.ru", + "http://not_found_site.123", + ] + ) mw = MainWindow() mw.urls.setPlainText(text) diff --git a/qt__pyqt__pyside__pyqode/clickable_image/main.py b/qt__pyqt__pyside__pyqode/clickable_image/main.py index f2520e183..85792d283 100644 --- a/qt__pyqt__pyside__pyqode/clickable_image/main.py +++ b/qt__pyqt__pyside__pyqode/clickable_image/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PyQt5 import Qt @@ -10,17 +10,17 @@ app = Qt.QApplication([]) button = Qt.QToolButton() -button.setIcon(Qt.QIcon('img.png')) +button.setIcon(Qt.QIcon("img.png")) button.setAutoRaise(True) button.setMinimumSize(40, 50) button.setIconSize(button.minimumSize()) layout = Qt.QVBoxLayout() -layout.addWidget(Qt.QLabel('Example:')) +layout.addWidget(Qt.QLabel("Example:")) layout.addWidget(button) central = Qt.QWidget() -central.setStyleSheet('background-color: green;') +central.setStyleSheet("background-color: green;") central.setLayout(layout) mw = Qt.QMainWindow() diff --git a/qt__pyqt__pyside__pyqode/clipboard__example.py b/qt__pyqt__pyside__pyqode/clipboard__example.py index 1c8d522bd..c4e46d003 100644 --- a/qt__pyqt__pyside__pyqode/clipboard__example.py +++ b/qt__pyqt__pyside__pyqode/clipboard__example.py @@ -1,29 +1,37 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -from PyQt5.QtWidgets import QApplication, QWidget, QGridLayout, QLabel, QPushButton, QMessageBox +import sys +import traceback + +from PyQt5.QtWidgets import ( + QApplication, + QWidget, + QGridLayout, + QLabel, + QPushButton, + QMessageBox, +) from PyQt5.QtCore import Qt -def log_uncaught_exceptions(ex_cls, ex, tb): - text = '{}: {}:\n'.format(ex_cls.__name__, ex) - import traceback - text += ''.join(traceback.format_tb(tb)) +def log_uncaught_exceptions(ex_cls, ex, tb) -> None: + text = f"{ex_cls.__name__}: {ex}:\n" + text += "".join(traceback.format_tb(tb)) print(text) - QMessageBox.critical(None, 'Error', text) + QMessageBox.critical(None, "Error", text) sys.exit(1) -import sys sys.excepthook = log_uncaught_exceptions class MainWindow(QWidget): - def __init__(self): + def __init__(self) -> None: super().__init__() self.setWindowFlag(Qt.WindowStaysOnTopHint) @@ -36,13 +44,13 @@ def __init__(self): button = QPushButton(x) button.clicked.connect(lambda ok, x=x: QApplication.clipboard().setText(x)) - layout.addWidget(QLabel(f'#{i}'), 0, i, Qt.AlignHCenter) + layout.addWidget(QLabel(f"#{i}"), 0, i, Qt.AlignHCenter) layout.addWidget(button, 1, i) self.setLayout(layout) -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) mw = MainWindow() diff --git a/qt__pyqt__pyside__pyqode/qt_custom_tray_menu.py b/qt__pyqt__pyside__pyqode/context_menu/qt_custom_tray_menu.py similarity index 89% rename from qt__pyqt__pyside__pyqode/qt_custom_tray_menu.py rename to qt__pyqt__pyside__pyqode/context_menu/qt_custom_tray_menu.py index 6c38e5d1d..d8c3c2d45 100644 --- a/qt__pyqt__pyside__pyqode/qt_custom_tray_menu.py +++ b/qt__pyqt__pyside__pyqode/context_menu/qt_custom_tray_menu.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PyQt5.Qt import QApplication, QSystemTrayIcon, QStyle, QLabel, QWidgetAction, QMenu @@ -15,7 +15,7 @@ tray = QSystemTrayIcon(icon) -menu_widget = QLabel('Hello, World!') +menu_widget = QLabel("Hello, World!") menu_widget_action = QWidgetAction(menu_widget) menu_widget_action.setDefaultWidget(menu_widget) diff --git a/qt__pyqt__pyside__pyqode/context_menu/qt_tray_with_simple_menu.py b/qt__pyqt__pyside__pyqode/context_menu/qt_tray_with_simple_menu.py new file mode 100644 index 000000000..66b358a0b --- /dev/null +++ b/qt__pyqt__pyside__pyqode/context_menu/qt_tray_with_simple_menu.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from PyQt5.Qt import QApplication, QSystemTrayIcon, QStyle, QMenu, QMessageBox, QWidget + + +app = QApplication([]) +# app.setQuitOnLastWindowClosed(False) + +mw = QWidget() +mw.show() + +icon = app.style().standardIcon(QStyle.SP_DirOpenIcon) +# OR +# icon = QIcon('favicon.ico') + +tray = QSystemTrayIcon(icon) + +menu = QMenu() + +menu_about = menu.addMenu("About") +action_about = menu_about.addAction("About") +action_about.triggered.connect( + lambda: QMessageBox.information(None, "Info", "Hello World!") +) + +action_about_qt = menu_about.addAction("About Qt") +action_about_qt.triggered.connect(lambda: QMessageBox.aboutQt(None)) + +menu.addSeparator() +action_quit = menu.addAction("Quit") +action_quit.triggered.connect(app.quit) + +tray.setContextMenu(menu) + + +def on_tray_activated(reason: QSystemTrayIcon.ActivationReason) -> None: + if reason == QSystemTrayIcon.ActivationReason.Context: + return + + mw.setVisible(not mw.isVisible()) + + +tray.activated.connect(on_tray_activated) + +tray.show() + +app.exec() diff --git a/qt__pyqt__pyside__pyqode/context_menu/table.py b/qt__pyqt__pyside__pyqode/context_menu/table.py new file mode 100644 index 000000000..f64040069 --- /dev/null +++ b/qt__pyqt__pyside__pyqode/context_menu/table.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import os + +from PyQt5.QtCore import QPoint, Qt, QModelIndex +from PyQt5.QtGui import QStandardItemModel, QStandardItem +from PyQt5.QtWidgets import ( + QApplication, + QWidget, + QVBoxLayout, + QTableWidget, + QTableWidgetItem, + QTableView, + QMenu, +) + + +class MainWindow(QWidget): + def __init__(self) -> None: + super().__init__() + + header_labels = ["NAME", "URL"] + + self.table_widget = QTableWidget() + self.table_widget.setEditTriggers(QTableView.NoEditTriggers) + self.table_widget.setSelectionBehavior(QTableView.SelectRows) + self.table_widget.setSelectionMode(QTableView.SingleSelection) + self.table_widget.setColumnCount(len(header_labels)) + self.table_widget.setHorizontalHeaderLabels(header_labels) + self.table_widget.horizontalHeader().setStretchLastSection(True) + self.table_widget.setContextMenuPolicy(Qt.CustomContextMenu) + self.table_widget.customContextMenuRequested.connect( + self._custom_menu_requested + ) + + self.model = QStandardItemModel() + self.model.setHorizontalHeaderLabels(header_labels) + + self.table_view = QTableView() + self.table_view.setEditTriggers(QTableView.NoEditTriggers) + self.table_view.setSelectionBehavior(QTableView.SelectRows) + self.table_view.setSelectionMode(QTableView.SingleSelection) + self.table_view.horizontalHeader().setStretchLastSection(True) + self.table_view.setModel(self.model) + self.table_view.setContextMenuPolicy(Qt.CustomContextMenu) + self.table_view.customContextMenuRequested.connect(self._custom_menu_requested) + + main_layout = QVBoxLayout(self) + main_layout.addWidget(self.table_view) + main_layout.addWidget(self.table_widget) + + self.fill_tables() + + def fill_tables(self) -> None: + self.model.appendRow( + [QStandardItem("rutube"), QStandardItem("https://rutube.ru/")] + ) + self.model.appendRow( + [QStandardItem("yandex"), QStandardItem("https://yandex.ru/")] + ) + self.model.appendRow( + [QStandardItem("youtube"), QStandardItem("https://www.youtube.com")] + ) + self.model.appendRow( + [QStandardItem("google"), QStandardItem("https://google.com/")] + ) + + self.table_widget.setRowCount(self.model.rowCount()) + for row in range(self.model.rowCount()): + for column in range(self.model.columnCount()): + text = self.model.item(row, column).text() + self.table_widget.setItem(row, column, QTableWidgetItem(text)) + + def _custom_menu_requested(self, p: QPoint) -> None: + table = self.sender() + + index: QModelIndex = table.indexAt(p) + if not index.isValid(): + return + + row: int = index.row() + + if isinstance(table, QTableWidget): + name = table.item(row, 0).text() + url = table.item(row, 1).text() + else: + name = self.model.item(row, 0).text() + url = self.model.item(row, 1).text() + + menu = QMenu(self) + menu.addAction(table.__class__.__name__).setEnabled(False) + menu.addSeparator() + menu.addAction("Copy NAME", lambda: QApplication.clipboard().setText(name)) + menu.addAction("Copy URL", lambda: QApplication.clipboard().setText(url)) + menu.addSeparator() + menu.addAction("Open URL", lambda: os.startfile(url)) + menu.exec(table.viewport().mapToGlobal(p)) + + +if __name__ == "__main__": + app = QApplication([]) + + w = MainWindow() + w.resize(600, 500) + w.show() + + app.exec() diff --git a/qt__pyqt__pyside__pyqode/context_menu/table_auto_menu.py b/qt__pyqt__pyside__pyqode/context_menu/table_auto_menu.py new file mode 100644 index 000000000..7722d202c --- /dev/null +++ b/qt__pyqt__pyside__pyqode/context_menu/table_auto_menu.py @@ -0,0 +1,144 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import os +from typing import Callable + +from PyQt5.QtCore import QPoint, Qt, QModelIndex +from PyQt5.QtGui import QStandardItemModel, QStandardItem +from PyQt5.QtWidgets import ( + QApplication, + QWidget, + QVBoxLayout, + QTableWidget, + QTableWidgetItem, + QTableView, + QMenu, + QAction, +) + + +def open_context_menu( + table: QTableView, + p: QPoint, + get_additional_actions_func: Callable[[QTableView, int], list[QAction]] = None, +) -> None: + index: QModelIndex = table.indexAt(p) + if not index.isValid(): + return + + menu = QMenu(table) + menu.addAction(table.__class__.__name__).setEnabled(False) + menu.addSeparator() + + row: int = index.row() + model = table.model() + + for column in range(model.columnCount()): + title = model.headerData(column, Qt.Horizontal) + + idx: QModelIndex = model.index(row, column) + value: str = str(model.data(idx)) + + menu.addAction( + f'Copy "{title}"', + lambda value=value: QApplication.clipboard().setText(value), + ) + + if get_additional_actions_func: + if actions := get_additional_actions_func(table, row): + menu.addSeparator() + menu.addActions(actions) + + menu.exec(table.viewport().mapToGlobal(p)) + + +class MainWindow(QWidget): + def __init__(self) -> None: + super().__init__() + + header_labels = ["NAME", "URL"] + + self.table_widget = QTableWidget() + self.table_widget.setEditTriggers(QTableView.NoEditTriggers) + self.table_widget.setSelectionBehavior(QTableView.SelectRows) + self.table_widget.setSelectionMode(QTableView.SingleSelection) + self.table_widget.setColumnCount(len(header_labels)) + self.table_widget.setHorizontalHeaderLabels(header_labels) + self.table_widget.horizontalHeader().setStretchLastSection(True) + self.table_widget.setContextMenuPolicy(Qt.CustomContextMenu) + self.table_widget.customContextMenuRequested.connect( + self._custom_menu_requested + ) + + self.model = QStandardItemModel() + self.model.setHorizontalHeaderLabels(header_labels) + + self.table_view = QTableView() + self.table_view.setEditTriggers(QTableView.NoEditTriggers) + self.table_view.setSelectionBehavior(QTableView.SelectRows) + self.table_view.setSelectionMode(QTableView.SingleSelection) + self.table_view.horizontalHeader().setStretchLastSection(True) + self.table_view.setModel(self.model) + self.table_view.setContextMenuPolicy(Qt.CustomContextMenu) + self.table_view.customContextMenuRequested.connect(self._custom_menu_requested) + + main_layout = QVBoxLayout(self) + main_layout.addWidget(self.table_view) + main_layout.addWidget(self.table_widget) + + self.fill_tables() + + def fill_tables(self) -> None: + self.model.appendRow( + [QStandardItem("rutube"), QStandardItem("https://rutube.ru/")] + ) + self.model.appendRow( + [QStandardItem("yandex"), QStandardItem("https://yandex.ru/")] + ) + self.model.appendRow( + [QStandardItem("youtube"), QStandardItem("https://www.youtube.com")] + ) + self.model.appendRow( + [QStandardItem("google"), QStandardItem("https://google.com/")] + ) + + self.table_widget.setRowCount(self.model.rowCount()) + for row in range(self.model.rowCount()): + for column in range(self.model.columnCount()): + text = self.model.item(row, column).text() + self.table_widget.setItem(row, column, QTableWidgetItem(text)) + + def _custom_menu_requested(self, p: QPoint) -> None: + def _get_additional_actions(table: QTableView, row: int) -> list[QAction]: + model = table.model() + + actions = [] + for column in range(model.columnCount()): + idx = model.index(row, column) + value: str = str(model.data(idx)) + if not value.startswith("http"): + continue + + action_open_url = QAction(f'Open "{value}"') + action_open_url.triggered.connect(lambda: os.startfile(value)) + + actions.append(action_open_url) + + return actions + + table: QTableView = self.sender() + open_context_menu(table, p, _get_additional_actions) + + +if __name__ == "__main__": + app = QApplication([]) + + w = MainWindow() + w.resize(600, 500) + w.show() + + app.exec() diff --git a/qt__pyqt__pyside__pyqode/create_child_window_by_center.py b/qt__pyqt__pyside__pyqode/create_child_window_by_center.py index 5f3c7a3e4..b4835f743 100644 --- a/qt__pyqt__pyside__pyqode/create_child_window_by_center.py +++ b/qt__pyqt__pyside__pyqode/create_child_window_by_center.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QVBoxLayout, QStyle @@ -9,19 +9,19 @@ class MainWindow(QWidget): - def __init__(self): + def __init__(self) -> None: super().__init__() - pb_1 = QPushButton('Create [1]') + pb_1 = QPushButton("Create [1]") pb_1.clicked.connect(self._create_new_window_1) - pb_2 = QPushButton('Create [2]') + pb_2 = QPushButton("Create [2]") pb_2.clicked.connect(self._create_new_window_2) - pb_3 = QPushButton('Create [3]') + pb_3 = QPushButton("Create [3]") pb_3.clicked.connect(self._create_new_window_3) - pb_4 = QPushButton('Create [4]') + pb_4 = QPushButton("Create [4]") pb_4.clicked.connect(self._create_new_window_4) layout = QVBoxLayout() @@ -33,12 +33,12 @@ def __init__(self): self.setLayout(layout) - def _create_new_window_1(self): + def _create_new_window_1(self) -> None: widget = QWidget(self, flags=Qt.Window) widget.resize(200, 100) widget.show() - def _create_new_window_2(self): + def _create_new_window_2(self) -> None: widget = QWidget(self, flags=Qt.Window) widget.resize(200, 100) @@ -47,7 +47,7 @@ def _create_new_window_2(self): widget.show() - def _create_new_window_3(self): + def _create_new_window_3(self) -> None: widget = QWidget(self, flags=Qt.Window) widget.resize(200, 100) @@ -58,15 +58,19 @@ def _create_new_window_3(self): widget.show() - def _create_new_window_4(self): + def _create_new_window_4(self) -> None: widget = QWidget(self, flags=Qt.Window) widget.resize(200, 100) - widget.setGeometry(QStyle.alignedRect(Qt.LeftToRight, Qt.AlignCenter, widget.size(), self.geometry())) + widget.setGeometry( + QStyle.alignedRect( + Qt.LeftToRight, Qt.AlignCenter, widget.size(), self.geometry() + ) + ) widget.show() -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) mw = MainWindow() diff --git a/qt__pyqt__pyside__pyqode/crypto__decrypto__xor.py b/qt__pyqt__pyside__pyqode/crypto__decrypto__xor.py index 995d3ea6e..1f275d954 100644 --- a/qt__pyqt__pyside__pyqode/crypto__decrypto__xor.py +++ b/qt__pyqt__pyside__pyqode/crypto__decrypto__xor.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PyQt5 import Qt @@ -9,27 +9,27 @@ # SOURCE: https://github.com/gil9red/SimplePyScripts/blob/7c3e004f8038cf7942226283fe0f5c184008b561/xor_crypto.py def crypto_xor_1(message, secret): - return ''.join(chr(ord(c) ^ secret) for c in message) + return "".join(chr(ord(c) ^ secret) for c in message) class Window(Qt.QWidget): - def __init__(self): + def __init__(self) -> None: super().__init__() - self.setWindowTitle('crypto / decrypto') + self.setWindowTitle("crypto / decrypto") - self.pte_text = Qt.QPlainTextEdit('Test / Тест') + self.pte_text = Qt.QPlainTextEdit("Test / Тест") self.sb_key = Qt.QSpinBox() self.sb_key.setRange(1, 1000000) self.sb_key.setValue(42) self.sb_key.setSizePolicy(Qt.QSizePolicy.Expanding, Qt.QSizePolicy.Preferred) - self.pb_convert = Qt.QPushButton('Convert') + self.pb_convert = Qt.QPushButton("Convert") self.pb_convert.clicked.connect(self._on_convert) layout_command = Qt.QHBoxLayout() - layout_command.addWidget(Qt.QLabel('Key:')) + layout_command.addWidget(Qt.QLabel("Key:")) layout_command.addWidget(self.sb_key) layout = Qt.QVBoxLayout() @@ -39,7 +39,7 @@ def __init__(self): self.setLayout(layout) - def _on_convert(self): + def _on_convert(self) -> None: text = self.pte_text.toPlainText() key = self.sb_key.value() @@ -48,7 +48,7 @@ def _on_convert(self): self.pte_text.setPlainText(text) -if __name__ == '__main__': +if __name__ == "__main__": app = Qt.QApplication([]) mw = Window() diff --git a/qt__pyqt__pyside__pyqode/darkstyle__QDarkStyleSheet__load_from_ui/main.py b/qt__pyqt__pyside__pyqode/darkstyle__QDarkStyleSheet__load_from_ui/main.py index cfcfae003..b2affa7f6 100644 --- a/qt__pyqt__pyside__pyqode/darkstyle__QDarkStyleSheet__load_from_ui/main.py +++ b/qt__pyqt__pyside__pyqode/darkstyle__QDarkStyleSheet__load_from_ui/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PyQt5 import Qt @@ -12,16 +12,16 @@ class MainWindow(Qt.QWidget): - def __init__(self): + def __init__(self) -> None: super().__init__() - uic.loadUi('mainwidget.ui', self) + uic.loadUi("mainwidget.ui", self) - self.cbPenStyle.addItem('Solid', Qt.Qt.SolidLine) - self.cbPenStyle.addItem('Dash', Qt.Qt.DashLine) - self.cbPenStyle.addItem('Dot', Qt.Qt.DotLine) - self.cbPenStyle.addItem('Dash Dot', Qt.Qt.DashDotLine) - self.cbPenStyle.addItem('Dash Dot Dot', Qt.Qt.DashDotDotLine) + self.cbPenStyle.addItem("Solid", Qt.Qt.SolidLine) + self.cbPenStyle.addItem("Dash", Qt.Qt.DashLine) + self.cbPenStyle.addItem("Dot", Qt.Qt.DotLine) + self.cbPenStyle.addItem("Dash Dot", Qt.Qt.DashDotLine) + self.cbPenStyle.addItem("Dash Dot Dot", Qt.Qt.DashDotDotLine) self.pen_color = Qt.Qt.green @@ -30,7 +30,7 @@ def __init__(self): self._update_pen_color() - def _choose_color(self): + def _choose_color(self) -> None: color = Qt.QColorDialog.getColor(self.pen_color) if not color.isValid(): return @@ -38,21 +38,21 @@ def _choose_color(self): self.pen_color = color self._update_pen_color() - def _update_pen_color(self): + def _update_pen_color(self) -> None: pixmap = Qt.QPixmap(self.pbPenColor.size()) pixmap.fill(self.pen_color) self.pbPenColor.setIcon(Qt.QIcon(pixmap)) -if __name__ == '__main__': +if __name__ == "__main__": app = Qt.QApplication([]) mw1 = MainWindow() - mw1.setWindowTitle('Standard') + mw1.setWindowTitle("Standard") mw1.show() mw2 = MainWindow() - mw2.setWindowTitle('Dark') + mw2.setWindowTitle("Dark") mw2.setStyleSheet(qdarkstyle.load_stylesheet_pyqt5()) mw2.show() diff --git a/qt__pyqt__pyside__pyqode/disable_specific_items_in_QComboBox.py b/qt__pyqt__pyside__pyqode/disable_specific_items_in_QComboBox.py index 4202eea32..029588bef 100644 --- a/qt__pyqt__pyside__pyqode/disable_specific_items_in_QComboBox.py +++ b/qt__pyqt__pyside__pyqode/disable_specific_items_in_QComboBox.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PyQt5.Qt import QApplication, QWidget, QVBoxLayout, QComboBox diff --git a/qt__pyqt__pyside__pyqode/download_and_show_async_image_on_QGraphicsScene_QNetworkAccessManager.py b/qt__pyqt__pyside__pyqode/download_and_show_async_image_on_QGraphicsScene_QNetworkAccessManager.py index 9b100a1bd..1fa1f3891 100644 --- a/qt__pyqt__pyside__pyqode/download_and_show_async_image_on_QGraphicsScene_QNetworkAccessManager.py +++ b/qt__pyqt__pyside__pyqode/download_and_show_async_image_on_QGraphicsScene_QNetworkAccessManager.py @@ -1,23 +1,29 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PyQt5.QtGui import QPixmap from PyQt5.QtCore import QUrl from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest from PyQt5.QtWidgets import ( - QApplication, QWidget, QVBoxLayout, QGraphicsScene, QGraphicsView, QGraphicsPixmapItem, QGraphicsTextItem + QApplication, + QWidget, + QVBoxLayout, + QGraphicsScene, + QGraphicsView, + QGraphicsPixmapItem, + QGraphicsTextItem, ) class Widget(QWidget): - def __init__(self): + def __init__(self) -> None: super().__init__() self.scene = QGraphicsScene() - self.scene.addItem(QGraphicsTextItem('loading...')) + self.scene.addItem(QGraphicsTextItem("loading...")) self.view = QGraphicsView() self.view.setScene(self.scene) @@ -33,7 +39,7 @@ def __init__(self): self.setLayout(layout) - def finish_request(self, reply): + def finish_request(self, reply) -> None: self.scene.clear() img = QPixmap() @@ -43,7 +49,7 @@ def finish_request(self, reply): self.scene.addItem(item) -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) w = Widget() diff --git a/qt__pyqt__pyside__pyqode/download_and_show_async_image_on_QLabel_QNetworkAccessManager.py b/qt__pyqt__pyside__pyqode/download_and_show_async_image_on_QLabel_QNetworkAccessManager.py index 92c684af3..0b06c223c 100644 --- a/qt__pyqt__pyside__pyqode/download_and_show_async_image_on_QLabel_QNetworkAccessManager.py +++ b/qt__pyqt__pyside__pyqode/download_and_show_async_image_on_QLabel_QNetworkAccessManager.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PyQt5.QtGui import QPixmap @@ -11,7 +11,7 @@ class Widget(QWidget): - def __init__(self): + def __init__(self) -> None: super().__init__() self.lbl = QLabel("loading...") @@ -28,14 +28,14 @@ def __init__(self): self.setLayout(layout) - def finish_request(self, reply): + def finish_request(self, reply) -> None: img = QPixmap() img.loadFromData(reply.readAll()) self.lbl.setPixmap(img) -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) w = Widget() diff --git a/qt__pyqt__pyside__pyqode/download_file/complex.py b/qt__pyqt__pyside__pyqode/download_file/complex.py index 474760e48..fb1e0f5b8 100644 --- a/qt__pyqt__pyside__pyqode/download_file/complex.py +++ b/qt__pyqt__pyside__pyqode/download_file/complex.py @@ -1,14 +1,21 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import os.path import traceback from urllib.request import urlretrieve -from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QLineEdit, QFormLayout, QTextEdit +from PyQt5.QtWidgets import ( + QApplication, + QWidget, + QPushButton, + QLineEdit, + QFormLayout, + QTextEdit, +) from PyQt5.QtCore import QThread, pyqtSignal @@ -17,14 +24,14 @@ class ThreadDownload(QThread): about_file_name = pyqtSignal(str) about_error = pyqtSignal(str) - def __init__(self, url: str = None, file_name: str = None): + def __init__(self, url: str = None, file_name: str = None) -> None: super().__init__() self.url = url self.file_name = file_name # SOURCE: https://github.com/gil9red/SimplePyScripts/blob/4692cbb36b5addb0f2bd5e93e69eb8c7c257bdf8/download_file/with_progress.py#L12 - def reporthook(self, blocknum, blocksize, totalsize): + def reporthook(self, blocknum, blocksize, totalsize) -> None: readsofar = blocknum * blocksize if totalsize > 0: percent = readsofar * 100.0 / totalsize @@ -32,16 +39,23 @@ def reporthook(self, blocknum, blocksize, totalsize): percent = 100 readsofar = totalsize - s = "%5.1f%% %*d / %d" % (percent, len(str(totalsize)), readsofar, totalsize) + s = "%5.1f%% %*d / %d" % ( + percent, + len(str(totalsize)), + readsofar, + totalsize, + ) self.about_progress.emit(s) # Total size is unknown else: self.about_progress.emit(f"read {readsofar}") - def run(self): + def run(self) -> None: try: - file_name, _ = urlretrieve(self.url, self.file_name, reporthook=self.reporthook) + file_name, _ = urlretrieve( + self.url, self.file_name, reporthook=self.reporthook + ) file_name = os.path.abspath(file_name) self.about_file_name.emit(file_name) @@ -50,20 +64,22 @@ def run(self): class MainWindow(QWidget): - def __init__(self): + def __init__(self) -> None: super().__init__() - self.line_edit_url = QLineEdit('https://codeload.github.com/gil9red/SimplePyScripts/zip/master') - self.line_edit_file_name = QLineEdit('SimplePyScripts.zip') + self.line_edit_url = QLineEdit( + "https://codeload.github.com/gil9red/SimplePyScripts/zip/master" + ) + self.line_edit_file_name = QLineEdit("SimplePyScripts.zip") - self.button_download = QPushButton('Download') + self.button_download = QPushButton("Download") self.button_download.clicked.connect(self.download) self.text_edit_log = QTextEdit() layout = QFormLayout() - layout.addRow('URL:', self.line_edit_url) - layout.addRow('File Name:', self.line_edit_file_name) + layout.addRow("URL:", self.line_edit_url) + layout.addRow("File Name:", self.line_edit_file_name) layout.addWidget(self.button_download) layout.addWidget(self.text_edit_log) @@ -76,22 +92,24 @@ def __init__(self): self.setLayout(layout) - def download(self): + def download(self) -> None: self.thread.url = self.line_edit_url.text() self.thread.file_name = self.line_edit_file_name.text() self.thread.start() - def _handle_about_progress(self, text: str): + def _handle_about_progress(self, text: str) -> None: self.setWindowTitle(text) - def _handle_about_file_name(self, text: str): - self.text_edit_log.append(f"""

File name: {text}

""") + def _handle_about_file_name(self, text: str) -> None: + self.text_edit_log.append( + f"""

File name: {text}

""" + ) - def _handle_about_error(self, text: str): + def _handle_about_error(self, text: str) -> None: self.text_edit_log.append(f"""
{text}
""") -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) mw = MainWindow() diff --git a/qt__pyqt__pyside__pyqode/download_file/simple.py b/qt__pyqt__pyside__pyqode/download_file/simple.py index baddf6f1e..9f62bc7b6 100644 --- a/qt__pyqt__pyside__pyqode/download_file/simple.py +++ b/qt__pyqt__pyside__pyqode/download_file/simple.py @@ -1,18 +1,19 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import os.path -from threading import Thread import traceback + +from threading import Thread from urllib.request import urlretrieve from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QLineEdit, QFormLayout -def download_file(url: str, file_name: str): +def download_file(url: str, file_name: str) -> None: try: local_file_name, _ = urlretrieve(url, file_name) print(os.path.abspath(local_file_name)) @@ -21,23 +22,25 @@ def download_file(url: str, file_name: str): class MainWindow(QWidget): - def __init__(self): + def __init__(self) -> None: super().__init__() - self.line_edit_url = QLineEdit('https://codeload.github.com/gil9red/SimplePyScripts/zip/master') - self.line_edit_file_name = QLineEdit('SimplePyScripts.zip') + self.line_edit_url = QLineEdit( + "https://codeload.github.com/gil9red/SimplePyScripts/zip/master" + ) + self.line_edit_file_name = QLineEdit("SimplePyScripts.zip") - self.button_download = QPushButton('Download') + self.button_download = QPushButton("Download") self.button_download.clicked.connect(self.download) layout = QFormLayout() - layout.addRow('URL:', self.line_edit_url) - layout.addRow('File Name:', self.line_edit_file_name) + layout.addRow("URL:", self.line_edit_url) + layout.addRow("File Name:", self.line_edit_file_name) layout.addWidget(self.button_download) self.setLayout(layout) - def download(self): + def download(self) -> None: url = self.line_edit_url.text() file_name = self.line_edit_file_name.text() @@ -45,7 +48,7 @@ def download(self): thread.start() -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) mw = MainWindow() diff --git a/qt__pyqt__pyside__pyqode/draw__ellipse_board/main.py b/qt__pyqt__pyside__pyqode/draw__ellipse_board/main.py index eaa0d385b..e565d4012 100644 --- a/qt__pyqt__pyside__pyqode/draw__ellipse_board/main.py +++ b/qt__pyqt__pyside__pyqode/draw__ellipse_board/main.py @@ -1,19 +1,19 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PyQt5 import Qt class CellWidget(Qt.QWidget): - def __init__(self): + def __init__(self) -> None: super().__init__() self.ball_size = 100 - def paintEvent(self, event: Qt.QPaintEvent): + def paintEvent(self, event: Qt.QPaintEvent) -> None: painter = Qt.QPainter(self) painter.setRenderHint(Qt.QPainter.Antialiasing) painter.setPen(Qt.Qt.NoPen) @@ -30,10 +30,10 @@ def paintEvent(self, event: Qt.QPaintEvent): class Window(Qt.QWidget): - def __init__(self): + def __init__(self) -> None: super().__init__() - self.setWindowTitle('draw__ellipse_board') + self.setWindowTitle("draw__ellipse_board") self.cell_widget = CellWidget() @@ -41,7 +41,9 @@ def __init__(self): self.pb_ball_size.setRange(5, 1000) self.sl_ball_size = Qt.QSlider(Qt.Qt.Horizontal) - self.sl_ball_size.setRange(self.pb_ball_size.minimum(), self.pb_ball_size.maximum()) + self.sl_ball_size.setRange( + self.pb_ball_size.minimum(), self.pb_ball_size.maximum() + ) self.sl_ball_size.valueChanged.connect(self._set_ball_size) self.pb_ball_size.valueChanged.connect(self._set_ball_size) @@ -58,7 +60,7 @@ def __init__(self): self.setLayout(layout) - def _set_ball_size(self, value): + def _set_ball_size(self, value) -> None: self.sl_ball_size.setValue(value) self.pb_ball_size.setValue(value) @@ -66,7 +68,7 @@ def _set_ball_size(self, value): self.cell_widget.update() -if __name__ == '__main__': +if __name__ == "__main__": app = Qt.QApplication([]) w = Window() diff --git a/qt__pyqt__pyside__pyqode/draw__ellipse_board__chess_order__decrease/main.py b/qt__pyqt__pyside__pyqode/draw__ellipse_board__chess_order__decrease/main.py index 419e4c956..15fae80ce 100644 --- a/qt__pyqt__pyside__pyqode/draw__ellipse_board__chess_order__decrease/main.py +++ b/qt__pyqt__pyside__pyqode/draw__ellipse_board__chess_order__decrease/main.py @@ -1,19 +1,19 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PyQt5 import Qt class Window(Qt.QWidget): - def __init__(self): + def __init__(self) -> None: super().__init__() - self.setWindowTitle('draw__ellipse_board__chess_order__decrease') + self.setWindowTitle("draw__ellipse_board__chess_order__decrease") - def paintEvent(self, event: Qt.QPaintEvent): + def paintEvent(self, event: Qt.QPaintEvent) -> None: painter = Qt.QPainter(self) painter.setRenderHint(Qt.QPainter.Antialiasing) painter.setPen(Qt.Qt.NoPen) @@ -44,7 +44,7 @@ def paintEvent(self, event: Qt.QPaintEvent): y += h_indent -if __name__ == '__main__': +if __name__ == "__main__": app = Qt.QApplication([]) w = Window() diff --git a/qt__pyqt__pyside__pyqode/draw__ellipse_board__decrease/main.py b/qt__pyqt__pyside__pyqode/draw__ellipse_board__decrease/main.py index e0b3e9383..cf6110eb3 100644 --- a/qt__pyqt__pyside__pyqode/draw__ellipse_board__decrease/main.py +++ b/qt__pyqt__pyside__pyqode/draw__ellipse_board__decrease/main.py @@ -1,19 +1,19 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PyQt5 import Qt class Window(Qt.QWidget): - def __init__(self): + def __init__(self) -> None: super().__init__() - self.setWindowTitle('draw__ellipse_board__decrease') + self.setWindowTitle("draw__ellipse_board__decrease") - def paintEvent(self, event: Qt.QPaintEvent): + def paintEvent(self, event: Qt.QPaintEvent) -> None: painter = Qt.QPainter(self) painter.setRenderHint(Qt.QPainter.Antialiasing) painter.setPen(Qt.Qt.NoPen) @@ -41,7 +41,7 @@ def paintEvent(self, event: Qt.QPaintEvent): y += h_indent -if __name__ == '__main__': +if __name__ == "__main__": app = Qt.QApplication([]) w = Window() diff --git a/qt__pyqt__pyside__pyqode/draw_frame_with_color_info/main.py b/qt__pyqt__pyside__pyqode/draw_frame_with_color_info/main.py index 1bcc66d49..f077dd9d8 100644 --- a/qt__pyqt__pyside__pyqode/draw_frame_with_color_info/main.py +++ b/qt__pyqt__pyside__pyqode/draw_frame_with_color_info/main.py @@ -1,25 +1,25 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -from typing import Union import sys from pathlib import Path -sys.path.append('..') - -from Good_text_foreground_color_for_a_given_background_color import get_good_text_foreground_color - from PyQt5.QtGui import QGuiApplication, QPainter, QImage, QColor, QFont, QFontMetrics from PyQt5.QtCore import Qt, QByteArray, QBuffer, QIODevice +sys.path.append("..") +from Good_text_foreground_color_for_a_given_background_color import ( + get_good_text_foreground_color, +) + APP = QGuiApplication([]) SIZE = 300 RADIUS = 20 -FAMILY_FONT = 'Courier New' # Need monospaced +FAMILY_FONT = "Courier New" # Need monospaced def get_optimal_font(family_font: str, w, h, text: str) -> QFont: @@ -28,21 +28,23 @@ def get_optimal_font(family_font: str, w, h, text: str) -> QFont: metrics = QFontMetrics(font) # SOURCE: https://github.com/gil9red/SimplePyScripts/blob/add91e36e1ee59b3956b9fafdcffc9f4ff10ed3d/qt__pyqt__pyside__pyqode/pyqt__QPainter__draw_table.py#L98 - factor = w / metrics.boundingRect(0, 0, int(w), int(h), Qt.AlignCenter, text).width() + factor = ( + w / metrics.boundingRect(0, 0, int(w), int(h), Qt.AlignCenter, text).width() + ) if factor < 1 or factor > 1.25: font.setPointSizeF(font.pointSizeF() * factor) return font -def draw_hex(painter: QPainter, size: int, color: QColor): +def draw_hex(painter: QPainter, size: int, color: QColor) -> None: r, g, b, _ = color.getRgb() - value = ''.join(f'{x:02X}' for x in [r, g, b]) + value = "".join(f"{x:02X}" for x in [r, g, b]) text_color = get_good_text_foreground_color(color) x, y, w_hex, h_hex = size * 0.05, size * 0.1, size * 0.9, size * 0.25 - font = get_optimal_font(FAMILY_FONT, w_hex, h_hex, text='FFFFFF') + font = get_optimal_font(FAMILY_FONT, w_hex, h_hex, text="FFFFFF") painter.save() @@ -54,14 +56,14 @@ def draw_hex(painter: QPainter, size: int, color: QColor): painter.restore() -def draw_rgb(painter: QPainter, size: int, color: QColor): +def draw_rgb(painter: QPainter, size: int, color: QColor) -> None: r, g, b, _ = color.getRgb() rgb = list(map(str, [r, g, b])) text_color, back_color = Qt.black, Qt.white w_rgb, h_rgb = size // 4, size // 6 - font = get_optimal_font(FAMILY_FONT, w_rgb, h_rgb, text='255') + font = get_optimal_font(FAMILY_FONT, w_rgb, h_rgb, text="255") font.setWeight(QFont.Bold) y = size - size // 4 @@ -85,7 +87,9 @@ def draw_rgb(painter: QPainter, size: int, color: QColor): painter.restore() -def get_frame_with_color_info(color: QColor, size=SIZE, rounded=True, as_bytes=False) -> Union[QImage, bytes]: +def get_frame_with_color_info( + color: QColor, size=SIZE, rounded=True, as_bytes=False +) -> QImage | bytes: image = QImage(size, size, QImage.Format_ARGB32) image.fill(Qt.transparent) @@ -95,7 +99,9 @@ def get_frame_with_color_info(color: QColor, size=SIZE, rounded=True, as_bytes=F painter.setBrush(color) if rounded: - painter.drawRoundedRect(0, 0, image.width(), image.height(), RADIUS, RADIUS, Qt.RelativeSize) + painter.drawRoundedRect( + 0, 0, image.width(), image.height(), RADIUS, RADIUS, Qt.RelativeSize + ) else: painter.drawRect(0, 0, image.width(), image.height()) @@ -114,15 +120,15 @@ def get_frame_with_color_info(color: QColor, size=SIZE, rounded=True, as_bytes=F return image -if __name__ == '__main__': - path = Path('.') / 'images' +if __name__ == "__main__": + path = Path(".") / "images" path.mkdir(parents=True, exist_ok=True) - for name in ['#007396', '#ff8c69', 'green', '#a000ff00', '#333']: + for name in ["#007396", "#ff8c69", "green", "#a000ff00", "#333"]: color = QColor(name) image = get_frame_with_color_info(color) - image.save(f'{path}/{name}.png') + image.save(f"{path}/{name}.png") - name = '#007396' + name = "#007396" image = get_frame_with_color_info(QColor(name), rounded=False) - image.save(f'images/no_rounded_{name}.png') + image.save(f"images/no_rounded_{name}.png") diff --git a/qt__pyqt__pyside__pyqode/draw_image_label__on_scroll_area/main.py b/qt__pyqt__pyside__pyqode/draw_image_label__on_scroll_area/main.py index d74febe31..7d29b5702 100644 --- a/qt__pyqt__pyside__pyqode/draw_image_label__on_scroll_area/main.py +++ b/qt__pyqt__pyside__pyqode/draw_image_label__on_scroll_area/main.py @@ -1,35 +1,43 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -from PyQt5.QtWidgets import QWidget, QApplication, QLabel, QHBoxLayout, QScrollArea, QMessageBox +import sys +import traceback + +from PyQt5.QtWidgets import ( + QWidget, + QApplication, + QLabel, + QHBoxLayout, + QScrollArea, + QMessageBox, +) from PyQt5.QtGui import QPainter, QColor, QPixmap, QPalette from PyQt5.QtCore import Qt, QEvent -def log_uncaught_exceptions(ex_cls, ex, tb): - text = '{}: {}:\n'.format(ex_cls.__name__, ex) - import traceback - text += ''.join(traceback.format_tb(tb)) +def log_uncaught_exceptions(ex_cls, ex, tb) -> None: + text = f"{ex_cls.__name__}: {ex}:\n" + text += "".join(traceback.format_tb(tb)) print(text) - QMessageBox.critical(None, 'Error', text) + QMessageBox.critical(None, "Error", text) sys.exit(1) -import sys sys.excepthook = log_uncaught_exceptions class Example(QWidget): - def __init__(self): + def __init__(self) -> None: super().__init__() self.flag = False - self.image = QPixmap('image.jpg') + self.image = QPixmap("image.jpg") self.label = QLabel() self.label.setAlignment(Qt.AlignLeft) @@ -48,7 +56,7 @@ def __init__(self): # Рисовать будем на картинке self.painter = QPainter(self.image) - self.painter.setBrush(QColor('yellow')) + self.painter.setBrush(QColor("yellow")) def eventFilter(self, obj, e): # Если событие пришло от self.label @@ -69,12 +77,12 @@ def eventFilter(self, obj, e): # Стандартная обработка событий return super().eventFilter(obj, e) - def draw_ellipse(self, e): + def draw_ellipse(self, e) -> None: self.painter.drawEllipse(e.pos(), 20, 20) self.label.setPixmap(self.image) -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) w = Example() diff --git a/qt__pyqt__pyside__pyqode/draw_spiral.py b/qt__pyqt__pyside__pyqode/draw_spiral.py index 8b82f5812..38ab2b73c 100644 --- a/qt__pyqt__pyside__pyqode/draw_spiral.py +++ b/qt__pyqt__pyside__pyqode/draw_spiral.py @@ -1,19 +1,27 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://doc.qt.io/qt-5/qtopengl-2dpainting-example.html from PyQt5.QtWidgets import QApplication, QWidget -from PyQt5.QtGui import QBrush, QFont, QPen, QPainter, QPaintEvent, QLinearGradient, QColor +from PyQt5.QtGui import ( + QBrush, + QFont, + QPen, + QPainter, + QPaintEvent, + QLinearGradient, + QColor, +) from PyQt5.QtCore import Qt, QPointF, QRectF, QTimer class Helper: - def __init__(self, is_reverse_rotation: bool, is_draw_all_spiral=False): + def __init__(self, is_reverse_rotation: bool, is_draw_all_spiral=False) -> None: super().__init__() self.is_reverse_rotation = is_reverse_rotation @@ -21,7 +29,7 @@ def __init__(self, is_reverse_rotation: bool, is_draw_all_spiral=False): gradient = QLinearGradient(QPointF(50, -20), QPointF(80, 20)) gradient.setColorAt(0.0, Qt.white) - gradient.setColorAt(1.0, QColor(0xa6, 0xce, 0x39)) + gradient.setColorAt(1.0, QColor(0xA6, 0xCE, 0x39)) self.background: QBrush = QBrush(QColor(64, 32, 64)) @@ -35,7 +43,7 @@ def __init__(self, is_reverse_rotation: bool, is_draw_all_spiral=False): self.textPen: QPen = QPen(Qt.white) - def _draw_spiral(self, painter: QPainter, elapsed: int, is_reverse_rotation=False): + def _draw_spiral(self, painter: QPainter, elapsed: int, is_reverse_rotation=False) -> None: r = elapsed / 1000.0 n = 30 @@ -49,11 +57,13 @@ def _draw_spiral(self, painter: QPainter, elapsed: int, is_reverse_rotation=Fals factor = (i + r) / n radius = 0 + 120.0 * factor circleRadius = 1 + factor * 20 - painter.drawEllipse(QRectF(radius, -circleRadius, circleRadius * 2, circleRadius * 2)) + painter.drawEllipse( + QRectF(radius, -circleRadius, circleRadius * 2, circleRadius * 2) + ) painter.restore() - def paint(self, painter: QPainter, event: QPaintEvent, elapsed: int): + def paint(self, painter: QPainter, event: QPaintEvent, elapsed: int) -> None: painter.fillRect(event.rect(), self.background) painter.translate(100, 100) @@ -63,7 +73,7 @@ def paint(self, painter: QPainter, event: QPaintEvent, elapsed: int): class Widget(QWidget): - def __init__(self, is_reverse_rotation: bool, is_draw_all_spiral=False): + def __init__(self, is_reverse_rotation: bool, is_draw_all_spiral=False) -> None: super().__init__() self.setFixedSize(200, 200) @@ -75,18 +85,18 @@ def __init__(self, is_reverse_rotation: bool, is_draw_all_spiral=False): self.timer.timeout.connect(self.animate) self.timer.start(50) - def animate(self): + def animate(self) -> None: self.elapsed = (self.elapsed + self.timer.interval()) % 1000 self.update() - def paintEvent(self, event: QPaintEvent): + def paintEvent(self, event: QPaintEvent) -> None: painter = QPainter(self) painter.setRenderHint(QPainter.Antialiasing) self.helper.paint(painter, event, self.elapsed) painter.end() -if __name__ == '__main__': +if __name__ == "__main__": from PyQt5.QtWidgets import QLabel, QHBoxLayout app = QApplication([]) @@ -99,9 +109,9 @@ def paintEvent(self, event: QPaintEvent): layout = QHBoxLayout(mw) layout.addWidget(w1) - layout.addWidget(QLabel('+')) + layout.addWidget(QLabel("+")) layout.addWidget(w2) - layout.addWidget(QLabel('=')) + layout.addWidget(QLabel("=")) layout.addWidget(w3) mw.show() diff --git a/qt__pyqt__pyside__pyqode/draw_wave_SIN_and_COS.py b/qt__pyqt__pyside__pyqode/draw_wave_SIN_and_COS.py index 5a1a21fe6..fab8cc9d3 100644 --- a/qt__pyqt__pyside__pyqode/draw_wave_SIN_and_COS.py +++ b/qt__pyqt__pyside__pyqode/draw_wave_SIN_and_COS.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" import math @@ -12,7 +12,7 @@ class MainWindow(QWidget): - def paintEvent(self, event): + def paintEvent(self, event) -> None: painter = QPainter(self) painter.setRenderHint(QPainter.Antialiasing) @@ -39,7 +39,7 @@ def paintEvent(self, event): painter.drawPoint(x, y) -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) mw = MainWindow() diff --git a/qt__pyqt__pyside__pyqode/example_QStackedWidget/use_buttons.py b/qt__pyqt__pyside__pyqode/example_QStackedWidget/use_buttons.py index d59736de9..dae8ceada 100644 --- a/qt__pyqt__pyside__pyqode/example_QStackedWidget/use_buttons.py +++ b/qt__pyqt__pyside__pyqode/example_QStackedWidget/use_buttons.py @@ -1,29 +1,39 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -from PyQt5.QtWidgets import QApplication, QVBoxLayout, QHBoxLayout, QWidget, QPushButton, QLabel, QStackedWidget +from PyQt5.QtWidgets import ( + QApplication, + QVBoxLayout, + QHBoxLayout, + QWidget, + QPushButton, + QLabel, + QStackedWidget, +) class MainWindow(QWidget): - def __init__(self): + def __init__(self) -> None: super().__init__() self.stacked_widget = QStackedWidget() - self.stacked_widget.addWidget(QLabel('1234')) - self.stacked_widget.addWidget(QLabel('ABCD')) - self.stacked_widget.addWidget(QLabel('FOO_BAR')) + self.stacked_widget.addWidget(QLabel("1234")) + self.stacked_widget.addWidget(QLabel("ABCD")) + self.stacked_widget.addWidget(QLabel("FOO_BAR")) - self.button_123 = QPushButton('1234') + self.button_123 = QPushButton("1234") self.button_123.clicked.connect(lambda: self.stacked_widget.setCurrentIndex(0)) - self.button_ABCD = QPushButton('ABCD') + self.button_ABCD = QPushButton("ABCD") self.button_ABCD.clicked.connect(lambda: self.stacked_widget.setCurrentIndex(1)) - self.button_FOO_BAR = QPushButton('FOO_BAR') - self.button_FOO_BAR.clicked.connect(lambda: self.stacked_widget.setCurrentIndex(2)) + self.button_FOO_BAR = QPushButton("FOO_BAR") + self.button_FOO_BAR.clicked.connect( + lambda: self.stacked_widget.setCurrentIndex(2) + ) layout_buttons = QVBoxLayout() layout_buttons.addWidget(self.button_123) @@ -35,7 +45,7 @@ def __init__(self): main_layout.addWidget(self.stacked_widget) -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) mw = MainWindow() diff --git a/qt__pyqt__pyside__pyqode/example_QStackedWidget/use_listwidget.py b/qt__pyqt__pyside__pyqode/example_QStackedWidget/use_listwidget.py index c5459772e..0fcdaee31 100644 --- a/qt__pyqt__pyside__pyqode/example_QStackedWidget/use_listwidget.py +++ b/qt__pyqt__pyside__pyqode/example_QStackedWidget/use_listwidget.py @@ -1,32 +1,41 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -from PyQt5.QtWidgets import QApplication, QHBoxLayout, QWidget, QLabel, QStackedWidget, QListWidget +from PyQt5.QtWidgets import ( + QApplication, + QHBoxLayout, + QWidget, + QLabel, + QStackedWidget, + QListWidget, +) class MainWindow(QWidget): - def __init__(self): + def __init__(self) -> None: super().__init__() self.stacked_widget = QStackedWidget() - self.stacked_widget.addWidget(QLabel('1234')) - self.stacked_widget.addWidget(QLabel('ABCD')) - self.stacked_widget.addWidget(QLabel('FOO_BAR')) + self.stacked_widget.addWidget(QLabel("1234")) + self.stacked_widget.addWidget(QLabel("ABCD")) + self.stacked_widget.addWidget(QLabel("FOO_BAR")) self.control_list = QListWidget() - self.control_list.addItems(['1234', 'ABCD', 'FOO_BAR']) + self.control_list.addItems(["1234", "ABCD", "FOO_BAR"]) self.control_list.setFixedWidth(80) - self.control_list.clicked.connect(lambda index: self.stacked_widget.setCurrentIndex(index.row())) + self.control_list.clicked.connect( + lambda index: self.stacked_widget.setCurrentIndex(index.row()) + ) main_layout = QHBoxLayout(self) main_layout.addWidget(self.control_list) main_layout.addWidget(self.stacked_widget) -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) mw = MainWindow() diff --git a/qt__pyqt__pyside__pyqode/extractor_html_using_QWebEnginePage.py b/qt__pyqt__pyside__pyqode/extractor_html_using_QWebEnginePage.py deleted file mode 100644 index 1affddc74..000000000 --- a/qt__pyqt__pyside__pyqode/extractor_html_using_QWebEnginePage.py +++ /dev/null @@ -1,47 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -__author__ = 'ipetrash' - - -# Основа взята из http://stackoverflow.com/a/37755811/5909792 -def get_html(url, check_content_func=None): - from PyQt5.QtCore import QUrl - from PyQt5.QtWidgets import QApplication - from PyQt5.QtWebEngineWidgets import QWebEnginePage - - class ExtractorHtml: - def __init__(self, url): - self.html = None - - _app = QApplication([]) - self._page = QWebEnginePage() - self._page.load(QUrl(url)) - self._page.loadFinished.connect(self._load_finished_handler) - - # Ожидание загрузки страницы и получения его содержимого - # Этот цикл асинхронный код делает синхронным - while self.html is None: - _app.processEvents() - - _app.quit() - - self._page = None - - def _callable(self, data): - if check_content_func: - if check_content_func(data): - self.html = data - - else: - self.html = data - - def _load_finished_handler(self): - self._page.toHtml(self._callable) - - return ExtractorHtml(url).html - - -url = 'http://www.dns-shop.ru/' -html = get_html(url, lambda html: 'price-list-downloader' in html) -print(html) diff --git a/qt__pyqt__pyside__pyqode/filling the large list without delay Qt/async.py b/qt__pyqt__pyside__pyqode/filling the large list without delay Qt/async.py index e46bd6dcc..d51d5a8ff 100644 --- a/qt__pyqt__pyside__pyqode/filling the large list without delay Qt/async.py +++ b/qt__pyqt__pyside__pyqode/filling the large list without delay Qt/async.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """ @@ -18,11 +18,11 @@ from PyQt5.QtCore import QThread, pyqtSignal -def log_uncaught_exceptions(ex_cls, ex, tb): - text = '{}: {}:\n'.format(ex_cls.__name__, ex) - text += ''.join(traceback.format_tb(tb)) +def log_uncaught_exceptions(ex_cls, ex, tb) -> None: + text = f"{ex_cls.__name__}: {ex}:\n" + text += "".join(traceback.format_tb(tb)) - print('Error: ', text) + print("Error: ", text) sys.excepthook = log_uncaught_exceptions @@ -31,13 +31,13 @@ def log_uncaught_exceptions(ex_cls, ex, tb): class MyThread(QThread): about_new_value = pyqtSignal(int) - def __init__(self): + def __init__(self) -> None: super().__init__() self.executed = False - def run(self): - print('start thread') + def run(self) -> None: + print("start thread") try: for i in range(1000000): @@ -51,7 +51,7 @@ def run(self): time.sleep(0.005) finally: - print('finish thread') + print("finish thread") def start(self, priority=QThread.InheritPriority): self.executed = True @@ -65,7 +65,7 @@ def exit(self, returnCode=0): class Widget(QWidget): - def __init__(self): + def __init__(self) -> None: super().__init__() self.lw = QListWidget() @@ -79,19 +79,19 @@ def __init__(self): self.thread = MyThread() self.thread.about_new_value.connect(lambda x: self.append_item(str(x))) - def append_item(self, item): + def append_item(self, item) -> None: self.lw.addItem(item) self.lw.scrollToBottom() - def fill(self): + def fill(self) -> None: self.thread.start() - def closeEvent(self, event): + def closeEvent(self, event) -> None: # После закрытия окна приложение не завершится пока список работает sys.exit() -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) w = Widget() diff --git a/qt__pyqt__pyside__pyqode/filling the large list without delay Qt/sync.py b/qt__pyqt__pyside__pyqode/filling the large list without delay Qt/sync.py index 99e694b01..ef922ccf3 100644 --- a/qt__pyqt__pyside__pyqode/filling the large list without delay Qt/sync.py +++ b/qt__pyqt__pyside__pyqode/filling the large list without delay Qt/sync.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """ @@ -16,11 +16,11 @@ from PyQt5.QtWidgets import QWidget, QListWidget, QApplication, QVBoxLayout -def log_uncaught_exceptions(ex_cls, ex, tb): - text = '{}: {}:\n'.format(ex_cls.__name__, ex) - text += ''.join(traceback.format_tb(tb)) +def log_uncaught_exceptions(ex_cls, ex, tb) -> None: + text = f"{ex_cls.__name__}: {ex}:\n" + text += "".join(traceback.format_tb(tb)) - print('Error: ', text) + print("Error: ", text) sys.excepthook = log_uncaught_exceptions @@ -33,7 +33,7 @@ def generator_large_list(): class Widget(QWidget): - def __init__(self): + def __init__(self) -> None: super().__init__() self.lw = QListWidget() @@ -44,17 +44,17 @@ def __init__(self): self.setLayout(layout) - def fill(self): + def fill(self) -> None: for i in generator_large_list(): self.lw.addItem(str(i)) self.lw.scrollToBottom() - def closeEvent(self, event): + def closeEvent(self, event) -> None: # После закрытия окна приложение не завершится пока список работает sys.exit() -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) w = Widget() diff --git a/qt__pyqt__pyside__pyqode/full_black_screen.py b/qt__pyqt__pyside__pyqode/full_black_screen.py index fd6715d2d..ac42ed33a 100644 --- a/qt__pyqt__pyside__pyqode/full_black_screen.py +++ b/qt__pyqt__pyside__pyqode/full_black_screen.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PyQt5.QtWidgets import QApplication, QWidget @@ -10,7 +10,7 @@ class MainWindow(QWidget): - def __init__(self): + def __init__(self) -> None: super().__init__() self.setMouseTracking(True) @@ -18,22 +18,22 @@ def __init__(self): self._enabledClose = False QTimer.singleShot(5000, self._setEnabledClose) - def _setEnabledClose(self): + def _setEnabledClose(self) -> None: self._enabledClose = True - def mouseMoveEvent(self, event): + def mouseMoveEvent(self, event) -> None: if self._enabledClose: self.close() - def mousePressEvent(self, event): + def mousePressEvent(self, event) -> None: if self._enabledClose: self.close() - def keyPressEvent(self, event): + def keyPressEvent(self, event) -> None: if self._enabledClose: self.close() - def paintEvent(self, event): + def paintEvent(self, event) -> None: color = Qt.black painter = QPainter(self) @@ -43,7 +43,7 @@ def paintEvent(self, event): painter.drawRect(self.rect()) -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) mw = MainWindow() diff --git a/qt__pyqt__pyside__pyqode/full_black_screen_close_manual.py b/qt__pyqt__pyside__pyqode/full_black_screen_close_manual.py index 6ebb2e940..251972571 100644 --- a/qt__pyqt__pyside__pyqode/full_black_screen_close_manual.py +++ b/qt__pyqt__pyside__pyqode/full_black_screen_close_manual.py @@ -1,18 +1,27 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QToolButton, QSizePolicy, QGraphicsOpacityEffect -from PyQt5.QtCore import QPropertyAnimation, QSequentialAnimationGroup +from PyQt5.QtWidgets import ( + QApplication, + QWidget, + QVBoxLayout, + QToolButton, + QSizePolicy, + QGraphicsOpacityEffect, +) +from PyQt5.QtGui import QKeyEvent +from PyQt5.QtCore import QPropertyAnimation, QSequentialAnimationGroup, Qt class MainWindow(QWidget): - def __init__(self, background_color: str = 'black', text_color: str = 'white'): + def __init__(self, background_color: str = "black", text_color: str = "white") -> None: super().__init__() - self.setStyleSheet(f""" + self.setStyleSheet( + f""" MainWindow {{ background-color: {background_color}; }} @@ -22,10 +31,11 @@ def __init__(self, background_color: str = 'black', text_color: str = 'white'): background-color: {background_color}; border: 1px solid darkgray; }} - """) + """ + ) self.button_close = QToolButton() - self.button_close.setText('ЗАКРЫТЬ') + self.button_close.setText("ЗАКРЫТЬ") self.button_close.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) self.button_close.setFixedHeight(60) self.button_close.clicked.connect(self.close) @@ -36,11 +46,11 @@ def __init__(self, background_color: str = 'black', text_color: str = 'white'): main_layout.addStretch() main_layout.addWidget(self.button_close) - def _add_animations(self): + def _add_animations(self) -> None: self.button_close.setGraphicsEffect(QGraphicsOpacityEffect()) animation_object = self.button_close.graphicsEffect() - animation_property = b'opacity' + animation_property = b"opacity" duration = 5000 start_value = 1.0 end_value = 0.0 @@ -61,8 +71,12 @@ def _add_animations(self): self.animation_group.addAnimation(animation2) self.animation_group.start() + def keyPressEvent(self, event: QKeyEvent) -> None: + if event.key() in [Qt.Key_Escape, Qt.Key_Return, Qt.Key_Enter, Qt.Key_Space]: + self.close() -if __name__ == '__main__': + +if __name__ == "__main__": app = QApplication([]) mw = MainWindow() diff --git a/qt__pyqt__pyside__pyqode/full_black_screen_close_manual_with_animations.py b/qt__pyqt__pyside__pyqode/full_black_screen_close_manual_with_animations.py new file mode 100644 index 000000000..dfc1dd7cf --- /dev/null +++ b/qt__pyqt__pyside__pyqode/full_black_screen_close_manual_with_animations.py @@ -0,0 +1,173 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import enum +from random import randint, choice + +from PyQt5.QtWidgets import QApplication, QWidget +from PyQt5.QtGui import QPainter, QPaintEvent +from PyQt5.QtCore import QTimer + +from full_black_screen_close_manual import MainWindow as BaseMainWindow +from pyq5__simple_balls__with_part_transparent_body import ( + Ball as BaseBall, + get_random_vector, + get_random_color, +) + + +class Animation: + def __init__(self, owner: QWidget = None) -> None: + self.owner = owner + + def set_owner(self, owner: QWidget) -> None: + self.owner = owner + + def prepare(self) -> None: + pass + + def tick(self) -> None: + pass + + def draw(self, painter: QPainter) -> None: + pass + + +class DirectionEnum(enum.IntEnum): + UP = 1 + DOWN = -1 + + +class Ball(BaseBall): + def __init__(self, x, y, r, v_x, v_y, color) -> None: + super().__init__(x, y, r, v_x, v_y, color) + + self.animation_alpha_direction = choice(list(DirectionEnum)) + + +class AnimationBalls(Animation): + def __init__( + self, + owner: QWidget = None, + number_balls: int = 50, + min_ball_alpha_color: int = 35, # Минимальное значение может быть 0 + max_ball_alpha_color: int = 160, # Максимальное значение может быть 255 + animation_ball_alpha_color: bool = True, + ) -> None: + super().__init__(owner) + + self.balls: list[Ball] = [] + self.number_balls = number_balls + self.min_ball_alpha_color = min_ball_alpha_color + self.max_ball_alpha_color = max_ball_alpha_color + self.animation_ball_alpha_color = animation_ball_alpha_color + + def prepare(self) -> None: + for _ in range(self.number_balls): + self.append_random_ball() + + def append_random_ball(self) -> None: + x = self.owner.width() // 2 + randint( + -self.owner.width() // 3, self.owner.width() // 3 + ) + y = self.owner.height() // 2 + randint( + -self.owner.height() // 3, self.owner.height() // 3 + ) + v_x, v_y = get_random_vector() + r, g, b = get_random_color() + a = randint(self.min_ball_alpha_color, self.max_ball_alpha_color) + + ball = Ball( + x, + y, + r=randint(50, 70), + v_x=v_x, + v_y=v_y, + color=(r, g, b, a), + ) + self.balls.append(ball) + + def tick(self) -> None: + for ball in self.balls: + ball.update() + + # Условия отскакивания шарика от левого и правого края + if ball.left <= 0 or ball.right >= self.owner.width(): + ball.v_x = -ball.v_x + + # Условия отскакивания шарика верхнего и нижнего края + if ball.top <= 0 or ball.bottom >= self.owner.height(): + ball.v_y = -ball.v_y + + if self.animation_ball_alpha_color: + alpha = ball.color[3] + ball.animation_alpha_direction + + if alpha >= self.max_ball_alpha_color: + ball.animation_alpha_direction = DirectionEnum.DOWN + + if alpha <= self.min_ball_alpha_color: + ball.animation_alpha_direction = DirectionEnum.UP + + ball.color = ball.color[:3] + (alpha,) + + def draw(self, painter: QPainter) -> None: + for ball in self.balls: + ball.draw(painter) + + +class MainWindow(BaseMainWindow): + def __init__(self, animations: list[Animation] = None) -> None: + super().__init__() + + self.animations: list[Animation] = animations + + # Таймер для обновления анимаций + self.timer = QTimer() + self.timer.setInterval(1000 // 20) + self.timer.timeout.connect(self.tick) + + def tick(self) -> None: + if not self.animations: + return + + for animation in self.animations: + animation.tick() + + self.update() + + def start_animations(self) -> None: + if not self.animations: + return + + for animation in self.animations: + animation.set_owner(self) + animation.prepare() + + self.timer.start() + + def paintEvent(self, event: QPaintEvent) -> None: + super().paintEvent(event) + + painter = QPainter(self) + painter.setRenderHint(QPainter.Antialiasing) + + for animation in self.animations: + animation.draw(painter) + + +if __name__ == "__main__": + app = QApplication([]) + + mw = MainWindow( + animations=[ + AnimationBalls(), + ] + ) + mw.resize(600, 600) + mw.showFullScreen() + mw.start_animations() + + app.exec() diff --git a/qt__pyqt__pyside__pyqode/full_white_screen_close_manual.py b/qt__pyqt__pyside__pyqode/full_white_screen_close_manual.py index b6197ab7a..b03d41b33 100644 --- a/qt__pyqt__pyside__pyqode/full_white_screen_close_manual.py +++ b/qt__pyqt__pyside__pyqode/full_white_screen_close_manual.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PyQt5.QtWidgets import QApplication @@ -9,10 +9,10 @@ from full_black_screen_close_manual import MainWindow -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) - mw = MainWindow(background_color='white', text_color='black') + mw = MainWindow(background_color="white", text_color="black") mw.resize(600, 600) mw.showFullScreen() diff --git a/qt__pyqt__pyside__pyqode/get_html_from_Qt5_QWebEnginePage.py b/qt__pyqt__pyside__pyqode/get_html_from_Qt5_QWebEnginePage.py index 4d234ff76..b88e0790b 100644 --- a/qt__pyqt__pyside__pyqode/get_html_from_Qt5_QWebEnginePage.py +++ b/qt__pyqt__pyside__pyqode/get_html_from_Qt5_QWebEnginePage.py @@ -1,17 +1,18 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" + + +from PyQt5.QtCore import QUrl +from PyQt5.QtWidgets import QApplication +from PyQt5.QtWebEngineWidgets import QWebEnginePage # Основа взята из http://stackoverflow.com/a/37755811/5909792 def get_html(url): - from PyQt5.QtCore import QUrl - from PyQt5.QtWidgets import QApplication - from PyQt5.QtWebEngineWidgets import QWebEnginePage - class ExtractorHtml: - def __init__(self, url): + def __init__(self, url) -> None: _app = QApplication([]) self._page = QWebEnginePage() self._page.loadFinished.connect(self._load_finished_handler) @@ -35,10 +36,10 @@ def __init__(self, url): # Чтобы избежать падений скрипта self._page = None - def _callable(self, data): + def _callable(self, data) -> None: self.html = data - def _load_finished_handler(self, _): + def _load_finished_handler(self, _) -> None: self._counter_finished += 1 if self._counter_finished == 2: @@ -47,6 +48,6 @@ def _load_finished_handler(self, _): return ExtractorHtml(url).html -if __name__ == '__main__': - url = 'http://gama-gama.ru/search/?searchField=titan' +if __name__ == "__main__": + url = "http://gama-gama.ru/search/?searchField=titan" print(get_html(url)) diff --git a/qt__pyqt__pyside__pyqode/get_mouse_cursor_to_pixmap__win10/gui_with_timer.py b/qt__pyqt__pyside__pyqode/get_mouse_cursor_to_pixmap__win10/gui_with_timer.py index 76915cfe6..46db7da3a 100644 --- a/qt__pyqt__pyside__pyqode/get_mouse_cursor_to_pixmap__win10/gui_with_timer.py +++ b/qt__pyqt__pyside__pyqode/get_mouse_cursor_to_pixmap__win10/gui_with_timer.py @@ -1,22 +1,23 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" +import win32gui + from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QVBoxLayout from PyQt5.QtGui import QPixmap from PyQt5.QtCore import QTimer from PyQt5.QtWinExtras import QtWin -import win32gui class MainWindow(QWidget): - def __init__(self): + def __init__(self) -> None: super().__init__() - self.DEFAULT_MOUSE_PIXMAP = QPixmap('default_mouse.png').scaledToWidth(16) + self.DEFAULT_MOUSE_PIXMAP = QPixmap("default_mouse.png").scaledToWidth(16) self.label = QLabel() @@ -28,7 +29,7 @@ def __init__(self): self.timer.setInterval(100) self.timer.start() - def _on_tick(self): + def _on_tick(self) -> None: # flags, hcursor, (x, y) = _, hcursor, _ = win32gui.GetCursorInfo() @@ -42,7 +43,7 @@ def _on_tick(self): self.label.setPixmap(pixmap) -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) mw = MainWindow() diff --git a/qt__pyqt__pyside__pyqode/get_mouse_cursor_to_pixmap__win10/main.py b/qt__pyqt__pyside__pyqode/get_mouse_cursor_to_pixmap__win10/main.py index 6664295b9..cdd1fb928 100644 --- a/qt__pyqt__pyside__pyqode/get_mouse_cursor_to_pixmap__win10/main.py +++ b/qt__pyqt__pyside__pyqode/get_mouse_cursor_to_pixmap__win10/main.py @@ -1,14 +1,14 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" +import win32gui + from PyQt5.QtWidgets import QApplication from PyQt5.QtWinExtras import QtWin -import win32gui - app = QApplication([]) @@ -21,4 +21,4 @@ pixmap = QtWin.fromHBITMAP(hbmColor.handle, QtWin.HBitmapPremultipliedAlpha) print(pixmap.size(), pixmap) -pixmap.save('img.png') +pixmap.save("img.png") diff --git a/qt__pyqt__pyside__pyqode/hello_world__QWebEngineView.py b/qt__pyqt__pyside__pyqode/hello_world__QWebEngineView.py index f7a3f7096..f4628243c 100644 --- a/qt__pyqt__pyside__pyqode/hello_world__QWebEngineView.py +++ b/qt__pyqt__pyside__pyqode/hello_world__QWebEngineView.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PyQt5.QtCore import QUrl @@ -14,7 +14,7 @@ view = QWebEngineView() view.show() -url = 'https://www.google.com/search?q=hello world' +url = "https://www.google.com/search?q=hello world" view.load(QUrl(url)) app.exec() diff --git a/qt__pyqt__pyside__pyqode/highlight previous tab Qt.py b/qt__pyqt__pyside__pyqode/highlight previous tab Qt.py index 5b7f50463..09e1b1718 100644 --- a/qt__pyqt__pyside__pyqode/highlight previous tab Qt.py +++ b/qt__pyqt__pyside__pyqode/highlight previous tab Qt.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """ @@ -14,25 +14,25 @@ class MainWindow(QMainWindow): - def __init__(self): + def __init__(self) -> None: super().__init__() self.prev_tab_index = -1 self.curr_tab_index = 0 self.tab_widget = QTabWidget() - self.tab_widget.addTab(QLabel('1'), '1') - self.tab_widget.addTab(QLabel('2'), '2') - self.tab_widget.addTab(QLabel('3'), '3') - self.tab_widget.addTab(QLabel('4'), '4') - self.tab_widget.addTab(QLabel('5'), '5') + self.tab_widget.addTab(QLabel("1"), "1") + self.tab_widget.addTab(QLabel("2"), "2") + self.tab_widget.addTab(QLabel("3"), "3") + self.tab_widget.addTab(QLabel("4"), "4") + self.tab_widget.addTab(QLabel("5"), "5") self.tab_widget.setCurrentIndex(self.curr_tab_index) self.tab_widget.currentChanged.connect(self.current_tab_changed) self.setCentralWidget(self.tab_widget) - def current_tab_changed(self, i): + def current_tab_changed(self, i) -> None: # Запоминание индексов предыдущей и текущей вкладки. # При переключении вкладок: # У предыдущей убирается выделение @@ -45,12 +45,12 @@ def current_tab_changed(self, i): self.prev_tab_index = self.curr_tab_index prev_text = self.tab_widget.tabText(self.prev_tab_index) - self.tab_widget.setTabText(self.prev_tab_index, '_' + prev_text + '_') + self.tab_widget.setTabText(self.prev_tab_index, "_" + prev_text + "_") self.curr_tab_index = i -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) mw = MainWindow() diff --git a/qt__pyqt__pyside__pyqode/hook_exceptions_in_Qt.py b/qt__pyqt__pyside__pyqode/hook_exceptions_in_Qt.py index 26f99c2a6..17305c9de 100644 --- a/qt__pyqt__pyside__pyqode/hook_exceptions_in_Qt.py +++ b/qt__pyqt__pyside__pyqode/hook_exceptions_in_Qt.py @@ -1,32 +1,32 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" +import sys +import traceback + from PyQt5.QtWidgets import * # Для отлова всех исключений, которые в слотах Qt могут "затеряться" и привести к тихому падению -def log_uncaught_exceptions(ex_cls, ex, tb): - text = '{}: {}:\n'.format(ex_cls.__name__, ex) +def log_uncaught_exceptions(ex_cls, ex, tb) -> None: + text = f"{ex_cls.__name__}: {ex}:\n" + text += "".join(traceback.format_tb(tb)) - import traceback - text += ''.join(traceback.format_tb(tb)) - - print('Error: ', text) - QMessageBox.critical(None, 'Error', text) + print("Error: ", text) + QMessageBox.critical(None, "Error", text) sys.exit(1) -import sys sys.excepthook = log_uncaught_exceptions app = QApplication([]) w = QLineEdit() -w.textChanged.conneect(lambda x: 1 + '1') +w.textChanged.conneect(lambda x: 1 + "1") # Emit textChanged signal -w.setText('!!!') +w.setText("!!!") diff --git a/qt__pyqt__pyside__pyqode/image on whole cell in QTableWidget/main.py b/qt__pyqt__pyside__pyqode/image on whole cell in QTableWidget/main.py index f8c5cf186..5a8ee92f8 100644 --- a/qt__pyqt__pyside__pyqode/image on whole cell in QTableWidget/main.py +++ b/qt__pyqt__pyside__pyqode/image on whole cell in QTableWidget/main.py @@ -1,9 +1,12 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" +import sys +import traceback + try: from PyQt5.QtWidgets import * from PyQt5.QtGui import * @@ -14,17 +17,15 @@ # Для отлова всех исключений, которые в слотах Qt могут "затеряться" и привести к тихому падению -def log_uncaught_exceptions(ex_cls, ex, tb): - text = '{}: {}:\n'.format(ex_cls.__name__, ex) - import traceback - text += ''.join(traceback.format_tb(tb)) +def log_uncaught_exceptions(ex_cls, ex, tb) -> None: + text = f"{ex_cls.__name__}: {ex}:\n" + text += "".join(traceback.format_tb(tb)) - print('Error: ', text) - QMessageBox.critical(None, 'Error', text) + print("Error: ", text) + QMessageBox.critical(None, "Error", text) sys.exit(1) -import sys sys.excepthook = log_uncaught_exceptions @@ -36,7 +37,7 @@ def create_item(img): class MyDelegate_1(QStyledItemDelegate): - def paint(self, painter, option, index): + def paint(self, painter, option, index) -> None: img = index.model().data(index, Qt.DecorationRole) if img is None: super().paint(painter, option, index) @@ -68,7 +69,7 @@ def paint(self, painter, option, index): class MyDelegate_2(QStyledItemDelegate): - def paint(self, painter, option, index): + def paint(self, painter, option, index) -> None: img = index.model().data(index, Qt.DecorationRole) if img is None: super().paint(painter, option, index) @@ -106,7 +107,7 @@ def paint(self, painter, option, index): # super().paint(painter, option, index) -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) table = QTableWidget() @@ -114,15 +115,15 @@ def paint(self, painter, option, index): table.show() table.resize(400, 200) - headers = ['Normal', 'Delegate v1', 'Delegate v1 (without img)', 'Delegate v2'] + headers = ["Normal", "Delegate v1", "Delegate v1 (without img)", "Delegate v2"] table.setColumnCount(len(headers)) table.setHorizontalHeaderLabels(headers) table.setRowCount(3) table.verticalHeader().hide() - pix_1 = QPixmap('favicon_google.png') - pix_2 = QPixmap('favicon_prog_org.png') - pix_3 = QPixmap('favicon_google_tr.png') + pix_1 = QPixmap("favicon_google.png") + pix_2 = QPixmap("favicon_prog_org.png") + pix_3 = QPixmap("favicon_google_tr.png") for col in range(table.columnCount()): if col == 2: diff --git a/qt__pyqt__pyside__pyqode/images__QListWidget__QTableWidget_QStyledItemDelegate.py b/qt__pyqt__pyside__pyqode/images__QListWidget__QTableWidget_QStyledItemDelegate.py index 88281062c..27ba3cf83 100644 --- a/qt__pyqt__pyside__pyqode/images__QListWidget__QTableWidget_QStyledItemDelegate.py +++ b/qt__pyqt__pyside__pyqode/images__QListWidget__QTableWidget_QStyledItemDelegate.py @@ -1,25 +1,31 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -import datetime as DT +import datetime as dt from glob import glob from pathlib import Path from PyQt5.QtWidgets import ( - QApplication, QListWidget, QListWidgetItem, QTableWidget, QTableWidgetItem, QStyledItemDelegate, QStyle + QApplication, + QListWidget, + QListWidgetItem, + QTableWidget, + QTableWidgetItem, + QStyledItemDelegate, + QStyle, ) from PyQt5.QtGui import QIcon, QPixmap from PyQt5.QtCore import QSize, Qt class ImageDelegate(QStyledItemDelegate): - def __init__(self, parent=None): + def __init__(self, parent=None) -> None: super().__init__(parent) - def paint(self, painter, option, index): + def paint(self, painter, option, index) -> None: pixmap = index.data(Qt.DecorationRole) if pixmap: # pixmap = pixmap.scaled( @@ -40,14 +46,15 @@ def paint(self, painter, option, index): ICON_HEIGHT = 128 -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) - file_names = glob(r'D:\все фотки\**\*.jpg', recursive=True) - print(f'Files: {len(file_names)}') + file_names = glob(r"D:\все фотки\**\*.jpg", recursive=True) + print(f"Files: {len(file_names)}") list_widget = QListWidget() - list_widget.setStyleSheet(""" + list_widget.setStyleSheet( + """ QListWidget::item { border: 1px solid gray; } @@ -55,7 +62,8 @@ def paint(self, painter, option, index): color: black; background-color: #0087BD; } - """) + """ + ) list_widget.setMovement(QListWidget.Static) list_widget.setDragEnabled(False) list_widget.setDragDropMode(QListWidget.NoDragDrop) @@ -70,7 +78,7 @@ def paint(self, painter, option, index): list_widget.resize(800, 600) list_widget.show() - headers = ['IMAGE', 'FILE NAME', 'DIRECTORY'] + headers = ["IMAGE", "FILE NAME", "DIRECTORY"] table_widget = QTableWidget() table_widget.setColumnCount(len(headers)) table_widget.setHorizontalHeaderLabels(headers) @@ -82,12 +90,16 @@ def paint(self, painter, option, index): table_widget.resize(800, 600) table_widget.show() - start_time = DT.datetime.now() + start_time = dt.datetime.now() row = 0 for i, file_name in enumerate(file_names, 1): - list_widget.setWindowTitle(f'{i} / {len(file_names)} ({i / len(file_names):.0%})') - table_widget.setWindowTitle(f'{i} / {len(file_names)} ({i / len(file_names):.0%})') + list_widget.setWindowTitle( + f"{i} / {len(file_names)} ({i / len(file_names):.0%})" + ) + table_widget.setWindowTitle( + f"{i} / {len(file_names)} ({i / len(file_names):.0%})" + ) pixmap = QPixmap(file_name) if pixmap.isNull(): @@ -98,10 +110,7 @@ def paint(self, painter, option, index): ) base_file_name = Path(file_name).name - item = QListWidgetItem( - QIcon(pixmap), - base_file_name - ) + item = QListWidgetItem(QIcon(pixmap), base_file_name) item.setTextAlignment(Qt.AlignHCenter | Qt.AlignBottom) item.setSizeHint(QSize(ICON_WIDTH, ICON_HEIGHT + 20)) list_widget.addItem(item) @@ -119,10 +128,14 @@ def paint(self, painter, option, index): QApplication.processEvents() list_widget.setWindowTitle( - list_widget.windowTitle() + '. Elapsed: ' + str(DT.datetime.now() - start_time).split('.')[0] + list_widget.windowTitle() + + ". Elapsed: " + + str(dt.datetime.now() - start_time).split(".")[0] ) table_widget.setWindowTitle( - table_widget.windowTitle() + '. Elapsed: ' + str(DT.datetime.now() - start_time).split('.')[0] + table_widget.windowTitle() + + ". Elapsed: " + + str(dt.datetime.now() - start_time).split(".")[0] ) app.exec() diff --git a/qt__pyqt__pyside__pyqode/images_widget__api.vk photos.search requests qt.py b/qt__pyqt__pyside__pyqode/images_widget__api.vk photos.search requests qt.py index 29ee8612e..68c79ca65 100644 --- a/qt__pyqt__pyside__pyqode/images_widget__api.vk photos.search requests qt.py +++ b/qt__pyqt__pyside__pyqode/images_widget__api.vk photos.search requests qt.py @@ -1,9 +1,11 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" +import requests + try: from PyQt5.QtWidgets import * from PyQt5.QtGui import * @@ -20,13 +22,12 @@ def get_img_urls(): - import requests rs = requests.get("https://api.vk.com/method/photos.search?v=5.64") img_urls = rs.json()["response"]["items"] - return [url['photo_130'] for url in img_urls] + return [url["photo_130"] for url in img_urls] -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) mw = QScrollArea() @@ -40,9 +41,9 @@ def get_img_urls(): columns = 4 i, j = 0, 0 - mw.setWindowTitle('Loading...') + mw.setWindowTitle("Loading...") - print('Images:', len(img_urls)) + print("Images:", len(img_urls)) url_by_label = dict() @@ -65,7 +66,6 @@ def get_img_urls(): mw.setWidget(w) for n, (url, label) in enumerate(url_by_label.items()): - import requests rs = requests.get(url) image = QImage.fromData(rs.content) @@ -73,9 +73,9 @@ def get_img_urls(): label.setPixmap(pixmap) - mw.setWindowTitle('{}/{} Loading...'.format(n, len(url_by_label))) + mw.setWindowTitle(f"{n}/{len(url_by_label)} Loading...") QApplication.processEvents() - mw.setWindowTitle('Ok!') + mw.setWindowTitle("Ok!") app.exec() diff --git a/qt__pyqt__pyside__pyqode/install_translator.py b/qt__pyqt__pyside__pyqode/install_translator.py new file mode 100644 index 000000000..7ac4ed192 --- /dev/null +++ b/qt__pyqt__pyside__pyqode/install_translator.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from PyQt5.QtWidgets import QApplication, QMessageBox +from PyQt5.QtCore import QTranslator, QLibraryInfo + + +def question() -> None: + QMessageBox.question( + None, + "TITLE", + "TEXT", + QMessageBox.Yes | QMessageBox.No | QMessageBox.Abort, + ) + + +app = QApplication([]) + +# NOTE: EN +question() + +translator = QTranslator() +if translator.load("qtbase_ru", directory=QLibraryInfo.location(QLibraryInfo.TranslationsPath)): + app.installTranslator(translator) + +# NOTE: RU +question() diff --git a/qt__pyqt__pyside__pyqode/label_framed.py b/qt__pyqt__pyside__pyqode/label_framed.py index c0caf5682..3f249fa00 100644 --- a/qt__pyqt__pyside__pyqode/label_framed.py +++ b/qt__pyqt__pyside__pyqode/label_framed.py @@ -1,20 +1,20 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PyQt5 import Qt class Widget(Qt.QWidget): - def __init__(self): + def __init__(self) -> None: super().__init__() layout = Qt.QVBoxLayout() - layout.addWidget(self.create_label_framed('Hello')) - layout.addWidget(self.create_label_framed('World')) - layout.addWidget(self.create_label_framed('999')) + layout.addWidget(self.create_label_framed("Hello")) + layout.addWidget(self.create_label_framed("World")) + layout.addWidget(self.create_label_framed("999")) self.setLayout(layout) @@ -26,7 +26,7 @@ def create_label_framed(text): return label -if __name__ == '__main__': +if __name__ == "__main__": app = Qt.QApplication([]) w = Widget() diff --git a/qt__pyqt__pyside__pyqode/latin_letters_similar_to_cyrillic/main.py b/qt__pyqt__pyside__pyqode/latin_letters_similar_to_cyrillic/main.py index 51630af6a..47019a532 100644 --- a/qt__pyqt__pyside__pyqode/latin_letters_similar_to_cyrillic/main.py +++ b/qt__pyqt__pyside__pyqode/latin_letters_similar_to_cyrillic/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://ru.stackoverflow.com/questions/839750/ @@ -14,26 +14,26 @@ class MainWindow(Qt.QWidget): - def __init__(self): + def __init__(self) -> None: super().__init__() - loadUi('main.ui', self) + loadUi("main.ui", self) self.btn_solve.clicked.connect(self.solve) self.btn_clear.clicked.connect(self.textEdit_words.clear) - self.textEdit_text.setPlainText('СВЕТА РОЕТ РОВ, ВОВКА СЕЕТ ОВЁС') + self.textEdit_text.setPlainText("СВЕТА РОЕТ РОВ, ВОВКА СЕЕТ ОВЁС") - def solve(self): + def solve(self) -> None: # Исходный текст text = self.textEdit_text.toPlainText() # Буквы, совпадающие с английскими - for word in re.findall(r'\b[АВСТРХОНКМУЕ]+\b', text): + for word in re.findall(r"\b[АВСТРХОНКМУЕ]+\b", text): self.textEdit_words.append(word) -if __name__ == '__main__': +if __name__ == "__main__": app = Qt.QApplication([]) mw = MainWindow() diff --git a/qt__pyqt__pyside__pyqode/layout_append_line__horizontal_vertical.py b/qt__pyqt__pyside__pyqode/layout_append_line__horizontal_vertical.py index b0ad05da2..05ba470bc 100644 --- a/qt__pyqt__pyside__pyqode/layout_append_line__horizontal_vertical.py +++ b/qt__pyqt__pyside__pyqode/layout_append_line__horizontal_vertical.py @@ -1,14 +1,14 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PyQt5.QtWidgets import QFrame class HorizontalLineWidget(QFrame): - def __init__(self): + def __init__(self) -> None: super().__init__() self.setFrameShape(QFrame.HLine) @@ -17,7 +17,7 @@ def __init__(self): class VerticalLineWidget(QFrame): - def __init__(self): + def __init__(self) -> None: super().__init__() self.setFrameShape(QFrame.VLine) @@ -25,18 +25,26 @@ def __init__(self): self.setLineWidth(1) -if __name__ == '__main__': - from PyQt5.QtWidgets import QApplication, QFormLayout, QHBoxLayout, QLineEdit, QCheckBox, QWidget, QLabel +if __name__ == "__main__": + from PyQt5.QtWidgets import ( + QApplication, + QFormLayout, + QHBoxLayout, + QLineEdit, + QCheckBox, + QWidget, + QLabel, + ) from PyQt5.QtCore import Qt app = QApplication([]) layout = QFormLayout() - layout.addRow('First', QLineEdit()) - layout.addRow('Last', QLineEdit()) + layout.addRow("First", QLineEdit()) + layout.addRow("Last", QLineEdit()) layout.addRow(HorizontalLineWidget()) - layout.addRow('Phone', QLineEdit()) + layout.addRow("Phone", QLineEdit()) layout.addRow(HorizontalLineWidget()) h_layout = QHBoxLayout() @@ -56,7 +64,7 @@ def __init__(self): layout.addItem(h_layout) - layout.addRow('Ok?', QCheckBox()) + layout.addRow("Ok?", QCheckBox()) mw = QWidget() mw.setLayout(layout) diff --git a/qt__pyqt__pyside__pyqode/layout_column_resizer/README.md b/qt__pyqt__pyside__pyqode/layout_column_resizer/README.md new file mode 100644 index 000000000..d79a879b3 --- /dev/null +++ b/qt__pyqt__pyside__pyqode/layout_column_resizer/README.md @@ -0,0 +1,3 @@ +# Screenshot + +![screenshot.png](screenshot.png) diff --git a/qt__pyqt__pyside__pyqode/layout_column_resizer/column_resizer.py b/qt__pyqt__pyside__pyqode/layout_column_resizer/column_resizer.py new file mode 100644 index 000000000..f3a07c642 --- /dev/null +++ b/qt__pyqt__pyside__pyqode/layout_column_resizer/column_resizer.py @@ -0,0 +1,179 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +# SOURCE: https://github.com/agateau/columnresizer + + +from dataclasses import dataclass + +from PyQt5.QtCore import QEvent, QTimer, QSize, QRect, Qt, QObject, qCritical +from PyQt5.QtWidgets import ( + QFormLayout, + QGridLayout, + QWidget, + QWidgetItem, + QLayout, + QLayoutItem, +) + + +class FormLayoutWidgetItem(QWidgetItem): + def __init__( + self, + widget: QWidget, + formLayout: QFormLayout, + itemRole: QFormLayout.ItemRole, + ) -> None: + super().__init__(widget) + + self.m_width: int = -1 + self.m_formLayout: QFormLayout = formLayout + self.m_itemRole: QFormLayout.ItemRole = itemRole + + def sizeHint(self) -> QSize: + size: QSize = super().sizeHint() + if self.m_width != -1: + size.setWidth(self.m_width) + + return size + + def minimumSize(self) -> QSize: + size: QSize = super().minimumSize() + if self.m_width != -1: + size.setWidth(self.m_width) + + return size + + def maximumSize(self) -> QSize: + size: QSize = super().maximumSize() + if self.m_width != -1: + size.setWidth(self.m_width) + + return size + + def setWidth(self, width: int) -> None: + if width != self.m_width: + self.m_width = width + self.invalidate() + + def setGeometry(self, _rect: QRect) -> None: + rect: QRect = _rect + width = self.widget().sizeHint().width() + if ( + self.m_itemRole == QFormLayout.LabelRole + and self.m_formLayout.labelAlignment() & Qt.AlignRight + ): + rect.setLeft(rect.right() - width) + + super().setGeometry(rect) + + def formLayout(self) -> QFormLayout: + return self.m_formLayout + + +@dataclass +class GridColumnInfo: + layout: QGridLayout + column: int + + +class ColumnResizerPrivate: + def __init__(self, q_ptr: "ColumnResizer") -> None: + self.q: ColumnResizer = q_ptr + + self.m_widgets: list[QWidget] = [] + self.m_wrWidgetItemList: list[FormLayoutWidgetItem] = [] + self.m_gridColumnInfoList: list[GridColumnInfo] = [] + + self.m_updateTimer: QTimer = QTimer(self.q) + self.m_updateTimer.setSingleShot(True) + self.m_updateTimer.setInterval(0) + self.m_updateTimer.timeout.connect(self.q.updateWidth) + + def scheduleWidthUpdate(self) -> None: + self.m_updateTimer.start() + + +class ColumnResizer(QObject): + def __init__(self, parent: QObject) -> None: + super().__init__(parent) + + self.d = ColumnResizerPrivate(self) + + def addWidget(self, widget: QWidget) -> None: + self.d.m_widgets.append(widget) + widget.installEventFilter(self) + self.d.scheduleWidthUpdate() + + # NOTE: Here the logic is changed relative to the original + def updateWidth(self) -> None: + width: int = 0 + x: int = 0 + for widget in self.d.m_widgets: + x = max(widget.pos().x(), x) + width = max(widget.sizeHint().width(), width) + + width += x + + for item in self.d.m_wrWidgetItemList: + item.setWidth(width - item.widget().pos().x()) + item.formLayout().update() + + for info in self.d.m_gridColumnInfoList: + info.layout.setColumnMinimumWidth(info.column, width) + + def eventFilter(self, _: QObject, event: QEvent) -> bool: + if event.type() == QEvent.Resize: + self.d.scheduleWidthUpdate() + + return False + + def addWidgetsFromLayout(self, layout: QLayout, column: int) -> None: + assert column >= 0 + + if isinstance(layout, QGridLayout): + self.addWidgetsFromGridLayout(layout, column) + elif isinstance(layout, QFormLayout): + if column > QFormLayout.ItemRole.SpanningRole: + qCritical( + f"column should not be more than {QFormLayout.ItemRole.SpanningRole} for QFormLayout" + ) + return + + role: QFormLayout.ItemRole = QFormLayout.ItemRole(column) + self.addWidgetsFromFormLayout(layout, role) + else: + qCritical(f"Don't know how to handle layout {layout}") + + def addWidgetsFromGridLayout(self, layout: QGridLayout, column: int) -> None: + for row in range(layout.rowCount()): + item: QLayoutItem = layout.itemAtPosition(row, column) + if not item: + continue + + widget: QWidget = item.widget() + if not widget: + continue + + self.addWidget(widget) + + self.d.m_gridColumnInfoList.append(GridColumnInfo(layout, column)) + + def addWidgetsFromFormLayout(self, layout: QFormLayout, role: QFormLayout.ItemRole) -> None: + for row in range(layout.rowCount()): + item: QLayoutItem = layout.itemAt(row, role) + if not item: + continue + + widget: QWidget = item.widget() + if not widget: + continue + + layout.removeItem(item) + newItem: FormLayoutWidgetItem = FormLayoutWidgetItem(widget, layout, role) + layout.setItem(row, role, newItem) + self.addWidget(widget) + self.d.m_wrWidgetItemList.append(newItem) diff --git a/qt__pyqt__pyside__pyqode/layout_column_resizer/column_resizer_pyqt6.py b/qt__pyqt__pyside__pyqode/layout_column_resizer/column_resizer_pyqt6.py new file mode 100644 index 000000000..f3e1756ec --- /dev/null +++ b/qt__pyqt__pyside__pyqode/layout_column_resizer/column_resizer_pyqt6.py @@ -0,0 +1,179 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +# SOURCE: https://github.com/agateau/columnresizer + + +from dataclasses import dataclass + +from PyQt6.QtCore import QEvent, QTimer, QSize, QRect, Qt, QObject, qCritical +from PyQt6.QtWidgets import ( + QFormLayout, + QGridLayout, + QWidget, + QWidgetItem, + QLayout, + QLayoutItem, +) + + +class FormLayoutWidgetItem(QWidgetItem): + def __init__( + self, + widget: QWidget, + formLayout: QFormLayout, + itemRole: QFormLayout.ItemRole, + ): + super().__init__(widget) + + self.m_width: int = -1 + self.m_formLayout: QFormLayout = formLayout + self.m_itemRole: QFormLayout.ItemRole = itemRole + + def sizeHint(self) -> QSize: + size: QSize = super().sizeHint() + if self.m_width != -1: + size.setWidth(self.m_width) + + return size + + def minimumSize(self) -> QSize: + size: QSize = super().minimumSize() + if self.m_width != -1: + size.setWidth(self.m_width) + + return size + + def maximumSize(self) -> QSize: + size: QSize = super().maximumSize() + if self.m_width != -1: + size.setWidth(self.m_width) + + return size + + def setWidth(self, width: int): + if width != self.m_width: + self.m_width = width + self.invalidate() + + def setGeometry(self, _rect: QRect): + rect: QRect = _rect + width = self.widget().sizeHint().width() + if ( + self.m_itemRole == QFormLayout.ItemRole.LabelRole + and self.m_formLayout.labelAlignment() & Qt.AlignmentFlag.AlignRight + ): + rect.setLeft(rect.right() - width) + + super().setGeometry(rect) + + def formLayout(self) -> QFormLayout: + return self.m_formLayout + + +@dataclass +class GridColumnInfo: + layout: QGridLayout + column: int + + +class ColumnResizerPrivate: + def __init__(self, q_ptr: "ColumnResizer"): + self.q: ColumnResizer = q_ptr + + self.m_widgets: list[QWidget] = [] + self.m_wrWidgetItemList: list[FormLayoutWidgetItem] = [] + self.m_gridColumnInfoList: list[GridColumnInfo] = [] + + self.m_updateTimer: QTimer = QTimer(self.q) + self.m_updateTimer.setSingleShot(True) + self.m_updateTimer.setInterval(0) + self.m_updateTimer.timeout.connect(self.q.updateWidth) + + def scheduleWidthUpdate(self): + self.m_updateTimer.start() + + +class ColumnResizer(QObject): + def __init__(self, parent: QObject): + super().__init__(parent) + + self.d = ColumnResizerPrivate(self) + + def addWidget(self, widget: QWidget): + self.d.m_widgets.append(widget) + widget.installEventFilter(self) + self.d.scheduleWidthUpdate() + + # NOTE: Here the logic is changed relative to the original + def updateWidth(self): + width: int = 0 + x: int = 0 + for widget in self.d.m_widgets: + x = max(widget.pos().x(), x) + width = max(widget.sizeHint().width(), width) + + width += x + + for item in self.d.m_wrWidgetItemList: + item.setWidth(width - item.widget().pos().x()) + item.formLayout().update() + + for info in self.d.m_gridColumnInfoList: + info.layout.setColumnMinimumWidth(info.column, width) + + def eventFilter(self, _: QObject, event: QEvent) -> bool: + if event.type() == QEvent.Type.Resize: + self.d.scheduleWidthUpdate() + + return False + + def addWidgetsFromLayout(self, layout: QLayout, column: int): + assert column >= 0 + + if isinstance(layout, QGridLayout): + self.addWidgetsFromGridLayout(layout, column) + elif isinstance(layout, QFormLayout): + if column > QFormLayout.ItemRole.SpanningRole.value: + qCritical( + f"column should not be more than {QFormLayout.ItemRole.SpanningRole.value} for QFormLayout" + ) + return + + role: QFormLayout.ItemRole = QFormLayout.ItemRole(column) + self.addWidgetsFromFormLayout(layout, role) + else: + qCritical(f"Don't know how to handle layout {layout}") + + def addWidgetsFromGridLayout(self, layout: QGridLayout, column: int): + for row in range(layout.rowCount()): + item: QLayoutItem = layout.itemAtPosition(row, column) + if not item: + continue + + widget: QWidget = item.widget() + if not widget: + continue + + self.addWidget(widget) + + self.d.m_gridColumnInfoList.append(GridColumnInfo(layout, column)) + + def addWidgetsFromFormLayout(self, layout: QFormLayout, role: QFormLayout.ItemRole): + for row in range(layout.rowCount()): + item: QLayoutItem = layout.itemAt(row, role) + if not item: + continue + + widget: QWidget = item.widget() + if not widget: + continue + + layout.removeItem(item) + newItem: FormLayoutWidgetItem = FormLayoutWidgetItem(widget, layout, role) + layout.setItem(row, role, newItem) + self.addWidget(widget) + self.d.m_wrWidgetItemList.append(newItem) diff --git a/qt__pyqt__pyside__pyqode/layout_column_resizer/example.py b/qt__pyqt__pyside__pyqode/layout_column_resizer/example.py new file mode 100644 index 000000000..f22f01688 --- /dev/null +++ b/qt__pyqt__pyside__pyqode/layout_column_resizer/example.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import sys +import platform + +from datetime import datetime +from pathlib import Path + +from PyQt5.QtWidgets import ( + QDialog, + QWidget, + QFormLayout, + QLabel, + QDialogButtonBox, + QGroupBox, + QVBoxLayout, + QScrollArea, +) + +from column_resizer import ColumnResizer + + +DIR: Path = Path(__file__).resolve().parent + +PROGRAM_NAME: str = DIR.name + + +class About(QDialog): + def __init__(self, title: str, use_column_resizer: bool) -> None: + super().__init__() + + self.setWindowTitle(title) + + self._started: datetime = datetime.now() + + gb_python = QGroupBox("Python:") + gb_python_layout = QFormLayout(gb_python) + gb_python_layout.addRow( + "Version:", + QLabel(sys.version), + ) + gb_python_layout.addRow( + "Implementation:", + QLabel(platform.python_implementation()), + ) + gb_python_layout.addRow("Executable:", QLabel(sys.executable)) + + button_box = QDialogButtonBox(QDialogButtonBox.Ok) + button_box.accepted.connect(self.accept) + button_box.rejected.connect(self.reject) + + fields_layout = QFormLayout() + fields_layout.addRow( + "Version:", + QLabel("1.0.0"), + ) + fields_layout.addRow( + "Directory:", + QLabel(str(DIR)), + ) + fields_layout.addRow( + "Argv:", + QLabel(" ".join(sys.argv[1:])), + ) + fields_layout.addRow(gb_python) + fields_layout.addRow( + "Platform:", + QLabel(platform.platform()), + ) + + fields_widget = QWidget() + fields_layout.setContentsMargins(0, 0, 0, 0) + fields_widget.setLayout(fields_layout) + + scroll_area = QScrollArea() + scroll_area.setWidget(fields_widget) + scroll_area.setWidgetResizable(True) + scroll_area.setFrameStyle(QScrollArea.NoFrame) + + main_layout = QVBoxLayout(self) + main_layout.addWidget(QLabel(f"

{PROGRAM_NAME}

")) + main_layout.addWidget(scroll_area) + main_layout.addWidget(button_box) + + if use_column_resizer: + resizer = ColumnResizer(self) + resizer.addWidgetsFromLayout(fields_layout, 0) + resizer.addWidgetsFromLayout(gb_python_layout, 0) + + self.resize(500, 300) + + +if __name__ == "__main__": + from PyQt5.QtWidgets import QApplication + + app = QApplication(sys.argv) + + w1 = About(f"use_column_resizer=False", use_column_resizer=False) + w1.show() + + w2 = About(f"use_column_resizer=True", use_column_resizer=True) + w2.show() + w2.move(w1.x() + w1.width(), w1.y()) + + sys.exit(app.exec()) diff --git a/qt__pyqt__pyside__pyqode/layout_column_resizer/example_pyqt6.py b/qt__pyqt__pyside__pyqode/layout_column_resizer/example_pyqt6.py new file mode 100644 index 000000000..7c8997c32 --- /dev/null +++ b/qt__pyqt__pyside__pyqode/layout_column_resizer/example_pyqt6.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import sys +import platform + +from datetime import datetime +from pathlib import Path + +from PyQt6.QtWidgets import ( + QDialog, + QWidget, + QFormLayout, + QLabel, + QDialogButtonBox, + QGroupBox, + QVBoxLayout, + QScrollArea, +) + +from column_resizer_pyqt6 import ColumnResizer + + +DIR: Path = Path(__file__).resolve().parent + +PROGRAM_NAME: str = DIR.name + + +class About(QDialog): + def __init__(self, title: str, use_column_resizer: bool) -> None: + super().__init__() + + self.setWindowTitle(title) + + self._started: datetime = datetime.now() + + gb_python = QGroupBox("Python:") + gb_python_layout = QFormLayout(gb_python) + gb_python_layout.addRow( + "Version:", + QLabel(sys.version), + ) + gb_python_layout.addRow( + "Implementation:", + QLabel(platform.python_implementation()), + ) + gb_python_layout.addRow("Executable:", QLabel(sys.executable)) + + button_box = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok) + button_box.accepted.connect(self.accept) + button_box.rejected.connect(self.reject) + + fields_layout = QFormLayout() + fields_layout.addRow( + "Version:", + QLabel("1.0.0"), + ) + fields_layout.addRow( + "Directory:", + QLabel(str(DIR)), + ) + fields_layout.addRow( + "Argv:", + QLabel(" ".join(sys.argv[1:])), + ) + fields_layout.addRow(gb_python) + fields_layout.addRow( + "Platform:", + QLabel(platform.platform()), + ) + + fields_widget = QWidget() + fields_layout.setContentsMargins(0, 0, 0, 0) + fields_widget.setLayout(fields_layout) + + scroll_area = QScrollArea() + scroll_area.setWidget(fields_widget) + scroll_area.setWidgetResizable(True) + scroll_area.setFrameStyle(QScrollArea.Shape.NoFrame) + + main_layout = QVBoxLayout(self) + main_layout.addWidget(QLabel(f"

{PROGRAM_NAME}

")) + main_layout.addWidget(scroll_area) + main_layout.addWidget(button_box) + + if use_column_resizer: + resizer = ColumnResizer(self) + resizer.addWidgetsFromLayout(fields_layout, 0) + resizer.addWidgetsFromLayout(gb_python_layout, 0) + + self.resize(500, 300) + + +if __name__ == "__main__": + from PyQt6.QtWidgets import QApplication + + app = QApplication(sys.argv) + + w1 = About(f"use_column_resizer=False", use_column_resizer=False) + w1.show() + + w2 = About(f"use_column_resizer=True", use_column_resizer=True) + w2.show() + w2.move(w1.x() + w1.width(), w1.y()) + + sys.exit(app.exec()) diff --git a/qt__pyqt__pyside__pyqode/layout_column_resizer/screenshot.png b/qt__pyqt__pyside__pyqode/layout_column_resizer/screenshot.png new file mode 100644 index 000000000..b47a455c6 Binary files /dev/null and b/qt__pyqt__pyside__pyqode/layout_column_resizer/screenshot.png differ diff --git a/qt__pyqt__pyside__pyqode/lazy__qtwidgets_itemviews_fetchmore_example__QAbstractListModel.py b/qt__pyqt__pyside__pyqode/lazy__qtwidgets_itemviews_fetchmore_example__QAbstractListModel.py index 3c6d1d147..3a01b50c5 100644 --- a/qt__pyqt__pyside__pyqode/lazy__qtwidgets_itemviews_fetchmore_example__QAbstractListModel.py +++ b/qt__pyqt__pyside__pyqode/lazy__qtwidgets_itemviews_fetchmore_example__QAbstractListModel.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" # SOURCE: https://doc.qt.io/qt-5.12/qtwidgets-itemviews-fetchmore-example.html @@ -10,16 +10,30 @@ from pathlib import Path from PyQt5.QtWidgets import ( - QApplication, QMainWindow, QListView, QGridLayout, QLabel, QWidget, - QLineEdit, QTextBrowser, QSizePolicy + QApplication, + QMainWindow, + QListView, + QGridLayout, + QLabel, + QWidget, + QLineEdit, + QTextBrowser, + QSizePolicy, +) +from PyQt5.QtCore import ( + QAbstractListModel, + QModelIndex, + Qt, + pyqtSignal, + QVariant, + QLibraryInfo, ) -from PyQt5.QtCore import QAbstractListModel, QModelIndex, Qt, pyqtSignal, QVariant, QLibraryInfo class FileListModel(QAbstractListModel): numberPopulated = pyqtSignal(int) - def __init__(self, batch_size=50, parent=None): + def __init__(self, batch_size=50, parent=None) -> None: super().__init__(parent) self.fileList = [] @@ -51,13 +65,15 @@ def data(self, index: QModelIndex, role=Qt.DisplayRole) -> QVariant: def canFetchMore(self, parent: QModelIndex = None) -> bool: return self.fileCount < len(self.fileList) - def fetchMore(self, parent: QModelIndex = None): + def fetchMore(self, parent: QModelIndex = None) -> None: remainder = len(self.fileList) - self.fileCount itemsToFetch = min(self.batch_size, remainder) if itemsToFetch <= 0: return - self.beginInsertRows(QModelIndex(), self.fileCount, self.fileCount + itemsToFetch - 1) + self.beginInsertRows( + QModelIndex(), self.fileCount, self.fileCount + itemsToFetch - 1 + ) self.fileCount += itemsToFetch @@ -65,7 +81,7 @@ def fetchMore(self, parent: QModelIndex = None): self.numberPopulated.emit(itemsToFetch) - def setDirPath(self, path: str): + def setDirPath(self, path: str) -> None: self.beginResetModel() # Recursive @@ -76,7 +92,7 @@ def setDirPath(self, path: str): class MainWindow(QMainWindow): - def __init__(self): + def __init__(self) -> None: super().__init__() self.setWindowTitle("Fetch More Example") @@ -110,11 +126,11 @@ def __init__(self): self.setCentralWidget(QWidget()) self.centralWidget().setLayout(layout) - def updateLog(self, number: int): + def updateLog(self, number: int) -> None: self.logViewer.append(f"{number} items added.") -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) mw = MainWindow() diff --git a/qt__pyqt__pyside__pyqode/load_url__async_thread__QNetworkAccessManager.py b/qt__pyqt__pyside__pyqode/load_url__async_thread__QNetworkAccessManager.py index cda726932..fbb1e2770 100644 --- a/qt__pyqt__pyside__pyqode/load_url__async_thread__QNetworkAccessManager.py +++ b/qt__pyqt__pyside__pyqode/load_url__async_thread__QNetworkAccessManager.py @@ -1,17 +1,17 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PyQt5 import Qt class MainWindow(Qt.QMainWindow): - def __init__(self): + def __init__(self) -> None: super().__init__() - self.button = Qt.QPushButton('Load url!') + self.button = Qt.QPushButton("Load url!") self.button.clicked.connect(self.on_clicked) self.manager = Qt.QNetworkAccessManager(self) @@ -19,19 +19,19 @@ def __init__(self): self.setCentralWidget(self.button) - def on_reply_finished(self, reply): - self.setWindowTitle('After load: {}'.format(reply)) + def on_reply_finished(self, reply) -> None: + self.setWindowTitle(f"After load: {reply}") - def on_clicked(self): - url = 'https://github.com/gil9red/SimplePyScripts' + def on_clicked(self) -> None: + url = "https://github.com/gil9red/SimplePyScripts" - self.setWindowTitle('Before load') + self.setWindowTitle("Before load") # Отправляем запрос self.manager.get(Qt.QNetworkRequest(Qt.QUrl(url))) -if __name__ == '__main__': +if __name__ == "__main__": app = Qt.QApplication([]) mw = MainWindow() diff --git a/qt__pyqt__pyside__pyqode/load_url__async_thread__QThread.py b/qt__pyqt__pyside__pyqode/load_url__async_thread__QThread.py index a2dd23952..a5b9efb71 100644 --- a/qt__pyqt__pyside__pyqode/load_url__async_thread__QThread.py +++ b/qt__pyqt__pyside__pyqode/load_url__async_thread__QThread.py @@ -1,43 +1,42 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" +import requests from PyQt5 import Qt class LoadUrlThread(Qt.QThread): load_finished = Qt.pyqtSignal(object) - def __init__(self, url): + def __init__(self, url) -> None: super().__init__() self.url = url - def run(self): - import requests + def run(self) -> None: rs = requests.get(self.url) - self.load_finished.emit(rs) class MainWindow(Qt.QMainWindow): - def __init__(self): + def __init__(self) -> None: super().__init__() - self.button = Qt.QPushButton('Load url!') + self.button = Qt.QPushButton("Load url!") self.button.clicked.connect(self.on_clicked) self.setCentralWidget(self.button) - def on_finished_load_url(self, rs): - self.setWindowTitle('After load: {}'.format(rs)) + def on_finished_load_url(self, rs) -> None: + self.setWindowTitle(f"After load: {rs}") - def on_clicked(self): - url = 'https://github.com/gil9red/SimplePyScripts' + def on_clicked(self) -> None: + url = "https://github.com/gil9red/SimplePyScripts" - self.setWindowTitle('Before load') + self.setWindowTitle("Before load") self.thread = LoadUrlThread(url) self.thread.load_finished.connect(self.on_finished_load_url) @@ -45,7 +44,7 @@ def on_clicked(self): self.thread.start() -if __name__ == '__main__': +if __name__ == "__main__": app = Qt.QApplication([]) mw = MainWindow() diff --git a/qt__pyqt__pyside__pyqode/model_with_iterator__QAbstractListModel/_more_examples.py b/qt__pyqt__pyside__pyqode/model_with_iterator__QAbstractListModel/_more_examples.py index 415e8d00a..9535232e3 100644 --- a/qt__pyqt__pyside__pyqode/model_with_iterator__QAbstractListModel/_more_examples.py +++ b/qt__pyqt__pyside__pyqode/model_with_iterator__QAbstractListModel/_more_examples.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from string import printable @@ -20,22 +20,30 @@ def get_infinity_generator(): class MainWindow(QWidget): - def __init__(self): + def __init__(self) -> None: super().__init__() main_layout = QHBoxLayout(self) - main_layout.addWidget(self._add_view_with_it('infinity_generator', get_infinity_generator())) - main_layout.addWidget(self._add_view_with_it('range(1_000_000)', range(1_000_000))) - main_layout.addWidget(self._add_view_with_it('list of pow2', [str(i ** i) for i in range(1_000)])) - main_layout.addWidget(self._add_view_with_it('str', printable * 100)) - main_layout.addWidget(self._add_view_with_it('dict', dict.fromkeys(dir(self)))) + main_layout.addWidget( + self._add_view_with_it("infinity_generator", get_infinity_generator()) + ) + main_layout.addWidget( + self._add_view_with_it("range(1_000_000)", range(1_000_000)) + ) + main_layout.addWidget( + self._add_view_with_it("list of pow2", [str(i**i) for i in range(1_000)]) + ) + main_layout.addWidget(self._add_view_with_it("str", printable * 100)) + main_layout.addWidget(self._add_view_with_it("dict", dict.fromkeys(dir(self)))) def _add_view_with_it(self, title: str, it: Iterator) -> QWidget: group_box = QGroupBox() - group_box.setTitle('1111') + group_box.setTitle("1111") model = IteratorListModel(it=it) - model.rowsInserted.connect(lambda: group_box.setTitle(f'[{title}] rows: {model.rowCount()}')) + model.rowsInserted.connect( + lambda: group_box.setTitle(f"[{title}] rows: {model.rowCount()}") + ) view = QListView() view.setModel(model) @@ -46,7 +54,7 @@ def _add_view_with_it(self, title: str, it: Iterator) -> QWidget: return group_box -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) mw = MainWindow() diff --git a/qt__pyqt__pyside__pyqode/model_with_iterator__QAbstractListModel/iterator_list_model.py b/qt__pyqt__pyside__pyqode/model_with_iterator__QAbstractListModel/iterator_list_model.py index ec65245aa..f09e6fbc0 100644 --- a/qt__pyqt__pyside__pyqode/model_with_iterator__QAbstractListModel/iterator_list_model.py +++ b/qt__pyqt__pyside__pyqode/model_with_iterator__QAbstractListModel/iterator_list_model.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from typing import Iterator @@ -11,7 +11,7 @@ class IteratorListModel(QAbstractListModel): - def __init__(self, it: Iterator, prefetch=100, parent=None): + def __init__(self, it: Iterator, prefetch=100, parent=None) -> None: super().__init__(parent) self._at_end = False @@ -22,7 +22,7 @@ def __init__(self, it: Iterator, prefetch=100, parent=None): def canFetchMore(self, parent: QModelIndex = None) -> bool: return not self._at_end - def fetchMore(self, parent: QModelIndex = None): + def fetchMore(self, parent: QModelIndex = None) -> None: if self._at_end: return @@ -58,11 +58,11 @@ def rowCount(self, parent: QModelIndex = None) -> int: return len(self._items) -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) model = IteratorListModel(it=range(1_000_000)) - model.rowsInserted.connect(lambda: mw.setWindowTitle(f'Rows: {model.rowCount()}')) + model.rowsInserted.connect(lambda: mw.setWindowTitle(f"Rows: {model.rowCount()}")) mw = QListView() mw.setModel(model) diff --git a/qt__pyqt__pyside__pyqode/move_window_on_center__QDesktopWidget.py b/qt__pyqt__pyside__pyqode/move_window_on_center__QDesktopWidget.py index 7a7d02ca0..a92a23f4b 100644 --- a/qt__pyqt__pyside__pyqode/move_window_on_center__QDesktopWidget.py +++ b/qt__pyqt__pyside__pyqode/move_window_on_center__QDesktopWidget.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PyQt5.QtWidgets import QApplication, QWidget @@ -12,7 +12,8 @@ mw = QWidget() rect = mw.frameGeometry() -center = app.desktop().availableGeometry().center() # This is where QDesktopWidget is used +# This is where QDesktopWidget is used +center = app.desktop().availableGeometry().center() rect.moveCenter(center) mw.move(rect.topLeft()) diff --git a/qt__pyqt__pyside__pyqode/multiprocessing__QApplication__create_child_process__with__Tkinter_and_Qt.py b/qt__pyqt__pyside__pyqode/multiprocessing__QApplication__create_child_process__with__Tkinter_and_Qt.py index dfcf28be6..614e5b281 100644 --- a/qt__pyqt__pyside__pyqode/multiprocessing__QApplication__create_child_process__with__Tkinter_and_Qt.py +++ b/qt__pyqt__pyside__pyqode/multiprocessing__QApplication__create_child_process__with__Tkinter_and_Qt.py @@ -1,54 +1,56 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from multiprocessing import Process +import tkinter as tk +from PyQt5.Qt import QApplication, Qt, QLabel -def go_qt(name): - from PyQt5.Qt import QApplication, Qt, QLabel + +def go_qt(name) -> None: app = QApplication([]) mw = QLabel() mw.setAlignment(Qt.AlignCenter) mw.setMinimumSize(150, 50) - mw.setText('Hello, ' + name) + mw.setText("Hello, " + name) mw.show() app.exec() -def go_tk(name): - import tkinter as tk +def go_tk(name) -> None: app = tk.Tk() app.minsize(150, 50) - mw = tk.Label(app, text='Hello, ' + name) - mw.pack(fill='both', expand=True) + mw = tk.Label(app, text="Hello, " + name) + mw.pack(fill="both", expand=True) app.mainloop() -def create_qt(): - p = Process(target=go_qt, args=('Qt',)) +def create_qt() -> None: + p = Process(target=go_qt, args=("Qt",)) p.start() -def create_tk(): - p = Process(target=go_tk, args=('Tk',)) +def create_tk() -> None: + p = Process(target=go_tk, args=("Tk",)) p.start() -if __name__ == '__main__': - from PyQt5.Qt import QApplication, QPushButton, QWidget, QVBoxLayout +if __name__ == "__main__": + from PyQt5.Qt import QPushButton, QWidget, QVBoxLayout + app = QApplication([]) - button_qt = QPushButton('Create Qt') + button_qt = QPushButton("Create Qt") button_qt.clicked.connect(create_qt) - button_tk = QPushButton('Create Tk') + button_tk = QPushButton("Create Tk") button_tk.clicked.connect(create_tk) layout = QVBoxLayout() diff --git a/qt__pyqt__pyside__pyqode/multiprocessing__QApplication__in_other_process.py b/qt__pyqt__pyside__pyqode/multiprocessing__QApplication__in_other_process.py index 9d70cf0ae..e216d0e33 100644 --- a/qt__pyqt__pyside__pyqode/multiprocessing__QApplication__in_other_process.py +++ b/qt__pyqt__pyside__pyqode/multiprocessing__QApplication__in_other_process.py @@ -1,24 +1,27 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -def go(name): - from PyQt5.Qt import QApplication, Qt, QLabel +from PyQt5.Qt import QApplication, Qt, QLabel + + +def go(name) -> None: app = QApplication([]) mw = QLabel() mw.setAlignment(Qt.AlignCenter) mw.setMinimumSize(150, 50) - mw.setText('Hello, ' + name) + mw.setText("Hello, " + name) mw.show() app.exec() -if __name__ == '__main__': +if __name__ == "__main__": from multiprocessing import Process - p = Process(target=go, args=('bob',)) + + p = Process(target=go, args=("bob",)) p.start() p.join() diff --git a/qt__pyqt__pyside__pyqode/multiprocessing__QApplication__in_other_process_list__Pool_map.py b/qt__pyqt__pyside__pyqode/multiprocessing__QApplication__in_other_process_list__Pool_map.py index 7a17434fa..c27cae3ae 100644 --- a/qt__pyqt__pyside__pyqode/multiprocessing__QApplication__in_other_process_list__Pool_map.py +++ b/qt__pyqt__pyside__pyqode/multiprocessing__QApplication__in_other_process_list__Pool_map.py @@ -1,12 +1,12 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -if __name__ == '__main__': +if __name__ == "__main__": from multiprocessing import Pool from multiprocessing__QApplication__in_other_process import go with Pool() as p: - p.map(go, ['Alice', 'Bob', 'World']) + p.map(go, ["Alice", "Bob", "World"]) diff --git a/qt__pyqt__pyside__pyqode/not_working__youtube_Maximum_durations_QWebView__PyQt4.py b/qt__pyqt__pyside__pyqode/not_working__youtube_Maximum_durations_QWebView__PyQt4.py index e9680d51f..412adc556 100644 --- a/qt__pyqt__pyside__pyqode/not_working__youtube_Maximum_durations_QWebView__PyQt4.py +++ b/qt__pyqt__pyside__pyqode/not_working__youtube_Maximum_durations_QWebView__PyQt4.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """ @@ -11,6 +11,7 @@ import sys + from bs4 import BeautifulSoup from PyQt4.QtGui import * @@ -32,7 +33,7 @@ def get_total_seconds(time_str): total_seconds = 0 - time_part = list(map(int, time_str.split(':'))) + time_part = list(map(int, time_str.split(":"))) time_part_number = len(time_part) if time_part_number == 1: @@ -47,7 +48,7 @@ def get_total_seconds(time_str): return total_seconds -def load_finished_handler(ok): +def load_finished_handler(ok) -> None: if not ok: return @@ -55,15 +56,15 @@ def load_finished_handler(ok): def get_video_time_list(): html = doc.toOuterXml() - root = BeautifulSoup(html, 'lxml') + root = BeautifulSoup(html, "lxml") - return [title.text for title in root.select('.video-time')] + return [title.text for title in root.select(".video-time")] def get_video_link_list(): html = doc.toOuterXml() - root = BeautifulSoup(html, 'lxml') + root = BeautifulSoup(html, "lxml") - return {a['href'] for a in root.select('.yt-uix-tile-link')} + return {a["href"] for a in root.select(".yt-uix-tile-link")} # Get video time and append to list global video_time_list @@ -77,9 +78,9 @@ def get_video_link_list(): timer = QTimer() timer.setInterval(5000) - def timeout_handler(): + def timeout_handler() -> None: doc = view.page().mainFrame().documentElement() - button = doc.findFirst('.load-more-button') + button = doc.findFirst(".load-more-button") # If button "More" visible is_load_more = not button.isNull() @@ -98,7 +99,7 @@ def timeout_handler(): # Click on button "More" print("More") - button.evaluateJavaScript('this.click()') + button.evaluateJavaScript("this.click()") timer.timeout.connect(timeout_handler) timer.start() @@ -108,9 +109,9 @@ def timeout_handler(): QApplication.instance().processEvents() print() - print('Total:') - print(' Videos: {}, {}'.format(len(video_link_list), video_link_list)) - print(' Durations: {}, {}'.format(len(video_time_list), video_time_list)) + print("Total:") + print(f" Videos: {len(video_link_list)}, {video_link_list}") + print(f" Durations: {len(video_time_list)}, {video_time_list}") # Find maximum duration max_total_seconds = 0 @@ -122,12 +123,12 @@ def timeout_handler(): max_total_seconds = total_seconds max_time_str = time_str - print(' Max durations: {}'.format(max_time_str)) + print(f" Max durations: {max_time_str}") sys.exit() -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) view = QWebView() @@ -135,7 +136,7 @@ def timeout_handler(): view.loadFinished.connect(load_finished_handler) - url = QUrl('https://www.youtube.com/channel/UCWvMAvbk23gFzZ3BRUfACRA/videos') + url = QUrl("https://www.youtube.com/channel/UCWvMAvbk23gFzZ3BRUfACRA/videos") view.load(url) app.exec() diff --git a/qt__pyqt__pyside__pyqode/not_working__youtube_summary_durations_all_video_QWebView__PyQt4.py b/qt__pyqt__pyside__pyqode/not_working__youtube_summary_durations_all_video_QWebView__PyQt4.py index d63b85b8b..8302eb998 100644 --- a/qt__pyqt__pyside__pyqode/not_working__youtube_summary_durations_all_video_QWebView__PyQt4.py +++ b/qt__pyqt__pyside__pyqode/not_working__youtube_summary_durations_all_video_QWebView__PyQt4.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" """ @@ -32,7 +32,7 @@ def get_total_seconds(time_str): total_seconds = 0 - time_part = list(map(int, time_str.split(':'))) + time_part = list(map(int, time_str.split(":"))) time_part_number = len(time_part) if time_part_number == 1: @@ -47,21 +47,21 @@ def get_total_seconds(time_str): return total_seconds -def load_finished_handler(ok): +def load_finished_handler(ok) -> None: if not ok: return doc = view.page().mainFrame().documentElement() - def update_url_video_by_time_dict(): + def update_url_video_by_time_dict() -> None: global url_video_by_time_dict html = doc.toOuterXml() - root = BeautifulSoup(html, 'lxml') + root = BeautifulSoup(html, "lxml") - for item in root.select('.channels-content-item'): - url = item.select_one('.yt-uix-tile-link')['href'] - time_str = item.select_one('.video-time').text + for item in root.select(".channels-content-item"): + url = item.select_one(".yt-uix-tile-link")["href"] + time_str = item.select_one(".video-time").text url_video_by_time_dict[url] = time_str @@ -72,9 +72,9 @@ def update_url_video_by_time_dict(): timer = QTimer() timer.setInterval(5000) - def timeout_handler(): + def timeout_handler() -> None: doc = view.page().mainFrame().documentElement() - button = doc.findFirst('.load-more-button') + button = doc.findFirst(".load-more-button") # If button "More" visible is_load_more = not button.isNull() @@ -88,7 +88,7 @@ def timeout_handler(): # Click on button "More" print("More") - button.evaluateJavaScript('this.click()') + button.evaluateJavaScript("this.click()") timer.timeout.connect(timeout_handler) timer.start() @@ -98,9 +98,9 @@ def timeout_handler(): QApplication.instance().processEvents() print() - print('Total:') - print(' Videos: {}'.format(len(url_video_by_time_dict))) - print(' Durations: {}'.format(list(url_video_by_time_dict.values()))) + print("Total:") + print(f" Videos: {len(url_video_by_time_dict)}") + print(f" Durations: {list(url_video_by_time_dict.values())}") # Summary duration sum_seconds = 0 @@ -112,13 +112,13 @@ def timeout_handler(): hh, mm = divmod(mm, 60) sum_time_str = "%d:%02d:%02d" % (hh, mm, ss) - print(' Summary duration (secs): {}'.format(sum_seconds)) - print(' Summary duration: {}'.format(sum_time_str)) + print(f" Summary duration (secs): {sum_seconds}") + print(f" Summary duration: {sum_time_str}") sys.exit() -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) view = QWebView() @@ -126,7 +126,7 @@ def timeout_handler(): view.loadFinished.connect(load_finished_handler) - url = QUrl('https://www.youtube.com/channel/UCWvMAvbk23gFzZ3BRUfACRA/videos') + url = QUrl("https://www.youtube.com/channel/UCWvMAvbk23gFzZ3BRUfACRA/videos") view.load(url) app.exec() diff --git a/qt__pyqt__pyside__pyqode/play_mp3__pyqt5__qmediaplayer/play.py b/qt__pyqt__pyside__pyqode/play_mp3__pyqt5__qmediaplayer/play.py index 85a92914b..518bc7668 100644 --- a/qt__pyqt__pyside__pyqode/play_mp3__pyqt5__qmediaplayer/play.py +++ b/qt__pyqt__pyside__pyqode/play_mp3__pyqt5__qmediaplayer/play.py @@ -1,13 +1,13 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PyQt5 import Qt -def _on_media_status_changed(status): +def _on_media_status_changed(status) -> None: if status == Qt.QMediaPlayer.EndOfMedia: Qt.QCoreApplication.instance().quit() @@ -15,7 +15,7 @@ def _on_media_status_changed(status): app = Qt.QCoreApplication([]) player = Qt.QMediaPlayer() -file_name = Qt.QUrl.fromLocalFile('example.mp3') +file_name = Qt.QUrl.fromLocalFile("example.mp3") player.setMedia(Qt.QMediaContent(file_name)) player.mediaStatusChanged.connect(_on_media_status_changed) player.play() diff --git a/qt__pyqt__pyside__pyqode/player_youtube_on_custom_page__QWebViewEngine.py b/qt__pyqt__pyside__pyqode/player_youtube_on_custom_page__QWebViewEngine.py index 11f1a81be..8578f4927 100644 --- a/qt__pyqt__pyside__pyqode/player_youtube_on_custom_page__QWebViewEngine.py +++ b/qt__pyqt__pyside__pyqode/player_youtube_on_custom_page__QWebViewEngine.py @@ -1,25 +1,27 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PyQt5.QtWidgets import QApplication from PyQt5.QtWebEngineWidgets import QWebEngineView -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) view = QWebEngineView() view.show() - view.setHtml("""\ + view.setHtml( + """\ - """) + """ + ) app.exec() diff --git a/qt__pyqt__pyside__pyqode/printer__QTextEdit__QPrinter__QPrinterDialog/main.py b/qt__pyqt__pyside__pyqode/printer__QTextEdit__QPrinter__QPrinterDialog/main.py index 8dd5fded4..104fd428e 100644 --- a/qt__pyqt__pyside__pyqode/printer__QTextEdit__QPrinter__QPrinterDialog/main.py +++ b/qt__pyqt__pyside__pyqode/printer__QTextEdit__QPrinter__QPrinterDialog/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PyQt5 import Qt @@ -14,7 +14,7 @@ """ -if __name__ == '__main__': +if __name__ == "__main__": app = Qt.QApplication([]) printer = Qt.QPrinter() diff --git a/qt__pyqt__pyside__pyqode/printer_to_PDF__QTextEdit__QPrinter__QPrinterDialog/main.py b/qt__pyqt__pyside__pyqode/printer_to_PDF__QTextEdit__QPrinter__QPrinterDialog/main.py index ed4a0bc22..449ae3993 100644 --- a/qt__pyqt__pyside__pyqode/printer_to_PDF__QTextEdit__QPrinter__QPrinterDialog/main.py +++ b/qt__pyqt__pyside__pyqode/printer_to_PDF__QTextEdit__QPrinter__QPrinterDialog/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PyQt5 import Qt @@ -14,7 +14,7 @@ """ -if __name__ == '__main__': +if __name__ == "__main__": app = Qt.QApplication([]) printer = Qt.QPrinter() @@ -23,7 +23,7 @@ te.setHtml(html) te.show() - printer.setOutputFileName('result.pdf') + printer.setOutputFileName("result.pdf") te.print(printer) app.exec() diff --git a/qt__pyqt__pyside__pyqode/pyq5__QTreeView_QFileSystemModel_and_viewer_as_QTextEdit__on_double_click.py b/qt__pyqt__pyside__pyqode/pyq5__QTreeView_QFileSystemModel_and_viewer_as_QTextEdit__on_double_click.py index d7193ee4c..89dd73810 100644 --- a/qt__pyqt__pyside__pyqode/pyq5__QTreeView_QFileSystemModel_and_viewer_as_QTextEdit__on_double_click.py +++ b/qt__pyqt__pyside__pyqode/pyq5__QTreeView_QFileSystemModel_and_viewer_as_QTextEdit__on_double_click.py @@ -1,17 +1,26 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" + -from PyQt5.Qt import QApplication, QMainWindow, QSplitter, QTreeView, QTextEdit, QFileSystemModel, QDir import os +from PyQt5.Qt import ( + QApplication, + QMainWindow, + QSplitter, + QTreeView, + QTextEdit, + QFileSystemModel, + QDir, +) class MainWindow(QMainWindow): - def __init__(self, parent=None): + def __init__(self, parent=None) -> None: super().__init__(parent) - self.setWindowTitle('Direct tree') + self.setWindowTitle("Direct tree") self.model = QFileSystemModel() self.model.setRootPath(QDir.rootPath()) @@ -34,10 +43,10 @@ def __init__(self, parent=None): self.setCentralWidget(splitter) - def _on_double_clicked(self, index): + def _on_double_clicked(self, index) -> None: file_name = self.model.filePath(index) - with open(file_name, encoding='utf-8') as f: + with open(file_name, encoding="utf-8") as f: text = f.read() self.textEdit.setPlainText(text) diff --git a/qt__pyqt__pyside__pyqode/pyq5__simple_balls__with_part_transparent_body.py b/qt__pyqt__pyside__pyqode/pyq5__simple_balls__with_part_transparent_body.py index e9392d508..c9d857d1e 100644 --- a/qt__pyqt__pyside__pyqode/pyq5__simple_balls__with_part_transparent_body.py +++ b/qt__pyqt__pyside__pyqode/pyq5__simple_balls__with_part_transparent_body.py @@ -1,13 +1,14 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from random import randint -from typing import List -from PyQt5.Qt import * +from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QPushButton +from PyQt5.QtGui import QPainter, QColor, QPen +from PyQt5.QtCore import QTimer, Qt # SOURCE: https://github.com/gil9red/SimplePyScripts/blob/1d6226ea5545e49b533e71c92d77856a7b2e171d/pygame__examples/simple_balls.py @@ -29,8 +30,15 @@ def update(self): self.y += self.v_y def draw(self, painter: QPainter): - r, g, b = self.color - color = QColor(r, g, b) + if isinstance(self.color, tuple): + if len(self.color) == 4: + r, g, b, a = self.color + color = QColor(r, g, b, a) + else: + r, g, b = self.color + color = QColor(r, g, b) + else: + color = self.color painter.save() painter.setPen(Qt.black) @@ -59,8 +67,21 @@ def right(self): return self.x + self.r +def get_random_vector() -> tuple[int, int]: + pos = 0, 0 + # Если pos равен (0, 0), пересчитываем значения, т.к. шарик должен двигаться + while pos == (0, 0): + pos = randint(-3, 3), randint(-3, 3) + + return pos + + +def get_random_color() -> tuple[int, int, int]: + return randint(0, 255), randint(0, 255), randint(0, 255) + + class Widget(QWidget): - def __init__(self): + def __init__(self) -> None: super().__init__() self.setWindowFlags(Qt.FramelessWindowHint) @@ -69,7 +90,7 @@ def __init__(self): self._old_pos = None self.frame_color = Qt.darkCyan - self.balls: List[Ball] = [] + self.balls: list[Ball] = [] timeout = 1000 // 60 @@ -84,22 +105,22 @@ def __init__(self): self.setLayout(layout) - def mousePressEvent(self, event): + def mousePressEvent(self, event) -> None: if event.button() == Qt.LeftButton: self._old_pos = event.pos() - def mouseReleaseEvent(self, event): + def mouseReleaseEvent(self, event) -> None: if event.button() == Qt.LeftButton: self._old_pos = None - def mouseMoveEvent(self, event): + def mouseMoveEvent(self, event) -> None: if not self._old_pos: return delta = event.pos() - self._old_pos self.move(self.pos() + delta) - def tick(self): + def tick(self) -> None: for ball in self.balls: ball.update() @@ -114,7 +135,7 @@ def tick(self): # Вызов перерисовки self.update() - def paintEvent(self, event): + def paintEvent(self, event) -> None: painter = QPainter(self) painter.setBrush(QColor(0, 0, 0, 1)) @@ -124,18 +145,7 @@ def paintEvent(self, event): for ball in self.balls: ball.draw(painter) - def append_random_ball(self): - def get_random_vector(): - pos = 0, 0 - # Если pos равен (0, 0), пересчитываем значения, т.к. шарик должен двигаться - while pos == (0, 0): - pos = randint(-3, 3), randint(-3, 3) - - return pos - - def get_random_color(): - return randint(0, 255), randint(0, 255), randint(0, 255) - + def append_random_ball(self) -> None: x = self.width() // 2 + randint(-self.width() // 4, self.width() // 4) y = self.height() // 2 + randint(-self.height() // 4, self.height() // 4) r = randint(10, 20) @@ -146,10 +156,10 @@ def get_random_color(): self.balls.append(ball) -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) - WIDTH = 600 # ширина экрана + WIDTH = 600 # ширина экрана HEIGHT = 600 # высота экрана BALL_NUMBER = 1000 diff --git a/qt__pyqt__pyside__pyqode/pyqode_example/main.py b/qt__pyqt__pyside__pyqode/pyqode_example/main.py index a98fd2ea1..d08cc1266 100644 --- a/qt__pyqt__pyside__pyqode/pyqode_example/main.py +++ b/qt__pyqt__pyside__pyqode/pyqode_example/main.py @@ -2,15 +2,16 @@ # -*- coding: utf-8 -*- -if __name__ == '__main__': +if __name__ == "__main__": import sys - from PySide.QtGui import QApplication, QMainWindow - app = QApplication(sys.argv) + from PySide.QtGui import QApplication, QMainWindow from pyqode.core import api from pyqode.core import modes + app = QApplication(sys.argv) + editor = api.CodeEdit() # Добавление модов: подсветка кода, подсветка текущей строки и @@ -19,10 +20,10 @@ editor.modes.append(modes.CaretLineHighlighterMode()) editor.modes.append(modes.IndenterMode()) - editor.setPlainText(open(__file__, encoding='utf-8').read(), None, None) + editor.setPlainText(open(__file__, encoding="utf-8").read(), None, None) mw = QMainWindow() - mw.setWindowTitle('pyqode') + mw.setWindowTitle("pyqode") mw.setCentralWidget(editor) mw.show() diff --git a/qt__pyqt__pyside__pyqode/pyqt5_QToolTip_with_programmatically_timeout.py b/qt__pyqt__pyside__pyqode/pyqt5_QToolTip_with_programmatically_timeout.py index b661bb814..38a464657 100644 --- a/qt__pyqt__pyside__pyqode/pyqt5_QToolTip_with_programmatically_timeout.py +++ b/qt__pyqt__pyside__pyqode/pyqt5_QToolTip_with_programmatically_timeout.py @@ -1,36 +1,38 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PyQt5 import QtWidgets as qtw from PyQt5.QtCore import QRect, QTimer -def show_tooltip(parent, widget): - qtw.QToolTip.showText(parent.mapToGlobal(widget.pos()), widget.toolTip(), widget, QRect()) +def show_tooltip(parent, widget) -> None: + qtw.QToolTip.showText( + parent.mapToGlobal(widget.pos()), widget.toolTip(), widget, QRect() + ) -if __name__ == '__main__': +if __name__ == "__main__": app = qtw.QApplication([]) line_edit = qtw.QLineEdit() - line_edit.setToolTip('This my LINE EDIT!') + line_edit.setToolTip("This my LINE EDIT!") - button = qtw.QPushButton('My button!') - button.setToolTip('Simple button...') + button = qtw.QPushButton("My button!") + button.setToolTip("Simple button...") text_edit = qtw.QTextEdit() - text_edit.setToolTip('TextEdit!') + text_edit.setToolTip("TextEdit!") layout = qtw.QFormLayout() - layout.addRow('Line edit:', line_edit) - layout.addRow('Button:', button) - layout.addRow('Text edit:', text_edit) + layout.addRow("Line edit:", line_edit) + layout.addRow("Button:", button) + layout.addRow("Text edit:", text_edit) w = qtw.QWidget() - w.setWindowTitle('Tooltip example') + w.setWindowTitle("Tooltip example") w.setLayout(layout) w.show() diff --git a/qt__pyqt__pyside__pyqode/pyqt5_QToolTip_with_programmatically_timeout__using_QEvent.py b/qt__pyqt__pyside__pyqode/pyqt5_QToolTip_with_programmatically_timeout__using_QEvent.py index bbe097d44..d2219b5ea 100644 --- a/qt__pyqt__pyside__pyqode/pyqt5_QToolTip_with_programmatically_timeout__using_QEvent.py +++ b/qt__pyqt__pyside__pyqode/pyqt5_QToolTip_with_programmatically_timeout__using_QEvent.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PyQt5 import QtWidgets as qtw @@ -9,29 +9,32 @@ from PyQt5.QtGui import QHelpEvent -def show_tooltip(parent, widget): - app.notify(widget, QHelpEvent(QHelpEvent.ToolTip, widget.pos(), parent.mapToGlobal(widget.pos()))) +def show_tooltip(parent, widget) -> None: + app.notify( + widget, + QHelpEvent(QHelpEvent.ToolTip, widget.pos(), parent.mapToGlobal(widget.pos())), + ) -if __name__ == '__main__': +if __name__ == "__main__": app = qtw.QApplication([]) line_edit = qtw.QLineEdit() - line_edit.setToolTip('This my LINE EDIT!') + line_edit.setToolTip("This my LINE EDIT!") - button = qtw.QPushButton('My button!') - button.setToolTip('Simple button...') + button = qtw.QPushButton("My button!") + button.setToolTip("Simple button...") text_edit = qtw.QTextEdit() - text_edit.setToolTip('TextEdit!') + text_edit.setToolTip("TextEdit!") layout = qtw.QFormLayout() - layout.addRow('Line edit:', line_edit) - layout.addRow('Button:', button) - layout.addRow('Text edit:', text_edit) + layout.addRow("Line edit:", line_edit) + layout.addRow("Button:", button) + layout.addRow("Text edit:", text_edit) w = qtw.QWidget() - w.setWindowTitle('Tooltip example') + w.setWindowTitle("Tooltip example") w.setLayout(layout) w.show() diff --git a/qt__pyqt__pyside__pyqode/pyqt5__QComboBox.py b/qt__pyqt__pyside__pyqode/pyqt5__QComboBox.py index d58201587..4dc2d35f8 100644 --- a/qt__pyqt__pyside__pyqode/pyqt5__QComboBox.py +++ b/qt__pyqt__pyside__pyqode/pyqt5__QComboBox.py @@ -1,22 +1,22 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PyQt5.Qt import * class MainWindow(QWidget): - def __init__(self): + def __init__(self) -> None: super().__init__() self.tb_result = QTextBrowser() self.cb_pets = QComboBox() self.cb_pets.currentIndexChanged.connect(self._on_pet_changed) - self.cb_pets.addItem('Собаки', userData='dogs') - self.cb_pets.addItem('Коты', userData='cats') + self.cb_pets.addItem("Собаки", userData="dogs") + self.cb_pets.addItem("Коты", userData="cats") layout = QVBoxLayout() layout.addWidget(self.cb_pets) @@ -24,7 +24,7 @@ def __init__(self): self.setLayout(layout) - def _on_pet_changed(self, index): + def _on_pet_changed(self, index) -> None: # print(index) # 0 # print(self.cb_pets.itemText(index)) # Собаки # print(self.cb_pets.itemData(index)) # dogs @@ -34,17 +34,17 @@ def _on_pet_changed(self, index): # print(self.cb_pets.currentData()) # dogs data = self.cb_pets.itemData(index) - if data == 'cats': + if data == "cats": text = "Вы любите кошек" - elif data == 'dogs': + elif data == "dogs": text = "Вы любите собак" else: - text = '' + text = "" self.tb_result.setHtml(text) -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) mw = MainWindow() diff --git a/qt__pyqt__pyside__pyqode/pyqt5__QFileSystemModel__append_selected_files_and_dirs.py b/qt__pyqt__pyside__pyqode/pyqt5__QFileSystemModel__append_selected_files_and_dirs.py index c9525960e..4919e88a7 100644 --- a/qt__pyqt__pyside__pyqode/pyqt5__QFileSystemModel__append_selected_files_and_dirs.py +++ b/qt__pyqt__pyside__pyqode/pyqt5__QFileSystemModel__append_selected_files_and_dirs.py @@ -1,18 +1,24 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PyQt5.QtWidgets import ( - QWidget, QFileSystemModel, QTreeView, QListWidget, QPushButton, QSplitter, - QVBoxLayout, QApplication + QWidget, + QFileSystemModel, + QTreeView, + QListWidget, + QPushButton, + QSplitter, + QVBoxLayout, + QApplication, ) from PyQt5.QtCore import QDir, Qt class Widget(QWidget): - def __init__(self): + def __init__(self) -> None: super().__init__() path = QDir.rootPath() @@ -23,11 +29,13 @@ def __init__(self): self.tree_view = QTreeView() self.tree_view.setModel(self.model) self.tree_view.setSelectionMode(QTreeView.ExtendedSelection) - self.tree_view.selectionModel().selectionChanged.connect(self._on_selection_changed) + self.tree_view.selectionModel().selectionChanged.connect( + self._on_selection_changed + ) self.list_files = QListWidget() - self.button_add = QPushButton('Добавить!') + self.button_add = QPushButton("Добавить!") self.button_add.setEnabled(False) self.button_add.clicked.connect(self._on_add) @@ -39,17 +47,17 @@ def __init__(self): main_layout.addWidget(splitter) main_layout.addWidget(self.button_add) - def _on_selection_changed(self, selected, deselected): + def _on_selection_changed(self, selected, deselected) -> None: has = self.tree_view.selectionModel().hasSelection() self.button_add.setEnabled(has) - def _on_add(self): + def _on_add(self) -> None: for row in self.tree_view.selectionModel().selectedRows(): path = self.model.filePath(row) self.list_files.addItem(path) -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) w = Widget() diff --git a/qt__pyqt__pyside__pyqode/pyqt5__QItemDelegate.py b/qt__pyqt__pyside__pyqode/pyqt5__QItemDelegate.py index 602fe569f..3b269007d 100644 --- a/qt__pyqt__pyside__pyqode/pyqt5__QItemDelegate.py +++ b/qt__pyqt__pyside__pyqode/pyqt5__QItemDelegate.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from datetime import datetime @@ -9,7 +9,7 @@ class MessagesWindow(QtWidgets.QTableView): - def __init__(self, parent=None): + def __init__(self, parent=None) -> None: super().__init__(parent) self.setModel(ModelMessages()) @@ -18,30 +18,49 @@ def __init__(self, parent=None): class ModelMessages(QtGui.QStandardItemModel): - def __init__(self, parent=None): + def __init__(self, parent=None) -> None: super().__init__(parent) - self.appendRow([QtGui.QStandardItem('2018-11-25 11:15:06.0001'), QtGui.QStandardItem('111')]) - self.appendRow([QtGui.QStandardItem('2018-11-25 11:15:06.0001'), QtGui.QStandardItem('111')]) - self.appendRow([QtGui.QStandardItem('2018-11-25 11:15:06.0001'), QtGui.QStandardItem('111')]) + self.appendRow( + [ + QtGui.QStandardItem("2018-11-25 11:15:06.0001"), + QtGui.QStandardItem("111"), + ] + ) + self.appendRow( + [ + QtGui.QStandardItem("2018-11-25 11:15:06.0001"), + QtGui.QStandardItem("111"), + ] + ) + self.appendRow( + [ + QtGui.QStandardItem("2018-11-25 11:15:06.0001"), + QtGui.QStandardItem("111"), + ] + ) class ItemDateFormat(QtWidgets.QItemDelegate): - def paint(self, painter, opt: QtWidgets.QStyleOptionViewItem, index): + def paint(self, painter, opt: QtWidgets.QStyleOptionViewItem, index) -> None: self.drawBackground(painter, opt, index) self.drawFocus(painter, opt, opt.rect) try: - opt.text = '{0:%d %b %H:%M}'.format(datetime.strptime(index.model().data(index), '%Y-%m-%d %H:%M:%S.%f')) + value = index.model().data(index) + dt = datetime.strptime(value, '%Y-%m-%d %H:%M:%S.%f') + opt.text = f"{dt:%d %b %H:%M}" except TypeError: pass opt.font.setItalic(True) opt.backgroundBrush = QtCore.Qt.yellow - QtWidgets.QApplication.style().drawControl(QtWidgets.QStyle.CE_ItemViewItem, opt, painter) + QtWidgets.QApplication.style().drawControl( + QtWidgets.QStyle.CE_ItemViewItem, opt, painter + ) -if __name__ == '__main__': +if __name__ == "__main__": app = QtWidgets.QApplication([]) w = MessagesWindow() diff --git a/qt__pyqt__pyside__pyqode/pyqt5__QLineEdit_with_fixed_cursor_position.py b/qt__pyqt__pyside__pyqode/pyqt5__QLineEdit_with_fixed_cursor_position.py index a96a0cf0a..919573d60 100644 --- a/qt__pyqt__pyside__pyqode/pyqt5__QLineEdit_with_fixed_cursor_position.py +++ b/qt__pyqt__pyside__pyqode/pyqt5__QLineEdit_with_fixed_cursor_position.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PyQt5 import Qt diff --git a/qt__pyqt__pyside__pyqode/pyqt5__QSyntaxHighlighter.py b/qt__pyqt__pyside__pyqode/pyqt5__QSyntaxHighlighter.py index 3ccf32644..0d8831cda 100644 --- a/qt__pyqt__pyside__pyqode/pyqt5__QSyntaxHighlighter.py +++ b/qt__pyqt__pyside__pyqode/pyqt5__QSyntaxHighlighter.py @@ -1,14 +1,16 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" -from PyQt5.Qt import * +from PyQt5.QtCore import Qt, QRegularExpression +from PyQt5.QtGui import QSyntaxHighlighter, QTextCharFormat, QFont +from PyQt5.QtWidgets import QApplication, QPlainTextEdit, QTextEdit class MyHighlighter(QSyntaxHighlighter): - def highlightBlock(self, text): + def highlightBlock(self, text) -> None: char_format = QTextCharFormat() char_format.setFontWeight(QFont.Bold) char_format.setForeground(Qt.darkMagenta) @@ -20,14 +22,23 @@ def highlightBlock(self, text): self.setFormat(match.capturedStart(), match.capturedLength(), char_format) -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) - mw = QTextEdit() - mw.setText("x = sin(1)\nb = cos(sin(PI));") - - highlighter = MyHighlighter(mw.document()) - - mw.show() + text_edit = QTextEdit() + text_edit.setWindowTitle("QTextEdit") + text_edit.setText("x = sin(1)\nb = cos(sin(PI));") + highlighter_text_edit = MyHighlighter(text_edit.document()) + text_edit.show() + + plain_text_edit = QPlainTextEdit() + plain_text_edit.setWindowTitle("QPlainTextEdit") + plain_text_edit.setPlainText(text_edit.toPlainText()) + highlighter_plain_text_edit = MyHighlighter(plain_text_edit.document()) + plain_text_edit.show() + + pos = text_edit.pos() + pos.setX(pos.x() + text_edit.width() + 5) + plain_text_edit.move(pos) app.exec() diff --git a/qt__pyqt__pyside__pyqode/pyqt5__QSyntaxHighlighter__with_many_QTextCharFormat.py b/qt__pyqt__pyside__pyqode/pyqt5__QSyntaxHighlighter__with_many_QTextCharFormat.py index 7d1a9283f..0c3db8a08 100644 --- a/qt__pyqt__pyside__pyqode/pyqt5__QSyntaxHighlighter__with_many_QTextCharFormat.py +++ b/qt__pyqt__pyside__pyqode/pyqt5__QSyntaxHighlighter__with_many_QTextCharFormat.py @@ -1,14 +1,14 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PyQt5.Qt import * class MyHighlighter(QSyntaxHighlighter): - def __init__(self, parent): + def __init__(self, parent) -> None: super().__init__(parent) self.regexp_by_format = dict() @@ -16,24 +16,26 @@ def __init__(self, parent): char_format = QTextCharFormat() char_format.setFontWeight(QFont.Bold) char_format.setForeground(Qt.darkMagenta) - self.regexp_by_format[r'\bsin\b'] = char_format + self.regexp_by_format[r"\bsin\b"] = char_format char_format = QTextCharFormat() char_format.setFontWeight(QFont.Bold) char_format.setFontItalic(True) char_format.setForeground(Qt.darkCyan) - self.regexp_by_format[r'\bcos\b'] = char_format + self.regexp_by_format[r"\bcos\b"] = char_format - def highlightBlock(self, text): + def highlightBlock(self, text) -> None: for regexp, char_format in self.regexp_by_format.items(): expression = QRegularExpression(regexp) it = expression.globalMatch(text) while it.hasNext(): match = it.next() - self.setFormat(match.capturedStart(), match.capturedLength(), char_format) + self.setFormat( + match.capturedStart(), match.capturedLength(), char_format + ) -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) mw = QTextEdit() diff --git a/qt__pyqt__pyside__pyqode/pyqt5__QTableWidget__select_all_row_and_column__by_cell_click.py b/qt__pyqt__pyside__pyqode/pyqt5__QTableWidget__select_all_row_and_column__by_cell_click.py index 1cfb3a9bc..79cf2fd54 100644 --- a/qt__pyqt__pyside__pyqode/pyqt5__QTableWidget__select_all_row_and_column__by_cell_click.py +++ b/qt__pyqt__pyside__pyqode/pyqt5__QTableWidget__select_all_row_and_column__by_cell_click.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PyQt5.QtWidgets import QMainWindow, QTableWidget, QTableWidgetItem, QApplication @@ -9,17 +9,17 @@ class Widget(QMainWindow): - def __init__(self): + def __init__(self) -> None: super().__init__() - self.setWindowTitle('Table') + self.setWindowTitle("Table") self.table_widget = QTableWidget() self.table_widget.cellClicked.connect(self._on_cell_clicked) self.setCentralWidget(self.table_widget) - def _on_cell_clicked(self, row, col): + def _on_cell_clicked(self, row, col) -> None: selection_model = self.table_widget.selectionModel() selection_model.clear() @@ -31,18 +31,18 @@ def _on_cell_clicked(self, row, col): selection_model.select(index, QItemSelectionModel.Select) - def fill(self): + def fill(self) -> None: self.table_widget.clear() - labels = ['ID', 'NAME', 'PRICE'] + labels = ["ID", "NAME", "PRICE"] self.table_widget.setColumnCount(len(labels)) self.table_widget.setHorizontalHeaderLabels(labels) for id_, name, price in [ - ['1', 'name_1', 'price_1'], - ['2', 'name_2', 'price_2'], - ['3', 'name_3', 'price_3'], + ["1", "name_1", "price_1"], + ["2", "name_2", "price_2"], + ["3", "name_3", "price_3"], ]: row = self.table_widget.rowCount() self.table_widget.setRowCount(row + 1) @@ -52,7 +52,7 @@ def fill(self): self.table_widget.setItem(row, 2, QTableWidgetItem(price)) -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) w = Widget() diff --git a/qt__pyqt__pyside__pyqode/pyqt5__QTableWidget__show_dialog_on_double_click_cell.py b/qt__pyqt__pyside__pyqode/pyqt5__QTableWidget__show_dialog_on_double_click_cell.py index a3d4cbfc5..9c67d101c 100644 --- a/qt__pyqt__pyside__pyqode/pyqt5__QTableWidget__show_dialog_on_double_click_cell.py +++ b/qt__pyqt__pyside__pyqode/pyqt5__QTableWidget__show_dialog_on_double_click_cell.py @@ -1,14 +1,14 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PyQt5 import Qt class MainWindow(Qt.QMainWindow): - def __init__(self): + def __init__(self) -> None: super().__init__() self.table = Qt.QTableWidget() @@ -16,7 +16,7 @@ def __init__(self): self.table.setColumnCount(2) self.table.itemDoubleClicked.connect(self.on_cell_item_clicked) - rows = ['Vasya', 'Petya', 'Masha'] + rows = ["Vasya", "Petya", "Masha"] for i, value in enumerate(rows): self.table.setItem(i, 0, Qt.QTableWidgetItem(str(i + 1))) @@ -29,15 +29,17 @@ def __init__(self): self.setCentralWidget(main_widget) - def on_cell_item_clicked(self, item): + def on_cell_item_clicked(self, item) -> None: print(item) - new_text, ok = Qt.QInputDialog.getText(self, 'Change Name', 'Change Name', text=item.text()) + new_text, ok = Qt.QInputDialog.getText( + self, "Change Name", "Change Name", text=item.text() + ) if ok: item.setText(new_text) -if __name__ == '__main__': +if __name__ == "__main__": app = Qt.QApplication([]) mw = MainWindow() diff --git a/qt__pyqt__pyside__pyqode/pyqt5__QThread_with_QProgressDialog_PleaseWait.py b/qt__pyqt__pyside__pyqode/pyqt5__QThread_with_QProgressDialog_PleaseWait.py index b203c538e..4d8d76894 100644 --- a/qt__pyqt__pyside__pyqode/pyqt5__QThread_with_QProgressDialog_PleaseWait.py +++ b/qt__pyqt__pyside__pyqode/pyqt5__QThread_with_QProgressDialog_PleaseWait.py @@ -1,29 +1,30 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" +import time from PyQt5.Qt import * class RunFuncThread(QThread): run_finished = pyqtSignal(object) - def __init__(self, func): + def __init__(self, func) -> None: super().__init__() self.func = func - def run(self): + def run(self) -> None: self.run_finished.emit(self.func()) class MainWindow(QWidget): - def __init__(self): + def __init__(self) -> None: super().__init__() - self.pb_go = QPushButton('Go') + self.pb_go = QPushButton("Go") self.pb_go.clicked.connect(self.go) self.te_log = QPlainTextEdit() @@ -34,14 +35,13 @@ def __init__(self): self.setLayout(layout) - def _on_run_finished(self, value): + def _on_run_finished(self, value) -> None: self.te_log.setPlainText(str(value)) - def go(self): + def go(self) -> None: progress_dialog = QProgressDialog(self) def foo(): - import time items = [] for i in range(10): @@ -57,13 +57,13 @@ def foo(): thread.run_finished.connect(progress_dialog.close) thread.start() - progress_dialog.setWindowTitle('Please wait...') + progress_dialog.setWindowTitle("Please wait...") progress_dialog.setLabelText(progress_dialog.windowTitle()) progress_dialog.setRange(0, 0) progress_dialog.exec() -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) mw = MainWindow() diff --git a/qt__pyqt__pyside__pyqode/pyqt5__QTreeView_QFileSystemModel.py b/qt__pyqt__pyside__pyqode/pyqt5__QTreeView_QFileSystemModel.py index 5e882a3e2..047c7fe30 100644 --- a/qt__pyqt__pyside__pyqode/pyqt5__QTreeView_QFileSystemModel.py +++ b/qt__pyqt__pyside__pyqode/pyqt5__QTreeView_QFileSystemModel.py @@ -1,14 +1,14 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PyQt5.QtWidgets import QTreeView, QFileSystemModel, QApplication from PyQt5.QtCore import QDir -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication([]) model = QFileSystemModel() diff --git a/qt__pyqt__pyside__pyqode/pyqt5__QWebEnginePage__Client_async__multiprocessing__process.py b/qt__pyqt__pyside__pyqode/pyqt5__QWebEnginePage__Client_async__multiprocessing__process.py index 1c9787b21..8013eed2f 100644 --- a/qt__pyqt__pyside__pyqode/pyqt5__QWebEnginePage__Client_async__multiprocessing__process.py +++ b/qt__pyqt__pyside__pyqode/pyqt5__QWebEnginePage__Client_async__multiprocessing__process.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PyQt5.QtWidgets import QApplication @@ -10,7 +10,7 @@ class Client(QWebEnginePage): - def __init__(self, urls): + def __init__(self, urls) -> None: self.app = QApplication([]) super().__init__() @@ -22,10 +22,10 @@ def __init__(self, urls): self.load(QUrl(url)) self.app.exec_() - def _on_load_finished(self): + def _on_load_finished(self) -> None: self.toHtml(self.callable) - def callable(self, html_str): + def callable(self, html_str) -> None: self.response_list.append(html_str) self.app.quit() @@ -35,22 +35,23 @@ def go(urls): return client.response_list -if __name__ == '__main__': +if __name__ == "__main__": urls = [ [ - 'http://doc.qt.io/Qt-5/qwebenginepage.html', - 'https://yandex.ru/', + "http://doc.qt.io/Qt-5/qwebenginepage.html", + "https://yandex.ru/", ], [ - 'http://doc.qt.io/Qt-5/qwebenginepage.html', - 'https://www.google.ru/', + "http://doc.qt.io/Qt-5/qwebenginepage.html", + "https://www.google.ru/", ], [ - 'https://www.google.ru/', - ] + "https://www.google.ru/", + ], ] from multiprocessing import Pool + with Pool() as p: results = p.map(go, urls) print(len(results)) @@ -61,7 +62,7 @@ def go(urls): print(len(result)) for html in result: - with open('result_{}.html'.format(number), 'w', encoding='utf-8') as f: + with open(f"result_{number}.html", "w", encoding="utf-8") as f: f.write(html) number += 1 diff --git a/qt__pyqt__pyside__pyqode/pyqt5__QWebEnginePage__Client_sync.py b/qt__pyqt__pyside__pyqode/pyqt5__QWebEnginePage__Client_sync.py index 4cc5bbd9d..9a6d08c47 100644 --- a/qt__pyqt__pyside__pyqode/pyqt5__QWebEnginePage__Client_sync.py +++ b/qt__pyqt__pyside__pyqode/pyqt5__QWebEnginePage__Client_sync.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -__author__ = 'ipetrash' +__author__ = "ipetrash" from PyQt5.QtWidgets import QApplication @@ -10,7 +10,7 @@ class Client(QWebEnginePage): - def __init__(self, urls): + def __init__(self, urls) -> None: self.app = QApplication([]) super().__init__() @@ -22,19 +22,20 @@ def __init__(self, urls): self.load(QUrl(url)) self.app.exec_() - def _on_load_finished(self): + def _on_load_finished(self) -> None: self.toHtml(self.callable) - def callable(self, html_str): + def callable(self, html_str) -> None: self.response_list.append(html_str) self.app.quit() -if __name__ == '__main__': +if __name__ == "__main__": urls = [ - 'http://doc.qt.io/Qt-5/qwebenginepage.html', - 'https://www.google.ru/', - 'https://yandex.ru/', + "http://doc.qt.io/Qt-5/qwebenginepage.html", + "https://www.google.ru/", + "https://yandex.ru/", ] client = Client(urls) - print(len(client.response_list), client.response_list) # 3 ['