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 eb8df38e5..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 = [] # Функция, принимающая аргументы и возвращающая декоратор @@ -22,7 +22,7 @@ def wrapper(self, *args, **kwargs): # Декоратор возвращает обертку return wrapper - # Возаращаем сам декоратор + # Возвращаем сам декоратор return decorator # Функция, принимающая аргументы и возвращающая декоратор @@ -41,7 +41,7 @@ def wrapper(self, *args, **kwargs): # Декоратор возвращает обертку return wrapper - # Возаращаем сам декоратор + # Возвращаем сам декоратор return decorator @_call_before(lambda self: self.result.append("+" + "-" * 10 + "+")) diff --git a/Decorators__examples/decorator_method_class__with_shelve.py b/Decorators__examples/decorator_method_class__with_shelve.py new file mode 100644 index 000000000..5ff1f1429 --- /dev/null +++ b/Decorators__examples/decorator_method_class__with_shelve.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import shelve +import functools + +from pathlib import Path +from typing import Any + + +DIR: Path = Path(__file__).resolve().parent +DIR_DB: Path = DIR / "databases" +DB_FILE_NAME: Path = DIR_DB / "db.shelve" + + +DIR_DB.mkdir(parents=True, exist_ok=True) + + +class DB: + db_name: str = str(DB_FILE_NAME) + + def __init__(self) -> None: + self.db: shelve.Shelf | None = None + + def session(*decorator_args, **decorator_kwargs): + def actual_decorator(func): + @functools.wraps(func) + def wrapped(self, *args, **kwargs): + has_db: bool = self.db is not None + try: + if not has_db: + self.db = shelve.open(self.db_name, writeback=True) + return func(self, *args, **kwargs) + finally: + if not has_db and self.db is not None: + self.db.close() + self.db = None + + return wrapped + + return actual_decorator + + @session() + def get_value(self, name: str, default: Any = None) -> Any: + if not name: + return dict(self.db) + + if name not in self.db: + return default + return self.db.get(name) + + @session() + def set_value(self, name: str, value: Any) -> None: + self.db[name] = value + + def inc_value(self, name: str) -> int: + value = self.get_value(name, default=0) + value += 1 + self.set_value(name, value) + return value + + +if __name__ == "__main__": + db = DB() + print("name", db.get_value("name")) + + db.set_value("name", 123) + + users: dict[str, dict[str, Any]] = db.get_value("users", default=dict()) + print("users", users) + if not users: + users["Foo"] = dict(name="Foo", age=12) + users["Bar"] = dict(name="Bar", age=12) + db.set_value("users", users) + + counter: dict[str, int] = db.get_value("counter", default=dict()) + print("counter", counter) + if "value" not in counter: + counter["value"] = 0 + counter["value"] += 1 + db.set_value("counter", counter) + + print([db.inc_value("age") for _ in range(3)]) + + print(dict(db.get_value(""))) diff --git a/Decorators__examples/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/hello_world__async.py b/Decorators__examples/hello_world__async.py new file mode 100644 index 000000000..69c05d92d --- /dev/null +++ b/Decorators__examples/hello_world__async.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import asyncio +import functools + + +def makebold(func): + @functools.wraps(func) + async def wrapped(*args, **kwargs): + return "" + await func(*args, **kwargs) + "" + + return wrapped + + +def makeitalic(func): + @functools.wraps(func) + async def wrapped(*args, **kwargs): + return "" + await func(*args, **kwargs) + "" + + return wrapped + + +def upper(func): + @functools.wraps(func) + async def wrapped(*args, **kwargs): + return (await func(*args, **kwargs)).upper() + + return wrapped + + +@makebold +@makeitalic +@upper +async def hello(text): + return text + + +loop = asyncio.new_event_loop() + +print(loop.run_until_complete(hello("Hello World!"))) +# HELLO WORLD! + +assert loop.run_until_complete(hello("Hello World!")) == "HELLO WORLD!" diff --git a/Decorators__examples/memoize_class.py b/Decorators__examples/memoize_class.py index 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/requests__append_attempts.py b/Decorators__examples/requests__append_attempts.py new file mode 100644 index 000000000..2919a37a8 --- /dev/null +++ b/Decorators__examples/requests__append_attempts.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import functools +import time + +import requests +from requests.exceptions import RequestException + + +def attempts( + max_number: int = 5, + sleep: int = 30, + ignored_exceptions: tuple[type(Exception)] = (Exception,), +): + def actual_decorator(func): + @functools.wraps(func) + def wrapped(*args, **kwargs): + number = 0 + while True: + try: + print("\nGO", args, kwargs) + return func(*args, **kwargs) + except Exception as e: + number += 1 + print(f"ERROR on {number}/{max_number}: {e}") + + if number >= max_number or not isinstance(e, ignored_exceptions): + raise e + + print(f"Sleep {sleep} seconds") + time.sleep(sleep) + + return wrapped + + return actual_decorator + + +@attempts( + max_number=3, + sleep=5, + ignored_exceptions=(RequestException,), +) +def do_get(url: str, *args, **kwargs) -> requests.Response: + rs = requests.get(url, *args, **kwargs) + rs.raise_for_status() + + return rs + + +print(do_get("https://google.com")) +""" +GO ('https://google.com',) {} + +""" + +print(do_get("http://sdfsdfs.dfsdf")) +""" +GO ('http://sdfsdfs.dfsdf',) {} +ERROR on 1/3: HTTPConnectionPool(host='sdfsdfs.dfsdf', port=80): Max retries exceeded with url: / (Caused by NewConnectionError(': Failed to establish a new connection: [Errno 11001] getaddrinfo failed')) +Sleep 5 seconds + +GO ('http://sdfsdfs.dfsdf',) {} +ERROR on 2/3: HTTPConnectionPool(host='sdfsdfs.dfsdf', port=80): Max retries exceeded with url: / (Caused by NewConnectionError(': Failed to establish a new connection: [Errno 11001] getaddrinfo failed')) +Sleep 5 seconds + +GO ('http://sdfsdfs.dfsdf',) {} +ERROR on 3/3: HTTPConnectionPool(host='sdfsdfs.dfsdf', port=80): Max retries exceeded with url: / (Caused by NewConnectionError(': Failed to establish a new connection: [Errno 11001] getaddrinfo failed')) +Traceback (most recent call last): + ... +socket.gaierror: [Errno 11001] getaddrinfo failed + +During handling of the above exception, another exception occurred: + +Traceback (most recent call last): + ... +urllib3.exceptions.NewConnectionError: : Failed to establish a new connection: [Errno 11001] getaddrinfo failed + +During handling of the above exception, another exception occurred: + +Traceback (most recent call last): + ... +urllib3.exceptions.MaxRetryError: HTTPConnectionPool(host='sdfsdfs.dfsdf', port=80): Max retries exceeded with url: / (Caused by NewConnectionError(': Failed to establish a new connection: [Errno 11001] getaddrinfo failed')) + +During handling of the above exception, another exception occurred: + +Traceback (most recent call last): + ... + raise ConnectionError(e, request=request) +requests.exceptions.ConnectionError: HTTPConnectionPool(host='sdfsdfs.dfsdf', port=80): Max retries exceeded with url: / (Caused by NewConnectionError(': Failed to establish a new connection: [Errno 11001] getaddrinfo failed')) +""" diff --git a/Decorators__examples/thread.py b/Decorators__examples/thread.py index 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 fd4cdaeb2..86c64dacc 100644 --- a/_FOO_TEST_TEST/FOO_TEST_TEST.py +++ b/_FOO_TEST_TEST/FOO_TEST_TEST.py @@ -4,3 +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/parse_web_outlook_calendar/main.py b/_FOO_TEST_TEST/parse_web_outlook_calendar/main.py deleted file mode 100644 index 6e793436b..000000000 --- a/_FOO_TEST_TEST/parse_web_outlook_calendar/main.py +++ /dev/null @@ -1,71 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -__author__ = "ipetrash" - - -import time - -# pip install selenium -from selenium import webdriver -from selenium.webdriver.common.by import By - -from config import LOGIN, PASSWORD - - -URL = "https://mail.compassplus.com" - -driver = webdriver.Firefox() - -try: - driver.implicitly_wait(10) # seconds - driver.get(URL) - print(f'Title: "{driver.title}"') - - driver.find_element(By.ID, "username").send_keys(LOGIN) - driver.find_element(By.ID, "password").send_keys(PASSWORD) - - # Делаем скриншот результата - driver.save_screenshot("before_auth.png") - - driver.find_element(By.CLASS_NAME, "signinbutton").click() - - driver.save_screenshot("after_auth.png") - print(f'Title: "{driver.title}"') - - driver.save_screenshot("before_click_on_calendar.png") - print(f'Title: "{driver.title}"') - - html = driver.page_source - print("Length:", len(html)) - open("driver.before_click_on_calendar.html", "w", encoding="utf-8").write(html) - - # Ждем и кликаем на кнопку - driver.find_element(By.XPATH, '//*[text()="Календарь"]').click() - - html = driver.page_source - print("Length:", len(html)) - open("driver.after_click_on_calendar.html", "w", encoding="utf-8").write(html) - - driver.save_screenshot("after_click_on_calendar.png") - print(f'Title: "{driver.title}"') - - # Ждем пока появится элемент - driver.find_element(By.CSS_SELECTOR, '[aria-label="Представление календаря"]') - - # Даем еще время на прогрузку календаря - time.sleep(10) - - html = driver.page_source - print("Length:", len(html)) - open("driver.after_click_on_calendar_2.html", "w", encoding="utf-8").write(html) - - driver.save_screenshot("after_click_on_calendar_2.png") - print(f'Title: "{driver.title}"') - - # TODO: нужно - -finally: - # TODO: - # driver.quit() - pass diff --git a/_FOO_TEST_TEST/print_processes_in_directory.py b/_FOO_TEST_TEST/print_processes_in_directory.py new file mode 100644 index 000000000..e081ba42d --- /dev/null +++ b/_FOO_TEST_TEST/print_processes_in_directory.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from pathlib import Path +from psutil import process_iter, Process, Error + + +def is_found(p: Process, cwd: str | Path) -> bool: + if isinstance(cwd, str): + cwd = Path(cwd) + return cwd.exists() and Path(p.cwd()).is_relative_to(cwd) + + +def get_processes(cwd: str | Path = None) -> list[Process]: + items = [] + for p in process_iter(): + try: + if is_found(p, cwd): + items.append(p) + except Error: + pass + return items + + +# TODO: Вывести деревом список процессов +for p in get_processes(r"C:\DEV__TX\trunk"): + print(p, p.cwd(), p.name(), p.cmdline(), p.parent()) + +# TODO: Алгоритм преобразования список в дерево diff --git a/_FOO_TEST_TEST/tx_version_calendar.html b/_FOO_TEST_TEST/tx_version_calendar.html new file mode 100644 index 000000000..b932157f3 --- /dev/null +++ b/_FOO_TEST_TEST/tx_version_calendar.html @@ -0,0 +1,283 @@ + + + + TX Version Calendar + + + + + + +

TX Version Calendar

