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)}" 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
010203040506070809101112
2024
+ +

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

+ + + + + + + + + + + + + +
120102030405060708091011
20222023
diff --git a/_FOO_TEST_TEST/FOO_TEST_TEST.py b/_FOO_TEST_TEST/FOO_TEST_TEST.py index 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

+ + + +
+ + + + + +
TaskMinor BugImportant BugEnd 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: 1002: 300
3: 7004: 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'