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/Base64_examples/decode_to_file.py b/Base64_examples/decode_to_file.py
index 06d4ba375..a2ce75c7e 100644
--- a/Base64_examples/decode_to_file.py
+++ b/Base64_examples/decode_to_file.py
@@ -7,7 +7,7 @@
from base64 import b64decode
-def decode_base64_to_file(file_name: str, text_base64: str):
+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)
diff --git a/Base64_examples/gui_base64.py b/Base64_examples/gui_base64.py
index 70afbfd9e..ba49bbf97 100644
--- a/Base64_examples/gui_base64.py
+++ b/Base64_examples/gui_base64.py
@@ -24,7 +24,7 @@
from PySide.QtCore import *
-def log_uncaught_exceptions(ex_cls, ex, tb):
+def log_uncaught_exceptions(ex_cls, ex, tb) -> None:
text = f"{ex_cls.__name__}: {ex}:\n"
text += "".join(traceback.format_tb(tb))
@@ -136,7 +136,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])
@@ -205,7 +205,7 @@ def __init__(self):
self.setLayout(layout)
- def show_detail_error_massage(self):
+ def show_detail_error_massage(self) -> None:
message = self.last_error_message + "\n\n" + self.last_detail_error_message
mb = QErrorMessage()
@@ -217,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()
@@ -256,7 +256,7 @@ def input_text_changed(self):
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"
diff --git a/CONTACT__examples/create_and_fill_database_from_dictionary.py b/CONTACT__examples/create_and_fill_database_from_dictionary.py
index fc566ae27..4537d3050 100644
--- a/CONTACT__examples/create_and_fill_database_from_dictionary.py
+++ b/CONTACT__examples/create_and_fill_database_from_dictionary.py
@@ -120,7 +120,7 @@ def create_connect():
def create_table(
table_name: str, sql_table: str, sql_table_data_rows: str, drop_table=False
-):
+) -> None:
# Создание таблицы
connect = create_connect()
try:
diff --git a/Callable modules/print_this.py b/Callable modules/print_this.py
index 507c4999f..6f8f95e67 100644
--- a/Callable modules/print_this.py
+++ b/Callable modules/print_this.py
@@ -9,7 +9,7 @@
# SOURCE: https://stackoverflow.com/a/1060872/5909792
class mod_call(object):
- def __call__(self, text):
+ def __call__(self, text) -> None:
print(text)
diff --git a/CreateNoteXmlNoteManagers/CreateNoteXmlNoteManagers.py b/CreateNoteXmlNoteManagers/CreateNoteXmlNoteManagers.py
index 4d9c6ba97..cb60a4485 100644
--- a/CreateNoteXmlNoteManagers/CreateNoteXmlNoteManagers.py
+++ b/CreateNoteXmlNoteManagers/CreateNoteXmlNoteManagers.py
@@ -7,7 +7,7 @@
import os
-def main(namespace):
+def main(namespace) -> None:
# @param namespace argparse.Namespace Содержит переданные в аргументах объекты.
indent = " " * 8
@@ -22,7 +22,7 @@ def main(namespace):
)
-def create_parser():
+def create_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
prog="CreateNoteXml",
description="Cкрипт генерирует xml-файл программы NotesManager.",
@@ -35,10 +35,11 @@ def create_parser():
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()
diff --git a/Crypto Git Repository/api.py b/Crypto Git Repository/api.py
index f02684c0f..bbd03226b 100644
--- a/Crypto Git Repository/api.py
+++ b/Crypto Git Repository/api.py
@@ -33,7 +33,7 @@ def get_repo():
repo = get_repo()
-def print_log(reverse=False):
+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)}]:")
@@ -44,27 +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/DNS price tracking/db.py b/DNS price tracking/db.py
index 8bb6adc59..91a0635fe 100644
--- a/DNS price tracking/db.py
+++ b/DNS price tracking/db.py
@@ -16,7 +16,7 @@
DB_FILE_NAME = str(pathlib.Path(__file__).resolve().parent / "tracked_products.sqlite")
-def db_create_backup(backup_dir="backup"):
+def db_create_backup(backup_dir="backup") -> None:
os.makedirs(backup_dir, exist_ok=True)
file_name = str(dt.datetime.today().date()) + ".sqlite"
@@ -67,12 +67,12 @@ def get_last_price_technopoint(self, actual_price=True):
return last_price.value_technopoint
- def append_price(self, value_dns, 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}, "
@@ -91,7 +91,7 @@ class Price(BaseModel):
class Meta:
indexes = ((("product_id", "date", "value_dns", "value_technopoint"), True),)
- def __str__(self):
+ def __str__(self) -> str:
return (
f"Price(value_dns={self.value_dns}, "
f"value_technopoint={self.value_technopoint}, "
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 1c3138000..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"
@@ -63,7 +63,7 @@ def fix_command(text):
if __name__ == "__main__":
# SHOW RESULT
- def check(text):
+ def check(text) -> None:
format_text = "{:<%s} -> {}" % (len(max(ALL_COMMANDS, key=len)) + 2)
command = fix_command(text)
@@ -95,8 +95,8 @@ def check(text):
#
# 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, f'Expected: "{expected}", get: "{command}"'
diff --git a/Decorators__examples/append_handlers.py b/Decorators__examples/append_handlers.py
index d95bbe4ce..c44b19bfd 100644
--- a/Decorators__examples/append_handlers.py
+++ b/Decorators__examples/append_handlers.py
@@ -8,7 +8,7 @@
class Collector:
- def __init__(self):
+ def __init__(self) -> None:
self.handlers = []
self.handlers_by_name = defaultdict(list)
@@ -26,7 +26,7 @@ def decorator(func):
@collector.add(name="test")
-def hello_world(end="!"):
+def hello_world(end="!") -> None:
print("hello world" + end)
@@ -41,7 +41,7 @@ def hello_world(end="!"):
@collector.add(name="this it say_hello!")
-def say_hello():
+def say_hello() -> None:
print("hello!")
diff --git a/Decorators__examples/combine_decorators__with_args.py b/Decorators__examples/combine_decorators__with_args.py
index 506c98594..6e71ff93a 100644
--- a/Decorators__examples/combine_decorators__with_args.py
+++ b/Decorators__examples/combine_decorators__with_args.py
@@ -18,7 +18,7 @@ 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)}"
@@ -30,7 +30,7 @@ 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)}"
@@ -53,7 +53,7 @@ def custom_tag(
):
def actual_decorator(func):
@functools.wraps(func)
- def wrapped(*args, **kwargs):
+ def wrapped(*args, **kwargs) -> str:
attrs = get_attrs_str(arguments)
return f"<{name}{attrs}>{func(*args, **kwargs)}{name}>"
diff --git a/Decorators__examples/decorator__args_as_funcs.py b/Decorators__examples/decorator__args_as_funcs.py
index 1563f2dda..b6b27499f 100644
--- a/Decorators__examples/decorator__args_as_funcs.py
+++ b/Decorators__examples/decorator__args_as_funcs.py
@@ -5,7 +5,7 @@
class TextBuilder:
- def __init__(self):
+ def __init__(self) -> None:
self.result = []
# Функция, принимающая аргументы и возвращающая декоратор
diff --git a/Decorators__examples/decorator_method_class__with_shelve.py b/Decorators__examples/decorator_method_class__with_shelve.py
index 1a73a703a..5ff1f1429 100644
--- a/Decorators__examples/decorator_method_class__with_shelve.py
+++ b/Decorators__examples/decorator_method_class__with_shelve.py
@@ -22,7 +22,7 @@
class DB:
db_name: str = str(DB_FILE_NAME)
- def __init__(self):
+ def __init__(self) -> None:
self.db: shelve.Shelf | None = None
def session(*decorator_args, **decorator_kwargs):
@@ -53,7 +53,7 @@ def get_value(self, name: str, default: Any = None) -> Any:
return self.db.get(name)
@session()
- def set_value(self, name: str, value: Any):
+ def set_value(self, name: str, value: Any) -> None:
self.db[name] = value
def inc_value(self, name: str) -> int:
diff --git a/Decorators__examples/example_1.py b/Decorators__examples/example_1.py
index 82c0e38a6..e7d123809 100644
--- a/Decorators__examples/example_1.py
+++ b/Decorators__examples/example_1.py
@@ -1,7 +1,7 @@
__author__ = "ipetrash"
-def getprint(str="hello world!"):
+def getprint(str="hello world!") -> None:
print(str)
@@ -16,7 +16,7 @@ def wrapper(*args, **kwargs):
return wrapper
-def predecor(w="W"):
+def predecor(w="W") -> None:
print(w, end=": ")
@@ -29,7 +29,7 @@ def predecor(w="W"):
def rgb2hex(get_rgb_func):
- def wrapper(*args, **kwargs):
+ def wrapper(*args, **kwargs) -> str:
r, g, b = get_rgb_func(*args, **kwargs)
return f"#{r:02x}{g:02x}{b:02x}"
@@ -37,7 +37,7 @@ def wrapper(*args, **kwargs):
class RGB:
- def __init__(self):
+ def __init__(self) -> None:
self._r = 0xFF
self._g = 0xFF
self._b = 0xFF
@@ -45,7 +45,7 @@ def __init__(self):
def getr(self):
return self._r
- def setr(self, r):
+ def setr(self, r) -> None:
self._r = r
r = property(getr, setr)
@@ -53,7 +53,7 @@ def setr(self, r):
def getg(self):
return self._g
- def setg(self, g):
+ def setg(self, g) -> None:
self._g = g
g = property(getg, setg)
@@ -61,12 +61,12 @@ def setg(self, g):
def getb(self):
return self._b
- def setb(self, b):
+ def setb(self, b) -> None:
self._b = b
b = property(getb, setb)
- def setrgb(self, r, g, b):
+ def setrgb(self, r, g, b) -> None:
self.r, self.g, self.b = r, g, b
@rgb2hex
@@ -82,7 +82,7 @@ def getrgb(self):
@decor
-def foo(a, b):
+def foo(a, b) -> None:
print(f"{a} ^ {b} = {(a ** b)}")
diff --git a/Decorators__examples/memoize_class.py b/Decorators__examples/memoize_class.py
index 2c68b48ae..3654b2225 100644
--- a/Decorators__examples/memoize_class.py
+++ b/Decorators__examples/memoize_class.py
@@ -6,7 +6,7 @@
# 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/thread.py b/Decorators__examples/thread.py
index bbb34bd2e..70426ff29 100644
--- a/Decorators__examples/thread.py
+++ b/Decorators__examples/thread.py
@@ -9,7 +9,7 @@
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()
@@ -18,14 +18,14 @@ def wrapper(*args, **kwargs):
if __name__ == "__main__":
@thread
- def _print_and_sleep(timeout=2):
+ def _print_and_sleep(timeout=2) -> None:
print("start. print_and_sleep")
time.sleep(timeout)
print("finish. print_and_sleep")
@thread
- def _print_loop(name, max_num=10):
+ def _print_loop(name, max_num=10) -> None:
print("start. _print_loop")
i = 0
diff --git a/Decorators__examples/timer.py b/Decorators__examples/timer.py
index aa8e5d92a..c441a32ad 100644
--- a/Decorators__examples/timer.py
+++ b/Decorators__examples/timer.py
@@ -19,7 +19,7 @@ def wrapper(*args, **kwargs):
if __name__ == "__main__":
@timer
- def my_sleep():
+ def my_sleep() -> None:
print(123)
time.sleep(0.3)
print(456)
diff --git a/EscapePyPromptLineString/main.py b/EscapePyPromptLineString/main.py
index 57a403ee7..4555e837e 100644
--- a/EscapePyPromptLineString/main.py
+++ b/EscapePyPromptLineString/main.py
@@ -23,7 +23,7 @@
from PySide.QtCore import *
-def log_uncaught_exceptions(ex_cls, ex, tb):
+def log_uncaught_exceptions(ex_cls, ex, tb) -> None:
text = f"{ex_cls.__name__}: {ex}:\n"
text += "".join(traceback.format_tb(tb))
@@ -36,7 +36,7 @@ def log_uncaught_exceptions(ex_cls, ex, tb):
class MainWindow(QWidget):
- def __init__(self):
+ def __init__(self) -> None:
super().__init__()
self.setWindowTitle("EscapePyPromptLineString")
@@ -77,7 +77,7 @@ def __init__(self):
self.setLayout(layout)
- def show_detail_error_massage(self):
+ def show_detail_error_massage(self) -> None:
message = self.last_error_message + "\n\n" + self.last_detail_error_message
mb = QErrorMessage()
@@ -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()
diff --git a/EscapeString/main.py b/EscapeString/main.py
index d90a6f4dc..c6b7903dd 100644
--- a/EscapeString/main.py
+++ b/EscapeString/main.py
@@ -23,7 +23,7 @@
from PySide.QtCore import *
-def log_uncaught_exceptions(ex_cls, ex, tb):
+def log_uncaught_exceptions(ex_cls, ex, tb) -> None:
text = f"{ex_cls.__name__}: {ex}:\n"
text += "".join(traceback.format_tb(tb))
@@ -47,7 +47,7 @@ class MainWindow(QWidget):
]
TITLE = "EscapeString"
- def __init__(self, parent=None):
+ def __init__(self, parent=None) -> None:
super().__init__(parent)
self.setWindowTitle(self.TITLE)
@@ -129,7 +129,7 @@ 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
@@ -161,7 +161,7 @@ def _escape(self, in_text: str, ignored="") -> str:
return "".join(out_text)
- def input_text_changed(self):
+ def input_text_changed(self) -> None:
self.label_error.clear()
self.button_detail_error.hide()
diff --git a/Fibonacci number/6 ways.py b/Fibonacci number/6 ways.py
index 4cef3fe92..25cd58937 100644
--- a/Fibonacci number/6 ways.py
+++ b/Fibonacci number/6 ways.py
@@ -66,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()
diff --git a/Fibonacci number/FibonacciNumber.py b/Fibonacci number/FibonacciNumber.py
index c9015d9c3..a164d783e 100644
--- a/Fibonacci number/FibonacciNumber.py
+++ b/Fibonacci number/FibonacciNumber.py
@@ -1,14 +1,14 @@
__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])
print(f)
-def fibo_2(n):
+def fibo_2(n) -> None:
a, b = 0, 1
print(a, b, end=" ")
for i in range(2, n + 1):
diff --git a/HelloPython/HelloPython.py b/HelloPython/HelloPython.py
index 45acc7ba5..388f750d8 100644
--- a/HelloPython/HelloPython.py
+++ b/HelloPython/HelloPython.py
@@ -5,7 +5,7 @@
from datetime import datetime, time
-def main(namespace):
+def main(namespace) -> None:
args = namespace.parse_args()
args.user = "Илья"
if args.user:
@@ -29,7 +29,7 @@ def main(namespace):
print("Привет, Python!")
-def create_parser():
+def create_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(description="Hello World Example!")
parser.add_argument("--user", type=str, help=" user name.")
return parser
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/No choice line edit.py b/No choice line edit.py
index b28356739..c8db94aac 100644
--- a/No choice line edit.py
+++ b/No choice line edit.py
@@ -14,7 +14,7 @@
class Widget(QWidget):
- def __init__(self):
+ def __init__(self) -> None:
super().__init__()
self.setWindowTitle("No choice")
@@ -33,7 +33,7 @@ def __init__(self):
self.line_edit_no_choice.setFocus()
- def on_text_edited_no_choice(self, 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)
diff --git a/ObjectWithArrayAccess.py b/ObjectWithArrayAccess.py
index 0cdd90e4e..1886745e0 100644
--- a/ObjectWithArrayAccess.py
+++ b/ObjectWithArrayAccess.py
@@ -5,16 +5,16 @@
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)
diff --git a/OpenSSL_example/generate_selfsigned_cert.py b/OpenSSL_example/generate_selfsigned_cert.py
index 8e9ee5663..c71bd75f5 100644
--- a/OpenSSL_example/generate_selfsigned_cert.py
+++ b/OpenSSL_example/generate_selfsigned_cert.py
@@ -22,7 +22,7 @@ def cert_gen(
validity_end_in_seconds=10 * 365 * 24 * 60 * 60,
key_file="key.pem",
cert_file="cert.pem",
-):
+) -> None:
# Create a key pair
k = crypto.PKey()
k.generate_key(crypto.TYPE_RSA, 4096)
diff --git a/OpenSSL_example/p12_to_pem.py b/OpenSSL_example/p12_to_pem.py
index d0d53a4ac..8c419fd66 100644
--- a/OpenSSL_example/p12_to_pem.py
+++ b/OpenSSL_example/p12_to_pem.py
@@ -8,7 +8,7 @@
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."""
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 63eb494c3..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
@@ -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 c02bb2f7a..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
@@ -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/gist_history_to_sqlite_db.py b/PyGithub_examples/gist_history_to_sqlite_db.py
index 06b68f552..8cd7ca839 100644
--- a/PyGithub_examples/gist_history_to_sqlite_db.py
+++ b/PyGithub_examples/gist_history_to_sqlite_db.py
@@ -12,7 +12,7 @@ def create_connect():
return sqlite3.connect("gist_commits.sqlite")
-def init_db():
+def init_db() -> None:
# Создание базы и таблицы
with create_connect() as connect:
connect.execute(
diff --git a/PyOpenGLExample/checkerboard.py b/PyOpenGLExample/checkerboard.py
index 5d4ae28e6..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)
diff --git a/PyOpenGLExample/gingerbread.py b/PyOpenGLExample/gingerbread.py
index da64a3b00..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)
diff --git a/PyOpenGLExample/helloworld.py b/PyOpenGLExample/helloworld.py
index 227086e07..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)
diff --git a/PyOpenGLExample/maze.py b/PyOpenGLExample/maze.py
index 039ffe3a0..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
@@ -155,7 +155,7 @@ def __init__(self, dimx, dimy):
# Push it back on the list since this is our next node
cell_list.append(conn)
- def draw(self):
+ def draw(self) -> None:
"""Draws the field"""
glBegin(GL_LINES)
for i in range(0, self.numx * self.numy):
@@ -184,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)
@@ -193,7 +193,7 @@ 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()
diff --git a/PyOpenGLExample/mouse.py b/PyOpenGLExample/mouse.py
index 3903dce15..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)
diff --git a/PyOpenGLExample/print_text.py b/PyOpenGLExample/print_text.py
index 4d58f78c7..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,12 +43,12 @@ 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()
@@ -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,7 +80,7 @@ def glut_print(x, y, font, text, r, g, b, a):
glDisable(GL_BLEND)
-def keyPressed(*args):
+def keyPressed(*args) -> None:
ESCAPE = b"\x1b"
if args[0] == ESCAPE:
sys.exit()
diff --git a/PyOpenGLExample/reshape.py b/PyOpenGLExample/reshape.py
index 360c28728..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)
diff --git a/PyOpenGLExample/rosette.py b/PyOpenGLExample/rosette.py
index 561e50f84..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 = []
diff --git a/PyOpenGLExample/scatter.py b/PyOpenGLExample/scatter.py
index 82be48d3b..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)
@@ -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)
diff --git a/PyOpenGLExample/sierpinski.py b/PyOpenGLExample/sierpinski.py
index f70bfb32d..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)
diff --git a/PyOpenGLExample/squares.py b/PyOpenGLExample/squares.py
index 64dd72e86..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,7 +24,7 @@ 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 = random.randint(0, 25) / 25.0
diff --git a/PyOpenGLExample/turtle.py b/PyOpenGLExample/turtle.py
index a1deb6187..4b8e94969 100644
--- a/PyOpenGLExample/turtle.py
+++ b/PyOpenGLExample/turtle.py
@@ -23,7 +23,7 @@
angle = 0.0
-def reset():
+def reset() -> None:
"""Reset the position to the origin"""
global curX
global curY
@@ -34,19 +34,19 @@ def reset():
angle = 0.0
-def turnTo(deg):
+def turnTo(deg) -> None:
"""Turn to a certain angle"""
global angle
angle = deg
-def turn(deg):
+def turn(deg) -> None:
"""Turn a certain number of degrees"""
global angle
angle += deg
-def forw(len, visible):
+def forw(len, visible) -> None:
"""Move forward over a certain distance"""
global curX
global curY
@@ -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()
diff --git a/PyOpenGLExample/wireframe.py b/PyOpenGLExample/wireframe.py
index 47523d88b..1d16c92d7 100644
--- a/PyOpenGLExample/wireframe.py
+++ b/PyOpenGLExample/wireframe.py
@@ -12,7 +12,7 @@
"""
-def axis(length):
+def axis(length) -> None:
"""Draws an axis (basicly a line with a cone on top)"""
glPushMatrix()
glBegin(GL_LINES)
@@ -24,7 +24,7 @@ def axis(length):
glPopMatrix()
-def three_axis(length):
+def three_axis(length) -> None:
"""Draws an X, Y and Z-axis"""
glPushMatrix()
@@ -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)
diff --git a/Serialize with pickle/SerializeWithPickle.py b/Serialize with pickle/SerializeWithPickle.py
index e799b9918..2ad97101d 100644
--- a/Serialize with pickle/SerializeWithPickle.py
+++ b/Serialize with pickle/SerializeWithPickle.py
@@ -48,20 +48,20 @@
class Monster:
"""Monster class!"""
- def __init__(self, health, power, name, level):
+ 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):
+ 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):
+ def say(self) -> None:
print("I'm %s" % self.name)
zombi = Monster(name="Zombi", health=100, power=10, level=2)
diff --git a/Sorting algorithm list/comparison_of_sorting.py b/Sorting algorithm list/comparison_of_sorting.py
index 2d9757002..afb189f32 100644
--- a/Sorting algorithm list/comparison_of_sorting.py
+++ b/Sorting algorithm list/comparison_of_sorting.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)
@@ -48,7 +48,7 @@ def localtrace(self, frame, why, arg):
raise Kill()
return self.localtrace
- def kill(self):
+ def kill(self) -> None:
self.killed = True
@@ -75,9 +75,10 @@ def the_wrapper_around_the_original_function(*args, **kwargs):
if __name__ == "__main__":
import random
- import time
import sorts
+ from timeit import default_timer
+
items = list(range(10**3))
random.shuffle(items)
@@ -87,11 +88,11 @@ def the_wrapper_around_the_original_function(*args, **kwargs):
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()
@@ -101,7 +102,7 @@ def run():
except Exception as e:
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))
diff --git a/Sorting algorithm list/sorts/bogosort.py b/Sorting algorithm list/sorts/bogosort.py
index 6d302b43f..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):
diff --git a/Sorting algorithm list/sorts/heap_sort.py b/Sorting algorithm list/sorts/heap_sort.py
index d1eeb2356..869ed18fb 100644
--- a/Sorting algorithm list/sorts/heap_sort.py
+++ b/Sorting algorithm list/sorts/heap_sort.py
@@ -13,7 +13,7 @@
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
diff --git a/Sorting/sorting.py b/Sorting/sorting.py
index 330c7038c..e441a5323 100644
--- a/Sorting/sorting.py
+++ b/Sorting/sorting.py
@@ -24,11 +24,11 @@
class Student:
- def __init__(self, name, age):
+ def __init__(self, name, age) -> None:
self.name = name
self.age = age
- def __repr__(self):
+ def __repr__(self) -> str:
return "%s (%d)" % (self.name, self.age)
diff --git a/Timer/pyside_qtimer.py b/Timer/pyside_qtimer.py
index 1d02eeb79..4d6640a2f 100644
--- a/Timer/pyside_qtimer.py
+++ b/Timer/pyside_qtimer.py
@@ -8,7 +8,7 @@
from PySide.QtCore import *
-def say():
+def say() -> None:
print("say!")
diff --git a/WrapperMap__work_with_dict_through_atts.py b/WrapperMap__work_with_dict_through_atts.py
index 8cc9b7cfe..316813bf1 100644
--- a/WrapperMap__work_with_dict_through_atts.py
+++ b/WrapperMap__work_with_dict_through_atts.py
@@ -5,7 +5,7 @@
class WrapperMap:
- def __init__(self, d: dict):
+ def __init__(self, d: dict) -> None:
self.d = d
def get_value(self):
@@ -18,7 +18,7 @@ def __getattr__(self, item: str):
return value
- def __repr__(self):
+ def __repr__(self) -> str:
return repr(self.d)
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 9c53aa684..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
@@ -12,13 +12,13 @@
class Dog:
- def __init__(self, name):
+ def __init__(self, name) -> None:
self.name = name
self.type = "Animal"
self.paws = 4
self.has_tail = True
- def __repr__(self):
+ def __repr__(self) -> str:
return f''
diff --git a/XML/XML_to_dict__xmltodict__examples/streaming_mode.py b/XML/XML_to_dict__xmltodict__examples/streaming_mode.py
index c7c8086e4..ead6df188 100644
--- a/XML/XML_to_dict__xmltodict__examples/streaming_mode.py
+++ b/XML/XML_to_dict__xmltodict__examples/streaming_mode.py
@@ -11,7 +11,7 @@
import xmltodict
-def handle(path, 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 d9be22934..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
@@ -18,7 +18,7 @@
url = "http://discogs-data.s3-us-west-2.amazonaws.com/data/2018/discogs_20180201_artists.xml.gz"
-def handle_artist(_, artist):
+def handle_artist(_, artist) -> bool:
print(artist["name"])
return True
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/sax_FIAS_with_progress__HOUSEGUID.py b/XML/sax_FIAS_with_progress__HOUSEGUID.py
index ddcb1ba70..39f211183 100644
--- a/XML/sax_FIAS_with_progress__HOUSEGUID.py
+++ b/XML/sax_FIAS_with_progress__HOUSEGUID.py
@@ -13,10 +13,10 @@
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):
+ def startElement(self, name, attrs) -> None:
if "HOUSEGUID" in attrs:
all_house_guid.add(attrs["HOUSEGUID"])
diff --git a/XML/sax_FIAS_with_progress__HOUSEGUID__no_collect.py b/XML/sax_FIAS_with_progress__HOUSEGUID__no_collect.py
index b4f98434c..71e03517a 100644
--- a/XML/sax_FIAS_with_progress__HOUSEGUID__no_collect.py
+++ b/XML/sax_FIAS_with_progress__HOUSEGUID__no_collect.py
@@ -13,11 +13,11 @@
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):
+ def startElement(self, name, attrs) -> None:
if "HOUSEGUID" in attrs:
guid = attrs["HOUSEGUID"]
if self.number > 0:
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 48891d48b..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
@@ -9,19 +9,19 @@
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.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("[")
- def startElement(self, name, attrs):
+ def startElement(self, name, attrs) -> None:
if "HOUSEGUID" in attrs:
guid = attrs["HOUSEGUID"]
@@ -33,7 +33,7 @@ def startElement(self, name, attrs):
next(self.it)
- def endDocument(self):
+ def endDocument(self) -> None:
self.f.write("]")
self.f.close()
diff --git a/XML/xml.etree.ElementTree__examples/pretty_print.py b/XML/xml.etree.ElementTree__examples/pretty_print.py
index ff36c786a..93836b547 100644
--- a/XML/xml.etree.ElementTree__examples/pretty_print.py
+++ b/XML/xml.etree.ElementTree__examples/pretty_print.py
@@ -10,7 +10,7 @@
# SOURCE: http://effbot.org/zone/element-lib.htm#prettyprint
-def indent(elem, level=0):
+def indent(elem, level=0) -> None:
i = "\n" + level * " "
if len(elem):
if not elem.text or not elem.text.strip():
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 f9f24ae66..0fa8d4d0d 100644
--- a/XML/xml.parsers.expat__examples__like_sax/hello_world.py
+++ b/XML/xml.parsers.expat__examples__like_sax/hello_world.py
@@ -11,15 +11,15 @@
# 3 handler functions
-def on_start_element(name, attrs):
+def on_start_element(name, attrs) -> None:
print("Start element:", name, attrs)
-def on_end_element(name):
+def on_end_element(name) -> None:
print("End element:", name)
-def on_char_data(data):
+def on_char_data(data) -> None:
print("Character data:", repr(data))
diff --git a/XML/xml_replace_comments/xml_replace_comments.py b/XML/xml_replace_comments/xml_replace_comments.py
index a08375ee6..868ee74e4 100644
--- a/XML/xml_replace_comments/xml_replace_comments.py
+++ b/XML/xml_replace_comments/xml_replace_comments.py
@@ -9,7 +9,7 @@
from lxml import etree
-def replace(file_name, save_file_name):
+def replace(file_name, save_file_name) -> None:
with open(file_name, encoding="utf8") as f:
text = f.read()
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 |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+
+
+ |
+ 01 | 02 | 03 | 04 |
+ 05 | 06 | 07 | 08 |
+ 09 | 10 | 11 | 12 |
+
+
+ | 2024 |
+
+
+
+
+
+
+
+ |
+ 09 | 10 | 11 | 12 |
+ 01 | 02 | 03 | 04 |
+ 05 | 06 | 07 | 08 |
+
+
+ | 2023 |
+ 2024 |
+
+
+
+
+
+
+
+ |
+ 12 | 01 | 02 | 03 |
+ 04 | 05 | 06 | 07 |
+ 08 | 09 | 10 | 11 |
+
+
+ | 2022 |
+ 2023 |
+
+
+
diff --git a/_FOO_TEST_TEST/FOO_TEST_TEST.py b/_FOO_TEST_TEST/FOO_TEST_TEST.py
index 9dae7bd57..86c64dacc 100644
--- a/_FOO_TEST_TEST/FOO_TEST_TEST.py
+++ b/_FOO_TEST_TEST/FOO_TEST_TEST.py
@@ -4,4 +4,885 @@
__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/job_compassplus/parse_jira_logged_time/common.py b/_FOO_TEST_TEST/config.py
similarity index 82%
rename from job_compassplus/parse_jira_logged_time/common.py
rename to _FOO_TEST_TEST/config.py
index 6c28083e4..07b3e6c3d 100644
--- a/job_compassplus/parse_jira_logged_time/common.py
+++ b/_FOO_TEST_TEST/config.py
@@ -8,4 +8,5 @@
DIR = Path(__file__).resolve().parent
-ROOT_DIR = DIR.parent
+
+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/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
+
+
+
+
+
+ | Task |
+ Minor Bug |
+ Important Bug |
+ End of Support |
+
+
+
+
+
+
\ No newline at end of file
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/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 8f074b831..b905dc183 100644
--- a/aiohttp__asyncio__examples/aiohttp_check_user_agent.py
+++ b/aiohttp__asyncio__examples/aiohttp_check_user_agent.py
@@ -10,7 +10,7 @@
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:
print("Status:", rs.status)
diff --git a/aiohttp__asyncio__examples/aiohttp_client.py b/aiohttp__asyncio__examples/aiohttp_client.py
index 07dd57d52..44ba6d470 100644
--- a/aiohttp__asyncio__examples/aiohttp_client.py
+++ b/aiohttp__asyncio__examples/aiohttp_client.py
@@ -10,7 +10,7 @@
import aiohttp
-async def main():
+async def main() -> None:
url = "https://python.org"
async with aiohttp.ClientSession() as session:
diff --git a/aiohttp__asyncio__examples/asyncio__hello_world.py b/aiohttp__asyncio__examples/asyncio__hello_world.py
index 1c4032bd3..432a7777e 100644
--- a/aiohttp__asyncio__examples/asyncio__hello_world.py
+++ b/aiohttp__asyncio__examples/asyncio__hello_world.py
@@ -7,7 +7,7 @@
import asyncio
-async def main():
+async def main() -> None:
print("Hello ", end="")
await asyncio.sleep(1)
print("World!")
diff --git a/aiohttp__asyncio__examples/concurrent_requests.py b/aiohttp__asyncio__examples/concurrent_requests.py
index cfc4fe6ea..647a97859 100644
--- a/aiohttp__asyncio__examples/concurrent_requests.py
+++ b/aiohttp__asyncio__examples/concurrent_requests.py
@@ -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,7 +21,7 @@ async def fetch_page(url: str, idx: int):
print(rs.content)
-async def main():
+async def main() -> None:
url = "https://python.org"
urls = [url] * 100
diff --git a/aiohttp__asyncio__examples/hello_world.py b/aiohttp__asyncio__examples/hello_world.py
index 40e951117..0a395a322 100644
--- a/aiohttp__asyncio__examples/hello_world.py
+++ b/aiohttp__asyncio__examples/hello_world.py
@@ -17,7 +17,7 @@ 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")
print(html)
diff --git a/aiohttp__asyncio__examples/hello_world__parse__lxml_etree.py b/aiohttp__asyncio__examples/hello_world__parse__lxml_etree.py
index a432ebd34..508eefbc5 100644
--- a/aiohttp__asyncio__examples/hello_world__parse__lxml_etree.py
+++ b/aiohttp__asyncio__examples/hello_world__parse__lxml_etree.py
@@ -20,7 +20,7 @@ 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")
root = etree.fromstring(xml_str)
diff --git a/aiohttp__asyncio__examples/ignore_aiohttp_ssl_error.py b/aiohttp__asyncio__examples/ignore_aiohttp_ssl_error.py
index f30eb2205..f6beffd36 100644
--- a/aiohttp__asyncio__examples/ignore_aiohttp_ssl_error.py
+++ b/aiohttp__asyncio__examples/ignore_aiohttp_ssl_error.py
@@ -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,7 +41,7 @@ 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",
diff --git a/alpha2_to_country/main.py b/alpha2_to_country/main.py
index 8cba527aa..c8ca59b42 100644
--- a/alpha2_to_country/main.py
+++ b/alpha2_to_country/main.py
@@ -18,7 +18,7 @@
ALPHA2_TO_COUNTRY = None
-def init():
+def init() -> None:
global ALPHA2_TO_COUNTRY
if FILE_NAME_COUNTRY.exists():
diff --git a/api_clans.py b/api_clans.py
index 66108962c..54ee5fbb6 100644
--- a/api_clans.py
+++ b/api_clans.py
@@ -14,7 +14,7 @@ class Api:
# TODO: this
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
diff --git a/ascii_table__simple_pretty__format.py b/ascii_table__simple_pretty__format.py
index 5bf24e712..afe0340f7 100644
--- a/ascii_table__simple_pretty__format.py
+++ b/ascii_table__simple_pretty__format.py
@@ -32,7 +32,7 @@ def pretty_table(data, cell_sep=" | ", header_separator=True, align=">") -> str:
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))
diff --git a/ascii_table__simple_pretty__ljust.py b/ascii_table__simple_pretty__ljust.py
index dda60264d..b762934b4 100644
--- a/ascii_table__simple_pretty__ljust.py
+++ b/ascii_table__simple_pretty__ljust.py
@@ -31,7 +31,7 @@ def pretty_table(data, cell_sep=" | ", header_separator=True) -> str:
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))
diff --git a/ascii_table__simple_pretty__rjust.py b/ascii_table__simple_pretty__rjust.py
index 2b3ce49a0..8abdf420d 100644
--- a/ascii_table__simple_pretty__rjust.py
+++ b/ascii_table__simple_pretty__rjust.py
@@ -31,7 +31,7 @@ def pretty_table(data, cell_sep=" | ", header_separator=True) -> str:
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))
diff --git a/autoclick_RDP_task_icon/main.py b/autoclick_RDP_task_icon/main.py
index 02cee0956..d8caa22fe 100644
--- a/autoclick_RDP_task_icon/main.py
+++ b/autoclick_RDP_task_icon/main.py
@@ -42,7 +42,7 @@ def get_pos_rdp_task_icon() -> tuple[int, int] | None:
pass
-def run():
+def run() -> None:
logging.info("")
logging.info("Run")
diff --git a/bin2str/gui.py b/bin2str/gui.py
index f96c4e1a1..808c22891 100644
--- a/bin2str/gui.py
+++ b/bin2str/gui.py
@@ -24,7 +24,7 @@
from bin2str import bin2str, str2bin
-def log_uncaught_exceptions(ex_cls, ex, tb):
+def log_uncaught_exceptions(ex_cls, ex, tb) -> None:
text = f"{ex_cls.__name__}: {ex}:\n"
text += "".join(traceback.format_tb(tb))
@@ -37,7 +37,7 @@ def log_uncaught_exceptions(ex_cls, ex, tb):
class Widget(QWidget):
- def __init__(self):
+ def __init__(self) -> None:
super().__init__()
self.setWindowTitle("bin2str")
@@ -65,13 +65,13 @@ def __init__(self):
self.setLayout(layout)
- def _bin2str(self):
+ def _bin2str(self) -> None:
text = self.plain_text_bin.toPlainText()
text = bin2str(text)
self.plain_text_str.setPlainText(text)
- def _str2bin(self):
+ def _str2bin(self) -> None:
text = self.plain_text_str.toPlainText()
text = str2bin(text)
diff --git a/build_exe/pyinstaller_example/compress_exe/main.py b/build_exe/pyinstaller_example/compress_exe/main.py
index 88ac109f1..f1e929469 100644
--- a/build_exe/pyinstaller_example/compress_exe/main.py
+++ b/build_exe/pyinstaller_example/compress_exe/main.py
@@ -17,7 +17,7 @@
button = QPushButton("Add")
text_edit = QTextEdit()
- def add_to_text():
+ def add_to_text() -> None:
text = line_edit.text()
text_edit.append(text)
diff --git a/build_exe/pyinstaller_example/gui/qt_pyqt5/main.py b/build_exe/pyinstaller_example/gui/qt_pyqt5/main.py
index 97c3ea417..f504fb661 100644
--- a/build_exe/pyinstaller_example/gui/qt_pyqt5/main.py
+++ b/build_exe/pyinstaller_example/gui/qt_pyqt5/main.py
@@ -22,7 +22,7 @@
text_edit = QTextEdit()
-def add_to_text():
+def add_to_text() -> None:
text = line_edit.text()
text_edit.append(text)
diff --git a/build_exe/pyinstaller_example/gui/qt_pyside/main.py b/build_exe/pyinstaller_example/gui/qt_pyside/main.py
index 88ac109f1..f1e929469 100644
--- a/build_exe/pyinstaller_example/gui/qt_pyside/main.py
+++ b/build_exe/pyinstaller_example/gui/qt_pyside/main.py
@@ -17,7 +17,7 @@
button = QPushButton("Add")
text_edit = QTextEdit()
- def add_to_text():
+ def add_to_text() -> None:
text = line_edit.text()
text_edit.append(text)
diff --git a/build_fake_image__build_exe/generator.py b/build_fake_image__build_exe/generator.py
index f16052dc1..8b0eabda3 100644
--- a/build_fake_image__build_exe/generator.py
+++ b/build_fake_image__build_exe/generator.py
@@ -9,7 +9,7 @@
# 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):
+def convert_image_to_ico(file_name, file_name_ico, icon_sizes=None) -> None:
img = Image.open(file_name)
if icon_sizes:
@@ -22,7 +22,7 @@ def convert_image_to_ico(file_name, file_name_ico, icon_sizes=None):
# SOURCE: https://github.com/gil9red/SimplePyScripts/blob/aec64c1749d4f6f3176e3222c7e7c554f40c693f/generator_py_with_inner_image_with_open/main.py
-def generate(file_name, inject_code: str):
+def generate(file_name, inject_code: str) -> None:
with open(file_name, "rb") as f:
img_bytes = f.read()
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/codewars/parse_molecule__Molecule to atoms.py b/codewars/parse_molecule__Molecule to atoms.py
index 18f928ef8..3484bc9eb 100644
--- a/codewars/parse_molecule__Molecule to atoms.py
+++ b/codewars/parse_molecule__Molecule to atoms.py
@@ -97,7 +97,7 @@ def parse_molecule(formula):
if __name__ == "__main__":
- def equals_atomically(obj1, obj2):
+ def equals_atomically(obj1, obj2) -> bool:
if len(obj1) != len(obj2):
return False
for k in obj1:
diff --git a/codingame/medium/Bender - Episode 1.py b/codingame/medium/Bender - Episode 1.py
index 9b293e7e7..bd97edcc7 100644
--- a/codingame/medium/Bender - Episode 1.py
+++ b/codingame/medium/Bender - Episode 1.py
@@ -45,12 +45,12 @@
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()
# Соберем все объекты на карте в словарь, исключаются пустые места и стенки
@@ -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,7 +99,7 @@ def _get_pos_j(self):
pos_j = property(_get_pos_j, _set_pos_j)
- def _set_pos(self, value):
+ def _set_pos(self, value) -> None:
self.objects_map["@"] = value
def _get_pos(self):
@@ -120,13 +120,13 @@ def city_map(self):
return map
- def print_city_map(self):
+ def print_city_map(self) -> None:
log()
for row in self.city_map():
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):
diff --git a/collector_bash_im/collector_bash_im.py b/collector_bash_im/collector_bash_im.py
index 1998bca75..eddb6ae3c 100644
--- a/collector_bash_im/collector_bash_im.py
+++ b/collector_bash_im/collector_bash_im.py
@@ -43,13 +43,13 @@ def get_logger(name, file="log.txt", encoding="utf8"):
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):
+ def __repr__(self) -> str:
return (
"".format(len(self.text), **self.__dict__)
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 d0c526330..b03abd482 100644
--- a/concurrency_in_python__threading_processing/about_1__single_thread.py
+++ b/concurrency_in_python__threading_processing/about_1__single_thread.py
@@ -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
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 b2bdc2974..4e68c7285 100644
--- a/concurrency_in_python__threading_processing/about_2__multithreading.py
+++ b/concurrency_in_python__threading_processing/about_2__multithreading.py
@@ -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):
diff --git a/concurrency_in_python__threading_processing/about_3__multiprocessing.py b/concurrency_in_python__threading_processing/about_3__multiprocessing.py
index d2121c1b0..6e71b2722 100644
--- a/concurrency_in_python__threading_processing/about_3__multiprocessing.py
+++ b/concurrency_in_python__threading_processing/about_3__multiprocessing.py
@@ -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):
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 1b7dbfd35..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
@@ -14,11 +14,11 @@
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))
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 6f440e73d..7695daacc 100644
--- a/concurrency_in_python__threading_processing/multiprocessing__examples/Manager__example.py
+++ b/concurrency_in_python__threading_processing/multiprocessing__examples/Manager__example.py
@@ -7,7 +7,7 @@
from multiprocessing import Process, Manager
-def f(users_data):
+def f(users_data) -> None:
users_data["users"][0]["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 bdc947530..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
@@ -7,7 +7,7 @@
import multiprocessing
-def run():
+def run() -> None:
import time
i = 1
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 376ccd528..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
@@ -7,7 +7,7 @@
from multiprocessing import Process, Pipe
-def f(con, name):
+def f(con, name) -> None:
con.send("Hello, " + name)
con.close()
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 ba4474230..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
@@ -7,7 +7,7 @@
from multiprocessing import Process, Queue
-def f(q, name):
+def f(q, name) -> None:
q.put("Hello, " + name)
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 56b47918a..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
@@ -7,7 +7,7 @@
from multiprocessing import Process
-def f(name):
+def f(name) -> None:
print("Hello,", name)
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 61d1c752e..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
@@ -8,7 +8,7 @@
import time
-def run():
+def run() -> None:
print(current_process())
for i in "Hello World!":
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 68d040a7d..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
@@ -8,7 +8,7 @@
import time
-def run():
+def run() -> None:
print(current_process())
for i in "Hello World!":
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 1da85f550..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
@@ -9,12 +9,12 @@
from multiprocessing__Tkinter__in_other_process import go as go_tk
-def create_qt():
+def create_qt() -> None:
p = Process(target=go_qt, args=("Qt",))
p.start()
-def create_tk():
+def create_tk() -> None:
p = Process(target=go_tk, args=("Tk",))
p.start()
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 a8b24f1b8..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
@@ -7,7 +7,7 @@
from PyQt5.Qt import QApplication, Qt, QLabel
-def go(name):
+def go(name) -> None:
app = QApplication([])
mw = QLabel()
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 cb49bec54..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
@@ -7,7 +7,7 @@
import tkinter as tk
-def go(name):
+def go(name) -> None:
app = tk.Tk()
app.minsize(150, 50)
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 081faf97b..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
@@ -12,19 +12,19 @@
from flask import Flask
-def go(port: int):
+def go(port: int) -> None:
app = Flask(__name__)
logging.basicConfig(level=logging.DEBUG)
@app.route("/")
- def index():
+ def index() -> str:
return f"Hello World! (port={port})"
app.run(port=port)
-def go_parser(urls):
+def go_parser(urls) -> None:
while True:
for url in urls:
try:
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 82077c0b0..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
@@ -8,14 +8,14 @@
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())
-def f(name):
+def f(name) -> None:
info("function f")
print("Hello,", name)
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 2354f06da..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
@@ -11,7 +11,7 @@
import time
-def f(lock, i):
+def f(lock, i) -> None:
with lock:
print("hello world", i)
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 48ddc315b..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
@@ -21,7 +21,7 @@
]
-def is_prime(n):
+def is_prime(n) -> bool:
if n < 2:
return False
if n == 2:
@@ -36,7 +36,7 @@ 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:
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 272919efa..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
@@ -15,7 +15,7 @@
MAX_WORKERS = 5
-def run(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 f8473ff8f..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
@@ -15,7 +15,7 @@
MAX_WORKERS = 5
-def run(name):
+def run(name) -> str:
time.sleep(randint(1, 4))
return f"name: {name}"
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 1ce431733..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
@@ -41,7 +41,7 @@ class AtomicCounter:
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()
@@ -66,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 b8e1c9419..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
@@ -8,7 +8,7 @@
import time
-def run():
+def run() -> None:
i = 1
# Бесконечный цикл
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 d9df962b1..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
@@ -37,7 +37,7 @@
# ------- VERSUS ------- #
-def go(count=1):
+def go(count=1) -> None:
t = time.clock()
pool = ThreadPool(count)
results = pool.map(urlopen, urls)
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 488e0fa0c..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
@@ -12,7 +12,7 @@
@app.route("/")
-def index():
+def index() -> str:
return f"Current thread: {threading.current_thread()}"
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 b8ca68bfb..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
@@ -12,7 +12,7 @@
@app.route("/")
-def index():
+def index() -> str:
return f"Current thread: {threading.current_thread()}"
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 43eb3f4fd..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
@@ -8,7 +8,7 @@
import time
-def run():
+def run() -> None:
print(threading.current_thread())
for i in "Hello World!":
print(i)
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 1d58ddeec..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
@@ -8,7 +8,7 @@
import time
-def run():
+def run() -> None:
print(threading.current_thread())
for i in "Hello World!":
print(i)
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 b76d3b282..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
@@ -8,7 +8,7 @@
import time
-def run(name="main", sleep_seconds=None):
+def run(name="main", sleep_seconds=None) -> None:
if sleep_seconds:
time.sleep(sleep_seconds)
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 52a71aac2..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
@@ -13,14 +13,14 @@
lock = threading.Lock()
-def inc(*args):
+def inc(*args) -> None:
global number
DATA["number"] += 1
number += 1
-def inc_lock(*args):
+def inc_lock(*args) -> None:
global number
with lock:
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 86014f261..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
@@ -8,13 +8,13 @@
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 0eae78b3b..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
@@ -10,7 +10,7 @@
from flask import Flask
-def run(port: int = 80):
+def run(port: int = 80) -> None:
app = Flask(__name__)
logging.basicConfig(level=logging.DEBUG)
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 f1ec750a9..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
@@ -9,10 +9,10 @@
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)
# Написать пользователю
@@ -26,7 +26,7 @@ def post_msg(self):
Thread(target=user_2.post_msg).start()
-def foo(name):
+def foo(name) -> None:
time.sleep(5)
# Написать пользователю
diff --git a/console_change_data_in_line.py b/console_change_data_in_line.py
index 8b6ff06cb..2e79600ef 100644
--- a/console_change_data_in_line.py
+++ b/console_change_data_in_line.py
@@ -8,7 +8,7 @@
import time
-def f1(n):
+def f1(n) -> None:
last_num_chars = 0
write, flush = sys.stdout.write, sys.stdout.flush
@@ -23,7 +23,7 @@ def f1(n):
print()
-def f2(n):
+def f2(n) -> None:
last_num_chars = 0
write, flush = sys.stdout.write, sys.stdout.flush
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 afdc6c4d4..db0959749 100644
--- a/contextlib__redirect__stdout_stderr/redirect__to_file_like_object.py
+++ b/contextlib__redirect__stdout_stderr/redirect__to_file_like_object.py
@@ -11,7 +11,7 @@
with StringIO() as f, redirect_stdout(f):
print("Hello ", end="")
- def foo():
+ def foo() -> None:
print("World")
foo()
diff --git a/convert_image_to_ico/main.py b/convert_image_to_ico/main.py
index 1f4f6442c..6fc355776 100644
--- a/convert_image_to_ico/main.py
+++ b/convert_image_to_ico/main.py
@@ -7,7 +7,7 @@
from PIL import Image
-def convert_image_to_ico(file_name, file_name_ico, icon_sizes=None):
+def convert_image_to_ico(file_name, file_name_ico, icon_sizes=None) -> None:
img = Image.open(file_name)
if icon_sizes:
diff --git a/copy2clipboard.py b/copy2clipboard.py
index 707e468c3..95f5988f2 100644
--- a/copy2clipboard.py
+++ b/copy2clipboard.py
@@ -15,7 +15,7 @@
from PySide.QtGui import QApplication
-def to(text: str):
+def to(text: str) -> None:
app = QApplication([])
app.clipboard().setText(text)
app = None
diff --git a/copy2clipboard__via_pyperclip.py b/copy2clipboard__via_pyperclip.py
index 389413c51..7db33e437 100644
--- a/copy2clipboard__via_pyperclip.py
+++ b/copy2clipboard__via_pyperclip.py
@@ -7,7 +7,7 @@
import pyperclip
-def to(text: str):
+def to(text: str) -> None:
pyperclip.copy(text)
pyperclip.paste()
diff --git a/copy_example.py b/copy_example.py
index c5ea1673e..b511a48da 100644
--- a/copy_example.py
+++ b/copy_example.py
@@ -6,22 +6,40 @@
"""RU: Пример использования модуля copy."""
-# TODO: https://docs.python.org/3.4/library/copy.html
-# TODO: больше примеров
-
import copy
-if __name__ == "__main__":
- a = [2, 3, [3.5, 3.6, [3.61, 3.62]], 4, 5]
- print(a, type(a), hex(id(a)), sep=", ")
+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=", ")
+_print_complex_data(copy.deepcopy(complex_data))
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/create_vars__use_globals.py b/create_vars__use_globals.py
index d994c9077..78c52f113 100644
--- a/create_vars__use_globals.py
+++ b/create_vars__use_globals.py
@@ -18,7 +18,7 @@
number = 0
-def counter():
+def counter() -> None:
# If not exists global
# if 'number' not in globals():
# globals()['number'] = 0
diff --git a/cron_converter__examples/test_from_jenkins.py b/cron_converter__examples/test_from_jenkins.py
index 077ab2b8b..52ce933fb 100644
--- a/cron_converter__examples/test_from_jenkins.py
+++ b/cron_converter__examples/test_from_jenkins.py
@@ -13,7 +13,7 @@
class Test(TestCase):
- def test_do_convert_every_15_minutes(self):
+ def test_do_convert_every_15_minutes(self) -> None:
# Every fifteen minutes
cron = "H/15 * * * *"
@@ -33,7 +33,7 @@ def test_do_convert_every_15_minutes(self):
self.assertEqual(actual, expected)
- def test_do_convert_every_1_hours(self):
+ def test_do_convert_every_1_hours(self) -> None:
cron = "H * * * *"
cron = do_convert(cron)
@@ -52,7 +52,7 @@ def test_do_convert_every_1_hours(self):
self.assertEqual(actual, expected)
- def test_do_convert_every_8_hours(self):
+ def test_do_convert_every_8_hours(self) -> None:
cron = "H */8 * * *"
cron = do_convert(cron)
@@ -79,7 +79,7 @@ def test_do_convert_every_8_hours(self):
self.assertEqual(actual, expected)
- def test_do_convert_every_24_hours(self):
+ def test_do_convert_every_24_hours(self) -> None:
cron = "H 0 * * *"
cron = do_convert(cron)
@@ -98,7 +98,7 @@ def test_do_convert_every_24_hours(self):
self.assertEqual(actual, expected)
- def test_do_convert_hourly(self):
+ def test_do_convert_hourly(self) -> None:
cron = "@hourly"
cron = do_convert(cron)
@@ -117,7 +117,7 @@ def test_do_convert_hourly(self):
self.assertEqual(actual, expected)
- def test_do_convert_daily(self):
+ def test_do_convert_daily(self) -> None:
cron = "@daily"
cron = do_convert(cron)
@@ -136,7 +136,7 @@ def test_do_convert_daily(self):
self.assertEqual(actual, expected)
- def test_do_convert_midnight(self):
+ def test_do_convert_midnight(self) -> None:
cron = "@midnight"
cron = do_convert(cron)
@@ -165,7 +165,7 @@ def test_do_convert_midnight(self):
self.assertEqual(actual, expected)
- def test_do_convert_weekly(self):
+ def test_do_convert_weekly(self) -> None:
cron = "@weekly"
cron = do_convert(cron)
@@ -194,7 +194,7 @@ def test_do_convert_weekly(self):
self.assertEqual(actual, expected)
- def test_do_convert_monthly(self):
+ def test_do_convert_monthly(self) -> None:
cron = "@monthly"
cron = do_convert(cron)
@@ -223,7 +223,7 @@ def test_do_convert_monthly(self):
self.assertEqual(actual, expected)
- def test_do_convert_yearly(self):
+ def test_do_convert_yearly(self) -> None:
cron = "@yearly"
cron = do_convert(cron)
@@ -242,7 +242,7 @@ def test_do_convert_yearly(self):
self.assertEqual(actual, expected)
- def test_do_convert_annually(self):
+ def test_do_convert_annually(self) -> None:
cron = "@annually"
cron = do_convert(cron)
@@ -261,7 +261,7 @@ def test_do_convert_annually(self):
self.assertEqual(actual, expected)
- def test_do_convert_complex_1(self):
+ def test_do_convert_complex_1(self) -> None:
# Every ten minutes in the first half of every hour
cron = "H(0-29)/10 * * * *"
@@ -297,7 +297,7 @@ def test_do_convert_complex_1(self):
self.assertEqual(actual, expected)
- def test_do_convert_complex_2(self):
+ 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"
@@ -331,7 +331,7 @@ def test_do_convert_complex_2(self):
self.assertEqual(actual, expected)
- def test_do_convert_complex_3(self):
+ 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"
@@ -365,7 +365,7 @@ def test_do_convert_complex_3(self):
self.assertEqual(actual, expected)
- def test_do_convert_complex_4(self):
+ 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 *"
diff --git a/css_to_xpath__gui/main.py b/css_to_xpath__gui/main.py
index ab875202e..7a78a57ce 100644
--- a/css_to_xpath__gui/main.py
+++ b/css_to_xpath__gui/main.py
@@ -16,7 +16,7 @@
css_to_xpath = HTMLTranslator(xhtml=True).css_to_xpath
-def log_uncaught_exceptions(ex_cls, ex, tb):
+def log_uncaught_exceptions(ex_cls, ex, tb) -> None:
text = f"{ex_cls.__name__}: {ex}:\n"
text += "".join(traceback.format_tb(tb))
@@ -29,7 +29,7 @@ def log_uncaught_exceptions(ex_cls, ex, tb):
class MainWindow(Qt.QWidget):
- def __init__(self):
+ def __init__(self) -> None:
super().__init__()
self.setWindowTitle("css_to_xpath__gui")
@@ -71,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()
@@ -100,7 +100,7 @@ def on_process(self):
self.label_error.setText("Error: " + self.last_error_message)
- def show_detail_error_message(self):
+ def show_detail_error_message(self) -> None:
message = self.last_error_message + "\n\n" + self.last_detail_error_message
mb = Qt.QErrorMessage()
diff --git a/curtain for sleeping.py b/curtain for sleeping.py
index 110281b9e..e9744f935 100644
--- a/curtain for sleeping.py
+++ b/curtain for sleeping.py
@@ -33,7 +33,7 @@
class CurtainWidget(QWidget):
- def __init__(self):
+ def __init__(self) -> None:
super().__init__()
self.setWindowTitle("Curtain for sleeping")
@@ -60,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()
@@ -73,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()
@@ -83,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)
diff --git a/custom_with__context_manager/sqlite_execute.py b/custom_with__context_manager/sqlite_execute.py
index f07f5a261..c27bb8f52 100644
--- a/custom_with__context_manager/sqlite_execute.py
+++ b/custom_with__context_manager/sqlite_execute.py
@@ -6,8 +6,11 @@
import sqlite3
+from types import TracebackType
+from typing import Optional, Type
-def old_old_variant():
+
+def old_old_variant() -> None:
connect = sqlite3.connect(":memory:")
try:
@@ -33,7 +36,7 @@ def old_old_variant():
connect.close()
-def old_variant():
+def old_variant() -> None:
with sqlite3.connect(":memory:") as connect:
print(connect.execute("SELECT sqlite_version();").fetchone())
@@ -55,17 +58,17 @@ def old_variant():
class SQLite3Connect(object):
- def __init__(self, database):
+ 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())
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 42971ea32..000000000
--- a/custom_with__context_manager/time_this_using_with.py
+++ /dev/null
@@ -1,39 +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(
- f"[{self.title}] total time: {time.clock() - self.start_time:.3f} sec"
- )
-
-
-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/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 2bcda88c4..42eef045d 100644
--- a/datetime_example/datetime_example.py
+++ b/datetime_example/datetime_example.py
@@ -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_strptime__and__setlocale.py b/datetime_strptime__and__setlocale.py
index f34d68f7f..1e69345ba 100644
--- a/datetime_strptime__and__setlocale.py
+++ b/datetime_strptime__and__setlocale.py
@@ -8,7 +8,7 @@
from datetime import datetime
-def check():
+def check() -> None:
try:
print(
datetime.strptime(
diff --git a/delete_sublist.py b/delete_sublist.py
index c2d3a32af..a8bdc3e50 100644
--- a/delete_sublist.py
+++ b/delete_sublist.py
@@ -25,7 +25,7 @@ def find_sublist(l, m):
pass
-def delete_sublist(l, m):
+def delete_sublist(l, m) -> None:
for i, j in find_sublist(l, m):
del l[i:j]
diff --git a/design_patterns__examples/Abstract Factory/example.py b/design_patterns__examples/Abstract Factory/example.py
index 23ab0a200..5566162ff 100644
--- a/design_patterns__examples/Abstract Factory/example.py
+++ b/design_patterns__examples/Abstract Factory/example.py
@@ -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")
diff --git a/design_patterns__examples/Adapter/example__using_composition.py b/design_patterns__examples/Adapter/example__using_composition.py
index d876d7176..008874430 100644
--- a/design_patterns__examples/Adapter/example__using_composition.py
+++ b/design_patterns__examples/Adapter/example__using_composition.py
@@ -18,17 +18,17 @@ def get_picture(self):
class GameConsole:
- def create_game_picture(self):
+ def create_game_picture(self) -> str:
return "picture from console"
class Antenna:
- def create_wave_picture(self):
+ 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,7 +44,7 @@ def get_picture(self):
class TV:
- def __init__(self, source: SourceAdapter):
+ def __init__(self, source: SourceAdapter) -> None:
self.source = source
def show_picture(self):
diff --git a/design_patterns__examples/Adapter/example__using_inheritance.py b/design_patterns__examples/Adapter/example__using_inheritance.py
index 6caaa7d33..6fff68655 100644
--- a/design_patterns__examples/Adapter/example__using_inheritance.py
+++ b/design_patterns__examples/Adapter/example__using_inheritance.py
@@ -18,12 +18,12 @@ def get_picture(self):
class GameConsole:
- def create_game_picture(self):
+ def create_game_picture(self) -> str:
return "picture from console"
class Antenna:
- def create_wave_picture(self):
+ def create_wave_picture(self) -> str:
return "picture from wave"
@@ -38,7 +38,7 @@ def get_picture(self):
class TV:
- def __init__(self, source: SourceAdapter):
+ def __init__(self, source: SourceAdapter) -> None:
self.source = source
def show_picture(self):
diff --git a/design_patterns__examples/Bridge/example_1/devices/radio.py b/design_patterns__examples/Bridge/example_1/devices/radio.py
index 0dfa9888a..d639c3eba 100644
--- a/design_patterns__examples/Bridge/example_1/devices/radio.py
+++ b/design_patterns__examples/Bridge/example_1/devices/radio.py
@@ -8,7 +8,7 @@
class Radio(Device):
- def __init__(self):
+ def __init__(self) -> None:
self._on = False
self._volume = 30
self._channel = 1
@@ -16,16 +16,16 @@ def __init__(self):
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:
@@ -36,10 +36,10 @@ def set_volume(self, volume: int):
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 fc884e464..5adfc0437 100644
--- a/design_patterns__examples/Bridge/example_1/devices/tv.py
+++ b/design_patterns__examples/Bridge/example_1/devices/tv.py
@@ -8,7 +8,7 @@
class Tv(Device):
- def __init__(self):
+ def __init__(self) -> None:
self._on = False
self._volume = 30
self._channel = 1
@@ -16,16 +16,16 @@ def __init__(self):
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:
@@ -36,10 +36,10 @@ def set_volume(self, volume: int):
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 53354a257..745a2a1f5 100644
--- a/design_patterns__examples/Bridge/example_1/main.py
+++ b/design_patterns__examples/Bridge/example_1/main.py
@@ -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()
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 ef84acb61..c0367b280 100644
--- a/design_patterns__examples/Bridge/example_1/remotes/advanced_remote.py
+++ b/design_patterns__examples/Bridge/example_1/remotes/advanced_remote.py
@@ -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 8c6bf5385..80b681269 100644
--- a/design_patterns__examples/Bridge/example_1/remotes/basic_remote.py
+++ b/design_patterns__examples/Bridge/example_1/remotes/basic_remote.py
@@ -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_2.py b/design_patterns__examples/Bridge/example_2.py
index 223c133e9..3e2248dd7 100644
--- a/design_patterns__examples/Bridge/example_2.py
+++ b/design_patterns__examples/Bridge/example_2.py
@@ -20,7 +20,7 @@ def draw_circle(self, x: int, y: int, radius: int):
class SmallCircleDrawer(Drawer):
RADIUS_MULTIPLIER = 0.25
- def draw_circle(self, x: int, y: int, radius: int):
+ def draw_circle(self, x: int, y: int, radius: int) -> None:
print(
f"Small circle center = {x},{y} radius = {radius * self.RADIUS_MULTIPLIER}"
)
@@ -29,37 +29,37 @@ def draw_circle(self, x: int, y: int, radius: int):
class LargeCircleDrawer(Drawer):
RADIUS_MULTIPLIER = 10
- def draw_circle(self, x: int, y: int, radius: int):
+ 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:
@@ -71,13 +71,13 @@ 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
diff --git a/design_patterns__examples/Builder/example.py b/design_patterns__examples/Builder/example.py
index 549cd2c36..ea97765c8 100644
--- a/design_patterns__examples/Builder/example.py
+++ b/design_patterns__examples/Builder/example.py
@@ -13,84 +13,84 @@
# "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()
diff --git a/design_patterns__examples/Builder/example2.py b/design_patterns__examples/Builder/example2.py
index f7b067050..9acb0c03d 100644
--- a/design_patterns__examples/Builder/example2.py
+++ b/design_patterns__examples/Builder/example2.py
@@ -4,27 +4,30 @@
__author__ = "ipetrash"
+from typing import Any, Self
+
+
class Foo:
- def __init__(self):
+ def __init__(self) -> None:
self.items = []
self.key_by_value = dict()
- def add_item(self, value):
+ def add_item(self, value) -> Self:
self.items.append(value)
return self
- def add_items(self, values):
+ def add_items(self, values) -> Self:
self.items += values
return self
- def set_value(self, key, value):
+ def set_value(self, key, value) -> Self:
self.key_by_value[key] = value
return self
- def get_value(self, key):
+ def get_value(self, key) -> Any:
return self.key_by_value[key]
- def __repr__(self):
+ def __repr__(self) -> str:
return f"Foo"
diff --git a/design_patterns__examples/Builder/example_wok.py b/design_patterns__examples/Builder/example_wok.py
index 4dae818e4..45e91d4f3 100644
--- a/design_patterns__examples/Builder/example_wok.py
+++ b/design_patterns__examples/Builder/example_wok.py
@@ -149,7 +149,7 @@ 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:
@@ -187,7 +187,7 @@ 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":
diff --git a/design_patterns__examples/Chain of responsibility/example_1.py b/design_patterns__examples/Chain of responsibility/example_1.py
index 45cfa0b43..388756434 100644
--- a/design_patterns__examples/Chain of responsibility/example_1.py
+++ b/design_patterns__examples/Chain of responsibility/example_1.py
@@ -25,10 +25,10 @@ def set_next(self, handler: "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,10 +51,10 @@ 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}"!'
@@ -64,7 +64,7 @@ def handle(self, obj):
if __name__ == "__main__":
- def client_code(handler: Handler):
+ def client_code(handler: Handler) -> None:
for obj in [None, "123", "456", "111", 456]:
print(f"Object {repr(obj)} is ", end="")
diff --git a/design_patterns__examples/Chain of responsibility/example_2.py b/design_patterns__examples/Chain of responsibility/example_2.py
index 63e6002bc..b1b310fe3 100644
--- a/design_patterns__examples/Chain of responsibility/example_2.py
+++ b/design_patterns__examples/Chain of responsibility/example_2.py
@@ -10,39 +10,39 @@
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)
diff --git a/design_patterns__examples/Chain of responsibility/example_3.py b/design_patterns__examples/Chain of responsibility/example_3.py
index 1ef248c00..7b6461f84 100644
--- a/design_patterns__examples/Chain of responsibility/example_3.py
+++ b/design_patterns__examples/Chain of responsibility/example_3.py
@@ -38,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
@@ -62,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.
@@ -91,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.
@@ -109,7 +109,7 @@ class EmailLogger(Logger):
msg (str): Message string.
"""
- def write_message(self, msg: str):
+ def write_message(self, msg: str) -> None:
print("Sending via email:", msg)
@@ -121,11 +121,11 @@ class FileLogger(Logger):
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 c02b511ea..4e1f86e83 100644
--- a/design_patterns__examples/Chain of responsibility/example_4.py
+++ b/design_patterns__examples/Chain of responsibility/example_4.py
@@ -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,7 +24,7 @@ def __init__(self, complexity: int, description: str):
# Абстрактный полицейский, который может заниматься расследованием преступлений
class Policeman(ABC):
- def __init__(self, deduction: int):
+ def __init__(self, deduction: int) -> None:
# Дедукция (умение распутывать сложные дела) у данного полицейского
self.deduction = deduction
@@ -43,7 +43,7 @@ def set_next(self, policeman: "Policeman") -> "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)
@@ -55,19 +55,19 @@ def investigate(self, criminal_action: CriminalAction):
class MartinRiggs(Policeman):
- def _investigate_сoncrete(self, description: str):
+ def _investigate_сoncrete(self, description: str) -> None:
print('Расследование по делу "' + description + '" ведет сержант Мартин Риггс')
class JohnMcClane(Policeman):
- def _investigate_сoncrete(self, description: str):
+ def _investigate_сoncrete(self, description: str) -> None:
print(
'Расследование по делу "' + description + '" ведет детектив Джон Макклейн'
)
class VincentHanna(Policeman):
- def _investigate_сoncrete(self, description: str):
+ def _investigate_сoncrete(self, description: str) -> None:
print(
'Расследование по делу "' + description + '" ведет лейтенант Винсент Ханна'
)
diff --git a/design_patterns__examples/Chain of responsibility/example_6.py b/design_patterns__examples/Chain of responsibility/example_6.py
index a1544e3d4..0b5be3e07 100644
--- a/design_patterns__examples/Chain of responsibility/example_6.py
+++ b/design_patterns__examples/Chain of responsibility/example_6.py
@@ -20,7 +20,7 @@ def get_timestamp() -> int:
# Базовый класс цепочки.
class Middleware(ABC):
- def __init__(self):
+ def __init__(self) -> None:
self._next: "Middleware" = None
# Помогает строить цепь из объектов-проверок.
@@ -44,7 +44,7 @@ def _check_next(self, email: str, password: str) -> bool:
# Конкретный элемент цепи обрабатывает запрос по-своему.
class ThrottlingMiddleware(Middleware):
- def __init__(self, request_per_minute: int):
+ def __init__(self, request_per_minute: int) -> None:
super().__init__()
self._request: int = 0
@@ -72,7 +72,7 @@ def check(self, email: str, password: str) -> bool:
# Конкретный элемент цепи обрабатывает запрос по-своему.
class UserExistsMiddleware(Middleware):
- def __init__(self, server: "Server"):
+ def __init__(self, server: "Server") -> None:
super().__init__()
self._server: Server = server
@@ -102,13 +102,13 @@ def check(self, email: str, password: str) -> bool:
# Класс сервера.
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 и пароль от клиента и запускает проверку
@@ -123,7 +123,7 @@ def log_in(self, email: str, password: str) -> bool:
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:
diff --git a/design_patterns__examples/Command/example.py b/design_patterns__examples/Command/example.py
index 866378b56..e21d52dd6 100644
--- a/design_patterns__examples/Command/example.py
+++ b/design_patterns__examples/Command/example.py
@@ -19,34 +19,34 @@ def execute(self):
class Car:
- def start_engine(self):
+ def start_engine(self) -> None:
print("Запустить двигатель")
- def stop_engine(self):
+ def stop_engine(self) -> None:
print("Остановить двигатель")
class StartCar(Command):
- def __init__(self, car: Car):
+ def __init__(self, car: Car) -> None:
self.car: Car = car
- def execute(self):
+ def execute(self) -> None:
self.car.start_engine()
class StopCar(Command):
- def __init__(self, car: Car):
+ def __init__(self, car: Car) -> None:
self.car: Car = car
- def execute(self):
+ def execute(self) -> None:
self.car.stop_engine()
class CarInvoker:
- def __init__(self, command: Command):
+ def __init__(self, command: Command) -> None:
self.command: Command = command
- def execute(self):
+ def execute(self) -> None:
self.command.execute()
diff --git a/design_patterns__examples/Composite/example.py b/design_patterns__examples/Composite/example.py
index 53e69a2e5..0c3b6fc2c 100644
--- a/design_patterns__examples/Composite/example.py
+++ b/design_patterns__examples/Composite/example.py
@@ -19,65 +19,65 @@ 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):
+ 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):
+ 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):
+ 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):
+ def draw(self, *args, **kwargs) -> None:
print(f"Line: x1={self.x1}, y1={self.y1}, x2={self.x2}, y2={self.y2}")
diff --git a/design_patterns__examples/Composite/example__pyqt_draw.py b/design_patterns__examples/Composite/example__pyqt_draw.py
index 35022ecef..a9e97de75 100644
--- a/design_patterns__examples/Composite/example__pyqt_draw.py
+++ b/design_patterns__examples/Composite/example__pyqt_draw.py
@@ -17,7 +17,7 @@
from PyQt5.Qt import *
-def log_uncaught_exceptions(ex_cls, ex, tb):
+def log_uncaught_exceptions(ex_cls, ex, tb) -> None:
text = f"{ex_cls.__name__}: {ex}:\n"
text += "".join(traceback.format_tb(tb))
@@ -31,82 +31,82 @@ def log_uncaught_exceptions(ex_cls, ex, tb):
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.graphic = graphic
- def paintEvent(self, event):
+ def paintEvent(self, event) -> None:
painter = QPainter(self)
painter.setBrush(Qt.green)
diff --git a/design_patterns__examples/Decorator/example_1.py b/design_patterns__examples/Decorator/example_1.py
index 8fd09366a..12edf7a6f 100644
--- a/design_patterns__examples/Decorator/example_1.py
+++ b/design_patterns__examples/Decorator/example_1.py
@@ -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
diff --git a/design_patterns__examples/Decorator/example_2.py b/design_patterns__examples/Decorator/example_2.py
index ff6c8f878..87a965c17 100644
--- a/design_patterns__examples/Decorator/example_2.py
+++ b/design_patterns__examples/Decorator/example_2.py
@@ -10,44 +10,44 @@
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!")
diff --git a/design_patterns__examples/Facade/example_1.py b/design_patterns__examples/Facade/example_1.py
index 4a120dc39..9142444d4 100644
--- a/design_patterns__examples/Facade/example_1.py
+++ b/design_patterns__examples/Facade/example_1.py
@@ -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,13 +46,13 @@ 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,
diff --git a/design_patterns__examples/Facade/example_3.py b/design_patterns__examples/Facade/example_3.py
index a4826d882..468c49830 100644
--- a/design_patterns__examples/Facade/example_3.py
+++ b/design_patterns__examples/Facade/example_3.py
@@ -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()
diff --git a/design_patterns__examples/Factory/example_2.py b/design_patterns__examples/Factory/example_2.py
index b6551353a..ae56828b1 100644
--- a/design_patterns__examples/Factory/example_2.py
+++ b/design_patterns__examples/Factory/example_2.py
@@ -21,17 +21,17 @@ def initial(animal: str) -> "Animal":
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")
diff --git a/design_patterns__examples/Flyweight/example_1.py b/design_patterns__examples/Flyweight/example_1.py
index 6384760aa..5f91166f1 100644
--- a/design_patterns__examples/Flyweight/example_1.py
+++ b/design_patterns__examples/Flyweight/example_1.py
@@ -12,16 +12,16 @@
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):
+ def report(self, col: int) -> None:
print(f" {self.row}{col}", end="")
class Factory:
- def __init__(self, max_rows: int):
+ def __init__(self, max_rows: int) -> None:
self._pool: list[Flyweight | None] = [None] * max_rows
def get_flyweight(self, row: int) -> Flyweight:
diff --git a/design_patterns__examples/Flyweight/example_2.py b/design_patterns__examples/Flyweight/example_2.py
index 809e30af4..f945f6de2 100644
--- a/design_patterns__examples/Flyweight/example_2.py
+++ b/design_patterns__examples/Flyweight/example_2.py
@@ -20,14 +20,14 @@ 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(f"{self.symbol} (point_size {self.point_size})")
# "FlyweightFactory"
class CharacterFactory:
- def __init__(self):
+ def __init__(self) -> None:
self._characters: dict[str, Character] = dict()
def get_character(self, key: str) -> Character:
@@ -52,7 +52,7 @@ def get_character(self, key: str) -> Character:
# "ConcreteFlyweight"
class CharacterA(Character):
- def __init__(self):
+ def __init__(self) -> None:
self.symbol = "A"
self.height = 100
self.width = 120
@@ -62,7 +62,7 @@ def __init__(self):
# "ConcreteFlyweight"
class CharacterB(Character):
- def __init__(self):
+ def __init__(self) -> None:
self.symbol = "B"
self.height = 100
self.width = 140
@@ -75,7 +75,7 @@ def __init__(self):
# "ConcreteFlyweight"
class CharacterZ(Character):
- def __init__(self):
+ def __init__(self) -> None:
self.symbol = "Z"
self.height = 100
self.width = 100
diff --git a/design_patterns__examples/Mediator/example_1.py b/design_patterns__examples/Mediator/example_1.py
index 5ad6c2649..2309230b6 100644
--- a/design_patterns__examples/Mediator/example_1.py
+++ b/design_patterns__examples/Mediator/example_1.py
@@ -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":
@@ -57,31 +57,31 @@ 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")
diff --git a/design_patterns__examples/Mediator/example_2.py b/design_patterns__examples/Mediator/example_2.py
index 81ec8852c..f0ccd146b 100644
--- a/design_patterns__examples/Mediator/example_2.py
+++ b/design_patterns__examples/Mediator/example_2.py
@@ -11,15 +11,15 @@
class Mediator:
@staticmethod
- def send_message(user: "User", msg: str):
+ 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)
diff --git a/design_patterns__examples/Mediator/example_notes__pyqt.py b/design_patterns__examples/Mediator/example_notes__pyqt.py
index 364fcec13..e0facea05 100644
--- a/design_patterns__examples/Mediator/example_notes__pyqt.py
+++ b/design_patterns__examples/Mediator/example_notes__pyqt.py
@@ -30,7 +30,7 @@
from PySide.QtCore import *
-def log_uncaught_exceptions(ex_cls, ex, tb):
+def log_uncaught_exceptions(ex_cls, ex, tb) -> None:
text = f"{ex_cls.__name__}: {ex}:\n"
text += "".join(traceback.format_tb(tb))
@@ -44,14 +44,14 @@ def log_uncaught_exceptions(ex_cls, ex, tb):
# Класс заметок
class Note:
- def __init__(self):
+ def __init__(self) -> None:
self._name = "note"
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:
@@ -62,12 +62,12 @@ def getText(self) -> str:
class DefaultListModel(QAbstractListModel):
- def __init__(self):
+ def __init__(self) -> None:
super().__init__()
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:
@@ -84,7 +84,7 @@ def data(self, index: QModelIndex, role: int = Qt.DisplayRole) -> Any:
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)
@@ -94,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()
@@ -109,57 +109,57 @@ def get_items(self) -> list[Note]:
# Общий интерфейс посредников.
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
@@ -170,7 +170,7 @@ def getName(self) -> str:
# Конкретные компоненты никак не связаны между собой. У них есть только один
# канал общения – через отправку уведомлений посреднику.
class AddButton(QPushButton, Component):
- def __init__(self):
+ def __init__(self) -> None:
super().__init__("Add")
# При клике на кнопку вызываем метод посредника
@@ -183,7 +183,7 @@ def getName(self) -> str:
# Конкретные компоненты никак не связаны между собой. У них есть только один
# канал общения – через отправку уведомлений посреднику.
class DeleteButton(QPushButton, Component):
- def __init__(self):
+ def __init__(self) -> None:
super().__init__("Del")
# При клике на кнопку вызываем метод посредника
@@ -196,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
@@ -235,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)
@@ -250,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
@@ -273,7 +273,7 @@ def getName(self) -> str:
# Конкретные компоненты никак не связаны между собой. У них есть только один
# канал общения – через отправку уведомлений посреднику.
class SaveButton(QPushButton, Component):
- def __init__(self):
+ def __init__(self) -> None:
super().__init__("Save")
# При клике на кнопку вызываем метод посредника
@@ -286,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()
@@ -298,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()
@@ -311,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
@@ -327,7 +327,7 @@ def __init__(self):
self._mainWindow = None
# Здесь происходит регистрация компонентов посредником.
- def registerComponent(self, component: Component):
+ def registerComponent(self, component: Component) -> None:
component.setMediator(self)
if component.getName() == "AddButton":
@@ -342,7 +342,7 @@ def registerComponent(self, component: Component):
elif component.getName() == "List":
self._list: ListNote = component
- def foo(*args):
+ def foo(*args) -> None:
empty = len(self._list.selectedIndexes()) == 0
self.hideElements(empty)
@@ -366,7 +366,7 @@ def foo(*args):
# Разнообразные методы общения с компонентами.
#
- def addNewNote(self, note: Note):
+ def addNewNote(self, note: Note) -> None:
# Инкрементальные названия
text = note.getName() + str(self._list.model().rowCount() + 1)
note.setName(text)
@@ -375,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):
+ 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())
@@ -393,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()
@@ -408,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)
@@ -427,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:"))
diff --git a/design_patterns__examples/Observer/example_1.py b/design_patterns__examples/Observer/example_1.py
index 678e01cf0..2585c4c06 100644
--- a/design_patterns__examples/Observer/example_1.py
+++ b/design_patterns__examples/Observer/example_1.py
@@ -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,14 +74,14 @@ 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):
+ def display(self) -> None:
print(
f"Сейчас значения: {self.temperature:.1f} градусов цельсия и {self.humidity:.1f}% влажности. "
f"Давление {self.pressure} мм рт. ст."
diff --git a/design_patterns__examples/Observer/example_4.py b/design_patterns__examples/Observer/example_4.py
index 95453aed2..c797b1f59 100644
--- a/design_patterns__examples/Observer/example_4.py
+++ b/design_patterns__examples/Observer/example_4.py
@@ -31,33 +31,33 @@ def update(self, event_type: str, file: IO):
class EventManager:
- def __init__(self, *operations):
+ 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):
+ def open_file(self, file_path: str) -> None:
self.file = open(file_path, "w", encoding="utf-8")
self.events.notify("open", self.file)
@@ -69,10 +69,10 @@ 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):
+ 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}"
@@ -80,11 +80,11 @@ def update(self, event_type: str, file: IO):
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(
diff --git a/design_patterns__examples/Prototype/example.py b/design_patterns__examples/Prototype/example.py
index 86aded0b7..d9793cacf 100644
--- a/design_patterns__examples/Prototype/example.py
+++ b/design_patterns__examples/Prototype/example.py
@@ -9,21 +9,22 @@
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)
@@ -33,13 +34,13 @@ def clone(self, name, **attr):
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):
+ def __str__(self) -> str:
return f"A({self.x}, {self.y}, {self.z}, {self.garbage})"
a = A()
diff --git a/design_patterns__examples/Proxy/example.py b/design_patterns__examples/Proxy/example.py
index d67ba0e66..2c520c2b7 100644
--- a/design_patterns__examples/Proxy/example.py
+++ b/design_patterns__examples/Proxy/example.py
@@ -43,7 +43,7 @@ def div(self, x, y):
class MathProxy(IMath):
"""Прокси"""
- def __init__(self):
+ def __init__(self) -> None:
self.math = None
# Быстрые операции - не требуют реального субъекта
diff --git a/design_patterns__examples/Proxy/example__cached.py b/design_patterns__examples/Proxy/example__cached.py
index f0e37f308..e3c7ee6d7 100644
--- a/design_patterns__examples/Proxy/example__cached.py
+++ b/design_patterns__examples/Proxy/example__cached.py
@@ -5,6 +5,9 @@
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()
@@ -58,15 +61,20 @@ def get_status_code(self, url: str) -> int:
if __name__ == "__main__":
- import time
+ 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(f"Elapsed time: {time.clock() - self.start_time:.6f} sec")
+ 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"
diff --git a/design_patterns__examples/Proxy/example__logged.py b/design_patterns__examples/Proxy/example__logged.py
index ab1e7d987..10e59266e 100644
--- a/design_patterns__examples/Proxy/example__logged.py
+++ b/design_patterns__examples/Proxy/example__logged.py
@@ -8,6 +8,8 @@
import sys
from logging.handlers import RotatingFileHandler
+from types import TracebackType
+from typing import Optional, Type
from example__cached import IGoUrl, GoUrl, GoUrlCachedProxy, requests
@@ -23,7 +25,9 @@ def get_logger(name, file="log.txt", encoding="utf-8"):
# Simple file handler
# fh = logging.FileHandler(file, encoding=encoding)
# or:
- fh = RotatingFileHandler(file, maxBytes=10_000_000, backupCount=5, encoding=encoding)
+ fh = RotatingFileHandler(
+ file, maxBytes=10_000_000, backupCount=5, encoding=encoding
+ )
fh.setFormatter(formatter)
log.addHandler(fh)
@@ -39,7 +43,7 @@ class GoUrlLoggedProxy(IGoUrl):
_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()
@@ -61,20 +65,25 @@ 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
+ 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(f"Elapsed time: {time.clock() - self.start_time:.6f} sec")
+ 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"
diff --git a/design_patterns__examples/Proxy/example__with_abc.py b/design_patterns__examples/Proxy/example__with_abc.py
index 82c9c4f68..30ff7f6b4 100644
--- a/design_patterns__examples/Proxy/example__with_abc.py
+++ b/design_patterns__examples/Proxy/example__with_abc.py
@@ -49,7 +49,7 @@ def div(self, x, y):
class MathProxy(IMath):
"""Прокси"""
- def __init__(self):
+ def __init__(self) -> None:
self.math = None
# Быстрые операции - не требуют реального субъекта
diff --git a/design_patterns__examples/Strategy/example.py b/design_patterns__examples/Strategy/example.py
index 70aa3e7d2..dddbba159 100644
--- a/design_patterns__examples/Strategy/example.py
+++ b/design_patterns__examples/Strategy/example.py
@@ -19,23 +19,23 @@ 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)
diff --git a/design_patterns__examples/Strategy/example_1.py b/design_patterns__examples/Strategy/example_1.py
index 11ed51b47..5519920bb 100644
--- a/design_patterns__examples/Strategy/example_1.py
+++ b/design_patterns__examples/Strategy/example_1.py
@@ -28,10 +28,10 @@ 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):
diff --git a/design_patterns__examples/Template method/example_2.py b/design_patterns__examples/Template method/example_2.py
index 3a47b5056..6c6b81956 100644
--- a/design_patterns__examples/Template method/example_2.py
+++ b/design_patterns__examples/Template method/example_2.py
@@ -18,19 +18,19 @@ 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()
@@ -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
...
diff --git a/design_patterns__examples/Template method/example_3.py b/design_patterns__examples/Template method/example_3.py
index 234fd0b7b..6ea680ff2 100644
--- a/design_patterns__examples/Template method/example_3.py
+++ b/design_patterns__examples/Template method/example_3.py
@@ -41,13 +41,13 @@ 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
@@ -68,10 +68,10 @@ 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:
@@ -87,7 +87,7 @@ def _simulate_network_latency(self):
# Класс социальной сети.
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
@@ -108,10 +108,10 @@ 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:
diff --git a/design_patterns__examples/Template method/example_4.py b/design_patterns__examples/Template method/example_4.py
index 076c6c080..31c765e05 100644
--- a/design_patterns__examples/Template method/example_4.py
+++ b/design_patterns__examples/Template method/example_4.py
@@ -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,14 +27,14 @@ 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()
@@ -44,11 +44,11 @@ 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:
@@ -60,11 +60,11 @@ 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
@@ -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,24 +110,24 @@ 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:
# Отправить воинов на позицию.
...
diff --git a/detection_of_site_changes_Unistream/gui.py b/detection_of_site_changes_Unistream/gui.py
index 703ba6118..afaf178f9 100644
--- a/detection_of_site_changes_Unistream/gui.py
+++ b/detection_of_site_changes_Unistream/gui.py
@@ -22,7 +22,7 @@
class MainWindow(QMainWindow):
- def __init__(self):
+ def __init__(self) -> None:
super().__init__()
self.setWindowTitle("detection_of_site_changes_Unistream")
@@ -53,7 +53,7 @@ def __init__(self):
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"
diff --git a/detection_of_site_changes_Unistream/main.py b/detection_of_site_changes_Unistream/main.py
index 2267877cd..bfb3676bb 100644
--- a/detection_of_site_changes_Unistream/main.py
+++ b/detection_of_site_changes_Unistream/main.py
@@ -57,7 +57,7 @@ def get_site_text(url="https://test.api.unistream.com/help/index.html"):
)
class WebPage(QWebPage):
- def userAgentForUrl(self, url):
+ 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:
@@ -149,7 +149,7 @@ class TextRevision(Base):
# Содержимым является только разница
diff = Column(String)
- def __init__(self, new_text, old_text=""):
+ def __init__(self, new_text, old_text="") -> None:
"""
Конструктор принимает контент и сравниваемый контент, запоминает хеш содержимого,
текущую дату и время и результат сравнения
@@ -166,7 +166,7 @@ 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):
+ def __repr__(self) -> str:
return f""
diff --git a/dict_to_url_params.py b/dict_to_url_params.py
index 45e869033..c30173bf5 100644
--- a/dict_to_url_params.py
+++ b/dict_to_url_params.py
@@ -5,7 +5,7 @@
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}]"
diff --git a/dis__bytecode__is_empty_function.py b/dis__bytecode__is_empty_function.py
index 2d7e1768e..7f54f9bea 100644
--- a/dis__bytecode__is_empty_function.py
+++ b/dis__bytecode__is_empty_function.py
@@ -20,10 +20,10 @@ def check_is_empty_function(function):
if __name__ == "__main__":
- def foo():
+ def foo() -> None:
pass
- def foo2():
+ def foo2() -> int:
return 1
print("Empty:", check_is_empty_function(foo))
diff --git a/download_file/download_file.py b/download_file/download_file.py
index 81641a7fa..bb3e5e297 100644
--- a/download_file/download_file.py
+++ b/download_file/download_file.py
@@ -28,26 +28,26 @@ def wrapper(*args, **kwargs):
@timer
-def way1(url: str, file_name: str):
+def way1(url: str, file_name: str) -> None:
resource = urlopen(url)
with open(file_name, "wb") as f:
f.write(resource.read())
@timer
-def way2(url: str, file_name: str):
+def way2(url: str, file_name: str) -> None:
urlretrieve(url, file_name)
@timer
-def way3(url: str, file_name: str):
+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: str, file_name: str):
+def way4(url: str, file_name: str) -> None:
h = httplib2.Http(".cache")
response, content = h.request(url)
with open(file_name, "wb") as f:
@@ -55,7 +55,7 @@ def way4(url: str, file_name: str):
@timer
-def way5(url: str, file_name: str):
+def way5(url: str, file_name: str) -> None:
g = Grab()
g.go(url)
g.response.save(file_name)
diff --git a/download_file/with_progress.py b/download_file/with_progress.py
index 14288e6e2..4eccbcc5a 100644
--- a/download_file/with_progress.py
+++ b/download_file/with_progress.py
@@ -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
@@ -56,7 +56,7 @@ def run(url, file_name, reporthook, callback_func):
print(download(URL, "SimplePyScripts.zip", as_thread=True))
- def callback_func(file_name: str):
+ def callback_func(file_name: str) -> None:
print("File name:", file_name)
print(
diff --git a/download_volume_readmanga.py b/download_volume_readmanga.py
index 8f9143f81..b29ce0054 100644
--- a/download_volume_readmanga.py
+++ b/download_volume_readmanga.py
@@ -39,7 +39,7 @@ def get_url_images(url):
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писок изображений пустой.")
return
diff --git a/draw fractal/Apollon_Set/Apollon_Set__PIL.py b/draw fractal/Apollon_Set/Apollon_Set__PIL.py
index 6e54f6655..0359ebadc 100644
--- a/draw fractal/Apollon_Set/Apollon_Set__PIL.py
+++ b/draw fractal/Apollon_Set/Apollon_Set__PIL.py
@@ -69,7 +69,7 @@
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
diff --git a/draw fractal/Cantor_dust/Cantor_dust__PIL.py b/draw fractal/Cantor_dust/Cantor_dust__PIL.py
index 4b6e59555..3dd63d6aa 100644
--- a/draw fractal/Cantor_dust/Cantor_dust__PIL.py
+++ b/draw fractal/Cantor_dust/Cantor_dust__PIL.py
@@ -40,8 +40,8 @@
from PIL import Image, ImageDraw
-def draw_cantor_dust(draw_by_image):
- def draw(x, y, size):
+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)
diff --git a/draw fractal/Circular_fractal/Circular_fractal__PIL.py b/draw fractal/Circular_fractal/Circular_fractal__PIL.py
index f74d1c0b9..28735840b 100644
--- a/draw fractal/Circular_fractal/Circular_fractal__PIL.py
+++ b/draw fractal/Circular_fractal/Circular_fractal__PIL.py
@@ -39,7 +39,7 @@
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
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 9cd17d4fd..dad5d9eb2 100644
--- a/draw fractal/Dragon_curve_1/Dragon_curve_1__PIL.py
+++ b/draw fractal/Dragon_curve_1/Dragon_curve_1__PIL.py
@@ -40,7 +40,7 @@
from PIL import Image, ImageDraw
-def draw_dragon_curve_1(draw_by_image, x1, y1, x2, y2, k):
+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
diff --git a/draw fractal/Fern/Fern__PIL.py b/draw fractal/Fern/Fern__PIL.py
index e19823408..5353cc35b 100644
--- a/draw fractal/Fern/Fern__PIL.py
+++ b/draw fractal/Fern/Fern__PIL.py
@@ -49,7 +49,7 @@
from PIL import Image, ImageDraw
-def draw_fern(draw_by_image, width, height):
+def draw_fern(draw_by_image, width, height) -> None:
n = 255
cx = 0.251
diff --git a/draw fractal/Fern_2/Fern_2__PIL.py b/draw fractal/Fern_2/Fern_2__PIL.py
index 0f850aabb..9662a6311 100644
--- a/draw fractal/Fern_2/Fern_2__PIL.py
+++ b/draw fractal/Fern_2/Fern_2__PIL.py
@@ -46,11 +46,11 @@
from PIL import Image, ImageDraw
-def draw_fern_2(draw_by_image):
- def line_to(x, y, l, u):
+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)
diff --git a/draw fractal/Fingerprint/Fingerprint__PIL.py b/draw fractal/Fingerprint/Fingerprint__PIL.py
index 676cdbff6..ad6415f4f 100644
--- a/draw fractal/Fingerprint/Fingerprint__PIL.py
+++ b/draw fractal/Fingerprint/Fingerprint__PIL.py
@@ -48,7 +48,7 @@
from PIL import Image, ImageDraw
-def draw_fingerprint(draw_by_image, width, height):
+def draw_fingerprint(draw_by_image, width, height) -> None:
n = 255
max = 10
diff --git a/draw fractal/Fractal_tree/Fractal_tree__PIL.py b/draw fractal/Fractal_tree/Fractal_tree__PIL.py
index baa0f658d..97316863e 100644
--- a/draw fractal/Fractal_tree/Fractal_tree__PIL.py
+++ b/draw fractal/Fractal_tree/Fractal_tree__PIL.py
@@ -64,7 +64,7 @@
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
diff --git a/draw fractal/Fractal_tree/Fractal_tree__Qt_gui.py b/draw fractal/Fractal_tree/Fractal_tree__Qt_gui.py
index 0389a994c..7495fbfde 100644
--- a/draw fractal/Fractal_tree/Fractal_tree__Qt_gui.py
+++ b/draw fractal/Fractal_tree/Fractal_tree__Qt_gui.py
@@ -59,7 +59,7 @@
class Widget(QWidget):
- def __init__(self):
+ def __init__(self) -> None:
super().__init__()
self.setWindowTitle("Fractal tree")
@@ -76,7 +76,7 @@ def __init__(self):
self.setLayout(main_layout)
- def generate_tree(self):
+ def generate_tree(self) -> None:
img = Image.new("RGB", (700, 600), "white")
draw_fractal_tree(ImageDraw.Draw(img), 350, 580, 3 * math.pi / 2, 200)
diff --git a/draw fractal/Gosper_curve/Gosper_curve__PIL.py b/draw fractal/Gosper_curve/Gosper_curve__PIL.py
index 7add7cecf..dd23d5b81 100644
--- a/draw fractal/Gosper_curve/Gosper_curve__PIL.py
+++ b/draw fractal/Gosper_curve/Gosper_curve__PIL.py
@@ -61,8 +61,8 @@
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)
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 9ecb1c28d..a4628ee1c 100644
--- a/draw fractal/Ice_fractal_1/Circular_fractal_1__PIL.py
+++ b/draw fractal/Ice_fractal_1/Circular_fractal_1__PIL.py
@@ -49,12 +49,12 @@
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)
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 a447e22b1..38e5d0d56 100644
--- a/draw fractal/Ice_fractal_2/Circular_fractal_2__PIL.py
+++ b/draw fractal/Ice_fractal_2/Circular_fractal_2__PIL.py
@@ -51,12 +51,12 @@
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)
diff --git a/draw fractal/Koch_curve/Koch_curve__PIL.py b/draw fractal/Koch_curve/Koch_curve__PIL.py
index 17b9e2dd7..db728139f 100644
--- a/draw fractal/Koch_curve/Koch_curve__PIL.py
+++ b/draw fractal/Koch_curve/Koch_curve__PIL.py
@@ -60,7 +60,7 @@
from PIL import Image, ImageDraw
-def draw_koch(draw, xa, ya, xe, ye, n):
+def draw_koch(draw, xa, ya, xe, ye, n) -> None:
"""
Draws koch curve between two points.
diff --git a/draw fractal/Koch_curve/Koch_curve__Qt.py b/draw fractal/Koch_curve/Koch_curve__Qt.py
index 67a5346cb..90aa52c09 100644
--- a/draw fractal/Koch_curve/Koch_curve__Qt.py
+++ b/draw fractal/Koch_curve/Koch_curve__Qt.py
@@ -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.
diff --git a/draw fractal/Koch_curve/Koch_curve__Qt_gui.py b/draw fractal/Koch_curve/Koch_curve__Qt_gui.py
index 1dad3cf96..0996c3e50 100644
--- a/draw fractal/Koch_curve/Koch_curve__Qt_gui.py
+++ b/draw fractal/Koch_curve/Koch_curve__Qt_gui.py
@@ -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.
@@ -130,7 +130,7 @@ def draw_koch(painter, xa, ya, xe, ye, n):
class Widget(QWidget):
- def __init__(self):
+ def __init__(self) -> None:
super().__init__()
self.setWindowTitle("Koch_curve snowflake")
@@ -155,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)
diff --git a/draw fractal/Koch_snowflake/Koch_snowflake__PIL.py b/draw fractal/Koch_snowflake/Koch_snowflake__PIL.py
index 3d3fb0f43..8a97d9a14 100644
--- a/draw fractal/Koch_snowflake/Koch_snowflake__PIL.py
+++ b/draw fractal/Koch_snowflake/Koch_snowflake__PIL.py
@@ -50,13 +50,13 @@
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"
diff --git a/draw fractal/Levy_curve/Levy_curve__PIL.py b/draw fractal/Levy_curve/Levy_curve__PIL.py
index 369776b57..50cb3752d 100644
--- a/draw fractal/Levy_curve/Levy_curve__PIL.py
+++ b/draw fractal/Levy_curve/Levy_curve__PIL.py
@@ -56,7 +56,7 @@
from PIL import Image, ImageDraw
-def draw_levy(draw):
+def draw_levy(draw) -> None:
iter = 50000
mx = 200
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 0048dc005..5c6a3ed5b 100644
--- a/draw fractal/Mandelbrot_Set_1/Mandelbrot_Set_1__PIL.py
+++ b/draw fractal/Mandelbrot_Set_1/Mandelbrot_Set_1__PIL.py
@@ -48,7 +48,7 @@
from PIL import Image, ImageDraw
-def draw_mandelbrot_set_1(draw_by_image, width, height):
+def draw_mandelbrot_set_1(draw_by_image, width, height) -> None:
n = 255
max = 10
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 9467f811e..d03467eb2 100644
--- a/draw fractal/Mandelbrot_Set_2/Mandelbrot_Set_2__PIL.py
+++ b/draw fractal/Mandelbrot_Set_2/Mandelbrot_Set_2__PIL.py
@@ -48,7 +48,7 @@
from PIL import Image, ImageDraw
-def draw_mandelbrot_set_2(draw_by_image, width, height):
+def draw_mandelbrot_set_2(draw_by_image, width, height) -> None:
n = 255
max = 10
diff --git a/draw fractal/Minkowski_curve/Minkowski_curve__PIL.py b/draw fractal/Minkowski_curve/Minkowski_curve__PIL.py
index e282614e6..f8f631a4e 100644
--- a/draw fractal/Minkowski_curve/Minkowski_curve__PIL.py
+++ b/draw fractal/Minkowski_curve/Minkowski_curve__PIL.py
@@ -79,7 +79,7 @@
from PIL import Image, ImageDraw
-def draw_minkowski(draw, xa, ya, xi, yi, n):
+def draw_minkowski(draw, xa, ya, xi, yi, n) -> None:
"""
Draws minkowski curve between two points.
diff --git a/draw fractal/Monkey_tree/Monkey_tree__PIL.py b/draw fractal/Monkey_tree/Monkey_tree__PIL.py
index 74cd0ff9a..e1d71f847 100644
--- a/draw fractal/Monkey_tree/Monkey_tree__PIL.py
+++ b/draw fractal/Monkey_tree/Monkey_tree__PIL.py
@@ -77,12 +77,12 @@
from PIL import Image, ImageDraw
-def draw_monkey_tree(draw_by_image):
+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):
+ def draw(x, y, l, u, t, q, s) -> None:
if t > 0:
if q == 1:
x += l * cos(u)
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 fd56eb7fa..05c44e2a6 100644
--- a/draw fractal/Pythagoras_tree_2/Pythagoras_tree_2__PIL.py
+++ b/draw fractal/Pythagoras_tree_2/Pythagoras_tree_2__PIL.py
@@ -45,11 +45,11 @@
from PIL import Image, ImageDraw
-def draw_pythagoras_tree_2(draw_by_image):
- def line_to(x, y, l, u):
+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)
diff --git a/draw fractal/Sierpinski_carpet/Sierpinski_carpet__PIL.py b/draw fractal/Sierpinski_carpet/Sierpinski_carpet__PIL.py
index 4b249abbe..4ecafccf6 100644
--- a/draw fractal/Sierpinski_carpet/Sierpinski_carpet__PIL.py
+++ b/draw fractal/Sierpinski_carpet/Sierpinski_carpet__PIL.py
@@ -50,8 +50,8 @@
from PIL import Image, ImageDraw
-def draw_sierpinski_carpet(draw_by_image, Z):
- def serp(x1, y1, x2, y2, n):
+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
diff --git a/draw fractal/Sierpinski_triangle/Sierpinski_triangle__PIL.py b/draw fractal/Sierpinski_triangle/Sierpinski_triangle__PIL.py
index 6add5bead..7037a8d6e 100644
--- a/draw fractal/Sierpinski_triangle/Sierpinski_triangle__PIL.py
+++ b/draw fractal/Sierpinski_triangle/Sierpinski_triangle__PIL.py
@@ -52,15 +52,15 @@
from PIL import Image, ImageDraw
-def draw_sierpinski_triangle(draw_by_image, Z):
+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
diff --git a/draw fractal/Snowflake/Snowflake__PIL.py b/draw fractal/Snowflake/Snowflake__PIL.py
index ce9dba293..28ca5b02c 100644
--- a/draw fractal/Snowflake/Snowflake__PIL.py
+++ b/draw fractal/Snowflake/Snowflake__PIL.py
@@ -39,8 +39,8 @@
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):
diff --git a/draw_wave.py b/draw_wave.py
index b9beb005a..a84976c18 100644
--- a/draw_wave.py
+++ b/draw_wave.py
@@ -44,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()
diff --git a/dynamic_methods_link_call.py b/dynamic_methods_link_call.py
index 93793c5a6..7755ae318 100644
--- a/dynamic_methods_link_call.py
+++ b/dynamic_methods_link_call.py
@@ -5,7 +5,7 @@
class CallBuilder:
- def __init__(self, part=None, sep=""):
+ def __init__(self, part=None, sep="") -> None:
self._part = part
self._sep = sep
diff --git a/effect_of_vanishing_photos/effect_of_vanishing_photos.py b/effect_of_vanishing_photos/effect_of_vanishing_photos.py
index 7153581b3..37ec67af4 100644
--- a/effect_of_vanishing_photos/effect_of_vanishing_photos.py
+++ b/effect_of_vanishing_photos/effect_of_vanishing_photos.py
@@ -31,7 +31,7 @@
from PySide.QtCore import *
-def log_uncaught_exceptions(ex_cls, ex, tb):
+def log_uncaught_exceptions(ex_cls, ex, tb) -> None:
text = f"{ex_cls.__name__}: {ex}:\n"
text += "".join(traceback.format_tb(tb))
@@ -45,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()
@@ -68,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()
@@ -80,7 +80,7 @@ def tick(self):
class Widget(QWidget):
- def __init__(self):
+ def __init__(self) -> None:
super().__init__()
self.setWindowTitle("effect_of_vanishing_photos.py")
@@ -91,12 +91,12 @@ def __init__(self):
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)
diff --git a/enum__examples.py b/enum__examples.py
index 3b7bc718e..2c9cbae63 100644
--- a/enum__examples.py
+++ b/enum__examples.py
@@ -60,7 +60,7 @@ class Planet(Enum):
URANUS = (8.686e25, 2.5559e7)
NEPTUNE = (1.024e26, 2.4746e7)
- def __init__(self, mass, radius):
+ def __init__(self, mass, radius) -> None:
self.mass = mass # in kilograms
self.radius = radius # in meters
diff --git a/eval_expr_with_time.py b/eval_expr_total_time.py
similarity index 50%
rename from eval_expr_with_time.py
rename to eval_expr_total_time.py
index f3d466958..10fa6bdc0 100644
--- a/eval_expr_with_time.py
+++ b/eval_expr_total_time.py
@@ -8,10 +8,12 @@
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})*$"
+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:
@@ -23,20 +25,44 @@ 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 = preprocess_expr_with_time(text)
- total_seconds = eval(text)
+ 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"
-
- print(eval_expr_with_time(text))
- # 10:34:23
+ 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
diff --git a/exit_handler.py b/exit_handler.py
index dbd843a3d..b4a31d9a0 100644
--- a/exit_handler.py
+++ b/exit_handler.py
@@ -11,7 +11,7 @@
start_time = timer()
-def exit_handler():
+def exit_handler() -> None:
print(f"Execution time: {timer() - start_time:.3f} secs.")
@@ -20,7 +20,7 @@ def exit_handler():
# OR with decorator:
@atexit.register
-def exit_handler():
+def exit_handler() -> None:
print(f"Execution time: {timer() - start_time:.3f} secs.")
diff --git a/explore__windows.py b/explore__windows.py
index 6dc61ba18..818614c91 100644
--- a/explore__windows.py
+++ b/explore__windows.py
@@ -9,7 +9,7 @@
from pathlib import Path
-def explore(path: str | Path, select=True):
+def explore(path: str | Path, select=True) -> None:
path = Path(path).resolve()
if path.is_dir() or path.is_file():
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 ef665738e..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
@@ -53,7 +53,7 @@
# Formatted string literals cannot be used as docstrings, even if they do not include expressions.
-def foo():
+def foo() -> None:
f"Not a docstring"
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 05a16f2cd..b8e6168e4 100644
--- a/f-strings__formatted string literals__PEP 498/my_example.py
+++ b/f-strings__formatted string literals__PEP 498/my_example.py
@@ -41,17 +41,17 @@ def strange_1(text):
# Use class
class Foo:
- def __init__(self, text=""):
+ def __init__(self, text="") -> None:
self.text = text
def strange_2(self, text):
return re.sub(r"[aeo]", " ", text)
@staticmethod
- def strange_3(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:
@@ -65,10 +65,10 @@ def __format__(self, format_spec):
return self.__str__()
- def __str__(self):
+ def __str__(self) -> str:
return f''
- def __repr__(self):
+ def __repr__(self) -> str:
return f''
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/src/blog/repositories.py b/fastapi__examples/blog_from_stepic/src/blog/repositories.py
index ee45a6676..c2926d033 100644
--- a/fastapi__examples/blog_from_stepic/src/blog/repositories.py
+++ b/fastapi__examples/blog_from_stepic/src/blog/repositories.py
@@ -27,7 +27,7 @@ class MemoryUsersRepository(UsersRepository):
Реализация пользовательского хранилища в оперативной памяти.
Пользователи инициализируются во время инициализации репозитория
"""
- def __init__(self):
+ def __init__(self) -> None:
self.users = [
Admin(
id="29ae7ebf-4445-42f2-9548-a3a54f095220", # это uuid4 – уникальный идентификатор пользователя
@@ -70,13 +70,13 @@ def create_article(self, article: Article):
class ShelveArticlesRepository(ArticlesRepository):
- def __init__(self):
+ 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):
+ 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
index 6d5e3a82c..d46096528 100644
--- a/fastapi__examples/blog_from_stepic/src/blog/resources.py
+++ b/fastapi__examples/blog_from_stepic/src/blog/resources.py
@@ -23,7 +23,7 @@
@router.get("/", response_class=HTMLResponse)
-def index():
+def index() -> str:
return """
diff --git a/fastapi__examples/logging/main.py b/fastapi__examples/logging/main.py
index a07aebf3a..0f55b572b 100644
--- a/fastapi__examples/logging/main.py
+++ b/fastapi__examples/logging/main.py
@@ -4,9 +4,7 @@
__author__ = "ipetrash"
-import uvicorn
from fastapi import FastAPI
-
from config import LOGGING_CONFIG
@@ -20,10 +18,9 @@ def index():
if __name__ == "__main__":
from pathlib import Path
+ import uvicorn
uvicorn.run(
- # NOTE: Тоже самое
- # app="running-programmatically:app",
app=f"{Path(__file__).stem}:app",
host="127.0.0.1",
port=8000,
diff --git a/fastapi__examples/market_from_stepic/requirements.txt b/fastapi__examples/market_from_stepic/requirements.txt
index 1cebfb6b5..72cc557f6 100644
--- a/fastapi__examples/market_from_stepic/requirements.txt
+++ b/fastapi__examples/market_from_stepic/requirements.txt
@@ -1,5 +1,5 @@
fastapi==0.111.0
uvicorn[standard]==0.30.1
-PyJWT==2.8.0
+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/src/market/db.py b/fastapi__examples/market_from_stepic/src/market/db.py
index caf8b76b1..8a164a2ef 100644
--- a/fastapi__examples/market_from_stepic/src/market/db.py
+++ b/fastapi__examples/market_from_stepic/src/market/db.py
@@ -31,7 +31,7 @@ class InvalidException(DbException):
class InvalidOrderStatusException(InvalidException):
- def __init__(self, prev_status: models.StatusOrderEnum, new_status: models.StatusOrderEnum):
+ 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}")
@@ -80,10 +80,10 @@ def get_value(self, name: str, default: Any = None) -> Any:
return self.db.get(name)
@session()
- def set_value(self, name: str, value: Any):
+ def set_value(self, name: str, value: Any) -> None:
self.db[name] = value
- def __init__(self, file_name: Path | str = DB_FILE_NAME):
+ def __init__(self, file_name: Path | str = DB_FILE_NAME) -> None:
self.file_name: str = str(file_name)
self.db: shelve.Shelf | None = None
@@ -93,7 +93,7 @@ def _generate_id(self) -> str:
return str(uuid4())
@lock()
- def rebuild_indexes(self, clear: bool = True):
+ 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:
@@ -111,14 +111,14 @@ def rebuild_indexes(self, clear: bool = True):
self.set_value(self.KEY_INDEXES, indexes)
@lock()
- def add_index(self, table: str, key: str, id: str):
+ 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):
+ 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)
@@ -130,7 +130,7 @@ def get_id_from_index(self, table: str, key: str) -> str | None:
return indexes[table].get(key)
@lock()
- def _do_init_db_objects(self):
+ def _do_init_db_objects(self) -> None:
self.rebuild_indexes(clear=False)
if self.KEY_USERS not in self.get_value(""):
@@ -271,7 +271,7 @@ def update_product(
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:
@@ -311,7 +311,7 @@ def create_shopping_cart(self, product_ids: list[str]) -> models.ShoppingCart:
return obj
@lock()
- def delete_shopping_cart(self, shopping_cart_id: str):
+ def delete_shopping_cart(self, shopping_cart_id: str) -> None:
# Проверка наличия
self.get_shopping_cart(shopping_cart_id, check_exists=True)
@@ -325,7 +325,7 @@ 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
)
@@ -356,7 +356,7 @@ 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
)
@@ -372,7 +372,7 @@ 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
)
diff --git a/fastapi__examples/market_from_stepic/src/market/main.py b/fastapi__examples/market_from_stepic/src/market/main.py
index 321ed6139..2e1ecb5a4 100644
--- a/fastapi__examples/market_from_stepic/src/market/main.py
+++ b/fastapi__examples/market_from_stepic/src/market/main.py
@@ -28,7 +28,7 @@ async def unicorn_exception_handler(_: Request, exc: DbException):
@app.get("/", response_class=HTMLResponse)
-def index():
+def index() -> str:
return """\
diff --git a/fastapi__examples/market_from_stepic/src/market/resources.py b/fastapi__examples/market_from_stepic/src/market/resources.py
index 0a02406f5..cb3467622 100644
--- a/fastapi__examples/market_from_stepic/src/market/resources.py
+++ b/fastapi__examples/market_from_stepic/src/market/resources.py
@@ -178,7 +178,7 @@ def create_shopping_cart(
@router.delete("/shopping-cart/{id}", status_code=status.HTTP_204_NO_CONTENT)
-def delete_shopping_cart(id: str):
+def delete_shopping_cart(id: str) -> None:
services.delete_shopping_cart(id)
diff --git a/fastapi__examples/market_from_stepic/src/market/services.py b/fastapi__examples/market_from_stepic/src/market/services.py
index 7f0c12b06..e0f838b71 100644
--- a/fastapi__examples/market_from_stepic/src/market/services.py
+++ b/fastapi__examples/market_from_stepic/src/market/services.py
@@ -66,7 +66,7 @@ def update_product(
name: str | None = None,
price_minor: int | None = None, # Копейки
description: str | None = None,
-):
+) -> None:
db.update_product(
id=id,
name=name,
@@ -85,15 +85,15 @@ def create_shopping_cart(product_ids: list[str] = None) -> models.IdBasedObj:
return models.IdBasedObj(id=shopping_cart.id)
-def delete_shopping_cart(shopping_cart_id: str):
+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,
-):
- return db.add_product_in_shopping_cart(
+) -> None:
+ db.add_product_in_shopping_cart(
shopping_cart_id=shopping_cart_id,
product_id=product_id,
)
@@ -102,8 +102,8 @@ def add_product_in_shopping_cart(
def remove_product_from_shopping_cart(
shopping_cart_id: str,
product_id: str,
-):
- return db.remove_product_from_shopping_cart(
+) -> None:
+ db.remove_product_from_shopping_cart(
shopping_cart_id=shopping_cart_id,
product_id=product_id,
)
@@ -143,7 +143,7 @@ def update_order(
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(
@@ -177,7 +177,7 @@ def submit_order(
id: str,
status: models.StatusOrderEnum,
context_user: models.User | None = None,
-):
+) -> None:
update_order(
id=id,
status=status,
diff --git a/fastapi__examples/running-programmatically.py b/fastapi__examples/running-programmatically.py
index 20ab24ea1..6d558312a 100644
--- a/fastapi__examples/running-programmatically.py
+++ b/fastapi__examples/running-programmatically.py
@@ -4,7 +4,6 @@
__author__ = "ipetrash"
-import uvicorn
from fastapi import FastAPI
@@ -18,10 +17,9 @@ def index():
if __name__ == "__main__":
from pathlib import Path
+ import uvicorn
uvicorn.run(
- # NOTE: Тоже самое
- # app="running-programmatically:app",
app=f"{Path(__file__).stem}:app",
host="127.0.0.1",
port=8000,
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/extract_pictures_from_fb2/fb2_pictures__using_bs4.py b/fb2__parsing/extract_pictures_from_fb2/fb2_pictures__using_bs4.py
index 19a1ea629..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
@@ -23,7 +23,7 @@
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)
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 022520907..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
@@ -23,7 +23,7 @@
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)
@@ -34,7 +34,7 @@ def do(file_name, output_dir="output", debug=True):
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"]
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 0afbe7af7..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
@@ -25,7 +25,7 @@
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)
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
index dc5110be0..cbe234673 100644
--- 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
@@ -54,7 +54,7 @@ def iter_blocks(text: str, start_str: str, end_str: str) -> Iterator[str]:
yield block
-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)
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 fc67172be..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
@@ -13,16 +13,17 @@
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 xml.etree import ElementTree as ET
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)
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 6be8b363c..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
@@ -22,7 +22,7 @@
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)
@@ -36,18 +36,18 @@ def do(file_name, output_dir="output", debug=True):
"number": 1,
}
- def on_start_element(name, attrs):
+ 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):
+ def on_char_data(data) -> None:
if PARSE_DATA["last_start_tag"] != "binary":
return
PARSE_DATA["last_tag_data"] += data
- def on_end_element(name):
+ def on_end_element(name) -> None:
if name != "binary":
return
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 d96312892..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
@@ -22,7 +22,7 @@
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)
@@ -39,18 +39,18 @@ def do(file_name, output_dir="output", debug=True):
}
class BinaryHandler(xml.sax.ContentHandler):
- def startElement(self, name, attrs):
+ 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):
+ def characters(self, content) -> None:
if PARSE_DATA["last_start_tag"] != "binary":
return
PARSE_DATA["last_tag_data"] += content
- def endElement(self, name):
+ def endElement(self, name) -> None:
if name != "binary":
return
diff --git a/fb2__parsing/extract_pictures_from_fb2/time_test.py b/fb2__parsing/extract_pictures_from_fb2/time_test.py
index 4c9e3cb36..8f1040d7b 100644
--- a/fb2__parsing/extract_pictures_from_fb2/time_test.py
+++ b/fb2__parsing/extract_pictures_from_fb2/time_test.py
@@ -7,22 +7,26 @@
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
+count = 20
runs = [
- ("LXML", "do_lxml"),
- ("XML EXPAT", "do_xml_expat"),
- ("XML.ETREE", "do_xml_etree"),
- ("XML SAX", "do_xml_sax"),
+ # ("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"),
diff --git a/fb2__parsing/get_sections.py b/fb2__parsing/get_sections.py
index ec6be26b7..fdf63a009 100644
--- a/fb2__parsing/get_sections.py
+++ b/fb2__parsing/get_sections.py
@@ -9,7 +9,7 @@
def get_sections_as_dict(root) -> dict[str, dict]:
# Рекурсивная функция поиска
- def _find_sections(root, root_dict: dict):
+ def _find_sections(root, root_dict: dict) -> None:
for section in root.find_all("section", recursive=False):
title = section.title.text.strip()
children = dict()
@@ -29,7 +29,7 @@ def _find_sections(root, root_dict: dict):
def get_sections_as_list(root) -> list[tuple[str, list]]:
# Рекурсивная функция поиска
- def _find_sections(root, children_list: list):
+ def _find_sections(root, children_list: list) -> None:
for section in root.find_all("section", recursive=False):
title = section.title.text.strip()
children = []
@@ -51,7 +51,7 @@ def _find_sections(root, children_list: list):
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", ". "))
if children:
diff --git a/fb2__parsing/print_section_by_text_number_length.py b/fb2__parsing/print_section_by_text_number_length.py
index 1851f3446..54a691d8e 100644
--- a/fb2__parsing/print_section_by_text_number_length.py
+++ b/fb2__parsing/print_section_by_text_number_length.py
@@ -9,7 +9,7 @@
def get_sections_as_dict(root) -> tuple[dict[str, dict], dict[str, Tag]]:
# Рекурсивная функция поиска
- def _find_sections(root, root_dict: dict, title_by_section: dict):
+ 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 = dict()
@@ -33,7 +33,7 @@ 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):
+ 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)
@@ -64,8 +64,8 @@ def _print_sections(
section_by_text: dict,
number_length_book_text,
level=1
- ):
- def _find_section_lines(root, level, lines: list):
+ ) -> None:
+ def _find_section_lines(root, level, lines: list) -> None:
for title, children in root.items():
text = "{}{}".format(" " * (level - 1), title.replace("\n", ". "))
diff --git a/file/append.py b/file/append.py
index e17641cc1..76c047a82 100644
--- a/file/append.py
+++ b/file/append.py
@@ -1,3 +1,6 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
__author__ = "ipetrash"
@@ -9,12 +12,12 @@
# Открыть файл в режиме добавления записей
-with open("foo.txt", mode="a") as f:
+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:
+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 47a3adc4b..000000000
--- a/file/foo.py
+++ /dev/null
@@ -1,6 +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 18d28bc8f..61ac67c1d 100644
--- a/file/read.py
+++ b/file/read.py
@@ -1,16 +1,20 @@
+#!/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
# Открыть файл в режиме чтения
-with open("foo.txt", mode="r") as f:
+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="")
+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 e75e1a87e..c74bcbb19 100644
--- a/file/seek.py
+++ b/file/seek.py
@@ -4,7 +4,7 @@
__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(f"{i}.")
diff --git a/file/write.py b/file/write.py
index c5f25069d..5c4fb7898 100644
--- a/file/write.py
+++ b/file/write.py
@@ -1,3 +1,6 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
__author__ = "ipetrash"
@@ -6,7 +9,7 @@
# Открыть файл в режиме записи
-with open("foo.txt", mode="w") as f:
+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")
diff --git a/file_tree_maker.py b/file_tree_maker.py
index eb6561044..67fbab1cd 100644
--- a/file_tree_maker.py
+++ b/file_tree_maker.py
@@ -13,7 +13,7 @@
class FileTreeMaker:
- def _recurse(self, parent_path, file_list, prefix, output_buf, level):
+ 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:
diff --git a/firefox/api.py b/firefox/api.py
index 2cd9f4deb..5e509a032 100644
--- a/firefox/api.py
+++ b/firefox/api.py
@@ -38,7 +38,7 @@ def close_tabs(
file_name_session: Path,
urls: list[str],
log: logging.Logger,
-):
+) -> None:
modified = False
json_data = get_sessionstore(file_name_session)
@@ -61,7 +61,7 @@ def close_tabs(
def close_duplicate_tabs(
file_name_session: Path,
log: logging.Logger,
-):
+) -> None:
json_data = get_sessionstore(file_name_session)
urls = get_tab_urls_from_sessionstore(json_data)
@@ -99,7 +99,7 @@ def close_bookmarks(
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 = ?)"
diff --git a/firefox/jsonlz4_mozLz4/mozlz4a.py b/firefox/jsonlz4_mozLz4/mozlz4a.py
index 128014ee3..bbee46ea8 100644
--- a/firefox/jsonlz4_mozLz4/mozlz4a.py
+++ b/firefox/jsonlz4_mozLz4/mozlz4a.py
@@ -79,7 +79,7 @@ def loads_json(file_obj: BinaryIO) -> dict:
return json.loads(data)
-def dumps_json(file_obj: BinaryIO, json_data: dict):
+def dumps_json(file_obj: BinaryIO, json_data: dict) -> None:
data = json.dumps(json_data).encode("utf-8")
compressed = compress_data(data)
diff --git a/firefox/jsonlz4_mozLz4/test/test.py b/firefox/jsonlz4_mozLz4/test/test.py
index a65eed66f..2179a9864 100644
--- a/firefox/jsonlz4_mozLz4/test/test.py
+++ b/firefox/jsonlz4_mozLz4/test/test.py
@@ -22,11 +22,11 @@
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):
+ def test_decompress_compress(self) -> None:
with open(FILE_TEST, "rb") as f:
expected_data = mozlz4a.decompress(f)
@@ -38,7 +38,7 @@ def test_decompress_compress(self):
self.assertEqual(expected_data, data)
- def test_compress_decompress_data(self):
+ def test_compress_decompress_data(self) -> None:
expected_data = str(uuid.uuid4()).encode("utf-8")
compressed_data = mozlz4a.compress_data(expected_data)
@@ -46,7 +46,7 @@ def test_compress_decompress_data(self):
self.assertEqual(expected_data, data)
- def test_json(self):
+ def test_json(self) -> None:
with open(FILE_TEST, "rb") as f:
expected_json_data = mozlz4a.loads_json(f)
diff --git a/flask-paginate__example/app.py b/flask-paginate__example/app.py
index 73d99425f..57ed5fd7c 100644
--- a/flask-paginate__example/app.py
+++ b/flask-paginate__example/app.py
@@ -17,14 +17,14 @@
@app.before_request
-def before_request():
+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):
+def teardown(error) -> None:
if hasattr(g, "conn"):
g.conn.close()
@@ -135,7 +135,7 @@ def get_pagination(**kwargs):
@click.command()
@click.option("--port", "-p", default=5000, help="listening port")
-def run(port):
+def run(port) -> None:
app.run(debug=True, port=port)
diff --git a/flask-paginate__example/sql.py b/flask-paginate__example/sql.py
index 6364baef1..21fc60f4b 100644
--- a/flask-paginate__example/sql.py
+++ b/flask-paginate__example/sql.py
@@ -16,12 +16,12 @@
@click.group()
-def cli():
+def cli() -> None:
pass
@cli.command(short_help="initialize database and tables")
-def init_db():
+def init_db() -> None:
conn = sqlite3.connect("test.db")
cur = conn.cursor()
cur.execute(sql)
@@ -31,7 +31,7 @@ def init_db():
@cli.command(short_help="fill records to database")
@click.option("--total", "-t", default=300, help="fill data for example")
-def fill_data(total):
+def fill_data(total) -> None:
conn = sqlite3.connect("test.db")
cur = conn.cursor()
for i in range(total):
diff --git a/flask__webservers/cors__hello_world.py b/flask__webservers/cors__hello_world.py
index 533425d6f..f771cddde 100644
--- a/flask__webservers/cors__hello_world.py
+++ b/flask__webservers/cors__hello_world.py
@@ -16,7 +16,7 @@
@app.route("/")
-def index():
+def index() -> str:
return "Hello World!"
diff --git a/flask__webservers/custom_error_page/main.py b/flask__webservers/custom_error_page/main.py
index 70626a955..64c67879b 100644
--- a/flask__webservers/custom_error_page/main.py
+++ b/flask__webservers/custom_error_page/main.py
@@ -39,7 +39,7 @@ def index():
@app.route("/500")
-def do_500():
+def do_500() -> None:
1/0
diff --git a/flask__webservers/flask_debugtoolbar__examples/app.py b/flask__webservers/flask_debugtoolbar__examples/app.py
index 567468709..85a51fbea 100644
--- a/flask__webservers/flask_debugtoolbar__examples/app.py
+++ b/flask__webservers/flask_debugtoolbar__examples/app.py
@@ -28,7 +28,7 @@ class ExampleModel(db.Model):
@app.before_first_request
-def setup():
+def setup() -> None:
db.create_all()
diff --git a/flask__webservers/flask_debugtoolbar__examples/hello_world.py b/flask__webservers/flask_debugtoolbar__examples/hello_world.py
index 58fbad365..d6846fb15 100644
--- a/flask__webservers/flask_debugtoolbar__examples/hello_world.py
+++ b/flask__webservers/flask_debugtoolbar__examples/hello_world.py
@@ -17,7 +17,7 @@
@app.route("/")
-def index():
+def index() -> str:
# NOTE: Need tab body: "Could not insert debug toolbar. tag not found in response."
return "Hello World!"
diff --git a/flask__webservers/get_URL__parameter_argument_query.py b/flask__webservers/get_URL__parameter_argument_query.py
index 2038e8ad8..1145a004d 100644
--- a/flask__webservers/get_URL__parameter_argument_query.py
+++ b/flask__webservers/get_URL__parameter_argument_query.py
@@ -13,7 +13,7 @@
@app.route("/")
-def index():
+def index() -> str:
return """
diff --git a/flask__webservers/get_upload_and_image_process/main.py b/flask__webservers/get_upload_and_image_process/main.py
index a62f2f7bc..6c7b48f64 100644
--- a/flask__webservers/get_upload_and_image_process/main.py
+++ b/flask__webservers/get_upload_and_image_process/main.py
@@ -35,7 +35,7 @@
# 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:
@@ -72,12 +72,12 @@ def img_to_base64_html(file_name__or__bytes__or__file_object):
LAST_IMAGE = "last_image.jpg"
-def save_last_image(file_data):
+def save_last_image(file_data) -> None:
with open(LAST_IMAGE, "wb") as f:
f.write(file_data)
-def load_last_image():
+def load_last_image() -> bytes:
with open(LAST_IMAGE, "rb") as f:
return f.read()
diff --git a/flask__webservers/get_upload_image_info/main.py b/flask__webservers/get_upload_image_info/main.py
index 20f5cc492..b1a2edacb 100644
--- a/flask__webservers/get_upload_image_info/main.py
+++ b/flask__webservers/get_upload_image_info/main.py
@@ -85,7 +85,7 @@ 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:
diff --git a/flask__webservers/hello_world.py b/flask__webservers/hello_world.py
index 0c8f7ba98..90f9c84e3 100644
--- a/flask__webservers/hello_world.py
+++ b/flask__webservers/hello_world.py
@@ -14,7 +14,7 @@
@app.route("/")
-def index():
+def index() -> str:
return "Hello World!"
diff --git a/flask__webservers/logging__examples/main_new.py b/flask__webservers/logging__examples/main_new.py
index 0d6d4809a..8000e0e63 100644
--- a/flask__webservers/logging__examples/main_new.py
+++ b/flask__webservers/logging__examples/main_new.py
@@ -41,7 +41,7 @@
@app.route("/")
-def index():
+def index() -> str:
log.debug("call index")
return "Hello World!"
diff --git a/flask__webservers/logging__examples/main_old.py b/flask__webservers/logging__examples/main_old.py
index fa9b5c5f8..e5bdab429 100644
--- a/flask__webservers/logging__examples/main_old.py
+++ b/flask__webservers/logging__examples/main_old.py
@@ -43,7 +43,7 @@
@app.route("/")
-def index():
+def index() -> str:
log.debug("call index")
return "Hello World!"
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
index 4602df5a8..6c2e83aec 100644
--- a/flask__webservers/logging__remove_date_from_werkzeug_logs/main_new.py
+++ b/flask__webservers/logging__remove_date_from_werkzeug_logs/main_new.py
@@ -41,7 +41,7 @@
@app.route("/")
-def index():
+def index() -> str:
log.debug("call index")
return "Hello World!"
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
index 46cd6c1a3..7b7c003bb 100644
--- a/flask__webservers/logging__remove_date_from_werkzeug_logs/main_old.py
+++ b/flask__webservers/logging__remove_date_from_werkzeug_logs/main_old.py
@@ -47,7 +47,7 @@
@app.route("/")
-def index():
+def index() -> str:
log.debug("call index")
return "Hello World!"
diff --git a/flask__webservers/post_data.py b/flask__webservers/post_data.py
index d220702f3..6d22577a5 100644
--- a/flask__webservers/post_data.py
+++ b/flask__webservers/post_data.py
@@ -13,7 +13,7 @@
@app.route("/")
-def index():
+def index() -> str:
return """
diff --git a/flask__webservers/post_data__as_form.py b/flask__webservers/post_data__as_form.py
index 8cb12bb16..ab732dd71 100644
--- a/flask__webservers/post_data__as_form.py
+++ b/flask__webservers/post_data__as_form.py
@@ -13,7 +13,7 @@
@app.route("/")
-def index():
+def index() -> str:
return """
diff --git a/flask__webservers/post_data__as_json.py b/flask__webservers/post_data__as_json.py
index 5a312d5e3..3c44aa550 100644
--- a/flask__webservers/post_data__as_json.py
+++ b/flask__webservers/post_data__as_json.py
@@ -13,7 +13,7 @@
@app.route("/")
-def index():
+def index() -> str:
return """
diff --git a/flask__webservers/print_hex_post_data/main.py b/flask__webservers/print_hex_post_data/main.py
index f3c94f9b7..e8b699b97 100644
--- a/flask__webservers/print_hex_post_data/main.py
+++ b/flask__webservers/print_hex_post_data/main.py
@@ -16,7 +16,7 @@
@app.route("/", methods=["POST"])
-def index():
+def index() -> str:
data = request.data
print(binascii.hexlify(data), data)
diff --git a/flask__webservers/run_with_random_port/main.py b/flask__webservers/run_with_random_port/main.py
index 56f3411aa..f9a8d2d53 100644
--- a/flask__webservers/run_with_random_port/main.py
+++ b/flask__webservers/run_with_random_port/main.py
@@ -13,7 +13,7 @@
@app.route("/")
-def hello():
+def hello() -> str:
return "Hello, world! running on %s" % request.host
diff --git a/flask__webservers/server__handle_stdin__input.py b/flask__webservers/server__handle_stdin__input.py
index 3abaef6fa..288ff43e8 100644
--- a/flask__webservers/server__handle_stdin__input.py
+++ b/flask__webservers/server__handle_stdin__input.py
@@ -20,7 +20,7 @@
text = "Hello World!"
-def go():
+def go() -> None:
time.sleep(2)
print("\n")
diff --git a/flask__webservers/server_with_additional_command_thread/main.py b/flask__webservers/server_with_additional_command_thread/main.py
index 9b02595ba..fcb917e94 100644
--- a/flask__webservers/server_with_additional_command_thread/main.py
+++ b/flask__webservers/server_with_additional_command_thread/main.py
@@ -40,7 +40,7 @@ def img_search():
return "text: " + text
-def loop_command_function():
+def loop_command_function() -> None:
while True:
global EXECUTE_COMMAND, COMMAND_TEXT
diff --git a/flask__webservers/server_with_window_notification/main.py b/flask__webservers/server_with_window_notification/main.py
index 57feff913..c7a16d49f 100644
--- a/flask__webservers/server_with_window_notification/main.py
+++ b/flask__webservers/server_with_window_notification/main.py
@@ -23,7 +23,7 @@
logging.basicConfig(level=logging.DEBUG)
-def show(text):
+def show(text) -> None:
title = str(threading.current_thread())
run_in_thread(title, text, duration=20)
@@ -34,7 +34,7 @@ def index():
@app.route("/show_notification")
-def show_notification():
+def show_notification() -> str:
text = request.args.get("text")
print("text:", text)
diff --git a/flask__webservers/show_lunch_menu_from_email/main.py b/flask__webservers/show_lunch_menu_from_email/main.py
index a8635c7aa..6ebcd5323 100644
--- a/flask__webservers/show_lunch_menu_from_email/main.py
+++ b/flask__webservers/show_lunch_menu_from_email/main.py
@@ -67,7 +67,7 @@ def save_attachment(msg):
return file_name
-def add_lunch_email_info(msg, file_name):
+def add_lunch_email_info(msg, file_name) -> None:
"""
Функция для добавления в поле comments docx информации о письме.
diff --git a/flask__webservers/show_my_ip/main.py b/flask__webservers/show_my_ip/main.py
index 88f4f3558..8ff891a59 100644
--- a/flask__webservers/show_my_ip/main.py
+++ b/flask__webservers/show_my_ip/main.py
@@ -13,7 +13,7 @@
@app.route("/")
-def index():
+def index() -> str:
return f"""Your IPv4 Address Is: {request.remote_addr}"""
diff --git a/flask__webservers/simple_upload_server/main.py b/flask__webservers/simple_upload_server/main.py
index 0eeb582f9..2f5faf9aa 100644
--- a/flask__webservers/simple_upload_server/main.py
+++ b/flask__webservers/simple_upload_server/main.py
@@ -11,7 +11,7 @@
@app.route("/", methods=["POST"])
-def index():
+def index() -> str:
file = request.files["file"]
# file.save(file.filename)
# OR:
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 473254f09..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
@@ -10,7 +10,7 @@
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}%)"
@@ -19,7 +19,7 @@ def progress(count, block_size, total_size):
)
-def create_test_file():
+def create_test_file() -> None:
file_name = "uploads/bigfile"
if os.path.exists(file_name):
return
diff --git a/flask__webservers/url_shortener/db.py b/flask__webservers/url_shortener/db.py
index 37cfb91a8..2b8c3dc08 100644
--- a/flask__webservers/url_shortener/db.py
+++ b/flask__webservers/url_shortener/db.py
@@ -23,7 +23,7 @@ def generate_link_id(length: int = LENGTH_URL_ID) -> str:
class NotDefinedParameterException(Exception):
- def __init__(self, parameter_name: str):
+ def __init__(self, parameter_name: str) -> None:
self.parameter_name = parameter_name
text = f'Parameter "{self.parameter_name}" must be defined!'
@@ -73,7 +73,7 @@ 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__
@@ -89,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)
diff --git a/flask__webservers/users/main_with_db.py b/flask__webservers/users/main_with_db.py
index 1a8a7a961..7f8fb28af 100644
--- a/flask__webservers/users/main_with_db.py
+++ b/flask__webservers/users/main_with_db.py
@@ -207,7 +207,7 @@ def logout():
@app.route("/protected")
@flask_login.login_required
-def protected():
+def protected() -> str:
return f"Logged in as: {flask_login.current_user.username}"
diff --git a/flask__webservers/users/main_without_db.py b/flask__webservers/users/main_without_db.py
index a98b7223d..927caa1fd 100644
--- a/flask__webservers/users/main_without_db.py
+++ b/flask__webservers/users/main_without_db.py
@@ -119,7 +119,7 @@ def login():
@app.route("/protected")
@flask_login.login_required
-def protected():
+def protected() -> str:
return f"Logged in as: {flask_login.current_user.id}"
diff --git a/flask__websocket/ajax_vs_websocket/websocket/main.py b/flask__websocket/ajax_vs_websocket/websocket/main.py
index 34a2f922d..f39e0d5b1 100644
--- a/flask__websocket/ajax_vs_websocket/websocket/main.py
+++ b/flask__websocket/ajax_vs_websocket/websocket/main.py
@@ -40,7 +40,7 @@ def index():
@socketio.on("post_method", namespace="/test")
-def post_method(message):
+def post_method(message) -> None:
# print(message)
with lock:
diff --git a/flask__websocket/commands__websocket__flask-socketio/main.py b/flask__websocket/commands__websocket__flask-socketio/main.py
index 0eeecb564..4324325e4 100644
--- a/flask__websocket/commands__websocket__flask-socketio/main.py
+++ b/flask__websocket/commands__websocket__flask-socketio/main.py
@@ -27,7 +27,7 @@ def index():
@socketio.on("my_event", namespace="/test")
-def test_message(message):
+def test_message(message) -> None:
session["receive_count"] = session.get("receive_count", 0) + 1
print(message)
@@ -54,17 +54,17 @@ def test_message(message):
@socketio.on("my_ping", namespace="/test")
-def ping_pong():
+def ping_pong() -> None:
emit("my_pong")
@socketio.on("connect", namespace="/test")
-def test_connect():
+def test_connect() -> None:
emit("my_response", {"data": "Connected"})
@socketio.on("disconnect", namespace="/test")
-def test_disconnect():
+def test_disconnect() -> None:
print("Client disconnected", request.sid)
diff --git a/flask__websocket/sending_all_from_while_in_thread/main.py b/flask__websocket/sending_all_from_while_in_thread/main.py
index 814dd0a34..fce7334c0 100644
--- a/flask__websocket/sending_all_from_while_in_thread/main.py
+++ b/flask__websocket/sending_all_from_while_in_thread/main.py
@@ -25,7 +25,7 @@
socketio = SocketIO(app, async_mode=async_mode)
-def send_all_cycled():
+def send_all_cycled() -> None:
with app.app_context():
i = 0
while True:
@@ -50,7 +50,7 @@ def index():
@socketio.on("my_event")
-def test_message(message):
+def test_message(message) -> None:
session["receive_count"] = session.get("receive_count", 0) + 1
print(message)
@@ -63,18 +63,18 @@ def test_message(message):
@socketio.on("my_ping")
-def ping_pong():
+def ping_pong() -> None:
emit("my_pong")
@socketio.on("connect")
-def test_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():
+def test_disconnect() -> None:
print("Client disconnected", request.sid)
diff --git a/flask__websocket/simple_chat/main.py b/flask__websocket/simple_chat/main.py
index 3b39ff133..55928b994 100644
--- a/flask__websocket/simple_chat/main.py
+++ b/flask__websocket/simple_chat/main.py
@@ -26,7 +26,7 @@ def index():
@socketio.on("my_event", namespace="/test")
-def test_message(message):
+def test_message(message) -> None:
session["receive_count"] = session.get("receive_count", 0) + 1
print(message)
@@ -39,17 +39,17 @@ def test_message(message):
@socketio.on("my_ping", namespace="/test")
-def ping_pong():
+def ping_pong() -> None:
emit("my_pong")
@socketio.on("connect", namespace="/test")
-def test_connect():
+def test_connect() -> None:
emit("my_response", {"data": f"Connected!", "count": 0, "sid": request.sid})
@socketio.on("disconnect", namespace="/test")
-def test_disconnect():
+def test_disconnect() -> None:
print("Client disconnected", request.sid)
diff --git a/flask__websocket/websocket__flask-socketio_example/app.py b/flask__websocket/websocket__flask-socketio_example/app.py
index d66496e44..526556795 100644
--- a/flask__websocket/websocket__flask-socketio_example/app.py
+++ b/flask__websocket/websocket__flask-socketio_example/app.py
@@ -27,7 +27,7 @@
thread_lock = Lock()
-def background_thread():
+def background_thread() -> None:
"""Example of how to send server generated events to clients."""
count = 0
while True:
@@ -46,7 +46,7 @@ def index():
@socketio.on("my_event", namespace="/test")
-def test_message(message):
+def test_message(message) -> None:
session["receive_count"] = session.get("receive_count", 0) + 1
emit(
"my_response",
@@ -55,7 +55,7 @@ def test_message(message):
@socketio.on("my_broadcast_event", namespace="/test")
-def test_broadcast_message(message):
+def test_broadcast_message(message) -> None:
session["receive_count"] = session.get("receive_count", 0) + 1
emit(
"my_response",
@@ -65,7 +65,7 @@ def test_broadcast_message(message):
@socketio.on("join", namespace="/test")
-def join(message):
+def join(message) -> None:
join_room(message["room"])
session["receive_count"] = session.get("receive_count", 0) + 1
emit(
@@ -75,7 +75,7 @@ def join(message):
@socketio.on("leave", namespace="/test")
-def leave(message):
+def leave(message) -> None:
leave_room(message["room"])
session["receive_count"] = session.get("receive_count", 0) + 1
emit(
@@ -85,7 +85,7 @@ def leave(message):
@socketio.on("close_room", namespace="/test")
-def close(message):
+def close(message) -> None:
session["receive_count"] = session.get("receive_count", 0) + 1
emit(
"my_response",
@@ -99,7 +99,7 @@ def close(message):
@socketio.on("my_room_event", namespace="/test")
-def send_room_message(message):
+def send_room_message(message) -> None:
session["receive_count"] = session.get("receive_count", 0) + 1
emit(
"my_response",
@@ -109,9 +109,9 @@ def send_room_message(message):
@socketio.on("disconnect_request", namespace="/test")
-def disconnect_request():
+def disconnect_request() -> None:
@copy_current_request_context
- def can_disconnect():
+ def can_disconnect() -> None:
disconnect()
session["receive_count"] = session.get("receive_count", 0) + 1
@@ -126,12 +126,12 @@ def can_disconnect():
@socketio.on("my_ping", namespace="/test")
-def ping_pong():
+def ping_pong() -> None:
emit("my_pong")
@socketio.on("connect", namespace="/test")
-def test_connect():
+def test_connect() -> None:
global thread
with thread_lock:
if thread is None:
@@ -140,7 +140,7 @@ def test_connect():
@socketio.on("disconnect", namespace="/test")
-def test_disconnect():
+def test_disconnect() -> None:
print("Client disconnected", request.sid)
diff --git a/flask__websocket/websocket__flask-socketio_example/app_namespace.py b/flask__websocket/websocket__flask-socketio_example/app_namespace.py
index f7afbc120..8b8b9bf6d 100644
--- a/flask__websocket/websocket__flask-socketio_example/app_namespace.py
+++ b/flask__websocket/websocket__flask-socketio_example/app_namespace.py
@@ -27,7 +27,7 @@
thread_lock = Lock()
-def background_thread():
+def background_thread() -> None:
"""Example of how to send server generated events to clients."""
count = 0
while True:
@@ -46,14 +46,14 @@ def index():
class MyNamespace(Namespace):
- def on_my_event(self, message):
+ def on_my_event(self, message) -> None:
session["receive_count"] = session.get("receive_count", 0) + 1
emit(
"my_response",
{"data": message["data"], "count": session["receive_count"]},
)
- def on_my_broadcast_event(self, message):
+ def on_my_broadcast_event(self, message) -> None:
session["receive_count"] = session.get("receive_count", 0) + 1
emit(
"my_response",
@@ -61,7 +61,7 @@ def on_my_broadcast_event(self, message):
broadcast=True,
)
- def on_join(self, message):
+ def on_join(self, message) -> None:
join_room(message["room"])
session["receive_count"] = session.get("receive_count", 0) + 1
emit(
@@ -72,7 +72,7 @@ def on_join(self, message):
},
)
- def on_leave(self, message):
+ def on_leave(self, message) -> None:
leave_room(message["room"])
session["receive_count"] = session.get("receive_count", 0) + 1
emit(
@@ -83,7 +83,7 @@ def on_leave(self, message):
},
)
- def on_close_room(self, message):
+ def on_close_room(self, message) -> None:
session["receive_count"] = session.get("receive_count", 0) + 1
emit(
"my_response",
@@ -95,7 +95,7 @@ def on_close_room(self, message):
)
close_room(message["room"])
- def on_my_room_event(self, message):
+ def on_my_room_event(self, message) -> None:
session["receive_count"] = session.get("receive_count", 0) + 1
emit(
"my_response",
@@ -103,7 +103,7 @@ def on_my_room_event(self, message):
room=message["room"],
)
- def on_disconnect_request(self):
+ def on_disconnect_request(self) -> None:
session["receive_count"] = session.get("receive_count", 0) + 1
emit(
"my_response",
@@ -111,17 +111,17 @@ def on_disconnect_request(self):
)
disconnect()
- def on_my_ping(self):
+ def on_my_ping(self) -> None:
emit("my_pong")
- def on_connect(self):
+ def on_connect(self) -> None:
global thread
with thread_lock:
if thread is None:
thread = socketio.start_background_task(background_thread)
emit("my_response", {"data": "Connected", "count": 0})
- def on_disconnect(self):
+ def on_disconnect(self) -> None:
print("Client disconnected", request.sid)
diff --git a/flask__websocket/websocket__flask-socketio_example/requirements.txt b/flask__websocket/websocket__flask-socketio_example/requirements.txt
index 0831b730c..736c5fac4 100644
--- a/flask__websocket/websocket__flask-socketio_example/requirements.txt
+++ b/flask__websocket/websocket__flask-socketio_example/requirements.txt
@@ -8,4 +8,4 @@ MarkupSafe==1.1.0
python-engineio
python-socketio
six==1.11.0
-Werkzeug==3.0.3
+Werkzeug==3.1.6
diff --git a/flask__websocket/websocket__flask-socketio_example/sessions.py b/flask__websocket/websocket__flask-socketio_example/sessions.py
index 9920d6b83..882e6749c 100644
--- a/flask__websocket/websocket__flask-socketio_example/sessions.py
+++ b/flask__websocket/websocket__flask-socketio_example/sessions.py
@@ -13,7 +13,7 @@
class User(UserMixin, object):
- def __init__(self, id=None):
+ def __init__(self, id=None) -> None:
self.id = id
@@ -46,7 +46,7 @@ def session_access():
@socketio.on("get-session")
-def get_session():
+def get_session() -> None:
emit(
"refresh-session",
{
@@ -57,7 +57,7 @@ def get_session():
@socketio.on("set-session")
-def set_session(data):
+def set_session(data) -> None:
if "session" in data:
session["value"] = data["session"]
elif "user" in data:
diff --git a/flask_http_auth/http_digest_auth.py b/flask_http_auth/http_digest_auth.py
index db5c92ce8..c3eb060c9 100644
--- a/flask_http_auth/http_digest_auth.py
+++ b/flask_http_auth/http_digest_auth.py
@@ -34,7 +34,7 @@ def get_password(username: str) -> str | None:
@app.route("/")
@auth.login_required
-def index():
+def index() -> str:
return f"Hello, {auth.username()}!"
diff --git a/flask_restful__examples/todo/main.py b/flask_restful__examples/todo/main.py
index b1b3a647b..5ef9fa067 100644
--- a/flask_restful__examples/todo/main.py
+++ b/flask_restful__examples/todo/main.py
@@ -22,7 +22,7 @@
}
-def abort_if_todo_doesnt_exist(todo_id):
+def abort_if_todo_doesnt_exist(todo_id) -> None:
if todo_id not in TODOS:
abort(404, message=f"Todo {todo_id} doesn't exist")
diff --git "a/for_games/Bloodborne/bloodborne_fandom_com__ru__wiki__\320\221\320\276\321\201\321\201\321\213/dumps/bosses.json" "b/for_games/Bloodborne/bloodborne_fandom_com__ru__wiki__\320\221\320\276\321\201\321\201\321\213/dumps/bosses.json"
new file mode 100644
index 000000000..1ecc3f91f
--- /dev/null
+++ "b/for_games/Bloodborne/bloodborne_fandom_com__ru__wiki__\320\221\320\276\321\201\321\201\321\213/dumps/bosses.json"
@@ -0,0 +1,192 @@
+{
+ "ОСНОВНАЯ ИГРА. ОБЯЗАТЕЛЬНЫЕ БОССЫ": [
+ {
+ "name": "Отец Гаскойн",
+ "url": "https://bloodborne.fandom.com/ru/wiki/%D0%9E%D1%82%D0%B5%D1%86_%D0%93%D0%B0%D1%81%D0%BA%D0%BE%D0%B9%D0%BD"
+ },
+ {
+ "name": "Викарий Амелия",
+ "url": "https://bloodborne.fandom.com/ru/wiki/%D0%92%D0%B8%D0%BA%D0%B0%D1%80%D0%B8%D0%B9_%D0%90%D0%BC%D0%B5%D0%BB%D0%B8%D1%8F"
+ },
+ {
+ "name": "Тень Ярнама",
+ "url": "https://bloodborne.fandom.com/ru/wiki/%D0%A2%D0%B5%D0%BD%D1%8C_%D0%AF%D1%80%D0%BD%D0%B0%D0%BC%D0%B0"
+ },
+ {
+ "name": "Ром, праздный паук",
+ "url": "https://bloodborne.fandom.com/ru/wiki/%D0%A0%D0%BE%D0%BC,_%D0%BF%D1%80%D0%B0%D0%B7%D0%B4%D0%BD%D1%8B%D0%B9_%D0%BF%D0%B0%D1%83%D0%BA"
+ },
+ {
+ "name": "Возродившийся",
+ "url": "https://bloodborne.fandom.com/ru/wiki/%D0%92%D0%BE%D0%B7%D1%80%D0%BE%D0%B4%D0%B8%D0%B2%D1%88%D0%B8%D0%B9%D1%81%D1%8F"
+ },
+ {
+ "name": "Миколаш, Хозяин кошмара",
+ "url": "https://bloodborne.fandom.com/ru/wiki/%D0%9C%D0%B8%D0%BA%D0%BE%D0%BB%D0%B0%D1%88,_%D0%A5%D0%BE%D0%B7%D1%8F%D0%B8%D0%BD_%D0%BA%D0%BE%D1%88%D0%BC%D0%B0%D1%80%D0%B0"
+ },
+ {
+ "name": "Кормилица Мерго",
+ "url": "https://bloodborne.fandom.com/ru/wiki/%D0%9A%D0%BE%D1%80%D0%BC%D0%B8%D0%BB%D0%B8%D1%86%D0%B0_%D0%9C%D0%B5%D1%80%D0%B3%D0%BE"
+ }
+ ],
+ "ОСНОВНАЯ ИГРА. ОПЦИОНАЛЬНЫЕ БОССЫ": [
+ {
+ "name": "Церковное Чудовище",
+ "url": "https://bloodborne.fandom.com/ru/wiki/%D0%A6%D0%B5%D1%80%D0%BA%D0%BE%D0%B2%D0%BD%D0%BE%D0%B5_%D0%A7%D1%83%D0%B4%D0%BE%D0%B2%D0%B8%D1%89%D0%B5"
+ },
+ {
+ "name": "Посланник Небес",
+ "url": "https://bloodborne.fandom.com/ru/wiki/%D0%9F%D0%BE%D1%81%D0%BB%D0%B0%D0%BD%D0%BD%D0%B8%D0%BA_%D0%9D%D0%B5%D0%B1%D0%B5%D1%81"
+ },
+ {
+ "name": "Ибраитас, дочь Космоса",
+ "url": "https://bloodborne.fandom.com/ru/wiki/%D0%98%D0%B1%D1%80%D0%B0%D0%B8%D1%82%D0%B0%D1%81,_%D0%B4%D0%BE%D1%87%D1%8C_%D0%9A%D0%BE%D1%81%D0%BC%D0%BE%D1%81%D0%B0"
+ },
+ {
+ "name": "Чудовище-кровоглот",
+ "url": "https://bloodborne.fandom.com/ru/wiki/%D0%A7%D1%83%D0%B4%D0%BE%D0%B2%D0%B8%D1%89%D0%B5-%D0%BA%D1%80%D0%BE%D0%B2%D0%BE%D0%B3%D0%BB%D0%BE%D1%82"
+ },
+ {
+ "name": "Черное чудовище Паарл",
+ "url": "https://bloodborne.fandom.com/ru/wiki/%D0%A7%D0%B5%D1%80%D0%BD%D0%BE%D0%B5_%D1%87%D1%83%D0%B4%D0%BE%D0%B2%D0%B8%D1%89%D0%B5_%D0%9F%D0%B0%D0%B0%D1%80%D0%BB"
+ },
+ {
+ "name": "Ведьма Хемвика",
+ "url": "https://bloodborne.fandom.com/ru/wiki/%D0%92%D0%B5%D0%B4%D1%8C%D0%BC%D0%B0_%D0%A5%D0%B5%D0%BC%D0%B2%D0%B8%D0%BA%D0%B0"
+ },
+ {
+ "name": "Мученик Логариус",
+ "url": "https://bloodborne.fandom.com/ru/wiki/%D0%9C%D1%83%D1%87%D0%B5%D0%BD%D0%B8%D0%BA_%D0%9B%D0%BE%D0%B3%D0%B0%D1%80%D0%B8%D1%83%D1%81"
+ },
+ {
+ "name": "Амигдала",
+ "url": "https://bloodborne.fandom.com/ru/wiki/%D0%90%D0%BC%D0%B8%D0%B3%D0%B4%D0%B0%D0%BB%D0%B0"
+ },
+ {
+ "name": "Герман, первый охотник",
+ "url": "https://bloodborne.fandom.com/ru/wiki/%D0%93%D0%B5%D1%80%D0%BC%D0%B0%D0%BD,_%D0%BF%D0%B5%D1%80%D0%B2%D1%8B%D0%B9_%D0%BE%D1%85%D0%BE%D1%82%D0%BD%D0%B8%D0%BA"
+ },
+ {
+ "name": "Присутствие луны",
+ "url": "https://bloodborne.fandom.com/ru/wiki/%D0%9F%D1%80%D0%B8%D1%81%D1%83%D1%82%D1%81%D1%82%D0%B2%D0%B8%D0%B5_%D0%BB%D1%83%D0%BD%D1%8B"
+ }
+ ],
+ "ДОПОЛНЕНИЕ THE OLD HUNTERS": [
+ {
+ "name": "Людвиг",
+ "url": "https://bloodborne.fandom.com/ru/wiki/%D0%9B%D1%8E%D0%B4%D0%B2%D0%B8%D0%B3"
+ },
+ {
+ "name": "Лоуренс, первый викарий",
+ "url": "https://bloodborne.fandom.com/ru/wiki/%D0%9B%D0%BE%D1%83%D1%80%D0%B5%D0%BD%D1%81,_%D0%BF%D0%B5%D1%80%D0%B2%D1%8B%D0%B9_%D0%B2%D0%B8%D0%BA%D0%B0%D1%80%D0%B8%D0%B9"
+ },
+ {
+ "name": "Живые неудачи",
+ "url": "https://bloodborne.fandom.com/ru/wiki/%D0%96%D0%B8%D0%B2%D1%8B%D0%B5_%D0%BD%D0%B5%D1%83%D0%B4%D0%B0%D1%87%D0%B8"
+ },
+ {
+ "name": "Леди Мария из Астральной часовой башни",
+ "url": "https://bloodborne.fandom.com/ru/wiki/%D0%9B%D0%B5%D0%B4%D0%B8_%D0%9C%D0%B0%D1%80%D0%B8%D1%8F_%D0%B8%D0%B7_%D0%90%D1%81%D1%82%D1%80%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B9_%D1%87%D0%B0%D1%81%D0%BE%D0%B2%D0%BE%D0%B9_%D0%B1%D0%B0%D1%88%D0%BD%D0%B8"
+ },
+ {
+ "name": "Сирота Кос",
+ "url": "https://bloodborne.fandom.com/ru/wiki/%D0%A1%D0%B8%D1%80%D0%BE%D1%82%D0%B0_%D0%9A%D0%BE%D1%81"
+ }
+ ],
+ "ОБЛАСТЬ ПТУМЕРУ": [
+ {
+ "name": "Мертвый гигант",
+ "url": "https://bloodborne.fandom.com/ru/wiki/%D0%9C%D0%B5%D1%80%D1%82%D0%B2%D1%8B%D0%B9_%D0%B3%D0%B8%D0%B3%D0%B0%D0%BD%D1%82"
+ },
+ {
+ "name": "Беспощадные Хранители",
+ "url": "https://bloodborne.fandom.com/ru/wiki/%D0%91%D0%B5%D1%81%D0%BF%D0%BE%D1%89%D0%B0%D0%B4%D0%BD%D1%8B%D0%B5_%D0%A5%D1%80%D0%B0%D0%BD%D0%B8%D1%82%D0%B5%D0%BB%D0%B8"
+ },
+ {
+ "name": "Сторожевой пес Древних Богов",
+ "url": "https://bloodborne.fandom.com/ru/wiki/%D0%A1%D1%82%D0%BE%D1%80%D0%BE%D0%B6%D0%B5%D0%B2%D0%BE%D0%B9_%D0%BF%D0%B5%D1%81_%D0%94%D1%80%D0%B5%D0%B2%D0%BD%D0%B8%D1%85_%D0%91%D0%BE%D0%B3%D0%BE%D0%B2"
+ },
+ {
+ "name": "Душа, одержимая чудовищем",
+ "url": "https://bloodborne.fandom.com/ru/wiki/%D0%94%D1%83%D1%88%D0%B0,_%D0%BE%D0%B4%D0%B5%D1%80%D0%B6%D0%B8%D0%BC%D0%B0%D1%8F_%D1%87%D1%83%D0%B4%D0%BE%D0%B2%D0%B8%D1%89%D0%B5%D0%BC"
+ },
+ {
+ "name": "Хранитель Древних Богов",
+ "url": "https://bloodborne.fandom.com/ru/wiki/%D0%A5%D1%80%D0%B0%D0%BD%D0%B8%D1%82%D0%B5%D0%BB%D1%8C_%D0%94%D1%80%D0%B5%D0%B2%D0%BD%D0%B8%D1%85_%D0%91%D0%BE%D0%B3%D0%BE%D0%B2"
+ },
+ {
+ "name": "Потомок птумериан",
+ "url": "https://bloodborne.fandom.com/ru/wiki/%D0%9F%D0%BE%D1%82%D0%BE%D0%BC%D0%BE%D0%BA_%D0%BF%D1%82%D1%83%D0%BC%D0%B5%D1%80%D0%B8%D0%B0%D0%BD"
+ },
+ {
+ "name": "Ром, праздный паук",
+ "url": "https://bloodborne.fandom.com/ru/wiki/%D0%A0%D0%BE%D0%BC,_%D0%BF%D1%80%D0%B0%D0%B7%D0%B4%D0%BD%D1%8B%D0%B9_%D0%BF%D0%B0%D1%83%D0%BA"
+ },
+ {
+ "name": "Чудовище-кровопуск",
+ "url": "https://bloodborne.fandom.com/ru/wiki/%D0%A7%D1%83%D0%B4%D0%BE%D0%B2%D0%B8%D1%89%D0%B5-%D0%BA%D1%80%D0%BE%D0%B2%D0%BE%D0%BF%D1%83%D1%81%D0%BA"
+ },
+ {
+ "name": "Амигдала",
+ "url": "https://bloodborne.fandom.com/ru/wiki/%D0%90%D0%BC%D0%B8%D0%B3%D0%B4%D0%B0%D0%BB%D0%B0"
+ },
+ {
+ "name": "Ярнам, Птумерианская королева",
+ "url": "https://bloodborne.fandom.com/ru/wiki/%D0%AF%D1%80%D0%BD%D0%B0%D0%BC,_%D0%9F%D1%82%D1%83%D0%BC%D0%B5%D1%80%D0%B8%D0%B0%D0%BD%D1%81%D0%BA%D0%B0%D1%8F_%D0%BA%D0%BE%D1%80%D0%BE%D0%BB%D0%B5%D0%B2%D0%B0"
+ }
+ ],
+ "ОБЛАСТЬ ДАЛЬНИХ ГРОБНИЦ": [
+ {
+ "name": "Вепрь-людоед",
+ "url": "https://bloodborne.fandom.com/ru/wiki/%D0%92%D0%B5%D0%BF%D1%80%D1%8C-%D0%BB%D1%8E%D0%B4%D0%BE%D0%B5%D0%B4"
+ },
+ {
+ "name": "Чудовище-кровоглот",
+ "url": "https://bloodborne.fandom.com/ru/wiki/%D0%A7%D1%83%D0%B4%D0%BE%D0%B2%D0%B8%D1%89%D0%B5-%D0%BA%D1%80%D0%BE%D0%B2%D0%BE%D0%B3%D0%BB%D0%BE%D1%82"
+ },
+ {
+ "name": "Мозгосос",
+ "url": "https://bloodborne.fandom.com/ru/wiki/%D0%9C%D0%BE%D0%B7%D0%B3%D0%BE%D1%81%D0%BE%D1%81"
+ },
+ {
+ "name": "Забытый безумец",
+ "url": "https://bloodborne.fandom.com/ru/wiki/%D0%97%D0%B0%D0%B1%D1%8B%D1%82%D1%8B%D0%B9_%D0%B1%D0%B5%D0%B7%D1%83%D0%BC%D0%B5%D1%86"
+ },
+ {
+ "name": "Птумерианский Старейшина",
+ "url": "https://bloodborne.fandom.com/ru/wiki/%D0%9F%D1%82%D1%83%D0%BC%D0%B5%D1%80%D0%B8%D0%B0%D0%BD%D1%81%D0%BA%D0%B8%D0%B9_%D0%A1%D1%82%D0%B0%D1%80%D0%B5%D0%B9%D1%88%D0%B8%D0%BD%D0%B0"
+ }
+ ],
+ "ОБЛАСТЬ ЛОРАНА": [
+ {
+ "name": "Чудовище-кровоглот",
+ "url": "https://bloodborne.fandom.com/ru/wiki/%D0%A7%D1%83%D0%B4%D0%BE%D0%B2%D0%B8%D1%89%D0%B5-%D0%BA%D1%80%D0%BE%D0%B2%D0%BE%D0%B3%D0%BB%D0%BE%D1%82"
+ },
+ {
+ "name": "Отвратительное чудовище",
+ "url": "https://bloodborne.fandom.com/ru/wiki/%D0%9E%D1%82%D0%B2%D1%80%D0%B0%D1%82%D0%B8%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D0%BE%D0%B5_%D1%87%D1%83%D0%B4%D0%BE%D0%B2%D0%B8%D1%89%D0%B5"
+ },
+ {
+ "name": "Серебряное чудовище Лорана",
+ "url": "https://bloodborne.fandom.com/ru/wiki/%D0%A1%D0%B5%D1%80%D0%B5%D0%B1%D1%80%D1%8F%D0%BD%D0%BE%D0%B5_%D1%87%D1%83%D0%B4%D0%BE%D0%B2%D0%B8%D1%89%D0%B5_%D0%9B%D0%BE%D1%80%D0%B0%D0%BD%D0%B0"
+ },
+ {
+ "name": "Черное чудовище Паарл",
+ "url": "https://bloodborne.fandom.com/ru/wiki/%D0%A7%D0%B5%D1%80%D0%BD%D0%BE%D0%B5_%D1%87%D1%83%D0%B4%D0%BE%D0%B2%D0%B8%D1%89%D0%B5_%D0%9F%D0%B0%D0%B0%D1%80%D0%BB"
+ }
+ ],
+ "ОБЛАСТЬ ИСЗА": [
+ {
+ "name": "Мозгосос",
+ "url": "https://bloodborne.fandom.com/ru/wiki/%D0%9C%D0%BE%D0%B7%D0%B3%D0%BE%D1%81%D0%BE%D1%81"
+ },
+ {
+ "name": "Посланник Небес",
+ "url": "https://bloodborne.fandom.com/ru/wiki/%D0%9F%D0%BE%D1%81%D0%BB%D0%B0%D0%BD%D0%BD%D0%B8%D0%BA_%D0%9D%D0%B5%D0%B1%D0%B5%D1%81"
+ },
+ {
+ "name": "Ибраитас, дочь Космоса",
+ "url": "https://bloodborne.fandom.com/ru/wiki/%D0%98%D0%B1%D1%80%D0%B0%D0%B8%D1%82%D0%B0%D1%81,_%D0%B4%D0%BE%D1%87%D1%8C_%D0%9A%D0%BE%D1%81%D0%BC%D0%BE%D1%81%D0%B0"
+ }
+ ]
+}
\ No newline at end of file
diff --git "a/for_games/Bloodborne/bloodborne_fandom_com__ru__wiki__\320\221\320\276\321\201\321\201\321\213/dumps/bosses__only_name.json" "b/for_games/Bloodborne/bloodborne_fandom_com__ru__wiki__\320\221\320\276\321\201\321\201\321\213/dumps/bosses__only_name.json"
new file mode 100644
index 000000000..5bc4c2ef5
--- /dev/null
+++ "b/for_games/Bloodborne/bloodborne_fandom_com__ru__wiki__\320\221\320\276\321\201\321\201\321\213/dumps/bosses__only_name.json"
@@ -0,0 +1,60 @@
+{
+ "ОСНОВНАЯ ИГРА. ОБЯЗАТЕЛЬНЫЕ БОССЫ": [
+ "Отец Гаскойн",
+ "Викарий Амелия",
+ "Тень Ярнама",
+ "Ром, праздный паук",
+ "Возродившийся",
+ "Миколаш, Хозяин кошмара",
+ "Кормилица Мерго"
+ ],
+ "ОСНОВНАЯ ИГРА. ОПЦИОНАЛЬНЫЕ БОССЫ": [
+ "Церковное Чудовище",
+ "Посланник Небес",
+ "Ибраитас, дочь Космоса",
+ "Чудовище-кровоглот",
+ "Черное чудовище Паарл",
+ "Ведьма Хемвика",
+ "Мученик Логариус",
+ "Амигдала",
+ "Герман, первый охотник",
+ "Присутствие луны"
+ ],
+ "ДОПОЛНЕНИЕ THE OLD HUNTERS": [
+ "Людвиг",
+ "Лоуренс, первый викарий",
+ "Живые неудачи",
+ "Леди Мария из Астральной часовой башни",
+ "Сирота Кос"
+ ],
+ "ОБЛАСТЬ ПТУМЕРУ": [
+ "Мертвый гигант",
+ "Беспощадные Хранители",
+ "Сторожевой пес Древних Богов",
+ "Душа, одержимая чудовищем",
+ "Хранитель Древних Богов",
+ "Потомок птумериан",
+ "Ром, праздный паук",
+ "Чудовище-кровопуск",
+ "Амигдала",
+ "Ярнам, Птумерианская королева"
+ ],
+ "ОБЛАСТЬ ДАЛЬНИХ ГРОБНИЦ": [
+ "Вепрь-людоед",
+ "Чудовище-кровоглот",
+ "Мозгосос",
+ "Забытый безумец",
+ "Птумерианский Старейшина"
+ ],
+ "ОБЛАСТЬ ЛОРАНА": [
+ "Чудовище-кровоглот",
+ "Отвратительное чудовище",
+ "Серебряное чудовище Лорана",
+ "Черное чудовище Паарл"
+ ],
+ "ОБЛАСТЬ ИСЗА": [
+ "Мозгосос",
+ "Посланник Небес",
+ "Ибраитас, дочь Космоса"
+ ]
+}
\ No newline at end of file
diff --git "a/for_games/Bloodborne/bloodborne_fandom_com__ru__wiki__\320\221\320\276\321\201\321\201\321\213/dumps/bosses__only_name.txt" "b/for_games/Bloodborne/bloodborne_fandom_com__ru__wiki__\320\221\320\276\321\201\321\201\321\213/dumps/bosses__only_name.txt"
new file mode 100644
index 000000000..9c5dcfc04
--- /dev/null
+++ "b/for_games/Bloodborne/bloodborne_fandom_com__ru__wiki__\320\221\320\276\321\201\321\201\321\213/dumps/bosses__only_name.txt"
@@ -0,0 +1,57 @@
+ОСНОВНАЯ ИГРА. ОБЯЗАТЕЛЬНЫЕ БОССЫ
+ Отец Гаскойн
+ Викарий Амелия
+ Тень Ярнама
+ Ром, праздный паук
+ Возродившийся
+ Миколаш, Хозяин кошмара
+ Кормилица Мерго
+
+ОСНОВНАЯ ИГРА. ОПЦИОНАЛЬНЫЕ БОССЫ
+ Церковное Чудовище
+ Посланник Небес
+ Ибраитас, дочь Космоса
+ Чудовище-кровоглот
+ Черное чудовище Паарл
+ Ведьма Хемвика
+ Мученик Логариус
+ Амигдала
+ Герман, первый охотник
+ Присутствие луны
+
+ДОПОЛНЕНИЕ THE OLD HUNTERS
+ Людвиг
+ Лоуренс, первый викарий
+ Живые неудачи
+ Леди Мария из Астральной часовой башни
+ Сирота Кос
+
+ОБЛАСТЬ ПТУМЕРУ
+ Мертвый гигант
+ Беспощадные Хранители
+ Сторожевой пес Древних Богов
+ Душа, одержимая чудовищем
+ Хранитель Древних Богов
+ Потомок птумериан
+ Ром, праздный паук
+ Чудовище-кровопуск
+ Амигдала
+ Ярнам, Птумерианская королева
+
+ОБЛАСТЬ ДАЛЬНИХ ГРОБНИЦ
+ Вепрь-людоед
+ Чудовище-кровоглот
+ Мозгосос
+ Забытый безумец
+ Птумерианский Старейшина
+
+ОБЛАСТЬ ЛОРАНА
+ Чудовище-кровоглот
+ Отвратительное чудовище
+ Серебряное чудовище Лорана
+ Черное чудовище Паарл
+
+ОБЛАСТЬ ИСЗА
+ Мозгосос
+ Посланник Небес
+ Ибраитас, дочь Космоса
diff --git "a/for_games/Bloodborne/bloodborne_fandom_com__ru__wiki__\320\221\320\276\321\201\321\201\321\213/main.py" "b/for_games/Bloodborne/bloodborne_fandom_com__ru__wiki__\320\221\320\276\321\201\321\201\321\213/main.py"
new file mode 100644
index 000000000..a84dade8d
--- /dev/null
+++ "b/for_games/Bloodborne/bloodborne_fandom_com__ru__wiki__\320\221\320\276\321\201\321\201\321\213/main.py"
@@ -0,0 +1,183 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+__author__ = "ipetrash"
+
+
+import json
+
+from collections import defaultdict
+from dataclasses import dataclass, asdict
+from pathlib import Path
+from urllib.parse import urljoin
+
+from bs4 import BeautifulSoup
+import requests
+
+
+@dataclass
+class Boss:
+ name: str
+ url: str
+
+
+URL: str = "https://bloodborne.fandom.com/ru/wiki/Боссы"
+
+
+def get_bosses() -> dict[str, list[Boss]]:
+ rs = requests.get(URL)
+ rs.raise_for_status()
+
+ soup = BeautifulSoup(rs.content, "html.parser")
+
+ bosses_by_category: dict[str, list[Boss]] = defaultdict(list)
+ category_name: str | None = None
+
+ for tr in soup.select("table.article-table tr"):
+ # Заголовок первым идет
+ th = tr.select_one("th")
+ if th:
+ category_name = th.text.strip().upper()
+ continue
+
+ if not category_name:
+ continue
+
+ bosses_by_category[category_name] += [
+ Boss(
+ name=a["title"],
+ url=urljoin(rs.url, a["href"]),
+ )
+ for a in tr.select("td > figure > a[href][title]")
+ ]
+
+ return bosses_by_category
+
+
+def print_bosses(bosses: dict[str, list[Boss]]) -> None:
+ total = sum(len(i) for i in bosses.values())
+ print(f"Боссы ({total}):")
+
+ for category, bosses in bosses.items():
+ print(f"{category} ({len(bosses)}):")
+
+ for i, boss in enumerate(bosses, 1):
+ print(f' {i}. "{boss.name}": {boss.url}')
+
+ print()
+
+
+def convert_bosses_to_only_name(bosses: dict[str, list[Boss]]) -> dict[str, list[str]]:
+ bosses_only_name = dict()
+ for category, bosses_list in bosses.items():
+ bosses_only_name[category] = [boss.name for boss in bosses_list]
+
+ return bosses_only_name
+
+
+def export_to_json(file_name: Path, bosses: dict[str, list[Boss | str]]) -> None:
+ file_name.parent.mkdir(parents=True, exist_ok=True)
+
+ json.dump(
+ bosses,
+ open(file_name, "w", encoding="utf-8"),
+ ensure_ascii=False,
+ indent=4,
+ default=asdict,
+ )
+
+
+def export_to_simple_text(file_name, bosses: dict[str, list[Boss]]) -> None:
+ file_name.parent.mkdir(parents=True, exist_ok=True)
+
+ with open(file_name, "w", encoding="utf-8") as f:
+ num = 0
+
+ for category, bosses_list in bosses.items():
+ num += 1
+ if num > 1:
+ f.write("\n")
+
+ f.write(category + "\n")
+
+ for boss in bosses_list:
+ f.write(f" {boss.name}\n")
+
+
+if __name__ == "__main__":
+ DIR: Path = Path(__file__).resolve().parent
+
+ bosses: dict[str, list[Boss]] = get_bosses()
+ print("Всего боссов:", sum(len(i) for i in bosses.values()))
+ # Всего боссов: 44
+
+ print()
+
+ print_bosses(bosses)
+ """
+ Боссы (44):
+ ОСНОВНАЯ ИГРА. ОБЯЗАТЕЛЬНЫЕ БОССЫ (7):
+ 1. "Отец Гаскойн": https://bloodborne.fandom.com/ru/wiki/%D0%9E%D1%82%D0%B5%D1%86_%D0%93%D0%B0%D1%81%D0%BA%D0%BE%D0%B9%D0%BD
+ 2. "Викарий Амелия": https://bloodborne.fandom.com/ru/wiki/%D0%92%D0%B8%D0%BA%D0%B0%D1%80%D0%B8%D0%B9_%D0%90%D0%BC%D0%B5%D0%BB%D0%B8%D1%8F
+ 3. "Тень Ярнама": https://bloodborne.fandom.com/ru/wiki/%D0%A2%D0%B5%D0%BD%D1%8C_%D0%AF%D1%80%D0%BD%D0%B0%D0%BC%D0%B0
+ 4. "Ром, праздный паук": https://bloodborne.fandom.com/ru/wiki/%D0%A0%D0%BE%D0%BC,_%D0%BF%D1%80%D0%B0%D0%B7%D0%B4%D0%BD%D1%8B%D0%B9_%D0%BF%D0%B0%D1%83%D0%BA
+ 5. "Возродившийся": https://bloodborne.fandom.com/ru/wiki/%D0%92%D0%BE%D0%B7%D1%80%D0%BE%D0%B4%D0%B8%D0%B2%D1%88%D0%B8%D0%B9%D1%81%D1%8F
+ 6. "Миколаш, Хозяин кошмара": https://bloodborne.fandom.com/ru/wiki/%D0%9C%D0%B8%D0%BA%D0%BE%D0%BB%D0%B0%D1%88,_%D0%A5%D0%BE%D0%B7%D1%8F%D0%B8%D0%BD_%D0%BA%D0%BE%D1%88%D0%BC%D0%B0%D1%80%D0%B0
+ 7. "Кормилица Мерго": https://bloodborne.fandom.com/ru/wiki/%D0%9A%D0%BE%D1%80%D0%BC%D0%B8%D0%BB%D0%B8%D1%86%D0%B0_%D0%9C%D0%B5%D1%80%D0%B3%D0%BE
+
+ ОСНОВНАЯ ИГРА. ОПЦИОНАЛЬНЫЕ БОССЫ (10):
+ 1. "Церковное Чудовище": https://bloodborne.fandom.com/ru/wiki/%D0%A6%D0%B5%D1%80%D0%BA%D0%BE%D0%B2%D0%BD%D0%BE%D0%B5_%D0%A7%D1%83%D0%B4%D0%BE%D0%B2%D0%B8%D1%89%D0%B5
+ 2. "Посланник Небес": https://bloodborne.fandom.com/ru/wiki/%D0%9F%D0%BE%D1%81%D0%BB%D0%B0%D0%BD%D0%BD%D0%B8%D0%BA_%D0%9D%D0%B5%D0%B1%D0%B5%D1%81
+ 3. "Ибраитас, дочь Космоса": https://bloodborne.fandom.com/ru/wiki/%D0%98%D0%B1%D1%80%D0%B0%D0%B8%D1%82%D0%B0%D1%81,_%D0%B4%D0%BE%D1%87%D1%8C_%D0%9A%D0%BE%D1%81%D0%BC%D0%BE%D1%81%D0%B0
+ 4. "Чудовище-кровоглот": https://bloodborne.fandom.com/ru/wiki/%D0%A7%D1%83%D0%B4%D0%BE%D0%B2%D0%B8%D1%89%D0%B5-%D0%BA%D1%80%D0%BE%D0%B2%D0%BE%D0%B3%D0%BB%D0%BE%D1%82
+ 5. "Черное чудовище Паарл": https://bloodborne.fandom.com/ru/wiki/%D0%A7%D0%B5%D1%80%D0%BD%D0%BE%D0%B5_%D1%87%D1%83%D0%B4%D0%BE%D0%B2%D0%B8%D1%89%D0%B5_%D0%9F%D0%B0%D0%B0%D1%80%D0%BB
+ 6. "Ведьма Хемвика": https://bloodborne.fandom.com/ru/wiki/%D0%92%D0%B5%D0%B4%D1%8C%D0%BC%D0%B0_%D0%A5%D0%B5%D0%BC%D0%B2%D0%B8%D0%BA%D0%B0
+ 7. "Мученик Логариус": https://bloodborne.fandom.com/ru/wiki/%D0%9C%D1%83%D1%87%D0%B5%D0%BD%D0%B8%D0%BA_%D0%9B%D0%BE%D0%B3%D0%B0%D1%80%D0%B8%D1%83%D1%81
+ 8. "Амигдала": https://bloodborne.fandom.com/ru/wiki/%D0%90%D0%BC%D0%B8%D0%B3%D0%B4%D0%B0%D0%BB%D0%B0
+ 9. "Герман, первый охотник": https://bloodborne.fandom.com/ru/wiki/%D0%93%D0%B5%D1%80%D0%BC%D0%B0%D0%BD,_%D0%BF%D0%B5%D1%80%D0%B2%D1%8B%D0%B9_%D0%BE%D1%85%D0%BE%D1%82%D0%BD%D0%B8%D0%BA
+ 10. "Присутствие луны": https://bloodborne.fandom.com/ru/wiki/%D0%9F%D1%80%D0%B8%D1%81%D1%83%D1%82%D1%81%D1%82%D0%B2%D0%B8%D0%B5_%D0%BB%D1%83%D0%BD%D1%8B
+
+ ДОПОЛНЕНИЕ THE OLD HUNTERS (5):
+ 1. "Людвиг": https://bloodborne.fandom.com/ru/wiki/%D0%9B%D1%8E%D0%B4%D0%B2%D0%B8%D0%B3
+ 2. "Лоуренс, первый викарий": https://bloodborne.fandom.com/ru/wiki/%D0%9B%D0%BE%D1%83%D1%80%D0%B5%D0%BD%D1%81,_%D0%BF%D0%B5%D1%80%D0%B2%D1%8B%D0%B9_%D0%B2%D0%B8%D0%BA%D0%B0%D1%80%D0%B8%D0%B9
+ 3. "Живые неудачи": https://bloodborne.fandom.com/ru/wiki/%D0%96%D0%B8%D0%B2%D1%8B%D0%B5_%D0%BD%D0%B5%D1%83%D0%B4%D0%B0%D1%87%D0%B8
+ 4. "Леди Мария из Астральной часовой башни": https://bloodborne.fandom.com/ru/wiki/%D0%9B%D0%B5%D0%B4%D0%B8_%D0%9C%D0%B0%D1%80%D0%B8%D1%8F_%D0%B8%D0%B7_%D0%90%D1%81%D1%82%D1%80%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B9_%D1%87%D0%B0%D1%81%D0%BE%D0%B2%D0%BE%D0%B9_%D0%B1%D0%B0%D1%88%D0%BD%D0%B8
+ 5. "Сирота Кос": https://bloodborne.fandom.com/ru/wiki/%D0%A1%D0%B8%D1%80%D0%BE%D1%82%D0%B0_%D0%9A%D0%BE%D1%81
+
+ ОБЛАСТЬ ПТУМЕРУ (10):
+ 1. "Мертвый гигант": https://bloodborne.fandom.com/ru/wiki/%D0%9C%D0%B5%D1%80%D1%82%D0%B2%D1%8B%D0%B9_%D0%B3%D0%B8%D0%B3%D0%B0%D0%BD%D1%82
+ 2. "Беспощадные Хранители": https://bloodborne.fandom.com/ru/wiki/%D0%91%D0%B5%D1%81%D0%BF%D0%BE%D1%89%D0%B0%D0%B4%D0%BD%D1%8B%D0%B5_%D0%A5%D1%80%D0%B0%D0%BD%D0%B8%D1%82%D0%B5%D0%BB%D0%B8
+ 3. "Сторожевой пес Древних Богов": https://bloodborne.fandom.com/ru/wiki/%D0%A1%D1%82%D0%BE%D1%80%D0%BE%D0%B6%D0%B5%D0%B2%D0%BE%D0%B9_%D0%BF%D0%B5%D1%81_%D0%94%D1%80%D0%B5%D0%B2%D0%BD%D0%B8%D1%85_%D0%91%D0%BE%D0%B3%D0%BE%D0%B2
+ 4. "Душа, одержимая чудовищем": https://bloodborne.fandom.com/ru/wiki/%D0%94%D1%83%D1%88%D0%B0,_%D0%BE%D0%B4%D0%B5%D1%80%D0%B6%D0%B8%D0%BC%D0%B0%D1%8F_%D1%87%D1%83%D0%B4%D0%BE%D0%B2%D0%B8%D1%89%D0%B5%D0%BC
+ 5. "Хранитель Древних Богов": https://bloodborne.fandom.com/ru/wiki/%D0%A5%D1%80%D0%B0%D0%BD%D0%B8%D1%82%D0%B5%D0%BB%D1%8C_%D0%94%D1%80%D0%B5%D0%B2%D0%BD%D0%B8%D1%85_%D0%91%D0%BE%D0%B3%D0%BE%D0%B2
+ 6. "Потомок птумериан": https://bloodborne.fandom.com/ru/wiki/%D0%9F%D0%BE%D1%82%D0%BE%D0%BC%D0%BE%D0%BA_%D0%BF%D1%82%D1%83%D0%BC%D0%B5%D1%80%D0%B8%D0%B0%D0%BD
+ 7. "Ром, праздный паук": https://bloodborne.fandom.com/ru/wiki/%D0%A0%D0%BE%D0%BC,_%D0%BF%D1%80%D0%B0%D0%B7%D0%B4%D0%BD%D1%8B%D0%B9_%D0%BF%D0%B0%D1%83%D0%BA
+ 8. "Чудовище-кровопуск": https://bloodborne.fandom.com/ru/wiki/%D0%A7%D1%83%D0%B4%D0%BE%D0%B2%D0%B8%D1%89%D0%B5-%D0%BA%D1%80%D0%BE%D0%B2%D0%BE%D0%BF%D1%83%D1%81%D0%BA
+ 9. "Амигдала": https://bloodborne.fandom.com/ru/wiki/%D0%90%D0%BC%D0%B8%D0%B3%D0%B4%D0%B0%D0%BB%D0%B0
+ 10. "Ярнам, Птумерианская королева": https://bloodborne.fandom.com/ru/wiki/%D0%AF%D1%80%D0%BD%D0%B0%D0%BC,_%D0%9F%D1%82%D1%83%D0%BC%D0%B5%D1%80%D0%B8%D0%B0%D0%BD%D1%81%D0%BA%D0%B0%D1%8F_%D0%BA%D0%BE%D1%80%D0%BE%D0%BB%D0%B5%D0%B2%D0%B0
+
+ ОБЛАСТЬ ДАЛЬНИХ ГРОБНИЦ (5):
+ 1. "Вепрь-людоед": https://bloodborne.fandom.com/ru/wiki/%D0%92%D0%B5%D0%BF%D1%80%D1%8C-%D0%BB%D1%8E%D0%B4%D0%BE%D0%B5%D0%B4
+ 2. "Чудовище-кровоглот": https://bloodborne.fandom.com/ru/wiki/%D0%A7%D1%83%D0%B4%D0%BE%D0%B2%D0%B8%D1%89%D0%B5-%D0%BA%D1%80%D0%BE%D0%B2%D0%BE%D0%B3%D0%BB%D0%BE%D1%82
+ 3. "Мозгосос": https://bloodborne.fandom.com/ru/wiki/%D0%9C%D0%BE%D0%B7%D0%B3%D0%BE%D1%81%D0%BE%D1%81
+ 4. "Забытый безумец": https://bloodborne.fandom.com/ru/wiki/%D0%97%D0%B0%D0%B1%D1%8B%D1%82%D1%8B%D0%B9_%D0%B1%D0%B5%D0%B7%D1%83%D0%BC%D0%B5%D1%86
+ 5. "Птумерианский Старейшина": https://bloodborne.fandom.com/ru/wiki/%D0%9F%D1%82%D1%83%D0%BC%D0%B5%D1%80%D0%B8%D0%B0%D0%BD%D1%81%D0%BA%D0%B8%D0%B9_%D0%A1%D1%82%D0%B0%D1%80%D0%B5%D0%B9%D1%88%D0%B8%D0%BD%D0%B0
+
+ ОБЛАСТЬ ЛОРАНА (4):
+ 1. "Чудовище-кровоглот": https://bloodborne.fandom.com/ru/wiki/%D0%A7%D1%83%D0%B4%D0%BE%D0%B2%D0%B8%D1%89%D0%B5-%D0%BA%D1%80%D0%BE%D0%B2%D0%BE%D0%B3%D0%BB%D0%BE%D1%82
+ 2. "Отвратительное чудовище": https://bloodborne.fandom.com/ru/wiki/%D0%9E%D1%82%D0%B2%D1%80%D0%B0%D1%82%D0%B8%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D0%BE%D0%B5_%D1%87%D1%83%D0%B4%D0%BE%D0%B2%D0%B8%D1%89%D0%B5
+ 3. "Серебряное чудовище Лорана": https://bloodborne.fandom.com/ru/wiki/%D0%A1%D0%B5%D1%80%D0%B5%D0%B1%D1%80%D1%8F%D0%BD%D0%BE%D0%B5_%D1%87%D1%83%D0%B4%D0%BE%D0%B2%D0%B8%D1%89%D0%B5_%D0%9B%D0%BE%D1%80%D0%B0%D0%BD%D0%B0
+ 4. "Черное чудовище Паарл": https://bloodborne.fandom.com/ru/wiki/%D0%A7%D0%B5%D1%80%D0%BD%D0%BE%D0%B5_%D1%87%D1%83%D0%B4%D0%BE%D0%B2%D0%B8%D1%89%D0%B5_%D0%9F%D0%B0%D0%B0%D1%80%D0%BB
+
+ ОБЛАСТЬ ИСЗА (3):
+ 1. "Мозгосос": https://bloodborne.fandom.com/ru/wiki/%D0%9C%D0%BE%D0%B7%D0%B3%D0%BE%D1%81%D0%BE%D1%81
+ 2. "Посланник Небес": https://bloodborne.fandom.com/ru/wiki/%D0%9F%D0%BE%D1%81%D0%BB%D0%B0%D0%BD%D0%BD%D0%B8%D0%BA_%D0%9D%D0%B5%D0%B1%D0%B5%D1%81
+ 3. "Ибраитас, дочь Космоса": https://bloodborne.fandom.com/ru/wiki/%D0%98%D0%B1%D1%80%D0%B0%D0%B8%D1%82%D0%B0%D1%81,_%D0%B4%D0%BE%D1%87%D1%8C_%D0%9A%D0%BE%D1%81%D0%BC%D0%BE%D1%81%D0%B0
+
+ """
+
+ export_to_json(DIR / "dumps/bosses.json", bosses)
+ export_to_json(
+ DIR / "dumps/bosses__only_name.json", convert_bosses_to_only_name(bosses)
+ )
+ export_to_simple_text(DIR / "dumps/bosses__only_name.txt", bosses)
diff --git "a/for_games/Bloodborne/bloodborne_fandom_com__ru__wiki__\320\232\320\260\321\202\320\265\320\263\320\276\321\200\320\270\321\217_\320\233\320\276\320\272\320\260\321\206\320\270\320\270/dumps/locations.json" "b/for_games/Bloodborne/bloodborne_fandom_com__ru__wiki__\320\232\320\260\321\202\320\265\320\263\320\276\321\200\320\270\321\217_\320\233\320\276\320\272\320\260\321\206\320\270\320\270/dumps/locations.json"
new file mode 100644
index 000000000..2f871ea0d
--- /dev/null
+++ "b/for_games/Bloodborne/bloodborne_fandom_com__ru__wiki__\320\232\320\260\321\202\320\265\320\263\320\276\321\200\320\270\321\217_\320\233\320\276\320\272\320\260\321\206\320\270\320\270/dumps/locations.json"
@@ -0,0 +1,46 @@
+{
+ "Алтарь отчаяния": "https://bloodborne.fandom.com/ru/wiki/%D0%90%D0%BB%D1%82%D0%B0%D1%80%D1%8C_%D0%BE%D1%82%D1%87%D0%B0%D1%8F%D0%BD%D0%B8%D1%8F",
+ "Астральная часовая башня": "https://bloodborne.fandom.com/ru/wiki/%D0%90%D1%81%D1%82%D1%80%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F_%D1%87%D0%B0%D1%81%D0%BE%D0%B2%D0%B0%D1%8F_%D0%B1%D0%B0%D1%88%D0%BD%D1%8F",
+ "Большой мост": "https://bloodborne.fandom.com/ru/wiki/%D0%91%D0%BE%D0%BB%D1%8C%D1%88%D0%BE%D0%B9_%D0%BC%D0%BE%D1%81%D1%82",
+ "Бюргенверт": "https://bloodborne.fandom.com/ru/wiki/%D0%91%D1%8E%D1%80%D0%B3%D0%B5%D0%BD%D0%B2%D0%B5%D1%80%D1%82",
+ "Верхний Соборный округ": "https://bloodborne.fandom.com/ru/wiki/%D0%92%D0%B5%D1%80%D1%85%D0%BD%D0%B8%D0%B9_%D0%A1%D0%BE%D0%B1%D0%BE%D1%80%D0%BD%D1%8B%D0%B9_%D0%BE%D0%BA%D1%80%D1%83%D0%B3",
+ "Главный собор": "https://bloodborne.fandom.com/ru/wiki/%D0%93%D0%BB%D0%B0%D0%B2%D0%BD%D1%8B%D0%B9_%D1%81%D0%BE%D0%B1%D0%BE%D1%80",
+ "Главный собор кошмара": "https://bloodborne.fandom.com/ru/wiki/%D0%93%D0%BB%D0%B0%D0%B2%D0%BD%D1%8B%D0%B9_%D1%81%D0%BE%D0%B1%D0%BE%D1%80_%D0%BA%D0%BE%D1%88%D0%BC%D0%B0%D1%80%D0%B0",
+ "Граница кошмара": "https://bloodborne.fandom.com/ru/wiki/%D0%93%D1%80%D0%B0%D0%BD%D0%B8%D1%86%D0%B0_%D0%BA%D0%BE%D1%88%D0%BC%D0%B0%D1%80%D0%B0",
+ "Гробница Идон": "https://bloodborne.fandom.com/ru/wiki/%D0%93%D1%80%D0%BE%D0%B1%D0%BD%D0%B8%D1%86%D0%B0_%D0%98%D0%B4%D0%BE%D0%BD",
+ "Зал Амигдалы": "https://bloodborne.fandom.com/ru/wiki/%D0%97%D0%B0%D0%BB_%D0%90%D0%BC%D0%B8%D0%B3%D0%B4%D0%B0%D0%BB%D1%8B",
+ "Зал исследований": "https://bloodborne.fandom.com/ru/wiki/%D0%97%D0%B0%D0%BB_%D0%B8%D1%81%D1%81%D0%BB%D0%B5%D0%B4%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B9",
+ "Зал королевы Нечистокровных": "https://bloodborne.fandom.com/ru/wiki/%D0%97%D0%B0%D0%BB_%D0%BA%D0%BE%D1%80%D0%BE%D0%BB%D0%B5%D0%B2%D1%8B_%D0%9D%D0%B5%D1%87%D0%B8%D1%81%D1%82%D0%BE%D0%BA%D1%80%D0%BE%D0%B2%D0%BD%D1%8B%D1%85",
+ "Запретная могила": "https://bloodborne.fandom.com/ru/wiki/%D0%97%D0%B0%D0%BF%D1%80%D0%B5%D1%82%D0%BD%D0%B0%D1%8F_%D0%BC%D0%BE%D0%B3%D0%B8%D0%BB%D0%B0",
+ "Запретный лес": "https://bloodborne.fandom.com/ru/wiki/%D0%97%D0%B0%D0%BF%D1%80%D0%B5%D1%82%D0%BD%D1%8B%D0%B9_%D0%BB%D0%B5%D1%81",
+ "Кладбище Черного чудовища": "https://bloodborne.fandom.com/ru/wiki/%D0%9A%D0%BB%D0%B0%D0%B4%D0%B1%D0%B8%D1%89%D0%B5_%D0%A7%D0%B5%D1%80%D0%BD%D0%BE%D0%B3%D0%BE_%D1%87%D1%83%D0%B4%D0%BE%D0%B2%D0%B8%D1%89%D0%B0",
+ "Кошмар Менсиса": "https://bloodborne.fandom.com/ru/wiki/%D0%9A%D0%BE%D1%88%D0%BC%D0%B0%D1%80_%D0%9C%D0%B5%D0%BD%D1%81%D0%B8%D1%81%D0%B0",
+ "Кошмар охотника": "https://bloodborne.fandom.com/ru/wiki/%D0%9A%D0%BE%D1%88%D0%BC%D0%B0%D1%80_%D0%BE%D1%85%D0%BE%D1%82%D0%BD%D0%B8%D0%BA%D0%B0",
+ "Лекционный корпус": "https://bloodborne.fandom.com/ru/wiki/%D0%9B%D0%B5%D0%BA%D1%86%D0%B8%D0%BE%D0%BD%D0%BD%D1%8B%D0%B9_%D0%BA%D0%BE%D1%80%D0%BF%D1%83%D1%81",
+ "Лечебница Йозефки": "https://bloodborne.fandom.com/ru/wiki/%D0%9B%D0%B5%D1%87%D0%B5%D0%B1%D0%BD%D0%B8%D1%86%D0%B0_%D0%99%D0%BE%D0%B7%D0%B5%D1%84%D0%BA%D0%B8",
+ "Лунарий Кормилицы Мерго": "https://bloodborne.fandom.com/ru/wiki/%D0%9B%D1%83%D0%BD%D0%B0%D1%80%D0%B8%D0%B9_%D0%9A%D0%BE%D1%80%D0%BC%D0%B8%D0%BB%D0%B8%D1%86%D1%8B_%D0%9C%D0%B5%D1%80%D0%B3%D0%BE",
+ "Мастерская Церкви исцеления": "https://bloodborne.fandom.com/ru/wiki/%D0%9C%D0%B0%D1%81%D1%82%D0%B5%D1%80%D1%81%D0%BA%D0%B0%D1%8F_%D0%A6%D0%B5%D1%80%D0%BA%D0%B2%D0%B8_%D0%B8%D1%81%D1%86%D0%B5%D0%BB%D0%B5%D0%BD%D0%B8%D1%8F",
+ "Место Логариуса": "https://bloodborne.fandom.com/ru/wiki/%D0%9C%D0%B5%D1%81%D1%82%D0%BE_%D0%9B%D0%BE%D0%B3%D0%B0%D1%80%D0%B8%D1%83%D1%81%D0%B0",
+ "Молельный переулок Хемвик": "https://bloodborne.fandom.com/ru/wiki/%D0%9C%D0%BE%D0%BB%D0%B5%D0%BB%D1%8C%D0%BD%D1%8B%D0%B9_%D0%BF%D0%B5%D1%80%D0%B5%D1%83%D0%BB%D0%BE%D0%BA_%D0%A5%D0%B5%D0%BC%D0%B2%D0%B8%D0%BA",
+ "Обитель ведьмы": "https://bloodborne.fandom.com/ru/wiki/%D0%9E%D0%B1%D0%B8%D1%82%D0%B5%D0%BB%D1%8C_%D0%B2%D0%B5%D0%B4%D1%8C%D0%BC%D1%8B",
+ "Озеро лунного отражения": "https://bloodborne.fandom.com/ru/wiki/%D0%9E%D0%B7%D0%B5%D1%80%D0%BE_%D0%BB%D1%83%D0%BD%D0%BD%D0%BE%D0%B3%D0%BE_%D0%BE%D1%82%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D1%8F",
+ "Площадь Пришествия": "https://bloodborne.fandom.com/ru/wiki/%D0%9F%D0%BB%D0%BE%D1%89%D0%B0%D0%B4%D1%8C_%D0%9F%D1%80%D0%B8%D1%88%D0%B5%D1%81%D1%82%D0%B2%D0%B8%D1%8F",
+ "Побережье": "https://bloodborne.fandom.com/ru/wiki/%D0%9F%D0%BE%D0%B1%D0%B5%D1%80%D0%B5%D0%B6%D1%8C%D0%B5",
+ "Подземелье чаши": "https://bloodborne.fandom.com/ru/wiki/%D0%9F%D0%BE%D0%B4%D0%B7%D0%B5%D0%BC%D0%B5%D0%BB%D1%8C%D0%B5_%D1%87%D0%B0%D1%88%D0%B8",
+ "Подземная куча трупов": "https://bloodborne.fandom.com/ru/wiki/%D0%9F%D0%BE%D0%B4%D0%B7%D0%B5%D0%BC%D0%BD%D0%B0%D1%8F_%D0%BA%D1%83%D1%87%D0%B0_%D1%82%D1%80%D1%83%D0%BF%D0%BE%D0%B2",
+ "Подземная тюрьма": "https://bloodborne.fandom.com/ru/wiki/%D0%9F%D0%BE%D0%B4%D0%B7%D0%B5%D0%BC%D0%BD%D0%B0%D1%8F_%D1%82%D1%8E%D1%80%D1%8C%D0%BC%D0%B0",
+ "Покинутый замок Кейнхёрст": "https://bloodborne.fandom.com/ru/wiki/%D0%9F%D0%BE%D0%BA%D0%B8%D0%BD%D1%83%D1%82%D1%8B%D0%B9_%D0%B7%D0%B0%D0%BC%D0%BE%D0%BA_%D0%9A%D0%B5%D0%B9%D0%BD%D1%85%D1%91%D1%80%D1%81%D1%82",
+ "Рыбацкая деревня": "https://bloodborne.fandom.com/ru/wiki/%D0%A0%D1%8B%D0%B1%D0%B0%D1%86%D0%BA%D0%B0%D1%8F_%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%BD%D1%8F",
+ "Сад светящегося дерева": "https://bloodborne.fandom.com/ru/wiki/%D0%A1%D0%B0%D0%B4_%D1%81%D0%B2%D0%B5%D1%82%D1%8F%D1%89%D0%B5%D0%B3%D0%BE%D1%81%D1%8F_%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%B0",
+ "Сады светящихся цветов": "https://bloodborne.fandom.com/ru/wiki/%D0%A1%D0%B0%D0%B4%D1%8B_%D1%81%D0%B2%D0%B5%D1%82%D1%8F%D1%89%D0%B8%D1%85%D1%81%D1%8F_%D1%86%D0%B2%D0%B5%D1%82%D0%BE%D0%B2",
+ "Соборный округ": "https://bloodborne.fandom.com/ru/wiki/%D0%A1%D0%BE%D0%B1%D0%BE%D1%80%D0%BD%D1%8B%D0%B9_%D0%BE%D0%BA%D1%80%D1%83%D0%B3",
+ "Сон охотника": "https://bloodborne.fandom.com/ru/wiki/%D0%A1%D0%BE%D0%BD_%D0%BE%D1%85%D0%BE%D1%82%D0%BD%D0%B8%D0%BA%D0%B0",
+ "Старая заброшенная мастерская": "https://bloodborne.fandom.com/ru/wiki/%D0%A1%D1%82%D0%B0%D1%80%D0%B0%D1%8F_%D0%B7%D0%B0%D0%B1%D1%80%D0%BE%D1%88%D0%B5%D0%BD%D0%BD%D0%B0%D1%8F_%D0%BC%D0%B0%D1%81%D1%82%D0%B5%D1%80%D1%81%D0%BA%D0%B0%D1%8F",
+ "Старый Ярнам": "https://bloodborne.fandom.com/ru/wiki/%D0%A1%D1%82%D0%B0%D1%80%D1%8B%D0%B9_%D0%AF%D1%80%D0%BD%D0%B0%D0%BC",
+ "Хижина рядом с маяком": "https://bloodborne.fandom.com/ru/wiki/%D0%A5%D0%B8%D0%B6%D0%B8%D0%BD%D0%B0_%D1%80%D1%8F%D0%B4%D0%BE%D0%BC_%D1%81_%D0%BC%D0%B0%D1%8F%D0%BA%D0%BE%D0%BC",
+ "Центральный Ярнам": "https://bloodborne.fandom.com/ru/wiki/%D0%A6%D0%B5%D0%BD%D1%82%D1%80%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B9_%D0%AF%D1%80%D0%BD%D0%B0%D0%BC",
+ "Церковь Доброй чаши": "https://bloodborne.fandom.com/ru/wiki/%D0%A6%D0%B5%D1%80%D0%BA%D0%BE%D0%B2%D1%8C_%D0%94%D0%BE%D0%B1%D1%80%D0%BE%D0%B9_%D1%87%D0%B0%D1%88%D0%B8",
+ "Церковь кошмара": "https://bloodborne.fandom.com/ru/wiki/%D0%A6%D0%B5%D1%80%D0%BA%D0%BE%D0%B2%D1%8C_%D0%BA%D0%BE%D1%88%D0%BC%D0%B0%D1%80%D0%B0",
+ "Часовня Идон": "https://bloodborne.fandom.com/ru/wiki/%D0%A7%D0%B0%D1%81%D0%BE%D0%B2%D0%BD%D1%8F_%D0%98%D0%B4%D0%BE%D0%BD",
+ "Яаар'гул, духовная деревня": "https://bloodborne.fandom.com/ru/wiki/%D0%AF%D0%B0%D0%B0%D1%80%27%D0%B3%D1%83%D0%BB,_%D0%B4%D1%83%D1%85%D0%BE%D0%B2%D0%BD%D0%B0%D1%8F_%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%BD%D1%8F"
+}
\ No newline at end of file
diff --git "a/for_games/Bloodborne/bloodborne_fandom_com__ru__wiki__\320\232\320\260\321\202\320\265\320\263\320\276\321\200\320\270\321\217_\320\233\320\276\320\272\320\260\321\206\320\270\320\270/dumps/locations_graph.html" "b/for_games/Bloodborne/bloodborne_fandom_com__ru__wiki__\320\232\320\260\321\202\320\265\320\263\320\276\321\200\320\270\321\217_\320\233\320\276\320\272\320\260\321\206\320\270\320\270/dumps/locations_graph.html"
new file mode 100644
index 000000000..7a99fbe73
--- /dev/null
+++ "b/for_games/Bloodborne/bloodborne_fandom_com__ru__wiki__\320\232\320\260\321\202\320\265\320\263\320\276\321\200\320\270\321\217_\320\233\320\276\320\272\320\260\321\206\320\270\320\270/dumps/locations_graph.html"
@@ -0,0 +1,145 @@
+
+
+
+
+Locations Bloodborne
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git "a/for_games/Bloodborne/bloodborne_fandom_com__ru__wiki__\320\232\320\260\321\202\320\265\320\263\320\276\321\200\320\270\321\217_\320\233\320\276\320\272\320\260\321\206\320\270\320\270/dumps/locations_transitions.json" "b/for_games/Bloodborne/bloodborne_fandom_com__ru__wiki__\320\232\320\260\321\202\320\265\320\263\320\276\321\200\320\270\321\217_\320\233\320\276\320\272\320\260\321\206\320\270\320\270/dumps/locations_transitions.json"
new file mode 100644
index 000000000..95592ee00
--- /dev/null
+++ "b/for_games/Bloodborne/bloodborne_fandom_com__ru__wiki__\320\232\320\260\321\202\320\265\320\263\320\276\321\200\320\270\321\217_\320\233\320\276\320\272\320\260\321\206\320\270\320\270/dumps/locations_transitions.json"
@@ -0,0 +1,374 @@
+[
+ [
+ "Алтарь отчаяния",
+ "Сады светящихся цветов"
+ ],
+ [
+ "Астральная часовая башня",
+ "Зал исследований"
+ ],
+ [
+ "Астральная часовая башня",
+ "Рыбацкая деревня"
+ ],
+ [
+ "Астральная часовая башня",
+ "Сад светящегося дерева"
+ ],
+ [
+ "Большой мост",
+ "Центральный Ярнам"
+ ],
+ [
+ "Бюргенверт",
+ "Запретный лес"
+ ],
+ [
+ "Бюргенверт",
+ "Озеро лунного отражения"
+ ],
+ [
+ "Верхний Соборный округ",
+ "Мастерская Церкви исцеления"
+ ],
+ [
+ "Верхний Соборный округ",
+ "Соборный округ"
+ ],
+ [
+ "Главный собор",
+ "Соборный округ"
+ ],
+ [
+ "Главный собор кошмара",
+ "Кошмар охотника"
+ ],
+ [
+ "Граница кошмара",
+ "Лекционный корпус"
+ ],
+ [
+ "Гробница Идон",
+ "Центральный Ярнам"
+ ],
+ [
+ "Гробница Идон",
+ "Часовня Идон"
+ ],
+ [
+ "Зал Амигдалы",
+ "Граница кошмара"
+ ],
+ [
+ "Зал исследований",
+ "Подземная куча трупов"
+ ],
+ [
+ "Зал исследований",
+ "Сад светящегося дерева"
+ ],
+ [
+ "Зал королевы Нечистокровных",
+ "Место Логариуса"
+ ],
+ [
+ "Зал королевы Нечистокровных",
+ "Покинутый замок Кейнхёрст"
+ ],
+ [
+ "Запретная могила",
+ "Бюргенверт"
+ ],
+ [
+ "Запретная могила",
+ "Запретный лес"
+ ],
+ [
+ "Запретный лес",
+ "Бюргенверт"
+ ],
+ [
+ "Запретный лес",
+ "Лечебница Йозефки"
+ ],
+ [
+ "Запретный лес",
+ "Соборный округ"
+ ],
+ [
+ "Кладбище Черного чудовища",
+ "Подземная тюрьма"
+ ],
+ [
+ "Кладбище Черного чудовища",
+ "Старый Ярнам"
+ ],
+ [
+ "Кладбище Черного чудовища",
+ "Яаар'гул, духовная деревня"
+ ],
+ [
+ "Кошмар Менсиса",
+ "Лекционный корпус"
+ ],
+ [
+ "Кошмар Менсиса",
+ "Лунарий Кормилицы Мерго"
+ ],
+ [
+ "Кошмар охотника",
+ "Подземная куча трупов"
+ ],
+ [
+ "Кошмар охотника",
+ "Церковь кошмара"
+ ],
+ [
+ "Лекционный корпус",
+ "Граница кошмара"
+ ],
+ [
+ "Лекционный корпус",
+ "Кошмар Менсиса"
+ ],
+ [
+ "Лекционный корпус",
+ "Площадь Пришествия"
+ ],
+ [
+ "Лекционный корпус",
+ "Соборный округ"
+ ],
+ [
+ "Лекционный корпус",
+ "Яаар'гул, духовная деревня"
+ ],
+ [
+ "Лечебница Йозефки",
+ "Запретный лес"
+ ],
+ [
+ "Лечебница Йозефки",
+ "Центральный Ярнам"
+ ],
+ [
+ "Лунарий Кормилицы Мерго",
+ "Кошмар Менсиса"
+ ],
+ [
+ "Мастерская Церкви исцеления",
+ "Верхний Соборный округ"
+ ],
+ [
+ "Мастерская Церкви исцеления",
+ "Главный собор"
+ ],
+ [
+ "Мастерская Церкви исцеления",
+ "Соборный округ"
+ ],
+ [
+ "Мастерская Церкви исцеления",
+ "Старая заброшенная мастерская"
+ ],
+ [
+ "Мастерская Церкви исцеления",
+ "Часовня Идон"
+ ],
+ [
+ "Место Логариуса",
+ "Зал королевы Нечистокровных"
+ ],
+ [
+ "Место Логариуса",
+ "Покинутый замок Кейнхёрст"
+ ],
+ [
+ "Молельный переулок Хемвик",
+ "Обитель ведьмы"
+ ],
+ [
+ "Молельный переулок Хемвик",
+ "Покинутый замок Кейнхёрст"
+ ],
+ [
+ "Молельный переулок Хемвик",
+ "Соборный округ"
+ ],
+ [
+ "Обитель ведьмы",
+ "Молельный переулок Хемвик"
+ ],
+ [
+ "Озеро лунного отражения",
+ "Бюргенверт"
+ ],
+ [
+ "Озеро лунного отражения",
+ "Яаар'гул, духовная деревня"
+ ],
+ [
+ "Площадь Пришествия",
+ "Лекционный корпус"
+ ],
+ [
+ "Площадь Пришествия",
+ "Яаар'гул, духовная деревня"
+ ],
+ [
+ "Побережье",
+ "Рыбацкая деревня"
+ ],
+ [
+ "Подземелье чаши",
+ "Сон охотника"
+ ],
+ [
+ "Подземная куча трупов",
+ "Зал исследований"
+ ],
+ [
+ "Подземная куча трупов",
+ "Кошмар охотника"
+ ],
+ [
+ "Подземная куча трупов",
+ "Церковь кошмара"
+ ],
+ [
+ "Подземная тюрьма",
+ "Старый Ярнам"
+ ],
+ [
+ "Покинутый замок Кейнхёрст",
+ "Место Логариуса"
+ ],
+ [
+ "Покинутый замок Кейнхёрст",
+ "Молельный переулок Хемвик"
+ ],
+ [
+ "Рыбацкая деревня",
+ "Астральная часовая башня"
+ ],
+ [
+ "Рыбацкая деревня",
+ "Побережье"
+ ],
+ [
+ "Рыбацкая деревня",
+ "Хижина рядом с маяком"
+ ],
+ [
+ "Сад светящегося дерева",
+ "Астральная часовая башня"
+ ],
+ [
+ "Сад светящегося дерева",
+ "Зал исследований"
+ ],
+ [
+ "Сады светящихся цветов",
+ "Алтарь отчаяния"
+ ],
+ [
+ "Сады светящихся цветов",
+ "Верхний Соборный округ"
+ ],
+ [
+ "Соборный округ",
+ "Запретный лес"
+ ],
+ [
+ "Соборный округ",
+ "Кошмар охотника"
+ ],
+ [
+ "Соборный округ",
+ "Лекционный корпус"
+ ],
+ [
+ "Соборный округ",
+ "Мастерская Церкви исцеления"
+ ],
+ [
+ "Соборный округ",
+ "Молельный переулок Хемвик"
+ ],
+ [
+ "Соборный округ",
+ "Старый Ярнам"
+ ],
+ [
+ "Соборный округ",
+ "Яаар'гул, духовная деревня"
+ ],
+ [
+ "Старая заброшенная мастерская",
+ "Мастерская Церкви исцеления"
+ ],
+ [
+ "Старый Ярнам",
+ "Соборный округ"
+ ],
+ [
+ "Старый Ярнам",
+ "Яаар'гул, духовная деревня"
+ ],
+ [
+ "Хижина рядом с маяком",
+ "Побережье"
+ ],
+ [
+ "Хижина рядом с маяком",
+ "Рыбацкая деревня"
+ ],
+ [
+ "Центральный Ярнам",
+ "Большой мост"
+ ],
+ [
+ "Центральный Ярнам",
+ "Гробница Идон"
+ ],
+ [
+ "Центральный Ярнам",
+ "Лечебница Йозефки"
+ ],
+ [
+ "Церковь Доброй чаши",
+ "Старый Ярнам"
+ ],
+ [
+ "Церковь кошмара",
+ "Кошмар охотника"
+ ],
+ [
+ "Церковь кошмара",
+ "Подземная куча трупов"
+ ],
+ [
+ "Часовня Идон",
+ "Мастерская Церкви исцеления"
+ ],
+ [
+ "Часовня Идон",
+ "Соборный округ"
+ ],
+ [
+ "Часовня Идон",
+ "Центральный Ярнам"
+ ],
+ [
+ "Яаар'гул, духовная деревня",
+ "Лекционный корпус"
+ ],
+ [
+ "Яаар'гул, духовная деревня",
+ "Озеро лунного отражения"
+ ],
+ [
+ "Яаар'гул, духовная деревня",
+ "Площадь Пришествия"
+ ]
+]
\ No newline at end of file
diff --git "a/for_games/Bloodborne/bloodborne_fandom_com__ru__wiki__\320\232\320\260\321\202\320\265\320\263\320\276\321\200\320\270\321\217_\320\233\320\276\320\272\320\260\321\206\320\270\320\270/main.py" "b/for_games/Bloodborne/bloodborne_fandom_com__ru__wiki__\320\232\320\260\321\202\320\265\320\263\320\276\321\200\320\270\321\217_\320\233\320\276\320\272\320\260\321\206\320\270\320\270/main.py"
new file mode 100644
index 000000000..0bc856a10
--- /dev/null
+++ "b/for_games/Bloodborne/bloodborne_fandom_com__ru__wiki__\320\232\320\260\321\202\320\265\320\263\320\276\321\200\320\270\321\217_\320\233\320\276\320\272\320\260\321\206\320\270\320\270/main.py"
@@ -0,0 +1,118 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+__author__ = "ipetrash"
+
+
+import json
+import time
+import sys
+
+from dataclasses import dataclass
+from urllib.parse import urljoin
+from pathlib import Path
+
+import requests
+from bs4 import BeautifulSoup, Tag
+
+
+DIR: Path = Path(__file__).resolve().parent
+
+sys.path.append(
+ str(DIR.parent.parent / "Dark Souls" / "generate_HTML_location_graph__with_d3js")
+)
+from generate_graph_html import generate as generate_graph_html
+
+
+DIR_DUMPS: Path = DIR / "dumps"
+DIR_DUMPS.mkdir(parents=True, exist_ok=True)
+
+URL: str = "https://bloodborne.fandom.com/ru/wiki/Категория:Локации"
+
+
+@dataclass
+class Location:
+ title: str
+ url: str
+
+ @classmethod
+ def from_tag(cls, tag: Tag) -> "Location":
+ return cls(
+ title=tag["title"],
+ url=urljoin(URL, tag["href"]),
+ )
+
+
+links: set[tuple[str, str]] = set()
+location_by_url: dict[str, str] = dict()
+
+rs = requests.get(URL)
+rs.raise_for_status()
+
+soup = BeautifulSoup(rs.content, "html.parser")
+locations: list[Location] = [
+ Location.from_tag(a)
+ for a in soup.select("a.category-page__member-link[href][title]")
+]
+
+for location in locations:
+ time.sleep(1)
+ for _ in range(3):
+ try:
+ # TODO: Оптимизации не хватает
+ rs = requests.get(location.url)
+ rs.raise_for_status()
+ except Exception as e:
+ print(f"Error: {e}, url: {location.url}")
+ time.sleep(1)
+
+ soup = BeautifulSoup(rs.content, "html.parser")
+
+ to_locations: list[tuple[str, str]] = [
+ (a["title"], urljoin(rs.url, a["href"]))
+ for a in soup.select('[data-source="Переходы"] a[href][title]')
+ ]
+ # У любой локации есть связанная с ней локация. Если нет - значит это страница не про локацию
+ if not to_locations:
+ continue
+
+ location_by_url[location.title] = location.url
+
+ print(location.title, location.url)
+ print(" Переходы:")
+
+ # Найдем связанные локации
+ for to_title, to_url in to_locations:
+ # Проверяем что ссылка ведет на локацию
+ if not any(True for l in locations if l.url == to_url): # TODO: Рефакторинг?
+ continue
+
+ print(f" {to_title}: {to_url}")
+ location_by_url[to_title] = to_url
+
+ links.add((location.title, to_title))
+
+print()
+
+# Экспорт в json
+location_by_url: dict[str, str] = dict(sorted(location_by_url.items()))
+json.dump(
+ location_by_url,
+ open(DIR_DUMPS / "locations.json", "w", encoding="utf-8"),
+ ensure_ascii=False,
+ indent=4,
+)
+
+links: list[tuple[str, str]] = sorted(links)
+json.dump(
+ links,
+ open(DIR_DUMPS / "locations_transitions.json", "w", encoding="utf-8"),
+ ensure_ascii=False,
+ indent=4,
+)
+
+generate_graph_html(
+ file_name_to=DIR_DUMPS / "locations_graph.html",
+ links=links,
+ title="Locations Bloodborne",
+)
diff --git a/for_games/Bot Buff Knight Advanced/__foo_test.py b/for_games/Bot Buff Knight Advanced/__foo_test.py
index 05cfafd8d..f4ed97917 100644
--- a/for_games/Bot Buff Knight Advanced/__foo_test.py
+++ b/for_games/Bot Buff Knight Advanced/__foo_test.py
@@ -20,7 +20,7 @@
from common import get_current_datetime_str
-def draw_rects(img, contours_rects, color=(0, 255, 0)):
+def draw_rects(img, contours_rects, color=(0, 255, 0)) -> None:
for x, y, w, h in contours_rects:
cv2.rectangle(img, (x, y), (x + w, y + h), color, thickness=5)
# cv2.rectangle(img, (x, y), (x + w, y + h), color, thickness=-1)
diff --git a/for_games/Bot Buff Knight Advanced/main.py b/for_games/Bot Buff Knight Advanced/main.py
index 78770abe8..26105e37c 100644
--- a/for_games/Bot Buff Knight Advanced/main.py
+++ b/for_games/Bot Buff Knight Advanced/main.py
@@ -61,7 +61,7 @@ def filter_fairy_and_button(rect_fairy, rect_button):
os.mkdir(DIR)
-def save_screenshot(prefix, img_hsv):
+def save_screenshot(prefix, img_hsv) -> None:
file_name = f"{DIR}/{prefix}__{get_current_datetime_str()}.png"
log.debug(file_name)
cv2.imwrite(file_name, cv2.cvtColor(np.array(img_hsv), cv2.COLOR_HSV2BGR))
@@ -90,12 +90,12 @@ def save_screenshot(prefix, img_hsv):
}
-def change_start():
+def change_start() -> None:
BOT_DATA["START"] = not BOT_DATA["START"]
log.debug("START: %s", BOT_DATA["START"])
-def change_auto_attack():
+def change_auto_attack() -> None:
BOT_DATA["AUTO_ATTACK"] = not BOT_DATA["AUTO_ATTACK"]
log.debug("AUTO_ATTACK: %s", BOT_DATA["AUTO_ATTACK"])
@@ -105,7 +105,7 @@ def change_auto_attack():
log.debug('Press "%s" for AUTO_ATTACK', AUTO_ATTACK_COMBINATION)
-def process_auto_attack():
+def process_auto_attack() -> None:
while True:
if not BOT_DATA["START"]:
time.sleep(0.01)
@@ -118,7 +118,7 @@ def process_auto_attack():
time.sleep(0.01)
-def process_find_fairy(img_hsv):
+def process_find_fairy(img_hsv) -> None:
rects_blue = find_rect_contours(img_hsv, BLUE_HSV_MIN, BLUE_HSV_MAX)
rects_orange = find_rect_contours(img_hsv, ORANGE_HSV_MIN, ORANGE_HSV_MAX)
rects_fairy = find_rect_contours(img_hsv, FAIRY_HSV_MIN, FAIRY_HSV_MAX)
diff --git a/for_games/Dark Souls/backup__save_files/by_autosave/common.py b/for_games/Dark Souls/backup__save_files/by_autosave/common.py
index cfaa2eced..d9a398a5a 100644
--- a/for_games/Dark Souls/backup__save_files/by_autosave/common.py
+++ b/for_games/Dark Souls/backup__save_files/by_autosave/common.py
@@ -6,11 +6,13 @@
import os
import time
-from glob import glob
import traceback
+from glob import glob
+
# For import utils.py
import sys
+
sys.path.append("..")
from utils import get_logger, backup
@@ -19,26 +21,28 @@
log = get_logger(__file__)
-def backup_saves(path_ds_save: str, forced=False):
+def backup_saves(path_ds_save: str, forced: bool = False, modified_minutes: int = 5) -> None:
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_timestamp: float = os.path.getmtime(path_file_name)
+ now_timestamp: float = 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
+ # Save backup. If less than modified_minutes have passed since the last modification of the file
+ is_modified: bool = (now_timestamp - save_timestamp) < (
+ modified_minutes * 60
+ )
+ ok: bool = 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}"
+ f"Reason: Forced={forced}, is modified file save={is_modified}"
)
if not ok:
continue
- file_name_backup = backup(path_file_name, now_timestamp)
+ file_name_backup: str = backup(path_file_name, now_timestamp)
log.debug(f"Saving backup: {file_name_backup}")
except:
@@ -46,9 +50,9 @@ def backup_saves(path_ds_save: str, forced=False):
time.sleep(5 * 60)
-def run(path_ds_save: str, timeout_minutes=5):
+def run(path_ds_save: str, timeout_minutes: int = 5) -> None:
# Example: r'~\Documents\NBGI\DarkSouls\*\DRAKS0005.sl2'
- path_ds_save = os.path.expanduser(path_ds_save)
+ path_ds_save: str = os.path.expanduser(path_ds_save)
print(f"{path_ds_save}\n")
@@ -58,4 +62,4 @@ def run(path_ds_save: str, timeout_minutes=5):
print(f"\nSleeping for {timeout_minutes} minutes\n")
time.sleep(timeout_minutes * 60)
- backup_saves(path_ds_save, False)
+ backup_saves(path_ds_save, forced=False, modified_minutes=timeout_minutes)
diff --git a/for_games/Dark Souls/backup__save_files/by_autosave/ds1.py b/for_games/Dark Souls/backup__save_files/by_autosave/ds1.py
index e7f0f86f9..3c11fadad 100644
--- a/for_games/Dark Souls/backup__save_files/by_autosave/ds1.py
+++ b/for_games/Dark Souls/backup__save_files/by_autosave/ds1.py
@@ -8,6 +8,7 @@
# OR r'~\Documents\NBGI\DarkSouls\*\*.sl2'
PATH_DS_SAVE = r"~\Documents\NBGI\DarkSouls\*\DRAKS0005.sl2"
+
if __name__ == "__main__":
from common import run
run(PATH_DS_SAVE)
diff --git a/for_games/Dark Souls/backup__save_files/by_autosave/ds1_remastered.py b/for_games/Dark Souls/backup__save_files/by_autosave/ds1_remastered.py
index b8b1b8cc4..4e484355a 100644
--- a/for_games/Dark Souls/backup__save_files/by_autosave/ds1_remastered.py
+++ b/for_games/Dark Souls/backup__save_files/by_autosave/ds1_remastered.py
@@ -8,6 +8,7 @@
# OR r'~\Documents\NBGI\DARK SOULS REMASTERED\*\*.sl2'
PATH_DS_SAVE = r"~\Documents\NBGI\DARK SOULS REMASTERED\*\DRAKS0005.sl2"
+
if __name__ == "__main__":
from common import run
run(PATH_DS_SAVE)
diff --git a/for_games/Dark Souls/backup__save_files/by_autosave/ds2.py b/for_games/Dark Souls/backup__save_files/by_autosave/ds2.py
index 954da1bd6..5bcf92e3a 100644
--- a/for_games/Dark Souls/backup__save_files/by_autosave/ds2.py
+++ b/for_games/Dark Souls/backup__save_files/by_autosave/ds2.py
@@ -7,6 +7,7 @@
# C:\Users\\AppData\Roaming\DarkSoulsII\*\*.sl2'
PATH_DS_SAVE = r"~\AppData\Roaming\DarkSoulsII\*\*.sl2"
+
if __name__ == "__main__":
from common import run
run(PATH_DS_SAVE)
diff --git a/for_games/Dark Souls/backup__save_files/by_autosave/ds3.py b/for_games/Dark Souls/backup__save_files/by_autosave/ds3.py
index 7c62dfe91..13523a69e 100644
--- a/for_games/Dark Souls/backup__save_files/by_autosave/ds3.py
+++ b/for_games/Dark Souls/backup__save_files/by_autosave/ds3.py
@@ -7,6 +7,7 @@
# C:\Users\\AppData\Roaming\DarkSoulsIII\*\*.sl2'
PATH_DS_SAVE = r"~\AppData\Roaming\DarkSoulsIII\*\*.sl2"
+
if __name__ == "__main__":
from common import run
run(PATH_DS_SAVE)
diff --git a/for_games/Dark Souls/backup__save_files/by_hotkey/common.py b/for_games/Dark Souls/backup__save_files/by_hotkey/common.py
index b7a1de068..345c3c0c4 100644
--- a/for_games/Dark Souls/backup__save_files/by_hotkey/common.py
+++ b/for_games/Dark Souls/backup__save_files/by_hotkey/common.py
@@ -21,7 +21,7 @@
import keyboard
-def beep():
+def beep() -> None:
try:
winsound.Beep(1000, duration=50)
except:
@@ -35,7 +35,7 @@ def beep():
HOTKEY = "CTRL + F1"
-def backup_saves(path_ds_save: str):
+def backup_saves(path_ds_save: str) -> None:
try:
for path_file_name in glob(path_ds_save):
file_name_backup = backup(path_file_name)
@@ -48,7 +48,7 @@ def backup_saves(path_ds_save: str):
time.sleep(5 * 60)
-def run(path_ds_save: str, hotkey=HOTKEY):
+def run(path_ds_save: str, hotkey: str = HOTKEY) -> None:
# Example: r'~\Documents\NBGI\DarkSouls\*\DRAKS0005.sl2'
path_ds_save = os.path.expanduser(path_ds_save)
diff --git a/for_games/Dark Souls/backup__save_files/by_hotkey/ds1.py b/for_games/Dark Souls/backup__save_files/by_hotkey/ds1.py
index 8eae6535b..f5c6c206f 100644
--- a/for_games/Dark Souls/backup__save_files/by_hotkey/ds1.py
+++ b/for_games/Dark Souls/backup__save_files/by_hotkey/ds1.py
@@ -8,6 +8,7 @@
# OR r'~\Documents\NBGI\DarkSouls\*\*.sl2'
PATH_DS_SAVE = r"~\Documents\NBGI\DarkSouls\*\DRAKS0005.sl2"
+
if __name__ == "__main__":
from common import run
# Example set another hotkey:
diff --git a/for_games/Dark Souls/backup__save_files/by_hotkey/ds1_remastered.py b/for_games/Dark Souls/backup__save_files/by_hotkey/ds1_remastered.py
index b8b1b8cc4..4e484355a 100644
--- a/for_games/Dark Souls/backup__save_files/by_hotkey/ds1_remastered.py
+++ b/for_games/Dark Souls/backup__save_files/by_hotkey/ds1_remastered.py
@@ -8,6 +8,7 @@
# OR r'~\Documents\NBGI\DARK SOULS REMASTERED\*\*.sl2'
PATH_DS_SAVE = r"~\Documents\NBGI\DARK SOULS REMASTERED\*\DRAKS0005.sl2"
+
if __name__ == "__main__":
from common import run
run(PATH_DS_SAVE)
diff --git a/for_games/Dark Souls/backup__save_files/by_hotkey/ds2.py b/for_games/Dark Souls/backup__save_files/by_hotkey/ds2.py
index 954da1bd6..5bcf92e3a 100644
--- a/for_games/Dark Souls/backup__save_files/by_hotkey/ds2.py
+++ b/for_games/Dark Souls/backup__save_files/by_hotkey/ds2.py
@@ -7,6 +7,7 @@
# C:\Users\\AppData\Roaming\DarkSoulsII\*\*.sl2'
PATH_DS_SAVE = r"~\AppData\Roaming\DarkSoulsII\*\*.sl2"
+
if __name__ == "__main__":
from common import run
run(PATH_DS_SAVE)
diff --git a/for_games/Dark Souls/backup__save_files/by_hotkey/ds3.py b/for_games/Dark Souls/backup__save_files/by_hotkey/ds3.py
index 7c62dfe91..13523a69e 100644
--- a/for_games/Dark Souls/backup__save_files/by_hotkey/ds3.py
+++ b/for_games/Dark Souls/backup__save_files/by_hotkey/ds3.py
@@ -7,6 +7,7 @@
# C:\Users\\AppData\Roaming\DarkSoulsIII\*\*.sl2'
PATH_DS_SAVE = r"~\AppData\Roaming\DarkSoulsIII\*\*.sl2"
+
if __name__ == "__main__":
from common import run
run(PATH_DS_SAVE)
diff --git a/for_games/Dark Souls/backup__save_files/utils.py b/for_games/Dark Souls/backup__save_files/utils.py
index ff8205783..3ae76339f 100644
--- a/for_games/Dark Souls/backup__save_files/utils.py
+++ b/for_games/Dark Souls/backup__save_files/utils.py
@@ -11,7 +11,7 @@
import sys
-def get_logger(name):
+def get_logger(name: str) -> logging.Logger:
log = logging.getLogger(name)
log.setLevel(logging.DEBUG)
@@ -24,7 +24,7 @@ def get_logger(name):
return log
-def backup(path_file_name: str, now_timestamp=None) -> str:
+def backup(path_file_name: str, now_timestamp: float | None = None) -> str:
if now_timestamp is None:
now_timestamp = time.time()
diff --git a/for_games/Dark Souls/common.py b/for_games/Dark Souls/common.py
index 5ecfb72cf..ae3d620bf 100644
--- a/for_games/Dark Souls/common.py
+++ b/for_games/Dark Souls/common.py
@@ -17,7 +17,7 @@
class Parser:
- def __init__(self, url_locations: str, log=True):
+ def __init__(self, url_locations: str, log=True) -> None:
self._url_locations = url_locations
self._is_log = log
@@ -43,7 +43,7 @@ def DS2(log=True) -> "Parser":
def DS3(log=True) -> "Parser":
return Parser(URL_DS3, log)
- def _log(self, *args, **kwargs):
+ def _log(self, *args, **kwargs) -> None:
self._is_log and print(*args, **kwargs)
@staticmethod
diff --git a/for_games/Dark Souls/generate_HTML_location_graph__with_d3js/generate_graph_html.py b/for_games/Dark Souls/generate_HTML_location_graph__with_d3js/generate_graph_html.py
index ce3f9bbd7..edeadb617 100644
--- a/for_games/Dark Souls/generate_HTML_location_graph__with_d3js/generate_graph_html.py
+++ b/for_games/Dark Souls/generate_HTML_location_graph__with_d3js/generate_graph_html.py
@@ -4,6 +4,12 @@
__author__ = "ipetrash"
+from pathlib import Path
+
+
+DIR: Path = Path(__file__).resolve().parent
+
+
def get_dataset_text(links: list[tuple[str, str]]) -> str:
# Список всех узлов графа
nodes = set()
@@ -54,16 +60,17 @@ def get_dataset_text(links: list[tuple[str, str]]) -> str:
def generate(
- file_name_to: str,
+ file_name_to: str | Path,
links: list[tuple[str, str]],
title: str = "Graph",
-):
+ file_name_template: str | Path = DIR / "template_graph.html",
+) -> None:
template_title = "{{title}}"
template_dataset = "{{dataset}}"
dataset_text = get_dataset_text(links)
- with open("template_graph.html", "r", encoding="utf-8") as f:
+ with open(file_name_template, "r", encoding="utf-8") as f:
text = f.read()
text = text.replace(template_title, title)
text = text.replace(template_dataset, dataset_text)
diff --git a/for_games/Dark Souls/ru_darksouls_wikia_com__fandom__bosses/print_stats_by_health_and_souls/_utils.py b/for_games/Dark Souls/ru_darksouls_wikia_com__fandom__bosses/print_stats_by_health_and_souls/_utils.py
index a6ed481dd..f5bd70785 100644
--- a/for_games/Dark Souls/ru_darksouls_wikia_com__fandom__bosses/print_stats_by_health_and_souls/_utils.py
+++ b/for_games/Dark Souls/ru_darksouls_wikia_com__fandom__bosses/print_stats_by_health_and_souls/_utils.py
@@ -228,7 +228,7 @@ def print_stats(
rows: List[Tuple[str, int, int]],
headers=("NAME", "HEALTH", "SOULS"),
sort_column=1,
-):
+) -> None:
rows.sort(key=lambda x: x[sort_column], reverse=True)
rows.insert(0, headers)
diff --git a/for_games/Dark Souls/ru_darksouls_wikia_com__fandom__bosses/utils.py b/for_games/Dark Souls/ru_darksouls_wikia_com__fandom__bosses/utils.py
index 4b897e8e2..c0d5129c4 100644
--- a/for_games/Dark Souls/ru_darksouls_wikia_com__fandom__bosses/utils.py
+++ b/for_games/Dark Souls/ru_darksouls_wikia_com__fandom__bosses/utils.py
@@ -85,7 +85,7 @@ def get_bosses(url: str) -> Dict[str, List[Boss]]:
return bosses_by_category
-def print_bosses(url: str, bosses: Dict[str, List[Boss]]):
+def print_bosses(url: str, bosses: Dict[str, List[Boss]]) -> None:
total = sum(len(i) for i in bosses.values())
print(f"{url} ({total}):")
@@ -108,7 +108,7 @@ def convert_bosses_to_only_name(bosses: Dict[str, List[Boss]]) -> Dict[str, List
return bosses_only_name
-def export_to_json(file_name, bosses):
+def export_to_json(file_name, bosses) -> None:
dir_name = os.path.dirname(file_name)
os.makedirs(dir_name, exist_ok=True)
@@ -120,7 +120,7 @@ def export_to_json(file_name, bosses):
)
-def export_to_sqlite(file_name: str, bosses_ds123: Dict[str, Dict[str, List[Boss]]]):
+def export_to_sqlite(file_name: str, bosses_ds123: Dict[str, Dict[str, List[Boss]]]) -> None:
dir_name = os.path.dirname(file_name)
os.makedirs(dir_name, exist_ok=True)
diff --git "a/for_games/Demons Souls/demonssouls_fandom_com__ru__wiki__\320\221\320\276\321\201\321\201\321\213/main.py" "b/for_games/Demons Souls/demonssouls_fandom_com__ru__wiki__\320\221\320\276\321\201\321\201\321\213/main.py"
index aba252d9f..aecf16556 100644
--- "a/for_games/Demons Souls/demonssouls_fandom_com__ru__wiki__\320\221\320\276\321\201\321\201\321\213/main.py"
+++ "b/for_games/Demons Souls/demonssouls_fandom_com__ru__wiki__\320\221\320\276\321\201\321\201\321\213/main.py"
@@ -55,7 +55,7 @@ def get_bosses(url: str) -> dict[str, list[Boss]]:
return bosses_by_category
-def print_bosses(url: str, bosses: dict[str, list[Boss]]):
+def print_bosses(url: str, bosses: dict[str, list[Boss]]) -> None:
total = sum(len(i) for i in bosses.values())
print(f"{url} ({total}):")
@@ -78,7 +78,7 @@ def convert_bosses_to_only_name(bosses: dict[str, list[Boss]]) -> dict[str, list
return bosses_only_name
-def export_to_json(file_name, bosses):
+def export_to_json(file_name, bosses) -> None:
dir_name = os.path.dirname(file_name)
os.makedirs(dir_name, exist_ok=True)
@@ -90,7 +90,7 @@ def export_to_json(file_name, bosses):
)
-def export_to_simple_text(file_name, bosses):
+def export_to_simple_text(file_name, bosses) -> None:
dir_name = os.path.dirname(file_name)
os.makedirs(dir_name, exist_ok=True)
diff --git a/for_games/Final_Fantasy_II__upgrade_skills_bot.py b/for_games/Final_Fantasy_II__upgrade_skills_bot.py
index 4a64756a3..d28f9f1a8 100644
--- a/for_games/Final_Fantasy_II__upgrade_skills_bot.py
+++ b/for_games/Final_Fantasy_II__upgrade_skills_bot.py
@@ -44,7 +44,7 @@ def get_logger(name, file="log.txt", encoding="utf-8", log_stdout=True, log_file
return log
-def change_start():
+def change_start() -> None:
DATA["START"] = not DATA["START"]
if DATA["START"]:
DATA["COUNTER"] = 0
@@ -52,17 +52,17 @@ def change_start():
log.debug("Change START: %s", DATA["START"])
-def set_min_counter():
+def set_min_counter() -> None:
DATA["MAX_COUNTER"] = 1
log.debug("Change MAX_COUNTER: %s", DATA["MAX_COUNTER"])
-def set_max_counter():
+def set_max_counter() -> None:
DATA["MAX_COUNTER"] = MAX_COUNTER
log.debug("Change MAX_COUNTER: %s", DATA["MAX_COUNTER"])
-def change_cancel_all_last():
+def change_cancel_all_last() -> None:
DATA["CANCEL_ALL_LAST"] = not DATA["CANCEL_ALL_LAST"]
log.debug("Change CANCEL_ALL_LAST: %s", DATA["CANCEL_ALL_LAST"])
diff --git a/for_games/Hollow_Knight/export_to_json.py b/for_games/Hollow_Knight/export_to_json.py
index 2a5100126..79d6c03c9 100644
--- a/for_games/Hollow_Knight/export_to_json.py
+++ b/for_games/Hollow_Knight/export_to_json.py
@@ -14,7 +14,7 @@ def export_to_json_str(bosses: dict[str, list]) -> str:
return json.dumps(bosses, ensure_ascii=False, indent=4)
-def export_to_json(file_name: str, bosses: dict[str, list]):
+def export_to_json(file_name: str, bosses: dict[str, list]) -> None:
dir_name = os.path.dirname(file_name)
os.makedirs(dir_name, exist_ok=True)
diff --git a/for_games/Hollow_Knight/print_bosses.py b/for_games/Hollow_Knight/print_bosses.py
index bc1ded54a..68fb34975 100644
--- a/for_games/Hollow_Knight/print_bosses.py
+++ b/for_games/Hollow_Knight/print_bosses.py
@@ -7,7 +7,7 @@
from utils import get_bosses, Boss
-def print_bosses(bosses: dict[str, list[Boss]], only_names=False):
+def print_bosses(bosses: dict[str, list[Boss]], only_names=False) -> None:
total = sum(len(i) for i in bosses.values())
print(f"Hollow Knight ({total}):")
diff --git a/for_games/RMMZ_update_fields_CGMZ_GameInfo.py b/for_games/RMMZ_update_fields_CGMZ_GameInfo.py
new file mode 100644
index 000000000..f48025af3
--- /dev/null
+++ b/for_games/RMMZ_update_fields_CGMZ_GameInfo.py
@@ -0,0 +1,98 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+__author__ = "ipetrash"
+
+
+import json
+import subprocess
+
+import re
+
+from pathlib import Path
+
+
+PATTERN_FIELD_VERSION: re.Pattern = re.compile(r'(?P"Left Text"\s*:\s*)".+?"')
+
+
+def process(path_dir: Path, forced: bool = False) -> None:
+ path_js_plugins: Path = path_dir / "js" / "plugins.js"
+
+ # If the last commit was in path_js_plugins, then skipping the file change
+ result: bytes = subprocess.check_output(
+ args=["git", "diff", "HEAD~1", "HEAD", path_js_plugins],
+ cwd=path_dir,
+ )
+ if result and not forced:
+ print(
+ f"Skipping change {path_js_plugins} because the file is in the last commit"
+ )
+ return
+
+ result: str = subprocess.check_output(
+ args=[
+ "git",
+ "log",
+ "-1",
+ '--pretty=format:{"hash":"%h","datetime":"%ad"}',
+ "--date=format:%Y-%m-%d %H:%M:%S",
+ ],
+ cwd=path_dir,
+ encoding="utf-8",
+ )
+ print("Git log:", result)
+
+ data: dict[str, str] = json.loads(result)
+ print("Git log (parsed):", data)
+
+ version: str = f'v{data["hash"]} ({data["datetime"]})'
+ print(f"Version: {version}")
+
+ js_plugins: str = path_js_plugins.read_text(encoding="utf-8")
+ lines: list[str] = js_plugins.splitlines()
+ for i, line in enumerate(lines):
+ if "CGMZ_GameInfo" not in line:
+ continue
+
+ print(f"Found line {line!r}")
+
+ m = PATTERN_FIELD_VERSION.search(line)
+ if not m:
+ continue
+
+ current_field_version: str = m.group()
+
+ new_field_version: str = PATTERN_FIELD_VERSION.sub(
+ rf'\g"{version}"', current_field_version
+ )
+ print(f"Replace: {current_field_version!r} -> {new_field_version!r}")
+
+ lines[i] = line.replace(current_field_version, new_field_version)
+
+ print(f"Write to {path_js_plugins}")
+ path_js_plugins.write_text("\n".join(lines) + "\n", encoding="utf-8")
+
+
+if __name__ == "__main__":
+ import argparse
+
+ parser = argparse.ArgumentParser(
+ description="Writes information about the last git commit to the CGMZ_GameInfo plugin parameter."
+ )
+ parser.add_argument(
+ "path_project",
+ help="Path to project (the directory containing game.rmmzproject)",
+ type=Path,
+ )
+ parser.add_argument(
+ "--forced",
+ help="Force update",
+ action="store_true",
+ default=False,
+ )
+ args = parser.parse_args()
+
+ process(
+ path_dir=args.path_project,
+ forced=args.forced,
+ )
diff --git a/for_games/Salt and Sanctuary parse bosses saltandsanctuary.fandom.com/export_to_json.py b/for_games/Salt and Sanctuary parse bosses saltandsanctuary.fandom.com/export_to_json.py
index 2c9f8e7d1..2f81f91e2 100644
--- a/for_games/Salt and Sanctuary parse bosses saltandsanctuary.fandom.com/export_to_json.py
+++ b/for_games/Salt and Sanctuary parse bosses saltandsanctuary.fandom.com/export_to_json.py
@@ -14,7 +14,7 @@ def export_to_json_str(bosses: dict[str, list[str]]) -> str:
return json.dumps(bosses, ensure_ascii=False, indent=4)
-def export_to_json(file_name: str, bosses: dict[str, list[str]]):
+def export_to_json(file_name: str, bosses: dict[str, list[str]]) -> None:
dir_name = os.path.dirname(file_name)
os.makedirs(dir_name, exist_ok=True)
diff --git a/for_games/Salt and Sanctuary parse bosses saltandsanctuary.fandom.com/print_bosses.py b/for_games/Salt and Sanctuary parse bosses saltandsanctuary.fandom.com/print_bosses.py
index b190b08b5..7ce020059 100644
--- a/for_games/Salt and Sanctuary parse bosses saltandsanctuary.fandom.com/print_bosses.py
+++ b/for_games/Salt and Sanctuary parse bosses saltandsanctuary.fandom.com/print_bosses.py
@@ -7,7 +7,7 @@
from utils import get_bosses, Boss
-def print_bosses(bosses: dict[str, list[Boss]], only_names=False):
+def print_bosses(bosses: dict[str, list[Boss]], only_names=False) -> None:
total = sum(len(i) for i in bosses.values())
print(f"Salt and Sanctuary ({total}):")
diff --git a/html_parsing/denuvo__get_games/game_debate_com.py b/for_games/denuvo__get_games/game_debate_com.py
similarity index 100%
rename from html_parsing/denuvo__get_games/game_debate_com.py
rename to for_games/denuvo__get_games/game_debate_com.py
diff --git a/html_parsing/denuvo__get_games/gamicus_fandom_com__wiki__Denuvo.py b/for_games/denuvo__get_games/gamicus_fandom_com__wiki__Denuvo.py
similarity index 100%
rename from html_parsing/denuvo__get_games/gamicus_fandom_com__wiki__Denuvo.py
rename to for_games/denuvo__get_games/gamicus_fandom_com__wiki__Denuvo.py
diff --git a/html_parsing/denuvo__get_games/store_steampowered_com__curator__26095454_Denuvo_Games__list__38826.py b/for_games/denuvo__get_games/store_steampowered_com__curator__26095454_Denuvo_Games__list__38826.py
similarity index 100%
rename from html_parsing/denuvo__get_games/store_steampowered_com__curator__26095454_Denuvo_Games__list__38826.py
rename to for_games/denuvo__get_games/store_steampowered_com__curator__26095454_Denuvo_Games__list__38826.py
diff --git a/html_parsing/exclusive_games/common.py b/for_games/exclusive_games/common.py
similarity index 100%
rename from html_parsing/exclusive_games/common.py
rename to for_games/exclusive_games/common.py
diff --git a/html_parsing/exclusive_games/ps3__gematsu_com.py b/for_games/exclusive_games/ps3__gematsu_com.py
similarity index 100%
rename from html_parsing/exclusive_games/ps3__gematsu_com.py
rename to for_games/exclusive_games/ps3__gematsu_com.py
diff --git a/html_parsing/exclusive_games/ps4__gematsu_com.py b/for_games/exclusive_games/ps4__gematsu_com.py
similarity index 100%
rename from html_parsing/exclusive_games/ps4__gematsu_com.py
rename to for_games/exclusive_games/ps4__gematsu_com.py
diff --git a/html_parsing/exclusive_games/ps4__ru_wikipedia_org.py b/for_games/exclusive_games/ps4__ru_wikipedia_org.py
similarity index 100%
rename from html_parsing/exclusive_games/ps4__ru_wikipedia_org.py
rename to for_games/exclusive_games/ps4__ru_wikipedia_org.py
diff --git a/html_parsing/exclusive_games/psp__listal_com.py b/for_games/exclusive_games/psp__listal_com.py
similarity index 100%
rename from html_parsing/exclusive_games/psp__listal_com.py
rename to for_games/exclusive_games/psp__listal_com.py
diff --git a/html_parsing/exclusive_games/switch__gematsu_com.py b/for_games/exclusive_games/switch__gematsu_com.py
similarity index 100%
rename from html_parsing/exclusive_games/switch__gematsu_com.py
rename to for_games/exclusive_games/switch__gematsu_com.py
diff --git a/html_parsing/exclusive_games/wii_u__gematsu_com.py b/for_games/exclusive_games/wii_u__gematsu_com.py
similarity index 100%
rename from html_parsing/exclusive_games/wii_u__gematsu_com.py
rename to for_games/exclusive_games/wii_u__gematsu_com.py
diff --git a/html_parsing/exclusive_games/xbox_360__gematsu_com.py b/for_games/exclusive_games/xbox_360__gematsu_com.py
similarity index 100%
rename from html_parsing/exclusive_games/xbox_360__gematsu_com.py
rename to for_games/exclusive_games/xbox_360__gematsu_com.py
diff --git a/html_parsing/exclusive_games/xbox_360__ru_wikipedia_org.py b/for_games/exclusive_games/xbox_360__ru_wikipedia_org.py
similarity index 100%
rename from html_parsing/exclusive_games/xbox_360__ru_wikipedia_org.py
rename to for_games/exclusive_games/xbox_360__ru_wikipedia_org.py
diff --git a/html_parsing/exclusive_games/xbox_one__gematsu_com.py b/for_games/exclusive_games/xbox_one__gematsu_com.py
similarity index 100%
rename from html_parsing/exclusive_games/xbox_one__gematsu_com.py
rename to for_games/exclusive_games/xbox_one__gematsu_com.py
diff --git a/html_parsing/exclusive_games/xbox_one__ru_wikipedia_org.py b/for_games/exclusive_games/xbox_one__ru_wikipedia_org.py
similarity index 100%
rename from html_parsing/exclusive_games/xbox_one__ru_wikipedia_org.py
rename to for_games/exclusive_games/xbox_one__ru_wikipedia_org.py
diff --git a/for_games/fitgirl_RE_Village_with_win10_notify.py b/for_games/fitgirl_RE_Village_with_win10_notify.py
index 99ce1dd7b..e0b5e42f8 100644
--- a/for_games/fitgirl_RE_Village_with_win10_notify.py
+++ b/for_games/fitgirl_RE_Village_with_win10_notify.py
@@ -25,7 +25,7 @@
from run_notify import run_in_thread
-def show_notify(name: str):
+def show_notify(name: str) -> None:
title = f"Уведомление за {datetime.now():%Y/%m/%d %H:%M:%S}"
print(f"{title}\n{name}\n")
diff --git a/game__get_random_rarity_item.py b/for_games/game__get_random_rarity_item.py
similarity index 92%
rename from game__get_random_rarity_item.py
rename to for_games/game__get_random_rarity_item.py
index 6808f27be..b0afc828b 100644
--- a/game__get_random_rarity_item.py
+++ b/for_games/game__get_random_rarity_item.py
@@ -7,7 +7,7 @@
import random
-def get_random_rarity_item():
+def get_random_rarity_item() -> str:
a = random.randrange(1, 1000)
if a <= 10:
diff --git a/for_games/gamestatus_info__lastcrackedgames.py b/for_games/gamestatus_info__lastcrackedgames.py
index 961bc246d..0c5e7ca26 100644
--- a/for_games/gamestatus_info__lastcrackedgames.py
+++ b/for_games/gamestatus_info__lastcrackedgames.py
@@ -8,6 +8,7 @@
from dataclasses import dataclass
from datetime import date
+from typing import Any
import requests
@@ -26,20 +27,23 @@ class Game:
title: str
url: str
protection: str
+ hacked_groups: str
release_date: date
crack_date: 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))
+ def from_dict(cls, data: dict[str, Any]) -> "Game":
+ def _get_title(key: str) -> str:
+ title: str = data[key]
+ if title.startswith("["):
+ title = ", ".join(json.loads(title))
+ return title
return cls(
title=data["title"],
url=f'{URL_BASE}/{data["slug"]}',
- protection=protection,
+ protection=_get_title("protections"),
+ hacked_groups=_get_title("hacked_groups"),
release_date=date.fromisoformat(data["release_date"]),
crack_date=date.fromisoformat(data["crack_date"]),
)
@@ -49,7 +53,7 @@ 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"]]
+ return [Game.from_dict(game) for game in rs.json()["list_crack_games"]]
if __name__ == "__main__":
@@ -59,11 +63,13 @@ def get_games() -> list[Game]:
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))
+ 1. Game(title='RAIDOU Remastered: The Mystery of the Soulless Army', url='https://gamestatus.info/raidou-remastered-the-mystery-of-the-soulless-army', protection='DRM', hacked_groups='DenuvOwO Hypervisor — это обход защиты, при котором обычно необходимо отключить защиту ПК, что делает ваше устройство более уязвимым. Будьте осторожны!', release_date=datetime.date(2025, 6, 18), crack_date=datetime.date(2026, 3, 31))
+ 2. Game(title="Sid Meier's Civilization® VII - Founders Edition", url='https://gamestatus.info/sid-meiers-civilization-vii', protection='Denuvo Anti-tamper', hacked_groups='DenuvOwO Hypervisor — это обход защиты, при котором обычно необходимо отключить защиту ПК, что делает ваше устройство более уязвимым. Будьте осторожны!', release_date=datetime.date(2025, 2, 10), crack_date=datetime.date(2026, 3, 31))
+ 3. Game(title='Persona 4 Arena Ultimax', url='https://gamestatus.info/persona-4-arena-ultimax', protection='DENUVO', hacked_groups='DenuvOwO Hypervisor — это обход защиты, при котором обычно необходимо отключить защиту ПК, что делает ваше устройство более уязвимым. Будьте осторожны!', release_date=datetime.date(2022, 3, 16), crack_date=datetime.date(2026, 3, 30))
+ 4. Game(title='SUPER ROBOT WARS Y', url='https://gamestatus.info/super-robot-wars-y', protection='Denuvo Anti-Tamper', hacked_groups='DenuvOwO Hypervisor — это обход защиты, при котором обычно необходимо отключить защиту ПК, что делает ваше устройство более уязвимым. Будьте осторожны!', release_date=datetime.date(2025, 8, 27), crack_date=datetime.date(2026, 3, 30))
+ 5. Game(title='Hozy', url='https://gamestatus.info/hozy', protection='STEAM', hacked_groups='RUNE', release_date=datetime.date(2026, 3, 30), crack_date=datetime.date(2026, 3, 30))
...
- 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))
+ 198. Game(title='Under The Island', url='https://gamestatus.info/under-the-island', protection='GOG', hacked_groups='DRM-Free', release_date=datetime.date(2026, 2, 17), crack_date=datetime.date(2026, 2, 17))
+ 199. Game(title='Fisherman Simulator', url='https://gamestatus.info/fisherman-simulator', protection='STEAM', hacked_groups='Tenoke', release_date=datetime.date(2026, 2, 13), crack_date=datetime.date(2026, 2, 17))
+ 200. Game(title='Styx: Blades of Greed', url='https://gamestatus.info/styx-blades-of-greed', protection='STEAM', hacked_groups='RUNE', release_date=datetime.date(2026, 2, 19), crack_date=datetime.date(2026, 2, 17))
"""
diff --git a/for_games/gamesvoice_ru__get_finished.py b/for_games/gamesvoice_ru__get_finished.py
new file mode 100644
index 000000000..013095374
--- /dev/null
+++ b/for_games/gamesvoice_ru__get_finished.py
@@ -0,0 +1,80 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+__author__ = "ipetrash"
+
+
+from dataclasses import dataclass
+from datetime import datetime, date
+from typing import Any
+
+import requests
+
+
+@dataclass
+class Game:
+ title_eng: str
+ title_rus: str | None
+ url: str
+ published_at: date
+
+
+session = requests.session()
+session.headers[
+ "User-Agent"
+] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0"
+
+
+def get_games() -> list[Game]:
+ rs = session.get(
+ "https://api.gamesvoice.ru/api/products/all",
+ params=dict(
+ is_develop=0,
+ not_by_published=1, # По дате выхода (новые-старые)
+ ),
+ )
+ rs.raise_for_status()
+
+ result: dict[str, Any] = rs.json()
+ assert result.get("success"), "Получен неуспешный ответ"
+
+ # NOTE Пример obj:
+ """
+ {
+ "id": 7,
+ "title_eng": "Alan Wake II",
+ "title_rus": null,
+ "preview": "https://api.gamesvoice.ru/storage/uploads/products/previews/37c4e26ba5c1b074367b6a8d2c53ad3e.png",
+ "search_preview": "https://api.gamesvoice.ru/storage/uploads/products/search_previews/0f590f1f3951cdd1c6271c8af62ef431.png",
+ "search_description": "\u003Cp\u003EAlan Wake II — это игра в жанре survival-horror от третьего лица. Игрокам предсто",
+ "alias": "alanwake2",
+ "published_at": "2025-04-17 00:00:00",
+ "renewal_at": "2025-05-05 00:00:00"
+ }
+ """
+ return [
+ Game(
+ title_eng=obj["title_eng"],
+ title_rus=obj["title_rus"],
+ url=f"https://www.gamesvoice.ru/product/{obj['alias']}",
+ published_at=datetime.fromisoformat(obj["published_at"]).date(),
+ )
+ for obj in result["data"]
+ ]
+
+
+if __name__ == "__main__":
+ items = get_games()
+ print(f"Games ({len(items)}):")
+ for i, game in enumerate(items, 1):
+ print(f" {i}. {game}")
+ """
+ Games (71):
+ 1. Game(title_eng='Volkolak: The Will of Gods', title_rus='Волколак: Воля Богов', url='https://www.gamesvoice.ru/product/volkolak', published_at=datetime.date(2025, 8, 22))
+ 2. Game(title_eng='One-Eyed Likho', title_rus='ЛИХО ОДНОГЛАЗОЕ', url='https://www.gamesvoice.ru/product/likho', published_at=datetime.date(2025, 7, 28))
+ 3. Game(title_eng='Silent Hill 2', title_rus='Сайлент Хилл 2', url='https://www.gamesvoice.ru/product/silenthill2', published_at=datetime.date(2025, 6, 28))
+ ...
+ 69. Game(title_eng='Panzer Corps', title_rus=None, url='https://www.gamesvoice.ru/product/panzercorps', published_at=datetime.date(2012, 7, 8))
+ 70. Game(title_eng='Mad Riders', title_rus=None, url='https://www.gamesvoice.ru/product/madriders', published_at=datetime.date(2012, 7, 3))
+ 71. Game(title_eng='Defenders of Ardania', title_rus=None, url='https://www.gamesvoice.ru/product/doa', published_at=datetime.date(2012, 5, 26))
+ """
diff --git a/html_parsing/gametime__use_cubiq_ru/extract_all_games.json b/for_games/gametime__use_cubiq_ru/extract_all_games.json
similarity index 100%
rename from html_parsing/gametime__use_cubiq_ru/extract_all_games.json
rename to for_games/gametime__use_cubiq_ru/extract_all_games.json
diff --git a/html_parsing/gametime__use_cubiq_ru/extract_all_games.py b/for_games/gametime__use_cubiq_ru/extract_all_games.py
similarity index 100%
rename from html_parsing/gametime__use_cubiq_ru/extract_all_games.py
rename to for_games/gametime__use_cubiq_ru/extract_all_games.py
diff --git a/html_parsing/gametime__use_cubiq_ru/extract_all_games__show_the_shortest_time.py b/for_games/gametime__use_cubiq_ru/extract_all_games__show_the_shortest_time.py
similarity index 100%
rename from html_parsing/gametime__use_cubiq_ru/extract_all_games__show_the_shortest_time.py
rename to for_games/gametime__use_cubiq_ru/extract_all_games__show_the_shortest_time.py
diff --git a/html_parsing/gametime__use_cubiq_ru/found_from_local_files.py b/for_games/gametime__use_cubiq_ru/found_from_local_files.py
similarity index 100%
rename from html_parsing/gametime__use_cubiq_ru/found_from_local_files.py
rename to for_games/gametime__use_cubiq_ru/found_from_local_files.py
diff --git a/html_parsing/gametime__use_cubiq_ru/main.py b/for_games/gametime__use_cubiq_ru/main.py
similarity index 100%
rename from html_parsing/gametime__use_cubiq_ru/main.py
rename to for_games/gametime__use_cubiq_ru/main.py
diff --git a/for_games/grid_clicker/grid_clicker.py b/for_games/grid_clicker/grid_clicker.py
new file mode 100644
index 000000000..1f99c3c26
--- /dev/null
+++ b/for_games/grid_clicker/grid_clicker.py
@@ -0,0 +1,204 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+__author__ = "ipetrash"
+
+
+import argparse
+import threading
+import time
+import sys
+
+# pip install pyautogui==0.9.54
+import pyautogui
+
+# pip install pynput==1.8.1
+from pynput import mouse
+
+# pip install screeninfo==0.8.1
+from screeninfo import Monitor, get_monitors
+
+
+pyautogui.FAILSAFE = False
+
+# Глобальный флаг работы
+is_running = True
+
+
+def on_click(x, y, button, pressed) -> bool | None:
+ global is_running
+ # Если нажата правая кнопка мыши (любая: нажатие или отпускание)
+ if button == mouse.Button.right:
+ print("\n[!] Обнаружен клик правой кнопкой. Останавливаю...")
+ is_running = False
+ return False # Останавливает сам слушатель pynput
+
+
+def start_click_listener() -> None:
+ # Запуск прослушивания мыши в отдельном потоке
+ with mouse.Listener(on_click=on_click) as listener:
+ listener.join()
+
+
+def click_all_on_screen(
+ step: int = 50,
+ do_click: bool = False,
+ sleep_time_before_starting_secs: int = 3,
+ # Режим 1: Прямые координаты
+ coords: tuple[int, int, int, int] | None = None, # (x1, y1, x2, y2)
+ # Режим 2: По номеру монитора
+ monitor_number: int = 1,
+ sleep_time_between_clicks_ms: int = 10,
+ offset_top: int = 50, # Защитный отступ сверху
+ offset_bottom: int = 50, # Защитный отступ снизу
+ offset_left: int = 50, # Защитный отступ слева
+ offset_right: int = 50, # Защитный отступ справа
+) -> None:
+ global is_running
+
+ if not do_click:
+ print("[!] ВНИМАНИЕ: Режим имитации включен, клики производиться не будут.")
+
+ # Запуск потока для-прерывателя
+ threading.Thread(target=start_click_listener, daemon=True).start()
+
+ # Режим 1: Прямые координаты
+ if coords:
+ start_x, start_y, end_x, end_y = coords
+ print(f"Режим ручных координат: X({start_x}..{end_x}), Y({start_y}..{end_y})")
+
+ # Режим 2: По номеру монитора
+ else:
+ monitors = get_monitors()
+ try:
+ monitor: Monitor = monitors[monitor_number - 1]
+ except IndexError:
+ print(f"[#] Ошибка: Номер монитора должен быть от 1 до {len(monitors)}")
+ return
+
+ # Определяем границы с учетом отступов
+ start_x = monitor.x + offset_left
+ end_x = monitor.x + monitor.width - offset_right
+ start_y = monitor.y + offset_top
+ end_y = monitor.y + monitor.height - offset_bottom
+ print(f"Режим монитора #{monitor_number} с отступами.")
+
+ print(f"Область: X от {start_x} до {end_x}, Y от {start_y} до {end_y}")
+ print(f"Шаг: {step}px")
+
+ sleep_time_between_clicks_secs: float = sleep_time_between_clicks_ms / 1000
+
+ time.sleep(sleep_time_before_starting_secs)
+
+ print("Работаю. Нажми ПРАВУЮ кнопку мыши для стопа.")
+
+ for y in range(start_y, end_y, step):
+ for x in range(start_x, end_x, step):
+ if not is_running:
+ print("Программа экстренно завершена.")
+ return
+
+ pyautogui.moveTo(x, y)
+ if do_click:
+ pyautogui.click(x, y)
+ time.sleep(sleep_time_between_clicks_secs)
+
+ print("Готово!")
+
+
+def main() -> None:
+ parser = argparse.ArgumentParser(
+ description="GridClicker: Скрипт для протыкивания экрана по сетке.",
+ # Этот параметр автоматически добавит (default: ...) в описание каждого аргумента
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter,
+ )
+
+ base_group = parser.add_argument_group("Основные настройки")
+ base_group.add_argument("-s", "--step", type=int, default=50, help="Шаг сетки")
+ base_group.add_argument(
+ "--do-click",
+ action="store_true",
+ help="РАЗРЕШИТЬ клики (без этого флага только перемещение курсора)",
+ )
+
+ base_group.add_argument(
+ "-t",
+ "--sleep_time_before_starting_secs",
+ type=int,
+ default=3,
+ dest="sleep_time_before_starting_secs",
+ help="Задержка перед началом (сек)",
+ )
+ base_group.add_argument(
+ "-tc",
+ "--sleep",
+ type=int,
+ default=10,
+ dest="sleep_time_between_clicks_ms",
+ help="Задержка (мс)",
+ )
+
+ # Новая группа для координат
+ coord_group = parser.add_argument_group(
+ "Режим конкретных координат (игнорирует настройки монитора)"
+ )
+ coord_group.add_argument("--x1", type=int, help="Левая граница")
+ coord_group.add_argument("--y1", type=int, help="Верхняя граница")
+ coord_group.add_argument("--x2", type=int, help="Правая граница")
+ coord_group.add_argument("--y2", type=int, help="Нижняя граница")
+
+ monitor_group = parser.add_argument_group(
+ "Настройки монитора (используются, если не заданы x1,y1,x2,y2)"
+ )
+ monitor_group.add_argument(
+ "-m", "--monitor", type=int, default=1, dest="monitor_number"
+ )
+ monitor_group.add_argument("--top", type=int, default=50, dest="offset_top")
+ monitor_group.add_argument("--bottom", type=int, default=50, dest="offset_bottom")
+ monitor_group.add_argument("--left", type=int, default=50, dest="offset_left")
+ monitor_group.add_argument("--right", type=int, default=50, dest="offset_right")
+
+ args = parser.parse_args()
+
+ # Если скрипт запущен без аргументов — показываем help и выходим
+ if len(sys.argv) == 1:
+ parser.print_help()
+ sys.exit(0)
+
+ # Собираем координаты в кортеж, если они все указаны
+ coords: tuple[int, int, int, int] | None = None
+ if all(v is not None for v in [args.x1, args.y1, args.x2, args.y2]):
+ coords = args.x1, args.y1, args.x2, args.y2
+
+ click_all_on_screen(
+ step=args.step,
+ do_click=args.do_click,
+ sleep_time_before_starting_secs=args.sleep_time_before_starting_secs,
+ sleep_time_between_clicks_ms=args.sleep_time_between_clicks_ms,
+ coords=coords,
+ monitor_number=args.monitor_number,
+ offset_top=args.offset_top,
+ offset_bottom=args.offset_bottom,
+ offset_left=args.offset_left,
+ offset_right=args.offset_right,
+ )
+
+
+if __name__ == "__main__":
+ # NOTE: Пример использования, режим прямых координат
+ # NOTE: grid_clicker.py --x1=-545 --y1=322 --x2=-42 --y2=1073
+ # NOTE: grid_clicker.py --do-click --x1=-545 --y1=322 --x2=-42 --y2=1073
+ # click_all_on_screen(
+ # step=100,
+ # coords=(-545, 322, -42, 1073),
+ # )
+
+ # NOTE: Пример использования, режим монитора
+ # click_all_on_screen(
+ # step=100,
+ # monitor_number=2,
+ # # sleep_time_between_clicks_ms=0.005, # 5 мс
+ # offset_bottom=150,
+ # )
+
+ main()
diff --git a/for_games/grid_clicker/grid_clicker_gui_select_rect.py b/for_games/grid_clicker/grid_clicker_gui_select_rect.py
new file mode 100644
index 000000000..eb5a8fff1
--- /dev/null
+++ b/for_games/grid_clicker/grid_clicker_gui_select_rect.py
@@ -0,0 +1,210 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+__author__ = "ipetrash"
+
+
+import json
+import sys
+
+from pathlib import Path
+from typing import Any
+
+from PyQt5.QtWidgets import (
+ QApplication,
+ QDialog,
+ QFrame,
+ QPushButton,
+ QFormLayout,
+ QSpinBox,
+)
+from PyQt5.QtCore import Qt, QRect, QPoint
+from PyQt5.QtGui import (
+ QPainter,
+ QColor,
+ QPen,
+ QMouseEvent,
+ QKeyEvent,
+ QPaintEvent,
+ QCloseEvent,
+)
+
+from grid_clicker import click_all_on_screen
+
+DIR: Path = Path(__file__).resolve().parent
+PATH_SETTINGS: Path = DIR / "settings.json"
+
+
+class AreaSelectorDialog(QDialog):
+ def __init__(self) -> None:
+ super().__init__()
+
+ self.setWindowFlag(Qt.WindowType.FramelessWindowHint)
+ self.setWindowFlag(Qt.WindowType.WindowStaysOnTopHint)
+
+ self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
+
+ self.setCursor(Qt.CursorShape.CrossCursor)
+
+ full_geometry: QRect = QApplication.desktop().geometry()
+ self.setGeometry(full_geometry)
+
+ self._begin_global: QPoint = QPoint() # Глобальная точка начала
+ self._end_global: QPoint = QPoint() # Глобальная точка конца
+ self._is_selecting: bool = False
+
+ self.selected_rect: QRect = QRect()
+ self.do_click: bool = True
+ self.need_run_clicker: bool = False
+
+ self.spin_box_step = QSpinBox(value=50)
+ self.button_run = QPushButton("Run click", clicked=self.on_run_clicked)
+ self.button_run_test = QPushButton(
+ "Run TEST click", clicked=self.on_run_test_clicked
+ )
+ self.button_close = QPushButton("Close", clicked=self.close)
+
+ self.control_widget = QFrame(self)
+ self.control_widget.setCursor(Qt.ArrowCursor)
+ self.control_widget.setStyleSheet("""
+ QFrame {
+ background-color: rgba(60, 60, 60, 200);
+ border-radius: 10px;
+ }
+ QCheckBox,
+ QLabel {
+ color: white;
+ }
+ """)
+ control_layout = QFormLayout(self.control_widget)
+ control_layout.addRow("&Step:", self.spin_box_step)
+ control_layout.addRow(self.button_run_test)
+ control_layout.addRow(self.button_run)
+ control_layout.addRow(self.button_close)
+
+ if PATH_SETTINGS.exists():
+ data: dict[str, Any] = json.loads(PATH_SETTINGS.read_text("UTF-8"))
+ coords: list[int] | None = data.get("coords")
+ if coords:
+ self.selected_rect.setCoords(*coords)
+ self.update()
+
+ self._move_control_widget()
+
+ def _move_control_widget(self):
+ if not self.selected_rect:
+ self.control_widget.hide()
+ return
+
+ local_begin = self.mapFromGlobal(self.selected_rect.topLeft())
+ self.control_widget.move(local_begin)
+ self.control_widget.show()
+
+ @property
+ def step(self) -> int:
+ return self.spin_box_step.value()
+
+ def on_run_test_clicked(self) -> None:
+ self.do_click = False
+ self.need_run_clicker = True
+ self.close()
+
+ def on_run_clicked(self) -> None:
+ self.do_click = True
+ self.need_run_clicker = True
+ self.close()
+
+ def mousePressEvent(self, event: QMouseEvent) -> None:
+ if event.button() == Qt.LeftButton:
+ self._begin_global = event.globalPos()
+ self._end_global = self._begin_global
+
+ self._is_selecting = True
+ self._move_control_widget()
+ self.update()
+
+ def mouseMoveEvent(self, event: QMouseEvent) -> None:
+ if self._is_selecting:
+ self._end_global = event.globalPos()
+
+ rect = QRect(self._begin_global, self._end_global)
+ self.selected_rect = rect.normalized()
+
+ self._move_control_widget()
+ self.update()
+
+ def mouseReleaseEvent(self, event: QMouseEvent) -> None:
+ if self._is_selecting and event.button() == Qt.LeftButton:
+ self._is_selecting = False
+
+ def keyPressEvent(self, event: QKeyEvent) -> None:
+ if event.key() == Qt.Key_Escape:
+ self.close()
+
+ def paintEvent(self, event: QPaintEvent) -> None:
+ painter = QPainter(self)
+ painter.fillRect(self.rect(), QColor(0, 0, 0, 100))
+
+ if self.selected_rect:
+ local_rect: QRect = QRect(
+ self.mapFromGlobal(self.selected_rect.topLeft()),
+ self.mapFromGlobal(self.selected_rect.bottomRight()),
+ )
+
+ painter.setPen(QPen(Qt.red, 2, Qt.SolidLine))
+ painter.fillRect(local_rect, QColor(255, 255, 255, 1))
+ painter.drawRect(local_rect)
+
+ def closeEvent(self, event: QCloseEvent) -> None:
+ if self.selected_rect:
+ data: dict[str, Any] = {
+ "coords": self.selected_rect.getCoords(),
+ }
+ PATH_SETTINGS.write_text(
+ json.dumps(data),
+ encoding="UTF-8",
+ )
+
+ super().closeEvent(event)
+
+
+if __name__ == "__main__":
+ # Поддержка высокого DPI (важно для правильных координат на 4K мониторах)
+ QApplication.setAttribute(Qt.ApplicationAttribute.AA_EnableHighDpiScaling)
+
+ app = QApplication(sys.argv)
+
+ selector = AreaSelectorDialog()
+ selector.exec()
+
+ selected_rect: QRect = selector.selected_rect
+ if selector.selected_rect:
+ w = selected_rect.width()
+ h = selected_rect.height()
+
+ # Извлекаем границы
+ x1 = selected_rect.left()
+ y1 = selected_rect.top()
+ x2 = selected_rect.right()
+ y2 = selected_rect.bottom()
+
+ print("--- Координаты ---")
+ print(f"x1={x1}, y1={y1}, x2={x2}, y2={y2}")
+ print(f"Tuple: ({x1}, {y1}, {x2}, {y2})")
+ print(f"Args: --x1={x1} --y1={y1} --x2={x2} --y2={y2}")
+ print(f"Размер: {w}x{h}")
+ print("------------------------------")
+
+ if selector.need_run_clicker:
+ click_all_on_screen(
+ do_click=selector.do_click,
+ step=selector.step,
+ sleep_time_before_starting_secs=1,
+ coords=(x1, y1, x2, y2),
+ )
+
+ else:
+ print("Выбор отменен.")
+
+ # NOTE: Не нужен для QDialog.exec
+ # sys.exit(app.exec())
diff --git a/for_games/hacking__game__Rezrog/main.py b/for_games/hacking__game__Rezrog/main.py
index f0a746591..964196897 100644
--- a/for_games/hacking__game__Rezrog/main.py
+++ b/for_games/hacking__game__Rezrog/main.py
@@ -50,26 +50,26 @@
import base64
import json
-# pip install pycryptodome
+# pip install pycryptodome==3.21.0
# OR:
-# pip install pycryptodomex
+# pip install pycryptodomex==3.21.0
from Crypto.Cipher import AES
-# pip install pkcs7
+# pip install pkcs7==0.1.2
from pkcs7 import PKCS7Encoder
-def encrypt(to_encrypt: str, key: str) -> bytes:
+def encrypt(to_encrypt: str, key: bytes) -> bytes:
encoder = PKCS7Encoder()
encryptor = AES.new(key, AES.MODE_ECB)
- pad_text = encoder.encode(to_encrypt)
+ pad_text = encoder.encode(to_encrypt).encode("utf-8")
cipher = encryptor.encrypt(pad_text)
return base64.b64encode(cipher)
-def decrypt(to_decrypt: bytes, key: str) -> str:
+def decrypt(to_decrypt: bytes, key: bytes) -> str:
encoder = PKCS7Encoder()
encryptor = AES.new(key, AES.MODE_ECB)
@@ -80,7 +80,7 @@ def decrypt(to_decrypt: bytes, key: str) -> str:
if __name__ == "__main__":
- KEY = "32647863128053433215894456187813"
+ KEY = b"32647863128053433215894456187813"
# C:\Users\\AppData\LocalLow\Soaphog\Rezrog\Save
# C:\Users\\AppData\LocalLow\Soaphog\Rezrog\Save\game save.json
@@ -91,11 +91,11 @@ def decrypt(to_decrypt: bytes, key: str) -> str:
print(array) # b'o41r7i6/ekDVTWAmDvsmuEwhqMelq3bF3wukSbVpeeFaKIdyX ...
- text = decrypt(array, KEY)
+ text: str = decrypt(array, KEY)
print(text)
# '{"goldEarnedTotal":23778,"timePlayedMage":3581.58911132813,"timePlayedArcher" ...'
- data = json.loads(text)
+ data: dict = json.loads(text)
print(data)
# {'goldEarnedTotal': 23778, 'timePlayedMage': 3581.58911132813, ...
@@ -112,7 +112,7 @@ def decrypt(to_decrypt: bytes, key: str) -> str:
print()
- new_array = encrypt(text, KEY)
+ new_array: bytes = encrypt(text, KEY)
print(new_array) # b'o41r7i6/ekDVTWAmDvsmuEwhqMelq3bF3wukSbVpeeFaKIdyX ...
with open("progress_encrypted.json", "wb") as f:
diff --git a/for_games/hotkeys_for_cheats__Disciples_Sacred_Lands/main.py b/for_games/hotkeys_for_cheats__Disciples_Sacred_Lands/main.py
index 41db2e1cc..027652d21 100644
--- a/for_games/hotkeys_for_cheats__Disciples_Sacred_Lands/main.py
+++ b/for_games/hotkeys_for_cheats__Disciples_Sacred_Lands/main.py
@@ -8,7 +8,7 @@
import keyboard
-def input_command(text: str):
+def input_command(text: str) -> None:
keyboard.press_and_release("enter")
keyboard.write(text)
diff --git a/for_games/input_cheats_starcraft.py b/for_games/input_cheats_starcraft.py
index 935349de0..6d9100edf 100644
--- a/for_games/input_cheats_starcraft.py
+++ b/for_games/input_cheats_starcraft.py
@@ -11,7 +11,7 @@
import keyboard
-def write(text: str):
+def write(text: str) -> None:
print(text)
# Удаление символа, который мог попасть при вводе хоткея, например Z, X, C или V
diff --git a/for_games/input_cheats_starcraft2.py b/for_games/input_cheats_starcraft2.py
index 73b45ea12..771ea44ce 100644
--- a/for_games/input_cheats_starcraft2.py
+++ b/for_games/input_cheats_starcraft2.py
@@ -13,7 +13,7 @@
import keyboard
-def write(text: str):
+def write(text: str) -> None:
print(text)
os.system(f"echo {text}|clip")
diff --git a/html_parsing/kanobu_ru__games__collections__igry_s_podderzhkoi_rtx.py b/for_games/kanobu_ru__games__collections__igry_s_podderzhkoi_rtx.py
similarity index 98%
rename from html_parsing/kanobu_ru__games__collections__igry_s_podderzhkoi_rtx.py
rename to for_games/kanobu_ru__games__collections__igry_s_podderzhkoi_rtx.py
index 8c726dc48..92f0a55e1 100644
--- a/html_parsing/kanobu_ru__games__collections__igry_s_podderzhkoi_rtx.py
+++ b/for_games/kanobu_ru__games__collections__igry_s_podderzhkoi_rtx.py
@@ -79,7 +79,7 @@ def get_games() -> list[Game]:
return items
-def save_as_html(file_name: str, items: list[Game], columns=4):
+def save_as_html(file_name: str, items: list[Game], columns=4) -> None:
title = "Games with RTX"
with open(file_name, "w", encoding="utf-8") as f:
diff --git a/minecraft__seed.py b/for_games/minecraft__seed.py
similarity index 100%
rename from minecraft__seed.py
rename to for_games/minecraft__seed.py
diff --git a/for_games/nintendo_switch_emulator__games/common.py b/for_games/nintendo_switch_emulator__games/common.py
index b6d41906f..546b0d4cd 100644
--- a/for_games/nintendo_switch_emulator__games/common.py
+++ b/for_games/nintendo_switch_emulator__games/common.py
@@ -12,7 +12,7 @@ def get_json_file_name(file_name: str) -> str:
return Path(file_name).name + ".json"
-def dump(file_name: str, games: list, playable_games: list, perfect_games: list):
+def dump(file_name: str, games: list, playable_games: list, perfect_games: list) -> None:
data = {
"perfect_games": {
"total": len(perfect_games),
diff --git a/for_games/press_release_keys__ScanCodes__for_games.py b/for_games/press_release_keys__ScanCodes__for_games.py
index 4ce4075d3..c788e0e6d 100644
--- a/for_games/press_release_keys__ScanCodes__for_games.py
+++ b/for_games/press_release_keys__ScanCodes__for_games.py
@@ -219,7 +219,7 @@ class Input(ctypes.Structure):
]
-def press_key(hex_key_code: int):
+def press_key(hex_key_code: int) -> None:
extra = ctypes.c_ulong(0)
ii_ = Input_I()
ii_.ki = KeyBdInput(0, hex_key_code, KEYEVENTF_SCANCODE, 0, ctypes.pointer(extra))
@@ -227,7 +227,7 @@ def press_key(hex_key_code: int):
ctypes.windll.user32.SendInput(1, ctypes.pointer(x), ctypes.sizeof(x))
-def release_key(hex_key_code: int):
+def release_key(hex_key_code: int) -> None:
extra = ctypes.c_ulong(0)
ii_ = Input_I()
ii_.ki = KeyBdInput(
@@ -237,7 +237,7 @@ def release_key(hex_key_code: int):
ctypes.windll.user32.SendInput(1, ctypes.pointer(x), ctypes.sizeof(x))
-def write_key(hex_key_code: int, interval=0.1, pause=None):
+def write_key(hex_key_code: int, interval=0.1, pause=None) -> None:
press_key(hex_key_code)
time.sleep(interval)
release_key(hex_key_code)
diff --git a/for_games/rbkgames_com__games__code_vein.py b/for_games/rbkgames_com__games__code_vein.py
index 2bec8043e..e254dcda6 100644
--- a/for_games/rbkgames_com__games__code_vein.py
+++ b/for_games/rbkgames_com__games__code_vein.py
@@ -9,7 +9,7 @@
url = "https://rbkgames.com/games/code-vein/articles/kody-krovi-v-code-vein-kak-poluchit/"
-rs = requests.get()
+rs = requests.get(url)
root = BeautifulSoup(rs.content, "html.parser")
items = []
diff --git a/for_games/solutions of games/ELEX__hacking_minigame.py b/for_games/solutions of games/ELEX__hacking_minigame.py
index 684003e25..8649ce898 100644
--- a/for_games/solutions of games/ELEX__hacking_minigame.py
+++ b/for_games/solutions of games/ELEX__hacking_minigame.py
@@ -16,7 +16,7 @@ def run(
keys: list[int],
fixed_values: list[str | int],
need_values: list[str | int],
-):
+) -> None:
while True:
items = keys.copy()
fixed_items = fixed_values.copy()
diff --git a/for_games/solutions of games/FFXIII-2_clock_puzzle_solver/main.py b/for_games/solutions of games/FFXIII-2_clock_puzzle_solver/main.py
index 41f559149..087fbfa68 100644
--- a/for_games/solutions of games/FFXIII-2_clock_puzzle_solver/main.py
+++ b/for_games/solutions of games/FFXIII-2_clock_puzzle_solver/main.py
@@ -27,7 +27,7 @@ def is_fail(clock_data: dict) -> bool:
return a == 0 and b == 0
-def click_clock_item(clock_data: dict, selected_index: int):
+def click_clock_item(clock_data: dict, selected_index: int) -> None:
items = clock_data["items"]
move_value = items[selected_index]
@@ -48,7 +48,7 @@ def click_clock_item(clock_data: dict, selected_index: int):
clock_data["history"].append(deepcopy(clock_data["items"]))
-def solve_step(clock_data: dict, index: int, result_win: dict):
+def solve_step(clock_data: dict, index: int, result_win: dict) -> None:
# Если текущий индекс указывает на уже активированную кнопку
if clock_data["items"][index] == 0:
return
@@ -95,7 +95,7 @@ def solver(clock_items: list) -> list:
return list(wins.values())
-def print_extended(clock_data: dict):
+def print_extended(clock_data: dict) -> None:
num_clock_items = len(clock_data["selected_indexes"])
# Форматирование строки для красивого отображения двухзначных индексов
@@ -111,7 +111,7 @@ def print_extended(clock_data: dict):
print(fmt_print.format(item, index, value))
-def print_simple(clock_data: dict):
+def print_simple(clock_data: dict) -> None:
simple_result = zip(clock_data["selected_indexes"], clock_data["selected_values"])
print(" -> ".join(f"{index}({value})" for index, value in simple_result))
diff --git a/for_games/solutions of games/SpongeBob SquarePants Battle for Bikini Bottom - Rehydrated. 8 cylinder puzzle/main.py b/for_games/solutions of games/SpongeBob SquarePants Battle for Bikini Bottom - Rehydrated. 8 cylinder puzzle/main.py
index 6416a3aa1..f0c4b1f72 100644
--- a/for_games/solutions of games/SpongeBob SquarePants Battle for Bikini Bottom - Rehydrated. 8 cylinder puzzle/main.py
+++ b/for_games/solutions of games/SpongeBob SquarePants Battle for Bikini Bottom - Rehydrated. 8 cylinder puzzle/main.py
@@ -7,7 +7,7 @@
import copy
-def activate(cylinders: list[bool], index: int):
+def activate(cylinders: list[bool], index: int) -> None:
left_index = (index - 1) % len(cylinders)
right_index = (index + 1) % len(cylinders)
@@ -20,7 +20,7 @@ def is_win(cylinders: list[bool]) -> bool:
return all(cylinders)
-def run(init_cylinders: list[bool]):
+def run(init_cylinders: list[bool]) -> None:
for start_index in range(len(init_cylinders)):
seq = [start_index]
diff --git a/for_games/solutions of games/solution_puzzle_from_game_Afterfall_InSanity/main.py b/for_games/solutions of games/solution_puzzle_from_game_Afterfall_InSanity/main.py
index d4d5e93fe..4db2fdf4b 100644
--- a/for_games/solutions of games/solution_puzzle_from_game_Afterfall_InSanity/main.py
+++ b/for_games/solutions of games/solution_puzzle_from_game_Afterfall_InSanity/main.py
@@ -14,7 +14,7 @@ class Button:
is_clicked: bool = False
-def activate(buttons: list[Button], index: int):
+def activate(buttons: list[Button], index: int) -> None:
button = buttons[index]
if button.is_clicked:
return
@@ -37,7 +37,7 @@ def is_win(buttons: list[Button]) -> bool:
return all(x.value for x in buttons)
-def run(init_buttons: list[Button]):
+def run(init_buttons: list[Button]) -> None:
for start_index in range(len(init_buttons)):
seq = [start_index]
diff --git a/function_example/function_example.py b/function_example/function_example.py
index 26ce1880b..036887abe 100644
--- a/function_example/function_example.py
+++ b/function_example/function_example.py
@@ -8,12 +8,12 @@ def my_sum(*args):
return s
-def my_print(**kwargs):
+def my_print(**kwargs) -> None:
for name, value in kwargs.items():
print("%s=%s(%s)" % (name, value, type(value)))
-def sql_insert(table, **kwargs):
+def sql_insert(table, **kwargs) -> str:
# Example: INSERT INTO users (login, pass) values('TestUser', '123456')
columns = kwargs.keys()
values = ["'%s'" % x for x in kwargs.values()]
@@ -24,7 +24,7 @@ def sql_insert(table, **kwargs):
)
-def say_hello(word="World", spl="!"):
+def say_hello(word="World", spl="!") -> None:
print("Hello, " + word + spl)
diff --git a/functor.py b/functor.py
index 554f8fa15..05f7a8834 100644
--- a/functor.py
+++ b/functor.py
@@ -8,7 +8,7 @@
class Foo:
- def __call__(self):
+ def __call__(self) -> int:
return 1
@@ -23,14 +23,14 @@ def __call__(self, a, b):
class Bar:
- def __init__(self, start_value=0):
+ def __init__(self, start_value=0) -> None:
self.value = start_value
def __call__(self, *args, **kwargs):
self.value += 1
return self
- def __str__(self):
+ def __str__(self) -> str:
return f""
diff --git a/games/game__guess_the_number/gui__pyqt/main.py b/games/game__guess_the_number/gui__pyqt/main.py
index 7be3bca80..55354089d 100644
--- a/games/game__guess_the_number/gui__pyqt/main.py
+++ b/games/game__guess_the_number/gui__pyqt/main.py
@@ -11,7 +11,7 @@
from PyQt5.Qt import *
-def log_uncaught_exceptions(ex_cls, ex, tb):
+def log_uncaught_exceptions(ex_cls, ex, tb) -> None:
text = f"{ex_cls.__name__}: {ex}:\n"
text += "".join(traceback.format_tb(tb))
@@ -25,14 +25,14 @@ def log_uncaught_exceptions(ex_cls, ex, tb):
# SOURCE: https://github.com/gil9red/SimplePyScripts/blob/907a596654aabb9948934b68b0f7b1fe392e23bd/qt__pyqt__pyside__pyqode/pyqt__QListWidget__Flow.py
class WrapListWidget(QListWidget):
- def __init__(self):
+ def __init__(self) -> None:
super().__init__()
self.setFlow(QListView.LeftToRight)
self.setWrapping(True)
self.setUniformItemSizes(True)
- def resizeEvent(self, event):
+ def resizeEvent(self, event) -> None:
super().resizeEvent(event)
self.setWrapping(self.isWrapping())
@@ -41,7 +41,7 @@ def resizeEvent(self, event):
class PageGuessWidget(QWidget):
about_prev = pyqtSignal()
- def __init__(self):
+ def __init__(self) -> None:
super().__init__()
self.lives = 10
@@ -71,11 +71,11 @@ def __init__(self):
self._update_states()
- def _update_states(self):
+ def _update_states(self) -> None:
self.lbl_lives.setText(f"Жизней: {self.lives}")
self.lbl_level.setText(f"Уровень: {self.level}")
- def start_game(self, level: int, lives=10):
+ def start_game(self, level: int, lives=10) -> None:
self.lives = lives
self.level = level
@@ -88,7 +88,7 @@ def start_game(self, level: int, lives=10):
self._update_states()
- def _on_number_clicked(self, item: QListWidgetItem):
+ def _on_number_clicked(self, item: QListWidgetItem) -> None:
num = int(item.text())
if self.guess_number == num:
@@ -122,7 +122,7 @@ def _on_number_clicked(self, item: QListWidgetItem):
class PageSelectLevelWidget(QWidget):
about_select_level = pyqtSignal(int)
- def __init__(self):
+ def __init__(self) -> None:
super().__init__()
self.grid_layout = QGridLayout()
@@ -140,7 +140,7 @@ def __init__(self):
self.setLayout(layout)
- def _add_levels(self, levels):
+ def _add_levels(self, levels) -> None:
start_row = self.grid_layout.rowCount()
for row, rows in enumerate(levels, start=start_row):
@@ -154,7 +154,7 @@ def _add_levels(self, levels):
class MainWindow(QMainWindow):
- def __init__(self):
+ def __init__(self) -> None:
super().__init__()
self.setWindowTitle("Guess the number")
@@ -174,7 +174,7 @@ def __init__(self):
self.setCentralWidget(self.stack_widget)
- def _on_select_level(self, level: int):
+ def _on_select_level(self, level: int) -> None:
# Переключаемся на страницу с игрой
self.stack_widget.setCurrentWidget(self.page_guess)
diff --git a/games/life/board.py b/games/life/board.py
index 6aa894f76..606d9016b 100644
--- a/games/life/board.py
+++ b/games/life/board.py
@@ -25,7 +25,7 @@ class StepResultEnum(enum.Enum):
class Board(QObject):
on_update_generation = pyqtSignal(int)
- def __init__(self):
+ def __init__(self) -> None:
super().__init__()
self.rows = 100
@@ -38,7 +38,7 @@ def __init__(self):
self.last_step_result: StepResultEnum = None
- def generate(self, seed: str = ""):
+ def generate(self, seed: str = "") -> None:
self.seed = seed
if not self.seed:
self.seed = get_random_seed()
@@ -62,7 +62,7 @@ def generation_number(self) -> int:
return self.__generation_number
@generation_number.setter
- def generation_number(self, value: int):
+ def generation_number(self, value: int) -> None:
self.__generation_number = value
self.on_update_generation.emit(self.generation_number)
diff --git a/games/life/main_console.py b/games/life/main_console.py
index 8402c5fd0..decd1f919 100644
--- a/games/life/main_console.py
+++ b/games/life/main_console.py
@@ -20,7 +20,7 @@
class MyLateFigletText(StaticRenderer):
- def __init__(self, rendered_text_func: Callable[[], str], **kwargs):
+ def __init__(self, rendered_text_func: Callable[[], str], **kwargs) -> None:
super().__init__()
self.rendered_text_func = rendered_text_func
@@ -45,7 +45,7 @@ def __init__(
height: int,
width: int,
next_scene: str,
- ):
+ ) -> None:
self.y = y
self.x = x
self.height = height
@@ -70,7 +70,7 @@ def __init__(
self.thread = Thread(target=self._run_timer, daemon=True)
- def start(self, timer_ms: int, seed: str):
+ def start(self, timer_ms: int, seed: str) -> None:
self.is_fail = False
self.timer_secs = timer_ms / 1000
@@ -81,7 +81,7 @@ def start(self, timer_ms: int, seed: str):
# TODO: RuntimeError: threads can only be started once
self.thread.start()
- def _run_timer(self):
+ def _run_timer(self) -> None:
while self.board.do_step() == StepResultEnum.OK:
time.sleep(self.timer_secs)
@@ -124,7 +124,7 @@ def __init__(
self,
screen: Screen,
y: int, x: int, height: int, width: int,
- ):
+ ) -> None:
self.y = y
self.x = x
self.height = height
@@ -167,7 +167,7 @@ def get_timer_ms(self) -> int:
def get_seed(self) -> str:
return self.le_seed.value
- def _start(self):
+ def _start(self) -> None:
# TODO: validator не работает. Не помогает save и validate
self.save(validate=True)
@@ -178,7 +178,7 @@ def _start(self):
)
-def demo(screen: Screen, scene: Scene):
+def demo(screen: Screen, scene: Scene) -> None:
menu_widget = MenuWidget(
screen,
y=0, x=0,
diff --git a/games/life/main_gui.py b/games/life/main_gui.py
index cebca1a08..0f1d28a1c 100644
--- a/games/life/main_gui.py
+++ b/games/life/main_gui.py
@@ -25,7 +25,7 @@
from board import Board, StepResultEnum, get_random_seed
-def log_uncaught_exceptions(ex_cls, ex, tb):
+def log_uncaught_exceptions(ex_cls, ex, tb) -> None:
text = f"{ex_cls.__name__}: {ex}:\n"
text += "".join(traceback.format_tb(tb))
@@ -41,13 +41,13 @@ def log_uncaught_exceptions(ex_cls, ex, tb):
class BoardWidget(QWidget):
- def __init__(self):
+ def __init__(self) -> None:
super().__init__()
self.board = Board()
self.cell_size = 1
- def resizeEvent(self, event):
+ def resizeEvent(self, event) -> None:
super().resizeEvent(event)
self.cell_size = min(self.width(), self.height()) // min(
@@ -58,14 +58,14 @@ def resizeEvent(self, event):
self.update()
- def _draw_cell_board(self, painter: QPainter, x: int, y: int, color: QColor):
+ def _draw_cell_board(self, painter: QPainter, x: int, y: int, color: QColor) -> None:
painter.setPen(Qt.NoPen)
painter.setBrush(color)
painter.drawRect(
x * self.cell_size, y * self.cell_size, self.cell_size, self.cell_size
)
- def _draw_board(self, painter: QPainter):
+ def _draw_board(self, painter: QPainter) -> None:
painter.save()
# Рисование заполненных ячеек
@@ -78,7 +78,7 @@ def _draw_board(self, painter: QPainter):
painter.restore()
- def paintEvent(self, event: QPaintEvent):
+ def paintEvent(self, event: QPaintEvent) -> None:
painter = QPainter(self)
painter.setRenderHint(QPainter.Antialiasing)
@@ -90,7 +90,7 @@ class MainWindow(QWidget):
TITLE = "Life"
- def __init__(self):
+ def __init__(self) -> None:
super().__init__()
self.board_widget = BoardWidget()
@@ -138,16 +138,16 @@ def __init__(self):
self.setLayout(main_layout)
self._update_states()
- def _do_rand_seed(self):
+ def _do_rand_seed(self) -> None:
self.le_seed.setText(get_random_seed())
- def start(self):
+ def start(self) -> None:
self.board.generate(seed=self.le_seed.text())
self.timer.stop()
self.start_pause_timer()
- def start_pause_timer(self):
+ def start_pause_timer(self) -> None:
if self.timer.isActive():
self.timer.stop()
else:
@@ -155,7 +155,7 @@ def start_pause_timer(self):
self._update_states()
- def _update_states(self):
+ def _update_states(self) -> None:
is_paused = not self.timer.isActive()
prefix = "[IS PAUSED] " if is_paused else ""
self.setWindowTitle(
@@ -168,12 +168,12 @@ def _update_states(self):
self.button_timer.setChecked(self.timer.isActive())
- def abort_game(self, reason: str):
+ def abort_game(self, reason: str) -> None:
self.timer.stop()
self._update_states()
QMessageBox.information(self, "Информация", f"Проигрыш! {reason}")
- def _on_logic(self):
+ def _on_logic(self) -> None:
result = self.board.do_step()
if result == StepResultEnum.OK:
return
@@ -190,7 +190,7 @@ def _on_logic(self):
self.abort_game(reason)
- def _on_tick(self):
+ def _on_tick(self) -> None:
self._on_logic()
self._update_states()
self.board_widget.update()
diff --git a/games/nim/main.py b/games/nim/main.py
new file mode 100644
index 000000000..25d7b1a3c
--- /dev/null
+++ b/games/nim/main.py
@@ -0,0 +1,175 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+__author__ = "ipetrash"
+
+
+import random
+
+from abc import ABC, abstractmethod
+from dataclasses import dataclass, field
+from typing import Optional
+
+
+# TODO:
+class FinishGameException(Exception):
+ pass
+
+
+@dataclass
+class Player(ABC):
+ name: str
+ game: Optional["Game"] = field(repr=False, default=None)
+
+ @abstractmethod
+ def is_bot(self) -> bool:
+ pass
+
+ @abstractmethod
+ def make_choice(self) -> int:
+ pass
+
+
+class UserPlayer(Player):
+ def is_bot(self) -> bool:
+ return False
+
+ def make_choice(self) -> int:
+ while True:
+ try:
+ stones = int(
+ input(
+ f"Игрок {self.name!r} выбирает камни от {self.game.MIN_STONES} до {self.game.MAX_STONES} "
+ f"(всего камней: {self.game.stones}): "
+ )
+ )
+ assert self.game.MIN_STONES <= stones <= self.game.MAX_STONES
+
+ if stones > self.game.stones:
+ print("Нельзя камней выбрать больше, чем осталось!")
+ continue
+
+ return stones
+ except (ValueError, AssertionError):
+ print("Неправильное значение!")
+ continue
+
+
+class BotPlayer(Player):
+ def is_bot(self) -> bool:
+ return True
+
+ def make_choice(self) -> int:
+ # Чтобы бот не выбирал очевидно проигрышное количество камней, когда можно победить
+ # Если осталось от 2 до 4 камней, то выбирать от 1 до 3 камней - чтобы остался только 1 камень
+ match self.game.stones:
+ case 1:
+ return 1 # Без вариантов, остается только проиграть
+ case 2:
+ return 1
+ case 3:
+ return 2
+ case 4:
+ return 3
+
+ return min(
+ self.game.stones,
+ random.randint(self.game.MIN_STONES, self.game.MAX_STONES),
+ )
+
+
+@dataclass
+class Game:
+ player1: Player
+ player2: Player
+
+ round: int = 0
+ stones: int = 0
+ is_finished: bool = False
+
+ MIN_STONES: int = 1
+ MAX_STONES: int = 3
+ MAX_NUMBER: int = 21
+
+ def start(self) -> None:
+ self.player1.game = self
+ self.player2.game = self
+
+ self.round = 0
+ self.stones = self.MAX_NUMBER
+
+ self.is_finished = False
+
+ player1_is_first: bool = False
+ variants: list[str] = ["y", "n"]
+
+ if self.player1.is_bot():
+ print(f"Игрок {self.player1.name!r} подкидывает монетку")
+ player1_is_first = random.choice(variants) == variants[0]
+ else:
+ while True:
+ try:
+ value: str = input(f"Ты первый? ({'/'.join(variants)}): ").lower()
+ if not value:
+ value = variants[0]
+ assert value in variants
+ player1_is_first = value == variants[0]
+ break
+
+ except AssertionError:
+ print("Неправильное значение!")
+ continue
+
+ # Меняем местами первого и второго игроков
+ if not player1_is_first:
+ self.player1, self.player2 = self.player2, self.player1
+
+ print(f"Первым ходит игрок {self.player1.name!r}")
+
+ def do_choice(self, player: Player):
+ stones: int = player.make_choice()
+ print(f"Игрок {player.name!r} выбрал: {stones}")
+
+ self.stones -= stones
+ if self.stones <= 0:
+ # TODO:
+ raise FinishGameException(f"Игрок {player.name!r} проиграл!")
+
+ # TODO: Немного ASCII для удобного просмотра
+ def move(self) -> bool:
+ self.round += 1
+
+ print(f"\nРаунд #{self.round}. Камней: {self.stones}")
+
+ try:
+ self.do_choice(self.player1)
+ self.do_choice(self.player2)
+
+ except FinishGameException as e:
+ print()
+ print(e)
+ self.is_finished = True
+
+ return not self.is_finished
+
+
+if __name__ == "__main__":
+ # game = Game(
+ # player1=BotPlayer("Бот1"),
+ # player2=BotPlayer("Бот2"),
+ # )
+
+ # game = Game(
+ # player1=UserPlayer("Человек1"),
+ # player2=UserPlayer("Человек2"),
+ # )
+
+ game = Game(
+ player1=UserPlayer("Человек"),
+ player2=BotPlayer("Бот"),
+ )
+
+ game.start()
+
+ while game.move():
+ pass
diff --git a/games/tetris/common.py b/games/tetris/common.py
deleted file mode 100644
index 192444c22..000000000
--- a/games/tetris/common.py
+++ /dev/null
@@ -1,16 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-__author__ = "ipetrash"
-
-
-import logging
-
-from config import DEBUG
-
-
-logging.basicConfig(
- level=logging.DEBUG if DEBUG else logging.WARNING,
- format="[%(asctime)s] %(filename)s:%(lineno)d %(levelname)-8s %(message)s",
-)
-logger = logging.getLogger(__file__)
diff --git a/games/tetris/etc/screenshot_gui.png b/games/tetris/etc/screenshot_gui.png
index d05bc61e4..f54781692 100644
Binary files a/games/tetris/etc/screenshot_gui.png and b/games/tetris/etc/screenshot_gui.png differ
diff --git a/games/tetris/main_console.py b/games/tetris/main_console.py
index b078b8aa0..ece99506f 100644
--- a/games/tetris/main_console.py
+++ b/games/tetris/main_console.py
@@ -17,8 +17,8 @@
from asciimatics.widgets import Frame
from asciimatics.exceptions import StopApplication, NextScene, ResizeScreenError
-from board import Board
-from piece import PieceO, PieceI, PieceS, PieceZ, PieceL, PieceJ, PieceT
+from src.core.board import Board
+from src.core.piece import PieceO, PieceI, PieceS, PieceZ, PieceL, PieceJ, PieceT
# SOURCE: https://asciimatics.readthedocs.io/en/stable/io.html#colours
@@ -35,7 +35,7 @@
class MyLateFigletText(StaticRenderer):
- def __init__(self, rendered_text_func: Callable[[], str], **kwargs):
+ def __init__(self, rendered_text_func: Callable[[], str], **kwargs) -> None:
super().__init__()
self.rendered_text_func = rendered_text_func
@@ -60,7 +60,7 @@ def __init__(
height: int,
width: int,
next_scene: str,
- ):
+ ) -> None:
self.y = y
self.x = x
self.height = height
@@ -85,13 +85,13 @@ def __init__(
self.thread = Thread(target=self._run_timer, daemon=True)
self.thread.start()
- def _run_timer(self):
+ def _run_timer(self) -> None:
while self.board.do_step():
time.sleep(0.3)
self.is_fail = True
- def update(self, frame_no: int):
+ def update(self, frame_no: int) -> None:
super().update(frame_no)
if self.is_fail:
@@ -155,8 +155,11 @@ def __init__(
self,
board: Board,
screen: Screen,
- y: int, x: int, height: int, width: int,
- ):
+ y: int,
+ x: int,
+ height: int,
+ width: int,
+ ) -> None:
self.y = y
self.x = x
self.height = height
@@ -175,7 +178,7 @@ def __init__(
self.board = board
self.next_piece = self.board.next_piece
- def update(self, frame_no: int):
+ def update(self, frame_no: int) -> None:
super().update(frame_no)
self.next_piece = self.board.next_piece
@@ -201,8 +204,11 @@ def __init__(
self,
board: Board,
screen: Screen,
- y: int, x: int, height: int, width: int,
- ):
+ y: int,
+ x: int,
+ height: int,
+ width: int,
+ ) -> None:
self.y = y
self.x = x
self.height = height
@@ -220,7 +226,7 @@ def __init__(
self.set_theme("monochrome")
self.board = board
- def update(self, frame_no: int):
+ def update(self, frame_no: int) -> None:
super().update(frame_no)
self.screen.print_at(f"Score: {self.board.score}", self.x, self.y)
@@ -233,7 +239,7 @@ def frame_update_count(self) -> int:
return 5
-def demo(screen: Screen, scene: Scene):
+def demo(screen: Screen, scene: Scene) -> None:
board = Board()
scenes = [
Scene(
diff --git a/games/tetris/main_gui.py b/games/tetris/main_gui.py
index fe2077e46..3e7685fab 100644
--- a/games/tetris/main_gui.py
+++ b/games/tetris/main_gui.py
@@ -4,20 +4,42 @@
__author__ = "ipetrash"
+import json
+import string
import sys
+import random
import traceback
-from PyQt5.QtWidgets import QWidget, QApplication, QMessageBox
-from PyQt5.QtGui import QPainter, QPaintEvent, QKeyEvent, QColor
-from PyQt5.QtCore import Qt, QTimer
-
-from board import Board
-from config import DEBUG
-from common import logger
-from piece import Piece
-
-
-def log_uncaught_exceptions(ex_cls, ex, tb):
+from dataclasses import dataclass
+from datetime import datetime
+from pathlib import Path
+from typing import Any
+
+from PyQt5.QtCore import Qt
+from PyQt5.QtGui import QKeyEvent
+from PyQt5.QtWidgets import (
+ QWidget,
+ QApplication,
+ QMessageBox,
+ QVBoxLayout,
+ QHBoxLayout,
+ QLabel,
+ QGridLayout,
+ QToolButton,
+ QPushButton,
+ QScrollArea,
+ QCheckBox,
+ QStyle,
+ QLineEdit,
+)
+
+from src.core.common import logger, ms_to_str
+from src.gui.common import StatusGameEnum
+from src.gui.board_widget import BoardWidget
+from src.gui.piece_widget import PieceWidget
+
+
+def log_uncaught_exceptions(ex_cls, ex, tb) -> None:
text = f"{ex_cls.__name__}: {ex}:\n"
text += "".join(traceback.format_tb(tb))
@@ -29,148 +51,271 @@ def log_uncaught_exceptions(ex_cls, ex, tb):
sys.excepthook = log_uncaught_exceptions
-class MainWindow(QWidget):
- CELL_SIZE = 20
- SPEED_MS = 400
+# SOURCE: https://github.com/gil9red/parse_jira_logged_time/blob/9a842ed071f317b7323b7b7775aa9f5195dbaf47/widgets/__init__.py#L218
+def get_scroll_area(widget: QWidget) -> QScrollArea:
+ scroll_area = QScrollArea()
+ scroll_area.setFrameStyle(QScrollArea.NoFrame)
+ scroll_area.setWidgetResizable(True)
+ scroll_area.setWidget(widget)
- TITLE = "Tetris"
+ return scroll_area
- def __init__(self):
- super().__init__()
- self.board = Board()
- self.board.on_update_score.connect(self._update_states)
+# SOURCE: https://github.com/gil9red/SimplePyScripts/blob/521cec6ee5915a23064617b17a8c9258f4592a46/games/life/board.py#L14
+def get_random_seed(length: int = 8) -> str:
+ return "".join(random.choices(string.ascii_letters + string.digits, k=length))
- self.current_piece: Piece = None
- self.next_piece: Piece = None
- self.timer = QTimer()
- self.timer.timeout.connect(self._on_tick)
- self.timer.setInterval(self.SPEED_MS)
- self.timer.start()
+DIR: Path = Path(__file__).resolve().parent
+FILE_HIGH_SCORES: Path = DIR / "high_scores.json"
- self._update_states()
- def _update_states(self):
- self.setWindowTitle(f"{self.TITLE}. Score: {self.board.score}")
+@dataclass
+class HighScore:
+ date_added: datetime
+ time: str
+ score: int
+
+ @classmethod
+ def parse(cls, data: dict[str, Any]) -> "HighScore":
+ return cls(
+ date_added=datetime.fromisoformat(data["date_added"]),
+ time=data["time"],
+ score=data["score"],
+ )
- def abort_game(self):
- self.timer.stop()
- QMessageBox.information(self, "Информация", "Проигрыш!")
+ def to_dict(self) -> dict[str, Any]:
+ return {
+ "date_added": self.date_added.isoformat(),
+ "time": self.time,
+ "score": self.score,
+ }
- def _on_logic(self):
- if not self.board.do_step():
- self.abort_game()
- return
- self.current_piece = self.board.current_piece
- self.next_piece = self.board.next_piece
+class MainWindow(QWidget):
+ TITLE: str = "Tetris"
- def _on_tick(self):
- self._on_logic()
- self._update_states()
- self.update()
+ def __init__(self) -> None:
+ super().__init__()
- def _draw_cell_board(self, painter: QPainter, x: int, y: int, color: QColor):
- painter.setPen(Qt.NoPen)
- painter.setBrush(color)
- painter.drawRect(
- x * self.CELL_SIZE, y * self.CELL_SIZE, self.CELL_SIZE, self.CELL_SIZE
+ self.high_scores: list[HighScore] = []
+
+ self.high_scores_view = QLabel()
+
+ self.next_piece_widget = PieceWidget()
+ self.score_label = QLabel()
+ self.playing_time_label = QLabel()
+
+ self.board_widget = BoardWidget()
+ self.board_widget.board.on_next_piece.connect(self.next_piece_widget.set_piece)
+ self.board_widget.board.on_update_score.connect(self._update_states)
+ self.board_widget.on_before_start.connect(self._on_before_start)
+ self.board_widget.on_tick.connect(self._update_states)
+ self.board_widget.on_finish.connect(self._on_finish)
+
+ def _add_button(text: str, key: Qt.Key) -> QToolButton:
+ button = QToolButton()
+ button.setText(text)
+ button.clicked.connect(lambda: self.board_widget.process_key(key))
+ return button
+
+ self.up_button = _add_button("🢁", Qt.Key_Up)
+ self.left_button = _add_button("🢀", Qt.Key_Left)
+ self.right_button = _add_button("🢂", Qt.Key_Right)
+ self.down_button = _add_button("🢃", Qt.Key_Down)
+ self.pause_resume_button = _add_button("Pause/Resume", Qt.Key_Space)
+
+ control_layout = QGridLayout()
+ control_layout.setAlignment(Qt.AlignCenter)
+ control_layout.addWidget(self.up_button, 0, 1)
+ control_layout.addWidget(self.left_button, 1, 0)
+ control_layout.addWidget(self.right_button, 1, 2)
+ control_layout.addWidget(self.down_button, 2, 1)
+ control_layout.addWidget(self.pause_resume_button, 3, 0, 1, 4)
+
+ self.start_button = QPushButton("START")
+ self.start_button.clicked.connect(self._do_start)
+
+ self.cb_random = QCheckBox("Random")
+
+ self.le_seed = QLineEdit(get_random_seed())
+ self.le_seed.setVisible(self.cb_random.isChecked())
+ self.le_seed.returnPressed.connect(self._do_start)
+ action_rand_seed = self.le_seed.addAction(
+ self.style().standardIcon(QStyle.SP_BrowserReload),
+ QLineEdit.TrailingPosition,
+ )
+ action_rand_seed.triggered.connect(
+ lambda: self.le_seed.setText(get_random_seed())
)
- def _draw_board(self, painter: QPainter):
- painter.save()
-
- # Рисование заполненных ячеек
- for y, row in enumerate(self.board.matrix):
- for x, cell_color in enumerate(row):
- if not cell_color:
- continue
+ self.cb_random.clicked.connect(self.le_seed.setVisible)
+
+ right_layout = QVBoxLayout()
+ right_layout.addWidget(self.next_piece_widget)
+ right_layout.addWidget(self.score_label)
+ right_layout.addWidget(self.playing_time_label)
+ right_layout.addWidget(self.start_button)
+ right_layout.addWidget(self.cb_random)
+ right_layout.addWidget(self.le_seed)
+ right_layout.addLayout(control_layout)
+ right_layout.addWidget(
+ QLabel(
+ """
+
+ | ENTER - Start |
+ | SPACE - Pause/Resume |
+ | 🢁 - Rotate | 🢀 - Left |
+ | 🢂 - Right | 🢃 - Down |
+
+ Scores by rows:
+
+ | 1: 100 | 2: 300 |
+ | 3: 700 | 4: 1500 |
+
+ """
+ )
+ )
+ right_layout.addWidget(self.high_scores_view)
+ right_layout.addStretch()
- self._draw_cell_board(painter, x, y, cell_color)
+ widget_right = QWidget()
+ widget_right.setLayout(right_layout)
- painter.setPen(Qt.black)
+ main_layout = QHBoxLayout(self)
+ main_layout.addWidget(self.board_widget, stretch=1)
+ main_layout.addWidget(get_scroll_area(widget_right))
- # Горизонтальные линии
- y1, y2 = 0, 0
- for i in range(self.board.ROWS + 1):
- painter.drawLine(0, y1, self.CELL_SIZE * self.board.COLS, y2)
- y1 += self.CELL_SIZE
- y2 += self.CELL_SIZE
+ for w in self.findChildren(QWidget):
+ # У BoardWidget и поля ввода нужен фокус для взаимодействия
+ if w in [self.board_widget, self.le_seed]:
+ continue
- # Вертикальные линии
- x1, x2 = 0, 0
- for i in range(self.board.COLS + 1):
- painter.drawLine(x1, 0, x2, self.CELL_SIZE * self.board.ROWS)
- x1 += self.CELL_SIZE
- x2 += self.CELL_SIZE
+ w.setFocusPolicy(Qt.FocusPolicy.NoFocus)
- painter.restore()
+ self.load_high_scores()
- def _draw_current_piece(self, painter: QPainter):
- if not self.current_piece:
- return
+ self._update_states()
- painter.save()
+ def _on_before_start(self) -> None:
+ self.board_widget.seed = None
+ if self.cb_random.isChecked():
+ self.board_widget.seed = self.le_seed.text()
- for x, y in self.current_piece.get_points():
- self._draw_cell_board(painter, x, y, self.current_piece.get_color())
+ def _do_start(self) -> None:
+ self.board_widget.start()
- # Рисуем центр фигуры
- if DEBUG:
- self._draw_cell_board(
- painter, self.current_piece.x, self.current_piece.y, Qt.black
+ def _on_finish(self) -> None:
+ self.high_scores.append(
+ HighScore(
+ date_added=datetime.now(),
+ time=ms_to_str(self.board_widget.playing_time_ms),
+ score=self.board_widget.board.score,
)
+ )
- x_next = self.board.COLS + 3
- y_next = 1
- for x, y in self.next_piece.get_points_for_state(x=x_next, y=y_next):
- self._draw_cell_board(painter, x, y, self.next_piece.get_color())
+ self._fill_high_scores_view()
+ self.save_high_scores()
- painter.restore()
+ self._update_states()
- def _draw_score(self, painter: QPainter):
- painter.save()
+ def get_top_high_scores(self, number: int = 5) -> list[HighScore]:
+ # Сортировка по убыванию очков и возрастанию времени
+ self.high_scores.sort(
+ key=lambda obj: (obj.score, -(int(obj.time.replace(":", "")))),
+ reverse=True,
+ )
+ if len(self.high_scores) > number:
+ self.high_scores = self.high_scores[:number]
+ return self.high_scores
+
+ def _update_states(self) -> None:
+ score: int = self.board_widget.board.score
+ playing_time = ms_to_str(self.board_widget.playing_time_ms)
+
+ title_parts = [self.TITLE]
+
+ if self.board_widget.status != StatusGameEnum.INITED:
+ title_parts.append(f"Score: {score}")
+ title_parts.append(f"Time: {playing_time}")
+
+ if self.board_widget.status == StatusGameEnum.PAUSED:
+ title_parts.append("Paused")
+
+ self.setWindowTitle(". ".join(title_parts))
+ self.score_label.setText(f"Score: {score}")
+ self.playing_time_label.setText(f"Time: {playing_time}")
+
+ is_inited_or_finished: bool = self.board_widget.status in [
+ StatusGameEnum.INITED,
+ StatusGameEnum.FINISHED,
+ ]
+ self.start_button.setEnabled(is_inited_or_finished)
+ self.cb_random.setEnabled(is_inited_or_finished)
+ self.le_seed.setEnabled(is_inited_or_finished)
+
+ is_started: bool = self.board_widget.status == StatusGameEnum.STARTED
+ self.up_button.setEnabled(is_started)
+ self.left_button.setEnabled(is_started)
+ self.right_button.setEnabled(is_started)
+ self.down_button.setEnabled(is_started)
+
+ self.pause_resume_button.setEnabled(
+ is_started or self.board_widget.status == StatusGameEnum.PAUSED
+ )
- painter.setPen(Qt.black)
+ def _fill_high_scores_view(self) -> None:
+ lines = ["High scores (top 5):"]
- x, y = self.CELL_SIZE * (self.board.COLS + 1), self.CELL_SIZE * 5
- painter.drawText(x, y, f"Score: {self.board.score}")
+ for i, high_score in enumerate(self.get_top_high_scores(), start=1):
+ lines.append(
+ f"{i}. {high_score.score} - {high_score.time}
"
+ f" {high_score.date_added:%d.%m.%Y %H:%M:%S}"
+ )
- painter.restore()
+ self.high_scores_view.setText("
".join(lines))
- def paintEvent(self, event: QPaintEvent):
- painter = QPainter(self)
- painter.setRenderHint(QPainter.Antialiasing)
+ def get_raw_high_scores(self) -> list[dict[str, Any]]:
+ return [high_score.to_dict() for high_score in self.high_scores]
- self._draw_board(painter)
- self._draw_current_piece(painter)
- self._draw_score(painter)
+ def set_raw_high_scores(self, items: list[dict[str, Any]]) -> None:
+ self.high_scores = [HighScore.parse(data) for data in items]
+ self._fill_high_scores_view()
- def keyReleaseEvent(self, event: QKeyEvent):
- match event.key():
- case Qt.Key_Left if self.current_piece:
- if self.current_piece.move_left():
- self.update()
+ def save_high_scores(self) -> None:
+ FILE_HIGH_SCORES.write_text(
+ json.dumps(self.get_raw_high_scores(), indent=4),
+ encoding="utf-8",
+ )
- case Qt.Key_Right if self.current_piece:
- if self.current_piece.move_right():
- self.update()
+ def load_high_scores(self) -> None:
+ if not FILE_HIGH_SCORES.exists():
+ return
- case Qt.Key_Up if self.current_piece:
- if self.current_piece.turn():
- self.update()
+ items: list[dict[str, Any]] = json.loads(
+ FILE_HIGH_SCORES.read_text(encoding="utf-8")
+ )
+ self.set_raw_high_scores(items)
- case Qt.Key_Down if self.current_piece:
- while self.current_piece.move_down():
- self.update()
+ def keyReleaseEvent(self, event: QKeyEvent) -> None:
+ self.board_widget.process_key(event.key())
+ self._update_states()
if __name__ == "__main__":
app = QApplication([])
mw = MainWindow()
- mw.resize(360, 480)
mw.show()
+ # mw = QWidget()
+ # main_layout = QHBoxLayout(mw)
+ #
+ # for cls in Piece.__subclasses__():
+ # piece_widget = PieceWidget()
+ # piece_widget.set_piece(cls(0, 0))
+ # main_layout.addWidget(piece_widget)
+ #
+ # mw.show()
+
app.exec()
diff --git a/games/tetris/config.py b/games/tetris/src/__init__.py
similarity index 81%
rename from games/tetris/config.py
rename to games/tetris/src/__init__.py
index b403d7edb..f25732790 100644
--- a/games/tetris/config.py
+++ b/games/tetris/src/__init__.py
@@ -2,6 +2,3 @@
# -*- coding: utf-8 -*-
__author__ = "ipetrash"
-
-
-DEBUG = False
diff --git a/games/tetris/src/core/__init__.py b/games/tetris/src/core/__init__.py
new file mode 100644
index 000000000..f25732790
--- /dev/null
+++ b/games/tetris/src/core/__init__.py
@@ -0,0 +1,4 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+__author__ = "ipetrash"
diff --git a/games/tetris/board.py b/games/tetris/src/core/board.py
similarity index 60%
rename from games/tetris/board.py
rename to games/tetris/src/core/board.py
index 8ae11581d..6f4da0d30 100644
--- a/games/tetris/board.py
+++ b/games/tetris/src/core/board.py
@@ -7,45 +7,61 @@
from PyQt5.QtCore import QObject, pyqtSignal
from PyQt5.QtGui import QColor
-from common import logger
-from piece import Piece
+from .common import logger
+from .piece import Piece
class Board(QObject):
- ROWS = 20
- COLS = 10
+ ROWS: int = 20
+ COLS: int = 10
+ on_next_piece = pyqtSignal(Piece)
on_update_score = pyqtSignal(int)
- def __init__(self):
+ def __init__(self) -> None:
super().__init__()
self.matrix: list[list[QColor | None]] = [
- [None for _ in range(self.COLS)]
- for _ in range(self.ROWS)
+ [None for _ in range(self.COLS)] for _ in range(self.ROWS)
]
- self.current_piece: Piece = None
- self.next_piece: Piece = None
+ self.current_piece: Piece | None = None
+ self.__next_piece: Piece | None = None
self.__score: int = 0
+ @property
+ def next_piece(self) -> Piece | None:
+ return self.__next_piece
+
+ @next_piece.setter
+ def next_piece(self, value: Piece | None) -> None:
+ self.__next_piece = value
+ self.on_next_piece.emit(value)
+
@property
def score(self) -> int:
return self.__score
@score.setter
- def score(self, value: int):
+ def score(self, value: int) -> None:
self.__score = value
- self.on_update_score.emit(self.score)
+ self.on_update_score.emit(value)
- def add_piece(self, piece: Piece):
+ def add_piece(self, piece: Piece) -> None:
logger.debug("[add_piece]")
for x, y in piece.get_points():
self.matrix[y][x] = piece.get_color()
- def _do_make_collapse_of_rows(self):
- to_delete = []
+ def clear(self) -> None:
+ self.score = 0
+
+ for row in self.matrix:
+ for i in range(len(row)):
+ row[i] = None
+
+ def _do_make_collapse_of_rows(self) -> None:
+ to_delete: list[list[QColor | None]] = []
for row in reversed(self.matrix):
if all(color for color in row):
to_delete.append(row)
@@ -58,7 +74,7 @@ def _do_make_collapse_of_rows(self):
if to_delete:
self.score += {1: 100, 2: 300, 3: 700, 4: 1500}[len(to_delete)]
- def _get_random_piece(self) -> Piece:
+ def get_random_piece(self) -> Piece:
return Piece.get_random(
x=self.COLS // 2, # По-умолчанию, по центру
y=0,
@@ -70,9 +86,9 @@ def do_step(self) -> bool:
if self.next_piece:
self.current_piece = self.next_piece
else:
- self.current_piece = self._get_random_piece()
+ self.current_piece = self.get_random_piece()
- self.next_piece = self._get_random_piece()
+ self.next_piece = self.get_random_piece()
# Если сразу после создания столкновение
if self.current_piece.is_collapse():
diff --git a/games/tetris/src/core/common.py b/games/tetris/src/core/common.py
new file mode 100644
index 000000000..ab623d839
--- /dev/null
+++ b/games/tetris/src/core/common.py
@@ -0,0 +1,33 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+__author__ = "ipetrash"
+
+
+import logging
+import sys
+
+from .config import DEBUG
+
+
+# SOURCE: https://github.com/gil9red/SimplePyScripts/blob/0360c558f85c0fe5e7320d88f90c0a4e23a7e342/seconds_to_str.py
+def seconds_to_str(seconds: int | float) -> str:
+ hh, mm = divmod(seconds, 3600)
+ mm, ss = divmod(mm, 60)
+ return "%02d:%02d:%02d" % (hh, mm, ss)
+
+
+def ms_to_str(ms: int | float) -> str:
+ return seconds_to_str(ms / 1000)
+
+
+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("tetris")
+logger.setLevel(logging.DEBUG if DEBUG else logging.WARNING)
+logger.addHandler(default_handler)
diff --git a/games/tetris/src/core/config.py b/games/tetris/src/core/config.py
new file mode 100644
index 000000000..c1e00b0be
--- /dev/null
+++ b/games/tetris/src/core/config.py
@@ -0,0 +1,7 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+__author__ = "ipetrash"
+
+
+DEBUG: bool = False
diff --git a/games/tetris/piece.py b/games/tetris/src/core/piece.py
similarity index 92%
rename from games/tetris/piece.py
rename to games/tetris/src/core/piece.py
index bc757918b..1abdbc52a 100644
--- a/games/tetris/piece.py
+++ b/games/tetris/src/core/piece.py
@@ -10,7 +10,7 @@
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QColor
-from common import logger
+from .common import logger
class Piece(abc.ABC):
@@ -21,7 +21,7 @@ class Piece(abc.ABC):
4: 1,
}
- def __init__(self, x: int, y: int, parent: "Board" = None):
+ def __init__(self, x: int, y: int, parent: "Board" = None) -> None:
self.x = x
self.y = y
self.parent = parent
@@ -32,7 +32,8 @@ def __init__(self, x: int, y: int, parent: "Board" = None):
@classmethod
def get_random(
cls,
- x: int, y: int,
+ x: int,
+ y: int,
parent: "Board",
rand_x: bool = False,
) -> "Piece":
@@ -101,12 +102,14 @@ def get_points_for_state(
for i, row in enumerate(points_board):
for j, value in enumerate(row):
if value != ".":
- points.append((
- # Рассчитываем разницу между X и остальными значениями
- # и прибавляем текущие координаты центра фигуры
- (j - idx_x) + x,
- (i - idx_y) + y
- ))
+ points.append(
+ (
+ # Рассчитываем разницу между X и остальными значениями
+ # и прибавляем текущие координаты центра фигуры
+ (j - idx_x) + x,
+ (i - idx_y) + y,
+ )
+ )
return points
def get_min_x(self) -> int:
@@ -121,10 +124,10 @@ def get_min_y(self) -> int:
def get_max_y(self) -> int:
return max(y for _, y in self.get_points())
- def go_to_next_state(self):
+ def go_to_next_state(self) -> None:
self.current_state = self.STATES[self.current_state]
- def set_points(self, points: list[tuple[int, int]]):
+ def set_points(self, points: list[tuple[int, int]]) -> None:
self.points = points
def get_points(self) -> list[tuple[int, int]]:
@@ -235,7 +238,7 @@ def _get_all_states(self) -> list[list[str]]:
return self._
def get_color(self) -> QColor:
- return QColor(Qt.cyan)
+ return QColor("#7F00FF") # Violet
class PieceS(Piece):
diff --git a/games/tetris/src/gui/__init__.py b/games/tetris/src/gui/__init__.py
new file mode 100644
index 000000000..f25732790
--- /dev/null
+++ b/games/tetris/src/gui/__init__.py
@@ -0,0 +1,4 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+__author__ = "ipetrash"
diff --git a/games/tetris/src/gui/board_widget.py b/games/tetris/src/gui/board_widget.py
new file mode 100644
index 000000000..f93d80b16
--- /dev/null
+++ b/games/tetris/src/gui/board_widget.py
@@ -0,0 +1,329 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+__author__ = "ipetrash"
+
+
+import random
+
+from PyQt5.QtCore import pyqtSignal, QTimer, QSize, Qt
+from PyQt5.QtGui import QPainter, QColor, QResizeEvent, QPaintEvent
+from PyQt5.QtWidgets import QWidget
+
+from ..core.board import Board
+from ..core.common import ms_to_str
+from .common import CELL_SIZE, StatusGameEnum, painter_context, draw_cell_board
+
+
+class BoardWidget(QWidget):
+ INDENT: int = 1
+ SPEED_MS: int = 400
+
+ on_tick = pyqtSignal()
+ on_before_start = pyqtSignal()
+ on_finish = pyqtSignal()
+
+ def __init__(self) -> None:
+ super().__init__()
+
+ self.board = Board()
+ self.seed: str | None = None
+
+ self.cell_size: int = CELL_SIZE
+
+ self.__timer = QTimer()
+ self.__timer.timeout.connect(self._on_tick)
+ self.__timer.setInterval(self.SPEED_MS)
+
+ self.playing_time_ms: int = 0
+ self.__status = StatusGameEnum.INITED
+
+ def minimumSizeHint(self) -> QSize:
+ columns = self.board.COLS
+ width = (CELL_SIZE * columns) + (self.INDENT * 2)
+
+ rows = self.board.ROWS
+ height = (CELL_SIZE * rows) + (self.INDENT * 2)
+ return QSize(width, height)
+
+ def _fill_random(self) -> None:
+ while True:
+ try:
+ self.board.clear()
+ self.board.current_piece = self.board.get_random_piece()
+ self.board.next_piece = self.board.get_random_piece()
+
+ while True:
+ filled_cells = sum(
+ sum(1 for cell in row if cell is not None)
+ for row in self.board.matrix
+ )
+ # Заполнили поле на нужное количество ячеек
+ if filled_cells >= 40:
+ return
+
+ # С рандомом не повезло нужно заново заполнить
+ if not self.board.do_step():
+ break
+
+ if not self.board.current_piece:
+ continue
+
+ for _ in range(random.randrange(0, self.board.COLS)):
+ match random.randint(1, 4):
+ case 1:
+ self.board.current_piece.move_left()
+ case 2:
+ self.board.current_piece.move_right()
+ case 3:
+ self.board.current_piece.turn()
+ case 4:
+ while self.board.current_piece.move_down():
+ pass
+
+ finally:
+ self.board.score = 0
+
+ def start(self) -> None:
+ if self.status not in [StatusGameEnum.INITED, StatusGameEnum.FINISHED]:
+ return
+
+ self.status = StatusGameEnum.INITED
+
+ self.on_before_start.emit()
+
+ random.seed(self.seed)
+ if self.seed:
+ self._fill_random()
+
+ self.status = StatusGameEnum.STARTED
+ self.update()
+
+ @property
+ def status(self) -> StatusGameEnum:
+ return self.__status
+
+ @status.setter
+ def status(self, value: StatusGameEnum) -> None:
+ if self.__status == value:
+ return
+
+ self.__status = value
+
+ match value:
+ case StatusGameEnum.INITED:
+ self.__timer.stop()
+ self.board.clear()
+ self.playing_time_ms = 0
+
+ case StatusGameEnum.STARTED:
+ self.__timer.start()
+
+ case StatusGameEnum.PAUSED:
+ self.__timer.stop()
+
+ case StatusGameEnum.FINISHED:
+ self.__timer.stop()
+ self.on_finish.emit()
+
+ def abort_game(self) -> None:
+ self.status = StatusGameEnum.FINISHED
+
+ def _on_logic(self) -> None:
+ if not self.board.do_step():
+ self.abort_game()
+
+ def _on_tick(self) -> None:
+ self._on_logic()
+
+ self.update()
+
+ self.playing_time_ms += self.__timer.interval()
+ self.on_tick.emit()
+
+ @painter_context
+ def _draw_board(self, painter: QPainter) -> None:
+ # Рисование заполненных ячеек
+ for y, row in enumerate(self.board.matrix):
+ for x, cell_color in enumerate(row):
+ if not cell_color:
+ continue
+
+ draw_cell_board(
+ painter,
+ x,
+ y,
+ cell_color,
+ cell_size=self.cell_size,
+ indent=self.INDENT,
+ )
+
+ painter.setPen(Qt.black)
+
+ # Горизонтальные линии
+ y1 = y2 = self.INDENT
+ x1 = self.INDENT
+ x2 = self.cell_size * self.board.COLS + self.INDENT
+ for i in range(self.board.ROWS + 1):
+ painter.drawLine(x1, y1, x2, y2)
+ y1 += self.cell_size
+ y2 += self.cell_size
+
+ # Вертикальные линии
+ x1 = x2 = self.INDENT
+ y1 = self.INDENT
+ y2 = self.cell_size * self.board.ROWS + self.INDENT
+ for i in range(self.board.COLS + 1):
+ painter.drawLine(x1, y1, x2, y2)
+ x1 += self.cell_size
+ x2 += self.cell_size
+
+ @painter_context
+ def _draw_current_piece(self, painter: QPainter) -> None:
+ if not self.board.current_piece:
+ return
+
+ for x, y in self.board.current_piece.get_points():
+ draw_cell_board(
+ painter,
+ x,
+ y,
+ self.board.current_piece.get_color(),
+ cell_size=self.cell_size,
+ indent=self.INDENT,
+ )
+
+ @painter_context
+ def _draw_shadow_of_current_piece(self, painter: QPainter) -> None:
+ if not self.board.current_piece:
+ return
+
+ points: list[tuple[int, int]] = self.board.current_piece.get_points()
+
+ color: QColor = self.board.current_piece.get_color()
+ color.setAlphaF(0.2)
+
+ # Рисование тени
+ for y, row in enumerate(self.board.matrix):
+ for x, _ in enumerate(row):
+ max_piece_y = max((py for px, py in points if px == x), default=-1)
+
+ min_field_y = -1
+ for y_, row_ in enumerate(self.board.matrix):
+ cell_color: QColor | None = row_[x]
+ if not cell_color:
+ continue
+
+ min_field_y = y_
+ break
+
+ if y <= max_piece_y or (min_field_y != -1 and y >= min_field_y):
+ continue
+
+ if (
+ x < self.board.current_piece.get_min_x()
+ or x > self.board.current_piece.get_max_x()
+ ):
+ continue
+
+ draw_cell_board(
+ painter, x, y, color, cell_size=self.cell_size, indent=self.INDENT
+ )
+
+ @painter_context
+ def _draw_glass(self, painter: QPainter) -> None:
+ match self.status:
+ case StatusGameEnum.INITED:
+ text = "Press START"
+ case StatusGameEnum.PAUSED:
+ text = "PAUSE"
+ case StatusGameEnum.FINISHED:
+ playing_time = ms_to_str(self.playing_time_ms)
+ text = f"FINISHED\nScore: {self.board.score}\nTime: {playing_time}"
+ case _:
+ return
+
+ # Алгоритм изменения размера текста взят из http://stackoverflow.com/a/2204501
+ # Для текущего пришлось немного адаптировать
+ max_line_width = max(
+ painter.fontMetrics().width(line) for line in text.splitlines()
+ )
+ factor = min(self.width(), self.height()) / max_line_width
+ if factor < 1 or factor > 1.25:
+ f = painter.font()
+
+ # NOTE: Замечено, что или pointSizeF, или pixelSize будет =1
+ if f.pointSizeF() > 0:
+ point_size = f.pointSizeF() * factor
+ if point_size > 0:
+ f.setPointSizeF(point_size)
+ else:
+ pixel_size = int(f.pixelSize() * factor)
+ if pixel_size > 0:
+ f.setPixelSize(pixel_size)
+
+ painter.setFont(f)
+
+ painter.setPen(Qt.black)
+
+ brush = QColor(Qt.lightGray)
+ brush.setAlphaF(0.6)
+
+ painter.setBrush(brush)
+
+ painter.drawRect(self.rect())
+ painter.drawText(self.rect(), Qt.AlignCenter, text)
+
+ def process_key(self, key: int) -> None:
+ if key == Qt.Key_Space and self.status in [
+ StatusGameEnum.STARTED,
+ StatusGameEnum.PAUSED,
+ ]:
+ if self.status == StatusGameEnum.STARTED:
+ self.status = StatusGameEnum.PAUSED
+ else:
+ self.status = StatusGameEnum.STARTED
+
+ self.update()
+ return
+
+ if key in [Qt.Key_Enter, Qt.Key_Return]:
+ self.start()
+ return
+
+ if self.board.current_piece and self.status == StatusGameEnum.STARTED:
+ match key:
+ case Qt.Key_Left:
+ self.board.current_piece.move_left()
+
+ case Qt.Key_Right:
+ self.board.current_piece.move_right()
+
+ case Qt.Key_Up:
+ self.board.current_piece.turn()
+
+ case Qt.Key_Down:
+ while self.board.current_piece.move_down():
+ pass
+
+ self.update()
+ return
+
+ def resizeEvent(self, event: QResizeEvent) -> None:
+ super().resizeEvent(event)
+
+ w_aspect = event.size().width() // self.board.COLS
+ h_aspect = event.size().height() // self.board.ROWS
+
+ self.cell_size = min(w_aspect, h_aspect)
+
+ self.update()
+
+ def paintEvent(self, event: QPaintEvent) -> None:
+ painter = QPainter(self)
+ painter.setRenderHint(QPainter.Antialiasing)
+
+ self._draw_current_piece(painter)
+ self._draw_shadow_of_current_piece(painter)
+ self._draw_board(painter)
+ self._draw_glass(painter)
diff --git a/games/tetris/src/gui/common.py b/games/tetris/src/gui/common.py
new file mode 100644
index 000000000..1aee93623
--- /dev/null
+++ b/games/tetris/src/gui/common.py
@@ -0,0 +1,61 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+__author__ = "ipetrash"
+
+
+import enum
+import functools
+from typing import Callable
+
+from PyQt5.QtCore import Qt
+from PyQt5.QtGui import QPainter, QColor
+
+
+CELL_SIZE: int = 20
+
+
+def painter_context(func: Callable):
+ @functools.wraps(func)
+ def decorated(*args, **kwargs):
+ painter: QPainter | None = None
+ for arg in args + tuple(kwargs.values()):
+ if isinstance(arg, QPainter):
+ painter = arg
+ break
+
+ if painter:
+ painter.save()
+ try:
+ return func(*args, **kwargs)
+ finally:
+ if painter:
+ painter.restore()
+
+ return decorated
+
+
+def draw_cell_board(
+ painter: QPainter,
+ x: int,
+ y: int,
+ color: QColor,
+ pen: Qt.GlobalColor = Qt.NoPen,
+ cell_size: int = CELL_SIZE,
+ indent: int = 0,
+) -> None:
+ painter.setPen(pen)
+ painter.setBrush(color)
+ painter.drawRect(
+ (x * cell_size) + indent,
+ (y * cell_size) + indent,
+ cell_size,
+ cell_size,
+ )
+
+
+class StatusGameEnum(enum.IntEnum):
+ INITED = enum.auto()
+ STARTED = enum.auto()
+ PAUSED = enum.auto()
+ FINISHED = enum.auto()
diff --git a/games/tetris/src/gui/piece_widget.py b/games/tetris/src/gui/piece_widget.py
new file mode 100644
index 000000000..e57ada03f
--- /dev/null
+++ b/games/tetris/src/gui/piece_widget.py
@@ -0,0 +1,38 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+__author__ = "ipetrash"
+
+from PyQt5.QtCore import QSize, Qt
+from PyQt5.QtGui import QPaintEvent, QPainter
+from PyQt5.QtWidgets import QWidget
+
+from ..core.piece import Piece
+from .common import CELL_SIZE, draw_cell_board
+
+
+class PieceWidget(QWidget):
+ INDENT: int = 1
+
+ def __init__(self, parent: QWidget | None = None) -> None:
+ super().__init__(parent)
+
+ self.piece: Piece | None = None
+
+ def set_piece(self, piece: Piece) -> None:
+ self.piece = piece
+ self.update()
+
+ def minimumSizeHint(self) -> QSize:
+ size = (CELL_SIZE * 4) + (self.INDENT * 2)
+ return QSize(size, size)
+
+ def paintEvent(self, event: QPaintEvent) -> None:
+ if not self.piece:
+ return
+
+ painter = QPainter(self)
+ painter.setRenderHint(QPainter.Antialiasing)
+
+ for x, y in self.piece.get_points_for_state(x=2, y=1):
+ draw_cell_board(painter, x, y, self.piece.get_color(), pen=Qt.black)
diff --git a/games/tic_tac_toe__example.py b/games/tic_tac_toe__example.py
index 4afd8c111..85fe6a6a6 100644
--- a/games/tic_tac_toe__example.py
+++ b/games/tic_tac_toe__example.py
@@ -15,7 +15,7 @@
class Window(QWidget):
- def __init__(self):
+ def __init__(self) -> None:
super().__init__()
# Матрица 3 на 3
@@ -26,7 +26,7 @@ def __init__(self):
# If True -- X else -- O
self._current_figure_flag = True
- def _check_win(self):
+ def _check_win(self) -> None:
b = self._board_tic_tac_toe
win_X = False
win_O = False
@@ -80,7 +80,7 @@ def _check_win(self):
QMessageBox.information(self, "Winner", "Win O")
return
- def mouseReleaseEvent(self, e):
+ def mouseReleaseEvent(self, e) -> None:
# Определяем позицию клика
i = e.pos().y() // self._size_cell
j = e.pos().x() // self._size_cell
@@ -97,7 +97,7 @@ def mouseReleaseEvent(self, e):
self._check_win()
- def paintEvent(self, e):
+ def paintEvent(self, e) -> None:
painter = QPainter(self)
painter.setPen(Qt.black)
diff --git a/get_actual_news_from_rss_ya/webserver/common.py b/get_actual_news_from_rss_ya/webserver/common.py
index 4ec6889cc..6e6bdd67b 100644
--- a/get_actual_news_from_rss_ya/webserver/common.py
+++ b/get_actual_news_from_rss_ya/webserver/common.py
@@ -24,7 +24,7 @@ def create_connect():
return sqlite3.connect(DB_FILE_NAME)
-def init_db():
+def init_db() -> None:
# Создание базы и таблицы
connect = create_connect()
try:
@@ -71,10 +71,10 @@ def init_db():
connect.close()
-def append_list_news(list_news: [str, str], interest: str):
+def append_list_news(list_news: [str, str], interest: str) -> None:
connect = create_connect()
- def insert_news(title, url, interest):
+ def insert_news(title, url, interest) -> None:
# Для отсеивания дубликатов
has = connect.execute("SELECT 1 FROM News WHERE url = ?", (url,)).fetchone()
if has:
@@ -161,7 +161,7 @@ def get_news_list_and_mark_as_read(
connect.close()
-def reset_all_is_read():
+def reset_all_is_read() -> None:
connect = create_connect()
try:
diff --git a/get_all_objects_in_memory.py b/get_all_objects_in_memory.py
index 42a756ead..ca9390429 100644
--- a/get_all_objects_in_memory.py
+++ b/get_all_objects_in_memory.py
@@ -14,7 +14,7 @@
# Recursively expand slist's objects
# into olist, using seen to track
# already processed objects.
-def _getr(slist, olist, seen):
+def _getr(slist, olist, seen) -> None:
for e in slist:
if id(e) in seen:
continue
@@ -47,7 +47,7 @@ def get_all_objects():
print(len(gc.get_objects()))
class Foo:
- def bar(self):
+ def bar(self) -> str:
return f"Foo(id={hex(id(self))})"
items = [Foo() for _ in range(3)]
diff --git a/get_cpu_process/get_cpu_process.py b/get_cpu_process/get_cpu_process.py
index d498631b1..c03b6151a 100644
--- a/get_cpu_process/get_cpu_process.py
+++ b/get_cpu_process/get_cpu_process.py
@@ -14,7 +14,7 @@
class ProcessNotFound(Exception):
- def __init__(self, name: str):
+ def __init__(self, name: str) -> None:
super().__init__(f"Process {name!r} not found")
diff --git a/get_external_ip.py b/get_external_ip.py
deleted file mode 100644
index 76081c042..000000000
--- a/get_external_ip.py
+++ /dev/null
@@ -1,16 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-__author__ = "ipetrash"
-
-
-import requests
-
-
-# SOURCE: https://stackoverflow.com/a/54585203/5909792
-def get_external_ip() -> str:
- return requests.get("https://www.wikipedia.org").headers["X-Client-IP"]
-
-
-if __name__ == "__main__":
- print(get_external_ip())
diff --git a/get_function_caller_name.py b/get_function_caller_name.py
index 36bbd295b..52de9833c 100644
--- a/get_function_caller_name.py
+++ b/get_function_caller_name.py
@@ -14,17 +14,17 @@ def caller_name() -> str:
if __name__ == "__main__":
- def foo():
+ def foo() -> None:
print(caller_name())
assert caller_name() == "foo"
foo()
- def bar():
+ def bar() -> None:
print(caller_name())
assert caller_name() == "bar"
- def zoo():
+ def zoo() -> None:
print(caller_name())
assert caller_name() == "zoo"
diff --git a/get_functions_from_object__module_class_object.py b/get_functions_from_object__module_class_object.py
index df7263a0e..38563a639 100644
--- a/get_functions_from_object__module_class_object.py
+++ b/get_functions_from_object__module_class_object.py
@@ -20,7 +20,7 @@ def get_name_by_func(obj: object) -> dict:
print(get_name_by_func(builtins))
class Foo:
- def a(self):
+ def a(self) -> int:
return 1
print(get_name_by_func(Foo))
diff --git a/get_md5_file/get_md5_file_gui.py b/get_md5_file/get_md5_file_gui.py
index aa1ad0e72..17628e6d5 100644
--- a/get_md5_file/get_md5_file_gui.py
+++ b/get_md5_file/get_md5_file_gui.py
@@ -45,7 +45,7 @@
from get_md5_file import md5sum
-def log_uncaught_exceptions(ex_cls, ex, tb):
+def log_uncaught_exceptions(ex_cls, ex, tb) -> None:
text = f"{ex_cls.__name__}: {ex}:\n"
text += "".join(traceback.format_tb(tb))
@@ -61,7 +61,7 @@ def log_uncaught_exceptions(ex_cls, ex, tb):
class MainWindow(QMainWindow):
- def __init__(self):
+ def __init__(self) -> None:
super().__init__()
self.setWindowTitle("get_md5_file_gui")
@@ -97,7 +97,7 @@ def __init__(self):
self.setCentralWidget(central_widget)
- def dragEnterEvent(self, event):
+ def dragEnterEvent(self, event) -> None:
mime = event.mimeData()
if mime.hasUrls() and len(mime.urls()) == 1:
event.acceptProposedAction()
diff --git a/get_my_public_ip/README.md b/get_my_public_ip/README.md
deleted file mode 100644
index 8f3f1acea..000000000
--- a/get_my_public_ip/README.md
+++ /dev/null
@@ -1,4 +0,0 @@
-get_my_public_ip
-===========
-
-RU: Содержит пример того как можно узнать свой IP.
\ No newline at end of file
diff --git a/get_my_public_ip/get_my_public_ip__using__ipify_org.py b/get_my_public_ip/api_ipify_org.py
similarity index 100%
rename from get_my_public_ip/get_my_public_ip__using__ipify_org.py
rename to get_my_public_ip/api_ipify_org.py
diff --git a/get_my_public_ip/get_my_public_ip__using__api_wipmania_com__with_grab.py b/get_my_public_ip/get_my_public_ip__using__api_wipmania_com__with_grab.py
deleted file mode 100644
index 1a99a7c0e..000000000
--- a/get_my_public_ip/get_my_public_ip__using__api_wipmania_com__with_grab.py
+++ /dev/null
@@ -1,23 +0,0 @@
-__author__ = "ipetrash"
-
-
-"""
-Пример того как можно узнать свой ip,
-используя запрос на сайт и модуль grab.
-"""
-
-
-from grab import Grab
-
-
-def get_my_public_ip() -> str:
- g = Grab()
- g.setup(proxy="proxy.compassplus.ru:3128", proxy_type="http")
- g.go("http://api.wipmania.com")
- context = g.doc.body.decode("utf-8")
- return context.split("
")[0]
-
-
-if __name__ == "__main__":
- ip = get_my_public_ip()
- print(f"My IP: {ip}")
diff --git a/get_my_public_ip/httpbin_org.py b/get_my_public_ip/httpbin_org.py
new file mode 100644
index 000000000..e49ba702e
--- /dev/null
+++ b/get_my_public_ip/httpbin_org.py
@@ -0,0 +1,18 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+__author__ = "ipetrash"
+
+
+import json
+from urllib.request import urlopen
+
+
+def get_my_public_ip() -> str:
+ with urlopen("https://httpbin.org/get") as f:
+ return json.loads(f.read())["origin"]
+
+
+if __name__ == "__main__":
+ ip = get_my_public_ip()
+ print(f"My IP: {ip}")
diff --git a/get_my_public_ip/get_my_public_ip__using__jsonip_com.py b/get_my_public_ip/jsonip_com.py
similarity index 100%
rename from get_my_public_ip/get_my_public_ip__using__jsonip_com.py
rename to get_my_public_ip/jsonip_com.py
diff --git a/get_my_public_ip/get_my_public_ip__using__api_wipmania_com.py b/get_my_public_ip/wikipedia_org.py
similarity index 67%
rename from get_my_public_ip/get_my_public_ip__using__api_wipmania_com.py
rename to get_my_public_ip/wikipedia_org.py
index aeb9de5f5..9f313336a 100644
--- a/get_my_public_ip/get_my_public_ip__using__api_wipmania_com.py
+++ b/get_my_public_ip/wikipedia_org.py
@@ -8,8 +8,8 @@
def get_my_public_ip() -> str:
- with urlopen("http://api.wipmania.com") as f:
- return f.read().decode("utf-8").split("
")[0]
+ with urlopen("https://www.wikipedia.org") as f:
+ return f.getheader("X-Client-IP")
if __name__ == "__main__":
diff --git a/get_quarter.py b/get_quarter.py
index fd1f1d718..5e65bdb03 100644
--- a/get_quarter.py
+++ b/get_quarter.py
@@ -7,7 +7,7 @@
from datetime import date, datetime
-def get_quarter(month_or_date=None) -> int:
+def get_quarter_num(month_or_date: date | datetime | int | None = None) -> int:
dt = month_or_date
if dt is None:
dt = date.today()
@@ -33,44 +33,44 @@ def get_quarter(month_or_date=None) -> int:
raise Exception(f'Invalid "month": {month}')
-def get_quarter_num(month_or_date=None) -> str:
- return ["I", "II", "III", "IV"][get_quarter(month_or_date) - 1]
+def get_quarter_roman(month_or_date: date | datetime | int | None = None) -> str:
+ return ["I", "II", "III", "IV"][get_quarter_num(month_or_date) - 1]
if __name__ == "__main__":
- print(get_quarter())
- print()
- print(get_quarter(datetime.today()))
- print(get_quarter(date.today()))
- print()
print(get_quarter_num())
+ print(get_quarter_num(datetime.today()))
+ print(get_quarter_num(date.today()))
+ print()
+
+ print(get_quarter_roman())
print()
for dt in [date(2018, month=i + 1, day=1) for i in range(12)]:
- print(dt, get_quarter(dt))
-
- assert get_quarter(1) == 1
- assert get_quarter(2) == 1
- assert get_quarter(3) == 1
- assert get_quarter(4) == 2
- assert get_quarter(5) == 2
- assert get_quarter(6) == 2
- assert get_quarter(7) == 3
- assert get_quarter(8) == 3
- assert get_quarter(9) == 3
- assert get_quarter(10) == 4
- assert get_quarter(11) == 4
- assert get_quarter(12) == 4
-
- assert get_quarter_num(1) == "I"
- assert get_quarter_num(2) == "I"
- assert get_quarter_num(3) == "I"
- assert get_quarter_num(4) == "II"
- assert get_quarter_num(5) == "II"
- assert get_quarter_num(6) == "II"
- assert get_quarter_num(7) == "III"
- assert get_quarter_num(8) == "III"
- assert get_quarter_num(9) == "III"
- assert get_quarter_num(10) == "IV"
- assert get_quarter_num(11) == "IV"
- assert get_quarter_num(12) == "IV"
+ print(dt, get_quarter_num(dt))
+
+ assert get_quarter_num(1) == 1
+ assert get_quarter_num(2) == 1
+ assert get_quarter_num(3) == 1
+ assert get_quarter_num(4) == 2
+ assert get_quarter_num(5) == 2
+ assert get_quarter_num(6) == 2
+ assert get_quarter_num(7) == 3
+ assert get_quarter_num(8) == 3
+ assert get_quarter_num(9) == 3
+ assert get_quarter_num(10) == 4
+ assert get_quarter_num(11) == 4
+ assert get_quarter_num(12) == 4
+
+ assert get_quarter_roman(1) == "I"
+ assert get_quarter_roman(2) == "I"
+ assert get_quarter_roman(3) == "I"
+ assert get_quarter_roman(4) == "II"
+ assert get_quarter_roman(5) == "II"
+ assert get_quarter_roman(6) == "II"
+ assert get_quarter_roman(7) == "III"
+ assert get_quarter_roman(8) == "III"
+ assert get_quarter_roman(9) == "III"
+ assert get_quarter_roman(10) == "IV"
+ assert get_quarter_roman(11) == "IV"
+ assert get_quarter_roman(12) == "IV"
diff --git a/github_api__examples/common.py b/github_api__examples/common.py
index f3fd44b91..b63ea6567 100644
--- a/github_api__examples/common.py
+++ b/github_api__examples/common.py
@@ -3,10 +3,59 @@
__author__ = "ipetrash"
+
+import os
+
+from pathlib import Path
from dataclasses import dataclass
+import requests
+
@dataclass
class User:
login: str
url: str
+
+
+DIR: Path = Path(__file__).resolve().parent
+
+TOKEN_FILE_NAME: Path = DIR / "TOKEN.txt"
+try:
+ TOKEN: str = os.environ.get("TOKEN") or TOKEN_FILE_NAME.read_text("utf-8").strip()
+except:
+ TOKEN: str = ""
+
+
+session = requests.Session()
+
+if TOKEN:
+ session.headers["Authorization"] = f"Bearer {TOKEN}"
+
+
+def get_users(url: str) -> list[User]:
+ per_page: int = 100
+ page: int = 1
+
+ items: list[User] = []
+
+ while True:
+ params = dict(per_page=per_page, page=page)
+ rs = session.get(url, params=params)
+ rs.raise_for_status()
+
+ result: list[dict] = rs.json()
+ if not result:
+ break
+
+ items += [
+ User(
+ login=item["login"],
+ url=item["html_url"],
+ )
+ for item in result
+ ]
+
+ page += 1
+
+ return items
diff --git a/github_api__examples/get_followers.py b/github_api__examples/get_followers.py
index 7c5b4cdb4..68c3b7ad7 100644
--- a/github_api__examples/get_followers.py
+++ b/github_api__examples/get_followers.py
@@ -4,48 +4,22 @@
__author__ = "ipetrash"
-import requests
-from common import User
+from common import User, get_users
def get_followers(owner: str) -> list[User]:
- url = f"https://api.github.com/users/{owner}/followers"
-
- per_page = 100
- page = 1
-
- items = []
-
- while True:
- params = dict(per_page=per_page, page=page)
- rs = requests.get(url, params=params)
- rs.raise_for_status()
-
- result: list[dict] = rs.json()
- if not result:
- break
-
- items += [
- User(
- login=item["login"],
- url=item["html_url"],
- )
- for item in result
- ]
-
- page += 1
-
- return items
+ url: str = f"https://api.github.com/users/{owner}/followers"
+ return get_users(url)
if __name__ == "__main__":
- users = get_followers("gil9red")
+ users: list[User] = get_followers("gil9red")
print(f"Followers ({len(users)}):")
print(*users[:5], sep="\n")
print("...")
print(*users[-5:], sep="\n")
"""
- Followers (92):
+ Followers (98):
User(login='shakshin', url='https://github.com/shakshin')
User(login='AgelxNash', url='https://github.com/AgelxNash')
User(login='insolor', url='https://github.com/insolor')
diff --git a/github_api__examples/get_last_commit.py b/github_api__examples/get_last_commit.py
new file mode 100644
index 000000000..1bf85be62
--- /dev/null
+++ b/github_api__examples/get_last_commit.py
@@ -0,0 +1,63 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+__author__ = "ipetrash"
+
+
+from dataclasses import dataclass
+from datetime import UTC, datetime
+from typing import Any
+
+from common import session
+
+
+@dataclass
+class CommitUser:
+ name: str
+ email: str
+ date: datetime
+
+ @classmethod
+ def parse_from_dict(cls, data: dict[str, Any]) -> "CommitUser":
+ return cls(
+ name=data["name"],
+ email=data["email"],
+ date=datetime.fromisoformat(data["date"]),
+ )
+
+
+# TODO: Поддержка всех полей
+@dataclass
+class Commit:
+ sha: str
+ message: str
+ author: CommitUser
+ committer: CommitUser
+
+ @classmethod
+ def parse_from_dict(cls, data: dict[str, Any]) -> "Commit":
+ return cls(
+ sha=data["sha"],
+ message=data["commit"]["message"],
+ author=CommitUser.parse_from_dict(data["commit"]["author"]),
+ committer=CommitUser.parse_from_dict(data["commit"]["committer"]),
+ )
+
+
+def get_last_commit(owner: str, repository: str) -> Commit:
+ url: str = f"https://api.github.com/repos/{owner}/{repository}/commits?per_page=1"
+
+ rs = session.get(url)
+ rs.raise_for_status()
+
+ result: dict[str, Any] = rs.json()[0]
+ return Commit.parse_from_dict(result)
+
+
+if __name__ == "__main__":
+ commit = get_last_commit(owner="gil9red", repository="RPG-Maker-MZ-Foo")
+ print(commit)
+ # Commit(sha='816a821788e113fdc80d17190fa1ed743c4b1524', message='Обновление. Улучшение алгоритма шагающей леди', author=CommitUser(name='gil9red', email='ilya.petrash@inbox.ru', date=datetime.datetime(2025, 10, 25, 18, 53, 41, tzinfo=datetime.timezone.utc)), committer=CommitUser(name='gil9red', email='ilya.petrash@inbox.ru', date=datetime.datetime(2025, 10, 25, 18, 53, 41, tzinfo=datetime.timezone.utc)))
+
+ print(datetime.now(UTC) - commit.author.date)
+ # 2 days, 14:21:39.576065
diff --git a/github_api__examples/get_stargazers.py b/github_api__examples/get_stargazers.py
index 84b1f22a1..af68c3f97 100644
--- a/github_api__examples/get_stargazers.py
+++ b/github_api__examples/get_stargazers.py
@@ -4,57 +4,31 @@
__author__ = "ipetrash"
-import requests
-from common import User
+from common import User, get_users
def get_stargazers(owner: str, repository: str) -> list[User]:
- url = f"https://api.github.com/repos/{owner}/{repository}/stargazers"
-
- per_page = 100
- page = 1
-
- items = []
-
- while True:
- params = dict(per_page=per_page, page=page)
- rs = requests.get(url, params=params)
- rs.raise_for_status()
-
- result: list[dict] = rs.json()
- if not result:
- break
-
- items += [
- User(
- login=item["login"],
- url=item["html_url"],
- )
- for item in result
- ]
-
- page += 1
-
- return items
+ url: str = f"https://api.github.com/repos/{owner}/{repository}/stargazers"
+ return get_users(url)
if __name__ == "__main__":
- users = get_stargazers("gil9red", "SimplePyScripts")
+ users: list[User] = get_stargazers("gil9red", "SimplePyScripts")
print(f"Stargazers ({len(users)}):")
print(*users[:5], sep="\n")
print("...")
print(*users[-5:], sep="\n")
"""
- Stargazers (148):
+ Stargazers (153):
User(login='numb7', url='https://github.com/numb7')
User(login='Martin-Winter', url='https://github.com/Martin-Winter')
User(login='Shaar68', url='https://github.com/Shaar68')
User(login='triplekill', url='https://github.com/triplekill')
User(login='JMSwag', url='https://github.com/JMSwag')
...
- User(login='Aarab228', url='https://github.com/Aarab228')
- User(login='jjh4568520', url='https://github.com/jjh4568520')
- User(login='eplord', url='https://github.com/eplord')
- User(login='EzzalddeenAli', url='https://github.com/EzzalddeenAli')
- User(login='techbrain19', url='https://github.com/techbrain19')
+ User(login='bobophp', url='https://github.com/bobophp')
+ User(login='emnioj', url='https://github.com/emnioj')
+ User(login='Wisepal61', url='https://github.com/Wisepal61')
+ User(login='Codycode91', url='https://github.com/Codycode91')
+ User(login='jellykioto', url='https://github.com/jellykioto')
"""
diff --git a/gitpython_examples/common.py b/gitpython_examples/common.py
index ebe0c1da1..49a4e0198 100644
--- a/gitpython_examples/common.py
+++ b/gitpython_examples/common.py
@@ -31,7 +31,6 @@
def get_repo():
try:
return git.Repo(REPO_PATH)
-
except:
return git.Repo.clone_from(URL_GIT, REPO_PATH)
@@ -46,7 +45,7 @@ def return_log(reverse=False):
return logs
-def print_log(reverse=False):
+def print_log(reverse=False) -> None:
logs = return_log(reverse)
print(f"Logs[{len(logs)}]:")
diff --git a/gitpython_examples/pull_all_repos.py b/gitpython_examples/pull_all_repos.py
index 156cb06d5..db96df41a 100644
--- a/gitpython_examples/pull_all_repos.py
+++ b/gitpython_examples/pull_all_repos.py
@@ -12,7 +12,7 @@
from common import get_total_commits
-def process_repository(repo_path: Path):
+def process_repository(repo_path: Path) -> None:
print(repo_path)
repo = git.Repo(repo_path)
@@ -31,7 +31,7 @@ def process_repository(repo_path: Path):
print(f"Repository is actual")
-def process_all(path: Path, pattern: str = '.git'):
+def process_all(path: Path, pattern: str = '.git') -> None:
paths: list[Path] = [
p.parent
for p in path.rglob(pattern)
diff --git a/hex2str/gui.py b/hex2str/gui.py
index 002df5707..3ac1a952e 100644
--- a/hex2str/gui.py
+++ b/hex2str/gui.py
@@ -25,7 +25,7 @@
from hex2str import hex2str, str2hex
-def log_uncaught_exceptions(ex_cls, ex, tb):
+def log_uncaught_exceptions(ex_cls, ex, tb) -> None:
text = f"{ex_cls.__name__}: {ex}:\n"
text += "".join(traceback.format_tb(tb))
@@ -38,7 +38,7 @@ def log_uncaught_exceptions(ex_cls, ex, tb):
class Widget(QWidget):
- def __init__(self):
+ def __init__(self) -> None:
super().__init__()
self.setWindowTitle("hex2str")
@@ -124,7 +124,7 @@ def __init__(self):
self.setLayout(layout)
- def _on_input_selection_changed(self):
+ def _on_input_selection_changed(self) -> None:
try:
# Нехорошо будет если второй будет вызывать сигналы, например о выделении текста
self.text_edit_output.blockSignals(True)
@@ -155,7 +155,7 @@ def _on_input_selection_changed(self):
finally:
self.text_edit_output.blockSignals(False)
- def _on_output_selection_changed(self):
+ def _on_output_selection_changed(self) -> None:
try:
# Нехорошо будет если второй будет вызывать сигналы, например о выделении текста
self.text_edit_input.blockSignals(True)
@@ -186,7 +186,7 @@ def _on_output_selection_changed(self):
finally:
self.text_edit_input.blockSignals(False)
- def show_detail_error_massage(self):
+ def show_detail_error_massage(self) -> None:
message = self.last_error_message + "\n\n" + self.last_detail_error_message
mb = QErrorMessage()
@@ -198,7 +198,7 @@ def show_detail_error_massage(self):
mb.exec_()
- def _convert(self):
+ def _convert(self) -> None:
self.label_error.clear()
self.button_detail_error.hide()
diff --git a/howlongtobeat__web_wrapper/README.md b/howlongtobeat__web_wrapper/README.md
new file mode 100644
index 000000000..807ae943f
--- /dev/null
+++ b/howlongtobeat__web_wrapper/README.md
@@ -0,0 +1,9 @@
+# Example
+
+http://127.0.0.1:10017/search-game/Half-Life%202
+http://127.0.0.1:10017/docs
+http://127.0.0.1:10017/redoc
+
+```
+python -m uvicorn main:app --port=10017
+```
diff --git a/howlongtobeat__web_wrapper/api_search.py b/howlongtobeat__web_wrapper/api_search.py
new file mode 100644
index 000000000..19645100b
--- /dev/null
+++ b/howlongtobeat__web_wrapper/api_search.py
@@ -0,0 +1,140 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+__author__ = "ipetrash"
+
+
+import re
+import unicodedata
+
+from typing import Any
+
+# NOTE: https://playwright.dev/python/docs/library#pip
+# pip install playwright==1.50.0
+# playwright install webkit
+from playwright.sync_api import Response, TimeoutError, sync_playwright
+
+DEFAULT_TIMEOUT_MS: int = 30_000
+
+
+def _is_found_game(game1: str, game2: str) -> bool:
+ # SOURCE: https://github.com/gil9red/price_of_games/blob/805f19528c3bcc0999669452c3652698fed22bcd/app_parser/utils.py#L219-L224
+ def strip_accents(s: str) -> str:
+ return "".join(
+ c
+ for c in unicodedata.normalize("NFD", s)
+ if unicodedata.category(c) != "Mn"
+ )
+
+ def _process_name(name: str) -> str:
+ name = strip_accents(name)
+ return re.sub(r"\W", "", name).lower()
+
+ return _process_name(game1) == _process_name(game2)
+
+
+def get_api_search_raw(game: str) -> dict[str, Any]:
+ def is_api_search(rs: Response) -> bool:
+ url: str = rs.url.lower()
+ return (
+ "/api/" in url
+ and rs.status == 200
+ and ("SEARCHTERMS" in str(rs.request.post_data).upper())
+ )
+
+ with sync_playwright() as p:
+ browser = p.firefox.launch()
+
+ page = browser.new_page()
+ page.set_default_timeout(DEFAULT_TIMEOUT_MS)
+
+ page.goto(f"https://howlongtobeat.com/?q={game}", wait_until="commit")
+
+ with page.expect_response(is_api_search) as response_info:
+ return response_info.value.json()
+
+
+def search_game(game: str, max_attempts: int = 5) -> dict[str, Any] | None:
+ game: str = re.sub("[©®™–]", "", game).replace("`", "'")
+
+ result: dict[str, Any] | None = None
+ for attempt in range(1, max_attempts + 1):
+ try:
+ result = get_api_search_raw(game)
+ break
+ except TimeoutError as e:
+ if attempt >= max_attempts:
+ raise e
+
+ if not result or "data" not in result:
+ return
+
+ for obj in result["data"]:
+ if (
+ _is_found_game(game, obj["game_name"])
+ # Поиск среди псевдонимов
+ or obj["game_alias"] == game
+ or any(_is_found_game(game, name) for name in obj["game_alias"].split(", "))
+ ):
+ return obj
+
+ return
+
+
+if __name__ == "__main__":
+ game = "Half-Life 2"
+ result = search_game(game)
+ print(game, result)
+ assert game == result["game_name"]
+
+ # game = "Warhammer 40,000: Space Marine 2"
+ # result = search_game(game)
+ # print(game, result)
+ # assert "Warhammer 40,000: Space Marine II" == result["game_name"]
+ #
+ # game = "Marc Eckō's Getting Up: Contents Under Pressure"
+ # result = search_game(game)
+ # print(game, result)
+ # assert "Marc Ecko's Getting Up: Contents Under Pressure" == result["game_name"]
+ #
+ # game = "Nightmares from the Deep 2: The Siren`s Call"
+ # result = search_game(game)
+ # print(game, result)
+ # assert "Nightmares from the Deep 2: The Siren's Call" == result["game_name"]
+ #
+ # print()
+ #
+ # game = search_game("Half-Life")
+ # print(game)
+ # assert game
+ #
+ # game = search_game("Final Fantasy IX")
+ # print(game)
+ # assert game
+ #
+ # game = search_game("Final Fantasy 9")
+ # print(game)
+ # assert game
+ #
+ # game = search_game("Final Fantasy VII")
+ # print(game)
+ # assert game
+
+ # from multiprocessing.dummy import Pool as ThreadPool
+ #
+ # with ThreadPool() as p:
+ # result = p.map(
+ # search_game,
+ # [
+ # "dfsdfsdfsdfsdfsdfsdfdsf",
+ # "Half-Life 2",
+ # "Half-Life",
+ # "Final Fantasy",
+ # "Final Fantasy IX",
+ # "Final Fantasy 9",
+ # "Final Fantasy VII",
+ # ],
+ # )
+ # print(len(result))
+ # for obj in result:
+ # print(obj)
diff --git a/howlongtobeat__web_wrapper/main.py b/howlongtobeat__web_wrapper/main.py
new file mode 100644
index 000000000..00f6bf92b
--- /dev/null
+++ b/howlongtobeat__web_wrapper/main.py
@@ -0,0 +1,29 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+__author__ = "ipetrash"
+
+
+from typing import Any
+from fastapi import FastAPI
+from api_search import search_game
+
+
+app = FastAPI()
+
+
+@app.get("/search-game/{name}")
+def index(name: str) -> dict[str, Any] | None:
+ return search_game(name)
+
+
+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=False,
+ )
diff --git a/html_parsing/rik_i_morti_online.py b/html_parsing/.deprecated/rik_i_morti_online.py
similarity index 100%
rename from html_parsing/rik_i_morti_online.py
rename to html_parsing/.deprecated/rik_i_morti_online.py
diff --git a/html_parsing/risens_team__berserk.py b/html_parsing/.deprecated/risens_team__berserk.py
similarity index 100%
rename from html_parsing/risens_team__berserk.py
rename to html_parsing/.deprecated/risens_team__berserk.py
diff --git a/html_parsing/yummyanime_club__anime_updates.py b/html_parsing/.deprecated/yummyanime_club__anime_updates.py
similarity index 97%
rename from html_parsing/yummyanime_club__anime_updates.py
rename to html_parsing/.deprecated/yummyanime_club__anime_updates.py
index 4a8312d0b..df45253a1 100644
--- a/html_parsing/yummyanime_club__anime_updates.py
+++ b/html_parsing/.deprecated/yummyanime_club__anime_updates.py
@@ -15,7 +15,7 @@ def get_text(el: Tag) -> str:
return text.replace("\xa0", " ")
-def parse_update_anime(update_el: Tag):
+def parse_update_anime(update_el: Tag) -> None:
title = get_text(update_el.select_one(".update-title"))
update_date = get_text(update_el.select_one(".update-date"))
update_info = get_text(update_el.select_one(".update-info"))
diff --git a/html_parsing/yummyanime_club__posts.py b/html_parsing/.deprecated/yummyanime_club__posts.py
similarity index 98%
rename from html_parsing/yummyanime_club__posts.py
rename to html_parsing/.deprecated/yummyanime_club__posts.py
index 16573f761..ffdedeb93 100644
--- a/html_parsing/yummyanime_club__posts.py
+++ b/html_parsing/.deprecated/yummyanime_club__posts.py
@@ -15,7 +15,7 @@ def get_text(el: Tag) -> str:
return text.replace("\xa0", " ")
-def parse_post_block(post_block: Tag):
+def parse_post_block(post_block: Tag) -> None:
title_el = post_block.select_one(".post-title")
url_post = urljoin(URL, title_el["href"])
title = get_text(title_el)
diff --git a/html_parsing/Time_magazine_covers/main.py b/html_parsing/Time_magazine_covers/main.py
index f62fddf5c..e5953953a 100644
--- a/html_parsing/Time_magazine_covers/main.py
+++ b/html_parsing/Time_magazine_covers/main.py
@@ -92,7 +92,7 @@ def dump_covers(
out_dir="out",
need_zip=False,
remove_out_covers=False,
-):
+) -> None:
covers = get_covers(year)
out_dir = normpath(abspath(out_dir + f"/{year}"))
diff --git a/html_parsing/com_x_life__berserk_chapters.py b/html_parsing/com_x_life__berserk_chapters.py
new file mode 100644
index 000000000..dd212762e
--- /dev/null
+++ b/html_parsing/com_x_life__berserk_chapters.py
@@ -0,0 +1,90 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+__author__ = "ipetrash"
+
+
+import json
+import re
+import time
+
+from dataclasses import dataclass
+from datetime import datetime, date
+from typing import Any, Self
+
+from playwright.sync_api import sync_playwright
+
+
+HOST: str = "https://com-x.life"
+URL: str = f"{HOST}/2789-berserk-read-online.html#chapters"
+
+
+@dataclass
+class Chapter:
+ title: str
+ url: str
+ pages: int
+ date: date
+
+ @classmethod
+ def from_dict(cls, chapter_id: int, d: dict[str, Any]) -> Self:
+ return cls(
+ title=d["title"],
+ url=f"{HOST}/reader/{chapter_id}/{d['id']}",
+ pages=d["pages"],
+ date=datetime.strptime(d["date"], "%d.%m.%Y").date(),
+ )
+
+
+def get_chapters() -> list[Chapter]:
+ chapters: list[Chapter] = []
+
+ with sync_playwright() as p:
+ browser = p.firefox.launch()
+ page = browser.new_page()
+
+ page.goto(URL)
+
+ for _ in range(10):
+ time.sleep(1)
+
+ text: str = page.content()
+
+ m = re.search(r"window.__DATA__\s*=\s*(\{.+?\});", text)
+ if not m:
+ continue
+
+ data: dict[str, Any] = json.loads(m.group(1))
+ chapter_id: int = data["news_id"]
+ chapters = [
+ Chapter.from_dict(chapter_id, chapter)
+ for chapter in data["chapters"]
+ ]
+ break
+
+ if not chapters:
+ raise Exception("Не удалось найти главы!")
+
+ return chapters
+
+
+if __name__ == "__main__":
+ chapters = get_chapters()
+ print(f"Chapters ({len(chapters)}):")
+ print(*chapters[:5], sep="\n")
+ print("...")
+ print(*chapters[-5:], sep="\n")
+ """
+ Chapters (400):
+ Chapter(title='Глава 383. Неведение безначально', url='https://com-x.life/reader/2789/741667', pages=24, date=datetime.date(2025, 9, 11))
+ Chapter(title='Глава 382. Усыпальница', url='https://com-x.life/reader/2789/719111', pages=22, date=datetime.date(2025, 6, 26))
+ Chapter(title='Глава 381. Полумесяц освещающий спину изгнанника', url='https://com-x.life/reader/2789/715388', pages=23, date=datetime.date(2025, 6, 13))
+ Chapter(title='Глава 380. Тени умирают дважды.', url='https://com-x.life/reader/2789/662311', pages=20, date=datetime.date(2025, 2, 28))
+ Chapter(title='379', url='https://com-x.life/reader/2789/656178', pages=22, date=datetime.date(2025, 2, 14))
+ ...
+ Chapter(title='Том 2. Глава 0D Хранители желаний II', url='https://com-x.life/reader/2789/417510', pages=125, date=datetime.date(2024, 2, 15))
+ Chapter(title='Том 1. Глава 0С Хранители желаний I', url='https://com-x.life/reader/2789/417509', pages=60, date=datetime.date(2024, 2, 15))
+ Chapter(title='Том 1. Глава 0B Клеймо', url='https://com-x.life/reader/2789/417508', pages=69, date=datetime.date(2024, 2, 15))
+ Chapter(title='Том 1. Глава 0А Чёрный Мечник', url='https://com-x.life/reader/2789/417507', pages=96, date=datetime.date(2024, 2, 15))
+ Chapter(title='Том 1. Глава 00 Прототип', url='https://com-x.life/reader/2789/417459', pages=46, date=datetime.date(2024, 2, 14))
+ """
diff --git "a/html_parsing/download_my_certificates_from_stepik_org/certs/\320\222\320\262\320\265\320\264\320\265\320\275\320\270\320\265 \320\262 SQL.pdf" "b/html_parsing/download_my_certificates_from_stepik_org/certs/\320\222\320\262\320\265\320\264\320\265\320\275\320\270\320\265 \320\262 SQL.pdf"
new file mode 100644
index 000000000..7522bbfa8
Binary files /dev/null and "b/html_parsing/download_my_certificates_from_stepik_org/certs/\320\222\320\262\320\265\320\264\320\265\320\275\320\270\320\265 \320\262 SQL.pdf" differ
diff --git a/html_parsing/encyclopedia_perumov_club__category__dose/db.py b/html_parsing/encyclopedia_perumov_club__category__dose/db.py
index b45f1162a..fd822418f 100644
--- a/html_parsing/encyclopedia_perumov_club__category__dose/db.py
+++ b/html_parsing/encyclopedia_perumov_club__category__dose/db.py
@@ -20,7 +20,7 @@
DB_FILE_NAME = str(pathlib.Path(__file__).resolve().parent / "database.sqlite")
-def db_create_backup(backup_dir="backup"):
+def db_create_backup(backup_dir="backup") -> None:
os.makedirs(backup_dir, exist_ok=True)
file_name = str(dt.datetime.today().date()) + ".sqlite"
@@ -43,7 +43,7 @@ class Dossier(BaseModel):
url = TextField(unique=True)
date = DateField(default=dt.date.today)
- def __str__(self):
+ def __str__(self) -> str:
return (
f"{self.__class__.__name__}(id={self.id}, title={self.title!r}, date={self.date}, "
f"url={self.url!r}, total_items={len(self.items)})"
@@ -55,7 +55,7 @@ class QuestionAnswer(BaseModel):
question_text = TextField()
answer_text = TextField()
- def __str__(self):
+ def __str__(self) -> str:
return (
f"{self.__class__.__name__}(id={self.id}, dossier_id={self.dossier.id}, "
f"question_text={shorten(self.question_text)!r}), answer_text={shorten(self.answer_text)!r})"
diff --git a/html_parsing/encyclopedia_perumov_club__category__dose/gui.py b/html_parsing/encyclopedia_perumov_club__category__dose/gui.py
index 401e0f7a2..e15f56afe 100644
--- a/html_parsing/encyclopedia_perumov_club__category__dose/gui.py
+++ b/html_parsing/encyclopedia_perumov_club__category__dose/gui.py
@@ -67,7 +67,7 @@ def get_table_widget(header_labels: list) -> QTableWidget:
class MainWindow(QMainWindow):
- def __init__(self):
+ def __init__(self) -> None:
super().__init__()
self.filter_le = QLineEdit()
@@ -123,7 +123,7 @@ def _get_search_pattern(self):
except:
pass
- def fill(self):
+ def fill(self) -> None:
# Удаление строк таблицы
while self.table_dossier.rowCount():
self.table_dossier.removeRow(0)
@@ -155,7 +155,7 @@ def fill(self):
self.table_dossier.setCurrentCell(0, 0)
self._on_table_dossier_item_clicked()
- def _on_table_dossier_item_clicked(self):
+ def _on_table_dossier_item_clicked(self) -> None:
# Удаление строк таблицы
while self.table_items.rowCount():
self.table_items.removeRow(0)
@@ -199,7 +199,7 @@ def _on_table_dossier_item_clicked(self):
self.table_items.setCurrentCell(0, 0)
self._on_table_items_item_clicked()
- def _on_table_items_item_clicked(self):
+ def _on_table_items_item_clicked(self) -> None:
item = self.table_items.currentItem()
if not item:
return
diff --git a/html_parsing/foxtrot_com_ua__ru__search/main.py b/html_parsing/foxtrot_com_ua__ru__search/main.py
index cd7ba0d27..8368e2bf6 100644
--- a/html_parsing/foxtrot_com_ua__ru__search/main.py
+++ b/html_parsing/foxtrot_com_ua__ru__search/main.py
@@ -78,7 +78,7 @@ def save_goods(
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)
diff --git a/html_parsing/gamesvoice_ru__get_finished.py b/html_parsing/gamesvoice_ru__get_finished.py
deleted file mode 100644
index e8f890508..000000000
--- a/html_parsing/gamesvoice_ru__get_finished.py
+++ /dev/null
@@ -1,105 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-__author__ = "ipetrash"
-
-
-from dataclasses import dataclass
-from urllib.parse import urljoin
-
-import requests
-from bs4 import BeautifulSoup, Tag
-
-
-@dataclass
-class Game:
- name: str
- url: str
- date_str: str
-
-
-def _get_text(el: Tag) -> str:
- if not el:
- return ""
- return el.get_text().replace("\u200b", " ").replace("\xa0", " ").strip()
-
-
-def _check_result(items: list[Game]):
- assert items, "Empty list"
- for game in [
- Game(
- name="A Plague Tale: Innocence",
- url="https://www.gamesvoice.ru/innocence",
- date_str="11 марта 2024",
- ),
- Game(
- name="Defenders of Ardania",
- url="https://www.gamesvoice.ru/doa",
- date_str="26 мая 2012",
- ),
- ]:
- assert game in items, f"Game not found: {game}"
-
-
-session = requests.session()
-session.headers[
- "User-Agent"
-] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0"
-
-
-def get_games() -> list[Game]:
- url = "https://www.gamesvoice.ru/"
-
- rs = session.get(url)
- rs.raise_for_status()
-
- items: list[Game] = []
-
- soup = BeautifulSoup(rs.content, "html.parser")
- for section in soup.select("section[data-block-level-container='ClassicSection']"):
- if "Локализации и озвучки GamesVoice" not in section.text:
- continue
-
- for item in section.select(".wixui-repeater__item > div > div"):
- divs = item.find_all("div")
-
- # Поиск по структуре
- if len(divs) != 3:
- continue
-
- div_url, div_name, div_date = divs
-
- a_el = div_url.select_one("a[href]")
- name = _get_text(div_name.select_one("h4"))
- date_str = _get_text(div_date.select_one("h1"))
-
- if not a_el or not name or not date_str:
- continue
-
- items.append(
- Game(
- name=name,
- url=urljoin(rs.url, a_el["href"]),
- date_str=date_str,
- )
- )
-
- _check_result(items)
- return items
-
-
-if __name__ == "__main__":
- items = get_games()
- print(f"Games ({len(items)}):")
- for i, game in enumerate(items, 1):
- print(f" {i}. {game}")
- """
- Games (55):
- 1. Game(name='A Plague Tale: Innocence', url='https://www.gamesvoice.ru/innocence', date_str='11 марта 2024')
- 2. Game(name='Close to the Sun', url='https://www.gamesvoice.ru/closetothesun', date_str='27 февраля 2024')
- 3. Game(name='Tomb Raider I-III Remastered', url='https://www.gamesvoice.ru/tomb-raider-remastered', date_str='14 февраля 2024')
- ...
- 53. Game(name='Panzer Corps', url='https://www.gamesvoice.ru/panzercorps', date_str='8 июля 2012')
- 54. Game(name='Mad Riders', url='https://www.gamesvoice.ru/madriders', date_str='3 июля 2012')
- 55. Game(name='Defenders of Ardania', url='https://www.gamesvoice.ru/doa', date_str='26 мая 2012')
- """
diff --git a/html_parsing/get_price_game/from_gama-gama.py b/html_parsing/get_price_game/from_gama-gama.py
index ba16194a0..46337d083 100644
--- a/html_parsing/get_price_game/from_gama-gama.py
+++ b/html_parsing/get_price_game/from_gama-gama.py
@@ -16,7 +16,7 @@
# Основа взята из http://stackoverflow.com/a/37755811/5909792
def get_html(url):
class ExtractorHtml:
- def __init__(self, url):
+ def __init__(self, url) -> None:
_app = QApplication([])
self._page = QWebEnginePage()
self._page.loadFinished.connect(self._load_finished_handler)
@@ -40,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:
diff --git a/html_parsing/get_price_game/from_gog_v2.py b/html_parsing/get_price_game/from_gog_v2.py
index 3e9c41b37..1a007b5f4 100644
--- a/html_parsing/get_price_game/from_gog_v2.py
+++ b/html_parsing/get_price_game/from_gog_v2.py
@@ -29,7 +29,7 @@ def get_games(name: str) -> list[tuple[str, str]]:
return [
(game["title"], game["price"]["baseMoney"]["amount"])
for game in data["products"]
- if game["price"]["baseMoney"]["currency"] == "RUB"
+ if game["price"] and game["price"]["baseMoney"]["currency"] == "RUB"
]
diff --git a/html_parsing/get_price_game/from_origin.py b/html_parsing/get_price_game/from_origin.py
index 3afb4022c..833aa63e2 100644
--- a/html_parsing/get_price_game/from_origin.py
+++ b/html_parsing/get_price_game/from_origin.py
@@ -14,7 +14,7 @@
# Основа взята из http://stackoverflow.com/a/37755811/5909792
def get_html(url):
class ExtractorHtml:
- def __init__(self, url):
+ def __init__(self, url) -> None:
_app = QApplication([])
self._page = QWebEnginePage()
@@ -43,10 +43,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._page.toHtml(self._callable)
return ExtractorHtml(url).html
diff --git a/html_parsing/get_video_list__xcadr.com.py b/html_parsing/get_video_list__xcadr.com.py
index a20ac657c..219ab0db6 100644
--- a/html_parsing/get_video_list__xcadr.com.py
+++ b/html_parsing/get_video_list__xcadr.com.py
@@ -54,7 +54,7 @@ def get_video_list(url) -> list[dict]:
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(
diff --git a/html_parsing/gitmanga_com__get_chapters.py b/html_parsing/gitmanga_com__get_chapters.py
index edff54163..002f867dc 100644
--- a/html_parsing/gitmanga_com__get_chapters.py
+++ b/html_parsing/gitmanga_com__get_chapters.py
@@ -39,7 +39,7 @@ def get_chapters(url: str) -> list[Chapter]:
if __name__ == "__main__":
- def print_chapter(items: list[Chapter]):
+ def print_chapter(items: list[Chapter]) -> None:
print(f"Chapters ({len(items)}):")
print(f" {items[0]}")
print(" ...")
diff --git a/html_parsing/grouple_co/common.py b/html_parsing/grouple_co/common.py
index defc9513a..4af15f367 100644
--- a/html_parsing/grouple_co/common.py
+++ b/html_parsing/grouple_co/common.py
@@ -10,6 +10,7 @@
import time
from dataclasses import dataclass, field
+from urllib.parse import urljoin, urlsplit
import requests
from bs4 import BeautifulSoup, Tag
@@ -41,19 +42,51 @@ class Status(AutoName):
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"
+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,
+ "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
@@ -63,11 +96,49 @@ def do_auth() -> requests.Response:
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)
# Если нужно авторизоваться
@@ -93,8 +164,7 @@ def parse_bookmark(el: Tag) -> Bookmark:
def get_bookmarks_by_status(status: Status) -> list[Bookmark]:
- url_bookmarks = "https://grouple.co/private/bookmarks"
- rs = load(url_bookmarks)
+ rs = load("/private/bookmarks")
m = re.search(r"var SITES = (\[.+]);", rs.text)
if not m:
@@ -121,12 +191,9 @@ def get_bookmarks_by_status(status: Status) -> list[Bookmark]:
}
headers = {
"Authorization": f'Bearer {session.cookies["gwt"]}',
- "Referer": url_bookmarks,
+ "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"]:
@@ -145,9 +212,7 @@ def get_bookmarks_by_status(status: Status) -> list[Bookmark]:
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"]:
break
@@ -159,31 +224,31 @@ def get_bookmarks_by_status(status: Status) -> list[Bookmark]:
def get_plain_all_bookmarks_from_user(user_id: int) -> list[Bookmark]:
- url = f"https://grouple.co/user/{user_id}/bookmarks"
- rs = load(url)
+ 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
- }
+ return {status: get_bookmarks_by_status(status) for status in Status}
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("HOST:", HOST)
+ # HOST: https://1.grouple.co
+
print("\n" + "-" * 50 + "\n")
print(get_plain_all_bookmarks_from_user(315828))
@@ -195,12 +260,18 @@ def get_all_bookmarks() -> dict[Status, list[Bookmark]]:
for i, bookmark in enumerate(bookmarks, 1):
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/get_updates_from_api.py b/html_parsing/grouple_co/get_updates_from_api.py
index 2c3387fd6..ce223c428 100644
--- a/html_parsing/grouple_co/get_updates_from_api.py
+++ b/html_parsing/grouple_co/get_updates_from_api.py
@@ -4,12 +4,12 @@
__author__ = "ipetrash"
-from common import session, load
+from common import session, do_post, load
def get_feeds_by_manga_chapters() -> list[str]:
# Auth and load
- rs = load("https://grouple.co/private/bookmarks/index#")
+ rs = load("/private/bookmarks/index#")
data = {
"bookmarkSort": "NAME",
@@ -27,9 +27,7 @@ def get_feeds_by_manga_chapters() -> list[str]:
}
)
- rs = session.post("https://grouple.co/api/bookmark/activitiesList", json=data)
- rs.raise_for_status()
-
+ rs = do_post("/api/bookmark/activitiesList", json=data)
return [
f'{item["element"]["name"]} {item["title"]}'
for item in rs.json()["list"]
diff --git a/html_parsing/grouple_co/print_bookmarks_completed__mangaTranslationCompleted.py b/html_parsing/grouple_co/print_bookmarks_completed__mangaTranslationCompleted.py
index 882bc85b7..34d962d81 100644
--- a/html_parsing/grouple_co/print_bookmarks_completed__mangaTranslationCompleted.py
+++ b/html_parsing/grouple_co/print_bookmarks_completed__mangaTranslationCompleted.py
@@ -15,7 +15,7 @@
print("\n")
-completed = [x for x in items if "переведено" in x.tags]
+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}")
diff --git a/html_parsing/howlongtobeat_com/common.py b/html_parsing/howlongtobeat_com/common.py
index 62f6d2d6f..8c244d9ef 100644
--- a/html_parsing/howlongtobeat_com/common.py
+++ b/html_parsing/howlongtobeat_com/common.py
@@ -43,7 +43,7 @@ class Game:
profile_genres: list[str] = field(default_factory=list)
- def __post_init__(self):
+ 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)
@@ -60,8 +60,8 @@ def parse(cls, data: dict[str, Any]) -> "Game":
duration_100_seconds=data["comp_100"],
duration_all_seconds=data["comp_all"],
release_world=data["release_world"],
- profile_platforms=data["profile_platform"].split(", "),
- # В случаи поиска жанры недоступны в результатах - нужно на страницу игры идти
+ # В случаи поиска некоторые из полей недоступны в результатах - нужно на страницу игры идти
+ profile_platforms=data.get("profile_platform", "").split(", "),
profile_genres=data.get("profile_genre", "").split(", "),
)
diff --git a/html_parsing/howlongtobeat_com/search.py b/html_parsing/howlongtobeat_com/search.py
index d10874588..8b4abb967 100644
--- a/html_parsing/howlongtobeat_com/search.py
+++ b/html_parsing/howlongtobeat_com/search.py
@@ -4,18 +4,97 @@
__author__ = "ipetrash"
+# import re # TODO: Удалить
+
+from datetime import datetime
+
+# from urllib.parse import urljoin # TODO: Удалить
from typing import Any
+
from common import URL_BASE, session, Game
-URL_SEARCH = f"{URL_BASE}/api/search"
+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'