+ + + +
+ + + + + +
TaskMinor BugImportant BugEnd of Support
+
+ + + + \ No newline at end of file diff --git a/about clipboard changed/with_notification/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/from_jenkins.py b/cron_converter__examples/from_jenkins.py new file mode 100644 index 000000000..4260a40a4 --- /dev/null +++ b/cron_converter__examples/from_jenkins.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import re + + +def do_convert(cron: str) -> str: + match cron: + case "@hourly": + cron = "H * * * *" + + case "@daily" | "@midnight": + cron = "H 0 * * *" + + case "@weekly": + cron = "H H * * H" + + case "@monthly": + cron = "H H H * *" + + case "@yearly" | "@annually": + cron = "0 0 1 1 *" + + # NOTE: "H(0-29)/10 * * * *" -> "0-29/10 * * * *" + cron = re.sub(r"H\((.+?)\)", r"\1", cron) + + parts: list[str] = cron.split() + + def _process(value: str, default_value: str = "0") -> str: + return value.replace("H/", "*/").replace("H", default_value) + + # Minute + parts[0] = _process(parts[0]) + + # Hour + parts[1] = _process(parts[1]) + + # Day (month). Тут диапазон начинается с 1 + parts[2] = _process(parts[2], default_value="1") + + # Month. Тут диапазон начинается с 1 + parts[3] = _process(parts[3], default_value="1") + + # Day (week) + parts[4] = _process(parts[4]) + + cron = " ".join(parts) + + return cron + + +if __name__ == "__main__": + from datetime import datetime + + # pip install cron-converter + from cron_converter import Cron + + cron = "H */8 * * *" + cron = do_convert(cron) + cron_instance = Cron(cron) + + print(f"Cron: {cron_instance}") + + start_date = datetime.now() + print(f"Start date: {start_date}") + + schedule = cron_instance.schedule(start_date) + print(f"Next: {schedule.next().isoformat()}") + print(f"Next: {schedule.next().isoformat()}") + print(f"Next: {schedule.next().isoformat()}") + print(f"Next: {schedule.next().isoformat()}") + print(f"Next: {schedule.next().isoformat()}") diff --git a/cron_converter__examples/hello_world.py b/cron_converter__examples/hello_world.py new file mode 100644 index 000000000..016e5c170 --- /dev/null +++ b/cron_converter__examples/hello_world.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from datetime import datetime + +# pip install cron-converter +from cron_converter import Cron + + +print("Every hour") +cron = "0 * * * *" +cron_instance = Cron(cron) + +print(f"Cron: {cron_instance}") + +start_date = datetime.now() +print(f"Start date: {start_date}") + +schedule = cron_instance.schedule(start_date) +print(f"Next: {schedule.next().isoformat()}") +print(f"Next: {schedule.next().isoformat()}") +print(f"Next: {schedule.next().isoformat()}") + +print() + +print("Every 8 hours") +cron = "0 */8 * * *" +cron_instance = Cron(cron) + +print(f"Cron: {cron_instance}") + +start_date = datetime.now() +print(f"Start date: {start_date}") + +schedule = cron_instance.schedule(start_date) +print(f"Next: {schedule.next().isoformat()}") +print(f"Next: {schedule.next().isoformat()}") +print(f"Next: {schedule.next().isoformat()}") + +print() + +print("Every 00:00") +cron = "0 0 * * *" +cron_instance = Cron(cron) + +print(f"Cron: {cron_instance}") + +start_date = datetime.now() +print(f"Start date: {start_date}") + +schedule = cron_instance.schedule(start_date) +print(f"Next: {schedule.next().isoformat()}") +print(f"Next: {schedule.next().isoformat()}") +print(f"Next: {schedule.next().isoformat()}") + +print() + +print("Every 00:00 at Saturday") +cron = "0 0 * * 6" +cron_instance = Cron(cron) + +print(f"Cron: {cron_instance}") + +start_date = datetime.now() +print(f"Start date: {start_date}") + +schedule = cron_instance.schedule(start_date) +print(f"Next: {schedule.next().isoformat()}") +print(f"Next: {schedule.next().isoformat()}") +print(f"Next: {schedule.next().isoformat()}") diff --git a/cron_converter__examples/test_from_jenkins.py b/cron_converter__examples/test_from_jenkins.py new file mode 100644 index 000000000..52ce933fb --- /dev/null +++ b/cron_converter__examples/test_from_jenkins.py @@ -0,0 +1,400 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from datetime import datetime +from unittest import TestCase + +from cron_converter import Cron + +from from_jenkins import do_convert + + +class Test(TestCase): + def test_do_convert_every_15_minutes(self) -> None: + # Every fifteen minutes + cron = "H/15 * * * *" + + cron = do_convert(cron) + + cron_instance = Cron(cron) + + start_date_str = "2024-01-01T12:00:00" + schedule = cron_instance.schedule(datetime.fromisoformat(start_date_str)) + + actual: list[str] = [ + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + ] + expected = ["2024-01-01T12:00:00", "2024-01-01T12:15:00", "2024-01-01T12:30:00"] + + self.assertEqual(actual, expected) + + def test_do_convert_every_1_hours(self) -> None: + cron = "H * * * *" + + cron = do_convert(cron) + + cron_instance = Cron(cron) + + start_date_str = "2024-01-01T12:00:00" + schedule = cron_instance.schedule(datetime.fromisoformat(start_date_str)) + + actual: list[str] = [ + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + ] + expected = ["2024-01-01T12:00:00", "2024-01-01T13:00:00", "2024-01-01T14:00:00"] + + self.assertEqual(actual, expected) + + def test_do_convert_every_8_hours(self) -> None: + cron = "H */8 * * *" + + cron = do_convert(cron) + + cron_instance = Cron(cron) + + start_date_str = "2024-01-01T12:00:00" + schedule = cron_instance.schedule(datetime.fromisoformat(start_date_str)) + + actual: list[str] = [ + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + ] + expected = [ + "2024-01-01T16:00:00", + "2024-01-02T00:00:00", + "2024-01-02T08:00:00", + "2024-01-02T16:00:00", + "2024-01-03T00:00:00", + ] + + self.assertEqual(actual, expected) + + def test_do_convert_every_24_hours(self) -> None: + cron = "H 0 * * *" + + cron = do_convert(cron) + + cron_instance = Cron(cron) + + start_date_str = "2024-01-01T12:00:00" + schedule = cron_instance.schedule(datetime.fromisoformat(start_date_str)) + + actual: list[str] = [ + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + ] + expected = ["2024-01-02T00:00:00", "2024-01-03T00:00:00", "2024-01-04T00:00:00"] + + self.assertEqual(actual, expected) + + def test_do_convert_hourly(self) -> None: + cron = "@hourly" + + cron = do_convert(cron) + + cron_instance = Cron(cron) + + start_date_str = "2024-01-01T12:00:00" + schedule = cron_instance.schedule(datetime.fromisoformat(start_date_str)) + + actual: list[str] = [ + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + ] + expected = ["2024-01-01T12:00:00", "2024-01-01T13:00:00", "2024-01-01T14:00:00"] + + self.assertEqual(actual, expected) + + def test_do_convert_daily(self) -> None: + cron = "@daily" + + cron = do_convert(cron) + + cron_instance = Cron(cron) + + start_date_str = "2024-01-01T12:00:00" + schedule = cron_instance.schedule(datetime.fromisoformat(start_date_str)) + + actual: list[str] = [ + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + ] + expected = ["2024-01-02T00:00:00", "2024-01-03T00:00:00", "2024-01-04T00:00:00"] + + self.assertEqual(actual, expected) + + def test_do_convert_midnight(self) -> None: + cron = "@midnight" + + cron = do_convert(cron) + + cron_instance = Cron(cron) + + start_date_str = "2024-01-01T12:00:00" + schedule = cron_instance.schedule(datetime.fromisoformat(start_date_str)) + + actual: list[str] = [ + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + ] + expected = [ + "2024-01-02T00:00:00", + "2024-01-03T00:00:00", + "2024-01-04T00:00:00", + "2024-01-05T00:00:00", + "2024-01-06T00:00:00", + "2024-01-07T00:00:00", + ] + + self.assertEqual(actual, expected) + + def test_do_convert_weekly(self) -> None: + cron = "@weekly" + + cron = do_convert(cron) + + cron_instance = Cron(cron) + + start_date_str = "2024-01-01T12:00:00" + schedule = cron_instance.schedule(datetime.fromisoformat(start_date_str)) + + actual: list[str] = [ + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + ] + expected = [ + "2024-01-07T00:00:00", + "2024-01-14T00:00:00", + "2024-01-21T00:00:00", + "2024-01-28T00:00:00", + "2024-02-04T00:00:00", + "2024-02-11T00:00:00", + ] + + self.assertEqual(actual, expected) + + def test_do_convert_monthly(self) -> None: + cron = "@monthly" + + cron = do_convert(cron) + + cron_instance = Cron(cron) + + start_date_str = "2024-01-01T12:00:00" + schedule = cron_instance.schedule(datetime.fromisoformat(start_date_str)) + + actual: list[str] = [ + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + ] + expected = [ + "2024-02-01T00:00:00", + "2024-03-01T00:00:00", + "2024-04-01T00:00:00", + "2024-05-01T00:00:00", + "2024-06-01T00:00:00", + "2024-07-01T00:00:00", + ] + + self.assertEqual(actual, expected) + + def test_do_convert_yearly(self) -> None: + cron = "@yearly" + + cron = do_convert(cron) + + cron_instance = Cron(cron) + + start_date_str = "2024-01-01T12:00:00" + schedule = cron_instance.schedule(datetime.fromisoformat(start_date_str)) + + actual: list[str] = [ + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + ] + expected = ["2025-01-01T00:00:00", "2026-01-01T00:00:00", "2027-01-01T00:00:00"] + + self.assertEqual(actual, expected) + + def test_do_convert_annually(self) -> None: + cron = "@annually" + + cron = do_convert(cron) + + cron_instance = Cron(cron) + + start_date_str = "2024-01-01T12:00:00" + schedule = cron_instance.schedule(datetime.fromisoformat(start_date_str)) + + actual: list[str] = [ + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + ] + expected = ["2025-01-01T00:00:00", "2026-01-01T00:00:00", "2027-01-01T00:00:00"] + + self.assertEqual(actual, expected) + + def test_do_convert_complex_1(self) -> None: + # Every ten minutes in the first half of every hour + cron = "H(0-29)/10 * * * *" + + cron = do_convert(cron) + + cron_instance = Cron(cron) + + start_date_str = "2024-01-01T12:00:00" + schedule = cron_instance.schedule(datetime.fromisoformat(start_date_str)) + + actual: list[str] = [ + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + ] + expected = [ + "2024-01-01T12:00:00", + "2024-01-01T12:10:00", + "2024-01-01T12:20:00", + "2024-01-01T13:00:00", + "2024-01-01T13:10:00", + "2024-01-01T13:20:00", + "2024-01-01T14:00:00", + "2024-01-01T14:10:00", + "2024-01-01T14:20:00", + ] + + self.assertEqual(actual, expected) + + def test_do_convert_complex_2(self) -> None: + # Once every two hours at 45 minutes past the hour starting at 9:45 AM and finishing at 3:45 PM every weekday + cron = "45 9-16/2 * * 1-5" + + cron = do_convert(cron) + + cron_instance = Cron(cron) + + start_date_str = "2024-01-04T12:00:00" + schedule = cron_instance.schedule(datetime.fromisoformat(start_date_str)) + + actual: list[str] = [ + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + ] + expected = [ + "2024-01-04T13:45:00", + "2024-01-04T15:45:00", + "2024-01-05T09:45:00", + "2024-01-05T11:45:00", + "2024-01-05T13:45:00", + "2024-01-05T15:45:00", + "2024-01-08T09:45:00", + "2024-01-08T11:45:00", + ] + + self.assertEqual(actual, expected) + + def test_do_convert_complex_3(self) -> None: + # Once in every two hour slot between 8 AM and 4 PM every weekday + cron = "H H(8-15)/2 * * 1-5" + + cron = do_convert(cron) + + cron_instance = Cron(cron) + + start_date_str = "2024-01-04T12:00:00" + schedule = cron_instance.schedule(datetime.fromisoformat(start_date_str)) + + actual: list[str] = [ + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + ] + expected = [ + "2024-01-04T12:00:00", + "2024-01-04T14:00:00", + "2024-01-05T08:00:00", + "2024-01-05T10:00:00", + "2024-01-05T12:00:00", + "2024-01-05T14:00:00", + "2024-01-08T08:00:00", + "2024-01-08T10:00:00", + ] + + self.assertEqual(actual, expected) + + def test_do_convert_complex_4(self) -> None: + # Once a day on the 1st and 15th of every month except December + cron = "H H 1,15 1-11 *" + + cron = do_convert(cron) + + cron_instance = Cron(cron) + + start_date_str = "2023-10-01T12:00:00" + schedule = cron_instance.schedule(datetime.fromisoformat(start_date_str)) + + actual: list[str] = [ + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + schedule.next().isoformat(), + ] + expected = [ + "2023-10-15T00:00:00", + "2023-11-01T00:00:00", + "2023-11-15T00:00:00", + "2024-01-01T00:00:00", + "2024-01-15T00:00:00", + "2024-02-01T00:00:00", + "2024-02-15T00:00:00", + "2024-03-01T00:00:00", + ] + + self.assertEqual(actual, expected) diff --git a/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 d45474417..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=10000000, 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/.gitignore b/fastapi__examples/blog_from_stepic/.gitignore new file mode 100644 index 000000000..87811cb1f --- /dev/null +++ b/fastapi__examples/blog_from_stepic/.gitignore @@ -0,0 +1,8 @@ +venv +*__pycache__* +.DS_Store +.idea +*.db +.bak +.dat +.dir \ No newline at end of file diff --git a/fastapi__examples/blog_from_stepic/README.md b/fastapi__examples/blog_from_stepic/README.md new file mode 100644 index 000000000..34c1a27a1 --- /dev/null +++ b/fastapi__examples/blog_from_stepic/README.md @@ -0,0 +1,4 @@ +https://stepik.org/lesson/1186984/step/7?unit=1222202 + +http://127.0.0.1:8000/docs +http://127.0.0.1:8000/redoc diff --git a/fastapi__examples/blog_from_stepic/requirements.txt b/fastapi__examples/blog_from_stepic/requirements.txt new file mode 100644 index 000000000..3af9cb384 --- /dev/null +++ b/fastapi__examples/blog_from_stepic/requirements.txt @@ -0,0 +1,2 @@ +fastapi==0.111.0 +uvicorn[standard]==0.30.1 diff --git a/fastapi__examples/blog_from_stepic/run.bat b/fastapi__examples/blog_from_stepic/run.bat new file mode 100644 index 000000000..98e4066f8 --- /dev/null +++ b/fastapi__examples/blog_from_stepic/run.bat @@ -0,0 +1 @@ +uvicorn blog.main:app --reload --app-dir src/ \ No newline at end of file diff --git a/games/tetris/config.py b/fastapi__examples/blog_from_stepic/src/blog/__init__.py similarity index 81% rename from games/tetris/config.py rename to fastapi__examples/blog_from_stepic/src/blog/__init__.py index b403d7edb..f25732790 100644 --- a/games/tetris/config.py +++ b/fastapi__examples/blog_from_stepic/src/blog/__init__.py @@ -2,6 +2,3 @@ # -*- coding: utf-8 -*- __author__ = "ipetrash" - - -DEBUG = False diff --git a/fastapi__examples/blog_from_stepic/src/blog/domains.py b/fastapi__examples/blog_from_stepic/src/blog/domains.py new file mode 100644 index 000000000..0822e86af --- /dev/null +++ b/fastapi__examples/blog_from_stepic/src/blog/domains.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from dataclasses import dataclass + + +@dataclass +class User: + """Обычный пользователь""" + id: str + + +@dataclass +class Admin(User): + """Пользователь, наделенный правами администратора""" + username: str + password: str + + +@dataclass +class Article: + """Сущность статьи""" + id: str + title: str + content: str diff --git a/fastapi__examples/blog_from_stepic/src/blog/main.py b/fastapi__examples/blog_from_stepic/src/blog/main.py new file mode 100644 index 000000000..46bd9e226 --- /dev/null +++ b/fastapi__examples/blog_from_stepic/src/blog/main.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from fastapi import FastAPI +from blog.resources import router + + +def get_app(): + app = FastAPI() + + app.include_router(router) # <- вот тут мы зарегистрировали роутер + + return app + + +app = get_app() diff --git a/fastapi__examples/blog_from_stepic/src/blog/repositories.py b/fastapi__examples/blog_from_stepic/src/blog/repositories.py new file mode 100644 index 000000000..c2926d033 --- /dev/null +++ b/fastapi__examples/blog_from_stepic/src/blog/repositories.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import shelve +from abc import ABC, abstractmethod + +from blog.domains import Admin, Article, User + + +class UsersRepository(ABC): + """ + Абстрактный репозиторий для пользователей. + От него нужно наследоваться в случае, когда нужно сделать другое хранилище, старое переписывать не нужно. + """ + @abstractmethod + def get_users( + self, username: str | None = None, password: str | None = None + ) -> list[User]: + pass + + +class MemoryUsersRepository(UsersRepository): + """ + Реализация пользовательского хранилища в оперативной памяти. + Пользователи инициализируются во время инициализации репозитория + """ + def __init__(self) -> None: + self.users = [ + Admin( + id="29ae7ebf-4445-42f2-9548-a3a54f095220", # это uuid4 – уникальный идентификатор пользователя + username="admin", + password="Admin_4321!", + ) + ] + + def get_users( + self, username: str | None = None, password: str | None = None + ) -> list[User]: + """ + :param username: фильтр по логину + :param password: фильтр по паролю + :return: отфильтрованные пользователи + """ + filtered_users = [] # тут собираются отфильтрованные пользователи + for user in self.users: # перебираем всех пользователей и осталвяем только тех, кто прошел фильтры + if username is not None and user.username != username: + continue + if password is not None and user.password != password: + continue + filtered_users.append(user) + return filtered_users + + +class ArticlesRepository(ABC): + """ + Абстрактный репозиторий для статей. + Он содержит методы, которые нужно реализовать в случае если захочется сделать новую реализацию репозитория. + Принцип такой же как и у пользователей. + """ + @abstractmethod + def get_articles(self) -> list[Article]: + pass + + @abstractmethod + def create_article(self, article: Article): + pass + + +class ShelveArticlesRepository(ArticlesRepository): + def __init__(self) -> None: + self.db_name = "articles" + + def get_articles(self) -> list[Article]: + with shelve.open(self.db_name) as db: + return list(db.values()) + + def create_article(self, article: Article) -> None: + with shelve.open(self.db_name) as db: + db[article.id] = article diff --git a/fastapi__examples/blog_from_stepic/src/blog/resources.py b/fastapi__examples/blog_from_stepic/src/blog/resources.py new file mode 100644 index 000000000..d46096528 --- /dev/null +++ b/fastapi__examples/blog_from_stepic/src/blog/resources.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from fastapi import APIRouter, status, HTTPException +from fastapi.responses import HTMLResponse + +from blog.domains import Admin +from blog.schemas import ( + GetArticlesModel, + CreateArticleModel, + LoginModel, + GetArticleModel, + ErrorModel, +) +from blog import services +from blog.repositories import ShelveArticlesRepository, MemoryUsersRepository + + +router = APIRouter() # это роутер, он нужен для FastAPI, чтобы определять эндпоинты + + +@router.get("/", response_class=HTMLResponse) +def index() -> str: + return """ + +
+ + + """ + + +@router.get("/articles", response_model=GetArticlesModel) +def get_articles() -> GetArticlesModel: + # во всех представлениях всегда происходит одно и то же: + # 1. получили данные + # 2. вызвали сервисный метод и получили из него результат + # 3. вернули результат клиенту в виде ответа + articles = services.get_articles(articles_repository=ShelveArticlesRepository()) + return GetArticlesModel( + items=[ + GetArticleModel(id=article.id, title=article.title, content=article.content) + for article in articles + ] + ) + + +@router.post( + "/articles", + response_model=GetArticleModel, + # 201 статус код потому что мы создаем объект – стандарт HTTP + status_code=status.HTTP_201_CREATED, + # Это нужно для сваггера. Мы перечисляем ответы эндпоинта, чтобы получить четкую документацию. + responses={201: {"model": GetArticleModel}, 401: {"model": ErrorModel}, 403: {"model": ErrorModel}}, +) +def create_article( + article: CreateArticleModel, +# credentials – тело с логином и паролем. Обычно аутентификация выглядит сложнее, но для нашего случая пойдет и так. + credentials: LoginModel, +): + current_user = services.login( + username=credentials.username, + password=credentials.password, + users_repository=MemoryUsersRepository(), + ) + + # Это аутентификация + if not current_user: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, detail="Unauthorized user" + ) + # а это авторизация + if not isinstance(current_user, Admin): + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, detail="Forbidden resource" + ) + + article = services.create_article( + title=article.title, + content=article.content, + articles_repository=ShelveArticlesRepository(), + ) + + return GetArticleModel(id=article.id, title=article.title, content=article.content) diff --git a/fastapi__examples/blog_from_stepic/src/blog/schemas.py b/fastapi__examples/blog_from_stepic/src/blog/schemas.py new file mode 100644 index 000000000..f3cf72f94 --- /dev/null +++ b/fastapi__examples/blog_from_stepic/src/blog/schemas.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from pydantic import BaseModel + + +class GetArticleModel(BaseModel): + id: str + title: str + content: str + + +class GetArticlesModel(BaseModel): + items: list[GetArticleModel] + + +class CreateArticleModel(BaseModel): + title: str + content: str + + +class LoginModel(BaseModel): + username: str + password: str + + +class ErrorModel(BaseModel): + detail: str diff --git a/fastapi__examples/blog_from_stepic/src/blog/services.py b/fastapi__examples/blog_from_stepic/src/blog/services.py new file mode 100644 index 000000000..7e285ed62 --- /dev/null +++ b/fastapi__examples/blog_from_stepic/src/blog/services.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from uuid import uuid4 + +from blog.domains import Article, User +from blog.repositories import ArticlesRepository, UsersRepository + + +def get_articles(articles_repository: ArticlesRepository) -> list[Article]: + return articles_repository.get_articles() + + +def create_article( + title: str, content: str, articles_repository: ArticlesRepository +) -> Article: + article = Article(id=str(uuid4()), title=title, content=content) + articles_repository.create_article(article=article) + return article + + +def login( + username: str, password: str, users_repository: UsersRepository +) -> User | None: + users = users_repository.get_users(username=username, password=password) + if users: + return users[0] diff --git a/fastapi__examples/blog_from_stepic/src/tests/__init__.py b/fastapi__examples/blog_from_stepic/src/tests/__init__.py new file mode 100644 index 000000000..f25732790 --- /dev/null +++ b/fastapi__examples/blog_from_stepic/src/tests/__init__.py @@ -0,0 +1,4 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" diff --git a/fastapi__examples/logging/config.py b/fastapi__examples/logging/config.py new file mode 100644 index 000000000..408959e9b --- /dev/null +++ b/fastapi__examples/logging/config.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from typing import Any + + +# SOURCE: https://github.com/encode/uvicorn/blob/d79f285184404694c77f7ca649858e7488270cf7/uvicorn/config.py#L66 +# Added fmt="[%(asctime)s] " +LOGGING_CONFIG: dict[str, Any] = { + "version": 1, + "disable_existing_loggers": False, + "formatters": { + "default": { + "()": "uvicorn.logging.DefaultFormatter", + "fmt": "[%(asctime)s] %(levelprefix)s %(message)s", + "use_colors": None, + }, + "access": { + "()": "uvicorn.logging.AccessFormatter", + "fmt": '[%(asctime)s] %(levelprefix)s %(client_addr)s - "%(request_line)s" %(status_code)s', # noqa: E501 + }, + }, + "handlers": { + "default": { + "formatter": "default", + "class": "logging.StreamHandler", + "stream": "ext://sys.stderr", + }, + "access": { + "formatter": "access", + "class": "logging.StreamHandler", + "stream": "ext://sys.stdout", + }, + }, + "loggers": { + "uvicorn": {"handlers": ["default"], "level": "INFO", "propagate": False}, + "uvicorn.error": {"level": "INFO"}, + "uvicorn.access": {"handlers": ["access"], "level": "INFO", "propagate": False}, + }, +} + + +if __name__ == "__main__": + import yaml + yaml.dump( + LOGGING_CONFIG, + open("logging_config.yml", mode="w", encoding="utf-8"), + sort_keys=False, + ) diff --git a/fastapi__examples/logging/logging_config.yml b/fastapi__examples/logging/logging_config.yml new file mode 100644 index 000000000..a4fcd4d2d --- /dev/null +++ b/fastapi__examples/logging/logging_config.yml @@ -0,0 +1,32 @@ +version: 1 +disable_existing_loggers: false +formatters: + default: + (): uvicorn.logging.DefaultFormatter + fmt: '[%(asctime)s] %(levelprefix)s %(message)s' + use_colors: null + access: + (): uvicorn.logging.AccessFormatter + fmt: '[%(asctime)s] %(levelprefix)s %(client_addr)s - "%(request_line)s" %(status_code)s' +handlers: + default: + formatter: default + class: logging.StreamHandler + stream: ext://sys.stderr + access: + formatter: access + class: logging.StreamHandler + stream: ext://sys.stdout +loggers: + uvicorn: + handlers: + - default + level: INFO + propagate: false + uvicorn.error: + level: INFO + uvicorn.access: + handlers: + - access + level: INFO + propagate: false diff --git a/fastapi__examples/logging/main.py b/fastapi__examples/logging/main.py new file mode 100644 index 000000000..0f55b572b --- /dev/null +++ b/fastapi__examples/logging/main.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from fastapi import FastAPI +from config import LOGGING_CONFIG + + +app = FastAPI() + + +@app.get("/") +def index(): + return {"text": "Hello World!"} + + +if __name__ == "__main__": + from pathlib import Path + import uvicorn + + uvicorn.run( + app=f"{Path(__file__).stem}:app", + host="127.0.0.1", + port=8000, + log_config=LOGGING_CONFIG, + reload=True, + ) diff --git a/fastapi__examples/logging/run_with_log_config.bat b/fastapi__examples/logging/run_with_log_config.bat new file mode 100644 index 000000000..9c340920d --- /dev/null +++ b/fastapi__examples/logging/run_with_log_config.bat @@ -0,0 +1 @@ +uvicorn main:app --reload --port=7777 --log-config=logging_config.yml \ No newline at end of file diff --git a/fastapi__examples/market_from_stepic/.gitignore b/fastapi__examples/market_from_stepic/.gitignore new file mode 100644 index 000000000..466b5ad6c --- /dev/null +++ b/fastapi__examples/market_from_stepic/.gitignore @@ -0,0 +1,12 @@ +venv +*__pycache__* +.DS_Store +.idea +*.db +.bak +.dat +.dir + +SECRET_KEY.txt +database/ +database-test/ diff --git a/fastapi__examples/market_from_stepic/README.md b/fastapi__examples/market_from_stepic/README.md new file mode 100644 index 000000000..46460a6e9 --- /dev/null +++ b/fastapi__examples/market_from_stepic/README.md @@ -0,0 +1,18 @@ +https://stepik.org/lesson/1186984/step/8?unit=1222202 + +# Полезные ссылки + +http://127.0.0.1:7777/docs +http://127.0.0.1:7777/redoc + +# Авторизация + +Токен Bearer + +Используется POST запрос на "/api/v1/token". + +Пример: [src/examples/auth_to_api.py](./src/examples/auth_to_api.py) + +# База данных + +Как и [прошлый учебный проект](../blog_from_stepic) использует shelve - ~~для прикола~~ в образовательных целях. diff --git a/fastapi__examples/market_from_stepic/requirements.txt b/fastapi__examples/market_from_stepic/requirements.txt new file mode 100644 index 000000000..72cc557f6 --- /dev/null +++ b/fastapi__examples/market_from_stepic/requirements.txt @@ -0,0 +1,5 @@ +fastapi==0.111.0 +uvicorn[standard]==0.30.1 +PyJWT==2.13.0 +passlib==1.7.4 +bcrypt==4.0.1 \ No newline at end of file diff --git a/fastapi__examples/market_from_stepic/run.bat b/fastapi__examples/market_from_stepic/run.bat new file mode 100644 index 000000000..1cf083ea8 --- /dev/null +++ b/fastapi__examples/market_from_stepic/run.bat @@ -0,0 +1 @@ +uvicorn market.main:app --reload --app-dir=src/ --port=7777 \ No newline at end of file diff --git a/fastapi__examples/market_from_stepic/run_python12.bat b/fastapi__examples/market_from_stepic/run_python12.bat new file mode 100644 index 000000000..a48ffed0e --- /dev/null +++ b/fastapi__examples/market_from_stepic/run_python12.bat @@ -0,0 +1 @@ +C:\Users\ipetrash\AppData\Local\Programs\Python\Python312\python.exe -m uvicorn market.main:app --reload --app-dir=src/ --port=7777 \ No newline at end of file diff --git a/fastapi__examples/market_from_stepic/src/examples/auth_to_api.py b/fastapi__examples/market_from_stepic/src/examples/auth_to_api.py new file mode 100644 index 000000000..7e0b646c3 --- /dev/null +++ b/fastapi__examples/market_from_stepic/src/examples/auth_to_api.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import json +import urllib.request +import urllib.parse + + +URL = "http://127.0.0.1:7777" + + +def get_token(username: str, password: str) -> str: + login_data = { + "username": username, + "password": password, + } + + req = urllib.request.Request( + f"{URL}/api/v1/token", + method="POST", + data=urllib.parse.urlencode(login_data).encode("utf-8"), + ) + with urllib.request.urlopen(req) as rs: + rs_data = json.loads(rs.read().decode("utf-8")) + return rs_data["token"] + + +def get_users(token: str) -> dict[str, dict]: + req = urllib.request.Request( + f"{URL}/api/v1/users", + method="GET", + ) + req.add_header("Authorization", f"Bearer {token}") + + with urllib.request.urlopen(req) as rs: + return json.loads(rs.read().decode("utf-8")) + + +if __name__ == "__main__": + token = get_token(username="admin", password="Admin_4321!") + print(token) + # eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyOWFlN2ViZi00NDQ1LTQyZjItOTU0OC1hM2E1NGYwOTUyMjAiLCJyb2xlIjoiYWRtaW4iLCJleHAiOjE3MjE5MTg0MTh9.rTETSqUw0hRWJQFrBcHo9NiHwhfeUqZ0brDMuPp70xw + + print(get_users(token)) + # {'items': [{'id': '29ae7ebf-4445-42f2-9548-a3a54f095220', 'role': 'admin', 'username': 'admin'}]} diff --git a/fastapi__examples/market_from_stepic/src/market/__init__.py b/fastapi__examples/market_from_stepic/src/market/__init__.py new file mode 100644 index 000000000..f25732790 --- /dev/null +++ b/fastapi__examples/market_from_stepic/src/market/__init__.py @@ -0,0 +1,4 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" diff --git a/fastapi__examples/market_from_stepic/src/market/auth.py b/fastapi__examples/market_from_stepic/src/market/auth.py new file mode 100644 index 000000000..f38fc4da2 --- /dev/null +++ b/fastapi__examples/market_from_stepic/src/market/auth.py @@ -0,0 +1,130 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from dataclasses import dataclass, asdict +from datetime import datetime, timedelta, timezone +from typing import Any, Annotated + +import jwt +from fastapi import Depends, HTTPException, status +from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer + +from market import models +from market import services +from market.config import SECRET_KEY, ALGORITHM, ACCESS_TOKEN_EXPIRE_MINUTES + + +optional_security = HTTPBearer(auto_error=False) + + +not_authenticated_exception = HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Not authenticated", + headers={"WWW-Authenticate": "Bearer"}, +) +credentials_exception = HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Could not validate credentials", + headers={"WWW-Authenticate": "Bearer"}, +) +signature_has_expired_exception = HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Signature has expired", + headers={"WWW-Authenticate": "Bearer"}, +) + + +@dataclass +class TokenPayload: + sub: str + role: models.UserRoleEnum + exp: datetime | None = None + + +def create_access_token( + token: TokenPayload, + expires_delta: timedelta = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES), +) -> str: + data: dict[str, Any] = asdict(token) + data["exp"] = datetime.now(timezone.utc) + expires_delta + return jwt.encode(data, SECRET_KEY, algorithm=ALGORITHM) + + +def parse_access_token(token_data: str) -> TokenPayload: + try: + payload: dict[str, Any] = jwt.decode( + token_data, SECRET_KEY, algorithms=[ALGORITHM] + ) + return TokenPayload(**payload) + + except jwt.exceptions.ExpiredSignatureError: + raise signature_has_expired_exception + + except Exception: + raise credentials_exception + + +def get_current_user_or_none( + credentials: HTTPAuthorizationCredentials | None = Depends(optional_security), +) -> models.User | None: + if not credentials: + return + + token = credentials.credentials + token_data = parse_access_token(token) + + user: models.User = services.get_user(token_data.sub) + if not user: + raise credentials_exception + + # NOTE: Если было понижение в роли? :D + if token_data.role != user.role: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="Role in the token and in the database does not match", + ) + + return user + + +def get_current_user( + current_user: Annotated[models.User | None, Depends(get_current_user_or_none)], +) -> models.User: + if not current_user: + raise not_authenticated_exception + + return current_user + + +def get_current_user_admin( + current_user: Annotated[models.User, Depends(get_current_user)], +): + if current_user.role != models.UserRoleEnum.ADMIN: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="Allowed only for admin", + ) + return current_user + + +def get_current_user_manager_or_admin( + current_user: Annotated[models.User, Depends(get_current_user)], +): + if current_user.role not in [models.UserRoleEnum.MANAGER, models.UserRoleEnum.ADMIN]: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="Allowed only for admin and manager", + ) + return current_user + + +if __name__ == "__main__": + # TODO: в тесты + token_data = create_access_token( + TokenPayload(sub="dfsdfsdfsdfsdf", role=models.UserRoleEnum.ADMIN) + ) + print(token_data) + print(parse_access_token(token_data)) diff --git a/fastapi__examples/market_from_stepic/src/market/config.py b/fastapi__examples/market_from_stepic/src/market/config.py new file mode 100644 index 000000000..6478e4ee6 --- /dev/null +++ b/fastapi__examples/market_from_stepic/src/market/config.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import os + +from pathlib import Path + + +DIR: Path = Path(__file__).resolve().parent + +DB_DIR_NAME: Path = DIR / "database" +DB_DIR_NAME.mkdir(parents=True, exist_ok=True) + +DB_FILE_NAME: Path = DB_DIR_NAME / "db.shelve" + +DB_TEST_DIR_NAME: Path = DIR / "database-test" +DB_TEST_DIR_NAME.mkdir(parents=True, exist_ok=True) + +DB_TEST_FILE_NAME: Path = DB_TEST_DIR_NAME / "db.shelve" + +SECRET_KEY_FILE_NAME = DIR / "SECRET_KEY.txt" +SECRET_KEY = ( + os.environ.get("SECRET_KEY") + or SECRET_KEY_FILE_NAME.read_text("utf-8").strip() +) +if not SECRET_KEY: + raise Exception("SECRET_KEY must be set in the SECRET_KEY.txt file or in an environment variable") + +ALGORITHM: str = "HS256" +ACCESS_TOKEN_EXPIRE_MINUTES: int = 60 * 24 * 30 # 30 days diff --git a/fastapi__examples/market_from_stepic/src/market/db.py b/fastapi__examples/market_from_stepic/src/market/db.py new file mode 100644 index 000000000..8a164a2ef --- /dev/null +++ b/fastapi__examples/market_from_stepic/src/market/db.py @@ -0,0 +1,509 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import functools +import threading +import shelve + +from datetime import datetime +from pathlib import Path +from typing import Any +from uuid import uuid4 + +from market import models +from market.config import DB_FILE_NAME +from market.security import get_password_hash + + +class DbException(Exception): + pass + + +class NotFoundException(DbException): + pass + + +class InvalidException(DbException): + pass + + +class InvalidOrderStatusException(InvalidException): + def __init__(self, prev_status: models.StatusOrderEnum, new_status: models.StatusOrderEnum) -> None: + super().__init__(f"Unable to change order status {prev_status.value!r} to {new_status.value!r}") + + +class DB: + KEY_USERS: str = "users" + KEY_PRODUCTS: str = "products" + KEY_SHOPPING_CARTS: str = "shopping_carts" + KEY_ORDERS: str = "orders" + KEY_INDEXES: str = "[indexes]" + + _mutex = threading.RLock() + + def session(*decorator_args, **decorator_kwargs): + def actual_decorator(func): + @functools.wraps(func) + def wrapped(self, *args, **kwargs): + with self._mutex: + has_db: bool = self.db is not None + try: + if not has_db: + self.db = shelve.open(self.file_name, writeback=True) + return func(self, *args, **kwargs) + finally: + if not has_db and self.db is not None: + self.db.close() + self.db = None + return wrapped + return actual_decorator + + def lock(*decorator_args, **decorator_kwargs): + def actual_decorator(func): + @functools.wraps(func) + def wrapped(self, *args, **kwargs): + with self._mutex: + return func(self, *args, **kwargs) + return wrapped + return actual_decorator + + @session() + def get_value(self, name: str, default: Any = None) -> Any: + if not name: + return dict(self.db) + + if name not in self.db: + return default + return self.db.get(name) + + @session() + def set_value(self, name: str, value: Any) -> None: + self.db[name] = value + + def __init__(self, file_name: Path | str = DB_FILE_NAME) -> None: + self.file_name: str = str(file_name) + self.db: shelve.Shelf | None = None + + self._do_init_db_objects() + + def _generate_id(self) -> str: + return str(uuid4()) + + @lock() + def rebuild_indexes(self, clear: bool = True) -> None: + indexes: dict[str, dict[str, str]] = self.get_value(self.KEY_INDEXES, default=dict()) + + if self.KEY_USERS not in indexes: + indexes[self.KEY_USERS] = dict() + + if self.KEY_PRODUCTS not in indexes: + indexes[self.KEY_PRODUCTS] = dict() + + if clear: + indexes.clear() + + indexes[self.KEY_USERS] = {obj.username: obj.id for obj in self.get_users()} + indexes[self.KEY_PRODUCTS] = {obj.name: obj.id for obj in self.get_products()} + + self.set_value(self.KEY_INDEXES, indexes) + + @lock() + def add_index(self, table: str, key: str, id: str) -> None: + indexes: dict[str, dict[str, str]] = self.get_value(self.KEY_INDEXES) + indexes[table][key] = id + + self.set_value(self.KEY_INDEXES, indexes) + + @lock() + def remove_index(self, table: str, key: str) -> None: + indexes: dict[str, dict[str, str]] = self.get_value(self.KEY_INDEXES) + indexes[table].pop(key) + + self.set_value(self.KEY_INDEXES, indexes) + + @lock() + def get_id_from_index(self, table: str, key: str) -> str | None: + indexes: dict[str, dict[str, str]] = self.get_value(self.KEY_INDEXES) + return indexes[table].get(key) + + @lock() + def _do_init_db_objects(self) -> None: + self.rebuild_indexes(clear=False) + + if self.KEY_USERS not in self.get_value(""): + self.set_value(self.KEY_USERS, dict()) + + if self.KEY_PRODUCTS not in self.get_value(""): + self.set_value(self.KEY_PRODUCTS, dict()) + + if self.KEY_SHOPPING_CARTS not in self.get_value(""): + self.set_value(self.KEY_SHOPPING_CARTS, dict()) + + if self.KEY_ORDERS not in self.get_value(""): + self.set_value(self.KEY_ORDERS, dict()) + + if not self.get_value(self.KEY_USERS): + self.create_user( + role=models.UserRoleEnum.ADMIN, + username="admin", + password="Admin_4321!", + id="29ae7ebf-4445-42f2-9548-a3a54f095220", + ) + + if not self.get_value(self.KEY_PRODUCTS): + self.create_product( + name="Coca Cola 1л.", + price_minor=8000, + description="Газированный напиток", + ) + self.create_product( + name="Coca Cola 2л.", + price_minor=13500, + description="Газированный напиток", + ) + self.create_product( + name="Pepsi 1л.", + price_minor=8000, + description="Газированный напиток", + ) + self.create_product( + name="Сникерс", + price_minor=4000, + description="Шоколадный батончик", + ) + + @lock() + def get_users( + self, + username: str | None = None, + ) -> list[models.UserInDb]: + """ + :param username: фильтр по логину + + :return: отфильтрованные пользователи + """ + + filtered_users = [] # Тут собираются отфильтрованные пользователи + + # Перебираем всех пользователей и оставляем только тех, кто прошел фильтры + for user in self.get_value(self.KEY_USERS).values(): + if username is not None and user.username != username: + continue + filtered_users.append(user) + + return filtered_users + + @lock() + def get_user(self, id: str, check_exists: bool = False) -> models.UserInDb | None: + obj = self.get_value(self.KEY_USERS).get(id) + if obj is None and check_exists: + raise NotFoundException(f"User #{id} not found!") + return obj + + @lock() + def get_user_by_username( + self, + username: str, + check_exists: bool = False, + ) -> models.UserInDb | None: + obj_id: str | None = self.get_id_from_index(self.KEY_USERS, username) + return self.get_user( + id=obj_id, + check_exists=check_exists, + ) + + @lock() + def create_user( + self, + role: models.UserRoleEnum, + username: str, + password: str, + id: str | None = None, + ) -> models.UserInDb: + obj_id: str | None = self.get_id_from_index(self.KEY_USERS, username) + if obj_id: + raise DbException(f"Cannot create user {username!r} - this nickname is taken") + + obj = models.UserInDb( + id=id if id else self._generate_id(), + role=role, + username=username, + hashed_password=get_password_hash(password), + ) + self.add_index(self.KEY_USERS, username, obj.id) + + users = self.get_value(self.KEY_USERS) + users[obj.id] = obj + self.set_value(self.KEY_USERS, users) + return obj + + @lock() + def create_product( + self, + name: str, + price_minor: int, + description: str, + ) -> models.Product: + obj_id: str | None = self.get_id_from_index(self.KEY_PRODUCTS, name) + if obj_id: + raise DbException(f"Cannot create product {name!r} - this name is taken") + + obj = models.Product( + id=self._generate_id(), + name=name, + price_minor=price_minor, + description=description, + ) + self.add_index(self.KEY_PRODUCTS, name, obj.id) + + products = self.get_value(self.KEY_PRODUCTS) + products[obj.id] = obj + self.set_value(self.KEY_PRODUCTS, products) + return obj + + @lock() + def update_product( + self, + id: str, + name: str | None = None, + price_minor: int | None = None, + description: str | None = None, + ) -> None: + product = self.get_product(id, check_exists=True) + + if name is not None: + product.name = name + + if price_minor is not None: + product.price_minor = price_minor + + if description is not None: + product.description = description + + products = self.get_value(self.KEY_PRODUCTS) + products[id] = product + + self.set_value(self.KEY_PRODUCTS, products) + + @lock() + def get_products(self) -> list[models.Product]: + return list(self.get_value(self.KEY_PRODUCTS).values()) + + @lock() + def get_product(self, id: str, check_exists: bool = False) -> models.Product | None: + obj = self.get_value(self.KEY_PRODUCTS).get(id) + if obj is None and check_exists: + raise NotFoundException(f"Product #{id} not found!") + return obj + + @lock() + def create_shopping_cart(self, product_ids: list[str]) -> models.ShoppingCart: + obj = models.ShoppingCart( + id=self._generate_id(), + product_ids=product_ids, + ) + shopping_carts = self.get_value(self.KEY_SHOPPING_CARTS) + shopping_carts[obj.id] = obj + self.set_value(self.KEY_SHOPPING_CARTS, shopping_carts) + return obj + + @lock() + def delete_shopping_cart(self, shopping_cart_id: str) -> None: + # Проверка наличия + self.get_shopping_cart(shopping_cart_id, check_exists=True) + + shopping_carts: dict = self.get_value(self.KEY_SHOPPING_CARTS) + shopping_carts.pop(shopping_cart_id) + + self.set_value(self.KEY_SHOPPING_CARTS, shopping_carts) + + @lock() + def update_shopping_cart( + self, + shopping_cart_id: str, + product_ids: list[str], + ) -> None: + shopping_cart: models.ShoppingCart = self.get_shopping_cart( + shopping_cart_id, check_exists=True + ) + shopping_cart.product_ids = product_ids + + shopping_carts = self.get_value(self.KEY_SHOPPING_CARTS) + shopping_carts[shopping_cart_id] = shopping_cart + + self.set_value(self.KEY_SHOPPING_CARTS, shopping_carts) + + @lock() + def get_shopping_carts(self) -> list[models.ShoppingCart]: + return list(self.get_value(self.KEY_SHOPPING_CARTS).values()) + + @lock() + def get_shopping_cart( + self, + id: str, + check_exists: bool = False, + ) -> models.ShoppingCart | None: + obj = self.get_value(self.KEY_SHOPPING_CARTS).get(id) + if obj is None and check_exists: + raise NotFoundException(f"Shopping cart #{id} not found!") + return obj + + @lock() + def add_product_in_shopping_cart( + self, + shopping_cart_id: str, + product_id: str, + ) -> None: + shopping_cart: models.ShoppingCart = self.get_shopping_cart( + shopping_cart_id, check_exists=True + ) + + # Проверка наличия + self.get_product(product_id, check_exists=True) + + shopping_cart.product_ids.append(product_id) + self.update_shopping_cart(shopping_cart_id, shopping_cart.product_ids) + + @lock() + def remove_product_from_shopping_cart( + self, + shopping_cart_id: str, + product_id: str, + ) -> None: + shopping_cart: models.ShoppingCart = self.get_shopping_cart( + shopping_cart_id, check_exists=True + ) + + # Проверка наличия + self.get_product(product_id, check_exists=True) + + if product_id in shopping_cart.product_ids: + shopping_cart.product_ids.remove(product_id) + + self.update_shopping_cart(shopping_cart_id, shopping_cart.product_ids) + + @lock() + def create_order( + self, + email: str, + shopping_cart_id: str, + ) -> models.Order: + # Проверка наличия + self.get_shopping_cart(shopping_cart_id, check_exists=True) + + obj = models.Order( + id=self._generate_id(), + email=email, + shopping_cart_id=shopping_cart_id, + ) + orders = self.get_value(self.KEY_ORDERS) + orders[obj.id] = obj + self.set_value(self.KEY_ORDERS, orders) + return obj + + @lock() + def update_order( + self, + id: str, + email: str | None = None, + shopping_cart_id: str | None = None, + status: models.StatusOrderEnum | None = None, + cancel_reason: str | None = None, + ): + order = self.get_order(id, check_exists=True) + + complete_statuses = (models.StatusOrderEnum.FINISHED, models.StatusOrderEnum.CANCELED) + if order.status in complete_statuses: + raise DbException(f"It is forbidden to update an order with status {order.status.value!r}") + + if email is not None: + order.email = email + + if shopping_cart_id is not None: + # Проверка наличия + self.get_shopping_cart(shopping_cart_id, check_exists=True) + + order.shopping_cart_id = shopping_cart_id + + if status is not None and status != order.status: + invalid_status_exception = InvalidOrderStatusException(order.status, status) + + match order.status: + case models.StatusOrderEnum.CREATED: + pass + case models.StatusOrderEnum.IN_PROCESSED: + # Если текущий статус "в процессе", то следующий может быть или отмена, или завершение + if status not in complete_statuses: + raise invalid_status_exception + case models.StatusOrderEnum.CANCELED: + raise invalid_status_exception + case models.StatusOrderEnum.FINISHED: + raise invalid_status_exception + case _: + raise InvalidException(f"Unsupported status {status.value!r}!") + + order.status = status + + if status in complete_statuses: + order.closed_date = datetime.now() + + if cancel_reason is not None: + order.cancel_reason = cancel_reason + + orders = self.get_value(self.KEY_ORDERS) + orders[id] = order + + self.set_value(self.KEY_ORDERS, orders) + + @lock() + def get_orders(self) -> list[models.Order]: + return list(self.get_value(self.KEY_ORDERS).values()) + + @lock() + def get_order( + self, + id: str, + check_exists: bool = False, + ) -> models.Order | None: + obj = self.get_value(self.KEY_ORDERS).get(id) + if obj is None and check_exists: + raise NotFoundException(f"Order #{id} not found!") + return obj + + +db = DB() + + +if __name__ == "__main__": + # db.rebuild_indexes() + print(db.get_value("")) + + # # TODO: В тесты + # from market.config import DB_TEST_FILE_NAME + # db_test = DB(file_name=DB_TEST_FILE_NAME) + # + # value = db_test.get_value("counter", default=1) + # print(f"Counter: {value}") + # + # def inc_counter(): + # value = db_test.get_value("counter", default=1) + # db_test.set_value("counter", value + 1) + # + # inc_counter() + + # TODO: не рабочий вариант, нужно использовать методы самого DB + # current_value = db_test.get_value("counter", default=1) + # + # max_workers = 5 + # number = 50 + # expected_value = current_value + number + # + # import concurrent.futures + # with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor: + # futures = [executor.submit(inc_counter) for _ in range(number)] + # concurrent.futures.wait(futures) + # + # print(expected_value, db_test.get_value("counter", default=1)) diff --git a/fastapi__examples/market_from_stepic/src/market/main.py b/fastapi__examples/market_from_stepic/src/market/main.py new file mode 100644 index 000000000..2e1ecb5a4 --- /dev/null +++ b/fastapi__examples/market_from_stepic/src/market/main.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from fastapi import FastAPI, Request, status +from fastapi.responses import HTMLResponse, JSONResponse + +from market.db import DbException, NotFoundException +from market.resources import router + + +app = FastAPI() + + +@app.exception_handler(DbException) +async def unicorn_exception_handler(_: Request, exc: DbException): + status_code = ( + status.HTTP_404_NOT_FOUND + if isinstance(exc, NotFoundException) + else status.HTTP_400_BAD_REQUEST + ) + return JSONResponse( + status_code=status_code, + content={"detail": str(exc)}, + ) + + +@app.get("/", response_class=HTMLResponse) +def index() -> str: + return """\ + + + + + + + + +
+ + + + + """ + + +app.include_router(router, prefix="/api/v1") diff --git a/fastapi__examples/market_from_stepic/src/market/models.py b/fastapi__examples/market_from_stepic/src/market/models.py new file mode 100644 index 000000000..5eb290742 --- /dev/null +++ b/fastapi__examples/market_from_stepic/src/market/models.py @@ -0,0 +1,183 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import enum + +from dataclasses import dataclass, field, fields +from datetime import datetime +from typing import Any + + +# TODO: аннотации +def create_from(cls, other): + data: dict[str, Any] = dict.fromkeys( + f.name for f in fields(cls) + ) + for f in fields(other): + name = f.name + if name in data: + data[name] = getattr(other, name) + + return cls(**data) + + +class UserRoleEnum(enum.StrEnum): + MANAGER = enum.auto() + ADMIN = enum.auto() + + +class StatusOrderEnum(enum.StrEnum): + CREATED = enum.auto() + IN_PROCESSED = enum.auto() + FINISHED = enum.auto() + CANCELED = enum.auto() + + +@dataclass +class IdBasedObj: + id: str + + +@dataclass +class User(IdBasedObj): + role: UserRoleEnum + username: str = None + + +@dataclass +class UserInDb(User): + hashed_password: str = None + + +@dataclass +class LoginResponse: + token: str + user: User + + +@dataclass +class CreateUser: + username: str + password: str + role: UserRoleEnum + + +@dataclass +class Users: + items: list[User] + + +@dataclass +class CreateProduct: + """Товар""" + + name: str + price_minor: int # Копейки + description: str + + +@dataclass +class Product(CreateProduct, IdBasedObj): + pass + + +@dataclass +class UpdateProduct: + """Товар""" + + name: str | None = None + price_minor: int | None = None # Копейки + description: str | None = None + + +@dataclass +class Product(IdBasedObj): + """Товар""" + + name: str + price_minor: int # Копейки + description: str + + +@dataclass +class Products: + items: list[Product] + + +@dataclass +class ProductsBasedObj: + product_ids: list[str] = field(default_factory=list) + + +@dataclass +class CreateShoppingCart(ProductsBasedObj): + """Корзина с товарами""" + + name: str = "" + + +@dataclass +class ShoppingCart(ProductsBasedObj, IdBasedObj): + """Корзина с товарами""" + + +@dataclass +class ShoppingCarts: + items: list[ShoppingCart] + + +@dataclass +class BaseOrder: + """Заказ""" + + email: str + shopping_cart_id: str + + +@dataclass +class Order(BaseOrder, IdBasedObj): + """Заказ""" + + status: StatusOrderEnum = StatusOrderEnum.CREATED + created_date: datetime = datetime.now() + cancel_reason: str | None = None + closed_date: datetime | None = None + + +@dataclass +class UpdateOrder: + """Заказ""" + + email: str | None = None + shopping_cart_id: str | None = None + status: StatusOrderEnum | None = None + cancel_reason: str | None = None + + +@dataclass +class SubmitOrder: + """Заказ""" + + status: StatusOrderEnum + + +@dataclass +class Orders: + items: list[Order] + + +# TODO: в тесты +# user1 = UserInDb("123", UserRoleEnum.ADMIN, "dfsf", "dddd") +# print(user1) +# user2 = User("123", UserRoleEnum.ADMIN, "dfsf") +# print(user2) +# # print(User(**user1)) +# print() +# +# print(*fields(user1), sep="\n") +# print(fields(User)) +# +# print(create_from(User, user1)) diff --git a/fastapi__examples/market_from_stepic/src/market/resources.py b/fastapi__examples/market_from_stepic/src/market/resources.py new file mode 100644 index 000000000..cb3467622 --- /dev/null +++ b/fastapi__examples/market_from_stepic/src/market/resources.py @@ -0,0 +1,242 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from typing import Annotated + +from fastapi import APIRouter, status, Depends, HTTPException +from fastapi.security import OAuth2PasswordRequestForm + +from market import auth +from market import models +from market import services +from market.security import verify_password + + +router = APIRouter() + + +@router.post("/token") +def login_for_access_token( + credentials: Annotated[OAuth2PasswordRequestForm, Depends()], +) -> models.LoginResponse: + exception_400 = HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Incorrect username or password", + ) + + if not credentials.username or not credentials.password: + raise exception_400 + + user: models.UserInDb = services.get_user_by_username(credentials.username) + if verify_password(credentials.password, user.hashed_password): + # Generate a JWT token + access_token = auth.create_access_token( + token=auth.TokenPayload(user.id, user.role), + ) + + # Return the access token and user details + return models.LoginResponse( + token=access_token, + user=models.User( + id=user.id, + username=user.username, + role=user.role, + ), + ) + + raise exception_400 + + +@router.get("/users/me/") +def read_users_me( + current_user: Annotated[models.User, Depends(auth.get_current_user)], +) -> models.User: + return current_user + + +@router.get("/users") +def get_users( + _: Annotated[models.Users, Depends(auth.get_current_user_admin)], +) -> models.Users: + return services.get_users() + + +@router.get("/user/{id}") +def get_user(id: str) -> models.User: + return services.get_user(id) + + +@router.post( + "/users", + status_code=status.HTTP_201_CREATED, +) +def create_user( + user: models.CreateUser, + _: Annotated[models.User, Depends(auth.get_current_user_admin)], +) -> models.IdBasedObj: + return services.create_user( + role=user.role, + username=user.username, + password=user.password, + ) + + +@router.get("/products") +def get_products() -> models.Products: + return services.get_products() + + +@router.get("/product/{id}") +def get_product(id: str) -> models.Product: + return services.get_product(id) + + +@router.patch("/product/{id}") +def update_product( + id: str, + other: models.UpdateProduct, + current_user: Annotated[models.User, Depends(auth.get_current_user_manager_or_admin)], +) -> models.Product: + if current_user.role == models.UserRoleEnum.MANAGER: + # У менеджера нет прав на переименование продукта + if other.name is not None: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="No rights to edit the name field", + ) + + services.update_product( + id=id, + name=other.name, + price_minor=other.price_minor, + description=other.description, + ) + + return services.get_product(id) + + +@router.post( + "/products", + status_code=status.HTTP_201_CREATED, +) +def create_product( + product: models.CreateProduct, + _: Annotated[models.User, Depends(auth.get_current_user_admin)], +) -> models.IdBasedObj: + return services.create_product( + name=product.name, + price_minor=product.price_minor, + description=product.description, + ) + + +@router.get("/shopping-carts") +def get_shopping_carts( + _: Annotated[models.User, Depends(auth.get_current_user_manager_or_admin)], +) -> models.ShoppingCarts: + return services.get_shopping_carts() + + +@router.get("/shopping-cart/{id}") +def get_shopping_cart(id: str) -> models.ShoppingCart: + return services.get_shopping_cart(id) + + +@router.post("/shopping-cart/{id}/products") +def add_product_in_shopping_cart(id: str, add_to: models.ProductsBasedObj) -> models.ShoppingCart: + for product_id in add_to.product_ids: + services.add_product_in_shopping_cart( + shopping_cart_id=id, product_id=product_id + ) + + return services.get_shopping_cart(id) + + +@router.delete("/shopping-cart/{id}/products") +def remove_product_from_shopping_cart(id: str, remove_from: models.ProductsBasedObj) -> models.ShoppingCart: + for product_id in remove_from.product_ids: + services.remove_product_from_shopping_cart( + shopping_cart_id=id, product_id=product_id + ) + + return services.get_shopping_cart(id) + + +@router.post( + "/shopping-carts", + status_code=status.HTTP_201_CREATED, +) +def create_shopping_cart( + shopping_cart: models.CreateShoppingCart, +) -> models.IdBasedObj: + return services.create_shopping_cart( + product_ids=shopping_cart.product_ids, + ) + + +@router.delete("/shopping-cart/{id}", status_code=status.HTTP_204_NO_CONTENT) +def delete_shopping_cart(id: str) -> None: + services.delete_shopping_cart(id) + + +@router.get("/orders") +def get_orders( + _: Annotated[models.User, Depends(auth.get_current_user_manager_or_admin)], +) -> models.Orders: + return services.get_orders() + + +@router.get("/order/{id}") +def get_order(id: str) -> models.Order: + return services.get_order(id) + + +@router.post( + "/orders", + status_code=status.HTTP_201_CREATED, +) +def create_order( + order: models.BaseOrder, +) -> models.IdBasedObj: + return services.create_order( + email=order.email, + shopping_cart_id=order.shopping_cart_id, + ) + + +@router.patch("/order/{id}") +def update_order( + id: str, + other: models.UpdateOrder, + current_user: Annotated[models.User | None, Depends(auth.get_current_user_or_none)] = None, +) -> models.Order: + services.update_order( + id=id, + email=other.email, + shopping_cart_id=other.shopping_cart_id, + status=other.status, + cancel_reason=other.cancel_reason, + context_user=current_user, + ) + + return services.get_order(id) + + +@router.post("/order/{id}/submit") +def submit_order( + id: str, + other: models.SubmitOrder, + current_user: Annotated[ + models.User | None, Depends(auth.get_current_user_or_none) + ] = None, +) -> models.Order: + services.submit_order( + id=id, + status=other.status, + context_user=current_user, + ) + + return services.get_order(id) diff --git a/fastapi__examples/market_from_stepic/src/market/security.py b/fastapi__examples/market_from_stepic/src/market/security.py new file mode 100644 index 000000000..2f2689b2b --- /dev/null +++ b/fastapi__examples/market_from_stepic/src/market/security.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from passlib.context import CryptContext + + +pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") + + +def verify_password(plain_password, hashed_password): + return pwd_context.verify(plain_password, hashed_password) + + +def get_password_hash(password): + return pwd_context.hash(password) diff --git a/fastapi__examples/market_from_stepic/src/market/services.py b/fastapi__examples/market_from_stepic/src/market/services.py new file mode 100644 index 000000000..e0f838b71 --- /dev/null +++ b/fastapi__examples/market_from_stepic/src/market/services.py @@ -0,0 +1,186 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import fastapi + +from market import models +from market.db import db + + +def get_users() -> models.Users: + return models.Users( + items=[models.create_from(models.User, user) for user in db.get_users()] + ) + + +def get_user(id: str) -> models.User: + return models.create_from( + models.User, + db.get_user(id, check_exists=True), + ) + + +def get_user_by_username(username: str) -> models.UserInDb: + return db.get_user_by_username(username, check_exists=True) + + +def create_user( + role: models.UserRoleEnum, + username: str, + password: str, +) -> models.IdBasedObj: + user = db.create_user( + role=role, + username=username, + password=password, + ) + return models.IdBasedObj(id=user.id) + + +def get_products() -> models.Products: + return models.Products(items=db.get_products()) + + +def get_product(id: str) -> models.Product: + return db.get_product(id, check_exists=True) + + +def create_product( + name: str, + price_minor: int, # Копейки + description: str, +) -> models.IdBasedObj: + product = db.create_product( + name=name, + price_minor=price_minor, + description=description, + ) + return models.IdBasedObj(id=product.id) + + +def update_product( + id: str, + name: str | None = None, + price_minor: int | None = None, # Копейки + description: str | None = None, +) -> None: + db.update_product( + id=id, + name=name, + price_minor=price_minor, + description=description, + ) + + +def create_shopping_cart(product_ids: list[str] = None) -> models.IdBasedObj: + if product_ids is None: + product_ids = [] + + shopping_cart = db.create_shopping_cart( + product_ids=product_ids, + ) + return models.IdBasedObj(id=shopping_cart.id) + + +def delete_shopping_cart(shopping_cart_id: str) -> None: + db.delete_shopping_cart(shopping_cart_id) + + +def add_product_in_shopping_cart( + shopping_cart_id: str, + product_id: str, +) -> None: + db.add_product_in_shopping_cart( + shopping_cart_id=shopping_cart_id, + product_id=product_id, + ) + + +def remove_product_from_shopping_cart( + shopping_cart_id: str, + product_id: str, +) -> None: + db.remove_product_from_shopping_cart( + shopping_cart_id=shopping_cart_id, + product_id=product_id, + ) + + +def get_shopping_carts() -> models.ShoppingCarts: + return models.ShoppingCarts(items=db.get_shopping_carts()) + + +def get_shopping_cart(id: str) -> models.ShoppingCart: + return db.get_shopping_cart(id, check_exists=True) + + +def get_orders() -> models.Orders: + return models.Orders(items=db.get_orders()) + + +def get_order(id: str) -> models.Order: + return db.get_order(id, check_exists=True) + + +def create_order( + email: str, + shopping_cart_id: str, +) -> models.IdBasedObj: + obj = db.create_order( + email=email, + shopping_cart_id=shopping_cart_id, + ) + return models.IdBasedObj(id=obj.id) + + +def update_order( + id: str, + email: str | None = None, + shopping_cart_id: str | None = None, + status: models.StatusOrderEnum | None = None, + cancel_reason: str | None = None, + context_user: models.User | None = None, +) -> None: + # Клиент не может сам запускать выполнение заказа или завершать его + if context_user is None and status in (models.StatusOrderEnum.IN_PROCESSED, models.StatusOrderEnum.FINISHED): + raise fastapi.HTTPException( + status_code=fastapi.status.HTTP_403_FORBIDDEN, + detail=f"No rights to set status {status.value!r}", + ) + + cancel_reason: str | None = cancel_reason + + # Если причина отмены не задана и статус отмена + if cancel_reason is None and status == models.StatusOrderEnum.CANCELED: + user_role: models.UserRoleEnum | None = context_user.role if context_user else None + match user_role: + case models.UserRoleEnum.ADMIN: + cancel_reason = f"Canceled by admin {context_user.username!r}" + case models.UserRoleEnum.MANAGER: + cancel_reason = f"Canceled by manager {context_user.username!r}" + case _: + cancel_reason = "Canceled by user" + + db.update_order( + id=id, + email=email, + shopping_cart_id=shopping_cart_id, + status=status, + cancel_reason=cancel_reason, + ) + + +def submit_order( + id: str, + status: models.StatusOrderEnum, + context_user: models.User | None = None, +) -> None: + update_order( + id=id, + status=status, + context_user=context_user, + ) + diff --git a/fastapi__examples/market_from_stepic/src/tests/__init__.py b/fastapi__examples/market_from_stepic/src/tests/__init__.py new file mode 100644 index 000000000..f25732790 --- /dev/null +++ b/fastapi__examples/market_from_stepic/src/tests/__init__.py @@ -0,0 +1,4 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" diff --git a/fastapi__examples/running-programmatically.py b/fastapi__examples/running-programmatically.py new file mode 100644 index 000000000..6d558312a --- /dev/null +++ b/fastapi__examples/running-programmatically.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from fastapi import FastAPI + + +app = FastAPI() + + +@app.get("/") +def index(): + return {"text": "Hello World!"} + + +if __name__ == "__main__": + from pathlib import Path + import uvicorn + + uvicorn.run( + app=f"{Path(__file__).stem}:app", + host="127.0.0.1", + port=8000, + reload=True, + ) diff --git a/fastapi__examples/show_my_ip.py b/fastapi__examples/show_my_ip.py new file mode 100644 index 000000000..90434e702 --- /dev/null +++ b/fastapi__examples/show_my_ip.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from dataclasses import dataclass +from fastapi import FastAPI, Request + + +@dataclass +class Ip: + host: str + + +app = FastAPI() + + +@app.get("/") +def index(request: Request) -> str: + return request.client.host + + +@app.get("/json") +def index(request: Request) -> Ip: + return Ip( + host=request.client.host, + ) + + +if __name__ == "__main__": + from pathlib import Path + import uvicorn + + uvicorn.run( + app=f"{Path(__file__).stem}:app", + host="127.0.0.1", + port=8000, + reload=True, + ) diff --git a/fb2__parsing/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/common.py b/firefox/common.py index 114a9f80c..84f3b8578 100644 --- a/firefox/common.py +++ b/firefox/common.py @@ -6,7 +6,7 @@ import logging import sys - +from logging.handlers import RotatingFileHandler from pathlib import Path @@ -21,23 +21,23 @@ def get_logger(file_name: str, dir_name=DIR_LOGS): file_name = dir_name / (Path(file_name).resolve().name + ".log") - log = logging.getLogger(__name__) + log = logging.getLogger(__file__) log.setLevel(logging.DEBUG) formatter = logging.Formatter( "[%(asctime)s] %(filename)s[LINE:%(lineno)d] %(levelname)-8s %(message)s" ) - fh = logging.FileHandler(file_name, encoding="utf-8") + fh = RotatingFileHandler( + file_name, maxBytes=10_000_000, backupCount=5, encoding="utf-8" + ) fh.setLevel(logging.DEBUG) + fh.setFormatter(formatter) + log.addHandler(fh) ch = logging.StreamHandler(stream=sys.stdout) ch.setLevel(logging.DEBUG) - - fh.setFormatter(formatter) ch.setFormatter(formatter) - - log.addHandler(fh) log.addHandler(ch) return log diff --git a/firefox/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/caching_response.py b/flask__webservers/caching_response.py new file mode 100644 index 000000000..ac2b6128e --- /dev/null +++ b/flask__webservers/caching_response.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +from datetime import datetime + +# pip install flask==2.3.3 +from flask import Flask, render_template_string + +# pip install flask-caching=2.0.2 +from flask_caching import Cache + + +config = { + "DEBUG": True, # some Flask specific configs + "CACHE_TYPE": "SimpleCache", # Flask-Caching related configs + "CACHE_DEFAULT_TIMEOUT": 300, +} + +app = Flask(__name__) + +# tell Flask to use the above defined config +app.config.from_mapping(config) +cache = Cache(app) + + +@app.route("/") +@cache.cached(timeout=50) +def index(): + # SOURCE: https://stackoverflow.com/a/55050637/5909792 + return render_template_string( + """ + + + + + +
+

{{ text }}

+
+ + + """, + text=str(datetime.now()), + ) + + +if __name__ == "__main__": + # Localhost + # port=0 -- random free port + # app.run(port=0) + app.run(port=50000) + + # # Public IP + # app.run(host='0.0.0.0') diff --git a/flask__webservers/cors__hello_world.py b/flask__webservers/cors__hello_world.py new file mode 100644 index 000000000..f771cddde --- /dev/null +++ b/flask__webservers/cors__hello_world.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +# pip install flask==2.3.3 +from flask import Flask + +# pip install flask-cors==4.0.0 +from flask_cors import CORS + + +app = Flask(__name__) +CORS(app) + + +@app.route("/") +def index() -> str: + return "Hello World!" + + +if __name__ == "__main__": + app.debug = True + + # Localhost + # port=0 -- random free port + # app.run(port=0) + app.run(port=50000) + + # # Public IP + # app.run(host='0.0.0.0') diff --git a/flask__webservers/custom_error_page/main.py b/flask__webservers/custom_error_page/main.py new file mode 100644 index 000000000..64c67879b --- /dev/null +++ b/flask__webservers/custom_error_page/main.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +# SOURCE: https://flask.palletsprojects.com/en/3.0.x/errorhandling/ + + +import logging + +from flask import Flask, render_template +from werkzeug.exceptions import HTTPException + + +app = Flask(__name__) +logging.basicConfig(level=logging.DEBUG) + + +@app.errorhandler(404) +def page_not_found(e): + # note that we set the 404 status explicitly + return render_template('errors/404.html'), 404 + + +@app.errorhandler(Exception) +def handle_exception(e): + # pass through HTTP errors + if isinstance(e, HTTPException): + return e + + # now you're handling non-HTTP exceptions only + return render_template("errors/500.html", e=e), 500 + + +@app.route("/") +def index(): + return render_template('index.html') + + +@app.route("/500") +def do_500() -> None: + 1/0 + + +if __name__ == "__main__": + app.run(port=5000) diff --git a/flask__webservers/custom_error_page/static/images/404.jpg b/flask__webservers/custom_error_page/static/images/404.jpg new file mode 100644 index 000000000..76a486e4a Binary files /dev/null and b/flask__webservers/custom_error_page/static/images/404.jpg differ diff --git a/flask__webservers/custom_error_page/static/images/500.jpg b/flask__webservers/custom_error_page/static/images/500.jpg new file mode 100644 index 000000000..f9edf60a6 Binary files /dev/null and b/flask__webservers/custom_error_page/static/images/500.jpg differ diff --git a/flask__webservers/custom_error_page/templates/base.html b/flask__webservers/custom_error_page/templates/base.html new file mode 100644 index 000000000..67f2c8af6 --- /dev/null +++ b/flask__webservers/custom_error_page/templates/base.html @@ -0,0 +1,11 @@ + + + + + + {% block title %}{% endblock %} + + + {% block content %}{% endblock %} + + diff --git a/flask__webservers/custom_error_page/templates/errors/404.html b/flask__webservers/custom_error_page/templates/errors/404.html new file mode 100644 index 000000000..40823bc07 --- /dev/null +++ b/flask__webservers/custom_error_page/templates/errors/404.html @@ -0,0 +1,10 @@ +{% extends 'base.html' %} + +{% block title %}Page Not Found{% endblock %} + +{% block content %} +

Page Not Found

+

What you were looking for is just not there.

+ +

go somewhere nice

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

Internal Server Error

+ +

Something unexpected happened... Please try again later

+

Error: {{ e }}

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

Start page

+

Go 404

+

Go 500

+{% endblock %} diff --git a/flask__webservers/filter-sort-paginate-on-server/items.json b/flask__webservers/filter-sort-paginate-on-server/items.json new file mode 100644 index 000000000..bc8789bd8 --- /dev/null +++ b/flask__webservers/filter-sort-paginate-on-server/items.json @@ -0,0 +1,302 @@ +[ + { + "id": 1, + "name": "Preston", + "description": "Magna commodo deserunt culpa anim anim exercitation reprehenderit officia occaecat consectetur nisi est ut. Tempor minim aute nostrud sunt do labore cillum. Consequat dolore do pariatur pariatur fugiat nulla anim exercitation culpa nisi qui dolor dolore magna.\r\n", + "command": "Autograte-prestonherrera@autograte.com" + }, + { + "id": 2, + "name": "Fay", + "description": "Elit laboris quis sint magna labore magna ullamco anim excepteur. Duis incididunt excepteur ut labore aliquip do. Aute sint do excepteur aliquip mollit proident fugiat sint. Mollit labore ex esse consectetur incididunt commodo in. Occaecat commodo eiusmod ad enim eiusmod consectetur anim cupidatat do duis est elit. Nisi occaecat est dolor dolor officia anim irure excepteur magna aute mollit.\r\n", + "command": "Liquidoc-fayherrera@liquidoc.com" + }, + { + "id": 3, + "name": "Dena", + "description": "Deserunt consequat Lorem adipisicing duis minim. Irure pariatur occaecat voluptate consectetur pariatur sint commodo excepteur incididunt esse amet id. Elit commodo nulla enim aliqua mollit ullamco duis anim consequat. Culpa sit ex nostrud fugiat reprehenderit fugiat. Incididunt enim ullamco esse velit consectetur. Esse cillum anim ullamco occaecat commodo enim aute laboris.\r\n", + "command": "Lingoage-denaherrera@lingoage.com" + }, + { + "id": 4, + "name": "Jacobs", + "description": "Voluptate anim amet dolor deserunt nostrud cillum magna velit enim qui. Dolore magna aliquip nisi eu exercitation eu eu Lorem. Ea occaecat culpa sit qui cupidatat veniam. Exercitation minim minim anim ad nulla veniam et velit. Nostrud laborum labore culpa do ipsum et cupidatat. Cupidatat officia adipisicing sunt aliquip id nisi deserunt.\r\n", + "command": "Imant-jacobsherrera@imant.com" + }, + { + "id": 5, + "name": "Richards", + "description": "Et eu laboris aute ipsum. Cupidatat consectetur dolor sint elit. Et quis laborum deserunt deserunt do cillum ipsum proident sunt laboris enim veniam. Sint id fugiat eu quis est dolore ut minim duis. Labore voluptate nulla adipisicing culpa proident.\r\n", + "command": "Octocore-richardsherrera@octocore.com" + }, + { + "id": 6, + "name": "Connie", + "description": "Officia culpa culpa proident fugiat dolor esse irure incididunt officia occaecat mollit amet. Ea exercitation reprehenderit proident laboris sint aute eiusmod voluptate. Mollit cupidatat exercitation laboris nulla sunt non ex.\r\n", + "command": "Circum-connieherrera@circum.com" + }, + { + "id": 7, + "name": "Lilia", + "description": "Aliquip sunt non do et nisi incididunt minim et. Nisi culpa elit in exercitation velit dolor cupidatat veniam consectetur mollit aliqua culpa. Ea irure commodo et cillum id. Sint pariatur eu cupidatat occaecat tempor excepteur do eiusmod sint.\r\n", + "command": "Petigems-liliaherrera@petigems.com" + }, + { + "id": 8, + "name": "Serena", + "description": "Lorem aliqua ad ut ut. Minim dolore incididunt sit enim incididunt consectetur. Ad cillum non sit sunt elit quis reprehenderit qui deserunt. Exercitation voluptate consectetur ullamco Lorem ipsum ut pariatur ea velit anim ex elit. Cupidatat proident reprehenderit ad aute in anim anim veniam ullamco mollit qui.\r\n", + "command": "Slumberia-serenaherrera@slumberia.com" + }, + { + "id": 9, + "name": "Cherie", + "description": "Labore incididunt mollit exercitation deserunt nostrud sint qui eu. Quis laboris labore ipsum cupidatat ea. Laboris consectetur ea Lorem deserunt tempor reprehenderit aliquip ipsum nostrud ex nisi veniam.\r\n", + "command": "Futurize-cherieherrera@futurize.com" + }, + { + "id": 10, + "name": "Lindsey", + "description": "Excepteur sit pariatur cupidatat veniam ullamco quis magna eu voluptate labore eiusmod magna tempor. Sint do sit in cillum id et ullamco aliquip nostrud occaecat. Consequat amet in nostrud adipisicing magna laborum duis labore fugiat culpa ut laborum. Non aliqua ea ex non. Nostrud quis ullamco nisi Lorem reprehenderit proident culpa fugiat.\r\n", + "command": "Aquoavo-lindseyherrera@aquoavo.com" + }, + { + "id": 11, + "name": "Nicole", + "description": "In est magna laboris velit sint ea. Quis tempor deserunt quis voluptate in proident adipisicing dolor exercitation laborum. Sit consequat reprehenderit cillum ut non ex aute sint. Lorem ea eu fugiat adipisicing nostrud cillum duis dolore incididunt nostrud nisi veniam aliqua occaecat.\r\n", + "command": "Strozen-nicoleherrera@strozen.com" + }, + { + "id": 12, + "name": "Joyner", + "description": "Dolore enim sunt adipisicing et reprehenderit. Incididunt laborum elit commodo duis sint Lorem fugiat dolore in cupidatat sunt commodo. Adipisicing do officia eiusmod adipisicing amet eu reprehenderit non incididunt excepteur ipsum sit. Et duis irure pariatur tempor laborum ad minim id enim fugiat exercitation aute mollit eu. Sint velit ipsum sint pariatur dolor aliquip sint irure adipisicing eu. Deserunt non eu dolor mollit irure elit consectetur voluptate sint ut dolore cillum do ullamco. Magna amet cillum est culpa aliquip occaecat eiusmod non est laborum non officia aliqua non.\r\n", + "command": "Rubadub-joynerherrera@rubadub.com" + }, + { + "id": 13, + "name": "Isabel", + "description": "Ad culpa esse mollit quis laborum deserunt consequat ad aliqua qui irure reprehenderit est mollit. Irure mollit cillum nostrud non. Ullamco laboris deserunt est et. Irure consectetur pariatur laboris in sunt aliqua ex reprehenderit duis laboris aliquip deserunt laborum. Pariatur ex veniam nulla ipsum amet excepteur qui dolor commodo. Incididunt aliqua nostrud et dolore nisi cupidatat dolore eu minim deserunt aute velit. Duis occaecat et duis commodo ex magna officia pariatur consectetur exercitation irure ullamco proident consectetur.\r\n", + "command": "Assurity-isabelherrera@assurity.com" + }, + { + "id": 14, + "name": "Jayne", + "description": "Ad cupidatat adipisicing occaecat ea magna mollit ex esse nostrud velit officia. Aliqua commodo enim commodo adipisicing cillum est in quis. Mollit sint ea adipisicing enim ea labore et. Cillum aliquip dolore elit aute sit sint incididunt exercitation dolore voluptate ut. Aliqua nostrud consectetur minim reprehenderit eu aliquip elit nulla consectetur nisi reprehenderit ullamco. Nostrud consectetur sunt in nostrud nostrud consectetur officia aliqua esse Lorem.\r\n", + "command": "Playce-jayneherrera@playce.com" + }, + { + "id": 15, + "name": "Tonia", + "description": "Eiusmod ex elit ullamco exercitation consectetur enim aliquip id qui dolor. Occaecat id et sit aute deserunt duis eiusmod mollit labore adipisicing. Voluptate consectetur dolore deserunt elit Lorem. Reprehenderit ex esse proident anim sunt qui pariatur in sit culpa.\r\n", + "command": "Fossiel-toniaherrera@fossiel.com" + }, + { + "id": 16, + "name": "Teresa", + "description": "Duis nostrud fugiat in duis laboris non irure quis. Non tempor reprehenderit eu incididunt occaecat exercitation incididunt excepteur deserunt cillum nulla deserunt elit sunt. Minim occaecat amet nostrud dolore Lorem labore incididunt tempor proident. Fugiat reprehenderit culpa quis pariatur eiusmod eu do aute eu sunt.\r\n", + "command": "Zosis-teresaherrera@zosis.com" + }, + { + "id": 17, + "name": "Nelda", + "description": "Nulla labore eiusmod aute aute mollit laborum mollit aliquip. Non qui id incididunt enim qui minim cupidatat aute proident est in nostrud. Id consequat eiusmod officia ex veniam elit occaecat veniam commodo eiusmod ullamco tempor. Pariatur exercitation sint sint anim nulla. Deserunt dolore enim cupidatat exercitation mollit elit magna magna Lorem mollit.\r\n", + "command": "Uneeq-neldaherrera@uneeq.com" + }, + { + "id": 18, + "name": "Farley", + "description": "Qui sit veniam nostrud consequat deserunt fugiat laboris occaecat. Laboris nisi sit velit velit aliqua ea labore amet sunt ullamco cillum. Laborum Lorem exercitation laboris consequat exercitation occaecat cupidatat ex elit officia aliqua deserunt culpa ea.\r\n", + "command": "Shopabout-farleyherrera@shopabout.com" + }, + { + "id": 19, + "name": "Porter", + "description": "Ipsum anim pariatur eiusmod sint sunt labore. Reprehenderit adipisicing laborum occaecat anim labore laboris mollit eiusmod minim et. Nisi dolor in dolor est nostrud. Commodo qui laborum ipsum occaecat esse est. Veniam velit mollit aute qui.\r\n", + "command": "Artiq-porterherrera@artiq.com" + }, + { + "id": 20, + "name": "Teri", + "description": "Lorem aute nostrud ipsum excepteur ipsum Lorem nulla eiusmod do. Eiusmod tempor nostrud commodo enim consectetur qui ut irure excepteur do duis reprehenderit amet tempor. Et in laboris exercitation anim sunt voluptate est cillum ullamco mollit reprehenderit anim ut.\r\n", + "command": "Omnigog-teriherrera@omnigog.com" + }, + { + "id": 21, + "name": "Miranda", + "description": "Ut dolore fugiat ex anim minim amet magna commodo mollit dolor excepteur reprehenderit laboris dolor. Amet consectetur anim reprehenderit do. Consectetur eu sit mollit culpa elit irure. Id exercitation ea nostrud cillum nostrud fugiat fugiat pariatur nulla culpa labore. Pariatur exercitation minim laboris cillum esse ullamco ut occaecat est. Excepteur officia officia officia consectetur tempor esse do. Nisi sit eiusmod ex anim ullamco non labore culpa.\r\n", + "command": "Comverges-mirandaherrera@comverges.com" + }, + { + "id": 22, + "name": "Hutchinson", + "description": "Et cupidatat aliquip culpa qui. Labore in sunt ut deserunt irure exercitation amet cupidatat aliqua nostrud velit sit aliquip ea. Ad aute consequat eiusmod non irure voluptate incididunt officia ipsum aliqua anim occaecat irure. Officia quis consectetur ea pariatur incididunt voluptate occaecat. Velit exercitation id mollit minim proident nostrud mollit adipisicing sint dolore.\r\n", + "command": "Zolavo-hutchinsonherrera@zolavo.com" + }, + { + "id": 23, + "name": "Woods", + "description": "Sunt sint laborum enim irure officia. Occaecat dolor velit esse adipisicing reprehenderit culpa laboris aute deserunt laboris exercitation. Aliqua elit ipsum reprehenderit laboris eu ad. Dolore mollit ullamco aute dolore dolore mollit dolore cillum incididunt aliquip qui tempor. Veniam ut labore in magna ullamco reprehenderit proident labore ea fugiat Lorem exercitation. Esse anim sint duis id eiusmod non eiusmod Lorem et. Pariatur sit cupidatat aliquip id reprehenderit minim nisi enim ipsum dolor cillum.\r\n", + "command": "Magnina-woodsherrera@magnina.com" + }, + { + "id": 24, + "name": "Minnie", + "description": "Velit elit cupidatat culpa culpa in commodo labore excepteur eu. Irure et dolore nulla adipisicing. Magna consectetur nisi do est veniam fugiat.\r\n", + "command": "Grupoli-minnieherrera@grupoli.com" + }, + { + "id": 25, + "name": "Faulkner", + "description": "Sunt aliquip quis quis consequat mollit aliqua laboris qui elit id duis et. Cupidatat officia excepteur anim non deserunt velit. Deserunt voluptate ex velit officia sit dolore minim tempor et. Et in dolor laboris dolore. Elit exercitation aliqua amet sint ut et cillum tempor proident incididunt sit. Dolore elit nisi proident anim exercitation tempor quis. Enim Lorem in laboris ipsum proident pariatur consectetur.\r\n", + "command": "Plasmosis-faulknerherrera@plasmosis.com" + }, + { + "id": 26, + "name": "Maude", + "description": "Consectetur esse Lorem id officia reprehenderit quis. Labore magna nisi deserunt sint officia cillum. Incididunt aliqua veniam officia incididunt elit mollit aliqua qui reprehenderit cillum anim. Qui tempor eu ex anim tempor amet minim dolore elit ipsum Lorem quis cupidatat. Reprehenderit velit quis aliquip Lorem incididunt.\r\n", + "command": "Jamnation-maudeherrera@jamnation.com" + }, + { + "id": 27, + "name": "Roberson", + "description": "Mollit sint aliqua veniam velit consectetur consequat. Ex fugiat labore reprehenderit in cupidatat nostrud adipisicing. Do sit nulla do nisi voluptate nostrud nostrud magna laboris.\r\n", + "command": "Kyagoro-robersonherrera@kyagoro.ru" + }, + { + "id": 28, + "name": "Debra", + "description": "Ex sit laboris duis velit culpa cupidatat nisi consectetur reprehenderit quis. Amet ea dolore irure nulla proident deserunt sint sint adipisicing pariatur sint elit. Non commodo aute Lorem cupidatat sit labore excepteur velit nostrud ut ea. Pariatur non qui sunt mollit amet veniam deserunt ullamco laborum est sint. Et minim fugiat irure dolor aliqua ut proident amet adipisicing culpa minim.\r\n", + "command": "Magneato-debraherrera@magneato.ru" + }, + { + "id": 29, + "name": "Pacheco", + "description": "Pariatur excepteur proident anim cillum dolore consectetur ipsum deserunt cupidatat cillum pariatur sunt officia laborum. Dolor voluptate culpa et elit pariatur ea. Voluptate velit ex commodo sunt exercitation ea nostrud quis elit est velit amet. Laboris est magna incididunt non sit minim culpa anim non proident occaecat irure veniam non. Ut quis laboris aliquip do tempor dolore ipsum reprehenderit deserunt deserunt. Do labore consectetur eu quis excepteur nulla anim consectetur ut dolore elit.\r\n", + "command": "Keengen-pachecoherrera@keengen.com" + }, + { + "id": 30, + "name": "Wyatt", + "description": "Commodo ex sint et amet tempor veniam culpa pariatur Lorem proident ut sint. Commodo anim occaecat anim in minim occaecat velit. Anim id id ea ex aliqua deserunt minim duis tempor ullamco voluptate esse. Duis mollit esse minim excepteur et consequat. Laborum voluptate culpa deserunt anim et nulla enim qui anim laborum anim exercitation. In dolor elit culpa velit ullamco aute anim Lorem excepteur esse magna Lorem exercitation.\r\n", + "command": "Newcube-wyattherrera@newcube.com" + }, + { + "id": 31, + "name": "Laurel", + "description": "Laborum exercitation aliqua occaecat labore. In velit ea fugiat elit eu quis sint duis elit officia irure enim proident tempor. Minim do quis mollit elit. Ipsum tempor laborum exercitation mollit ut qui tempor laboris magna fugiat qui sit aliqua quis. Elit velit aliquip ipsum esse quis anim pariatur anim irure minim. Veniam laborum id commodo et mollit Lorem aliqua laborum ipsum magna. Occaecat amet fugiat proident in.\r\n", + "command": "Escenta-laurelherrera@escenta.org" + }, + { + "id": 32, + "name": "Hopper", + "description": "Do consequat magna in quis quis. Veniam esse tempor velit ex irure. Minim reprehenderit excepteur sint mollit sit. Incididunt nisi velit minim ad anim nisi incididunt mollit culpa proident.\r\n", + "command": "Portalis-hopperherrera@portalis.com" + }, + { + "id": 33, + "name": "Guzman", + "description": "Lorem in ipsum elit eiusmod. Nostrud officia veniam voluptate culpa anim eu ex qui duis do quis ea quis aliquip. Do dolore aliqua qui enim sunt do exercitation non non esse nostrud eu. Aliqua enim consectetur sunt minim non. Eu anim sunt ipsum id aliquip labore laboris exercitation. Commodo cupidatat exercitation occaecat ea anim sint pariatur incididunt velit minim eu.\r\n", + "command": "Jimbies-guzmanherrera@jimbies.ru" + }, + { + "id": 34, + "name": "Amber", + "description": "Nostrud reprehenderit culpa veniam eu ipsum et id nostrud. Deserunt do aliquip exercitation enim ullamco incididunt fugiat in nostrud exercitation anim. Dolor cillum nisi nostrud ipsum reprehenderit excepteur quis adipisicing mollit voluptate consequat ea occaecat. Minim mollit non in fugiat magna voluptate.\r\n", + "command": "Zaggles-amberherrera@zaggles.ru" + }, + { + "id": 35, + "name": "Ruthie", + "description": "Deserunt nostrud dolor labore laboris. Elit pariatur non cillum minim veniam. Nisi incididunt cillum in consectetur enim nulla ea dolore pariatur. Velit magna in ex exercitation voluptate eiusmod velit eiusmod nisi in elit nisi cillum. Non do labore aute eiusmod ut et nulla commodo.\r\n", + "command": "Boilicon-ruthieherrera@boilicon.org" + }, + { + "id": 36, + "name": "Billie", + "description": "Consectetur minim pariatur duis dolor cupidatat laboris occaecat nisi veniam reprehenderit laborum veniam et. Officia ullamco fugiat proident cupidatat fugiat ut culpa minim sint. Elit eiusmod id labore voluptate sit tempor ea cupidatat aute voluptate anim. Do laboris dolor elit duis nulla aliquip incididunt amet duis magna dolore esse. Eiusmod eiusmod nisi enim aute aliquip do ea laborum duis voluptate nostrud deserunt officia.\r\n", + "command": "Geekko-billieherrera@geekko.com" + }, + { + "id": 37, + "name": "Lindsay", + "description": "Ea laboris exercitation tempor amet mollit in ea excepteur ut excepteur quis. Sit ipsum ipsum ullamco aliquip anim cillum veniam. Do incididunt excepteur duis ullamco aute quis elit minim ipsum.\r\n", + "command": "Rocklogic-lindsayherrera@rocklogic.org" + }, + { + "id": 38, + "name": "Mercer", + "description": "Mollit labore aliqua tempor pariatur veniam duis enim laborum. Ipsum pariatur ut do ullamco velit ea nisi adipisicing ullamco est ea elit laboris pariatur. Ex consequat do deserunt pariatur mollit ut ipsum incididunt aute sit proident reprehenderit eu.\r\n", + "command": "Satiance-mercerherrera@satiance.org" + }, + { + "id": 39, + "name": "Benson", + "description": "Proident est aliqua eu nisi tempor ut veniam irure. Sunt sit ex enim elit consequat eu officia cillum occaecat esse. Lorem id magna et veniam et ullamco adipisicing nostrud irure Lorem. Velit proident occaecat eiusmod ullamco nisi commodo. Amet velit ex commodo qui id exercitation amet fugiat reprehenderit velit magna consequat quis. Laborum minim exercitation esse veniam ad do qui duis mollit adipisicing voluptate nostrud cupidatat cupidatat. Reprehenderit ipsum ex nostrud do ex sit excepteur mollit aute.\r\n", + "command": "Lyria-bensonherrera@lyria.com" + }, + { + "id": 40, + "name": "Melisa", + "description": "Mollit esse sunt ullamco excepteur cillum non cupidatat laborum consequat enim ea. Amet quis incididunt cupidatat incididunt excepteur voluptate sit. Culpa proident laboris occaecat fugiat labore id eu aute magna ea irure commodo laborum voluptate. Ut excepteur esse sint culpa reprehenderit sit. Officia tempor elit dolore ex sint culpa. Nostrud ipsum et dolore aliquip magna laboris aliquip commodo qui consequat in commodo amet. Velit proident non esse reprehenderit excepteur consectetur eu irure.\r\n", + "command": "Genmy-melisaherrera@genmy.com" + }, + { + "id": 41, + "name": "Amber", + "description": "Et tempor cupidatat consequat veniam minim labore quis duis incididunt quis anim. Ut incididunt fugiat pariatur ipsum occaecat dolore ea aute id nisi commodo. Amet eiusmod proident dolore Lorem. Ut consequat officia sint nostrud est in occaecat esse.\r\n", + "command": "Asimiline-ellenherrera@asimiline.org" + }, + { + "id": 42, + "name": "Jocelyn", + "description": "Est anim mollit aliqua qui est cillum. Est voluptate eu laboris ea adipisicing occaecat nostrud aliquip exercitation cillum deserunt sit cillum. Cillum sunt fugiat sit veniam dolore velit minim cillum occaecat ut ex aliqua ipsum.\r\n", + "command": "Insurety-jocelynherrera@insurety.org" + }, + { + "id": 43, + "name": "Mccullough", + "description": "Ullamco aliqua et id et minim. Est amet deserunt adipisicing cupidatat ipsum quis tempor. Irure deserunt amet aliquip officia id. Quis deserunt dolore aute quis aliqua irure voluptate ex qui ad. Sint fugiat do nisi sunt ad laborum officia reprehenderit. Non commodo velit amet enim mollit occaecat tempor quis eiusmod ipsum ex commodo sunt.\r\n", + "command": "Straloy-mcculloughherrera@straloy.com" + }, + { + "id": 44, + "name": "Lisa", + "description": "Nostrud fugiat in commodo ex tempor. Veniam excepteur mollit eiusmod ut labore quis sint qui nisi. Voluptate elit labore nostrud est pariatur do amet commodo consectetur.\r\n", + "command": "Flyboyz-lisaherrera@flyboyz.com" + }, + { + "id": 45, + "name": "Abigail", + "description": "Deserunt nisi irure ex culpa ut eu commodo tempor proident qui. Magna nostrud amet qui ipsum et ipsum. Est cillum ullamco voluptate labore laborum quis ut et consectetur reprehenderit sint. Sint nostrud fugiat consectetur mollit nisi excepteur veniam excepteur tempor adipisicing dolor amet. Proident commodo exercitation dolore reprehenderit quis quis in aliquip est veniam sit quis ipsum nisi. Laborum ut pariatur in enim cupidatat enim deserunt adipisicing irure ut tempor non consequat aute. Ea cupidatat excepteur sunt eiusmod.\r\n", + "command": "Overfork-abigailherrera@overfork.com" + }, + { + "id": 46, + "name": "Estrada", + "description": "Aute qui nisi adipisicing qui ad laborum sit duis deserunt sint in consequat. Dolor irure mollit eu nostrud officia. Sit amet irure ea nisi in mollit amet ad laborum ea nulla ex tempor. Voluptate labore est exercitation proident ullamco magna magna pariatur. Fugiat enim eu officia ut ullamco quis officia proident commodo. Laborum incididunt consectetur eiusmod nulla qui. Anim commodo eiusmod sunt fugiat ea amet ex et in pariatur mollit mollit.\r\n", + "command": "Typhonica-estradaherrera@typhonica.com" + }, + { + "id": 47, + "name": "Amber", + "description": "Aute quis cillum id nostrud labore duis aliquip duis commodo officia irure amet Lorem. Esse voluptate amet cillum adipisicing amet esse nulla deserunt. Aliquip eu quis pariatur reprehenderit et occaecat excepteur ut do consequat consequat reprehenderit et. Aliqua minim exercitation consectetur laboris enim minim ex voluptate quis. Laboris pariatur dolore eiusmod Lorem nulla tempor reprehenderit quis nisi enim aute. Non velit sit pariatur officia minim laboris minim irure sunt irure ea ex anim culpa.\r\n", + "command": "Vinch-susanaherrera@vinch.com" + }, + { + "id": 48, + "name": "Joseph", + "description": "Elit et exercitation et officia ad mollit consectetur id laborum consectetur adipisicing sint quis. Aliqua deserunt eiusmod voluptate ex. Ipsum nulla aliquip occaecat in minim do in adipisicing sunt in minim.\r\n", + "command": "Jumpstack-josephherrera@jumpstack.ru" + }, + { + "id": 49, + "name": "Johnnie", + "description": "Aliqua Lorem in ea ea duis. Consectetur ex laborum non nulla enim magna elit qui exercitation commodo consectetur. Consequat irure mollit veniam consectetur tempor enim aliquip amet nulla aliquip excepteur reprehenderit. Reprehenderit excepteur eu nulla cillum excepteur nulla velit consequat anim. Amet ut aute et ut aliqua. Consequat nostrud duis in eiusmod quis consectetur ad nostrud nostrud do exercitation occaecat dolore qui. Tempor sint ad do consequat.\r\n", + "command": "Biohab-johnnieherrera@biohab.ru" + }, + { + "id": 50, + "name": "Amber", + "description": "Aliquip aliqua nostrud veniam est. Laborum ut et nostrud proident qui proident. Esse quis amet in culpa sint tempor proident consectetur velit incididunt reprehenderit. Eu sint exercitation qui do ullamco.\r\n", + "command": "Luxuria-estesherrera@luxuria.ru" + } +] \ No newline at end of file diff --git a/flask__webservers/filter-sort-paginate-on-server/main.py b/flask__webservers/filter-sort-paginate-on-server/main.py new file mode 100644 index 000000000..314282a7a --- /dev/null +++ b/flask__webservers/filter-sort-paginate-on-server/main.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +# NOTE: https://datatables.net/manual/server-side + + +import json +import re +from functools import cmp_to_key +from pathlib import Path +from typing import Any + +from flask import Flask, render_template, jsonify, Response, request + +# pip install querystring-parser==1.2.4 +from querystring_parser import parser + + +def cmp(a: Any, b: Any) -> int: + return (a > b) - (a < b) + + +ITEMS = json.load(open("items.json", encoding="utf-8")) + + +app = Flask(__name__) + + +@app.route("/") +def index() -> str: + return render_template( + "index.html", + title=Path(__file__).resolve().parent.name, + ) + + +@app.route("/api/get_items") +def api_get_items() -> Response: + args: dict[str, Any] = parser.parse(request.query_string) + + search_value: str = args["search"]["value"] + has_search_regex: str = args["search"]["regex"] == "true" + + # TODO: Фильтрация может быть по отдельным столбцам в поле search + # { + # 0: {'data': 'id', 'name': 'id', 'searchable': 'true', 'orderable': 'true', 'search': {'value': '', 'regex': 'false'}}, + # 1: {'data': 'name', 'name': 'name', 'searchable': 'true', 'orderable': 'true', 'search': {'value': '', 'regex': 'false'}}, + # 2: {'data': 'description', 'name': 'description', 'searchable': 'true', 'orderable': 'true', 'search': {'value': '', 'regex': 'false'}}, + # 3: {'data': 'command', 'name': 'command', 'searchable': 'true', 'orderable': 'true', 'search': {'value': '', 'regex': 'false'}} + # } + # columns: dict[int, dict[str, str]] = args["columns"] + print("columns:", args["columns"]) + + filtered_items: list[dict[str, Any]] = [] + for item in ITEMS: + if search_value: + values: list[str] = [str(x).lower() for x in item.values()] + if not any( + ( + re.search(search_value, value) + if has_search_regex + else search_value.lower() in value + ) + for value in values + ): + continue + + filtered_items.append(item) + + number_of_filtered = len(filtered_items) + + order: dict[int, dict[str, str]] = args.get("order", dict()) + print("order:", order) + if order: + def cmp2(item1: dict[str, str], item2: dict[str, str]) -> int: + # NOTE: https://stackoverflow.com/a/62381089/5909792 + total_result: int | None = None + for order_column in order.values(): + name: str = order_column["name"] + result = cmp(item1[name], item2[name]) + if order_column["dir"] == "desc": + result = -result + + if total_result is None: + total_result = result + else: + total_result = total_result or result + + return total_result + + filtered_items.sort(key=cmp_to_key(cmp2)) + + start: int = int(args["start"]) + length: int = int(args["length"]) + if length > 0: + filtered_items = filtered_items[start : start + length] + + return jsonify( + { + "draw": int(args["draw"]), + "recordsTotal": len(ITEMS), + "recordsFiltered": number_of_filtered, + "data": filtered_items, + } + ) + + +if __name__ == "__main__": + app.run() diff --git a/flask__webservers/filter-sort-paginate-on-server/static/index.js b/flask__webservers/filter-sort-paginate-on-server/static/index.js new file mode 100644 index 000000000..498ec7edf --- /dev/null +++ b/flask__webservers/filter-sort-paginate-on-server/static/index.js @@ -0,0 +1,22 @@ +$(function() { + new DataTable('table', { + ajax: 'api/get_items', + rowId: 'id', + serverSide: true, + processing: true, + lengthMenu: [ + [5, 10, 25, 50, -1], + ["5 records", "10 records", "25 records", "50 records", "All records"] + ], + columns: [ + { name: 'id', data: 'id', title: 'Id', }, + { name: 'name', data: 'name', title: 'Name' }, + { name: 'description', data: 'description', title: 'Description', }, + { name: 'command', data: 'command', title: 'Command', } + ], + order: [ + // Сортировка по убыванию id + [0, "desc"], + ], + }); +}); diff --git a/flask__webservers/filter-sort-paginate-on-server/templates/index.html b/flask__webservers/filter-sort-paginate-on-server/templates/index.html new file mode 100644 index 000000000..911c791f4 --- /dev/null +++ b/flask__webservers/filter-sort-paginate-on-server/templates/index.html @@ -0,0 +1,18 @@ + + + + + + + {{ title }} + + + + + + +
+ + + + diff --git a/flask__webservers/flask_debugtoolbar__examples/app.py b/flask__webservers/flask_debugtoolbar__examples/app.py index 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/hello_world_with_compress.py b/flask__webservers/hello_world_with_compress.py new file mode 100644 index 000000000..4c7185f22 --- /dev/null +++ b/flask__webservers/hello_world_with_compress.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import logging + +from flask import Flask + +# pip install flask-compress +from flask_compress import Compress + + +app = Flask(__name__) +Compress(app) + +logging.basicConfig(level=logging.DEBUG) + + +@app.route("/") +def index(): + return "Hello World!" * 100 + + +if __name__ == "__main__": + app.debug = True + + # Localhost + # port=0 -- random free port + # app.run(port=0) + app.run(port=5001) + + # # Public IP + # app.run(host='0.0.0.0') diff --git a/flask__webservers/logging__examples/log-config.yaml b/flask__webservers/logging__examples/log-config.yaml new file mode 100644 index 000000000..b427173a0 --- /dev/null +++ b/flask__webservers/logging__examples/log-config.yaml @@ -0,0 +1,27 @@ +version: 1 + +formatters: + default: + format: "[%(asctime)s] %(filename)s[LINE:%(lineno)d] %(levelname)-8s %(message)s" + +handlers: + console: + class: "logging.StreamHandler" + formatter: "default" + stream: "ext://sys.stdout" + + web-server-file: + class: "logging.handlers.RotatingFileHandler" + formatter: "default" + filename: "main_new.log" + encoding: "utf-8" + backupCount: 5 + maxBytes: 10000000 + delay: true + +loggers: + web-server: &web-server + handlers: ["console", "web-server-file"] + level: "DEBUG" + + werkzeug: *web-server diff --git a/flask__webservers/logging__examples/main_new.py b/flask__webservers/logging__examples/main_new.py new file mode 100644 index 000000000..8000e0e63 --- /dev/null +++ b/flask__webservers/logging__examples/main_new.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import logging.config +from pathlib import Path +from typing import Any + +# pip install PyYAML +import yaml + +from flask import Flask + + +DIR: Path = Path(__file__).resolve().parent + +DIR_LOGS: Path = DIR / "logs" +DIR_LOGS.mkdir(parents=True, exist_ok=True) + +CONFIG_LOG_FILE_NAME: Path = DIR / "log-config.yaml" + + +LOGGING: dict[str, Any] = yaml.safe_load( + CONFIG_LOG_FILE_NAME.read_text("utf-8") +) +for handler in LOGGING["handlers"].values(): + try: + handler["filename"] = DIR_LOGS / handler["filename"] + except KeyError: + pass + +logging.config.dictConfig(LOGGING) + +log = logging.getLogger("web-server") + + +app = Flask(__name__) +app.logger = log + + +@app.route("/") +def index() -> str: + log.debug("call index") + return "Hello World!" + + +if __name__ == "__main__": + app.debug = True + + app.run(host="0.0.0.0", port=5000) diff --git a/flask__webservers/logging__examples/main_old.py b/flask__webservers/logging__examples/main_old.py new file mode 100644 index 000000000..e5bdab429 --- /dev/null +++ b/flask__webservers/logging__examples/main_old.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import logging +import sys +from logging.handlers import RotatingFileHandler +from pathlib import Path + +from flask import Flask + + +DIR_LOGS: Path = Path(__file__).resolve().parent / "logs" +DIR_LOGS.mkdir(parents=True, exist_ok=True) + + +app = Flask(__name__) + +formatter = logging.Formatter( + "[%(asctime)s] %(filename)s:%(lineno)d %(levelname)-8s %(message)s" +) + +file_handler = RotatingFileHandler( + DIR_LOGS / "main_old.log", maxBytes=10_000_000, backupCount=5, encoding="utf-8" +) +file_handler.setFormatter(formatter) + +stream_handler = logging.StreamHandler(stream=sys.stdout) +stream_handler.setFormatter(formatter) + +log: logging.Logger = app.logger +log.handlers.clear() +log.setLevel(logging.DEBUG) +log.addHandler(file_handler) +log.addHandler(stream_handler) + +log_werkzeug = logging.getLogger("werkzeug") +log_werkzeug.setLevel(logging.DEBUG) +log_werkzeug.addHandler(file_handler) +log_werkzeug.addHandler(stream_handler) + + +@app.route("/") +def index() -> str: + log.debug("call index") + return "Hello World!" + + +if __name__ == "__main__": + app.debug = True + + app.run(host="0.0.0.0", port=5000) diff --git a/flask__webservers/logging__remove_date_from_werkzeug_logs/log-config.yaml b/flask__webservers/logging__remove_date_from_werkzeug_logs/log-config.yaml new file mode 100644 index 000000000..90c089ccd --- /dev/null +++ b/flask__webservers/logging__remove_date_from_werkzeug_logs/log-config.yaml @@ -0,0 +1,30 @@ +version: 1 + +formatters: + default: + format: "[%(asctime)s] %(filename)s[LINE:%(lineno)d] %(levelname)-8s %(message)s" + +handlers: + console: + class: "logging.StreamHandler" + formatter: "default" + stream: "ext://sys.stdout" + + web-server-file: + class: "logging.handlers.RotatingFileHandler" + formatter: "default" + filename: "main_new.log" + encoding: "utf-8" + backupCount: 5 + maxBytes: 10000000 + delay: true + +filters: + filter_remove_date_from_werkzeug_logs: + (): utils.FilterRemoveDateFromWerkzeugLogs + +loggers: + werkzeug: + handlers: ["console", "web-server-file"] + level: "DEBUG" + filters: ["filter_remove_date_from_werkzeug_logs"] diff --git a/flask__webservers/logging__remove_date_from_werkzeug_logs/main_new.py b/flask__webservers/logging__remove_date_from_werkzeug_logs/main_new.py new file mode 100644 index 000000000..6c2e83aec --- /dev/null +++ b/flask__webservers/logging__remove_date_from_werkzeug_logs/main_new.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import logging.config +from pathlib import Path +from typing import Any + +# pip install PyYAML +import yaml + +from flask import Flask + + +DIR: Path = Path(__file__).resolve().parent + +DIR_LOGS: Path = DIR / "logs" +DIR_LOGS.mkdir(parents=True, exist_ok=True) + +CONFIG_LOG_FILE_NAME: Path = DIR / "log-config.yaml" + + +LOGGING: dict[str, Any] = yaml.safe_load( + CONFIG_LOG_FILE_NAME.read_text("utf-8") +) +for handler in LOGGING["handlers"].values(): + try: + handler["filename"] = DIR_LOGS / handler["filename"] + except KeyError: + pass + +logging.config.dictConfig(LOGGING) + +log = logging.getLogger("werkzeug") + + +app = Flask(__name__) +app.logger = log + + +@app.route("/") +def index() -> str: + log.debug("call index") + return "Hello World!" + + +if __name__ == "__main__": + app.debug = True + + app.run(host="0.0.0.0", port=5000) diff --git a/flask__webservers/logging__remove_date_from_werkzeug_logs/main_old.py b/flask__webservers/logging__remove_date_from_werkzeug_logs/main_old.py new file mode 100644 index 000000000..7b7c003bb --- /dev/null +++ b/flask__webservers/logging__remove_date_from_werkzeug_logs/main_old.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import logging +import sys + +from logging.handlers import RotatingFileHandler +from pathlib import Path + +from flask import Flask + +from utils import FilterRemoveDateFromWerkzeugLogs + + +DIR_LOGS: Path = Path(__file__).resolve().parent / "logs" +DIR_LOGS.mkdir(parents=True, exist_ok=True) + + +app = Flask(__name__) + +formatter = logging.Formatter( + "[%(asctime)s] %(filename)s:%(lineno)d %(levelname)-8s %(message)s" +) + +file_handler = RotatingFileHandler( + DIR_LOGS / "main_old.log", maxBytes=10_000_000, backupCount=5, encoding="utf-8" +) +file_handler.setFormatter(formatter) + +stream_handler = logging.StreamHandler(stream=sys.stdout) +stream_handler.setFormatter(formatter) + +log: logging.Logger = app.logger +log.handlers.clear() +log.setLevel(logging.DEBUG) +log.addHandler(file_handler) +log.addHandler(stream_handler) + +log_werkzeug = logging.getLogger("werkzeug") +log_werkzeug.setLevel(logging.DEBUG) +log_werkzeug.addHandler(file_handler) +log_werkzeug.addHandler(stream_handler) +log_werkzeug.addFilter(FilterRemoveDateFromWerkzeugLogs()) + + +@app.route("/") +def index() -> str: + log.debug("call index") + return "Hello World!" + + +if __name__ == "__main__": + app.debug = True + + app.run(host="0.0.0.0", port=5000) diff --git a/flask__webservers/logging__remove_date_from_werkzeug_logs/utils.py b/flask__webservers/logging__remove_date_from_werkzeug_logs/utils.py new file mode 100644 index 000000000..531a2deba --- /dev/null +++ b/flask__webservers/logging__remove_date_from_werkzeug_logs/utils.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import logging +import re + + +# NOTE: Fix https://github.com/pallets/werkzeug/blob/72b2e48e7d44927b1b7d6b2f940d0691230de893/src/werkzeug/serving.py#L425C38-L425C44 +class FilterRemoveDateFromWerkzeugLogs(logging.Filter): + # '192.168.0.102 - - [30/Jun/2024 01:14:03] "%s" %s %s' -> '192.168.0.102 - "%s" %s %s' + pattern: re.Pattern = re.compile(r' - - \[.+?] "') + + def filter(self, record: logging.LogRecord) -> bool: + record.msg = self.pattern.sub(' - "', record.msg) + return True diff --git a/flask__webservers/post_data.py b/flask__webservers/post_data.py index 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 new file mode 100644 index 000000000..7f8fb28af --- /dev/null +++ b/flask__webservers/users/main_with_db.py @@ -0,0 +1,216 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +# SOURCE: https://www.geeksforgeeks.org/how-to-add-authentication-to-your-app-with-flask-login/ + + +import os + +from typing import Optional + +# pip install flask==3.0.0 +import flask + +# pip install flask-login==0.6.2 +import flask_login + +# pip install flask-sqlalchemy==3.1.1 +# TODO: Bug fix for flask-login==0.6.2 - flask-sqlalchemy installed/updated Werkzeug +# pip install Werkzeug==2.3.7 +from flask_sqlalchemy import SQLAlchemy + + +# TODO: Убрать дублирование кода в шаблонах +# TODO: Не хранить пароль в чистом виде +# TODO: Сделать вариант примера с peewee + + +app = flask.Flask(__name__) +app.secret_key = "super secret string" # Change this! +app.config["SQLALCHEMY_DATABASE_URI"] = os.environ.get("DATABASE_URI", "sqlite:///db.sqlite") + +db = SQLAlchemy() + +login_manager = flask_login.LoginManager() +login_manager.init_app(app) + + +class User(db.Model, flask_login.UserMixin): + id = db.Column(db.Integer, primary_key=True) + username = db.Column(db.String(250), unique=True, nullable=False) + password = db.Column(db.String(250), nullable=False) + + @classmethod + def get_by(cls, username: str) -> Optional["User"]: + return cls.query.filter_by( + username=username + ).first() + + +db.init_app(app) + +with app.app_context(): + db.create_all() + + +@login_manager.user_loader +def loader_user(user_id: int): + return db.session.get(User, user_id) + + +@app.route("/") +def index(): + return flask.render_template_string(""" + + + + + + + Home + + + + + {% if current_user.is_authenticated %} +

You are logged as {{ current_user.username }}

+ {% else %} +

You are not logged in

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

Create an account

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

Login to your account

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

You are logged as {{ current_user.id }}

+ Protected + {% else %} +

You are not logged in

+ {% endif %} + + + """ + ) + + +@app.route("/login", methods=["GET", "POST"]) +def login(): + if flask.request.method == "GET": + return """ +
+ + + +
+ """ + + email = flask.request.form["email"] + if email in USERS and flask.request.form["password"] == USERS[email]["password"]: + user = User.create(email) + flask_login.login_user(user) + return flask.redirect(flask.url_for("index")) + + return "Bad login" + + +@app.route("/protected") +@flask_login.login_required +def protected() -> str: + return f"Logged in as: {flask_login.current_user.id}" + + +@app.route("/logout") +@flask_login.login_required +def logout(): + flask_login.logout_user() + return flask.redirect(flask.url_for("index")) + + +@login_manager.unauthorized_handler +def unauthorized_handler(): + return "Unauthorized", 401 + + +if __name__ == "__main__": + app.debug = True + app.run(port=10101) diff --git a/flask__websocket/ajax_vs_websocket/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 new file mode 100644 index 000000000..fce7334c0 --- /dev/null +++ b/flask__websocket/sending_all_from_while_in_thread/main.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__author__ = "ipetrash" + + +import time + +from datetime import datetime +from threading import Thread + +from flask import Flask, render_template, session, request + +# pip install flask-socketio==5.3.6 +from flask_socketio import SocketIO, emit + + +# Set this variable to "threading", "eventlet" or "gevent" to test the +# different async modes, or leave it set to None for the application to choose +# the best option based on installed packages. +async_mode = None + +app = Flask(__name__) +app.config["SECRET_KEY"] = "secret!" +socketio = SocketIO(app, async_mode=async_mode) + + +def send_all_cycled() -> None: + with app.app_context(): + i = 0 + while True: + i += 1 + emit( + "my_response_all", + {"data": f"#{i}. {datetime.now().isoformat()}"}, + broadcast=True, # Send all clients + namespace="/", + ) + time.sleep(1) + + +thread = Thread(target=send_all_cycled) +thread.daemon = True +thread.start() + + +@app.route("/") +def index(): + return render_template("index.html") + + +@socketio.on("my_event") +def test_message(message) -> None: + session["receive_count"] = session.get("receive_count", 0) + 1 + print(message) + + response = message["data"] + emit( + "my_response", + {"data": response, "count": session["receive_count"], "sid": request.sid}, + broadcast=True, # Send all clients + ) + + +@socketio.on("my_ping") +def ping_pong() -> None: + emit("my_pong") + + +@socketio.on("connect") +def test_connect() -> None: + print("Client connected", request.sid) + emit("my_response", {"data": f"Connected!", "count": 0, "sid": request.sid}) + + +@socketio.on("disconnect") +def test_disconnect() -> None: + print("Client disconnected", request.sid) + + +if __name__ == "__main__": + # HOST = '127.0.0.1' + # HOST = "0.0.0.0" + PORT = 12000 + # print(f"http://{HOST}:{PORT}") + + socketio.run( + app, + # host=HOST, + port=PORT, + allow_unsafe_werkzeug=True, + ) diff --git a/flask__websocket/sending_all_from_while_in_thread/static/index.js b/flask__websocket/sending_all_from_while_in_thread/static/index.js new file mode 100644 index 000000000..d94c761e9 --- /dev/null +++ b/flask__websocket/sending_all_from_while_in_thread/static/index.js @@ -0,0 +1,62 @@ +$(document).ready(function() { + // Connect to the Socket.IO server. + var socket = io(); + + // Event handler for new connections. + // The callback function is invoked when a connection with the + // server is established. + socket.on('connect', function() { + socket.emit('my_event', {data: "I'm connected!"}); + }); + + // Event handler for server sent data. + // The callback function is invoked whenever the server emits data + // to the client. The data is then displayed in the "Received" + // section of the page. + socket.on('my_response', function(msg, cb) { + console.log(msg); + $('#log').prepend( + '
' + $('
').text(`Received #${msg.count}: ${msg.data} from #${msg.sid}`).html() + ); + if (cb) + cb(); + }); + + socket.on('my_response_all', function(msg, cb) { + console.log(msg); + $('#server_data').text(msg.data); + if (cb) + cb(); + }); + + // Interval function that tests message latency by sending a "ping" + // message. The server then responds with a "pong" message and the + // round trip time is measured. + var ping_pong_times = []; + var start_time; + window.setInterval(function() { + start_time = (new Date).getTime(); + socket.emit('my_ping'); + }, 1000); + + // Handler for the "pong" message. When the pong is received, the + // time from the ping is stored, and the average of the last 30 + // samples is average and displayed. + socket.on('my_pong', function() { + var latency = (new Date).getTime() - start_time; + ping_pong_times.push(latency); + ping_pong_times = ping_pong_times.slice(-30); // keep last 30 samples + var sum = 0; + for (var i = 0; i < ping_pong_times.length; i++) + sum += ping_pong_times[i]; + $('#ping-pong').text(Math.round(10 * sum / ping_pong_times.length) / 10); + }); + + // Handlers for the different forms in the page. + // These accept data from the user and send it to the server in a + // variety of ways + $('form#emit').submit(function(event) { + socket.emit('my_event', {data: $('#emit_data').val()}); + return false; + }); +}); \ No newline at end of file diff --git a/flask__websocket/sending_all_from_while_in_thread/static/jquery.js b/flask__websocket/sending_all_from_while_in_thread/static/jquery.js new file mode 100644 index 000000000..d4b67f7e6 --- /dev/null +++ b/flask__websocket/sending_all_from_while_in_thread/static/jquery.js @@ -0,0 +1,10308 @@ +/*! + * jQuery JavaScript Library v1.11.1 + * http://jquery.com/ + * + * Includes Sizzle.js + * http://sizzlejs.com/ + * + * Copyright 2005, 2014 jQuery Foundation, Inc. and other contributors + * Released under the MIT license + * http://jquery.org/license + * + * Date: 2014-05-01T17:42Z + */ + +(function( global, factory ) { + + if ( typeof module === "object" && typeof module.exports === "object" ) { + // For CommonJS and CommonJS-like environments where a proper window is present, + // execute the factory and get jQuery + // For environments that do not inherently posses a window with a document + // (such as Node.js), expose a jQuery-making factory as module.exports + // This accentuates the need for the creation of a real window + // e.g. var jQuery = require("jquery")(window); + // See ticket #14549 for more info + module.exports = global.document ? + factory( global, true ) : + function( w ) { + if ( !w.document ) { + throw new Error( "jQuery requires a window with a document" ); + } + return factory( w ); + }; + } else { + factory( global ); + } + +// Pass this if window is not defined yet +}(typeof window !== "undefined" ? window : this, function( window, noGlobal ) { + +// Can't do this because several apps including ASP.NET trace +// the stack via arguments.caller.callee and Firefox dies if +// you try to trace through "use strict" call chains. (#13335) +// Support: Firefox 18+ +// + +var deletedIds = []; + +var slice = deletedIds.slice; + +var concat = deletedIds.concat; + +var push = deletedIds.push; + +var indexOf = deletedIds.indexOf; + +var class2type = {}; + +var toString = class2type.toString; + +var hasOwn = class2type.hasOwnProperty; + +var support = {}; + + + +var + version = "1.11.1", + + // Define a local copy of jQuery + jQuery = function( selector, context ) { + // The jQuery object is actually just the init constructor 'enhanced' + // Need init if jQuery is called (just allow error to be thrown if not included) + return new jQuery.fn.init( selector, context ); + }, + + // Support: Android<4.1, IE<9 + // Make sure we trim BOM and NBSP + rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, + + // Matches dashed string for camelizing + rmsPrefix = /^-ms-/, + rdashAlpha = /-([\da-z])/gi, + + // Used by jQuery.camelCase as callback to replace() + fcamelCase = function( all, letter ) { + return letter.toUpperCase(); + }; + +jQuery.fn = jQuery.prototype = { + // The current version of jQuery being used + jquery: version, + + constructor: jQuery, + + // Start with an empty selector + selector: "", + + // The default length of a jQuery object is 0 + length: 0, + + toArray: function() { + return slice.call( this ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + return num != null ? + + // Return just the one element from the set + ( num < 0 ? this[ num + this.length ] : this[ num ] ) : + + // Return all the elements in a clean array + slice.call( this ); + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems ) { + + // Build a new jQuery matched element set + var ret = jQuery.merge( this.constructor(), elems ); + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + ret.context = this.context; + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + // (You can seed the arguments with an array of args, but this is + // only used internally.) + each: function( callback, args ) { + return jQuery.each( this, callback, args ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map(this, function( elem, i ) { + return callback.call( elem, i, elem ); + })); + }, + + slice: function() { + return this.pushStack( slice.apply( this, arguments ) ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + eq: function( i ) { + var len = this.length, + j = +i + ( i < 0 ? len : 0 ); + return this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] ); + }, + + end: function() { + return this.prevObject || this.constructor(null); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: push, + sort: deletedIds.sort, + splice: deletedIds.splice +}; + +jQuery.extend = jQuery.fn.extend = function() { + var src, copyIsArray, copy, name, options, clone, + target = arguments[0] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + + // skip the boolean and the target + target = arguments[ i ] || {}; + i++; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !jQuery.isFunction(target) ) { + target = {}; + } + + // extend jQuery itself if only one argument is passed + if ( i === length ) { + target = this; + i--; + } + + for ( ; i < length; i++ ) { + // Only deal with non-null/undefined values + if ( (options = arguments[ i ]) != null ) { + // Extend the base object + for ( name in options ) { + src = target[ name ]; + copy = options[ name ]; + + // Prevent never-ending loop + if ( target === copy ) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) { + if ( copyIsArray ) { + copyIsArray = false; + clone = src && jQuery.isArray(src) ? src : []; + + } else { + clone = src && jQuery.isPlainObject(src) ? src : {}; + } + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend({ + // Unique for each copy of jQuery on the page + expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), + + // Assume jQuery is ready without the ready module + isReady: true, + + error: function( msg ) { + throw new Error( msg ); + }, + + noop: function() {}, + + // See test/unit/core.js for details concerning isFunction. + // Since version 1.3, DOM methods and functions like alert + // aren't supported. They return false on IE (#2968). + isFunction: function( obj ) { + return jQuery.type(obj) === "function"; + }, + + isArray: Array.isArray || function( obj ) { + return jQuery.type(obj) === "array"; + }, + + isWindow: function( obj ) { + /* jshint eqeqeq: false */ + return obj != null && obj == obj.window; + }, + + isNumeric: function( obj ) { + // parseFloat NaNs numeric-cast false positives (null|true|false|"") + // ...but misinterprets leading-number strings, particularly hex literals ("0x...") + // subtraction forces infinities to NaN + return !jQuery.isArray( obj ) && obj - parseFloat( obj ) >= 0; + }, + + isEmptyObject: function( obj ) { + var name; + for ( name in obj ) { + return false; + } + return true; + }, + + isPlainObject: function( obj ) { + var key; + + // Must be an Object. + // Because of IE, we also have to check the presence of the constructor property. + // Make sure that DOM nodes and window objects don't pass through, as well + if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) { + return false; + } + + try { + // Not own constructor property must be Object + if ( obj.constructor && + !hasOwn.call(obj, "constructor") && + !hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) { + return false; + } + } catch ( e ) { + // IE8,9 Will throw exceptions on certain host objects #9897 + return false; + } + + // Support: IE<9 + // Handle iteration over inherited properties before own properties. + if ( support.ownLast ) { + for ( key in obj ) { + return hasOwn.call( obj, key ); + } + } + + // Own properties are enumerated firstly, so to speed up, + // if last one is own, then all properties are own. + for ( key in obj ) {} + + return key === undefined || hasOwn.call( obj, key ); + }, + + type: function( obj ) { + if ( obj == null ) { + return obj + ""; + } + return typeof obj === "object" || typeof obj === "function" ? + class2type[ toString.call(obj) ] || "object" : + typeof obj; + }, + + // Evaluates a script in a global context + // Workarounds based on findings by Jim Driscoll + // http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context + globalEval: function( data ) { + if ( data && jQuery.trim( data ) ) { + // We use execScript on Internet Explorer + // We use an anonymous function so that context is window + // rather than jQuery in Firefox + ( window.execScript || function( data ) { + window[ "eval" ].call( window, data ); + } )( data ); + } + }, + + // Convert dashed to camelCase; used by the css and data modules + // Microsoft forgot to hump their vendor prefix (#9572) + camelCase: function( string ) { + return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); + }, + + nodeName: function( elem, name ) { + return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); + }, + + // args is for internal usage only + each: function( obj, callback, args ) { + var value, + i = 0, + length = obj.length, + isArray = isArraylike( obj ); + + if ( args ) { + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback.apply( obj[ i ], args ); + + if ( value === false ) { + break; + } + } + } else { + for ( i in obj ) { + value = callback.apply( obj[ i ], args ); + + if ( value === false ) { + break; + } + } + } + + // A special, fast, case for the most common use of each + } else { + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback.call( obj[ i ], i, obj[ i ] ); + + if ( value === false ) { + break; + } + } + } else { + for ( i in obj ) { + value = callback.call( obj[ i ], i, obj[ i ] ); + + if ( value === false ) { + break; + } + } + } + } + + return obj; + }, + + // Support: Android<4.1, IE<9 + trim: function( text ) { + return text == null ? + "" : + ( text + "" ).replace( rtrim, "" ); + }, + + // results is for internal usage only + makeArray: function( arr, results ) { + var ret = results || []; + + if ( arr != null ) { + if ( isArraylike( Object(arr) ) ) { + jQuery.merge( ret, + typeof arr === "string" ? + [ arr ] : arr + ); + } else { + push.call( ret, arr ); + } + } + + return ret; + }, + + inArray: function( elem, arr, i ) { + var len; + + if ( arr ) { + if ( indexOf ) { + return indexOf.call( arr, elem, i ); + } + + len = arr.length; + i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0; + + for ( ; i < len; i++ ) { + // Skip accessing in sparse arrays + if ( i in arr && arr[ i ] === elem ) { + return i; + } + } + } + + return -1; + }, + + merge: function( first, second ) { + var len = +second.length, + j = 0, + i = first.length; + + while ( j < len ) { + first[ i++ ] = second[ j++ ]; + } + + // Support: IE<9 + // Workaround casting of .length to NaN on otherwise arraylike objects (e.g., NodeLists) + if ( len !== len ) { + while ( second[j] !== undefined ) { + first[ i++ ] = second[ j++ ]; + } + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, invert ) { + var callbackInverse, + matches = [], + i = 0, + length = elems.length, + callbackExpect = !invert; + + // Go through the array, only saving the items + // that pass the validator function + for ( ; i < length; i++ ) { + callbackInverse = !callback( elems[ i ], i ); + if ( callbackInverse !== callbackExpect ) { + matches.push( elems[ i ] ); + } + } + + return matches; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var value, + i = 0, + length = elems.length, + isArray = isArraylike( elems ), + ret = []; + + // Go through the array, translating each of the items to their new values + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + + // Go through every key on the object, + } else { + for ( i in elems ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + } + + // Flatten any nested arrays + return concat.apply( [], ret ); + }, + + // A global GUID counter for objects + guid: 1, + + // Bind a function to a context, optionally partially applying any + // arguments. + proxy: function( fn, context ) { + var args, proxy, tmp; + + if ( typeof context === "string" ) { + tmp = fn[ context ]; + context = fn; + fn = tmp; + } + + // Quick check to determine if target is callable, in the spec + // this throws a TypeError, but we will just return undefined. + if ( !jQuery.isFunction( fn ) ) { + return undefined; + } + + // Simulated bind + args = slice.call( arguments, 2 ); + proxy = function() { + return fn.apply( context || this, args.concat( slice.call( arguments ) ) ); + }; + + // Set the guid of unique handler to the same of original handler, so it can be removed + proxy.guid = fn.guid = fn.guid || jQuery.guid++; + + return proxy; + }, + + now: function() { + return +( new Date() ); + }, + + // jQuery.support is not used in Core but other projects attach their + // properties to it so it needs to exist. + support: support +}); + +// Populate the class2type map +jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); +}); + +function isArraylike( obj ) { + var length = obj.length, + type = jQuery.type( obj ); + + if ( type === "function" || jQuery.isWindow( obj ) ) { + return false; + } + + if ( obj.nodeType === 1 && length ) { + return true; + } + + return type === "array" || length === 0 || + typeof length === "number" && length > 0 && ( length - 1 ) in obj; +} +var Sizzle = +/*! + * Sizzle CSS Selector Engine v1.10.19 + * http://sizzlejs.com/ + * + * Copyright 2013 jQuery Foundation, Inc. and other contributors + * Released under the MIT license + * http://jquery.org/license + * + * Date: 2014-04-18 + */ +(function( window ) { + +var i, + support, + Expr, + getText, + isXML, + tokenize, + compile, + select, + outermostContext, + sortInput, + hasDuplicate, + + // Local document vars + setDocument, + document, + docElem, + documentIsHTML, + rbuggyQSA, + rbuggyMatches, + matches, + contains, + + // Instance-specific data + expando = "sizzle" + -(new Date()), + preferredDoc = window.document, + dirruns = 0, + done = 0, + classCache = createCache(), + tokenCache = createCache(), + compilerCache = createCache(), + sortOrder = function( a, b ) { + if ( a === b ) { + hasDuplicate = true; + } + return 0; + }, + + // General-purpose constants + strundefined = typeof undefined, + MAX_NEGATIVE = 1 << 31, + + // Instance methods + hasOwn = ({}).hasOwnProperty, + arr = [], + pop = arr.pop, + push_native = arr.push, + push = arr.push, + slice = arr.slice, + // Use a stripped-down indexOf if we can't use a native one + indexOf = arr.indexOf || function( elem ) { + var i = 0, + len = this.length; + for ( ; i < len; i++ ) { + if ( this[i] === elem ) { + return i; + } + } + return -1; + }, + + booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped", + + // Regular expressions + + // Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace + whitespace = "[\\x20\\t\\r\\n\\f]", + // http://www.w3.org/TR/css3-syntax/#characters + characterEncoding = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+", + + // Loosely modeled on CSS identifier characters + // An unquoted value should be a CSS identifier http://www.w3.org/TR/css3-selectors/#attribute-selectors + // Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier + identifier = characterEncoding.replace( "w", "w#" ), + + // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors + attributes = "\\[" + whitespace + "*(" + characterEncoding + ")(?:" + whitespace + + // Operator (capture 2) + "*([*^$|!~]?=)" + whitespace + + // "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]" + "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace + + "*\\]", + + pseudos = ":(" + characterEncoding + ")(?:\\((" + + // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: + // 1. quoted (capture 3; capture 4 or capture 5) + "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + + // 2. simple (capture 6) + "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + + // 3. anything else (capture 2) + ".*" + + ")\\)|)", + + // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter + rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ), + + rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), + rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ), + + rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*?)" + whitespace + "*\\]", "g" ), + + rpseudo = new RegExp( pseudos ), + ridentifier = new RegExp( "^" + identifier + "$" ), + + matchExpr = { + "ID": new RegExp( "^#(" + characterEncoding + ")" ), + "CLASS": new RegExp( "^\\.(" + characterEncoding + ")" ), + "TAG": new RegExp( "^(" + characterEncoding.replace( "w", "w*" ) + ")" ), + "ATTR": new RegExp( "^" + attributes ), + "PSEUDO": new RegExp( "^" + pseudos ), + "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), + "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), + // For use in libraries implementing .is() + // We use this for POS matching in `select` + "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + + whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) + }, + + rinputs = /^(?:input|select|textarea|button)$/i, + rheader = /^h\d$/i, + + rnative = /^[^{]+\{\s*\[native \w/, + + // Easily-parseable/retrievable ID or TAG or CLASS selectors + rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, + + rsibling = /[+~]/, + rescape = /'|\\/g, + + // CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters + runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ), + funescape = function( _, escaped, escapedWhitespace ) { + var high = "0x" + escaped - 0x10000; + // NaN means non-codepoint + // Support: Firefox<24 + // Workaround erroneous numeric interpretation of +"0x" + return high !== high || escapedWhitespace ? + escaped : + high < 0 ? + // BMP codepoint + String.fromCharCode( high + 0x10000 ) : + // Supplemental Plane codepoint (surrogate pair) + String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); + }; + +// Optimize for push.apply( _, NodeList ) +try { + push.apply( + (arr = slice.call( preferredDoc.childNodes )), + preferredDoc.childNodes + ); + // Support: Android<4.0 + // Detect silently failing push.apply + arr[ preferredDoc.childNodes.length ].nodeType; +} catch ( e ) { + push = { apply: arr.length ? + + // Leverage slice if possible + function( target, els ) { + push_native.apply( target, slice.call(els) ); + } : + + // Support: IE<9 + // Otherwise append directly + function( target, els ) { + var j = target.length, + i = 0; + // Can't trust NodeList.length + while ( (target[j++] = els[i++]) ) {} + target.length = j - 1; + } + }; +} + +function Sizzle( selector, context, results, seed ) { + var match, elem, m, nodeType, + // QSA vars + i, groups, old, nid, newContext, newSelector; + + if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) { + setDocument( context ); + } + + context = context || document; + results = results || []; + + if ( !selector || typeof selector !== "string" ) { + return results; + } + + if ( (nodeType = context.nodeType) !== 1 && nodeType !== 9 ) { + return []; + } + + if ( documentIsHTML && !seed ) { + + // Shortcuts + if ( (match = rquickExpr.exec( selector )) ) { + // Speed-up: Sizzle("#ID") + if ( (m = match[1]) ) { + if ( nodeType === 9 ) { + elem = context.getElementById( m ); + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document (jQuery #6963) + if ( elem && elem.parentNode ) { + // Handle the case where IE, Opera, and Webkit return items + // by name instead of ID + if ( elem.id === m ) { + results.push( elem ); + return results; + } + } else { + return results; + } + } else { + // Context is not a document + if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) && + contains( context, elem ) && elem.id === m ) { + results.push( elem ); + return results; + } + } + + // Speed-up: Sizzle("TAG") + } else if ( match[2] ) { + push.apply( results, context.getElementsByTagName( selector ) ); + return results; + + // Speed-up: Sizzle(".CLASS") + } else if ( (m = match[3]) && support.getElementsByClassName && context.getElementsByClassName ) { + push.apply( results, context.getElementsByClassName( m ) ); + return results; + } + } + + // QSA path + if ( support.qsa && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) { + nid = old = expando; + newContext = context; + newSelector = nodeType === 9 && selector; + + // qSA works strangely on Element-rooted queries + // We can work around this by specifying an extra ID on the root + // and working up from there (Thanks to Andrew Dupont for the technique) + // IE 8 doesn't work on object elements + if ( nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { + groups = tokenize( selector ); + + if ( (old = context.getAttribute("id")) ) { + nid = old.replace( rescape, "\\$&" ); + } else { + context.setAttribute( "id", nid ); + } + nid = "[id='" + nid + "'] "; + + i = groups.length; + while ( i-- ) { + groups[i] = nid + toSelector( groups[i] ); + } + newContext = rsibling.test( selector ) && testContext( context.parentNode ) || context; + newSelector = groups.join(","); + } + + if ( newSelector ) { + try { + push.apply( results, + newContext.querySelectorAll( newSelector ) + ); + return results; + } catch(qsaError) { + } finally { + if ( !old ) { + context.removeAttribute("id"); + } + } + } + } + } + + // All others + return select( selector.replace( rtrim, "$1" ), context, results, seed ); +} + +/** + * Create key-value caches of limited size + * @returns {Function(string, Object)} Returns the Object data after storing it on itself with + * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) + * deleting the oldest entry + */ +function createCache() { + var keys = []; + + function cache( key, value ) { + // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) + if ( keys.push( key + " " ) > Expr.cacheLength ) { + // Only keep the most recent entries + delete cache[ keys.shift() ]; + } + return (cache[ key + " " ] = value); + } + return cache; +} + +/** + * Mark a function for special use by Sizzle + * @param {Function} fn The function to mark + */ +function markFunction( fn ) { + fn[ expando ] = true; + return fn; +} + +/** + * Support testing using an element + * @param {Function} fn Passed the created div and expects a boolean result + */ +function assert( fn ) { + var div = document.createElement("div"); + + try { + return !!fn( div ); + } catch (e) { + return false; + } finally { + // Remove from its parent by default + if ( div.parentNode ) { + div.parentNode.removeChild( div ); + } + // release memory in IE + div = null; + } +} + +/** + * Adds the same handler for all of the specified attrs + * @param {String} attrs Pipe-separated list of attributes + * @param {Function} handler The method that will be applied + */ +function addHandle( attrs, handler ) { + var arr = attrs.split("|"), + i = attrs.length; + + while ( i-- ) { + Expr.attrHandle[ arr[i] ] = handler; + } +} + +/** + * Checks document order of two siblings + * @param {Element} a + * @param {Element} b + * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b + */ +function siblingCheck( a, b ) { + var cur = b && a, + diff = cur && a.nodeType === 1 && b.nodeType === 1 && + ( ~b.sourceIndex || MAX_NEGATIVE ) - + ( ~a.sourceIndex || MAX_NEGATIVE ); + + // Use IE sourceIndex if available on both nodes + if ( diff ) { + return diff; + } + + // Check if b follows a + if ( cur ) { + while ( (cur = cur.nextSibling) ) { + if ( cur === b ) { + return -1; + } + } + } + + return a ? 1 : -1; +} + +/** + * Returns a function to use in pseudos for input types + * @param {String} type + */ +function createInputPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for buttons + * @param {String} type + */ +function createButtonPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return (name === "input" || name === "button") && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for positionals + * @param {Function} fn + */ +function createPositionalPseudo( fn ) { + return markFunction(function( argument ) { + argument = +argument; + return markFunction(function( seed, matches ) { + var j, + matchIndexes = fn( [], seed.length, argument ), + i = matchIndexes.length; + + // Match elements found at the specified indexes + while ( i-- ) { + if ( seed[ (j = matchIndexes[i]) ] ) { + seed[j] = !(matches[j] = seed[j]); + } + } + }); + }); +} + +/** + * Checks a node for validity as a Sizzle context + * @param {Element|Object=} context + * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value + */ +function testContext( context ) { + return context && typeof context.getElementsByTagName !== strundefined && context; +} + +// Expose support vars for convenience +support = Sizzle.support = {}; + +/** + * Detects XML nodes + * @param {Element|Object} elem An element or a document + * @returns {Boolean} True iff elem is a non-HTML XML node + */ +isXML = Sizzle.isXML = function( elem ) { + // documentElement is verified for cases where it doesn't yet exist + // (such as loading iframes in IE - #4833) + var documentElement = elem && (elem.ownerDocument || elem).documentElement; + return documentElement ? documentElement.nodeName !== "HTML" : false; +}; + +/** + * Sets document-related variables once based on the current document + * @param {Element|Object} [doc] An element or document object to use to set the document + * @returns {Object} Returns the current document + */ +setDocument = Sizzle.setDocument = function( node ) { + var hasCompare, + doc = node ? node.ownerDocument || node : preferredDoc, + parent = doc.defaultView; + + // If no document and documentElement is available, return + if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) { + return document; + } + + // Set our document + document = doc; + docElem = doc.documentElement; + + // Support tests + documentIsHTML = !isXML( doc ); + + // Support: IE>8 + // If iframe document is assigned to "document" variable and if iframe has been reloaded, + // IE will throw "permission denied" error when accessing "document" variable, see jQuery #13936 + // IE6-8 do not support the defaultView property so parent will be undefined + if ( parent && parent !== parent.top ) { + // IE11 does not have attachEvent, so all must suffer + if ( parent.addEventListener ) { + parent.addEventListener( "unload", function() { + setDocument(); + }, false ); + } else if ( parent.attachEvent ) { + parent.attachEvent( "onunload", function() { + setDocument(); + }); + } + } + + /* Attributes + ---------------------------------------------------------------------- */ + + // Support: IE<8 + // Verify that getAttribute really returns attributes and not properties (excepting IE8 booleans) + support.attributes = assert(function( div ) { + div.className = "i"; + return !div.getAttribute("className"); + }); + + /* getElement(s)By* + ---------------------------------------------------------------------- */ + + // Check if getElementsByTagName("*") returns only elements + support.getElementsByTagName = assert(function( div ) { + div.appendChild( doc.createComment("") ); + return !div.getElementsByTagName("*").length; + }); + + // Check if getElementsByClassName can be trusted + support.getElementsByClassName = rnative.test( doc.getElementsByClassName ) && assert(function( div ) { + div.innerHTML = "
"; + + // Support: Safari<4 + // Catch class over-caching + div.firstChild.className = "i"; + // Support: Opera<10 + // Catch gEBCN failure to find non-leading classes + return div.getElementsByClassName("i").length === 2; + }); + + // Support: IE<10 + // Check if getElementById returns elements by name + // The broken getElementById methods don't pick up programatically-set names, + // so use a roundabout getElementsByName test + support.getById = assert(function( div ) { + docElem.appendChild( div ).id = expando; + return !doc.getElementsByName || !doc.getElementsByName( expando ).length; + }); + + // ID find and filter + if ( support.getById ) { + Expr.find["ID"] = function( id, context ) { + if ( typeof context.getElementById !== strundefined && documentIsHTML ) { + var m = context.getElementById( id ); + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + return m && m.parentNode ? [ m ] : []; + } + }; + Expr.filter["ID"] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + return elem.getAttribute("id") === attrId; + }; + }; + } else { + // Support: IE6/7 + // getElementById is not reliable as a find shortcut + delete Expr.find["ID"]; + + Expr.filter["ID"] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + var node = typeof elem.getAttributeNode !== strundefined && elem.getAttributeNode("id"); + return node && node.value === attrId; + }; + }; + } + + // Tag + Expr.find["TAG"] = support.getElementsByTagName ? + function( tag, context ) { + if ( typeof context.getElementsByTagName !== strundefined ) { + return context.getElementsByTagName( tag ); + } + } : + function( tag, context ) { + var elem, + tmp = [], + i = 0, + results = context.getElementsByTagName( tag ); + + // Filter out possible comments + if ( tag === "*" ) { + while ( (elem = results[i++]) ) { + if ( elem.nodeType === 1 ) { + tmp.push( elem ); + } + } + + return tmp; + } + return results; + }; + + // Class + Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) { + if ( typeof context.getElementsByClassName !== strundefined && documentIsHTML ) { + return context.getElementsByClassName( className ); + } + }; + + /* QSA/matchesSelector + ---------------------------------------------------------------------- */ + + // QSA and matchesSelector support + + // matchesSelector(:active) reports false when true (IE9/Opera 11.5) + rbuggyMatches = []; + + // qSa(:focus) reports false when true (Chrome 21) + // We allow this because of a bug in IE8/9 that throws an error + // whenever `document.activeElement` is accessed on an iframe + // So, we allow :focus to pass through QSA all the time to avoid the IE error + // See http://bugs.jquery.com/ticket/13378 + rbuggyQSA = []; + + if ( (support.qsa = rnative.test( doc.querySelectorAll )) ) { + // Build QSA regex + // Regex strategy adopted from Diego Perini + assert(function( div ) { + // Select is set to empty string on purpose + // This is to test IE's treatment of not explicitly + // setting a boolean content attribute, + // since its presence should be enough + // http://bugs.jquery.com/ticket/12359 + div.innerHTML = ""; + + // Support: IE8, Opera 11-12.16 + // Nothing should be selected when empty strings follow ^= or $= or *= + // The test attribute must be unknown in Opera but "safe" for WinRT + // http://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section + if ( div.querySelectorAll("[msallowclip^='']").length ) { + rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); + } + + // Support: IE8 + // Boolean attributes and "value" are not treated correctly + if ( !div.querySelectorAll("[selected]").length ) { + rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); + } + + // Webkit/Opera - :checked should return selected option elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + // IE8 throws error here and will not see later tests + if ( !div.querySelectorAll(":checked").length ) { + rbuggyQSA.push(":checked"); + } + }); + + assert(function( div ) { + // Support: Windows 8 Native Apps + // The type and name attributes are restricted during .innerHTML assignment + var input = doc.createElement("input"); + input.setAttribute( "type", "hidden" ); + div.appendChild( input ).setAttribute( "name", "D" ); + + // Support: IE8 + // Enforce case-sensitivity of name attribute + if ( div.querySelectorAll("[name=d]").length ) { + rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); + } + + // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) + // IE8 throws error here and will not see later tests + if ( !div.querySelectorAll(":enabled").length ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Opera 10-11 does not throw on post-comma invalid pseudos + div.querySelectorAll("*,:x"); + rbuggyQSA.push(",.*:"); + }); + } + + if ( (support.matchesSelector = rnative.test( (matches = docElem.matches || + docElem.webkitMatchesSelector || + docElem.mozMatchesSelector || + docElem.oMatchesSelector || + docElem.msMatchesSelector) )) ) { + + assert(function( div ) { + // Check to see if it's possible to do matchesSelector + // on a disconnected node (IE 9) + support.disconnectedMatch = matches.call( div, "div" ); + + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call( div, "[s!='']:x" ); + rbuggyMatches.push( "!=", pseudos ); + }); + } + + rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") ); + rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") ); + + /* Contains + ---------------------------------------------------------------------- */ + hasCompare = rnative.test( docElem.compareDocumentPosition ); + + // Element contains another + // Purposefully does not implement inclusive descendent + // As in, an element does not contain itself + contains = hasCompare || rnative.test( docElem.contains ) ? + function( a, b ) { + var adown = a.nodeType === 9 ? a.documentElement : a, + bup = b && b.parentNode; + return a === bup || !!( bup && bup.nodeType === 1 && ( + adown.contains ? + adown.contains( bup ) : + a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 + )); + } : + function( a, b ) { + if ( b ) { + while ( (b = b.parentNode) ) { + if ( b === a ) { + return true; + } + } + } + return false; + }; + + /* Sorting + ---------------------------------------------------------------------- */ + + // Document order sorting + sortOrder = hasCompare ? + function( a, b ) { + + // Flag for duplicate removal + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + // Sort on method existence if only one input has compareDocumentPosition + var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; + if ( compare ) { + return compare; + } + + // Calculate position if both inputs belong to the same document + compare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ? + a.compareDocumentPosition( b ) : + + // Otherwise we know they are disconnected + 1; + + // Disconnected nodes + if ( compare & 1 || + (!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) { + + // Choose the first element that is related to our preferred document + if ( a === doc || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) { + return -1; + } + if ( b === doc || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) { + return 1; + } + + // Maintain original order + return sortInput ? + ( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) : + 0; + } + + return compare & 4 ? -1 : 1; + } : + function( a, b ) { + // Exit early if the nodes are identical + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + var cur, + i = 0, + aup = a.parentNode, + bup = b.parentNode, + ap = [ a ], + bp = [ b ]; + + // Parentless nodes are either documents or disconnected + if ( !aup || !bup ) { + return a === doc ? -1 : + b === doc ? 1 : + aup ? -1 : + bup ? 1 : + sortInput ? + ( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) : + 0; + + // If the nodes are siblings, we can do a quick check + } else if ( aup === bup ) { + return siblingCheck( a, b ); + } + + // Otherwise we need full lists of their ancestors for comparison + cur = a; + while ( (cur = cur.parentNode) ) { + ap.unshift( cur ); + } + cur = b; + while ( (cur = cur.parentNode) ) { + bp.unshift( cur ); + } + + // Walk down the tree looking for a discrepancy + while ( ap[i] === bp[i] ) { + i++; + } + + return i ? + // Do a sibling check if the nodes have a common ancestor + siblingCheck( ap[i], bp[i] ) : + + // Otherwise nodes in our document sort first + ap[i] === preferredDoc ? -1 : + bp[i] === preferredDoc ? 1 : + 0; + }; + + return doc; +}; + +Sizzle.matches = function( expr, elements ) { + return Sizzle( expr, null, null, elements ); +}; + +Sizzle.matchesSelector = function( elem, expr ) { + // Set document vars if needed + if ( ( elem.ownerDocument || elem ) !== document ) { + setDocument( elem ); + } + + // Make sure that attribute selectors are quoted + expr = expr.replace( rattributeQuotes, "='$1']" ); + + if ( support.matchesSelector && documentIsHTML && + ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && + ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { + + try { + var ret = matches.call( elem, expr ); + + // IE 9's matchesSelector returns false on disconnected nodes + if ( ret || support.disconnectedMatch || + // As well, disconnected nodes are said to be in a document + // fragment in IE 9 + elem.document && elem.document.nodeType !== 11 ) { + return ret; + } + } catch(e) {} + } + + return Sizzle( expr, document, null, [ elem ] ).length > 0; +}; + +Sizzle.contains = function( context, elem ) { + // Set document vars if needed + if ( ( context.ownerDocument || context ) !== document ) { + setDocument( context ); + } + return contains( context, elem ); +}; + +Sizzle.attr = function( elem, name ) { + // Set document vars if needed + if ( ( elem.ownerDocument || elem ) !== document ) { + setDocument( elem ); + } + + var fn = Expr.attrHandle[ name.toLowerCase() ], + // Don't get fooled by Object.prototype properties (jQuery #13807) + val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? + fn( elem, name, !documentIsHTML ) : + undefined; + + return val !== undefined ? + val : + support.attributes || !documentIsHTML ? + elem.getAttribute( name ) : + (val = elem.getAttributeNode(name)) && val.specified ? + val.value : + null; +}; + +Sizzle.error = function( msg ) { + throw new Error( "Syntax error, unrecognized expression: " + msg ); +}; + +/** + * Document sorting and removing duplicates + * @param {ArrayLike} results + */ +Sizzle.uniqueSort = function( results ) { + var elem, + duplicates = [], + j = 0, + i = 0; + + // Unless we *know* we can detect duplicates, assume their presence + hasDuplicate = !support.detectDuplicates; + sortInput = !support.sortStable && results.slice( 0 ); + results.sort( sortOrder ); + + if ( hasDuplicate ) { + while ( (elem = results[i++]) ) { + if ( elem === results[ i ] ) { + j = duplicates.push( i ); + } + } + while ( j-- ) { + results.splice( duplicates[ j ], 1 ); + } + } + + // Clear input after sorting to release objects + // See https://github.com/jquery/sizzle/pull/225 + sortInput = null; + + return results; +}; + +/** + * Utility function for retrieving the text value of an array of DOM nodes + * @param {Array|Element} elem + */ +getText = Sizzle.getText = function( elem ) { + var node, + ret = "", + i = 0, + nodeType = elem.nodeType; + + if ( !nodeType ) { + // If no nodeType, this is expected to be an array + while ( (node = elem[i++]) ) { + // Do not traverse comment nodes + ret += getText( node ); + } + } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { + // Use textContent for elements + // innerText usage removed for consistency of new lines (jQuery #11153) + if ( typeof elem.textContent === "string" ) { + return elem.textContent; + } else { + // Traverse its children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + ret += getText( elem ); + } + } + } else if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + // Do not include comment or processing instruction nodes + + return ret; +}; + +Expr = Sizzle.selectors = { + + // Can be adjusted by the user + cacheLength: 50, + + createPseudo: markFunction, + + match: matchExpr, + + attrHandle: {}, + + find: {}, + + relative: { + ">": { dir: "parentNode", first: true }, + " ": { dir: "parentNode" }, + "+": { dir: "previousSibling", first: true }, + "~": { dir: "previousSibling" } + }, + + preFilter: { + "ATTR": function( match ) { + match[1] = match[1].replace( runescape, funescape ); + + // Move the given value to match[3] whether quoted or unquoted + match[3] = ( match[3] || match[4] || match[5] || "" ).replace( runescape, funescape ); + + if ( match[2] === "~=" ) { + match[3] = " " + match[3] + " "; + } + + return match.slice( 0, 4 ); + }, + + "CHILD": function( match ) { + /* matches from matchExpr["CHILD"] + 1 type (only|nth|...) + 2 what (child|of-type) + 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) + 4 xn-component of xn+y argument ([+-]?\d*n|) + 5 sign of xn-component + 6 x of xn-component + 7 sign of y-component + 8 y of y-component + */ + match[1] = match[1].toLowerCase(); + + if ( match[1].slice( 0, 3 ) === "nth" ) { + // nth-* requires argument + if ( !match[3] ) { + Sizzle.error( match[0] ); + } + + // numeric x and y parameters for Expr.filter.CHILD + // remember that false/true cast respectively to 0/1 + match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) ); + match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" ); + + // other types prohibit arguments + } else if ( match[3] ) { + Sizzle.error( match[0] ); + } + + return match; + }, + + "PSEUDO": function( match ) { + var excess, + unquoted = !match[6] && match[2]; + + if ( matchExpr["CHILD"].test( match[0] ) ) { + return null; + } + + // Accept quoted arguments as-is + if ( match[3] ) { + match[2] = match[4] || match[5] || ""; + + // Strip excess characters from unquoted arguments + } else if ( unquoted && rpseudo.test( unquoted ) && + // Get excess from tokenize (recursively) + (excess = tokenize( unquoted, true )) && + // advance to the next closing parenthesis + (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) { + + // excess is a negative index + match[0] = match[0].slice( 0, excess ); + match[2] = unquoted.slice( 0, excess ); + } + + // Return only captures needed by the pseudo filter method (type and argument) + return match.slice( 0, 3 ); + } + }, + + filter: { + + "TAG": function( nodeNameSelector ) { + var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); + return nodeNameSelector === "*" ? + function() { return true; } : + function( elem ) { + return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; + }; + }, + + "CLASS": function( className ) { + var pattern = classCache[ className + " " ]; + + return pattern || + (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) && + classCache( className, function( elem ) { + return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== strundefined && elem.getAttribute("class") || "" ); + }); + }, + + "ATTR": function( name, operator, check ) { + return function( elem ) { + var result = Sizzle.attr( elem, name ); + + if ( result == null ) { + return operator === "!="; + } + if ( !operator ) { + return true; + } + + result += ""; + + return operator === "=" ? result === check : + operator === "!=" ? result !== check : + operator === "^=" ? check && result.indexOf( check ) === 0 : + operator === "*=" ? check && result.indexOf( check ) > -1 : + operator === "$=" ? check && result.slice( -check.length ) === check : + operator === "~=" ? ( " " + result + " " ).indexOf( check ) > -1 : + operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : + false; + }; + }, + + "CHILD": function( type, what, argument, first, last ) { + var simple = type.slice( 0, 3 ) !== "nth", + forward = type.slice( -4 ) !== "last", + ofType = what === "of-type"; + + return first === 1 && last === 0 ? + + // Shortcut for :nth-*(n) + function( elem ) { + return !!elem.parentNode; + } : + + function( elem, context, xml ) { + var cache, outerCache, node, diff, nodeIndex, start, + dir = simple !== forward ? "nextSibling" : "previousSibling", + parent = elem.parentNode, + name = ofType && elem.nodeName.toLowerCase(), + useCache = !xml && !ofType; + + if ( parent ) { + + // :(first|last|only)-(child|of-type) + if ( simple ) { + while ( dir ) { + node = elem; + while ( (node = node[ dir ]) ) { + if ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) { + return false; + } + } + // Reverse direction for :only-* (if we haven't yet done so) + start = dir = type === "only" && !start && "nextSibling"; + } + return true; + } + + start = [ forward ? parent.firstChild : parent.lastChild ]; + + // non-xml :nth-child(...) stores cache data on `parent` + if ( forward && useCache ) { + // Seek `elem` from a previously-cached index + outerCache = parent[ expando ] || (parent[ expando ] = {}); + cache = outerCache[ type ] || []; + nodeIndex = cache[0] === dirruns && cache[1]; + diff = cache[0] === dirruns && cache[2]; + node = nodeIndex && parent.childNodes[ nodeIndex ]; + + while ( (node = ++nodeIndex && node && node[ dir ] || + + // Fallback to seeking `elem` from the start + (diff = nodeIndex = 0) || start.pop()) ) { + + // When found, cache indexes on `parent` and break + if ( node.nodeType === 1 && ++diff && node === elem ) { + outerCache[ type ] = [ dirruns, nodeIndex, diff ]; + break; + } + } + + // Use previously-cached element index if available + } else if ( useCache && (cache = (elem[ expando ] || (elem[ expando ] = {}))[ type ]) && cache[0] === dirruns ) { + diff = cache[1]; + + // xml :nth-child(...) or :nth-last-child(...) or :nth(-last)?-of-type(...) + } else { + // Use the same loop as above to seek `elem` from the start + while ( (node = ++nodeIndex && node && node[ dir ] || + (diff = nodeIndex = 0) || start.pop()) ) { + + if ( ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) && ++diff ) { + // Cache the index of each encountered element + if ( useCache ) { + (node[ expando ] || (node[ expando ] = {}))[ type ] = [ dirruns, diff ]; + } + + if ( node === elem ) { + break; + } + } + } + } + + // Incorporate the offset, then check against cycle size + diff -= last; + return diff === first || ( diff % first === 0 && diff / first >= 0 ); + } + }; + }, + + "PSEUDO": function( pseudo, argument ) { + // pseudo-class names are case-insensitive + // http://www.w3.org/TR/selectors/#pseudo-classes + // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters + // Remember that setFilters inherits from pseudos + var args, + fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || + Sizzle.error( "unsupported pseudo: " + pseudo ); + + // The user may use createPseudo to indicate that + // arguments are needed to create the filter function + // just as Sizzle does + if ( fn[ expando ] ) { + return fn( argument ); + } + + // But maintain support for old signatures + if ( fn.length > 1 ) { + args = [ pseudo, pseudo, "", argument ]; + return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? + markFunction(function( seed, matches ) { + var idx, + matched = fn( seed, argument ), + i = matched.length; + while ( i-- ) { + idx = indexOf.call( seed, matched[i] ); + seed[ idx ] = !( matches[ idx ] = matched[i] ); + } + }) : + function( elem ) { + return fn( elem, 0, args ); + }; + } + + return fn; + } + }, + + pseudos: { + // Potentially complex pseudos + "not": markFunction(function( selector ) { + // Trim the selector passed to compile + // to avoid treating leading and trailing + // spaces as combinators + var input = [], + results = [], + matcher = compile( selector.replace( rtrim, "$1" ) ); + + return matcher[ expando ] ? + markFunction(function( seed, matches, context, xml ) { + var elem, + unmatched = matcher( seed, null, xml, [] ), + i = seed.length; + + // Match elements unmatched by `matcher` + while ( i-- ) { + if ( (elem = unmatched[i]) ) { + seed[i] = !(matches[i] = elem); + } + } + }) : + function( elem, context, xml ) { + input[0] = elem; + matcher( input, null, xml, results ); + return !results.pop(); + }; + }), + + "has": markFunction(function( selector ) { + return function( elem ) { + return Sizzle( selector, elem ).length > 0; + }; + }), + + "contains": markFunction(function( text ) { + return function( elem ) { + return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1; + }; + }), + + // "Whether an element is represented by a :lang() selector + // is based solely on the element's language value + // being equal to the identifier C, + // or beginning with the identifier C immediately followed by "-". + // The matching of C against the element's language value is performed case-insensitively. + // The identifier C does not have to be a valid language name." + // http://www.w3.org/TR/selectors/#lang-pseudo + "lang": markFunction( function( lang ) { + // lang value must be a valid identifier + if ( !ridentifier.test(lang || "") ) { + Sizzle.error( "unsupported lang: " + lang ); + } + lang = lang.replace( runescape, funescape ).toLowerCase(); + return function( elem ) { + var elemLang; + do { + if ( (elemLang = documentIsHTML ? + elem.lang : + elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) { + + elemLang = elemLang.toLowerCase(); + return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; + } + } while ( (elem = elem.parentNode) && elem.nodeType === 1 ); + return false; + }; + }), + + // Miscellaneous + "target": function( elem ) { + var hash = window.location && window.location.hash; + return hash && hash.slice( 1 ) === elem.id; + }, + + "root": function( elem ) { + return elem === docElem; + }, + + "focus": function( elem ) { + return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex); + }, + + // Boolean properties + "enabled": function( elem ) { + return elem.disabled === false; + }, + + "disabled": function( elem ) { + return elem.disabled === true; + }, + + "checked": function( elem ) { + // In CSS3, :checked should return both checked and selected elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + var nodeName = elem.nodeName.toLowerCase(); + return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected); + }, + + "selected": function( elem ) { + // Accessing this property makes selected-by-default + // options in Safari work properly + if ( elem.parentNode ) { + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + // Contents + "empty": function( elem ) { + // http://www.w3.org/TR/selectors/#empty-pseudo + // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), + // but not by others (comment: 8; processing instruction: 7; etc.) + // nodeType < 6 works because attributes (2) do not appear as children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + if ( elem.nodeType < 6 ) { + return false; + } + } + return true; + }, + + "parent": function( elem ) { + return !Expr.pseudos["empty"]( elem ); + }, + + // Element/input types + "header": function( elem ) { + return rheader.test( elem.nodeName ); + }, + + "input": function( elem ) { + return rinputs.test( elem.nodeName ); + }, + + "button": function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === "button" || name === "button"; + }, + + "text": function( elem ) { + var attr; + return elem.nodeName.toLowerCase() === "input" && + elem.type === "text" && + + // Support: IE<8 + // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" + ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === "text" ); + }, + + // Position-in-collection + "first": createPositionalPseudo(function() { + return [ 0 ]; + }), + + "last": createPositionalPseudo(function( matchIndexes, length ) { + return [ length - 1 ]; + }), + + "eq": createPositionalPseudo(function( matchIndexes, length, argument ) { + return [ argument < 0 ? argument + length : argument ]; + }), + + "even": createPositionalPseudo(function( matchIndexes, length ) { + var i = 0; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "odd": createPositionalPseudo(function( matchIndexes, length ) { + var i = 1; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "lt": createPositionalPseudo(function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; --i >= 0; ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "gt": createPositionalPseudo(function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; ++i < length; ) { + matchIndexes.push( i ); + } + return matchIndexes; + }) + } +}; + +Expr.pseudos["nth"] = Expr.pseudos["eq"]; + +// Add button/input type pseudos +for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { + Expr.pseudos[ i ] = createInputPseudo( i ); +} +for ( i in { submit: true, reset: true } ) { + Expr.pseudos[ i ] = createButtonPseudo( i ); +} + +// Easy API for creating new setFilters +function setFilters() {} +setFilters.prototype = Expr.filters = Expr.pseudos; +Expr.setFilters = new setFilters(); + +tokenize = Sizzle.tokenize = function( selector, parseOnly ) { + var matched, match, tokens, type, + soFar, groups, preFilters, + cached = tokenCache[ selector + " " ]; + + if ( cached ) { + return parseOnly ? 0 : cached.slice( 0 ); + } + + soFar = selector; + groups = []; + preFilters = Expr.preFilter; + + while ( soFar ) { + + // Comma and first run + if ( !matched || (match = rcomma.exec( soFar )) ) { + if ( match ) { + // Don't consume trailing commas as valid + soFar = soFar.slice( match[0].length ) || soFar; + } + groups.push( (tokens = []) ); + } + + matched = false; + + // Combinators + if ( (match = rcombinators.exec( soFar )) ) { + matched = match.shift(); + tokens.push({ + value: matched, + // Cast descendant combinators to space + type: match[0].replace( rtrim, " " ) + }); + soFar = soFar.slice( matched.length ); + } + + // Filters + for ( type in Expr.filter ) { + if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] || + (match = preFilters[ type ]( match ))) ) { + matched = match.shift(); + tokens.push({ + value: matched, + type: type, + matches: match + }); + soFar = soFar.slice( matched.length ); + } + } + + if ( !matched ) { + break; + } + } + + // Return the length of the invalid excess + // if we're just parsing + // Otherwise, throw an error or return tokens + return parseOnly ? + soFar.length : + soFar ? + Sizzle.error( selector ) : + // Cache the tokens + tokenCache( selector, groups ).slice( 0 ); +}; + +function toSelector( tokens ) { + var i = 0, + len = tokens.length, + selector = ""; + for ( ; i < len; i++ ) { + selector += tokens[i].value; + } + return selector; +} + +function addCombinator( matcher, combinator, base ) { + var dir = combinator.dir, + checkNonElements = base && dir === "parentNode", + doneName = done++; + + return combinator.first ? + // Check against closest ancestor/preceding element + function( elem, context, xml ) { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + return matcher( elem, context, xml ); + } + } + } : + + // Check against all ancestor/preceding elements + function( elem, context, xml ) { + var oldCache, outerCache, + newCache = [ dirruns, doneName ]; + + // We can't set arbitrary data on XML nodes, so they don't benefit from dir caching + if ( xml ) { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + if ( matcher( elem, context, xml ) ) { + return true; + } + } + } + } else { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + outerCache = elem[ expando ] || (elem[ expando ] = {}); + if ( (oldCache = outerCache[ dir ]) && + oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { + + // Assign to newCache so results back-propagate to previous elements + return (newCache[ 2 ] = oldCache[ 2 ]); + } else { + // Reuse newcache so results back-propagate to previous elements + outerCache[ dir ] = newCache; + + // A match means we're done; a fail means we have to keep checking + if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) { + return true; + } + } + } + } + } + }; +} + +function elementMatcher( matchers ) { + return matchers.length > 1 ? + function( elem, context, xml ) { + var i = matchers.length; + while ( i-- ) { + if ( !matchers[i]( elem, context, xml ) ) { + return false; + } + } + return true; + } : + matchers[0]; +} + +function multipleContexts( selector, contexts, results ) { + var i = 0, + len = contexts.length; + for ( ; i < len; i++ ) { + Sizzle( selector, contexts[i], results ); + } + return results; +} + +function condense( unmatched, map, filter, context, xml ) { + var elem, + newUnmatched = [], + i = 0, + len = unmatched.length, + mapped = map != null; + + for ( ; i < len; i++ ) { + if ( (elem = unmatched[i]) ) { + if ( !filter || filter( elem, context, xml ) ) { + newUnmatched.push( elem ); + if ( mapped ) { + map.push( i ); + } + } + } + } + + return newUnmatched; +} + +function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { + if ( postFilter && !postFilter[ expando ] ) { + postFilter = setMatcher( postFilter ); + } + if ( postFinder && !postFinder[ expando ] ) { + postFinder = setMatcher( postFinder, postSelector ); + } + return markFunction(function( seed, results, context, xml ) { + var temp, i, elem, + preMap = [], + postMap = [], + preexisting = results.length, + + // Get initial elements from seed or context + elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ), + + // Prefilter to get matcher input, preserving a map for seed-results synchronization + matcherIn = preFilter && ( seed || !selector ) ? + condense( elems, preMap, preFilter, context, xml ) : + elems, + + matcherOut = matcher ? + // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, + postFinder || ( seed ? preFilter : preexisting || postFilter ) ? + + // ...intermediate processing is necessary + [] : + + // ...otherwise use results directly + results : + matcherIn; + + // Find primary matches + if ( matcher ) { + matcher( matcherIn, matcherOut, context, xml ); + } + + // Apply postFilter + if ( postFilter ) { + temp = condense( matcherOut, postMap ); + postFilter( temp, [], context, xml ); + + // Un-match failing elements by moving them back to matcherIn + i = temp.length; + while ( i-- ) { + if ( (elem = temp[i]) ) { + matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem); + } + } + } + + if ( seed ) { + if ( postFinder || preFilter ) { + if ( postFinder ) { + // Get the final matcherOut by condensing this intermediate into postFinder contexts + temp = []; + i = matcherOut.length; + while ( i-- ) { + if ( (elem = matcherOut[i]) ) { + // Restore matcherIn since elem is not yet a final match + temp.push( (matcherIn[i] = elem) ); + } + } + postFinder( null, (matcherOut = []), temp, xml ); + } + + // Move matched elements from seed to results to keep them synchronized + i = matcherOut.length; + while ( i-- ) { + if ( (elem = matcherOut[i]) && + (temp = postFinder ? indexOf.call( seed, elem ) : preMap[i]) > -1 ) { + + seed[temp] = !(results[temp] = elem); + } + } + } + + // Add elements to results, through postFinder if defined + } else { + matcherOut = condense( + matcherOut === results ? + matcherOut.splice( preexisting, matcherOut.length ) : + matcherOut + ); + if ( postFinder ) { + postFinder( null, results, matcherOut, xml ); + } else { + push.apply( results, matcherOut ); + } + } + }); +} + +function matcherFromTokens( tokens ) { + var checkContext, matcher, j, + len = tokens.length, + leadingRelative = Expr.relative[ tokens[0].type ], + implicitRelative = leadingRelative || Expr.relative[" "], + i = leadingRelative ? 1 : 0, + + // The foundational matcher ensures that elements are reachable from top-level context(s) + matchContext = addCombinator( function( elem ) { + return elem === checkContext; + }, implicitRelative, true ), + matchAnyContext = addCombinator( function( elem ) { + return indexOf.call( checkContext, elem ) > -1; + }, implicitRelative, true ), + matchers = [ function( elem, context, xml ) { + return ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( + (checkContext = context).nodeType ? + matchContext( elem, context, xml ) : + matchAnyContext( elem, context, xml ) ); + } ]; + + for ( ; i < len; i++ ) { + if ( (matcher = Expr.relative[ tokens[i].type ]) ) { + matchers = [ addCombinator(elementMatcher( matchers ), matcher) ]; + } else { + matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches ); + + // Return special upon seeing a positional matcher + if ( matcher[ expando ] ) { + // Find the next relative operator (if any) for proper handling + j = ++i; + for ( ; j < len; j++ ) { + if ( Expr.relative[ tokens[j].type ] ) { + break; + } + } + return setMatcher( + i > 1 && elementMatcher( matchers ), + i > 1 && toSelector( + // If the preceding token was a descendant combinator, insert an implicit any-element `*` + tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" }) + ).replace( rtrim, "$1" ), + matcher, + i < j && matcherFromTokens( tokens.slice( i, j ) ), + j < len && matcherFromTokens( (tokens = tokens.slice( j )) ), + j < len && toSelector( tokens ) + ); + } + matchers.push( matcher ); + } + } + + return elementMatcher( matchers ); +} + +function matcherFromGroupMatchers( elementMatchers, setMatchers ) { + var bySet = setMatchers.length > 0, + byElement = elementMatchers.length > 0, + superMatcher = function( seed, context, xml, results, outermost ) { + var elem, j, matcher, + matchedCount = 0, + i = "0", + unmatched = seed && [], + setMatched = [], + contextBackup = outermostContext, + // We must always have either seed elements or outermost context + elems = seed || byElement && Expr.find["TAG"]( "*", outermost ), + // Use integer dirruns iff this is the outermost matcher + dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1), + len = elems.length; + + if ( outermost ) { + outermostContext = context !== document && context; + } + + // Add elements passing elementMatchers directly to results + // Keep `i` a string if there are no elements so `matchedCount` will be "00" below + // Support: IE<9, Safari + // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id + for ( ; i !== len && (elem = elems[i]) != null; i++ ) { + if ( byElement && elem ) { + j = 0; + while ( (matcher = elementMatchers[j++]) ) { + if ( matcher( elem, context, xml ) ) { + results.push( elem ); + break; + } + } + if ( outermost ) { + dirruns = dirrunsUnique; + } + } + + // Track unmatched elements for set filters + if ( bySet ) { + // They will have gone through all possible matchers + if ( (elem = !matcher && elem) ) { + matchedCount--; + } + + // Lengthen the array for every element, matched or not + if ( seed ) { + unmatched.push( elem ); + } + } + } + + // Apply set filters to unmatched elements + matchedCount += i; + if ( bySet && i !== matchedCount ) { + j = 0; + while ( (matcher = setMatchers[j++]) ) { + matcher( unmatched, setMatched, context, xml ); + } + + if ( seed ) { + // Reintegrate element matches to eliminate the need for sorting + if ( matchedCount > 0 ) { + while ( i-- ) { + if ( !(unmatched[i] || setMatched[i]) ) { + setMatched[i] = pop.call( results ); + } + } + } + + // Discard index placeholder values to get only actual matches + setMatched = condense( setMatched ); + } + + // Add matches to results + push.apply( results, setMatched ); + + // Seedless set matches succeeding multiple successful matchers stipulate sorting + if ( outermost && !seed && setMatched.length > 0 && + ( matchedCount + setMatchers.length ) > 1 ) { + + Sizzle.uniqueSort( results ); + } + } + + // Override manipulation of globals by nested matchers + if ( outermost ) { + dirruns = dirrunsUnique; + outermostContext = contextBackup; + } + + return unmatched; + }; + + return bySet ? + markFunction( superMatcher ) : + superMatcher; +} + +compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { + var i, + setMatchers = [], + elementMatchers = [], + cached = compilerCache[ selector + " " ]; + + if ( !cached ) { + // Generate a function of recursive functions that can be used to check each element + if ( !match ) { + match = tokenize( selector ); + } + i = match.length; + while ( i-- ) { + cached = matcherFromTokens( match[i] ); + if ( cached[ expando ] ) { + setMatchers.push( cached ); + } else { + elementMatchers.push( cached ); + } + } + + // Cache the compiled function + cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) ); + + // Save selector and tokenization + cached.selector = selector; + } + return cached; +}; + +/** + * A low-level selection function that works with Sizzle's compiled + * selector functions + * @param {String|Function} selector A selector or a pre-compiled + * selector function built with Sizzle.compile + * @param {Element} context + * @param {Array} [results] + * @param {Array} [seed] A set of elements to match against + */ +select = Sizzle.select = function( selector, context, results, seed ) { + var i, tokens, token, type, find, + compiled = typeof selector === "function" && selector, + match = !seed && tokenize( (selector = compiled.selector || selector) ); + + results = results || []; + + // Try to minimize operations if there is no seed and only one group + if ( match.length === 1 ) { + + // Take a shortcut and set the context if the root selector is an ID + tokens = match[0] = match[0].slice( 0 ); + if ( tokens.length > 2 && (token = tokens[0]).type === "ID" && + support.getById && context.nodeType === 9 && documentIsHTML && + Expr.relative[ tokens[1].type ] ) { + + context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0]; + if ( !context ) { + return results; + + // Precompiled matchers will still verify ancestry, so step up a level + } else if ( compiled ) { + context = context.parentNode; + } + + selector = selector.slice( tokens.shift().value.length ); + } + + // Fetch a seed set for right-to-left matching + i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length; + while ( i-- ) { + token = tokens[i]; + + // Abort if we hit a combinator + if ( Expr.relative[ (type = token.type) ] ) { + break; + } + if ( (find = Expr.find[ type ]) ) { + // Search, expanding context for leading sibling combinators + if ( (seed = find( + token.matches[0].replace( runescape, funescape ), + rsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context + )) ) { + + // If seed is empty or no tokens remain, we can return early + tokens.splice( i, 1 ); + selector = seed.length && toSelector( tokens ); + if ( !selector ) { + push.apply( results, seed ); + return results; + } + + break; + } + } + } + } + + // Compile and execute a filtering function if one is not provided + // Provide `match` to avoid retokenization if we modified the selector above + ( compiled || compile( selector, match ) )( + seed, + context, + !documentIsHTML, + results, + rsibling.test( selector ) && testContext( context.parentNode ) || context + ); + return results; +}; + +// One-time assignments + +// Sort stability +support.sortStable = expando.split("").sort( sortOrder ).join("") === expando; + +// Support: Chrome<14 +// Always assume duplicates if they aren't passed to the comparison function +support.detectDuplicates = !!hasDuplicate; + +// Initialize against the default document +setDocument(); + +// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) +// Detached nodes confoundingly follow *each other* +support.sortDetached = assert(function( div1 ) { + // Should return 1, but returns 4 (following) + return div1.compareDocumentPosition( document.createElement("div") ) & 1; +}); + +// Support: IE<8 +// Prevent attribute/property "interpolation" +// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx +if ( !assert(function( div ) { + div.innerHTML = ""; + return div.firstChild.getAttribute("href") === "#" ; +}) ) { + addHandle( "type|href|height|width", function( elem, name, isXML ) { + if ( !isXML ) { + return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); + } + }); +} + +// Support: IE<9 +// Use defaultValue in place of getAttribute("value") +if ( !support.attributes || !assert(function( div ) { + div.innerHTML = ""; + div.firstChild.setAttribute( "value", "" ); + return div.firstChild.getAttribute( "value" ) === ""; +}) ) { + addHandle( "value", function( elem, name, isXML ) { + if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { + return elem.defaultValue; + } + }); +} + +// Support: IE<9 +// Use getAttributeNode to fetch booleans when getAttribute lies +if ( !assert(function( div ) { + return div.getAttribute("disabled") == null; +}) ) { + addHandle( booleans, function( elem, name, isXML ) { + var val; + if ( !isXML ) { + return elem[ name ] === true ? name.toLowerCase() : + (val = elem.getAttributeNode( name )) && val.specified ? + val.value : + null; + } + }); +} + +return Sizzle; + +})( window ); + + + +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; +jQuery.expr[":"] = jQuery.expr.pseudos; +jQuery.unique = Sizzle.uniqueSort; +jQuery.text = Sizzle.getText; +jQuery.isXMLDoc = Sizzle.isXML; +jQuery.contains = Sizzle.contains; + + + +var rneedsContext = jQuery.expr.match.needsContext; + +var rsingleTag = (/^<(\w+)\s*\/?>(?:<\/\1>|)$/); + + + +var risSimple = /^.[^:#\[\.,]*$/; + +// Implement the identical functionality for filter and not +function winnow( elements, qualifier, not ) { + if ( jQuery.isFunction( qualifier ) ) { + return jQuery.grep( elements, function( elem, i ) { + /* jshint -W018 */ + return !!qualifier.call( elem, i, elem ) !== not; + }); + + } + + if ( qualifier.nodeType ) { + return jQuery.grep( elements, function( elem ) { + return ( elem === qualifier ) !== not; + }); + + } + + if ( typeof qualifier === "string" ) { + if ( risSimple.test( qualifier ) ) { + return jQuery.filter( qualifier, elements, not ); + } + + qualifier = jQuery.filter( qualifier, elements ); + } + + return jQuery.grep( elements, function( elem ) { + return ( jQuery.inArray( elem, qualifier ) >= 0 ) !== not; + }); +} + +jQuery.filter = function( expr, elems, not ) { + var elem = elems[ 0 ]; + + if ( not ) { + expr = ":not(" + expr + ")"; + } + + return elems.length === 1 && elem.nodeType === 1 ? + jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [] : + jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { + return elem.nodeType === 1; + })); +}; + +jQuery.fn.extend({ + find: function( selector ) { + var i, + ret = [], + self = this, + len = self.length; + + if ( typeof selector !== "string" ) { + return this.pushStack( jQuery( selector ).filter(function() { + for ( i = 0; i < len; i++ ) { + if ( jQuery.contains( self[ i ], this ) ) { + return true; + } + } + }) ); + } + + for ( i = 0; i < len; i++ ) { + jQuery.find( selector, self[ i ], ret ); + } + + // Needed because $( selector, context ) becomes $( context ).find( selector ) + ret = this.pushStack( len > 1 ? jQuery.unique( ret ) : ret ); + ret.selector = this.selector ? this.selector + " " + selector : selector; + return ret; + }, + filter: function( selector ) { + return this.pushStack( winnow(this, selector || [], false) ); + }, + not: function( selector ) { + return this.pushStack( winnow(this, selector || [], true) ); + }, + is: function( selector ) { + return !!winnow( + this, + + // If this is a positional/relative selector, check membership in the returned set + // so $("p:first").is("p:last") won't return true for a doc with two "p". + typeof selector === "string" && rneedsContext.test( selector ) ? + jQuery( selector ) : + selector || [], + false + ).length; + } +}); + + +// Initialize a jQuery object + + +// A central reference to the root jQuery(document) +var rootjQuery, + + // Use the correct document accordingly with window argument (sandbox) + document = window.document, + + // A simple way to check for HTML strings + // Prioritize #id over to avoid XSS via location.hash (#9521) + // Strict HTML recognition (#11290: must start with <) + rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/, + + init = jQuery.fn.init = function( selector, context ) { + var match, elem; + + // HANDLE: $(""), $(null), $(undefined), $(false) + if ( !selector ) { + return this; + } + + // Handle HTML strings + if ( typeof selector === "string" ) { + if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) { + // Assume that strings that start and end with <> are HTML and skip the regex check + match = [ null, selector, null ]; + + } else { + match = rquickExpr.exec( selector ); + } + + // Match html or make sure no context is specified for #id + if ( match && (match[1] || !context) ) { + + // HANDLE: $(html) -> $(array) + if ( match[1] ) { + context = context instanceof jQuery ? context[0] : context; + + // scripts is true for back-compat + // Intentionally let the error be thrown if parseHTML is not present + jQuery.merge( this, jQuery.parseHTML( + match[1], + context && context.nodeType ? context.ownerDocument || context : document, + true + ) ); + + // HANDLE: $(html, props) + if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) { + for ( match in context ) { + // Properties of context are called as methods if possible + if ( jQuery.isFunction( this[ match ] ) ) { + this[ match ]( context[ match ] ); + + // ...and otherwise set as attributes + } else { + this.attr( match, context[ match ] ); + } + } + } + + return this; + + // HANDLE: $(#id) + } else { + elem = document.getElementById( match[2] ); + + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + if ( elem && elem.parentNode ) { + // Handle the case where IE and Opera return items + // by name instead of ID + if ( elem.id !== match[2] ) { + return rootjQuery.find( selector ); + } + + // Otherwise, we inject the element directly into the jQuery object + this.length = 1; + this[0] = elem; + } + + this.context = document; + this.selector = selector; + return this; + } + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return ( context || rootjQuery ).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return this.constructor( context ).find( selector ); + } + + // HANDLE: $(DOMElement) + } else if ( selector.nodeType ) { + this.context = this[0] = selector; + this.length = 1; + return this; + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( jQuery.isFunction( selector ) ) { + return typeof rootjQuery.ready !== "undefined" ? + rootjQuery.ready( selector ) : + // Execute immediately if ready is not present + selector( jQuery ); + } + + if ( selector.selector !== undefined ) { + this.selector = selector.selector; + this.context = selector.context; + } + + return jQuery.makeArray( selector, this ); + }; + +// Give the init function the jQuery prototype for later instantiation +init.prototype = jQuery.fn; + +// Initialize central reference +rootjQuery = jQuery( document ); + + +var rparentsprev = /^(?:parents|prev(?:Until|All))/, + // methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; + +jQuery.extend({ + dir: function( elem, dir, until ) { + var matched = [], + cur = elem[ dir ]; + + while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) { + if ( cur.nodeType === 1 ) { + matched.push( cur ); + } + cur = cur[dir]; + } + return matched; + }, + + sibling: function( n, elem ) { + var r = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + r.push( n ); + } + } + + return r; + } +}); + +jQuery.fn.extend({ + has: function( target ) { + var i, + targets = jQuery( target, this ), + len = targets.length; + + return this.filter(function() { + for ( i = 0; i < len; i++ ) { + if ( jQuery.contains( this, targets[i] ) ) { + return true; + } + } + }); + }, + + closest: function( selectors, context ) { + var cur, + i = 0, + l = this.length, + matched = [], + pos = rneedsContext.test( selectors ) || typeof selectors !== "string" ? + jQuery( selectors, context || this.context ) : + 0; + + for ( ; i < l; i++ ) { + for ( cur = this[i]; cur && cur !== context; cur = cur.parentNode ) { + // Always skip document fragments + if ( cur.nodeType < 11 && (pos ? + pos.index(cur) > -1 : + + // Don't pass non-elements to Sizzle + cur.nodeType === 1 && + jQuery.find.matchesSelector(cur, selectors)) ) { + + matched.push( cur ); + break; + } + } + } + + return this.pushStack( matched.length > 1 ? jQuery.unique( matched ) : matched ); + }, + + // Determine the position of an element within + // the matched set of elements + index: function( elem ) { + + // No argument, return index in parent + if ( !elem ) { + return ( this[0] && this[0].parentNode ) ? this.first().prevAll().length : -1; + } + + // index in selector + if ( typeof elem === "string" ) { + return jQuery.inArray( this[0], jQuery( elem ) ); + } + + // Locate the position of the desired element + return jQuery.inArray( + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[0] : elem, this ); + }, + + add: function( selector, context ) { + return this.pushStack( + jQuery.unique( + jQuery.merge( this.get(), jQuery( selector, context ) ) + ) + ); + }, + + addBack: function( selector ) { + return this.add( selector == null ? + this.prevObject : this.prevObject.filter(selector) + ); + } +}); + +function sibling( cur, dir ) { + do { + cur = cur[ dir ]; + } while ( cur && cur.nodeType !== 1 ); + + return cur; +} + +jQuery.each({ + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return jQuery.dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, i, until ) { + return jQuery.dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return sibling( elem, "nextSibling" ); + }, + prev: function( elem ) { + return sibling( elem, "previousSibling" ); + }, + nextAll: function( elem ) { + return jQuery.dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return jQuery.dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, i, until ) { + return jQuery.dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, i, until ) { + return jQuery.dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem ); + }, + children: function( elem ) { + return jQuery.sibling( elem.firstChild ); + }, + contents: function( elem ) { + return jQuery.nodeName( elem, "iframe" ) ? + elem.contentDocument || elem.contentWindow.document : + jQuery.merge( [], elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var ret = jQuery.map( this, fn, until ); + + if ( name.slice( -5 ) !== "Until" ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + ret = jQuery.filter( selector, ret ); + } + + if ( this.length > 1 ) { + // Remove duplicates + if ( !guaranteedUnique[ name ] ) { + ret = jQuery.unique( ret ); + } + + // Reverse order for parents* and prev-derivatives + if ( rparentsprev.test( name ) ) { + ret = ret.reverse(); + } + } + + return this.pushStack( ret ); + }; +}); +var rnotwhite = (/\S+/g); + + + +// String to Object options format cache +var optionsCache = {}; + +// Convert String-formatted options into Object-formatted ones and store in cache +function createOptions( options ) { + var object = optionsCache[ options ] = {}; + jQuery.each( options.match( rnotwhite ) || [], function( _, flag ) { + object[ flag ] = true; + }); + return object; +} + +/* + * Create a callback list using the following parameters: + * + * options: an optional list of space-separated options that will change how + * the callback list behaves or a more traditional option object + * + * By default a callback list will act like an event callback list and can be + * "fired" multiple times. + * + * Possible options: + * + * once: will ensure the callback list can only be fired once (like a Deferred) + * + * memory: will keep track of previous values and will call any callback added + * after the list has been fired right away with the latest "memorized" + * values (like a Deferred) + * + * unique: will ensure a callback can only be added once (no duplicate in the list) + * + * stopOnFalse: interrupt callings when a callback returns false + * + */ +jQuery.Callbacks = function( options ) { + + // Convert options from String-formatted to Object-formatted if needed + // (we check in cache first) + options = typeof options === "string" ? + ( optionsCache[ options ] || createOptions( options ) ) : + jQuery.extend( {}, options ); + + var // Flag to know if list is currently firing + firing, + // Last fire value (for non-forgettable lists) + memory, + // Flag to know if list was already fired + fired, + // End of the loop when firing + firingLength, + // Index of currently firing callback (modified by remove if needed) + firingIndex, + // First callback to fire (used internally by add and fireWith) + firingStart, + // Actual callback list + list = [], + // Stack of fire calls for repeatable lists + stack = !options.once && [], + // Fire callbacks + fire = function( data ) { + memory = options.memory && data; + fired = true; + firingIndex = firingStart || 0; + firingStart = 0; + firingLength = list.length; + firing = true; + for ( ; list && firingIndex < firingLength; firingIndex++ ) { + if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) { + memory = false; // To prevent further calls using add + break; + } + } + firing = false; + if ( list ) { + if ( stack ) { + if ( stack.length ) { + fire( stack.shift() ); + } + } else if ( memory ) { + list = []; + } else { + self.disable(); + } + } + }, + // Actual Callbacks object + self = { + // Add a callback or a collection of callbacks to the list + add: function() { + if ( list ) { + // First, we save the current length + var start = list.length; + (function add( args ) { + jQuery.each( args, function( _, arg ) { + var type = jQuery.type( arg ); + if ( type === "function" ) { + if ( !options.unique || !self.has( arg ) ) { + list.push( arg ); + } + } else if ( arg && arg.length && type !== "string" ) { + // Inspect recursively + add( arg ); + } + }); + })( arguments ); + // Do we need to add the callbacks to the + // current firing batch? + if ( firing ) { + firingLength = list.length; + // With memory, if we're not firing then + // we should call right away + } else if ( memory ) { + firingStart = start; + fire( memory ); + } + } + return this; + }, + // Remove a callback from the list + remove: function() { + if ( list ) { + jQuery.each( arguments, function( _, arg ) { + var index; + while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { + list.splice( index, 1 ); + // Handle firing indexes + if ( firing ) { + if ( index <= firingLength ) { + firingLength--; + } + if ( index <= firingIndex ) { + firingIndex--; + } + } + } + }); + } + return this; + }, + // Check if a given callback is in the list. + // If no argument is given, return whether or not list has callbacks attached. + has: function( fn ) { + return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length ); + }, + // Remove all callbacks from the list + empty: function() { + list = []; + firingLength = 0; + return this; + }, + // Have the list do nothing anymore + disable: function() { + list = stack = memory = undefined; + return this; + }, + // Is it disabled? + disabled: function() { + return !list; + }, + // Lock the list in its current state + lock: function() { + stack = undefined; + if ( !memory ) { + self.disable(); + } + return this; + }, + // Is it locked? + locked: function() { + return !stack; + }, + // Call all callbacks with the given context and arguments + fireWith: function( context, args ) { + if ( list && ( !fired || stack ) ) { + args = args || []; + args = [ context, args.slice ? args.slice() : args ]; + if ( firing ) { + stack.push( args ); + } else { + fire( args ); + } + } + return this; + }, + // Call all the callbacks with the given arguments + fire: function() { + self.fireWith( this, arguments ); + return this; + }, + // To know if the callbacks have already been called at least once + fired: function() { + return !!fired; + } + }; + + return self; +}; + + +jQuery.extend({ + + Deferred: function( func ) { + var tuples = [ + // action, add listener, listener list, final state + [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ], + [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ], + [ "notify", "progress", jQuery.Callbacks("memory") ] + ], + state = "pending", + promise = { + state: function() { + return state; + }, + always: function() { + deferred.done( arguments ).fail( arguments ); + return this; + }, + then: function( /* fnDone, fnFail, fnProgress */ ) { + var fns = arguments; + return jQuery.Deferred(function( newDefer ) { + jQuery.each( tuples, function( i, tuple ) { + var fn = jQuery.isFunction( fns[ i ] ) && fns[ i ]; + // deferred[ done | fail | progress ] for forwarding actions to newDefer + deferred[ tuple[1] ](function() { + var returned = fn && fn.apply( this, arguments ); + if ( returned && jQuery.isFunction( returned.promise ) ) { + returned.promise() + .done( newDefer.resolve ) + .fail( newDefer.reject ) + .progress( newDefer.notify ); + } else { + newDefer[ tuple[ 0 ] + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments ); + } + }); + }); + fns = null; + }).promise(); + }, + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj ) { + return obj != null ? jQuery.extend( obj, promise ) : promise; + } + }, + deferred = {}; + + // Keep pipe for back-compat + promise.pipe = promise.then; + + // Add list-specific methods + jQuery.each( tuples, function( i, tuple ) { + var list = tuple[ 2 ], + stateString = tuple[ 3 ]; + + // promise[ done | fail | progress ] = list.add + promise[ tuple[1] ] = list.add; + + // Handle state + if ( stateString ) { + list.add(function() { + // state = [ resolved | rejected ] + state = stateString; + + // [ reject_list | resolve_list ].disable; progress_list.lock + }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock ); + } + + // deferred[ resolve | reject | notify ] + deferred[ tuple[0] ] = function() { + deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments ); + return this; + }; + deferred[ tuple[0] + "With" ] = list.fireWith; + }); + + // Make the deferred a promise + promise.promise( deferred ); + + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); + } + + // All done! + return deferred; + }, + + // Deferred helper + when: function( subordinate /* , ..., subordinateN */ ) { + var i = 0, + resolveValues = slice.call( arguments ), + length = resolveValues.length, + + // the count of uncompleted subordinates + remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0, + + // the master Deferred. If resolveValues consist of only a single Deferred, just use that. + deferred = remaining === 1 ? subordinate : jQuery.Deferred(), + + // Update function for both resolve and progress values + updateFunc = function( i, contexts, values ) { + return function( value ) { + contexts[ i ] = this; + values[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; + if ( values === progressValues ) { + deferred.notifyWith( contexts, values ); + + } else if ( !(--remaining) ) { + deferred.resolveWith( contexts, values ); + } + }; + }, + + progressValues, progressContexts, resolveContexts; + + // add listeners to Deferred subordinates; treat others as resolved + if ( length > 1 ) { + progressValues = new Array( length ); + progressContexts = new Array( length ); + resolveContexts = new Array( length ); + for ( ; i < length; i++ ) { + if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) { + resolveValues[ i ].promise() + .done( updateFunc( i, resolveContexts, resolveValues ) ) + .fail( deferred.reject ) + .progress( updateFunc( i, progressContexts, progressValues ) ); + } else { + --remaining; + } + } + } + + // if we're not waiting on anything, resolve the master + if ( !remaining ) { + deferred.resolveWith( resolveContexts, resolveValues ); + } + + return deferred.promise(); + } +}); + + +// The deferred used on DOM ready +var readyList; + +jQuery.fn.ready = function( fn ) { + // Add the callback + jQuery.ready.promise().done( fn ); + + return this; +}; + +jQuery.extend({ + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // A counter to track how many items to wait for before + // the ready event fires. See #6781 + readyWait: 1, + + // Hold (or release) the ready event + holdReady: function( hold ) { + if ( hold ) { + jQuery.readyWait++; + } else { + jQuery.ready( true ); + } + }, + + // Handle when the DOM is ready + ready: function( wait ) { + + // Abort if there are pending holds or we're already ready + if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { + return; + } + + // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). + if ( !document.body ) { + return setTimeout( jQuery.ready ); + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If a normal DOM Ready event fired, decrement, and wait if need be + if ( wait !== true && --jQuery.readyWait > 0 ) { + return; + } + + // If there are functions bound, to execute + readyList.resolveWith( document, [ jQuery ] ); + + // Trigger any bound ready events + if ( jQuery.fn.triggerHandler ) { + jQuery( document ).triggerHandler( "ready" ); + jQuery( document ).off( "ready" ); + } + } +}); + +/** + * Clean-up method for dom ready events + */ +function detach() { + if ( document.addEventListener ) { + document.removeEventListener( "DOMContentLoaded", completed, false ); + window.removeEventListener( "load", completed, false ); + + } else { + document.detachEvent( "onreadystatechange", completed ); + window.detachEvent( "onload", completed ); + } +} + +/** + * The ready event handler and self cleanup method + */ +function completed() { + // readyState === "complete" is good enough for us to call the dom ready in oldIE + if ( document.addEventListener || event.type === "load" || document.readyState === "complete" ) { + detach(); + jQuery.ready(); + } +} + +jQuery.ready.promise = function( obj ) { + if ( !readyList ) { + + readyList = jQuery.Deferred(); + + // Catch cases where $(document).ready() is called after the browser event has already occurred. + // we once tried to use readyState "interactive" here, but it caused issues like the one + // discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15 + if ( document.readyState === "complete" ) { + // Handle it asynchronously to allow scripts the opportunity to delay ready + setTimeout( jQuery.ready ); + + // Standards-based browsers support DOMContentLoaded + } else if ( document.addEventListener ) { + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", completed, false ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", completed, false ); + + // If IE event model is used + } else { + // Ensure firing before onload, maybe late but safe also for iframes + document.attachEvent( "onreadystatechange", completed ); + + // A fallback to window.onload, that will always work + window.attachEvent( "onload", completed ); + + // If IE and not a frame + // continually check to see if the document is ready + var top = false; + + try { + top = window.frameElement == null && document.documentElement; + } catch(e) {} + + if ( top && top.doScroll ) { + (function doScrollCheck() { + if ( !jQuery.isReady ) { + + try { + // Use the trick by Diego Perini + // http://javascript.nwbox.com/IEContentLoaded/ + top.doScroll("left"); + } catch(e) { + return setTimeout( doScrollCheck, 50 ); + } + + // detach all dom ready events + detach(); + + // and execute any waiting functions + jQuery.ready(); + } + })(); + } + } + } + return readyList.promise( obj ); +}; + + +var strundefined = typeof undefined; + + + +// Support: IE<9 +// Iteration over object's inherited properties before its own +var i; +for ( i in jQuery( support ) ) { + break; +} +support.ownLast = i !== "0"; + +// Note: most support tests are defined in their respective modules. +// false until the test is run +support.inlineBlockNeedsLayout = false; + +// Execute ASAP in case we need to set body.style.zoom +jQuery(function() { + // Minified: var a,b,c,d + var val, div, body, container; + + body = document.getElementsByTagName( "body" )[ 0 ]; + if ( !body || !body.style ) { + // Return for frameset docs that don't have a body + return; + } + + // Setup + div = document.createElement( "div" ); + container = document.createElement( "div" ); + container.style.cssText = "position:absolute;border:0;width:0;height:0;top:0;left:-9999px"; + body.appendChild( container ).appendChild( div ); + + if ( typeof div.style.zoom !== strundefined ) { + // Support: IE<8 + // Check if natively block-level elements act like inline-block + // elements when setting their display to 'inline' and giving + // them layout + div.style.cssText = "display:inline;margin:0;border:0;padding:1px;width:1px;zoom:1"; + + support.inlineBlockNeedsLayout = val = div.offsetWidth === 3; + if ( val ) { + // Prevent IE 6 from affecting layout for positioned elements #11048 + // Prevent IE from shrinking the body in IE 7 mode #12869 + // Support: IE<8 + body.style.zoom = 1; + } + } + + body.removeChild( container ); +}); + + + + +(function() { + var div = document.createElement( "div" ); + + // Execute the test only if not already executed in another module. + if (support.deleteExpando == null) { + // Support: IE<9 + support.deleteExpando = true; + try { + delete div.test; + } catch( e ) { + support.deleteExpando = false; + } + } + + // Null elements to avoid leaks in IE. + div = null; +})(); + + +/** + * Determines whether an object can have data + */ +jQuery.acceptData = function( elem ) { + var noData = jQuery.noData[ (elem.nodeName + " ").toLowerCase() ], + nodeType = +elem.nodeType || 1; + + // Do not set data on non-element DOM nodes because it will not be cleared (#8335). + return nodeType !== 1 && nodeType !== 9 ? + false : + + // Nodes accept data unless otherwise specified; rejection can be conditional + !noData || noData !== true && elem.getAttribute("classid") === noData; +}; + + +var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, + rmultiDash = /([A-Z])/g; + +function dataAttr( elem, key, data ) { + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + + var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase(); + + data = elem.getAttribute( name ); + + if ( typeof data === "string" ) { + try { + data = data === "true" ? true : + data === "false" ? false : + data === "null" ? null : + // Only convert to a number if it doesn't change the string + +data + "" === data ? +data : + rbrace.test( data ) ? jQuery.parseJSON( data ) : + data; + } catch( e ) {} + + // Make sure we set the data so it isn't changed later + jQuery.data( elem, key, data ); + + } else { + data = undefined; + } + } + + return data; +} + +// checks a cache object for emptiness +function isEmptyDataObject( obj ) { + var name; + for ( name in obj ) { + + // if the public data object is empty, the private is still empty + if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) { + continue; + } + if ( name !== "toJSON" ) { + return false; + } + } + + return true; +} + +function internalData( elem, name, data, pvt /* Internal Use Only */ ) { + if ( !jQuery.acceptData( elem ) ) { + return; + } + + var ret, thisCache, + internalKey = jQuery.expando, + + // We have to handle DOM nodes and JS objects differently because IE6-7 + // can't GC object references properly across the DOM-JS boundary + isNode = elem.nodeType, + + // Only DOM nodes need the global jQuery cache; JS object data is + // attached directly to the object so GC can occur automatically + cache = isNode ? jQuery.cache : elem, + + // Only defining an ID for JS objects if its cache already exists allows + // the code to shortcut on the same path as a DOM node with no cache + id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey; + + // Avoid doing any more work than we need to when trying to get data on an + // object that has no data at all + if ( (!id || !cache[id] || (!pvt && !cache[id].data)) && data === undefined && typeof name === "string" ) { + return; + } + + if ( !id ) { + // Only DOM nodes need a new unique ID for each element since their data + // ends up in the global cache + if ( isNode ) { + id = elem[ internalKey ] = deletedIds.pop() || jQuery.guid++; + } else { + id = internalKey; + } + } + + if ( !cache[ id ] ) { + // Avoid exposing jQuery metadata on plain JS objects when the object + // is serialized using JSON.stringify + cache[ id ] = isNode ? {} : { toJSON: jQuery.noop }; + } + + // An object can be passed to jQuery.data instead of a key/value pair; this gets + // shallow copied over onto the existing cache + if ( typeof name === "object" || typeof name === "function" ) { + if ( pvt ) { + cache[ id ] = jQuery.extend( cache[ id ], name ); + } else { + cache[ id ].data = jQuery.extend( cache[ id ].data, name ); + } + } + + thisCache = cache[ id ]; + + // jQuery data() is stored in a separate object inside the object's internal data + // cache in order to avoid key collisions between internal data and user-defined + // data. + if ( !pvt ) { + if ( !thisCache.data ) { + thisCache.data = {}; + } + + thisCache = thisCache.data; + } + + if ( data !== undefined ) { + thisCache[ jQuery.camelCase( name ) ] = data; + } + + // Check for both converted-to-camel and non-converted data property names + // If a data property was specified + if ( typeof name === "string" ) { + + // First Try to find as-is property data + ret = thisCache[ name ]; + + // Test for null|undefined property data + if ( ret == null ) { + + // Try to find the camelCased property + ret = thisCache[ jQuery.camelCase( name ) ]; + } + } else { + ret = thisCache; + } + + return ret; +} + +function internalRemoveData( elem, name, pvt ) { + if ( !jQuery.acceptData( elem ) ) { + return; + } + + var thisCache, i, + isNode = elem.nodeType, + + // See jQuery.data for more information + cache = isNode ? jQuery.cache : elem, + id = isNode ? elem[ jQuery.expando ] : jQuery.expando; + + // If there is already no cache entry for this object, there is no + // purpose in continuing + if ( !cache[ id ] ) { + return; + } + + if ( name ) { + + thisCache = pvt ? cache[ id ] : cache[ id ].data; + + if ( thisCache ) { + + // Support array or space separated string names for data keys + if ( !jQuery.isArray( name ) ) { + + // try the string as a key before any manipulation + if ( name in thisCache ) { + name = [ name ]; + } else { + + // split the camel cased version by spaces unless a key with the spaces exists + name = jQuery.camelCase( name ); + if ( name in thisCache ) { + name = [ name ]; + } else { + name = name.split(" "); + } + } + } else { + // If "name" is an array of keys... + // When data is initially created, via ("key", "val") signature, + // keys will be converted to camelCase. + // Since there is no way to tell _how_ a key was added, remove + // both plain key and camelCase key. #12786 + // This will only penalize the array argument path. + name = name.concat( jQuery.map( name, jQuery.camelCase ) ); + } + + i = name.length; + while ( i-- ) { + delete thisCache[ name[i] ]; + } + + // If there is no data left in the cache, we want to continue + // and let the cache object itself get destroyed + if ( pvt ? !isEmptyDataObject(thisCache) : !jQuery.isEmptyObject(thisCache) ) { + return; + } + } + } + + // See jQuery.data for more information + if ( !pvt ) { + delete cache[ id ].data; + + // Don't destroy the parent cache unless the internal data object + // had been the only thing left in it + if ( !isEmptyDataObject( cache[ id ] ) ) { + return; + } + } + + // Destroy the cache + if ( isNode ) { + jQuery.cleanData( [ elem ], true ); + + // Use delete when supported for expandos or `cache` is not a window per isWindow (#10080) + /* jshint eqeqeq: false */ + } else if ( support.deleteExpando || cache != cache.window ) { + /* jshint eqeqeq: true */ + delete cache[ id ]; + + // When all else fails, null + } else { + cache[ id ] = null; + } +} + +jQuery.extend({ + cache: {}, + + // The following elements (space-suffixed to avoid Object.prototype collisions) + // throw uncatchable exceptions if you attempt to set expando properties + noData: { + "applet ": true, + "embed ": true, + // ...but Flash objects (which have this classid) *can* handle expandos + "object ": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" + }, + + hasData: function( elem ) { + elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ]; + return !!elem && !isEmptyDataObject( elem ); + }, + + data: function( elem, name, data ) { + return internalData( elem, name, data ); + }, + + removeData: function( elem, name ) { + return internalRemoveData( elem, name ); + }, + + // For internal use only. + _data: function( elem, name, data ) { + return internalData( elem, name, data, true ); + }, + + _removeData: function( elem, name ) { + return internalRemoveData( elem, name, true ); + } +}); + +jQuery.fn.extend({ + data: function( key, value ) { + var i, name, data, + elem = this[0], + attrs = elem && elem.attributes; + + // Special expections of .data basically thwart jQuery.access, + // so implement the relevant behavior ourselves + + // Gets all values + if ( key === undefined ) { + if ( this.length ) { + data = jQuery.data( elem ); + + if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) { + i = attrs.length; + while ( i-- ) { + + // Support: IE11+ + // The attrs elements can be null (#14894) + if ( attrs[ i ] ) { + name = attrs[ i ].name; + if ( name.indexOf( "data-" ) === 0 ) { + name = jQuery.camelCase( name.slice(5) ); + dataAttr( elem, name, data[ name ] ); + } + } + } + jQuery._data( elem, "parsedAttrs", true ); + } + } + + return data; + } + + // Sets multiple values + if ( typeof key === "object" ) { + return this.each(function() { + jQuery.data( this, key ); + }); + } + + return arguments.length > 1 ? + + // Sets one value + this.each(function() { + jQuery.data( this, key, value ); + }) : + + // Gets one value + // Try to fetch any internally stored data first + elem ? dataAttr( elem, key, jQuery.data( elem, key ) ) : undefined; + }, + + removeData: function( key ) { + return this.each(function() { + jQuery.removeData( this, key ); + }); + } +}); + + +jQuery.extend({ + queue: function( elem, type, data ) { + var queue; + + if ( elem ) { + type = ( type || "fx" ) + "queue"; + queue = jQuery._data( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( data ) { + if ( !queue || jQuery.isArray(data) ) { + queue = jQuery._data( elem, type, jQuery.makeArray(data) ); + } else { + queue.push( data ); + } + } + return queue || []; + } + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), + startLength = queue.length, + fn = queue.shift(), + hooks = jQuery._queueHooks( elem, type ), + next = function() { + jQuery.dequeue( elem, type ); + }; + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + startLength--; + } + + if ( fn ) { + + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift( "inprogress" ); + } + + // clear up the last queue stop function + delete hooks.stop; + fn.call( elem, next, hooks ); + } + + if ( !startLength && hooks ) { + hooks.empty.fire(); + } + }, + + // not intended for public consumption - generates a queueHooks object, or returns the current one + _queueHooks: function( elem, type ) { + var key = type + "queueHooks"; + return jQuery._data( elem, key ) || jQuery._data( elem, key, { + empty: jQuery.Callbacks("once memory").add(function() { + jQuery._removeData( elem, type + "queue" ); + jQuery._removeData( elem, key ); + }) + }); + } +}); + +jQuery.fn.extend({ + queue: function( type, data ) { + var setter = 2; + + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + setter--; + } + + if ( arguments.length < setter ) { + return jQuery.queue( this[0], type ); + } + + return data === undefined ? + this : + this.each(function() { + var queue = jQuery.queue( this, type, data ); + + // ensure a hooks for this queue + jQuery._queueHooks( this, type ); + + if ( type === "fx" && queue[0] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + }); + }, + dequeue: function( type ) { + return this.each(function() { + jQuery.dequeue( this, type ); + }); + }, + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + }, + // Get a promise resolved when queues of a certain type + // are emptied (fx is the type by default) + promise: function( type, obj ) { + var tmp, + count = 1, + defer = jQuery.Deferred(), + elements = this, + i = this.length, + resolve = function() { + if ( !( --count ) ) { + defer.resolveWith( elements, [ elements ] ); + } + }; + + if ( typeof type !== "string" ) { + obj = type; + type = undefined; + } + type = type || "fx"; + + while ( i-- ) { + tmp = jQuery._data( elements[ i ], type + "queueHooks" ); + if ( tmp && tmp.empty ) { + count++; + tmp.empty.add( resolve ); + } + } + resolve(); + return defer.promise( obj ); + } +}); +var pnum = (/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/).source; + +var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; + +var isHidden = function( elem, el ) { + // isHidden might be called from jQuery#filter function; + // in that case, element will be second argument + elem = el || elem; + return jQuery.css( elem, "display" ) === "none" || !jQuery.contains( elem.ownerDocument, elem ); + }; + + + +// Multifunctional method to get and set values of a collection +// The value/s can optionally be executed if it's a function +var access = jQuery.access = function( elems, fn, key, value, chainable, emptyGet, raw ) { + var i = 0, + length = elems.length, + bulk = key == null; + + // Sets many values + if ( jQuery.type( key ) === "object" ) { + chainable = true; + for ( i in key ) { + jQuery.access( elems, fn, i, key[i], true, emptyGet, raw ); + } + + // Sets one value + } else if ( value !== undefined ) { + chainable = true; + + if ( !jQuery.isFunction( value ) ) { + raw = true; + } + + if ( bulk ) { + // Bulk operations run against the entire set + if ( raw ) { + fn.call( elems, value ); + fn = null; + + // ...except when executing function values + } else { + bulk = fn; + fn = function( elem, key, value ) { + return bulk.call( jQuery( elem ), value ); + }; + } + } + + if ( fn ) { + for ( ; i < length; i++ ) { + fn( elems[i], key, raw ? value : value.call( elems[i], i, fn( elems[i], key ) ) ); + } + } + } + + return chainable ? + elems : + + // Gets + bulk ? + fn.call( elems ) : + length ? fn( elems[0], key ) : emptyGet; +}; +var rcheckableType = (/^(?:checkbox|radio)$/i); + + + +(function() { + // Minified: var a,b,c + var input = document.createElement( "input" ), + div = document.createElement( "div" ), + fragment = document.createDocumentFragment(); + + // Setup + div.innerHTML = "
a"; + + // IE strips leading whitespace when .innerHTML is used + support.leadingWhitespace = div.firstChild.nodeType === 3; + + // Make sure that tbody elements aren't automatically inserted + // IE will insert them into empty tables + support.tbody = !div.getElementsByTagName( "tbody" ).length; + + // Make sure that link elements get serialized correctly by innerHTML + // This requires a wrapper element in IE + support.htmlSerialize = !!div.getElementsByTagName( "link" ).length; + + // Makes sure cloning an html5 element does not cause problems + // Where outerHTML is undefined, this still works + support.html5Clone = + document.createElement( "nav" ).cloneNode( true ).outerHTML !== "<:nav>"; + + // Check if a disconnected checkbox will retain its checked + // value of true after appended to the DOM (IE6/7) + input.type = "checkbox"; + input.checked = true; + fragment.appendChild( input ); + support.appendChecked = input.checked; + + // Make sure textarea (and checkbox) defaultValue is properly cloned + // Support: IE6-IE11+ + div.innerHTML = ""; + support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; + + // #11217 - WebKit loses check when the name is after the checked attribute + fragment.appendChild( div ); + div.innerHTML = ""; + + // Support: Safari 5.1, iOS 5.1, Android 4.x, Android 2.3 + // old WebKit doesn't clone checked state correctly in fragments + support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; + + // Support: IE<9 + // Opera does not clone events (and typeof div.attachEvent === undefined). + // IE9-10 clones events bound via attachEvent, but they don't trigger with .click() + support.noCloneEvent = true; + if ( div.attachEvent ) { + div.attachEvent( "onclick", function() { + support.noCloneEvent = false; + }); + + div.cloneNode( true ).click(); + } + + // Execute the test only if not already executed in another module. + if (support.deleteExpando == null) { + // Support: IE<9 + support.deleteExpando = true; + try { + delete div.test; + } catch( e ) { + support.deleteExpando = false; + } + } +})(); + + +(function() { + var i, eventName, + div = document.createElement( "div" ); + + // Support: IE<9 (lack submit/change bubble), Firefox 23+ (lack focusin event) + for ( i in { submit: true, change: true, focusin: true }) { + eventName = "on" + i; + + if ( !(support[ i + "Bubbles" ] = eventName in window) ) { + // Beware of CSP restrictions (https://developer.mozilla.org/en/Security/CSP) + div.setAttribute( eventName, "t" ); + support[ i + "Bubbles" ] = div.attributes[ eventName ].expando === false; + } + } + + // Null elements to avoid leaks in IE. + div = null; +})(); + + +var rformElems = /^(?:input|select|textarea)$/i, + rkeyEvent = /^key/, + rmouseEvent = /^(?:mouse|pointer|contextmenu)|click/, + rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, + rtypenamespace = /^([^.]*)(?:\.(.+)|)$/; + +function returnTrue() { + return true; +} + +function returnFalse() { + return false; +} + +function safeActiveElement() { + try { + return document.activeElement; + } catch ( err ) { } +} + +/* + * Helper functions for managing events -- not part of the public interface. + * Props to Dean Edwards' addEvent library for many of the ideas. + */ +jQuery.event = { + + global: {}, + + add: function( elem, types, handler, data, selector ) { + var tmp, events, t, handleObjIn, + special, eventHandle, handleObj, + handlers, type, namespaces, origType, + elemData = jQuery._data( elem ); + + // Don't attach events to noData or text/comment nodes (but allow plain objects) + if ( !elemData ) { + return; + } + + // Caller can pass in an object of custom data in lieu of the handler + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + selector = handleObjIn.selector; + } + + // Make sure that the handler has a unique ID, used to find/remove it later + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure and main handler, if this is the first + if ( !(events = elemData.events) ) { + events = elemData.events = {}; + } + if ( !(eventHandle = elemData.handle) ) { + eventHandle = elemData.handle = function( e ) { + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded + return typeof jQuery !== strundefined && (!e || jQuery.event.triggered !== e.type) ? + jQuery.event.dispatch.apply( eventHandle.elem, arguments ) : + undefined; + }; + // Add elem as a property of the handle fn to prevent a memory leak with IE non-native events + eventHandle.elem = elem; + } + + // Handle multiple events separated by a space + types = ( types || "" ).match( rnotwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[t] ) || []; + type = origType = tmp[1]; + namespaces = ( tmp[2] || "" ).split( "." ).sort(); + + // There *must* be a type, no attaching namespace-only handlers + if ( !type ) { + continue; + } + + // If event changes its type, use the special event handlers for the changed type + special = jQuery.event.special[ type ] || {}; + + // If selector defined, determine special event api type, otherwise given type + type = ( selector ? special.delegateType : special.bindType ) || type; + + // Update special based on newly reset type + special = jQuery.event.special[ type ] || {}; + + // handleObj is passed to all event handlers + handleObj = jQuery.extend({ + type: type, + origType: origType, + data: data, + handler: handler, + guid: handler.guid, + selector: selector, + needsContext: selector && jQuery.expr.match.needsContext.test( selector ), + namespace: namespaces.join(".") + }, handleObjIn ); + + // Init the event handler queue if we're the first + if ( !(handlers = events[ type ]) ) { + handlers = events[ type ] = []; + handlers.delegateCount = 0; + + // Only use addEventListener/attachEvent if the special events handler returns false + if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + // Bind the global event handler to the element + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle, false ); + + } else if ( elem.attachEvent ) { + elem.attachEvent( "on" + type, eventHandle ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add to the element's handler list, delegates in front + if ( selector ) { + handlers.splice( handlers.delegateCount++, 0, handleObj ); + } else { + handlers.push( handleObj ); + } + + // Keep track of which events have ever been used, for event optimization + jQuery.event.global[ type ] = true; + } + + // Nullify elem to prevent memory leaks in IE + elem = null; + }, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, selector, mappedTypes ) { + var j, handleObj, tmp, + origCount, t, events, + special, handlers, type, + namespaces, origType, + elemData = jQuery.hasData( elem ) && jQuery._data( elem ); + + if ( !elemData || !(events = elemData.events) ) { + return; + } + + // Once for each type.namespace in types; type may be omitted + types = ( types || "" ).match( rnotwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[t] ) || []; + type = origType = tmp[1]; + namespaces = ( tmp[2] || "" ).split( "." ).sort(); + + // Unbind all events (on this namespace, if provided) for the element + if ( !type ) { + for ( type in events ) { + jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); + } + continue; + } + + special = jQuery.event.special[ type ] || {}; + type = ( selector ? special.delegateType : special.bindType ) || type; + handlers = events[ type ] || []; + tmp = tmp[2] && new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ); + + // Remove matching events + origCount = j = handlers.length; + while ( j-- ) { + handleObj = handlers[ j ]; + + if ( ( mappedTypes || origType === handleObj.origType ) && + ( !handler || handler.guid === handleObj.guid ) && + ( !tmp || tmp.test( handleObj.namespace ) ) && + ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) { + handlers.splice( j, 1 ); + + if ( handleObj.selector ) { + handlers.delegateCount--; + } + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + } + + // Remove generic event handler if we removed something and no more handlers exist + // (avoids potential for endless recursion during removal of special event handlers) + if ( origCount && !handlers.length ) { + if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) { + jQuery.removeEvent( elem, type, elemData.handle ); + } + + delete events[ type ]; + } + } + + // Remove the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + delete elemData.handle; + + // removeData also checks for emptiness and clears the expando if empty + // so use it instead of delete + jQuery._removeData( elem, "events" ); + } + }, + + trigger: function( event, data, elem, onlyHandlers ) { + var handle, ontype, cur, + bubbleType, special, tmp, i, + eventPath = [ elem || document ], + type = hasOwn.call( event, "type" ) ? event.type : event, + namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split(".") : []; + + cur = tmp = elem = elem || document; + + // Don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + // focus/blur morphs to focusin/out; ensure we're not firing them right now + if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { + return; + } + + if ( type.indexOf(".") >= 0 ) { + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split("."); + type = namespaces.shift(); + namespaces.sort(); + } + ontype = type.indexOf(":") < 0 && "on" + type; + + // Caller can pass in a jQuery.Event object, Object, or just an event type string + event = event[ jQuery.expando ] ? + event : + new jQuery.Event( type, typeof event === "object" && event ); + + // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) + event.isTrigger = onlyHandlers ? 2 : 3; + event.namespace = namespaces.join("."); + event.namespace_re = event.namespace ? + new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ) : + null; + + // Clean up the event in case it is being reused + event.result = undefined; + if ( !event.target ) { + event.target = elem; + } + + // Clone any incoming data and prepend the event, creating the handler arg list + data = data == null ? + [ event ] : + jQuery.makeArray( data, [ event ] ); + + // Allow special events to draw outside the lines + special = jQuery.event.special[ type ] || {}; + if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { + return; + } + + // Determine event propagation path in advance, per W3C events spec (#9951) + // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) + if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) { + + bubbleType = special.delegateType || type; + if ( !rfocusMorph.test( bubbleType + type ) ) { + cur = cur.parentNode; + } + for ( ; cur; cur = cur.parentNode ) { + eventPath.push( cur ); + tmp = cur; + } + + // Only add window if we got to document (e.g., not plain obj or detached DOM) + if ( tmp === (elem.ownerDocument || document) ) { + eventPath.push( tmp.defaultView || tmp.parentWindow || window ); + } + } + + // Fire handlers on the event path + i = 0; + while ( (cur = eventPath[i++]) && !event.isPropagationStopped() ) { + + event.type = i > 1 ? + bubbleType : + special.bindType || type; + + // jQuery handler + handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" ); + if ( handle ) { + handle.apply( cur, data ); + } + + // Native handler + handle = ontype && cur[ ontype ]; + if ( handle && handle.apply && jQuery.acceptData( cur ) ) { + event.result = handle.apply( cur, data ); + if ( event.result === false ) { + event.preventDefault(); + } + } + } + event.type = type; + + // If nobody prevented the default action, do it now + if ( !onlyHandlers && !event.isDefaultPrevented() ) { + + if ( (!special._default || special._default.apply( eventPath.pop(), data ) === false) && + jQuery.acceptData( elem ) ) { + + // Call a native DOM method on the target with the same name name as the event. + // Can't use an .isFunction() check here because IE6/7 fails that test. + // Don't do default actions on window, that's where global variables be (#6170) + if ( ontype && elem[ type ] && !jQuery.isWindow( elem ) ) { + + // Don't re-trigger an onFOO event when we call its FOO() method + tmp = elem[ ontype ]; + + if ( tmp ) { + elem[ ontype ] = null; + } + + // Prevent re-triggering of the same event, since we already bubbled it above + jQuery.event.triggered = type; + try { + elem[ type ](); + } catch ( e ) { + // IE<9 dies on focus/blur to hidden element (#1486,#12518) + // only reproducible on winXP IE8 native, not IE9 in IE8 mode + } + jQuery.event.triggered = undefined; + + if ( tmp ) { + elem[ ontype ] = tmp; + } + } + } + } + + return event.result; + }, + + dispatch: function( event ) { + + // Make a writable jQuery.Event from the native event object + event = jQuery.event.fix( event ); + + var i, ret, handleObj, matched, j, + handlerQueue = [], + args = slice.call( arguments ), + handlers = ( jQuery._data( this, "events" ) || {} )[ event.type ] || [], + special = jQuery.event.special[ event.type ] || {}; + + // Use the fix-ed jQuery.Event rather than the (read-only) native event + args[0] = event; + event.delegateTarget = this; + + // Call the preDispatch hook for the mapped type, and let it bail if desired + if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { + return; + } + + // Determine handlers + handlerQueue = jQuery.event.handlers.call( this, event, handlers ); + + // Run delegates first; they may want to stop propagation beneath us + i = 0; + while ( (matched = handlerQueue[ i++ ]) && !event.isPropagationStopped() ) { + event.currentTarget = matched.elem; + + j = 0; + while ( (handleObj = matched.handlers[ j++ ]) && !event.isImmediatePropagationStopped() ) { + + // Triggered event must either 1) have no namespace, or + // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace). + if ( !event.namespace_re || event.namespace_re.test( handleObj.namespace ) ) { + + event.handleObj = handleObj; + event.data = handleObj.data; + + ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler ) + .apply( matched.elem, args ); + + if ( ret !== undefined ) { + if ( (event.result = ret) === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + } + } + } + + // Call the postDispatch hook for the mapped type + if ( special.postDispatch ) { + special.postDispatch.call( this, event ); + } + + return event.result; + }, + + handlers: function( event, handlers ) { + var sel, handleObj, matches, i, + handlerQueue = [], + delegateCount = handlers.delegateCount, + cur = event.target; + + // Find delegate handlers + // Black-hole SVG instance trees (#13180) + // Avoid non-left-click bubbling in Firefox (#3861) + if ( delegateCount && cur.nodeType && (!event.button || event.type !== "click") ) { + + /* jshint eqeqeq: false */ + for ( ; cur != this; cur = cur.parentNode || this ) { + /* jshint eqeqeq: true */ + + // Don't check non-elements (#13208) + // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) + if ( cur.nodeType === 1 && (cur.disabled !== true || event.type !== "click") ) { + matches = []; + for ( i = 0; i < delegateCount; i++ ) { + handleObj = handlers[ i ]; + + // Don't conflict with Object.prototype properties (#13203) + sel = handleObj.selector + " "; + + if ( matches[ sel ] === undefined ) { + matches[ sel ] = handleObj.needsContext ? + jQuery( sel, this ).index( cur ) >= 0 : + jQuery.find( sel, this, null, [ cur ] ).length; + } + if ( matches[ sel ] ) { + matches.push( handleObj ); + } + } + if ( matches.length ) { + handlerQueue.push({ elem: cur, handlers: matches }); + } + } + } + } + + // Add the remaining (directly-bound) handlers + if ( delegateCount < handlers.length ) { + handlerQueue.push({ elem: this, handlers: handlers.slice( delegateCount ) }); + } + + return handlerQueue; + }, + + fix: function( event ) { + if ( event[ jQuery.expando ] ) { + return event; + } + + // Create a writable copy of the event object and normalize some properties + var i, prop, copy, + type = event.type, + originalEvent = event, + fixHook = this.fixHooks[ type ]; + + if ( !fixHook ) { + this.fixHooks[ type ] = fixHook = + rmouseEvent.test( type ) ? this.mouseHooks : + rkeyEvent.test( type ) ? this.keyHooks : + {}; + } + copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props; + + event = new jQuery.Event( originalEvent ); + + i = copy.length; + while ( i-- ) { + prop = copy[ i ]; + event[ prop ] = originalEvent[ prop ]; + } + + // Support: IE<9 + // Fix target property (#1925) + if ( !event.target ) { + event.target = originalEvent.srcElement || document; + } + + // Support: Chrome 23+, Safari? + // Target should not be a text node (#504, #13143) + if ( event.target.nodeType === 3 ) { + event.target = event.target.parentNode; + } + + // Support: IE<9 + // For mouse/key events, metaKey==false if it's undefined (#3368, #11328) + event.metaKey = !!event.metaKey; + + return fixHook.filter ? fixHook.filter( event, originalEvent ) : event; + }, + + // Includes some event props shared by KeyEvent and MouseEvent + props: "altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "), + + fixHooks: {}, + + keyHooks: { + props: "char charCode key keyCode".split(" "), + filter: function( event, original ) { + + // Add which for key events + if ( event.which == null ) { + event.which = original.charCode != null ? original.charCode : original.keyCode; + } + + return event; + } + }, + + mouseHooks: { + props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "), + filter: function( event, original ) { + var body, eventDoc, doc, + button = original.button, + fromElement = original.fromElement; + + // Calculate pageX/Y if missing and clientX/Y available + if ( event.pageX == null && original.clientX != null ) { + eventDoc = event.target.ownerDocument || document; + doc = eventDoc.documentElement; + body = eventDoc.body; + + event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 ); + event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 ); + } + + // Add relatedTarget, if necessary + if ( !event.relatedTarget && fromElement ) { + event.relatedTarget = fromElement === event.target ? original.toElement : fromElement; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + // Note: button is not normalized, so don't use it + if ( !event.which && button !== undefined ) { + event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) ); + } + + return event; + } + }, + + special: { + load: { + // Prevent triggered image.load events from bubbling to window.load + noBubble: true + }, + focus: { + // Fire native event if possible so blur/focus sequence is correct + trigger: function() { + if ( this !== safeActiveElement() && this.focus ) { + try { + this.focus(); + return false; + } catch ( e ) { + // Support: IE<9 + // If we error on focus to hidden element (#1486, #12518), + // let .trigger() run the handlers + } + } + }, + delegateType: "focusin" + }, + blur: { + trigger: function() { + if ( this === safeActiveElement() && this.blur ) { + this.blur(); + return false; + } + }, + delegateType: "focusout" + }, + click: { + // For checkbox, fire native event so checked state will be right + trigger: function() { + if ( jQuery.nodeName( this, "input" ) && this.type === "checkbox" && this.click ) { + this.click(); + return false; + } + }, + + // For cross-browser consistency, don't fire native .click() on links + _default: function( event ) { + return jQuery.nodeName( event.target, "a" ); + } + }, + + beforeunload: { + postDispatch: function( event ) { + + // Support: Firefox 20+ + // Firefox doesn't alert if the returnValue field is not set. + if ( event.result !== undefined && event.originalEvent ) { + event.originalEvent.returnValue = event.result; + } + } + } + }, + + simulate: function( type, elem, event, bubble ) { + // Piggyback on a donor event to simulate a different one. + // Fake originalEvent to avoid donor's stopPropagation, but if the + // simulated event prevents default then we do the same on the donor. + var e = jQuery.extend( + new jQuery.Event(), + event, + { + type: type, + isSimulated: true, + originalEvent: {} + } + ); + if ( bubble ) { + jQuery.event.trigger( e, null, elem ); + } else { + jQuery.event.dispatch.call( elem, e ); + } + if ( e.isDefaultPrevented() ) { + event.preventDefault(); + } + } +}; + +jQuery.removeEvent = document.removeEventListener ? + function( elem, type, handle ) { + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle, false ); + } + } : + function( elem, type, handle ) { + var name = "on" + type; + + if ( elem.detachEvent ) { + + // #8545, #7054, preventing memory leaks for custom events in IE6-8 + // detachEvent needed property on element, by name of that event, to properly expose it to GC + if ( typeof elem[ name ] === strundefined ) { + elem[ name ] = null; + } + + elem.detachEvent( name, handle ); + } + }; + +jQuery.Event = function( src, props ) { + // Allow instantiation without the 'new' keyword + if ( !(this instanceof jQuery.Event) ) { + return new jQuery.Event( src, props ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = src.defaultPrevented || + src.defaultPrevented === undefined && + // Support: IE < 9, Android < 4.0 + src.returnValue === false ? + returnTrue : + returnFalse; + + // Event type + } else { + this.type = src; + } + + // Put explicitly provided properties onto the event object + if ( props ) { + jQuery.extend( this, props ); + } + + // Create a timestamp if incoming event doesn't have one + this.timeStamp = src && src.timeStamp || jQuery.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; +}; + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse, + + preventDefault: function() { + var e = this.originalEvent; + + this.isDefaultPrevented = returnTrue; + if ( !e ) { + return; + } + + // If preventDefault exists, run it on the original event + if ( e.preventDefault ) { + e.preventDefault(); + + // Support: IE + // Otherwise set the returnValue property of the original event to false + } else { + e.returnValue = false; + } + }, + stopPropagation: function() { + var e = this.originalEvent; + + this.isPropagationStopped = returnTrue; + if ( !e ) { + return; + } + // If stopPropagation exists, run it on the original event + if ( e.stopPropagation ) { + e.stopPropagation(); + } + + // Support: IE + // Set the cancelBubble property of the original event to true + e.cancelBubble = true; + }, + stopImmediatePropagation: function() { + var e = this.originalEvent; + + this.isImmediatePropagationStopped = returnTrue; + + if ( e && e.stopImmediatePropagation ) { + e.stopImmediatePropagation(); + } + + this.stopPropagation(); + } +}; + +// Create mouseenter/leave events using mouseover/out and event-time checks +jQuery.each({ + mouseenter: "mouseover", + mouseleave: "mouseout", + pointerenter: "pointerover", + pointerleave: "pointerout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + delegateType: fix, + bindType: fix, + + handle: function( event ) { + var ret, + target = this, + related = event.relatedTarget, + handleObj = event.handleObj; + + // For mousenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if ( !related || (related !== target && !jQuery.contains( target, related )) ) { + event.type = handleObj.origType; + ret = handleObj.handler.apply( this, arguments ); + event.type = fix; + } + return ret; + } + }; +}); + +// IE submit delegation +if ( !support.submitBubbles ) { + + jQuery.event.special.submit = { + setup: function() { + // Only need this for delegated form submit events + if ( jQuery.nodeName( this, "form" ) ) { + return false; + } + + // Lazy-add a submit handler when a descendant form may potentially be submitted + jQuery.event.add( this, "click._submit keypress._submit", function( e ) { + // Node name check avoids a VML-related crash in IE (#9807) + var elem = e.target, + form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined; + if ( form && !jQuery._data( form, "submitBubbles" ) ) { + jQuery.event.add( form, "submit._submit", function( event ) { + event._submit_bubble = true; + }); + jQuery._data( form, "submitBubbles", true ); + } + }); + // return undefined since we don't need an event listener + }, + + postDispatch: function( event ) { + // If form was submitted by the user, bubble the event up the tree + if ( event._submit_bubble ) { + delete event._submit_bubble; + if ( this.parentNode && !event.isTrigger ) { + jQuery.event.simulate( "submit", this.parentNode, event, true ); + } + } + }, + + teardown: function() { + // Only need this for delegated form submit events + if ( jQuery.nodeName( this, "form" ) ) { + return false; + } + + // Remove delegated handlers; cleanData eventually reaps submit handlers attached above + jQuery.event.remove( this, "._submit" ); + } + }; +} + +// IE change delegation and checkbox/radio fix +if ( !support.changeBubbles ) { + + jQuery.event.special.change = { + + setup: function() { + + if ( rformElems.test( this.nodeName ) ) { + // IE doesn't fire change on a check/radio until blur; trigger it on click + // after a propertychange. Eat the blur-change in special.change.handle. + // This still fires onchange a second time for check/radio after blur. + if ( this.type === "checkbox" || this.type === "radio" ) { + jQuery.event.add( this, "propertychange._change", function( event ) { + if ( event.originalEvent.propertyName === "checked" ) { + this._just_changed = true; + } + }); + jQuery.event.add( this, "click._change", function( event ) { + if ( this._just_changed && !event.isTrigger ) { + this._just_changed = false; + } + // Allow triggered, simulated change events (#11500) + jQuery.event.simulate( "change", this, event, true ); + }); + } + return false; + } + // Delegated event; lazy-add a change handler on descendant inputs + jQuery.event.add( this, "beforeactivate._change", function( e ) { + var elem = e.target; + + if ( rformElems.test( elem.nodeName ) && !jQuery._data( elem, "changeBubbles" ) ) { + jQuery.event.add( elem, "change._change", function( event ) { + if ( this.parentNode && !event.isSimulated && !event.isTrigger ) { + jQuery.event.simulate( "change", this.parentNode, event, true ); + } + }); + jQuery._data( elem, "changeBubbles", true ); + } + }); + }, + + handle: function( event ) { + var elem = event.target; + + // Swallow native change events from checkbox/radio, we already triggered them above + if ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox") ) { + return event.handleObj.handler.apply( this, arguments ); + } + }, + + teardown: function() { + jQuery.event.remove( this, "._change" ); + + return !rformElems.test( this.nodeName ); + } + }; +} + +// Create "bubbling" focus and blur events +if ( !support.focusinBubbles ) { + jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { + + // Attach a single capturing handler on the document while someone wants focusin/focusout + var handler = function( event ) { + jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true ); + }; + + jQuery.event.special[ fix ] = { + setup: function() { + var doc = this.ownerDocument || this, + attaches = jQuery._data( doc, fix ); + + if ( !attaches ) { + doc.addEventListener( orig, handler, true ); + } + jQuery._data( doc, fix, ( attaches || 0 ) + 1 ); + }, + teardown: function() { + var doc = this.ownerDocument || this, + attaches = jQuery._data( doc, fix ) - 1; + + if ( !attaches ) { + doc.removeEventListener( orig, handler, true ); + jQuery._removeData( doc, fix ); + } else { + jQuery._data( doc, fix, attaches ); + } + } + }; + }); +} + +jQuery.fn.extend({ + + on: function( types, selector, data, fn, /*INTERNAL*/ one ) { + var type, origFn; + + // Types can be a map of types/handlers + if ( typeof types === "object" ) { + // ( types-Object, selector, data ) + if ( typeof selector !== "string" ) { + // ( types-Object, data ) + data = data || selector; + selector = undefined; + } + for ( type in types ) { + this.on( type, selector, data, types[ type ], one ); + } + return this; + } + + if ( data == null && fn == null ) { + // ( types, fn ) + fn = selector; + data = selector = undefined; + } else if ( fn == null ) { + if ( typeof selector === "string" ) { + // ( types, selector, fn ) + fn = data; + data = undefined; + } else { + // ( types, data, fn ) + fn = data; + data = selector; + selector = undefined; + } + } + if ( fn === false ) { + fn = returnFalse; + } else if ( !fn ) { + return this; + } + + if ( one === 1 ) { + origFn = fn; + fn = function( event ) { + // Can use an empty set, since event contains the info + jQuery().off( event ); + return origFn.apply( this, arguments ); + }; + // Use same guid so caller can remove using origFn + fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); + } + return this.each( function() { + jQuery.event.add( this, types, fn, data, selector ); + }); + }, + one: function( types, selector, data, fn ) { + return this.on( types, selector, data, fn, 1 ); + }, + off: function( types, selector, fn ) { + var handleObj, type; + if ( types && types.preventDefault && types.handleObj ) { + // ( event ) dispatched jQuery.Event + handleObj = types.handleObj; + jQuery( types.delegateTarget ).off( + handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType, + handleObj.selector, + handleObj.handler + ); + return this; + } + if ( typeof types === "object" ) { + // ( types-object [, selector] ) + for ( type in types ) { + this.off( type, selector, types[ type ] ); + } + return this; + } + if ( selector === false || typeof selector === "function" ) { + // ( types [, fn] ) + fn = selector; + selector = undefined; + } + if ( fn === false ) { + fn = returnFalse; + } + return this.each(function() { + jQuery.event.remove( this, types, fn, selector ); + }); + }, + + trigger: function( type, data ) { + return this.each(function() { + jQuery.event.trigger( type, data, this ); + }); + }, + triggerHandler: function( type, data ) { + var elem = this[0]; + if ( elem ) { + return jQuery.event.trigger( type, data, elem, true ); + } + } +}); + + +function createSafeFragment( document ) { + var list = nodeNames.split( "|" ), + safeFrag = document.createDocumentFragment(); + + if ( safeFrag.createElement ) { + while ( list.length ) { + safeFrag.createElement( + list.pop() + ); + } + } + return safeFrag; +} + +var nodeNames = "abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|" + + "header|hgroup|mark|meter|nav|output|progress|section|summary|time|video", + rinlinejQuery = / jQuery\d+="(?:null|\d+)"/g, + rnoshimcache = new RegExp("<(?:" + nodeNames + ")[\\s/>]", "i"), + rleadingWhitespace = /^\s+/, + rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi, + rtagName = /<([\w:]+)/, + rtbody = /\s*$/g, + + // We have to close these tags to support XHTML (#13200) + wrapMap = { + option: [ 1, "" ], + legend: [ 1, "
", "
" ], + area: [ 1, "", "" ], + param: [ 1, "", "" ], + thead: [ 1, "", "
" ], + tr: [ 2, "", "
" ], + col: [ 2, "", "
" ], + td: [ 3, "", "
" ], + + // IE6-8 can't serialize link, script, style, or any html5 (NoScope) tags, + // unless wrapped in a div with non-breaking characters in front of it. + _default: support.htmlSerialize ? [ 0, "", "" ] : [ 1, "X
", "
" ] + }, + safeFragment = createSafeFragment( document ), + fragmentDiv = safeFragment.appendChild( document.createElement("div") ); + +wrapMap.optgroup = wrapMap.option; +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +function getAll( context, tag ) { + var elems, elem, + i = 0, + found = typeof context.getElementsByTagName !== strundefined ? context.getElementsByTagName( tag || "*" ) : + typeof context.querySelectorAll !== strundefined ? context.querySelectorAll( tag || "*" ) : + undefined; + + if ( !found ) { + for ( found = [], elems = context.childNodes || context; (elem = elems[i]) != null; i++ ) { + if ( !tag || jQuery.nodeName( elem, tag ) ) { + found.push( elem ); + } else { + jQuery.merge( found, getAll( elem, tag ) ); + } + } + } + + return tag === undefined || tag && jQuery.nodeName( context, tag ) ? + jQuery.merge( [ context ], found ) : + found; +} + +// Used in buildFragment, fixes the defaultChecked property +function fixDefaultChecked( elem ) { + if ( rcheckableType.test( elem.type ) ) { + elem.defaultChecked = elem.checked; + } +} + +// Support: IE<8 +// Manipulating tables requires a tbody +function manipulationTarget( elem, content ) { + return jQuery.nodeName( elem, "table" ) && + jQuery.nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ? + + elem.getElementsByTagName("tbody")[0] || + elem.appendChild( elem.ownerDocument.createElement("tbody") ) : + elem; +} + +// Replace/restore the type attribute of script elements for safe DOM manipulation +function disableScript( elem ) { + elem.type = (jQuery.find.attr( elem, "type" ) !== null) + "/" + elem.type; + return elem; +} +function restoreScript( elem ) { + var match = rscriptTypeMasked.exec( elem.type ); + if ( match ) { + elem.type = match[1]; + } else { + elem.removeAttribute("type"); + } + return elem; +} + +// Mark scripts as having already been evaluated +function setGlobalEval( elems, refElements ) { + var elem, + i = 0; + for ( ; (elem = elems[i]) != null; i++ ) { + jQuery._data( elem, "globalEval", !refElements || jQuery._data( refElements[i], "globalEval" ) ); + } +} + +function cloneCopyEvent( src, dest ) { + + if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) { + return; + } + + var type, i, l, + oldData = jQuery._data( src ), + curData = jQuery._data( dest, oldData ), + events = oldData.events; + + if ( events ) { + delete curData.handle; + curData.events = {}; + + for ( type in events ) { + for ( i = 0, l = events[ type ].length; i < l; i++ ) { + jQuery.event.add( dest, type, events[ type ][ i ] ); + } + } + } + + // make the cloned public data object a copy from the original + if ( curData.data ) { + curData.data = jQuery.extend( {}, curData.data ); + } +} + +function fixCloneNodeIssues( src, dest ) { + var nodeName, e, data; + + // We do not need to do anything for non-Elements + if ( dest.nodeType !== 1 ) { + return; + } + + nodeName = dest.nodeName.toLowerCase(); + + // IE6-8 copies events bound via attachEvent when using cloneNode. + if ( !support.noCloneEvent && dest[ jQuery.expando ] ) { + data = jQuery._data( dest ); + + for ( e in data.events ) { + jQuery.removeEvent( dest, e, data.handle ); + } + + // Event data gets referenced instead of copied if the expando gets copied too + dest.removeAttribute( jQuery.expando ); + } + + // IE blanks contents when cloning scripts, and tries to evaluate newly-set text + if ( nodeName === "script" && dest.text !== src.text ) { + disableScript( dest ).text = src.text; + restoreScript( dest ); + + // IE6-10 improperly clones children of object elements using classid. + // IE10 throws NoModificationAllowedError if parent is null, #12132. + } else if ( nodeName === "object" ) { + if ( dest.parentNode ) { + dest.outerHTML = src.outerHTML; + } + + // This path appears unavoidable for IE9. When cloning an object + // element in IE9, the outerHTML strategy above is not sufficient. + // If the src has innerHTML and the destination does not, + // copy the src.innerHTML into the dest.innerHTML. #10324 + if ( support.html5Clone && ( src.innerHTML && !jQuery.trim(dest.innerHTML) ) ) { + dest.innerHTML = src.innerHTML; + } + + } else if ( nodeName === "input" && rcheckableType.test( src.type ) ) { + // IE6-8 fails to persist the checked state of a cloned checkbox + // or radio button. Worse, IE6-7 fail to give the cloned element + // a checked appearance if the defaultChecked value isn't also set + + dest.defaultChecked = dest.checked = src.checked; + + // IE6-7 get confused and end up setting the value of a cloned + // checkbox/radio button to an empty string instead of "on" + if ( dest.value !== src.value ) { + dest.value = src.value; + } + + // IE6-8 fails to return the selected option to the default selected + // state when cloning options + } else if ( nodeName === "option" ) { + dest.defaultSelected = dest.selected = src.defaultSelected; + + // IE6-8 fails to set the defaultValue to the correct value when + // cloning other types of input fields + } else if ( nodeName === "input" || nodeName === "textarea" ) { + dest.defaultValue = src.defaultValue; + } +} + +jQuery.extend({ + clone: function( elem, dataAndEvents, deepDataAndEvents ) { + var destElements, node, clone, i, srcElements, + inPage = jQuery.contains( elem.ownerDocument, elem ); + + if ( support.html5Clone || jQuery.isXMLDoc(elem) || !rnoshimcache.test( "<" + elem.nodeName + ">" ) ) { + clone = elem.cloneNode( true ); + + // IE<=8 does not properly clone detached, unknown element nodes + } else { + fragmentDiv.innerHTML = elem.outerHTML; + fragmentDiv.removeChild( clone = fragmentDiv.firstChild ); + } + + if ( (!support.noCloneEvent || !support.noCloneChecked) && + (elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) { + + // We eschew Sizzle here for performance reasons: http://jsperf.com/getall-vs-sizzle/2 + destElements = getAll( clone ); + srcElements = getAll( elem ); + + // Fix all IE cloning issues + for ( i = 0; (node = srcElements[i]) != null; ++i ) { + // Ensure that the destination node is not null; Fixes #9587 + if ( destElements[i] ) { + fixCloneNodeIssues( node, destElements[i] ); + } + } + } + + // Copy the events from the original to the clone + if ( dataAndEvents ) { + if ( deepDataAndEvents ) { + srcElements = srcElements || getAll( elem ); + destElements = destElements || getAll( clone ); + + for ( i = 0; (node = srcElements[i]) != null; i++ ) { + cloneCopyEvent( node, destElements[i] ); + } + } else { + cloneCopyEvent( elem, clone ); + } + } + + // Preserve script evaluation history + destElements = getAll( clone, "script" ); + if ( destElements.length > 0 ) { + setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); + } + + destElements = srcElements = node = null; + + // Return the cloned set + return clone; + }, + + buildFragment: function( elems, context, scripts, selection ) { + var j, elem, contains, + tmp, tag, tbody, wrap, + l = elems.length, + + // Ensure a safe fragment + safe = createSafeFragment( context ), + + nodes = [], + i = 0; + + for ( ; i < l; i++ ) { + elem = elems[ i ]; + + if ( elem || elem === 0 ) { + + // Add nodes directly + if ( jQuery.type( elem ) === "object" ) { + jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); + + // Convert non-html into a text node + } else if ( !rhtml.test( elem ) ) { + nodes.push( context.createTextNode( elem ) ); + + // Convert html into DOM nodes + } else { + tmp = tmp || safe.appendChild( context.createElement("div") ); + + // Deserialize a standard representation + tag = (rtagName.exec( elem ) || [ "", "" ])[ 1 ].toLowerCase(); + wrap = wrapMap[ tag ] || wrapMap._default; + + tmp.innerHTML = wrap[1] + elem.replace( rxhtmlTag, "<$1>" ) + wrap[2]; + + // Descend through wrappers to the right content + j = wrap[0]; + while ( j-- ) { + tmp = tmp.lastChild; + } + + // Manually add leading whitespace removed by IE + if ( !support.leadingWhitespace && rleadingWhitespace.test( elem ) ) { + nodes.push( context.createTextNode( rleadingWhitespace.exec( elem )[0] ) ); + } + + // Remove IE's autoinserted from table fragments + if ( !support.tbody ) { + + // String was a , *may* have spurious + elem = tag === "table" && !rtbody.test( elem ) ? + tmp.firstChild : + + // String was a bare or + wrap[1] === "
" && !rtbody.test( elem ) ? + tmp : + 0; + + j = elem && elem.childNodes.length; + while ( j-- ) { + if ( jQuery.nodeName( (tbody = elem.childNodes[j]), "tbody" ) && !tbody.childNodes.length ) { + elem.removeChild( tbody ); + } + } + } + + jQuery.merge( nodes, tmp.childNodes ); + + // Fix #12392 for WebKit and IE > 9 + tmp.textContent = ""; + + // Fix #12392 for oldIE + while ( tmp.firstChild ) { + tmp.removeChild( tmp.firstChild ); + } + + // Remember the top-level container for proper cleanup + tmp = safe.lastChild; + } + } + } + + // Fix #11356: Clear elements from fragment + if ( tmp ) { + safe.removeChild( tmp ); + } + + // Reset defaultChecked for any radios and checkboxes + // about to be appended to the DOM in IE 6/7 (#8060) + if ( !support.appendChecked ) { + jQuery.grep( getAll( nodes, "input" ), fixDefaultChecked ); + } + + i = 0; + while ( (elem = nodes[ i++ ]) ) { + + // #4087 - If origin and destination elements are the same, and this is + // that element, do not do anything + if ( selection && jQuery.inArray( elem, selection ) !== -1 ) { + continue; + } + + contains = jQuery.contains( elem.ownerDocument, elem ); + + // Append to fragment + tmp = getAll( safe.appendChild( elem ), "script" ); + + // Preserve script evaluation history + if ( contains ) { + setGlobalEval( tmp ); + } + + // Capture executables + if ( scripts ) { + j = 0; + while ( (elem = tmp[ j++ ]) ) { + if ( rscriptType.test( elem.type || "" ) ) { + scripts.push( elem ); + } + } + } + } + + tmp = null; + + return safe; + }, + + cleanData: function( elems, /* internal */ acceptData ) { + var elem, type, id, data, + i = 0, + internalKey = jQuery.expando, + cache = jQuery.cache, + deleteExpando = support.deleteExpando, + special = jQuery.event.special; + + for ( ; (elem = elems[i]) != null; i++ ) { + if ( acceptData || jQuery.acceptData( elem ) ) { + + id = elem[ internalKey ]; + data = id && cache[ id ]; + + if ( data ) { + if ( data.events ) { + for ( type in data.events ) { + if ( special[ type ] ) { + jQuery.event.remove( elem, type ); + + // This is a shortcut to avoid jQuery.event.remove's overhead + } else { + jQuery.removeEvent( elem, type, data.handle ); + } + } + } + + // Remove cache only if it was not already removed by jQuery.event.remove + if ( cache[ id ] ) { + + delete cache[ id ]; + + // IE does not allow us to delete expando properties from nodes, + // nor does it have a removeAttribute function on Document nodes; + // we must handle all of these cases + if ( deleteExpando ) { + delete elem[ internalKey ]; + + } else if ( typeof elem.removeAttribute !== strundefined ) { + elem.removeAttribute( internalKey ); + + } else { + elem[ internalKey ] = null; + } + + deletedIds.push( id ); + } + } + } + } + } +}); + +jQuery.fn.extend({ + text: function( value ) { + return access( this, function( value ) { + return value === undefined ? + jQuery.text( this ) : + this.empty().append( ( this[0] && this[0].ownerDocument || document ).createTextNode( value ) ); + }, null, value, arguments.length ); + }, + + append: function() { + return this.domManip( arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.appendChild( elem ); + } + }); + }, + + prepend: function() { + return this.domManip( arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.insertBefore( elem, target.firstChild ); + } + }); + }, + + before: function() { + return this.domManip( arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this ); + } + }); + }, + + after: function() { + return this.domManip( arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this.nextSibling ); + } + }); + }, + + remove: function( selector, keepData /* Internal Use Only */ ) { + var elem, + elems = selector ? jQuery.filter( selector, this ) : this, + i = 0; + + for ( ; (elem = elems[i]) != null; i++ ) { + + if ( !keepData && elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem ) ); + } + + if ( elem.parentNode ) { + if ( keepData && jQuery.contains( elem.ownerDocument, elem ) ) { + setGlobalEval( getAll( elem, "script" ) ); + } + elem.parentNode.removeChild( elem ); + } + } + + return this; + }, + + empty: function() { + var elem, + i = 0; + + for ( ; (elem = this[i]) != null; i++ ) { + // Remove element nodes and prevent memory leaks + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + } + + // Remove any remaining nodes + while ( elem.firstChild ) { + elem.removeChild( elem.firstChild ); + } + + // If this is a select, ensure that it displays empty (#12336) + // Support: IE<9 + if ( elem.options && jQuery.nodeName( elem, "select" ) ) { + elem.options.length = 0; + } + } + + return this; + }, + + clone: function( dataAndEvents, deepDataAndEvents ) { + dataAndEvents = dataAndEvents == null ? false : dataAndEvents; + deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; + + return this.map(function() { + return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); + }); + }, + + html: function( value ) { + return access( this, function( value ) { + var elem = this[ 0 ] || {}, + i = 0, + l = this.length; + + if ( value === undefined ) { + return elem.nodeType === 1 ? + elem.innerHTML.replace( rinlinejQuery, "" ) : + undefined; + } + + // See if we can take a shortcut and just use innerHTML + if ( typeof value === "string" && !rnoInnerhtml.test( value ) && + ( support.htmlSerialize || !rnoshimcache.test( value ) ) && + ( support.leadingWhitespace || !rleadingWhitespace.test( value ) ) && + !wrapMap[ (rtagName.exec( value ) || [ "", "" ])[ 1 ].toLowerCase() ] ) { + + value = value.replace( rxhtmlTag, "<$1>" ); + + try { + for (; i < l; i++ ) { + // Remove element nodes and prevent memory leaks + elem = this[i] || {}; + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + elem.innerHTML = value; + } + } + + elem = 0; + + // If using innerHTML throws an exception, use the fallback method + } catch(e) {} + } + + if ( elem ) { + this.empty().append( value ); + } + }, null, value, arguments.length ); + }, + + replaceWith: function() { + var arg = arguments[ 0 ]; + + // Make the changes, replacing each context element with the new content + this.domManip( arguments, function( elem ) { + arg = this.parentNode; + + jQuery.cleanData( getAll( this ) ); + + if ( arg ) { + arg.replaceChild( elem, this ); + } + }); + + // Force removal if there was no new content (e.g., from empty arguments) + return arg && (arg.length || arg.nodeType) ? this : this.remove(); + }, + + detach: function( selector ) { + return this.remove( selector, true ); + }, + + domManip: function( args, callback ) { + + // Flatten any nested arrays + args = concat.apply( [], args ); + + var first, node, hasScripts, + scripts, doc, fragment, + i = 0, + l = this.length, + set = this, + iNoClone = l - 1, + value = args[0], + isFunction = jQuery.isFunction( value ); + + // We can't cloneNode fragments that contain checked, in WebKit + if ( isFunction || + ( l > 1 && typeof value === "string" && + !support.checkClone && rchecked.test( value ) ) ) { + return this.each(function( index ) { + var self = set.eq( index ); + if ( isFunction ) { + args[0] = value.call( this, index, self.html() ); + } + self.domManip( args, callback ); + }); + } + + if ( l ) { + fragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, this ); + first = fragment.firstChild; + + if ( fragment.childNodes.length === 1 ) { + fragment = first; + } + + if ( first ) { + scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); + hasScripts = scripts.length; + + // Use the original fragment for the last item instead of the first because it can end up + // being emptied incorrectly in certain situations (#8070). + for ( ; i < l; i++ ) { + node = fragment; + + if ( i !== iNoClone ) { + node = jQuery.clone( node, true, true ); + + // Keep references to cloned scripts for later restoration + if ( hasScripts ) { + jQuery.merge( scripts, getAll( node, "script" ) ); + } + } + + callback.call( this[i], node, i ); + } + + if ( hasScripts ) { + doc = scripts[ scripts.length - 1 ].ownerDocument; + + // Reenable scripts + jQuery.map( scripts, restoreScript ); + + // Evaluate executable scripts on first document insertion + for ( i = 0; i < hasScripts; i++ ) { + node = scripts[ i ]; + if ( rscriptType.test( node.type || "" ) && + !jQuery._data( node, "globalEval" ) && jQuery.contains( doc, node ) ) { + + if ( node.src ) { + // Optional AJAX dependency, but won't run scripts if not present + if ( jQuery._evalUrl ) { + jQuery._evalUrl( node.src ); + } + } else { + jQuery.globalEval( ( node.text || node.textContent || node.innerHTML || "" ).replace( rcleanScript, "" ) ); + } + } + } + } + + // Fix #11809: Avoid leaking memory + fragment = first = null; + } + } + + return this; + } +}); + +jQuery.each({ + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after", + replaceAll: "replaceWith" +}, function( name, original ) { + jQuery.fn[ name ] = function( selector ) { + var elems, + i = 0, + ret = [], + insert = jQuery( selector ), + last = insert.length - 1; + + for ( ; i <= last; i++ ) { + elems = i === last ? this : this.clone(true); + jQuery( insert[i] )[ original ]( elems ); + + // Modern browsers can apply jQuery collections as arrays, but oldIE needs a .get() + push.apply( ret, elems.get() ); + } + + return this.pushStack( ret ); + }; +}); + + +var iframe, + elemdisplay = {}; + +/** + * Retrieve the actual display of a element + * @param {String} name nodeName of the element + * @param {Object} doc Document object + */ +// Called only from within defaultDisplay +function actualDisplay( name, doc ) { + var style, + elem = jQuery( doc.createElement( name ) ).appendTo( doc.body ), + + // getDefaultComputedStyle might be reliably used only on attached element + display = window.getDefaultComputedStyle && ( style = window.getDefaultComputedStyle( elem[ 0 ] ) ) ? + + // Use of this method is a temporary fix (more like optmization) until something better comes along, + // since it was removed from specification and supported only in FF + style.display : jQuery.css( elem[ 0 ], "display" ); + + // We don't have any data stored on the element, + // so use "detach" method as fast way to get rid of the element + elem.detach(); + + return display; +} + +/** + * Try to determine the default display value of an element + * @param {String} nodeName + */ +function defaultDisplay( nodeName ) { + var doc = document, + display = elemdisplay[ nodeName ]; + + if ( !display ) { + display = actualDisplay( nodeName, doc ); + + // If the simple way fails, read from inside an iframe + if ( display === "none" || !display ) { + + // Use the already-created iframe if possible + iframe = (iframe || jQuery( "