From 141b8a2b19be19eeee5d7735f44564b6e2ece1d2 Mon Sep 17 00:00:00 2001 From: Mark Barbaric Date: Fri, 30 Aug 2024 14:36:13 +0100 Subject: [PATCH 01/12] threading - added some threading examples --- .../background_task.py | 30 +++++++++++++++++++ .../asyncio_background_task/helpers.py | 20 +++++++++++++ .../asyncio_background_task/logger.py | 19 ++++++++++++ .../threading/asyncio_background_task/main.py | 22 ++++++++++++++ .../asyncio_background_task/model.py | 12 ++++++++ .../asyncio_background_task/settings.py | 14 +++++++++ .../consumer_producer_threading/consumer.py | 16 ++++++++++ .../logging_config.py | 22 ++++++++++++++ .../consumer_producer_threading/main.py | 25 ++++++++++++++++ .../consumer_producer_threading/pipeline.py | 21 +++++++++++++ .../consumer_producer_threading/producer.py | 14 +++++++++ .../consumer_producer_threading/settings.py | 14 +++++++++ .../threading/threading_events/main.py | 21 +++++++++++++ 13 files changed, 250 insertions(+) create mode 100644 src/general_python/threading/asyncio_background_task/background_task.py create mode 100644 src/general_python/threading/asyncio_background_task/helpers.py create mode 100644 src/general_python/threading/asyncio_background_task/logger.py create mode 100644 src/general_python/threading/asyncio_background_task/main.py create mode 100644 src/general_python/threading/asyncio_background_task/model.py create mode 100644 src/general_python/threading/asyncio_background_task/settings.py create mode 100644 src/general_python/threading/consumer_producer_threading/consumer.py create mode 100644 src/general_python/threading/consumer_producer_threading/logging_config.py create mode 100644 src/general_python/threading/consumer_producer_threading/main.py create mode 100644 src/general_python/threading/consumer_producer_threading/pipeline.py create mode 100644 src/general_python/threading/consumer_producer_threading/producer.py create mode 100644 src/general_python/threading/consumer_producer_threading/settings.py create mode 100644 src/general_python/threading/threading_events/main.py diff --git a/src/general_python/threading/asyncio_background_task/background_task.py b/src/general_python/threading/asyncio_background_task/background_task.py new file mode 100644 index 0000000..aa6d5ea --- /dev/null +++ b/src/general_python/threading/asyncio_background_task/background_task.py @@ -0,0 +1,30 @@ +import logging +from model import TestModel, FullModel +from helpers import write_json_to_file +from settings import get_app_settings + + +async def custom_coro(): + """_summary_ + """ + logger = logging.getLogger(get_app_settings().logger_name) + logger.info('Coroutine is running') + base_models: list[TestModel] = [] + for i in range(get_app_settings().background_task_iterations): + if i % 100 == 0: + logger.info(f"Creating model {i}") + + new_model = TestModel( + name=f"Name: {i}", + value=i + ) + base_models.append(new_model) + + # simulate some long running task + full_model = FullModel( + models=base_models + ) + json_bytes = full_model.model_dump_json() + logger.info("Writing Model to json") + write_json_to_file(json_bytes, "test.json") + logger.info('Coroutine is done') diff --git a/src/general_python/threading/asyncio_background_task/helpers.py b/src/general_python/threading/asyncio_background_task/helpers.py new file mode 100644 index 0000000..a28c353 --- /dev/null +++ b/src/general_python/threading/asyncio_background_task/helpers.py @@ -0,0 +1,20 @@ +import os +import logging +from settings import get_app_settings + + +def write_json_to_file(json_string: str, file_name: str): + """_summary_ + + Args: + json_string (str): _description_ + file_name (str): _description_ + """ + logger = logging.getLogger(get_app_settings().logger_name) + file_path = os.path.join(os.getcwd(), file_name) + logger.info(f"attempting to write json file to path: {file_path}") + try: + with open(file_path, 'w') as f: + f.write(json_string) + except FileNotFoundError: + logger.error(f"file_path: {file_path} not found") diff --git a/src/general_python/threading/asyncio_background_task/logger.py b/src/general_python/threading/asyncio_background_task/logger.py new file mode 100644 index 0000000..f97881c --- /dev/null +++ b/src/general_python/threading/asyncio_background_task/logger.py @@ -0,0 +1,19 @@ +import logging + + +def setup_custom_logger(logger_name: str): + """_summary_ + + Args: + name (str): _description_ + + Returns: + _type_: _description_ + """ + formatter = logging.Formatter(fmt='%(asctime)s - %(levelname)s - %(module)s - %(message)s') + handler = logging.StreamHandler() + handler.setFormatter(formatter) + logger = logging.getLogger(logger_name) + logger.setLevel(logging.DEBUG) + logger.addHandler(handler) + return logger diff --git a/src/general_python/threading/asyncio_background_task/main.py b/src/general_python/threading/asyncio_background_task/main.py new file mode 100644 index 0000000..594fcc8 --- /dev/null +++ b/src/general_python/threading/asyncio_background_task/main.py @@ -0,0 +1,22 @@ +import asyncio +import logging +from settings import get_app_settings +from logger import setup_custom_logger +from background_task import custom_coro + + +async def main(): + logger = logging.getLogger(get_app_settings().logger_name) + logger.info("launching main thread") + coro = custom_coro() + _ = asyncio.create_task(coro) + await asyncio.sleep(0) + + for _ in range(get_app_settings().main_task_iterations): + logger.info("sleeping for 2 secs") + await asyncio.sleep(2) + + +if __name__ == "__main__": + logger = setup_custom_logger(get_app_settings().logger_name) + asyncio.run(main()) diff --git a/src/general_python/threading/asyncio_background_task/model.py b/src/general_python/threading/asyncio_background_task/model.py new file mode 100644 index 0000000..7c7f0fc --- /dev/null +++ b/src/general_python/threading/asyncio_background_task/model.py @@ -0,0 +1,12 @@ +from pydantic import BaseModel, ConfigDict + + +class TestModel(BaseModel): + model_config = ConfigDict(extra='forbid', frozen=True) + name: str + value: int + + +class FullModel(BaseModel): + model_config = ConfigDict(extra='forbid', frozen=True) + models: list[TestModel] diff --git a/src/general_python/threading/asyncio_background_task/settings.py b/src/general_python/threading/asyncio_background_task/settings.py new file mode 100644 index 0000000..01ba2a7 --- /dev/null +++ b/src/general_python/threading/asyncio_background_task/settings.py @@ -0,0 +1,14 @@ +from pydantic_settings import BaseSettings +import functools + + +class WebscrapeSettings(BaseSettings): + logger_name: str = "root" + background_task_iterations: int = 10 ** 8 + main_task_iterations: int = 10 + + +@functools.lru_cache +def get_app_settings() -> WebscrapeSettings: + settings = WebscrapeSettings() + return settings diff --git a/src/general_python/threading/consumer_producer_threading/consumer.py b/src/general_python/threading/consumer_producer_threading/consumer.py new file mode 100644 index 0000000..04cf1b8 --- /dev/null +++ b/src/general_python/threading/consumer_producer_threading/consumer.py @@ -0,0 +1,16 @@ +import logging +from settings import get_app_settings + + +def consumer(pipeline, event): + """Pretend we're saving a number in the database.""" + logger = logging.getLogger(get_app_settings().logger_name) + while not event.is_set() or not pipeline.empty(): + message = pipeline.get_message("Consumer") + logger.info( + "Consumer storing message: %s (queue size=%s)", + message, + pipeline.qsize(), + ) + + logger.info("Consumer received EXIT event. Exiting") diff --git a/src/general_python/threading/consumer_producer_threading/logging_config.py b/src/general_python/threading/consumer_producer_threading/logging_config.py new file mode 100644 index 0000000..ba2c773 --- /dev/null +++ b/src/general_python/threading/consumer_producer_threading/logging_config.py @@ -0,0 +1,22 @@ +import logging + + +def setup_custom_logger(logger_name: str): + """_summary_ + + Args: + name (str): _description_ + + Returns: + _type_: _description_ + """ + formatter = logging.Formatter(fmt='%(asctime)s - %(levelname)s - %(module)s - %(message)s') + handler = logging.StreamHandler() + handler.setFormatter(formatter) + logger = logging.getLogger(logger_name) + logger.setLevel(logging.DEBUG) + logger.addHandler(handler) + file_handler = logging.FileHandler('logger.log') + file_handler.setLevel(logging.DEBUG) + logger.addHandler(file_handler) + return logger diff --git a/src/general_python/threading/consumer_producer_threading/main.py b/src/general_python/threading/consumer_producer_threading/main.py new file mode 100644 index 0000000..7b44f51 --- /dev/null +++ b/src/general_python/threading/consumer_producer_threading/main.py @@ -0,0 +1,25 @@ +import logging +import threading +import time +from concurrent import futures +from logging_config import setup_custom_logger +from settings import get_app_settings +from consumer import consumer +from producer import producer +from pipeline import Pipeline + + +if __name__ == "__main__": + setup_custom_logger(get_app_settings().logger_name) + logger = logging.getLogger(get_app_settings().logger_name) + logger.info("starting main thread") + pipeline = Pipeline() + event = threading.Event() + + with futures.ThreadPoolExecutor(max_workers=2) as executor: + executor.submit(producer, pipeline, event) + executor.submit(consumer, pipeline, event) + logger.info("main thread is sleeping for 1 sec") + time.sleep(0.1) + logger.info("main thread setting threading.Event") + event.set() diff --git a/src/general_python/threading/consumer_producer_threading/pipeline.py b/src/general_python/threading/consumer_producer_threading/pipeline.py new file mode 100644 index 0000000..29b5c0a --- /dev/null +++ b/src/general_python/threading/consumer_producer_threading/pipeline.py @@ -0,0 +1,21 @@ +import queue +import logging +from settings import get_app_settings + + +class Pipeline(queue.Queue): + def __init__(self): + super().__init__(maxsize=10) + + def get_message(self, name): + logger = logging.getLogger(get_app_settings().logger_name) + logger.debug(name) + value = self.get() + logger.debug(f"{name}: got {value} from queue") + return value + + def set_message(self, value, name): + logger = logging.getLogger(get_app_settings().logger_name) + logger.debug(f"{name}: is about to add {value} to queue") + self.put(value) + logger.debug(f"{name}: was added {value} to queue") diff --git a/src/general_python/threading/consumer_producer_threading/producer.py b/src/general_python/threading/consumer_producer_threading/producer.py new file mode 100644 index 0000000..0e8ee3a --- /dev/null +++ b/src/general_python/threading/consumer_producer_threading/producer.py @@ -0,0 +1,14 @@ +import random +import logging +from settings import get_app_settings + + +def producer(pipeline, event): + """Pretend we're getting a number from the network.""" + logger = logging.getLogger(get_app_settings().logger_name) + while not event.is_set(): + message = random.randint(1, 101) + logger.info("Producer got message: %s", message) + pipeline.set_message(message, "Producer") + + logger.info("Producer received EXIT event. Exiting") diff --git a/src/general_python/threading/consumer_producer_threading/settings.py b/src/general_python/threading/consumer_producer_threading/settings.py new file mode 100644 index 0000000..01ba2a7 --- /dev/null +++ b/src/general_python/threading/consumer_producer_threading/settings.py @@ -0,0 +1,14 @@ +from pydantic_settings import BaseSettings +import functools + + +class WebscrapeSettings(BaseSettings): + logger_name: str = "root" + background_task_iterations: int = 10 ** 8 + main_task_iterations: int = 10 + + +@functools.lru_cache +def get_app_settings() -> WebscrapeSettings: + settings = WebscrapeSettings() + return settings diff --git a/src/general_python/threading/threading_events/main.py b/src/general_python/threading/threading_events/main.py new file mode 100644 index 0000000..e1bbcda --- /dev/null +++ b/src/general_python/threading/threading_events/main.py @@ -0,0 +1,21 @@ +from time import sleep +from random import random +from threading import Thread +from threading import Event + + +def task(event: Event, number: int): + event.wait() + value = random() + sleep(value) + print(f"Thread {number} got value {value}") + + +if __name__ == "__main__": + event = Event() + for i in range(5): + thread = Thread(target=task, args=(event, i)) + thread.start() + print("main thread blocking") + sleep(2) + event.set() From 2052b11164aa29dd1685cedf121320bf7e433225 Mon Sep 17 00:00:00 2001 From: Mark Barbaric Date: Sun, 1 Sep 2024 16:03:26 +0100 Subject: [PATCH 02/12] threading - more playing around with asynciou. --- .gitignore | 6 +- poetry.lock | 561 +++++++++++++++++- pyproject.toml | 5 + .../threading/asyncio_progress/main.py | 31 + .../asyncio_queues/producer_consumer.py | 47 ++ .../threading/asyncio_queues/progress.py | 53 ++ .../rest/fastapi_logging/README.md | 1 - 7 files changed, 701 insertions(+), 3 deletions(-) create mode 100644 src/general_python/threading/asyncio_progress/main.py create mode 100644 src/general_python/threading/asyncio_queues/producer_consumer.py create mode 100644 src/general_python/threading/asyncio_queues/progress.py delete mode 100644 src/system_design/rest/fastapi_logging/README.md diff --git a/.gitignore b/.gitignore index a277a25..5ce5288 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,8 @@ __pycache__/ .coverage .coverage.* -coverage.* \ No newline at end of file +coverage.* + +# general files +*.json +*.log \ No newline at end of file diff --git a/poetry.lock b/poetry.lock index ad85a17..c7f47f5 100644 --- a/poetry.lock +++ b/poetry.lock @@ -47,6 +47,19 @@ doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphin test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] trio = ["trio (>=0.23)"] +[[package]] +name = "asyncio" +version = "3.4.3" +description = "reference implementation of PEP 3156" +optional = false +python-versions = "*" +files = [ + {file = "asyncio-3.4.3-cp33-none-win32.whl", hash = "sha256:b62c9157d36187eca799c378e572c969f0da87cd5fc42ca372d92cdb06e7e1de"}, + {file = "asyncio-3.4.3-cp33-none-win_amd64.whl", hash = "sha256:c46a87b48213d7464f22d9a497b9eef8c1928b68320a2fa94240f969f6fec08c"}, + {file = "asyncio-3.4.3-py3-none-any.whl", hash = "sha256:c4d18b22701821de07bd6aea8b53d21449ec0ec5680645e5317062ea21817d2d"}, + {file = "asyncio-3.4.3.tar.gz", hash = "sha256:83360ff8bc97980e4ff25c964c7bd3923d333d177aa4f7fb736b019f26c7cb41"}, +] + [[package]] name = "blinker" version = "1.8.2" @@ -94,6 +107,90 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +[[package]] +name = "contourpy" +version = "1.3.0" +description = "Python library for calculating contours of 2D quadrilateral grids" +optional = false +python-versions = ">=3.9" +files = [ + {file = "contourpy-1.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:880ea32e5c774634f9fcd46504bf9f080a41ad855f4fef54f5380f5133d343c7"}, + {file = "contourpy-1.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:76c905ef940a4474a6289c71d53122a4f77766eef23c03cd57016ce19d0f7b42"}, + {file = "contourpy-1.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92f8557cbb07415a4d6fa191f20fd9d2d9eb9c0b61d1b2f52a8926e43c6e9af7"}, + {file = "contourpy-1.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:36f965570cff02b874773c49bfe85562b47030805d7d8360748f3eca570f4cab"}, + {file = "contourpy-1.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cacd81e2d4b6f89c9f8a5b69b86490152ff39afc58a95af002a398273e5ce589"}, + {file = "contourpy-1.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69375194457ad0fad3a839b9e29aa0b0ed53bb54db1bfb6c3ae43d111c31ce41"}, + {file = "contourpy-1.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a52040312b1a858b5e31ef28c2e865376a386c60c0e248370bbea2d3f3b760d"}, + {file = "contourpy-1.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3faeb2998e4fcb256542e8a926d08da08977f7f5e62cf733f3c211c2a5586223"}, + {file = "contourpy-1.3.0-cp310-cp310-win32.whl", hash = "sha256:36e0cff201bcb17a0a8ecc7f454fe078437fa6bda730e695a92f2d9932bd507f"}, + {file = "contourpy-1.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:87ddffef1dbe5e669b5c2440b643d3fdd8622a348fe1983fad7a0f0ccb1cd67b"}, + {file = "contourpy-1.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0fa4c02abe6c446ba70d96ece336e621efa4aecae43eaa9b030ae5fb92b309ad"}, + {file = "contourpy-1.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:834e0cfe17ba12f79963861e0f908556b2cedd52e1f75e6578801febcc6a9f49"}, + {file = "contourpy-1.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dbc4c3217eee163fa3984fd1567632b48d6dfd29216da3ded3d7b844a8014a66"}, + {file = "contourpy-1.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4865cd1d419e0c7a7bf6de1777b185eebdc51470800a9f42b9e9decf17762081"}, + {file = "contourpy-1.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:303c252947ab4b14c08afeb52375b26781ccd6a5ccd81abcdfc1fafd14cf93c1"}, + {file = "contourpy-1.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:637f674226be46f6ba372fd29d9523dd977a291f66ab2a74fbeb5530bb3f445d"}, + {file = "contourpy-1.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:76a896b2f195b57db25d6b44e7e03f221d32fe318d03ede41f8b4d9ba1bff53c"}, + {file = "contourpy-1.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e1fd23e9d01591bab45546c089ae89d926917a66dceb3abcf01f6105d927e2cb"}, + {file = "contourpy-1.3.0-cp311-cp311-win32.whl", hash = "sha256:d402880b84df3bec6eab53cd0cf802cae6a2ef9537e70cf75e91618a3801c20c"}, + {file = "contourpy-1.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:6cb6cc968059db9c62cb35fbf70248f40994dfcd7aa10444bbf8b3faeb7c2d67"}, + {file = "contourpy-1.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:570ef7cf892f0afbe5b2ee410c507ce12e15a5fa91017a0009f79f7d93a1268f"}, + {file = "contourpy-1.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:da84c537cb8b97d153e9fb208c221c45605f73147bd4cadd23bdae915042aad6"}, + {file = "contourpy-1.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0be4d8425bfa755e0fd76ee1e019636ccc7c29f77a7c86b4328a9eb6a26d0639"}, + {file = "contourpy-1.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9c0da700bf58f6e0b65312d0a5e695179a71d0163957fa381bb3c1f72972537c"}, + {file = "contourpy-1.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eb8b141bb00fa977d9122636b16aa67d37fd40a3d8b52dd837e536d64b9a4d06"}, + {file = "contourpy-1.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3634b5385c6716c258d0419c46d05c8aa7dc8cb70326c9a4fb66b69ad2b52e09"}, + {file = "contourpy-1.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0dce35502151b6bd35027ac39ba6e5a44be13a68f55735c3612c568cac3805fd"}, + {file = "contourpy-1.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:aea348f053c645100612b333adc5983d87be69acdc6d77d3169c090d3b01dc35"}, + {file = "contourpy-1.3.0-cp312-cp312-win32.whl", hash = "sha256:90f73a5116ad1ba7174341ef3ea5c3150ddf20b024b98fb0c3b29034752c8aeb"}, + {file = "contourpy-1.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:b11b39aea6be6764f84360fce6c82211a9db32a7c7de8fa6dd5397cf1d079c3b"}, + {file = "contourpy-1.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3e1c7fa44aaae40a2247e2e8e0627f4bea3dd257014764aa644f319a5f8600e3"}, + {file = "contourpy-1.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:364174c2a76057feef647c802652f00953b575723062560498dc7930fc9b1cb7"}, + {file = "contourpy-1.3.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32b238b3b3b649e09ce9aaf51f0c261d38644bdfa35cbaf7b263457850957a84"}, + {file = "contourpy-1.3.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d51fca85f9f7ad0b65b4b9fe800406d0d77017d7270d31ec3fb1cc07358fdea0"}, + {file = "contourpy-1.3.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:732896af21716b29ab3e988d4ce14bc5133733b85956316fb0c56355f398099b"}, + {file = "contourpy-1.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d73f659398a0904e125280836ae6f88ba9b178b2fed6884f3b1f95b989d2c8da"}, + {file = "contourpy-1.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c6c7c2408b7048082932cf4e641fa3b8ca848259212f51c8c59c45aa7ac18f14"}, + {file = "contourpy-1.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f317576606de89da6b7e0861cf6061f6146ead3528acabff9236458a6ba467f8"}, + {file = "contourpy-1.3.0-cp313-cp313-win32.whl", hash = "sha256:31cd3a85dbdf1fc002280c65caa7e2b5f65e4a973fcdf70dd2fdcb9868069294"}, + {file = "contourpy-1.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:4553c421929ec95fb07b3aaca0fae668b2eb5a5203d1217ca7c34c063c53d087"}, + {file = "contourpy-1.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:345af746d7766821d05d72cb8f3845dfd08dd137101a2cb9b24de277d716def8"}, + {file = "contourpy-1.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3bb3808858a9dc68f6f03d319acd5f1b8a337e6cdda197f02f4b8ff67ad2057b"}, + {file = "contourpy-1.3.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:420d39daa61aab1221567b42eecb01112908b2cab7f1b4106a52caaec8d36973"}, + {file = "contourpy-1.3.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4d63ee447261e963af02642ffcb864e5a2ee4cbfd78080657a9880b8b1868e18"}, + {file = "contourpy-1.3.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:167d6c890815e1dac9536dca00828b445d5d0df4d6a8c6adb4a7ec3166812fa8"}, + {file = "contourpy-1.3.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:710a26b3dc80c0e4febf04555de66f5fd17e9cf7170a7b08000601a10570bda6"}, + {file = "contourpy-1.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:75ee7cb1a14c617f34a51d11fa7524173e56551646828353c4af859c56b766e2"}, + {file = "contourpy-1.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:33c92cdae89ec5135d036e7218e69b0bb2851206077251f04a6c4e0e21f03927"}, + {file = "contourpy-1.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a11077e395f67ffc2c44ec2418cfebed032cd6da3022a94fc227b6faf8e2acb8"}, + {file = "contourpy-1.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e8134301d7e204c88ed7ab50028ba06c683000040ede1d617298611f9dc6240c"}, + {file = "contourpy-1.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e12968fdfd5bb45ffdf6192a590bd8ddd3ba9e58360b29683c6bb71a7b41edca"}, + {file = "contourpy-1.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fd2a0fc506eccaaa7595b7e1418951f213cf8255be2600f1ea1b61e46a60c55f"}, + {file = "contourpy-1.3.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4cfb5c62ce023dfc410d6059c936dcf96442ba40814aefbfa575425a3a7f19dc"}, + {file = "contourpy-1.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68a32389b06b82c2fdd68276148d7b9275b5f5cf13e5417e4252f6d1a34f72a2"}, + {file = "contourpy-1.3.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:94e848a6b83da10898cbf1311a815f770acc9b6a3f2d646f330d57eb4e87592e"}, + {file = "contourpy-1.3.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:d78ab28a03c854a873787a0a42254a0ccb3cb133c672f645c9f9c8f3ae9d0800"}, + {file = "contourpy-1.3.0-cp39-cp39-win32.whl", hash = "sha256:81cb5ed4952aae6014bc9d0421dec7c5835c9c8c31cdf51910b708f548cf58e5"}, + {file = "contourpy-1.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:14e262f67bd7e6eb6880bc564dcda30b15e351a594657e55b7eec94b6ef72843"}, + {file = "contourpy-1.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:fe41b41505a5a33aeaed2a613dccaeaa74e0e3ead6dd6fd3a118fb471644fd6c"}, + {file = "contourpy-1.3.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eca7e17a65f72a5133bdbec9ecf22401c62bcf4821361ef7811faee695799779"}, + {file = "contourpy-1.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:1ec4dc6bf570f5b22ed0d7efba0dfa9c5b9e0431aeea7581aa217542d9e809a4"}, + {file = "contourpy-1.3.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:00ccd0dbaad6d804ab259820fa7cb0b8036bda0686ef844d24125d8287178ce0"}, + {file = "contourpy-1.3.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ca947601224119117f7c19c9cdf6b3ab54c5726ef1d906aa4a69dfb6dd58102"}, + {file = "contourpy-1.3.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c6ec93afeb848a0845a18989da3beca3eec2c0f852322efe21af1931147d12cb"}, + {file = "contourpy-1.3.0.tar.gz", hash = "sha256:7ffa0db17717a8ffb127efd0c95a4362d996b892c2904db72428d5b52e1938a4"}, +] + +[package.dependencies] +numpy = ">=1.23" + +[package.extras] +bokeh = ["bokeh", "selenium"] +docs = ["furo", "sphinx (>=7.2)", "sphinx-copybutton"] +mypy = ["contourpy[bokeh,docs]", "docutils-stubs", "mypy (==1.11.1)", "types-Pillow"] +test = ["Pillow", "contourpy[test-no-images]", "matplotlib"] +test-no-images = ["pytest", "pytest-cov", "pytest-rerunfailures", "pytest-xdist", "wurlitzer"] + [[package]] name = "coverage" version = "7.6.1" @@ -178,6 +275,21 @@ files = [ [package.extras] toml = ["tomli"] +[[package]] +name = "cycler" +version = "0.12.1" +description = "Composable style cycles" +optional = false +python-versions = ">=3.8" +files = [ + {file = "cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30"}, + {file = "cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c"}, +] + +[package.extras] +docs = ["ipython", "matplotlib", "numpydoc", "sphinx"] +tests = ["pytest", "pytest-cov", "pytest-xdist"] + [[package]] name = "dnspython" version = "2.6.1" @@ -313,6 +425,71 @@ six = ">=1.3.0" [package.extras] docs = ["sphinx"] +[[package]] +name = "fonttools" +version = "4.53.1" +description = "Tools to manipulate font files" +optional = false +python-versions = ">=3.8" +files = [ + {file = "fonttools-4.53.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0679a30b59d74b6242909945429dbddb08496935b82f91ea9bf6ad240ec23397"}, + {file = "fonttools-4.53.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e8bf06b94694251861ba7fdeea15c8ec0967f84c3d4143ae9daf42bbc7717fe3"}, + {file = "fonttools-4.53.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b96cd370a61f4d083c9c0053bf634279b094308d52fdc2dd9a22d8372fdd590d"}, + {file = "fonttools-4.53.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1c7c5aa18dd3b17995898b4a9b5929d69ef6ae2af5b96d585ff4005033d82f0"}, + {file = "fonttools-4.53.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e013aae589c1c12505da64a7d8d023e584987e51e62006e1bb30d72f26522c41"}, + {file = "fonttools-4.53.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9efd176f874cb6402e607e4cc9b4a9cd584d82fc34a4b0c811970b32ba62501f"}, + {file = "fonttools-4.53.1-cp310-cp310-win32.whl", hash = "sha256:c8696544c964500aa9439efb6761947393b70b17ef4e82d73277413f291260a4"}, + {file = "fonttools-4.53.1-cp310-cp310-win_amd64.whl", hash = "sha256:8959a59de5af6d2bec27489e98ef25a397cfa1774b375d5787509c06659b3671"}, + {file = "fonttools-4.53.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:da33440b1413bad53a8674393c5d29ce64d8c1a15ef8a77c642ffd900d07bfe1"}, + {file = "fonttools-4.53.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5ff7e5e9bad94e3a70c5cd2fa27f20b9bb9385e10cddab567b85ce5d306ea923"}, + {file = "fonttools-4.53.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6e7170d675d12eac12ad1a981d90f118c06cf680b42a2d74c6c931e54b50719"}, + {file = "fonttools-4.53.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bee32ea8765e859670c4447b0817514ca79054463b6b79784b08a8df3a4d78e3"}, + {file = "fonttools-4.53.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6e08f572625a1ee682115223eabebc4c6a2035a6917eac6f60350aba297ccadb"}, + {file = "fonttools-4.53.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b21952c092ffd827504de7e66b62aba26fdb5f9d1e435c52477e6486e9d128b2"}, + {file = "fonttools-4.53.1-cp311-cp311-win32.whl", hash = "sha256:9dfdae43b7996af46ff9da520998a32b105c7f098aeea06b2226b30e74fbba88"}, + {file = "fonttools-4.53.1-cp311-cp311-win_amd64.whl", hash = "sha256:d4d0096cb1ac7a77b3b41cd78c9b6bc4a400550e21dc7a92f2b5ab53ed74eb02"}, + {file = "fonttools-4.53.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d92d3c2a1b39631a6131c2fa25b5406855f97969b068e7e08413325bc0afba58"}, + {file = "fonttools-4.53.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3b3c8ebafbee8d9002bd8f1195d09ed2bd9ff134ddec37ee8f6a6375e6a4f0e8"}, + {file = "fonttools-4.53.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32f029c095ad66c425b0ee85553d0dc326d45d7059dbc227330fc29b43e8ba60"}, + {file = "fonttools-4.53.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10f5e6c3510b79ea27bb1ebfcc67048cde9ec67afa87c7dd7efa5c700491ac7f"}, + {file = "fonttools-4.53.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f677ce218976496a587ab17140da141557beb91d2a5c1a14212c994093f2eae2"}, + {file = "fonttools-4.53.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9e6ceba2a01b448e36754983d376064730690401da1dd104ddb543519470a15f"}, + {file = "fonttools-4.53.1-cp312-cp312-win32.whl", hash = "sha256:791b31ebbc05197d7aa096bbc7bd76d591f05905d2fd908bf103af4488e60670"}, + {file = "fonttools-4.53.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ed170b5e17da0264b9f6fae86073be3db15fa1bd74061c8331022bca6d09bab"}, + {file = "fonttools-4.53.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:c818c058404eb2bba05e728d38049438afd649e3c409796723dfc17cd3f08749"}, + {file = "fonttools-4.53.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:651390c3b26b0c7d1f4407cad281ee7a5a85a31a110cbac5269de72a51551ba2"}, + {file = "fonttools-4.53.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e54f1bba2f655924c1138bbc7fa91abd61f45c68bd65ab5ed985942712864bbb"}, + {file = "fonttools-4.53.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9cd19cf4fe0595ebdd1d4915882b9440c3a6d30b008f3cc7587c1da7b95be5f"}, + {file = "fonttools-4.53.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:2af40ae9cdcb204fc1d8f26b190aa16534fcd4f0df756268df674a270eab575d"}, + {file = "fonttools-4.53.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:35250099b0cfb32d799fb5d6c651220a642fe2e3c7d2560490e6f1d3f9ae9169"}, + {file = "fonttools-4.53.1-cp38-cp38-win32.whl", hash = "sha256:f08df60fbd8d289152079a65da4e66a447efc1d5d5a4d3f299cdd39e3b2e4a7d"}, + {file = "fonttools-4.53.1-cp38-cp38-win_amd64.whl", hash = "sha256:7b6b35e52ddc8fb0db562133894e6ef5b4e54e1283dff606fda3eed938c36fc8"}, + {file = "fonttools-4.53.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:75a157d8d26c06e64ace9df037ee93a4938a4606a38cb7ffaf6635e60e253b7a"}, + {file = "fonttools-4.53.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4824c198f714ab5559c5be10fd1adf876712aa7989882a4ec887bf1ef3e00e31"}, + {file = "fonttools-4.53.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:becc5d7cb89c7b7afa8321b6bb3dbee0eec2b57855c90b3e9bf5fb816671fa7c"}, + {file = "fonttools-4.53.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84ec3fb43befb54be490147b4a922b5314e16372a643004f182babee9f9c3407"}, + {file = "fonttools-4.53.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:73379d3ffdeecb376640cd8ed03e9d2d0e568c9d1a4e9b16504a834ebadc2dfb"}, + {file = "fonttools-4.53.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:02569e9a810f9d11f4ae82c391ebc6fb5730d95a0657d24d754ed7763fb2d122"}, + {file = "fonttools-4.53.1-cp39-cp39-win32.whl", hash = "sha256:aae7bd54187e8bf7fd69f8ab87b2885253d3575163ad4d669a262fe97f0136cb"}, + {file = "fonttools-4.53.1-cp39-cp39-win_amd64.whl", hash = "sha256:e5b708073ea3d684235648786f5f6153a48dc8762cdfe5563c57e80787c29fbb"}, + {file = "fonttools-4.53.1-py3-none-any.whl", hash = "sha256:f1f8758a2ad110bd6432203a344269f445a2907dc24ef6bccfd0ac4e14e0d71d"}, + {file = "fonttools-4.53.1.tar.gz", hash = "sha256:e128778a8e9bc11159ce5447f76766cefbd876f44bd79aff030287254e4752c4"}, +] + +[package.extras] +all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "fs (>=2.2.0,<3)", "lxml (>=4.0)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "pycairo", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.1.0)", "xattr", "zopfli (>=0.1.4)"] +graphite = ["lz4 (>=1.7.4.2)"] +interpolatable = ["munkres", "pycairo", "scipy"] +lxml = ["lxml (>=4.0)"] +pathops = ["skia-pathops (>=0.5.0)"] +plot = ["matplotlib"] +repacker = ["uharfbuzz (>=0.23.0)"] +symfont = ["sympy"] +type1 = ["xattr"] +ufo = ["fs (>=2.2.0,<3)"] +unicode = ["unicodedata2 (>=15.1.0)"] +woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"] + [[package]] name = "h11" version = "0.14.0" @@ -467,6 +644,119 @@ MarkupSafe = ">=2.0" [package.extras] i18n = ["Babel (>=2.7)"] +[[package]] +name = "kiwisolver" +version = "1.4.5" +description = "A fast implementation of the Cassowary constraint solver" +optional = false +python-versions = ">=3.7" +files = [ + {file = "kiwisolver-1.4.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:05703cf211d585109fcd72207a31bb170a0f22144d68298dc5e61b3c946518af"}, + {file = "kiwisolver-1.4.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:146d14bebb7f1dc4d5fbf74f8a6cb15ac42baadee8912eb84ac0b3b2a3dc6ac3"}, + {file = "kiwisolver-1.4.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6ef7afcd2d281494c0a9101d5c571970708ad911d028137cd558f02b851c08b4"}, + {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9eaa8b117dc8337728e834b9c6e2611f10c79e38f65157c4c38e9400286f5cb1"}, + {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ec20916e7b4cbfb1f12380e46486ec4bcbaa91a9c448b97023fde0d5bbf9e4ff"}, + {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39b42c68602539407884cf70d6a480a469b93b81b7701378ba5e2328660c847a"}, + {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aa12042de0171fad672b6c59df69106d20d5596e4f87b5e8f76df757a7c399aa"}, + {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2a40773c71d7ccdd3798f6489aaac9eee213d566850a9533f8d26332d626b82c"}, + {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:19df6e621f6d8b4b9c4d45f40a66839294ff2bb235e64d2178f7522d9170ac5b"}, + {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:83d78376d0d4fd884e2c114d0621624b73d2aba4e2788182d286309ebdeed770"}, + {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e391b1f0a8a5a10ab3b9bb6afcfd74f2175f24f8975fb87ecae700d1503cdee0"}, + {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:852542f9481f4a62dbb5dd99e8ab7aedfeb8fb6342349a181d4036877410f525"}, + {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59edc41b24031bc25108e210c0def6f6c2191210492a972d585a06ff246bb79b"}, + {file = "kiwisolver-1.4.5-cp310-cp310-win32.whl", hash = "sha256:a6aa6315319a052b4ee378aa171959c898a6183f15c1e541821c5c59beaa0238"}, + {file = "kiwisolver-1.4.5-cp310-cp310-win_amd64.whl", hash = "sha256:d0ef46024e6a3d79c01ff13801cb19d0cad7fd859b15037aec74315540acc276"}, + {file = "kiwisolver-1.4.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:11863aa14a51fd6ec28688d76f1735f8f69ab1fabf388851a595d0721af042f5"}, + {file = "kiwisolver-1.4.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8ab3919a9997ab7ef2fbbed0cc99bb28d3c13e6d4b1ad36e97e482558a91be90"}, + {file = "kiwisolver-1.4.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fcc700eadbbccbf6bc1bcb9dbe0786b4b1cb91ca0dcda336eef5c2beed37b797"}, + {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dfdd7c0b105af050eb3d64997809dc21da247cf44e63dc73ff0fd20b96be55a9"}, + {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76c6a5964640638cdeaa0c359382e5703e9293030fe730018ca06bc2010c4437"}, + {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bbea0db94288e29afcc4c28afbf3a7ccaf2d7e027489c449cf7e8f83c6346eb9"}, + {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ceec1a6bc6cab1d6ff5d06592a91a692f90ec7505d6463a88a52cc0eb58545da"}, + {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:040c1aebeda72197ef477a906782b5ab0d387642e93bda547336b8957c61022e"}, + {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f91de7223d4c7b793867797bacd1ee53bfe7359bd70d27b7b58a04efbb9436c8"}, + {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:faae4860798c31530dd184046a900e652c95513796ef51a12bc086710c2eec4d"}, + {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:b0157420efcb803e71d1b28e2c287518b8808b7cf1ab8af36718fd0a2c453eb0"}, + {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:06f54715b7737c2fecdbf140d1afb11a33d59508a47bf11bb38ecf21dc9ab79f"}, + {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fdb7adb641a0d13bdcd4ef48e062363d8a9ad4a182ac7647ec88f695e719ae9f"}, + {file = "kiwisolver-1.4.5-cp311-cp311-win32.whl", hash = "sha256:bb86433b1cfe686da83ce32a9d3a8dd308e85c76b60896d58f082136f10bffac"}, + {file = "kiwisolver-1.4.5-cp311-cp311-win_amd64.whl", hash = "sha256:6c08e1312a9cf1074d17b17728d3dfce2a5125b2d791527f33ffbe805200a355"}, + {file = "kiwisolver-1.4.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:32d5cf40c4f7c7b3ca500f8985eb3fb3a7dfc023215e876f207956b5ea26632a"}, + {file = "kiwisolver-1.4.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f846c260f483d1fd217fe5ed7c173fb109efa6b1fc8381c8b7552c5781756192"}, + {file = "kiwisolver-1.4.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5ff5cf3571589b6d13bfbfd6bcd7a3f659e42f96b5fd1c4830c4cf21d4f5ef45"}, + {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7269d9e5f1084a653d575c7ec012ff57f0c042258bf5db0954bf551c158466e7"}, + {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da802a19d6e15dffe4b0c24b38b3af68e6c1a68e6e1d8f30148c83864f3881db"}, + {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3aba7311af82e335dd1e36ffff68aaca609ca6290c2cb6d821a39aa075d8e3ff"}, + {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:763773d53f07244148ccac5b084da5adb90bfaee39c197554f01b286cf869228"}, + {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2270953c0d8cdab5d422bee7d2007f043473f9d2999631c86a223c9db56cbd16"}, + {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d099e745a512f7e3bbe7249ca835f4d357c586d78d79ae8f1dcd4d8adeb9bda9"}, + {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:74db36e14a7d1ce0986fa104f7d5637aea5c82ca6326ed0ec5694280942d1162"}, + {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7e5bab140c309cb3a6ce373a9e71eb7e4873c70c2dda01df6820474f9889d6d4"}, + {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0f114aa76dc1b8f636d077979c0ac22e7cd8f3493abbab152f20eb8d3cda71f3"}, + {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:88a2df29d4724b9237fc0c6eaf2a1adae0cdc0b3e9f4d8e7dc54b16812d2d81a"}, + {file = "kiwisolver-1.4.5-cp312-cp312-win32.whl", hash = "sha256:72d40b33e834371fd330fb1472ca19d9b8327acb79a5821d4008391db8e29f20"}, + {file = "kiwisolver-1.4.5-cp312-cp312-win_amd64.whl", hash = "sha256:2c5674c4e74d939b9d91dda0fae10597ac7521768fec9e399c70a1f27e2ea2d9"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3a2b053a0ab7a3960c98725cfb0bf5b48ba82f64ec95fe06f1d06c99b552e130"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cd32d6c13807e5c66a7cbb79f90b553642f296ae4518a60d8d76243b0ad2898"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59ec7b7c7e1a61061850d53aaf8e93db63dce0c936db1fda2658b70e4a1be709"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:da4cfb373035def307905d05041c1d06d8936452fe89d464743ae7fb8371078b"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2400873bccc260b6ae184b2b8a4fec0e4082d30648eadb7c3d9a13405d861e89"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1b04139c4236a0f3aff534479b58f6f849a8b351e1314826c2d230849ed48985"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:4e66e81a5779b65ac21764c295087de82235597a2293d18d943f8e9e32746265"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:7931d8f1f67c4be9ba1dd9c451fb0eeca1a25b89e4d3f89e828fe12a519b782a"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:b3f7e75f3015df442238cca659f8baa5f42ce2a8582727981cbfa15fee0ee205"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:bbf1d63eef84b2e8c89011b7f2235b1e0bf7dacc11cac9431fc6468e99ac77fb"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:4c380469bd3f970ef677bf2bcba2b6b0b4d5c75e7a020fb863ef75084efad66f"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-win32.whl", hash = "sha256:9408acf3270c4b6baad483865191e3e582b638b1654a007c62e3efe96f09a9a3"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-win_amd64.whl", hash = "sha256:5b94529f9b2591b7af5f3e0e730a4e0a41ea174af35a4fd067775f9bdfeee01a"}, + {file = "kiwisolver-1.4.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:11c7de8f692fc99816e8ac50d1d1aef4f75126eefc33ac79aac02c099fd3db71"}, + {file = "kiwisolver-1.4.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:53abb58632235cd154176ced1ae8f0d29a6657aa1aa9decf50b899b755bc2b93"}, + {file = "kiwisolver-1.4.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:88b9f257ca61b838b6f8094a62418421f87ac2a1069f7e896c36a7d86b5d4c29"}, + {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3195782b26fc03aa9c6913d5bad5aeb864bdc372924c093b0f1cebad603dd712"}, + {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc579bf0f502e54926519451b920e875f433aceb4624a3646b3252b5caa9e0b6"}, + {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a580c91d686376f0f7c295357595c5a026e6cbc3d77b7c36e290201e7c11ecb"}, + {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cfe6ab8da05c01ba6fbea630377b5da2cd9bcbc6338510116b01c1bc939a2c18"}, + {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d2e5a98f0ec99beb3c10e13b387f8db39106d53993f498b295f0c914328b1333"}, + {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a51a263952b1429e429ff236d2f5a21c5125437861baeed77f5e1cc2d2c7c6da"}, + {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3edd2fa14e68c9be82c5b16689e8d63d89fe927e56debd6e1dbce7a26a17f81b"}, + {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:74d1b44c6cfc897df648cc9fdaa09bc3e7679926e6f96df05775d4fb3946571c"}, + {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:76d9289ed3f7501012e05abb8358bbb129149dbd173f1f57a1bf1c22d19ab7cc"}, + {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:92dea1ffe3714fa8eb6a314d2b3c773208d865a0e0d35e713ec54eea08a66250"}, + {file = "kiwisolver-1.4.5-cp38-cp38-win32.whl", hash = "sha256:5c90ae8c8d32e472be041e76f9d2f2dbff4d0b0be8bd4041770eddb18cf49a4e"}, + {file = "kiwisolver-1.4.5-cp38-cp38-win_amd64.whl", hash = "sha256:c7940c1dc63eb37a67721b10d703247552416f719c4188c54e04334321351ced"}, + {file = "kiwisolver-1.4.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9407b6a5f0d675e8a827ad8742e1d6b49d9c1a1da5d952a67d50ef5f4170b18d"}, + {file = "kiwisolver-1.4.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:15568384086b6df3c65353820a4473575dbad192e35010f622c6ce3eebd57af9"}, + {file = "kiwisolver-1.4.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0dc9db8e79f0036e8173c466d21ef18e1befc02de8bf8aa8dc0813a6dc8a7046"}, + {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:cdc8a402aaee9a798b50d8b827d7ecf75edc5fb35ea0f91f213ff927c15f4ff0"}, + {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6c3bd3cde54cafb87d74d8db50b909705c62b17c2099b8f2e25b461882e544ff"}, + {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:955e8513d07a283056b1396e9a57ceddbd272d9252c14f154d450d227606eb54"}, + {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:346f5343b9e3f00b8db8ba359350eb124b98c99efd0b408728ac6ebf38173958"}, + {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b9098e0049e88c6a24ff64545cdfc50807818ba6c1b739cae221bbbcbc58aad3"}, + {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:00bd361b903dc4bbf4eb165f24d1acbee754fce22ded24c3d56eec268658a5cf"}, + {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7b8b454bac16428b22560d0a1cf0a09875339cab69df61d7805bf48919415901"}, + {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:f1d072c2eb0ad60d4c183f3fb44ac6f73fb7a8f16a2694a91f988275cbf352f9"}, + {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:31a82d498054cac9f6d0b53d02bb85811185bcb477d4b60144f915f3b3126342"}, + {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6512cb89e334e4700febbffaaa52761b65b4f5a3cf33f960213d5656cea36a77"}, + {file = "kiwisolver-1.4.5-cp39-cp39-win32.whl", hash = "sha256:9db8ea4c388fdb0f780fe91346fd438657ea602d58348753d9fb265ce1bca67f"}, + {file = "kiwisolver-1.4.5-cp39-cp39-win_amd64.whl", hash = "sha256:59415f46a37f7f2efeec758353dd2eae1b07640d8ca0f0c42548ec4125492635"}, + {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5c7b3b3a728dc6faf3fc372ef24f21d1e3cee2ac3e9596691d746e5a536de920"}, + {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:620ced262a86244e2be10a676b646f29c34537d0d9cc8eb26c08f53d98013390"}, + {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:378a214a1e3bbf5ac4a8708304318b4f890da88c9e6a07699c4ae7174c09a68d"}, + {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aaf7be1207676ac608a50cd08f102f6742dbfc70e8d60c4db1c6897f62f71523"}, + {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:ba55dce0a9b8ff59495ddd050a0225d58bd0983d09f87cfe2b6aec4f2c1234e4"}, + {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:fd32ea360bcbb92d28933fc05ed09bffcb1704ba3fc7942e81db0fd4f81a7892"}, + {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5e7139af55d1688f8b960ee9ad5adafc4ac17c1c473fe07133ac092310d76544"}, + {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:dced8146011d2bc2e883f9bd68618b8247387f4bbec46d7392b3c3b032640126"}, + {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9bf3325c47b11b2e51bca0824ea217c7cd84491d8ac4eefd1e409705ef092bd"}, + {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:5794cf59533bc3f1b1c821f7206a3617999db9fbefc345360aafe2e067514929"}, + {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e368f200bbc2e4f905b8e71eb38b3c04333bddaa6a2464a6355487b02bb7fb09"}, + {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5d706eba36b4c4d5bc6c6377bb6568098765e990cfc21ee16d13963fab7b3e7"}, + {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85267bd1aa8880a9c88a8cb71e18d3d64d2751a790e6ca6c27b8ccc724bcd5ad"}, + {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:210ef2c3a1f03272649aff1ef992df2e724748918c4bc2d5a90352849eb40bea"}, + {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:11d011a7574eb3b82bcc9c1a1d35c1d7075677fdd15de527d91b46bd35e935ee"}, + {file = "kiwisolver-1.4.5.tar.gz", hash = "sha256:e57e563a57fb22a142da34f38acc2fc1a5c864bc29ca1517a88abc963e60d6ec"}, +] + [[package]] name = "markdown-it-py" version = "3.0.0" @@ -560,6 +850,69 @@ files = [ {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, ] +[[package]] +name = "matplotlib" +version = "3.9.2" +description = "Python plotting package" +optional = false +python-versions = ">=3.9" +files = [ + {file = "matplotlib-3.9.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:9d78bbc0cbc891ad55b4f39a48c22182e9bdaea7fc0e5dbd364f49f729ca1bbb"}, + {file = "matplotlib-3.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c375cc72229614632c87355366bdf2570c2dac01ac66b8ad048d2dabadf2d0d4"}, + {file = "matplotlib-3.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d94ff717eb2bd0b58fe66380bd8b14ac35f48a98e7c6765117fe67fb7684e64"}, + {file = "matplotlib-3.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab68d50c06938ef28681073327795c5db99bb4666214d2d5f880ed11aeaded66"}, + {file = "matplotlib-3.9.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:65aacf95b62272d568044531e41de26285d54aec8cb859031f511f84bd8b495a"}, + {file = "matplotlib-3.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:3fd595f34aa8a55b7fc8bf9ebea8aa665a84c82d275190a61118d33fbc82ccae"}, + {file = "matplotlib-3.9.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d8dd059447824eec055e829258ab092b56bb0579fc3164fa09c64f3acd478772"}, + {file = "matplotlib-3.9.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c797dac8bb9c7a3fd3382b16fe8f215b4cf0f22adccea36f1545a6d7be310b41"}, + {file = "matplotlib-3.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d719465db13267bcef19ea8954a971db03b9f48b4647e3860e4bc8e6ed86610f"}, + {file = "matplotlib-3.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8912ef7c2362f7193b5819d17dae8629b34a95c58603d781329712ada83f9447"}, + {file = "matplotlib-3.9.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7741f26a58a240f43bee74965c4882b6c93df3e7eb3de160126d8c8f53a6ae6e"}, + {file = "matplotlib-3.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:ae82a14dab96fbfad7965403c643cafe6515e386de723e498cf3eeb1e0b70cc7"}, + {file = "matplotlib-3.9.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:ac43031375a65c3196bee99f6001e7fa5bdfb00ddf43379d3c0609bdca042df9"}, + {file = "matplotlib-3.9.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:be0fc24a5e4531ae4d8e858a1a548c1fe33b176bb13eff7f9d0d38ce5112a27d"}, + {file = "matplotlib-3.9.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf81de2926c2db243c9b2cbc3917619a0fc85796c6ba4e58f541df814bbf83c7"}, + {file = "matplotlib-3.9.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6ee45bc4245533111ced13f1f2cace1e7f89d1c793390392a80c139d6cf0e6c"}, + {file = "matplotlib-3.9.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:306c8dfc73239f0e72ac50e5a9cf19cc4e8e331dd0c54f5e69ca8758550f1e1e"}, + {file = "matplotlib-3.9.2-cp312-cp312-win_amd64.whl", hash = "sha256:5413401594cfaff0052f9d8b1aafc6d305b4bd7c4331dccd18f561ff7e1d3bd3"}, + {file = "matplotlib-3.9.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:18128cc08f0d3cfff10b76baa2f296fc28c4607368a8402de61bb3f2eb33c7d9"}, + {file = "matplotlib-3.9.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4876d7d40219e8ae8bb70f9263bcbe5714415acfdf781086601211335e24f8aa"}, + {file = "matplotlib-3.9.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6d9f07a80deab4bb0b82858a9e9ad53d1382fd122be8cde11080f4e7dfedb38b"}, + {file = "matplotlib-3.9.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7c0410f181a531ec4e93bbc27692f2c71a15c2da16766f5ba9761e7ae518413"}, + {file = "matplotlib-3.9.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:909645cce2dc28b735674ce0931a4ac94e12f5b13f6bb0b5a5e65e7cea2c192b"}, + {file = "matplotlib-3.9.2-cp313-cp313-win_amd64.whl", hash = "sha256:f32c7410c7f246838a77d6d1eff0c0f87f3cb0e7c4247aebea71a6d5a68cab49"}, + {file = "matplotlib-3.9.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:37e51dd1c2db16ede9cfd7b5cabdfc818b2c6397c83f8b10e0e797501c963a03"}, + {file = "matplotlib-3.9.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b82c5045cebcecd8496a4d694d43f9cc84aeeb49fe2133e036b207abe73f4d30"}, + {file = "matplotlib-3.9.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f053c40f94bc51bc03832a41b4f153d83f2062d88c72b5e79997072594e97e51"}, + {file = "matplotlib-3.9.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dbe196377a8248972f5cede786d4c5508ed5f5ca4a1e09b44bda889958b33f8c"}, + {file = "matplotlib-3.9.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5816b1e1fe8c192cbc013f8f3e3368ac56fbecf02fb41b8f8559303f24c5015e"}, + {file = "matplotlib-3.9.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:cef2a73d06601437be399908cf13aee74e86932a5ccc6ccdf173408ebc5f6bb2"}, + {file = "matplotlib-3.9.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e0830e188029c14e891fadd99702fd90d317df294c3298aad682739c5533721a"}, + {file = "matplotlib-3.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03ba9c1299c920964e8d3857ba27173b4dbb51ca4bab47ffc2c2ba0eb5e2cbc5"}, + {file = "matplotlib-3.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1cd93b91ab47a3616b4d3c42b52f8363b88ca021e340804c6ab2536344fad9ca"}, + {file = "matplotlib-3.9.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:6d1ce5ed2aefcdce11904fc5bbea7d9c21fff3d5f543841edf3dea84451a09ea"}, + {file = "matplotlib-3.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:b2696efdc08648536efd4e1601b5fd491fd47f4db97a5fbfd175549a7365c1b2"}, + {file = "matplotlib-3.9.2-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:d52a3b618cb1cbb769ce2ee1dcdb333c3ab6e823944e9a2d36e37253815f9556"}, + {file = "matplotlib-3.9.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:039082812cacd6c6bec8e17a9c1e6baca230d4116d522e81e1f63a74d01d2e21"}, + {file = "matplotlib-3.9.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6758baae2ed64f2331d4fd19be38b7b4eae3ecec210049a26b6a4f3ae1c85dcc"}, + {file = "matplotlib-3.9.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:050598c2b29e0b9832cde72bcf97627bf00262adbc4a54e2b856426bb2ef0697"}, + {file = "matplotlib-3.9.2.tar.gz", hash = "sha256:96ab43906269ca64a6366934106fa01534454a69e471b7bf3d79083981aaab92"}, +] + +[package.dependencies] +contourpy = ">=1.0.1" +cycler = ">=0.10" +fonttools = ">=4.22.0" +kiwisolver = ">=1.3.1" +numpy = ">=1.23" +packaging = ">=20.0" +pillow = ">=8" +pyparsing = ">=2.3.1" +python-dateutil = ">=2.7" + +[package.extras] +dev = ["meson-python (>=0.13.1)", "numpy (>=1.25)", "pybind11 (>=2.6)", "setuptools (>=64)", "setuptools_scm (>=7)"] + [[package]] name = "mdurl" version = "0.1.2" @@ -571,6 +924,67 @@ files = [ {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, ] +[[package]] +name = "numpy" +version = "2.1.0" +description = "Fundamental package for array computing in Python" +optional = false +python-versions = ">=3.10" +files = [ + {file = "numpy-2.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6326ab99b52fafdcdeccf602d6286191a79fe2fda0ae90573c5814cd2b0bc1b8"}, + {file = "numpy-2.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0937e54c09f7a9a68da6889362ddd2ff584c02d015ec92672c099b61555f8911"}, + {file = "numpy-2.1.0-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:30014b234f07b5fec20f4146f69e13cfb1e33ee9a18a1879a0142fbb00d47673"}, + {file = "numpy-2.1.0-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:899da829b362ade41e1e7eccad2cf274035e1cb36ba73034946fccd4afd8606b"}, + {file = "numpy-2.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08801848a40aea24ce16c2ecde3b756f9ad756586fb2d13210939eb69b023f5b"}, + {file = "numpy-2.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:398049e237d1aae53d82a416dade04defed1a47f87d18d5bd615b6e7d7e41d1f"}, + {file = "numpy-2.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0abb3916a35d9090088a748636b2c06dc9a6542f99cd476979fb156a18192b84"}, + {file = "numpy-2.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:10e2350aea18d04832319aac0f887d5fcec1b36abd485d14f173e3e900b83e33"}, + {file = "numpy-2.1.0-cp310-cp310-win32.whl", hash = "sha256:f6b26e6c3b98adb648243670fddc8cab6ae17473f9dc58c51574af3e64d61211"}, + {file = "numpy-2.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:f505264735ee074250a9c78247ee8618292091d9d1fcc023290e9ac67e8f1afa"}, + {file = "numpy-2.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:76368c788ccb4f4782cf9c842b316140142b4cbf22ff8db82724e82fe1205dce"}, + {file = "numpy-2.1.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:f8e93a01a35be08d31ae33021e5268f157a2d60ebd643cfc15de6ab8e4722eb1"}, + {file = "numpy-2.1.0-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:9523f8b46485db6939bd069b28b642fec86c30909cea90ef550373787f79530e"}, + {file = "numpy-2.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54139e0eb219f52f60656d163cbe67c31ede51d13236c950145473504fa208cb"}, + {file = "numpy-2.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5ebbf9fbdabed208d4ecd2e1dfd2c0741af2f876e7ae522c2537d404ca895c3"}, + {file = "numpy-2.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:378cb4f24c7d93066ee4103204f73ed046eb88f9ad5bb2275bb9fa0f6a02bd36"}, + {file = "numpy-2.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8f699a709120b220dfe173f79c73cb2a2cab2c0b88dd59d7b49407d032b8ebd"}, + {file = "numpy-2.1.0-cp311-cp311-win32.whl", hash = "sha256:ffbd6faeb190aaf2b5e9024bac9622d2ee549b7ec89ef3a9373fa35313d44e0e"}, + {file = "numpy-2.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:0af3a5987f59d9c529c022c8c2a64805b339b7ef506509fba7d0556649b9714b"}, + {file = "numpy-2.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fe76d75b345dc045acdbc006adcb197cc680754afd6c259de60d358d60c93736"}, + {file = "numpy-2.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f358ea9e47eb3c2d6eba121ab512dfff38a88db719c38d1e67349af210bc7529"}, + {file = "numpy-2.1.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:dd94ce596bda40a9618324547cfaaf6650b1a24f5390350142499aa4e34e53d1"}, + {file = "numpy-2.1.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:b47c551c6724960479cefd7353656498b86e7232429e3a41ab83be4da1b109e8"}, + {file = "numpy-2.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0756a179afa766ad7cb6f036de622e8a8f16ffdd55aa31f296c870b5679d745"}, + {file = "numpy-2.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24003ba8ff22ea29a8c306e61d316ac74111cebf942afbf692df65509a05f111"}, + {file = "numpy-2.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b34fa5e3b5d6dc7e0a4243fa0f81367027cb6f4a7215a17852979634b5544ee0"}, + {file = "numpy-2.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c4f982715e65036c34897eb598d64aef15150c447be2cfc6643ec7a11af06574"}, + {file = "numpy-2.1.0-cp312-cp312-win32.whl", hash = "sha256:c4cd94dfefbefec3f8b544f61286584292d740e6e9d4677769bc76b8f41deb02"}, + {file = "numpy-2.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:a0cdef204199278f5c461a0bed6ed2e052998276e6d8ab2963d5b5c39a0500bc"}, + {file = "numpy-2.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8ab81ccd753859ab89e67199b9da62c543850f819993761c1e94a75a814ed667"}, + {file = "numpy-2.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:442596f01913656d579309edcd179a2a2f9977d9a14ff41d042475280fc7f34e"}, + {file = "numpy-2.1.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:848c6b5cad9898e4b9ef251b6f934fa34630371f2e916261070a4eb9092ffd33"}, + {file = "numpy-2.1.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:54c6a63e9d81efe64bfb7bcb0ec64332a87d0b87575f6009c8ba67ea6374770b"}, + {file = "numpy-2.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:652e92fc409e278abdd61e9505649e3938f6d04ce7ef1953f2ec598a50e7c195"}, + {file = "numpy-2.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ab32eb9170bf8ffcbb14f11613f4a0b108d3ffee0832457c5d4808233ba8977"}, + {file = "numpy-2.1.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:8fb49a0ba4d8f41198ae2d52118b050fd34dace4b8f3fb0ee34e23eb4ae775b1"}, + {file = "numpy-2.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:44e44973262dc3ae79e9063a1284a73e09d01b894b534a769732ccd46c28cc62"}, + {file = "numpy-2.1.0-cp313-cp313-win32.whl", hash = "sha256:ab83adc099ec62e044b1fbb3a05499fa1e99f6d53a1dde102b2d85eff66ed324"}, + {file = "numpy-2.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:de844aaa4815b78f6023832590d77da0e3b6805c644c33ce94a1e449f16d6ab5"}, + {file = "numpy-2.1.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:343e3e152bf5a087511cd325e3b7ecfd5b92d369e80e74c12cd87826e263ec06"}, + {file = "numpy-2.1.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f07fa2f15dabe91259828ce7d71b5ca9e2eb7c8c26baa822c825ce43552f4883"}, + {file = "numpy-2.1.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5474dad8c86ee9ba9bb776f4b99ef2d41b3b8f4e0d199d4f7304728ed34d0300"}, + {file = "numpy-2.1.0-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:1f817c71683fd1bb5cff1529a1d085a57f02ccd2ebc5cd2c566f9a01118e3b7d"}, + {file = "numpy-2.1.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a3336fbfa0d38d3deacd3fe7f3d07e13597f29c13abf4d15c3b6dc2291cbbdd"}, + {file = "numpy-2.1.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a894c51fd8c4e834f00ac742abad73fc485df1062f1b875661a3c1e1fb1c2f6"}, + {file = "numpy-2.1.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:9156ca1f79fc4acc226696e95bfcc2b486f165a6a59ebe22b2c1f82ab190384a"}, + {file = "numpy-2.1.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:624884b572dff8ca8f60fab591413f077471de64e376b17d291b19f56504b2bb"}, + {file = "numpy-2.1.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:15ef8b2177eeb7e37dd5ef4016f30b7659c57c2c0b57a779f1d537ff33a72c7b"}, + {file = "numpy-2.1.0-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:e5f0642cdf4636198a4990de7a71b693d824c56a757862230454629cf62e323d"}, + {file = "numpy-2.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15976718c004466406342789f31b6673776360f3b1e3c575f25302d7e789575"}, + {file = "numpy-2.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:6c1de77ded79fef664d5098a66810d4d27ca0224e9051906e634b3f7ead134c2"}, + {file = "numpy-2.1.0.tar.gz", hash = "sha256:7dc90da0081f7e1da49ec4e398ede6a8e9cc4f5ebe5f9e06b443ed889ee9aaa2"}, +] + [[package]] name = "packaging" version = "24.1" @@ -598,6 +1012,103 @@ gevent = ["gevent"] tornado = ["tornado"] twisted = ["twisted"] +[[package]] +name = "pillow" +version = "10.4.0" +description = "Python Imaging Library (Fork)" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pillow-10.4.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:4d9667937cfa347525b319ae34375c37b9ee6b525440f3ef48542fcf66f2731e"}, + {file = "pillow-10.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:543f3dc61c18dafb755773efc89aae60d06b6596a63914107f75459cf984164d"}, + {file = "pillow-10.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7928ecbf1ece13956b95d9cbcfc77137652b02763ba384d9ab508099a2eca856"}, + {file = "pillow-10.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4d49b85c4348ea0b31ea63bc75a9f3857869174e2bf17e7aba02945cd218e6f"}, + {file = "pillow-10.4.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:6c762a5b0997f5659a5ef2266abc1d8851ad7749ad9a6a5506eb23d314e4f46b"}, + {file = "pillow-10.4.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a985e028fc183bf12a77a8bbf36318db4238a3ded7fa9df1b9a133f1cb79f8fc"}, + {file = "pillow-10.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:812f7342b0eee081eaec84d91423d1b4650bb9828eb53d8511bcef8ce5aecf1e"}, + {file = "pillow-10.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ac1452d2fbe4978c2eec89fb5a23b8387aba707ac72810d9490118817d9c0b46"}, + {file = "pillow-10.4.0-cp310-cp310-win32.whl", hash = "sha256:bcd5e41a859bf2e84fdc42f4edb7d9aba0a13d29a2abadccafad99de3feff984"}, + {file = "pillow-10.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:ecd85a8d3e79cd7158dec1c9e5808e821feea088e2f69a974db5edf84dc53141"}, + {file = "pillow-10.4.0-cp310-cp310-win_arm64.whl", hash = "sha256:ff337c552345e95702c5fde3158acb0625111017d0e5f24bf3acdb9cc16b90d1"}, + {file = "pillow-10.4.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:0a9ec697746f268507404647e531e92889890a087e03681a3606d9b920fbee3c"}, + {file = "pillow-10.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfe91cb65544a1321e631e696759491ae04a2ea11d36715eca01ce07284738be"}, + {file = "pillow-10.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dc6761a6efc781e6a1544206f22c80c3af4c8cf461206d46a1e6006e4429ff3"}, + {file = "pillow-10.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e84b6cc6a4a3d76c153a6b19270b3526a5a8ed6b09501d3af891daa2a9de7d6"}, + {file = "pillow-10.4.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:bbc527b519bd3aa9d7f429d152fea69f9ad37c95f0b02aebddff592688998abe"}, + {file = "pillow-10.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:76a911dfe51a36041f2e756b00f96ed84677cdeb75d25c767f296c1c1eda1319"}, + {file = "pillow-10.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:59291fb29317122398786c2d44427bbd1a6d7ff54017075b22be9d21aa59bd8d"}, + {file = "pillow-10.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:416d3a5d0e8cfe4f27f574362435bc9bae57f679a7158e0096ad2beb427b8696"}, + {file = "pillow-10.4.0-cp311-cp311-win32.whl", hash = "sha256:7086cc1d5eebb91ad24ded9f58bec6c688e9f0ed7eb3dbbf1e4800280a896496"}, + {file = "pillow-10.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cbed61494057c0f83b83eb3a310f0bf774b09513307c434d4366ed64f4128a91"}, + {file = "pillow-10.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:f5f0c3e969c8f12dd2bb7e0b15d5c468b51e5017e01e2e867335c81903046a22"}, + {file = "pillow-10.4.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:673655af3eadf4df6b5457033f086e90299fdd7a47983a13827acf7459c15d94"}, + {file = "pillow-10.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:866b6942a92f56300012f5fbac71f2d610312ee65e22f1aa2609e491284e5597"}, + {file = "pillow-10.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29dbdc4207642ea6aad70fbde1a9338753d33fb23ed6956e706936706f52dd80"}, + {file = "pillow-10.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf2342ac639c4cf38799a44950bbc2dfcb685f052b9e262f446482afaf4bffca"}, + {file = "pillow-10.4.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:f5b92f4d70791b4a67157321c4e8225d60b119c5cc9aee8ecf153aace4aad4ef"}, + {file = "pillow-10.4.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:86dcb5a1eb778d8b25659d5e4341269e8590ad6b4e8b44d9f4b07f8d136c414a"}, + {file = "pillow-10.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:780c072c2e11c9b2c7ca37f9a2ee8ba66f44367ac3e5c7832afcfe5104fd6d1b"}, + {file = "pillow-10.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:37fb69d905be665f68f28a8bba3c6d3223c8efe1edf14cc4cfa06c241f8c81d9"}, + {file = "pillow-10.4.0-cp312-cp312-win32.whl", hash = "sha256:7dfecdbad5c301d7b5bde160150b4db4c659cee2b69589705b6f8a0c509d9f42"}, + {file = "pillow-10.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:1d846aea995ad352d4bdcc847535bd56e0fd88d36829d2c90be880ef1ee4668a"}, + {file = "pillow-10.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:e553cad5179a66ba15bb18b353a19020e73a7921296a7979c4a2b7f6a5cd57f9"}, + {file = "pillow-10.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8bc1a764ed8c957a2e9cacf97c8b2b053b70307cf2996aafd70e91a082e70df3"}, + {file = "pillow-10.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6209bb41dc692ddfee4942517c19ee81b86c864b626dbfca272ec0f7cff5d9fb"}, + {file = "pillow-10.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bee197b30783295d2eb680b311af15a20a8b24024a19c3a26431ff83eb8d1f70"}, + {file = "pillow-10.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ef61f5dd14c300786318482456481463b9d6b91ebe5ef12f405afbba77ed0be"}, + {file = "pillow-10.4.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:297e388da6e248c98bc4a02e018966af0c5f92dfacf5a5ca22fa01cb3179bca0"}, + {file = "pillow-10.4.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:e4db64794ccdf6cb83a59d73405f63adbe2a1887012e308828596100a0b2f6cc"}, + {file = "pillow-10.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd2880a07482090a3bcb01f4265f1936a903d70bc740bfcb1fd4e8a2ffe5cf5a"}, + {file = "pillow-10.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b35b21b819ac1dbd1233317adeecd63495f6babf21b7b2512d244ff6c6ce309"}, + {file = "pillow-10.4.0-cp313-cp313-win32.whl", hash = "sha256:551d3fd6e9dc15e4c1eb6fc4ba2b39c0c7933fa113b220057a34f4bb3268a060"}, + {file = "pillow-10.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:030abdbe43ee02e0de642aee345efa443740aa4d828bfe8e2eb11922ea6a21ea"}, + {file = "pillow-10.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:5b001114dd152cfd6b23befeb28d7aee43553e2402c9f159807bf55f33af8a8d"}, + {file = "pillow-10.4.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:8d4d5063501b6dd4024b8ac2f04962d661222d120381272deea52e3fc52d3736"}, + {file = "pillow-10.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7c1ee6f42250df403c5f103cbd2768a28fe1a0ea1f0f03fe151c8741e1469c8b"}, + {file = "pillow-10.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b15e02e9bb4c21e39876698abf233c8c579127986f8207200bc8a8f6bb27acf2"}, + {file = "pillow-10.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a8d4bade9952ea9a77d0c3e49cbd8b2890a399422258a77f357b9cc9be8d680"}, + {file = "pillow-10.4.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:43efea75eb06b95d1631cb784aa40156177bf9dd5b4b03ff38979e048258bc6b"}, + {file = "pillow-10.4.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:950be4d8ba92aca4b2bb0741285a46bfae3ca699ef913ec8416c1b78eadd64cd"}, + {file = "pillow-10.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d7480af14364494365e89d6fddc510a13e5a2c3584cb19ef65415ca57252fb84"}, + {file = "pillow-10.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:73664fe514b34c8f02452ffb73b7a92c6774e39a647087f83d67f010eb9a0cf0"}, + {file = "pillow-10.4.0-cp38-cp38-win32.whl", hash = "sha256:e88d5e6ad0d026fba7bdab8c3f225a69f063f116462c49892b0149e21b6c0a0e"}, + {file = "pillow-10.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:5161eef006d335e46895297f642341111945e2c1c899eb406882a6c61a4357ab"}, + {file = "pillow-10.4.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0ae24a547e8b711ccaaf99c9ae3cd975470e1a30caa80a6aaee9a2f19c05701d"}, + {file = "pillow-10.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:298478fe4f77a4408895605f3482b6cc6222c018b2ce565c2b6b9c354ac3229b"}, + {file = "pillow-10.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:134ace6dc392116566980ee7436477d844520a26a4b1bd4053f6f47d096997fd"}, + {file = "pillow-10.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:930044bb7679ab003b14023138b50181899da3f25de50e9dbee23b61b4de2126"}, + {file = "pillow-10.4.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:c76e5786951e72ed3686e122d14c5d7012f16c8303a674d18cdcd6d89557fc5b"}, + {file = "pillow-10.4.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b2724fdb354a868ddf9a880cb84d102da914e99119211ef7ecbdc613b8c96b3c"}, + {file = "pillow-10.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dbc6ae66518ab3c5847659e9988c3b60dc94ffb48ef9168656e0019a93dbf8a1"}, + {file = "pillow-10.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:06b2f7898047ae93fad74467ec3d28fe84f7831370e3c258afa533f81ef7f3df"}, + {file = "pillow-10.4.0-cp39-cp39-win32.whl", hash = "sha256:7970285ab628a3779aecc35823296a7869f889b8329c16ad5a71e4901a3dc4ef"}, + {file = "pillow-10.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:961a7293b2457b405967af9c77dcaa43cc1a8cd50d23c532e62d48ab6cdd56f5"}, + {file = "pillow-10.4.0-cp39-cp39-win_arm64.whl", hash = "sha256:32cda9e3d601a52baccb2856b8ea1fc213c90b340c542dcef77140dfa3278a9e"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5b4815f2e65b30f5fbae9dfffa8636d992d49705723fe86a3661806e069352d4"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:8f0aef4ef59694b12cadee839e2ba6afeab89c0f39a3adc02ed51d109117b8da"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f4727572e2918acaa9077c919cbbeb73bd2b3ebcfe033b72f858fc9fbef0026"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff25afb18123cea58a591ea0244b92eb1e61a1fd497bf6d6384f09bc3262ec3e"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:dc3e2db6ba09ffd7d02ae9141cfa0ae23393ee7687248d46a7507b75d610f4f5"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:02a2be69f9c9b8c1e97cf2713e789d4e398c751ecfd9967c18d0ce304efbf885"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0755ffd4a0c6f267cccbae2e9903d95477ca2f77c4fcf3a3a09570001856c8a5"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:a02364621fe369e06200d4a16558e056fe2805d3468350df3aef21e00d26214b"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:1b5dea9831a90e9d0721ec417a80d4cbd7022093ac38a568db2dd78363b00908"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b885f89040bb8c4a1573566bbb2f44f5c505ef6e74cec7ab9068c900047f04b"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87dd88ded2e6d74d31e1e0a99a726a6765cda32d00ba72dc37f0651f306daaa8"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:2db98790afc70118bd0255c2eeb465e9767ecf1f3c25f9a1abb8ffc8cfd1fe0a"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f7baece4ce06bade126fb84b8af1c33439a76d8a6fd818970215e0560ca28c27"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:cfdd747216947628af7b259d274771d84db2268ca062dd5faf373639d00113a3"}, + {file = "pillow-10.4.0.tar.gz", hash = "sha256:166c1cd4d24309b30d61f79f4a9114b7b2313d7450912277855ff5dfd7cd4a06"}, +] + +[package.extras] +docs = ["furo", "olefile", "sphinx (>=7.3)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"] +fpx = ["olefile"] +mic = ["olefile"] +tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] +typing = ["typing-extensions"] +xmp = ["defusedxml"] + [[package]] name = "pluggy" version = "1.5.0" @@ -736,6 +1247,26 @@ files = [ [package.dependencies] typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" +[[package]] +name = "pydantic-settings" +version = "2.4.0" +description = "Settings management using Pydantic" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic_settings-2.4.0-py3-none-any.whl", hash = "sha256:bb6849dc067f1687574c12a639e231f3a6feeed0a12d710c1382045c5db1c315"}, + {file = "pydantic_settings-2.4.0.tar.gz", hash = "sha256:ed81c3a0f46392b4d7c0a565c05884e6e54b3456e6f0fe4d8814981172dc9a88"}, +] + +[package.dependencies] +pydantic = ">=2.7.0" +python-dotenv = ">=0.21.0" + +[package.extras] +azure-key-vault = ["azure-identity (>=1.16.0)", "azure-keyvault-secrets (>=4.8.0)"] +toml = ["tomli (>=2.0.1)"] +yaml = ["pyyaml (>=6.0.1)"] + [[package]] name = "pygments" version = "2.18.0" @@ -750,6 +1281,20 @@ files = [ [package.extras] windows-terminal = ["colorama (>=0.4.6)"] +[[package]] +name = "pyparsing" +version = "3.1.4" +description = "pyparsing module - Classes and methods to define and execute parsing grammars" +optional = false +python-versions = ">=3.6.8" +files = [ + {file = "pyparsing-3.1.4-py3-none-any.whl", hash = "sha256:a6a7ee4235a3f944aa1fa2249307708f893fe5717dc603503c6c7969c070fb7c"}, + {file = "pyparsing-3.1.4.tar.gz", hash = "sha256:f86ec8d1a83f11977c9a6ea7598e8c27fc5cddfa5b07ea2241edbbde1d7bc032"}, +] + +[package.extras] +diagrams = ["jinja2", "railroad-diagrams"] + [[package]] name = "pytest" version = "8.3.2" @@ -772,6 +1317,20 @@ tomli = {version = ">=1", markers = "python_version < \"3.11\""} [package.extras] dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +description = "Extensions to the standard Python datetime module" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, + {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, +] + +[package.dependencies] +six = ">=1.5" + [[package]] name = "python-dotenv" version = "1.0.1" @@ -1250,4 +1809,4 @@ watchdog = ["watchdog (>=2.3)"] [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "29fd7b78ee691e2c0c3b1de9e4ee0c56b2ba2021494bc7a6b68fb106c8bf6f74" +content-hash = "43b2bdbf4fb9b4d6b723cf346faa29dc17e7c4335280c039f27642ca93329776" diff --git a/pyproject.toml b/pyproject.toml index 0b1f16d..fb89567 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,6 +18,11 @@ httpx = "^0.27.0" coverage = "^7.6.1" flask = "^3.0.3" flask-restful = "^0.3.10" +pydantic-settings = "^2.4.0" +asyncio = "^3.4.3" + +[tool.poetry.group.jupyter.dependencies] +matplotlib = "^3.9.2" [build-system] requires = ["poetry-core"] diff --git a/src/general_python/threading/asyncio_progress/main.py b/src/general_python/threading/asyncio_progress/main.py new file mode 100644 index 0000000..e79c946 --- /dev/null +++ b/src/general_python/threading/asyncio_progress/main.py @@ -0,0 +1,31 @@ +# SuperFastPython.com +# example of showing the progress of tasks using a callback +import random +import asyncio + +# callback function to show the progress of tasks +def progress(task): + # report progress of the task + print('.', end='') + +# coroutine task +async def work(): + # simulate effort + await asyncio.sleep(random.random() * 10) + +# main coroutine +async def main(): + # create and schedule many tasks + tasks = [asyncio.create_task(work()) for _ in range(20)] + # add done callback function to all tasks + for task in tasks: + task.add_done_callback(progress) + # wait for all tasks to complete + _ = await asyncio.wait(tasks) + # report final message + print('\nAll done.') + +# run the asyncio program + +if __name__ == "__main__": + asyncio.run(main()) \ No newline at end of file diff --git a/src/general_python/threading/asyncio_queues/producer_consumer.py b/src/general_python/threading/asyncio_queues/producer_consumer.py new file mode 100644 index 0000000..14d2d6a --- /dev/null +++ b/src/general_python/threading/asyncio_queues/producer_consumer.py @@ -0,0 +1,47 @@ +# SuperFastPython.com +# example of using an asyncio queue +from random import random +import asyncio + +# change this to alter behavior between consumer and producer +MAX_QUEUE_SIZE = 10 + +# coroutine to generate work +async def producer(queue): + print('Producer: Running') + # generate work + for i in range(10): + # generate a value + value = random() + # block to simulate work + # add to the queue + print(f"Producer: put {value} at count: {i}") + await queue.put(value) + # send an all done signal + await queue.put(None) + print('Producer: Done') + +# coroutine to consume work +async def consumer(queue): + print('Consumer: Running') + # consume work + while True: + # get a unit of work + item = await queue.get() + print(f"Consumer: retrieved {item}") + # check for stop signal + if item is None: + break + # report + # all done + print('Consumer: Done') + +# entry point coroutine +async def main(): + # create the shared queue + queue = asyncio.Queue(MAX_QUEUE_SIZE) + # run the producer and consumers + await asyncio.gather(producer(queue), consumer(queue)) + +# start the asyncio program +asyncio.run(main()) \ No newline at end of file diff --git a/src/general_python/threading/asyncio_queues/progress.py b/src/general_python/threading/asyncio_queues/progress.py new file mode 100644 index 0000000..bf5e35b --- /dev/null +++ b/src/general_python/threading/asyncio_queues/progress.py @@ -0,0 +1,53 @@ +import asyncio +import random +import time + + +async def worker(name, queue): + while True: + # Get a "work item" out of the queue. + sleep_for = await queue.get() + + # Sleep for the "sleep_for" seconds. + await asyncio.sleep(sleep_for) + + # Notify the queue that the "work item" has been processed. + queue.task_done() + + print(f'{name} has slept for {sleep_for:.2f} seconds') + + +async def main(): + # Create a queue that we will use to store our "workload". + queue = asyncio.Queue() + + # Generate random timings and put them into the queue. + total_sleep_time = 0 + for _ in range(20): + sleep_for = random.uniform(0.05, 1.0) + total_sleep_time += sleep_for + queue.put_nowait(sleep_for) + + # Create three worker tasks to process the queue concurrently. + tasks = [] + for i in range(3): + task = asyncio.create_task(worker(f'worker-{i}', queue)) + tasks.append(task) + + # Wait until the queue is fully processed. + started_at = time.monotonic() + await queue.join() + total_slept_for = time.monotonic() - started_at + + # Cancel our worker tasks. + for task in tasks: + task.cancel() + # Wait until all worker tasks are cancelled. + await asyncio.gather(*tasks, return_exceptions=True) + + print('====') + print(f'3 workers slept in parallel for {total_slept_for:.2f} seconds') + print(f'total expected sleep time: {total_sleep_time:.2f} seconds') + + +asyncio.run(main()) \ No newline at end of file diff --git a/src/system_design/rest/fastapi_logging/README.md b/src/system_design/rest/fastapi_logging/README.md deleted file mode 100644 index 411276a..0000000 --- a/src/system_design/rest/fastapi_logging/README.md +++ /dev/null @@ -1 +0,0 @@ -https://rajansahu713.medium.com/implement-logging-in-fastapi-applications-8160c2bf753b \ No newline at end of file From c868263aa0717c6b6f6d362ffcda8d9b3297a0a2 Mon Sep 17 00:00:00 2001 From: Mark Barbaric Date: Sun, 1 Sep 2024 19:00:52 +0100 Subject: [PATCH 03/12] mysql - working on some more advanced sqlalchemy work. --- .vscode/launch.json | 21 +- create_users_and_blog_posts_table.sql | 16 ++ docker-compose.yml | 18 ++ poetry.lock | 232 +++++++++++++++++- pyproject.toml | 4 + sql_scripts/populate_database.sql | 14 ++ sql_scripts/select_blog_post_id.sql | 2 + .../rest/flask/flask_routers/README.md | 2 + .../rest/flask/flask_sqlalchemy/README.md | 3 + .../flask/flask_sqlalchemy/app/__init__.py | 25 ++ .../rest/flask/flask_sqlalchemy/app/config.py | 8 + .../flask_sqlalchemy/app/model/blog_posts.py | 17 ++ .../flask_sqlalchemy/app/model/model_class.py | 5 + .../flask/flask_sqlalchemy/app/model/users.py | 13 + .../app/resources/blog_posts.py | 20 ++ .../flask_sqlalchemy/app/resources/users.py | 19 ++ 16 files changed, 415 insertions(+), 4 deletions(-) create mode 100644 create_users_and_blog_posts_table.sql create mode 100644 docker-compose.yml create mode 100644 sql_scripts/populate_database.sql create mode 100644 sql_scripts/select_blog_post_id.sql create mode 100644 src/system_design/rest/flask/flask_routers/README.md create mode 100644 src/system_design/rest/flask/flask_sqlalchemy/README.md create mode 100644 src/system_design/rest/flask/flask_sqlalchemy/app/__init__.py create mode 100644 src/system_design/rest/flask/flask_sqlalchemy/app/config.py create mode 100644 src/system_design/rest/flask/flask_sqlalchemy/app/model/blog_posts.py create mode 100644 src/system_design/rest/flask/flask_sqlalchemy/app/model/model_class.py create mode 100644 src/system_design/rest/flask/flask_sqlalchemy/app/model/users.py create mode 100644 src/system_design/rest/flask/flask_sqlalchemy/app/resources/blog_posts.py create mode 100644 src/system_design/rest/flask/flask_sqlalchemy/app/resources/users.py diff --git a/.vscode/launch.json b/.vscode/launch.json index 5b7fd5c..d4ae7b8 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -4,8 +4,23 @@ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ - - - + { + "name": "Python Debugger: Flask SQL Alchemy", + "type": "debugpy", + "request": "launch", + "module": "flask", + "env": { + "FLASK_APP": "src/system_design/rest/flask/flask_sqlalchemy/app/__init__.py", + "FLASK_DEBUG": "1" + }, + "args": [ + "run", + "--no-reload", + "--port", + "5000" + ], + "jinja": true, + "autoStartBrowser": false + } ] } \ No newline at end of file diff --git a/create_users_and_blog_posts_table.sql b/create_users_and_blog_posts_table.sql new file mode 100644 index 0000000..39ba136 --- /dev/null +++ b/create_users_and_blog_posts_table.sql @@ -0,0 +1,16 @@ +START TRANSACTION; + +CREATE TABLE users( + user_id VARCHAR(36) UNIQUE NOT NULL, + user_name VARCHAR(12) NOT NULL +); + +CREATE TABLE blog_posts( + blog_post_id VARCHAR(36) NOT NULL, + blog_post_name VARCHAR(12), + user_id VARCHAR(36) NOT NULL, + PRIMARY KEY (blog_post_id), + FOREIGN KEY (user_id) REFERENCES users(user_id) +); + +COMMIT; \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..f1519ef --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,18 @@ +services: + mysql: + image: mysql + restart: always + environment: + MYSQL_DATABASE: 'db' + MYSQL_USER: 'user' + MYSQL_PASSWORD: 'password' + MYSQL_ROOT_PASSWORD: example + ports: + - '3306:3306' + expose: + - '3306' + volumes: + - mysql:/var/lib/mysql + +volumes: + mysql: \ No newline at end of file diff --git a/poetry.lock b/poetry.lock index c7f47f5..fb66207 100644 --- a/poetry.lock +++ b/poetry.lock @@ -425,6 +425,21 @@ six = ">=1.3.0" [package.extras] docs = ["sphinx"] +[[package]] +name = "flask-sqlalchemy" +version = "3.1.1" +description = "Add SQLAlchemy support to your Flask application." +optional = false +python-versions = ">=3.8" +files = [ + {file = "flask_sqlalchemy-3.1.1-py3-none-any.whl", hash = "sha256:4ba4be7f419dc72f4efd8802d69974803c37259dd42f3913b0dcf75c9447e0a0"}, + {file = "flask_sqlalchemy-3.1.1.tar.gz", hash = "sha256:e4b68bb881802dda1a7d878b2fc84c06d1ee57fb40b874d3dc97dabfa36b8312"}, +] + +[package.dependencies] +flask = ">=2.2.5" +sqlalchemy = ">=2.0.16" + [[package]] name = "fonttools" version = "4.53.1" @@ -490,6 +505,77 @@ ufo = ["fs (>=2.2.0,<3)"] unicode = ["unicodedata2 (>=15.1.0)"] woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"] +[[package]] +name = "greenlet" +version = "3.0.3" +description = "Lightweight in-process concurrent programming" +optional = false +python-versions = ">=3.7" +files = [ + {file = "greenlet-3.0.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:9da2bd29ed9e4f15955dd1595ad7bc9320308a3b766ef7f837e23ad4b4aac31a"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d353cadd6083fdb056bb46ed07e4340b0869c305c8ca54ef9da3421acbdf6881"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dca1e2f3ca00b84a396bc1bce13dd21f680f035314d2379c4160c98153b2059b"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ed7fb269f15dc662787f4119ec300ad0702fa1b19d2135a37c2c4de6fadfd4a"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd4f49ae60e10adbc94b45c0b5e6a179acc1736cf7a90160b404076ee283cf83"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:73a411ef564e0e097dbe7e866bb2dda0f027e072b04da387282b02c308807405"}, + {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7f362975f2d179f9e26928c5b517524e89dd48530a0202570d55ad6ca5d8a56f"}, + {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:649dde7de1a5eceb258f9cb00bdf50e978c9db1b996964cd80703614c86495eb"}, + {file = "greenlet-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:68834da854554926fbedd38c76e60c4a2e3198c6fbed520b106a8986445caaf9"}, + {file = "greenlet-3.0.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:b1b5667cced97081bf57b8fa1d6bfca67814b0afd38208d52538316e9422fc61"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52f59dd9c96ad2fc0d5724107444f76eb20aaccb675bf825df6435acb7703559"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:afaff6cf5200befd5cec055b07d1c0a5a06c040fe5ad148abcd11ba6ab9b114e"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe754d231288e1e64323cfad462fcee8f0288654c10bdf4f603a39ed923bef33"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2797aa5aedac23af156bbb5a6aa2cd3427ada2972c828244eb7d1b9255846379"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7f009caad047246ed379e1c4dbcb8b020f0a390667ea74d2387be2998f58a22"}, + {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c5e1536de2aad7bf62e27baf79225d0d64360d4168cf2e6becb91baf1ed074f3"}, + {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:894393ce10ceac937e56ec00bb71c4c2f8209ad516e96033e4b3b1de270e200d"}, + {file = "greenlet-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:1ea188d4f49089fc6fb283845ab18a2518d279c7cd9da1065d7a84e991748728"}, + {file = "greenlet-3.0.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:70fb482fdf2c707765ab5f0b6655e9cfcf3780d8d87355a063547b41177599be"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4d1ac74f5c0c0524e4a24335350edad7e5f03b9532da7ea4d3c54d527784f2e"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:149e94a2dd82d19838fe4b2259f1b6b9957d5ba1b25640d2380bea9c5df37676"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15d79dd26056573940fcb8c7413d84118086f2ec1a8acdfa854631084393efcc"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b7db1ebff4ba09aaaeae6aa491daeb226c8150fc20e836ad00041bcb11230"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fcd2469d6a2cf298f198f0487e0a5b1a47a42ca0fa4dfd1b6862c999f018ebbf"}, + {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1f672519db1796ca0d8753f9e78ec02355e862d0998193038c7073045899f305"}, + {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2516a9957eed41dd8f1ec0c604f1cdc86758b587d964668b5b196a9db5bfcde6"}, + {file = "greenlet-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:bba5387a6975598857d86de9eac14210a49d554a77eb8261cc68b7d082f78ce2"}, + {file = "greenlet-3.0.3-cp37-cp37m-macosx_11_0_universal2.whl", hash = "sha256:5b51e85cb5ceda94e79d019ed36b35386e8c37d22f07d6a751cb659b180d5274"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:daf3cb43b7cf2ba96d614252ce1684c1bccee6b2183a01328c98d36fcd7d5cb0"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99bf650dc5d69546e076f413a87481ee1d2d09aaaaaca058c9251b6d8c14783f"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dd6e660effd852586b6a8478a1d244b8dc90ab5b1321751d2ea15deb49ed414"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3391d1e16e2a5a1507d83e4a8b100f4ee626e8eca43cf2cadb543de69827c4c"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1f145462f1fa6e4a4ae3c0f782e580ce44d57c8f2c7aae1b6fa88c0b2efdb41"}, + {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1a7191e42732df52cb5f39d3527217e7ab73cae2cb3694d241e18f53d84ea9a7"}, + {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0448abc479fab28b00cb472d278828b3ccca164531daab4e970a0458786055d6"}, + {file = "greenlet-3.0.3-cp37-cp37m-win32.whl", hash = "sha256:b542be2440edc2d48547b5923c408cbe0fc94afb9f18741faa6ae970dbcb9b6d"}, + {file = "greenlet-3.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:01bc7ea167cf943b4c802068e178bbf70ae2e8c080467070d01bfa02f337ee67"}, + {file = "greenlet-3.0.3-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:1996cb9306c8595335bb157d133daf5cf9f693ef413e7673cb07e3e5871379ca"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ddc0f794e6ad661e321caa8d2f0a55ce01213c74722587256fb6566049a8b04"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9db1c18f0eaad2f804728c67d6c610778456e3e1cc4ab4bbd5eeb8e6053c6fc"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7170375bcc99f1a2fbd9c306f5be8764eaf3ac6b5cb968862cad4c7057756506"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b66c9c1e7ccabad3a7d037b2bcb740122a7b17a53734b7d72a344ce39882a1b"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:098d86f528c855ead3479afe84b49242e174ed262456c342d70fc7f972bc13c4"}, + {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:81bb9c6d52e8321f09c3d165b2a78c680506d9af285bfccbad9fb7ad5a5da3e5"}, + {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fd096eb7ffef17c456cfa587523c5f92321ae02427ff955bebe9e3c63bc9f0da"}, + {file = "greenlet-3.0.3-cp38-cp38-win32.whl", hash = "sha256:d46677c85c5ba00a9cb6f7a00b2bfa6f812192d2c9f7d9c4f6a55b60216712f3"}, + {file = "greenlet-3.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:419b386f84949bf0e7c73e6032e3457b82a787c1ab4a0e43732898a761cc9dbf"}, + {file = "greenlet-3.0.3-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:da70d4d51c8b306bb7a031d5cff6cc25ad253affe89b70352af5f1cb68e74b53"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086152f8fbc5955df88382e8a75984e2bb1c892ad2e3c80a2508954e52295257"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d73a9fe764d77f87f8ec26a0c85144d6a951a6c438dfe50487df5595c6373eac"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7dcbe92cc99f08c8dd11f930de4d99ef756c3591a5377d1d9cd7dd5e896da71"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1551a8195c0d4a68fac7a4325efac0d541b48def35feb49d803674ac32582f61"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:64d7675ad83578e3fc149b617a444fab8efdafc9385471f868eb5ff83e446b8b"}, + {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b37eef18ea55f2ffd8f00ff8fe7c8d3818abd3e25fb73fae2ca3b672e333a7a6"}, + {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:77457465d89b8263bca14759d7c1684df840b6811b2499838cc5b040a8b5b113"}, + {file = "greenlet-3.0.3-cp39-cp39-win32.whl", hash = "sha256:57e8974f23e47dac22b83436bdcf23080ade568ce77df33159e019d161ce1d1e"}, + {file = "greenlet-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:c5ee858cfe08f34712f548c3c363e807e7186f03ad7a5039ebadb29e8c6be067"}, + {file = "greenlet-3.0.3.tar.gz", hash = "sha256:43374442353259554ce33599da8b692d5aa96f8976d567d4badf263371fbe491"}, +] + +[package.extras] +docs = ["Sphinx", "furo"] +test = ["objgraph", "psutil"] + [[package]] name = "h11" version = "0.14.0" @@ -924,6 +1010,63 @@ files = [ {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, ] +[[package]] +name = "mysql-connector-python" +version = "9.0.0" +description = "MySQL driver written in Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "mysql-connector-python-9.0.0.tar.gz", hash = "sha256:8a404db37864acca43fd76222d1fbc7ff8d17d4ce02d803289c2141c2693ce9e"}, + {file = "mysql_connector_python-9.0.0-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:72bfd0213364c2bea0244f6432ababb2f204cff43f4f886c65dca2be11f536ee"}, + {file = "mysql_connector_python-9.0.0-cp310-cp310-macosx_13_0_x86_64.whl", hash = "sha256:052058cf3dc0bf183ab522132f3b18a614a26f3e392ae886efcdab38d4f4fc42"}, + {file = "mysql_connector_python-9.0.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:f41cb8da8bb487ed60329ac31789c50621f0e6d2c26abc7d4ae2383838fb1b93"}, + {file = "mysql_connector_python-9.0.0-cp310-cp310-manylinux_2_17_x86_64.whl", hash = "sha256:67fc2b2e67a63963c633fc884f285a8de5a626967a3cc5f5d48ac3e8d15b122d"}, + {file = "mysql_connector_python-9.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:933c3e39d30cc6f9ff636d27d18aa3f1341b23d803ade4b57a76f91c26d14066"}, + {file = "mysql_connector_python-9.0.0-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:7af7f68198f2aca3a520e1201fe2b329331e0ca19a481f3b3451cb0746f56c01"}, + {file = "mysql_connector_python-9.0.0-cp311-cp311-macosx_13_0_x86_64.whl", hash = "sha256:38c229d76cd1dea8465357855f2b2842b7a9b201f17dea13b0eab7d3b9d6ad74"}, + {file = "mysql_connector_python-9.0.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:c01aad36f0c34ca3f642018be37fd0d55c546f088837cba88f1a1aff408c63dd"}, + {file = "mysql_connector_python-9.0.0-cp311-cp311-manylinux_2_17_x86_64.whl", hash = "sha256:853c5916d188ef2c357a474e15ac81cafae6085e599ceb9b2b0bcb9104118e63"}, + {file = "mysql_connector_python-9.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:134b71e439e2eafaee4c550365221ae2890dd54fb76227c64a87a94a07fe79b4"}, + {file = "mysql_connector_python-9.0.0-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:9199d6ecc81576602990178f0c2fb71737c53a598c8a2f51e1097a53fcfaee40"}, + {file = "mysql_connector_python-9.0.0-cp312-cp312-macosx_13_0_x86_64.whl", hash = "sha256:b267a6c000b7f98e6436a9acefa5582a9662e503b0632a2562e3093a677f6845"}, + {file = "mysql_connector_python-9.0.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:ac92b2f2a9307ac0c4aafdfcf7ecf01ec92dfebd9140f8c95353adfbf5822cd4"}, + {file = "mysql_connector_python-9.0.0-cp312-cp312-manylinux_2_17_x86_64.whl", hash = "sha256:ced1fa55e653d28f66c4f3569ed524d4d92098119dcd80c2fa026872a30eba55"}, + {file = "mysql_connector_python-9.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:ca8349fe56ce39498d9b5ca8eabba744774e94d85775259f26a43a03e8825429"}, + {file = "mysql_connector_python-9.0.0-cp38-cp38-macosx_13_0_x86_64.whl", hash = "sha256:a48534b881c176557ddc78527c8c75b4c9402511e972670ad33c5e49d31eddfe"}, + {file = "mysql_connector_python-9.0.0-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:e90a7b96ce2c6a60f6e2609b0c83f45bd55e144cc7c2a9714e344938827da363"}, + {file = "mysql_connector_python-9.0.0-cp38-cp38-manylinux_2_17_x86_64.whl", hash = "sha256:2a8f451c4d700802fdfe515890c14974766c322213df2ceed3b27752929dc70f"}, + {file = "mysql_connector_python-9.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:2dcf05355315e5c7c81e9eca34395d78f29c4da3662e869e42dd7b16380f92ce"}, + {file = "mysql_connector_python-9.0.0-cp39-cp39-macosx_13_0_arm64.whl", hash = "sha256:823190e7f2a9b4bcc574ab6bb72a33802933e1a8c171594faad90162d2d27758"}, + {file = "mysql_connector_python-9.0.0-cp39-cp39-macosx_13_0_x86_64.whl", hash = "sha256:b8639d8aa381a7d19b92ca1a32448f09baaf80787e50187d1f7d072191430768"}, + {file = "mysql_connector_python-9.0.0-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:a688ea65b2ea771b9b69dc409377240a7cab7c1aafef46cd75219d5a94ba49e0"}, + {file = "mysql_connector_python-9.0.0-cp39-cp39-manylinux_2_17_x86_64.whl", hash = "sha256:6d92c58f71c691f86ad35bb2f3e13d7a9cc1c84ce0b04c146e5980e450faeff1"}, + {file = "mysql_connector_python-9.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:eacc353dcf6f39665d4ca3311ded5ddae0f5a117f03107991d4185ffa59fd890"}, + {file = "mysql_connector_python-9.0.0-py2.py3-none-any.whl", hash = "sha256:016d81bb1499dee8b77c82464244e98f10d3671ceefb4023adc559267d1fad50"}, +] + +[package.extras] +dns-srv = ["dnspython (==2.6.1)"] +fido2 = ["fido2 (==1.1.2)"] +gssapi = ["gssapi (>=1.6.9,<=1.8.2)"] +telemetry = ["opentelemetry-api (==1.18.0)", "opentelemetry-exporter-otlp-proto-http (==1.18.0)", "opentelemetry-sdk (==1.18.0)"] + +[[package]] +name = "mysqlclient" +version = "2.2.0" +description = "Python interface to MySQL" +optional = false +python-versions = ">=3.8" +files = [ + {file = "mysqlclient-2.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:68837b6bb23170acffb43ae411e47533a560b6360c06dac39aa55700972c93b2"}, + {file = "mysqlclient-2.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:5670679ff1be1cc3fef0fa81bf39f0cd70605ba121141050f02743eb878ac114"}, + {file = "mysqlclient-2.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:004fe1d30d2c2ff8072f8ea513bcec235fd9b896f70dad369461d0ad7e570e98"}, + {file = "mysqlclient-2.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:9c6b142836c7dba4f723bf9c93cc46b6e5081d65b2af807f400dda9eb85a16d0"}, + {file = "mysqlclient-2.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:955dba905a7443ce4788c63fdb9f8d688316260cf60b20ff51ac3b1c77616ede"}, + {file = "mysqlclient-2.2.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:530ece9995a36cadb6211b9787f0c9e05cdab6702549bdb4236af5e9b535ed6a"}, + {file = "mysqlclient-2.2.0.tar.gz", hash = "sha256:04368445f9c487d8abb7a878e3d23e923e6072c04a6c320f9e0dc8a82efba14e"}, +] + [[package]] name = "numpy" version = "2.1.0" @@ -1483,6 +1626,93 @@ files = [ {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, ] +[[package]] +name = "sqlalchemy" +version = "2.0.32" +description = "Database Abstraction Library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "SQLAlchemy-2.0.32-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0c9045ecc2e4db59bfc97b20516dfdf8e41d910ac6fb667ebd3a79ea54084619"}, + {file = "SQLAlchemy-2.0.32-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1467940318e4a860afd546ef61fefb98a14d935cd6817ed07a228c7f7c62f389"}, + {file = "SQLAlchemy-2.0.32-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5954463675cb15db8d4b521f3566a017c8789222b8316b1e6934c811018ee08b"}, + {file = "SQLAlchemy-2.0.32-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:167e7497035c303ae50651b351c28dc22a40bb98fbdb8468cdc971821b1ae533"}, + {file = "SQLAlchemy-2.0.32-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b27dfb676ac02529fb6e343b3a482303f16e6bc3a4d868b73935b8792edb52d0"}, + {file = "SQLAlchemy-2.0.32-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bf2360a5e0f7bd75fa80431bf8ebcfb920c9f885e7956c7efde89031695cafb8"}, + {file = "SQLAlchemy-2.0.32-cp310-cp310-win32.whl", hash = "sha256:306fe44e754a91cd9d600a6b070c1f2fadbb4a1a257b8781ccf33c7067fd3e4d"}, + {file = "SQLAlchemy-2.0.32-cp310-cp310-win_amd64.whl", hash = "sha256:99db65e6f3ab42e06c318f15c98f59a436f1c78179e6a6f40f529c8cc7100b22"}, + {file = "SQLAlchemy-2.0.32-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:21b053be28a8a414f2ddd401f1be8361e41032d2ef5884b2f31d31cb723e559f"}, + {file = "SQLAlchemy-2.0.32-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b178e875a7a25b5938b53b006598ee7645172fccafe1c291a706e93f48499ff5"}, + {file = "SQLAlchemy-2.0.32-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:723a40ee2cc7ea653645bd4cf024326dea2076673fc9d3d33f20f6c81db83e1d"}, + {file = "SQLAlchemy-2.0.32-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:295ff8689544f7ee7e819529633d058bd458c1fd7f7e3eebd0f9268ebc56c2a0"}, + {file = "SQLAlchemy-2.0.32-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:49496b68cd190a147118af585173ee624114dfb2e0297558c460ad7495f9dfe2"}, + {file = "SQLAlchemy-2.0.32-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:acd9b73c5c15f0ec5ce18128b1fe9157ddd0044abc373e6ecd5ba376a7e5d961"}, + {file = "SQLAlchemy-2.0.32-cp311-cp311-win32.whl", hash = "sha256:9365a3da32dabd3e69e06b972b1ffb0c89668994c7e8e75ce21d3e5e69ddef28"}, + {file = "SQLAlchemy-2.0.32-cp311-cp311-win_amd64.whl", hash = "sha256:8bd63d051f4f313b102a2af1cbc8b80f061bf78f3d5bd0843ff70b5859e27924"}, + {file = "SQLAlchemy-2.0.32-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6bab3db192a0c35e3c9d1560eb8332463e29e5507dbd822e29a0a3c48c0a8d92"}, + {file = "SQLAlchemy-2.0.32-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:19d98f4f58b13900d8dec4ed09dd09ef292208ee44cc9c2fe01c1f0a2fe440e9"}, + {file = "SQLAlchemy-2.0.32-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cd33c61513cb1b7371fd40cf221256456d26a56284e7d19d1f0b9f1eb7dd7e8"}, + {file = "SQLAlchemy-2.0.32-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d6ba0497c1d066dd004e0f02a92426ca2df20fac08728d03f67f6960271feec"}, + {file = "SQLAlchemy-2.0.32-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2b6be53e4fde0065524f1a0a7929b10e9280987b320716c1509478b712a7688c"}, + {file = "SQLAlchemy-2.0.32-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:916a798f62f410c0b80b63683c8061f5ebe237b0f4ad778739304253353bc1cb"}, + {file = "SQLAlchemy-2.0.32-cp312-cp312-win32.whl", hash = "sha256:31983018b74908ebc6c996a16ad3690301a23befb643093fcfe85efd292e384d"}, + {file = "SQLAlchemy-2.0.32-cp312-cp312-win_amd64.whl", hash = "sha256:4363ed245a6231f2e2957cccdda3c776265a75851f4753c60f3004b90e69bfeb"}, + {file = "SQLAlchemy-2.0.32-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b8afd5b26570bf41c35c0121801479958b4446751a3971fb9a480c1afd85558e"}, + {file = "SQLAlchemy-2.0.32-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c750987fc876813f27b60d619b987b057eb4896b81117f73bb8d9918c14f1cad"}, + {file = "SQLAlchemy-2.0.32-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ada0102afff4890f651ed91120c1120065663506b760da4e7823913ebd3258be"}, + {file = "SQLAlchemy-2.0.32-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:78c03d0f8a5ab4f3034c0e8482cfcc415a3ec6193491cfa1c643ed707d476f16"}, + {file = "SQLAlchemy-2.0.32-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:3bd1cae7519283ff525e64645ebd7a3e0283f3c038f461ecc1c7b040a0c932a1"}, + {file = "SQLAlchemy-2.0.32-cp37-cp37m-win32.whl", hash = "sha256:01438ebcdc566d58c93af0171c74ec28efe6a29184b773e378a385e6215389da"}, + {file = "SQLAlchemy-2.0.32-cp37-cp37m-win_amd64.whl", hash = "sha256:4979dc80fbbc9d2ef569e71e0896990bc94df2b9fdbd878290bd129b65ab579c"}, + {file = "SQLAlchemy-2.0.32-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c742be912f57586ac43af38b3848f7688863a403dfb220193a882ea60e1ec3a"}, + {file = "SQLAlchemy-2.0.32-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:62e23d0ac103bcf1c5555b6c88c114089587bc64d048fef5bbdb58dfd26f96da"}, + {file = "SQLAlchemy-2.0.32-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:251f0d1108aab8ea7b9aadbd07fb47fb8e3a5838dde34aa95a3349876b5a1f1d"}, + {file = "SQLAlchemy-2.0.32-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ef18a84e5116340e38eca3e7f9eeaaef62738891422e7c2a0b80feab165905f"}, + {file = "SQLAlchemy-2.0.32-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:3eb6a97a1d39976f360b10ff208c73afb6a4de86dd2a6212ddf65c4a6a2347d5"}, + {file = "SQLAlchemy-2.0.32-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0c1c9b673d21477cec17ab10bc4decb1322843ba35b481585facd88203754fc5"}, + {file = "SQLAlchemy-2.0.32-cp38-cp38-win32.whl", hash = "sha256:c41a2b9ca80ee555decc605bd3c4520cc6fef9abde8fd66b1cf65126a6922d65"}, + {file = "SQLAlchemy-2.0.32-cp38-cp38-win_amd64.whl", hash = "sha256:8a37e4d265033c897892279e8adf505c8b6b4075f2b40d77afb31f7185cd6ecd"}, + {file = "SQLAlchemy-2.0.32-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:52fec964fba2ef46476312a03ec8c425956b05c20220a1a03703537824b5e8e1"}, + {file = "SQLAlchemy-2.0.32-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:328429aecaba2aee3d71e11f2477c14eec5990fb6d0e884107935f7fb6001632"}, + {file = "SQLAlchemy-2.0.32-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85a01b5599e790e76ac3fe3aa2f26e1feba56270023d6afd5550ed63c68552b3"}, + {file = "SQLAlchemy-2.0.32-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aaf04784797dcdf4c0aa952c8d234fa01974c4729db55c45732520ce12dd95b4"}, + {file = "SQLAlchemy-2.0.32-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4488120becf9b71b3ac718f4138269a6be99a42fe023ec457896ba4f80749525"}, + {file = "SQLAlchemy-2.0.32-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:14e09e083a5796d513918a66f3d6aedbc131e39e80875afe81d98a03312889e6"}, + {file = "SQLAlchemy-2.0.32-cp39-cp39-win32.whl", hash = "sha256:0d322cc9c9b2154ba7e82f7bf25ecc7c36fbe2d82e2933b3642fc095a52cfc78"}, + {file = "SQLAlchemy-2.0.32-cp39-cp39-win_amd64.whl", hash = "sha256:7dd8583df2f98dea28b5cd53a1beac963f4f9d087888d75f22fcc93a07cf8d84"}, + {file = "SQLAlchemy-2.0.32-py3-none-any.whl", hash = "sha256:e567a8793a692451f706b363ccf3c45e056b67d90ead58c3bc9471af5d212202"}, + {file = "SQLAlchemy-2.0.32.tar.gz", hash = "sha256:c1b88cc8b02b6a5f0efb0345a03672d4c897dc7d92585176f88c67346f565ea8"}, +] + +[package.dependencies] +greenlet = {version = "!=0.4.17", markers = "python_version < \"3.13\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"} +typing-extensions = ">=4.6.0" + +[package.extras] +aiomysql = ["aiomysql (>=0.2.0)", "greenlet (!=0.4.17)"] +aioodbc = ["aioodbc", "greenlet (!=0.4.17)"] +aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing_extensions (!=3.10.0.1)"] +asyncio = ["greenlet (!=0.4.17)"] +asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (!=0.4.17)"] +mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5)"] +mssql = ["pyodbc"] +mssql-pymssql = ["pymssql"] +mssql-pyodbc = ["pyodbc"] +mypy = ["mypy (>=0.910)"] +mysql = ["mysqlclient (>=1.4.0)"] +mysql-connector = ["mysql-connector-python"] +oracle = ["cx_oracle (>=8)"] +oracle-oracledb = ["oracledb (>=1.0.1)"] +postgresql = ["psycopg2 (>=2.7)"] +postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] +postgresql-pg8000 = ["pg8000 (>=1.29.1)"] +postgresql-psycopg = ["psycopg (>=3.0.7)"] +postgresql-psycopg2binary = ["psycopg2-binary"] +postgresql-psycopg2cffi = ["psycopg2cffi"] +postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"] +pymysql = ["pymysql"] +sqlcipher = ["sqlcipher3_binary"] + [[package]] name = "starlette" version = "0.37.2" @@ -1809,4 +2039,4 @@ watchdog = ["watchdog (>=2.3)"] [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "43b2bdbf4fb9b4d6b723cf346faa29dc17e7c4335280c039f27642ca93329776" +content-hash = "b3620e6ac7bb17b104b55d56b169fbe345c6860c06e7ae1f39073e579c609047" diff --git a/pyproject.toml b/pyproject.toml index fb89567..2ac29e6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,6 +20,10 @@ flask = "^3.0.3" flask-restful = "^0.3.10" pydantic-settings = "^2.4.0" asyncio = "^3.4.3" +flask-sqlalchemy = "^3.1.1" +mysql-connector-python = "^9.0.0" +sqlalchemy = "^2.0.32" +mysqlclient = "2.2.0" [tool.poetry.group.jupyter.dependencies] matplotlib = "^3.9.2" diff --git a/sql_scripts/populate_database.sql b/sql_scripts/populate_database.sql new file mode 100644 index 0000000..93ae8be --- /dev/null +++ b/sql_scripts/populate_database.sql @@ -0,0 +1,14 @@ +START TRANSACTION; + +SET @uuid1 = uuid(); +SET @uuid2 = uuid(); + +INSERT INTO users(user_id, user_name) +VALUES (@uuid1, 'mark'), +(@uuid2, 'john'); + +INSERT INTO blog_posts(blog_post_id, blog_post_name, user_id) +VALUES(uuid(), 'Post 1', @uuid1), +(uuid(), 'Post 2', @uuid1); + +COMMIT; \ No newline at end of file diff --git a/sql_scripts/select_blog_post_id.sql b/sql_scripts/select_blog_post_id.sql new file mode 100644 index 0000000..14b43ed --- /dev/null +++ b/sql_scripts/select_blog_post_id.sql @@ -0,0 +1,2 @@ +SELECT * FROM blog_posts.blog_post_id, blog_posts.blog_post_name, users.user_name +INNER JOIN users ON users.user_id = blog_posts.user_id \ No newline at end of file diff --git a/src/system_design/rest/flask/flask_routers/README.md b/src/system_design/rest/flask/flask_routers/README.md new file mode 100644 index 0000000..4f15f9f --- /dev/null +++ b/src/system_design/rest/flask/flask_routers/README.md @@ -0,0 +1,2 @@ +https://agallagher.net/coding/flask_sqlalchemy_pytest/ +https://testdriven.io/blog/flask-pytest/ \ No newline at end of file diff --git a/src/system_design/rest/flask/flask_sqlalchemy/README.md b/src/system_design/rest/flask/flask_sqlalchemy/README.md new file mode 100644 index 0000000..e0ccd67 --- /dev/null +++ b/src/system_design/rest/flask/flask_sqlalchemy/README.md @@ -0,0 +1,3 @@ +https://www.j-labs.pl/blog-technologiczny/flask-restful-with-sqlalchemy/ +https://realpython.com/flask-connexion-rest-api-part-2/ +https://medium.com/@choihalim/python-building-restful-apis-with-flask-and-sqlalchemy-4bd0997bae3a \ No newline at end of file diff --git a/src/system_design/rest/flask/flask_sqlalchemy/app/__init__.py b/src/system_design/rest/flask/flask_sqlalchemy/app/__init__.py new file mode 100644 index 0000000..7d9d3cc --- /dev/null +++ b/src/system_design/rest/flask/flask_sqlalchemy/app/__init__.py @@ -0,0 +1,25 @@ +from .config import Config +from flask import Flask +from flask_restful import Api +from flask_sqlalchemy import SQLAlchemy +from app.resources.blog_posts import BlogPostListResource, BlogPostResource +from app.resources.users import UserListResource +from app.model.model_class import Base + + +def create_app(config_class=Config): + app = Flask(__name__) + app.config.from_object(config_class) + api = Api(app) + db = SQLAlchemy(app, + model_class=Base) + api.add_resource(BlogPostListResource, + '/posts', + resource_class_kwargs={'db': db}) + api.add_resource(BlogPostResource, + '/posts/', + resource_class_kwargs={'db': db}) + api.add_resource(UserListResource, + '/users', + resource_class_kwargs={'db': db}) + return app diff --git a/src/system_design/rest/flask/flask_sqlalchemy/app/config.py b/src/system_design/rest/flask/flask_sqlalchemy/app/config.py new file mode 100644 index 0000000..dbdc861 --- /dev/null +++ b/src/system_design/rest/flask/flask_sqlalchemy/app/config.py @@ -0,0 +1,8 @@ +import os + + +class Config: + API_VERSION = 'v1' + HOST = '0.0.0.0' + PORT = os.environ.get("FLASK_PORT", 5000) + SQLALCHEMY_DATABASE_URI = "mysql+mysqlconnector://user:password@127.0.0.1:3306/db" diff --git a/src/system_design/rest/flask/flask_sqlalchemy/app/model/blog_posts.py b/src/system_design/rest/flask/flask_sqlalchemy/app/model/blog_posts.py new file mode 100644 index 0000000..9a84e62 --- /dev/null +++ b/src/system_design/rest/flask/flask_sqlalchemy/app/model/blog_posts.py @@ -0,0 +1,17 @@ +from sqlalchemy import Column, UUID, String, ForeignKey +from sqlalchemy.orm import mapped_column +from .model_class import Base as ModelBase + + +class BlogPosts(ModelBase): + __tablename__ = "blog_posts" + blog_post_id = Column(UUID, primary_key=True) + blog_post_name = Column(String, unique=True) + user_id = mapped_column(ForeignKey("users.user_id")) + + def to_dict(self): + return { + 'blog_post_id': self.blog_post_id, + 'blog_post_name': self.blog_post_name, + 'user_id': self.user_id + } \ No newline at end of file diff --git a/src/system_design/rest/flask/flask_sqlalchemy/app/model/model_class.py b/src/system_design/rest/flask/flask_sqlalchemy/app/model/model_class.py new file mode 100644 index 0000000..710b933 --- /dev/null +++ b/src/system_design/rest/flask/flask_sqlalchemy/app/model/model_class.py @@ -0,0 +1,5 @@ +from sqlalchemy.orm import DeclarativeBase + + +class Base(DeclarativeBase): + pass diff --git a/src/system_design/rest/flask/flask_sqlalchemy/app/model/users.py b/src/system_design/rest/flask/flask_sqlalchemy/app/model/users.py new file mode 100644 index 0000000..cb160d2 --- /dev/null +++ b/src/system_design/rest/flask/flask_sqlalchemy/app/model/users.py @@ -0,0 +1,13 @@ +from sqlalchemy import UUID, Column, String +from .model_class import Base as ModelBase + +class Users(ModelBase): + __tablename__ = "users" + user_id = Column(UUID, primary_key=True) + user_name = Column(String, unique=True) + + def to_dict(self): + return { + 'user_id' : str(self.user_id), + 'user_name' : self.user_name + } \ No newline at end of file diff --git a/src/system_design/rest/flask/flask_sqlalchemy/app/resources/blog_posts.py b/src/system_design/rest/flask/flask_sqlalchemy/app/resources/blog_posts.py new file mode 100644 index 0000000..6150f61 --- /dev/null +++ b/src/system_design/rest/flask/flask_sqlalchemy/app/resources/blog_posts.py @@ -0,0 +1,20 @@ +from flask_restful import Resource +from ..model.blog_posts import BlogPosts +from ..model.users import Users + + +class BlogPostListResource(Resource): + + def __init__(self, **kwargs): + self._db_instance = kwargs['db'] + + def get(self): + session = self._db_instance.session + blog_posts = (session.query(BlogPosts)).all() + + return [blog_post.to_dict() for blog_post in blog_posts] + + +class BlogPostResource(Resource): + def get(self, blog_post_id): + return self._db_session.query.filter_by(blog_post_id=blog_post_id) diff --git a/src/system_design/rest/flask/flask_sqlalchemy/app/resources/users.py b/src/system_design/rest/flask/flask_sqlalchemy/app/resources/users.py new file mode 100644 index 0000000..e745fd8 --- /dev/null +++ b/src/system_design/rest/flask/flask_sqlalchemy/app/resources/users.py @@ -0,0 +1,19 @@ +from flask_restful import Resource +from ..model.users import Users + + +class UserListResource(Resource): + def __init__(self, **kwargs): + self._db_session = kwargs['db'] + + def get(self): + users = self._db_session.session.query(Users).all() + return [user.to_dict() for user in users] + + +class UserResource(Resource): + def __init__(self, **kwargs): + self._db_instance = kwargs['db'] + + def get(self, user_id): + user = self._db_instance.session.query(Users).get(user_id) \ No newline at end of file From c9855cfb58561aa195ac52fce86526bfd5b7db71 Mon Sep 17 00:00:00 2001 From: Mark Barbaric Date: Wed, 4 Sep 2024 08:37:41 +0100 Subject: [PATCH 04/12] mysql - working on inner and left join statements. --- .../rest/flask/flask_sqlalchemy/README.md | 5 ++- .../flask/flask_sqlalchemy/app/__init__.py | 31 ++++++++++--- .../rest/flask/flask_sqlalchemy/app/config.py | 6 +++ .../flask/flask_sqlalchemy/app/extensions.py | 5 +++ .../flask_sqlalchemy/app/model/__init__.py | 0 .../flask_sqlalchemy/app/model/blog_posts.py | 11 +++-- .../app/resources/__init__.py | 0 .../app/resources/blog_posts.py | 9 ++-- .../flask_sqlalchemy/app/resources/users.py | 6 ++- .../rest/flask/flask_sql_alchemy/conftest.py | 43 +++++++++++++++++++ .../flask_sql_alchemy/test_users_router.py | 4 ++ 11 files changed, 103 insertions(+), 17 deletions(-) create mode 100644 src/system_design/rest/flask/flask_sqlalchemy/app/extensions.py create mode 100644 src/system_design/rest/flask/flask_sqlalchemy/app/model/__init__.py create mode 100644 src/system_design/rest/flask/flask_sqlalchemy/app/resources/__init__.py create mode 100644 tests/system_design/rest/flask/flask_sql_alchemy/conftest.py create mode 100644 tests/system_design/rest/flask/flask_sql_alchemy/test_users_router.py diff --git a/src/system_design/rest/flask/flask_sqlalchemy/README.md b/src/system_design/rest/flask/flask_sqlalchemy/README.md index e0ccd67..99a0879 100644 --- a/src/system_design/rest/flask/flask_sqlalchemy/README.md +++ b/src/system_design/rest/flask/flask_sqlalchemy/README.md @@ -1,3 +1,6 @@ https://www.j-labs.pl/blog-technologiczny/flask-restful-with-sqlalchemy/ https://realpython.com/flask-connexion-rest-api-part-2/ -https://medium.com/@choihalim/python-building-restful-apis-with-flask-and-sqlalchemy-4bd0997bae3a \ No newline at end of file +https://medium.com/@choihalim/python-building-restful-apis-with-flask-and-sqlalchemy-4bd0997bae3a + + +https://chatgpt.com/c/65c6b392-06df-47d8-b5a6-10055489136a \ No newline at end of file diff --git a/src/system_design/rest/flask/flask_sqlalchemy/app/__init__.py b/src/system_design/rest/flask/flask_sqlalchemy/app/__init__.py index 7d9d3cc..ad06af9 100644 --- a/src/system_design/rest/flask/flask_sqlalchemy/app/__init__.py +++ b/src/system_design/rest/flask/flask_sqlalchemy/app/__init__.py @@ -1,18 +1,18 @@ -from .config import Config +import argparse from flask import Flask from flask_restful import Api -from flask_sqlalchemy import SQLAlchemy -from app.resources.blog_posts import BlogPostListResource, BlogPostResource -from app.resources.users import UserListResource -from app.model.model_class import Base +from .resources.blog_posts import BlogPostListResource, BlogPostResource +from .resources.users import UserListResource +from .extensions import db +from .config import Config + def create_app(config_class=Config): app = Flask(__name__) app.config.from_object(config_class) api = Api(app) - db = SQLAlchemy(app, - model_class=Base) + db.init_app(app) api.add_resource(BlogPostListResource, '/posts', resource_class_kwargs={'db': db}) @@ -23,3 +23,20 @@ def create_app(config_class=Config): '/users', resource_class_kwargs={'db': db}) return app + + +def main(): + parser = argparse.ArgumentParser(description='Run the Flask application.') + parser.add_argument('--config', help='The configuration to use.') + parser.add_argument('--host', default='127.0.0.1', help='The host to listen on.') + parser.add_argument('--port', default=5000, type=int, help='The port to listen on.') + parser.add_argument('--debug', action='store_true', help='Run in debug mode.') + + args = parser.parse_args() + app = create_app(args.config) + + app.run(host=args.host, port=args.port, debug=args.debug) + + +if __name__ == "__main__": + main() diff --git a/src/system_design/rest/flask/flask_sqlalchemy/app/config.py b/src/system_design/rest/flask/flask_sqlalchemy/app/config.py index dbdc861..f00a7ff 100644 --- a/src/system_design/rest/flask/flask_sqlalchemy/app/config.py +++ b/src/system_design/rest/flask/flask_sqlalchemy/app/config.py @@ -6,3 +6,9 @@ class Config: HOST = '0.0.0.0' PORT = os.environ.get("FLASK_PORT", 5000) SQLALCHEMY_DATABASE_URI = "mysql+mysqlconnector://user:password@127.0.0.1:3306/db" + + +class TestConfig(Config): + SQLALCHEMY_DATABASE_URI = 'mysql:///:memory:' # In-memory SQLite database for testing + TESTING = True + WTF_CSRF_ENABLED = False # Disable CSRF tokens in testing for simplicity \ No newline at end of file diff --git a/src/system_design/rest/flask/flask_sqlalchemy/app/extensions.py b/src/system_design/rest/flask/flask_sqlalchemy/app/extensions.py new file mode 100644 index 0000000..ad37955 --- /dev/null +++ b/src/system_design/rest/flask/flask_sqlalchemy/app/extensions.py @@ -0,0 +1,5 @@ +from flask_sqlalchemy import SQLAlchemy +from .model.model_class import Base + + +db = SQLAlchemy(model_class=Base) diff --git a/src/system_design/rest/flask/flask_sqlalchemy/app/model/__init__.py b/src/system_design/rest/flask/flask_sqlalchemy/app/model/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/system_design/rest/flask/flask_sqlalchemy/app/model/blog_posts.py b/src/system_design/rest/flask/flask_sqlalchemy/app/model/blog_posts.py index 9a84e62..1823273 100644 --- a/src/system_design/rest/flask/flask_sqlalchemy/app/model/blog_posts.py +++ b/src/system_design/rest/flask/flask_sqlalchemy/app/model/blog_posts.py @@ -1,5 +1,5 @@ from sqlalchemy import Column, UUID, String, ForeignKey -from sqlalchemy.orm import mapped_column +from sqlalchemy.orm import mapped_column, relationship from .model_class import Base as ModelBase @@ -7,11 +7,14 @@ class BlogPosts(ModelBase): __tablename__ = "blog_posts" blog_post_id = Column(UUID, primary_key=True) blog_post_name = Column(String, unique=True) - user_id = mapped_column(ForeignKey("users.user_id")) + + user_id = mapped_column(UUID, ForeignKey("users.user_id")) + user = relationship("Users") def to_dict(self): return { - 'blog_post_id': self.blog_post_id, + 'blog_post_id': str(self.blog_post_id), 'blog_post_name': self.blog_post_name, - 'user_id': self.user_id + 'user_id': str(self.user_id), + 'user_name': self.user.user_name } \ No newline at end of file diff --git a/src/system_design/rest/flask/flask_sqlalchemy/app/resources/__init__.py b/src/system_design/rest/flask/flask_sqlalchemy/app/resources/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/system_design/rest/flask/flask_sqlalchemy/app/resources/blog_posts.py b/src/system_design/rest/flask/flask_sqlalchemy/app/resources/blog_posts.py index 6150f61..78c7a6f 100644 --- a/src/system_design/rest/flask/flask_sqlalchemy/app/resources/blog_posts.py +++ b/src/system_design/rest/flask/flask_sqlalchemy/app/resources/blog_posts.py @@ -1,4 +1,6 @@ from flask_restful import Resource +from sqlalchemy.orm import joinedload +from sqlalchemy.orm.session import Session from ..model.blog_posts import BlogPosts from ..model.users import Users @@ -6,12 +8,11 @@ class BlogPostListResource(Resource): def __init__(self, **kwargs): - self._db_instance = kwargs['db'] + self._db_instance = kwargs['db'] def get(self): - session = self._db_instance.session - blog_posts = (session.query(BlogPosts)).all() - + session: Session = self._db_instance.session + blog_posts = session.query(BlogPosts).join(Users).options(joinedload(BlogPosts.user)) return [blog_post.to_dict() for blog_post in blog_posts] diff --git a/src/system_design/rest/flask/flask_sqlalchemy/app/resources/users.py b/src/system_design/rest/flask/flask_sqlalchemy/app/resources/users.py index e745fd8..535053a 100644 --- a/src/system_design/rest/flask/flask_sqlalchemy/app/resources/users.py +++ b/src/system_design/rest/flask/flask_sqlalchemy/app/resources/users.py @@ -8,7 +8,11 @@ def __init__(self, **kwargs): def get(self): users = self._db_session.session.query(Users).all() - return [user.to_dict() for user in users] + + if len(users) > 0: + return [user.to_dict() for user in users] + else: + return [] class UserResource(Resource): diff --git a/tests/system_design/rest/flask/flask_sql_alchemy/conftest.py b/tests/system_design/rest/flask/flask_sql_alchemy/conftest.py new file mode 100644 index 0000000..41bc6a6 --- /dev/null +++ b/tests/system_design/rest/flask/flask_sql_alchemy/conftest.py @@ -0,0 +1,43 @@ +import pytest +from src.system_design.rest.flask.flask_sqlalchemy.app.config import TestConfig +from src.system_design.rest.flask.flask_sqlalchemy.app.extensions import db as _db +from src.system_design.rest.flask.flask_sqlalchemy.app import create_app + + +@pytest.fixture +def flask_sql_alchemy_app(): + app = create_app(TestConfig) + with app.app_context(): + yield app + + +@pytest.fixture(scope='session') +def db(flask_sql_alchemy_app): + _db.init_app(flask_sql_alchemy_app) + + with flask_sql_alchemy_app.app_context(): + _db.create_all() + yield _db + _db.drop_all() + + +@pytest.fixture(scope='function') +def session(db): + connection = db.engine.connect() + transaction = connection.begin() + + options = dict(bind=connection, binds={}) + session = db.create_scoped_session(options=options) + + db.session = session + + yield session + + transaction.rollback() + connection.close() + session.remove() + + +@pytest.fixture +def flask_sql_alchemy_test_client(flask_sql_alchemy_app): + return flask_sql_alchemy_app.test_client() \ No newline at end of file diff --git a/tests/system_design/rest/flask/flask_sql_alchemy/test_users_router.py b/tests/system_design/rest/flask/flask_sql_alchemy/test_users_router.py new file mode 100644 index 0000000..4846f92 --- /dev/null +++ b/tests/system_design/rest/flask/flask_sql_alchemy/test_users_router.py @@ -0,0 +1,4 @@ +def test_base_resource(flask_sql_alchemy_test_client): + res = flask_sql_alchemy_test_client.get('/users') + assert res.status_code == 200 + assert res.json == [] \ No newline at end of file From b585e6fa19285965d488f56b7f29a090db9b6667 Mon Sep 17 00:00:00 2001 From: Mark Barbaric Date: Wed, 4 Sep 2024 14:46:39 +0100 Subject: [PATCH 05/12] mysql - added sqllite in memory testing and first set of tests for users. --- create_users_and_blog_posts_table.sql | 16 ----- docker-compose.yml | 8 ++- sql_scripts/flask_sql_alchemy_setup.sql | 51 ++++++++++++++++ sql_scripts/populate_database.sql | 14 ----- sql_scripts/select_blog_post_id.sql | 2 - .../rest/flask/flask_sqlalchemy/app/config.py | 2 +- .../flask_sqlalchemy/app/model/review.py | 13 ++++ .../rest/flask/flask_sql_alchemy/conftest.py | 61 +++++++++++-------- .../routers/test_users_router.py | 8 +++ .../flask_sql_alchemy/test_users_router.py | 4 -- 10 files changed, 114 insertions(+), 65 deletions(-) delete mode 100644 create_users_and_blog_posts_table.sql create mode 100644 sql_scripts/flask_sql_alchemy_setup.sql delete mode 100644 sql_scripts/populate_database.sql delete mode 100644 sql_scripts/select_blog_post_id.sql create mode 100644 src/system_design/rest/flask/flask_sqlalchemy/app/model/review.py create mode 100644 tests/system_design/rest/flask/flask_sql_alchemy/routers/test_users_router.py delete mode 100644 tests/system_design/rest/flask/flask_sql_alchemy/test_users_router.py diff --git a/create_users_and_blog_posts_table.sql b/create_users_and_blog_posts_table.sql deleted file mode 100644 index 39ba136..0000000 --- a/create_users_and_blog_posts_table.sql +++ /dev/null @@ -1,16 +0,0 @@ -START TRANSACTION; - -CREATE TABLE users( - user_id VARCHAR(36) UNIQUE NOT NULL, - user_name VARCHAR(12) NOT NULL -); - -CREATE TABLE blog_posts( - blog_post_id VARCHAR(36) NOT NULL, - blog_post_name VARCHAR(12), - user_id VARCHAR(36) NOT NULL, - PRIMARY KEY (blog_post_id), - FOREIGN KEY (user_id) REFERENCES users(user_id) -); - -COMMIT; \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index f1519ef..e15b7c1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,17 +2,19 @@ services: mysql: image: mysql restart: always + command: --init-file /data/application/init.sql + volumes: + - ../sql_scripts/flask_sql_alchemy_setup.sql:/data/application/init.sql + - mysql:/var/lib/mysql environment: MYSQL_DATABASE: 'db' MYSQL_USER: 'user' MYSQL_PASSWORD: 'password' + MYSQL_ROOT_USER: 'root' MYSQL_ROOT_PASSWORD: example ports: - '3306:3306' expose: - '3306' - volumes: - - mysql:/var/lib/mysql - volumes: mysql: \ No newline at end of file diff --git a/sql_scripts/flask_sql_alchemy_setup.sql b/sql_scripts/flask_sql_alchemy_setup.sql new file mode 100644 index 0000000..276aed6 --- /dev/null +++ b/sql_scripts/flask_sql_alchemy_setup.sql @@ -0,0 +1,51 @@ +START TRANSACTION; + +CREATE TABLE users( + user_id VARCHAR(36) UNIQUE NOT NULL, + user_name VARCHAR(12) NOT NULL +); + +CREATE TABLE blog_posts( + blog_post_id VARCHAR(36) NOT NULL, + blog_post_name VARCHAR(12), + user_id VARCHAR(36) NOT NULL, + PRIMARY KEY (blog_post_id), + FOREIGN KEY (user_id) REFERENCES users(user_id) +); + +CREATE TABLE user_reviews( + review_id VARCHAR(36) NOT NULL, + rating FLOAT NOT NULL, + blog_post_id VARCHAR(36) NOT NULL, + user_id VARCHAR(36) NOT NULL, + PRIMARY KEY (review_id), + FOREIGN KEY (blog_post_id) REFERENCES blog_posts(blog_post_id), + FOREIGN KEY (user_id) REFERENCES users(user_id) +); + +COMMIT; + +START TRANSACTION; + +SET @user_uuid1 = uuid(); +SET @user_uuid2 = uuid(); +SET @user_uuid3 = uuid(); +SET @blog_post_id1 = uuid(); +SET @blog_post_id2 = uuid(); +SET @review_uuid1 = uuid(); +SET @review_uuid2 = uuid(); + +INSERT INTO users(user_id, user_name) +VALUES (@user_uuid1, 'mark'), +(@user_uuid2, 'john'), +(@user_uuid3, 'peter'); + +INSERT INTO blog_posts(blog_post_id, blog_post_name, user_id) +VALUES(@blog_post_id1, 'Post 1', @user_uuid1), +(@blog_post_id2, 'Post 2', @user_uuid1); + +INSERT INTO user_reviews(review_id, rating, user_id, blog_post_id) +VALUES(uuid(), 4, @user_uuid2, @blog_post_id1), +(uuid(), 3.5, @user_uuid3, @blog_post_id1); + +COMMIT; \ No newline at end of file diff --git a/sql_scripts/populate_database.sql b/sql_scripts/populate_database.sql deleted file mode 100644 index 93ae8be..0000000 --- a/sql_scripts/populate_database.sql +++ /dev/null @@ -1,14 +0,0 @@ -START TRANSACTION; - -SET @uuid1 = uuid(); -SET @uuid2 = uuid(); - -INSERT INTO users(user_id, user_name) -VALUES (@uuid1, 'mark'), -(@uuid2, 'john'); - -INSERT INTO blog_posts(blog_post_id, blog_post_name, user_id) -VALUES(uuid(), 'Post 1', @uuid1), -(uuid(), 'Post 2', @uuid1); - -COMMIT; \ No newline at end of file diff --git a/sql_scripts/select_blog_post_id.sql b/sql_scripts/select_blog_post_id.sql deleted file mode 100644 index 14b43ed..0000000 --- a/sql_scripts/select_blog_post_id.sql +++ /dev/null @@ -1,2 +0,0 @@ -SELECT * FROM blog_posts.blog_post_id, blog_posts.blog_post_name, users.user_name -INNER JOIN users ON users.user_id = blog_posts.user_id \ No newline at end of file diff --git a/src/system_design/rest/flask/flask_sqlalchemy/app/config.py b/src/system_design/rest/flask/flask_sqlalchemy/app/config.py index f00a7ff..774052e 100644 --- a/src/system_design/rest/flask/flask_sqlalchemy/app/config.py +++ b/src/system_design/rest/flask/flask_sqlalchemy/app/config.py @@ -9,6 +9,6 @@ class Config: class TestConfig(Config): - SQLALCHEMY_DATABASE_URI = 'mysql:///:memory:' # In-memory SQLite database for testing + SQLALCHEMY_DATABASE_URI = 'sqlite:///:memory:' # In-memory SQLite database for testing TESTING = True WTF_CSRF_ENABLED = False # Disable CSRF tokens in testing for simplicity \ No newline at end of file diff --git a/src/system_design/rest/flask/flask_sqlalchemy/app/model/review.py b/src/system_design/rest/flask/flask_sqlalchemy/app/model/review.py new file mode 100644 index 0000000..6a9d8b7 --- /dev/null +++ b/src/system_design/rest/flask/flask_sqlalchemy/app/model/review.py @@ -0,0 +1,13 @@ +from .model_class import Base as ModelBase +from sqlalchemy import UUID, Column, Float, ForeignKey +from sqlalchemy.orm import mapped_column, relationship + + +class Review(ModelBase): + __tablename__ = "reviews" + review_id = Column(UUID, primary_key=True) + rating = Column(Float) + blog_post_id = mapped_column(UUID, ForeignKey('blog_posts.blog_post_id')) + blog_post = relationship('BlogPosts') + user_id = mapped_column(UUID, ForeignKey('users.user_id')) + users = relationship('Users') diff --git a/tests/system_design/rest/flask/flask_sql_alchemy/conftest.py b/tests/system_design/rest/flask/flask_sql_alchemy/conftest.py index 41bc6a6..8c12ba6 100644 --- a/tests/system_design/rest/flask/flask_sql_alchemy/conftest.py +++ b/tests/system_design/rest/flask/flask_sql_alchemy/conftest.py @@ -1,43 +1,54 @@ import pytest +import uuid +from sqlalchemy import create_engine from src.system_design.rest.flask.flask_sqlalchemy.app.config import TestConfig from src.system_design.rest.flask.flask_sqlalchemy.app.extensions import db as _db +from src.system_design.rest.flask.flask_sqlalchemy.app.model.users import Users +from src.system_design.rest.flask.flask_sqlalchemy.app.model.blog_posts import BlogPosts +from src.system_design.rest.flask.flask_sqlalchemy.app.model.review import Review from src.system_design.rest.flask.flask_sqlalchemy.app import create_app - @pytest.fixture def flask_sql_alchemy_app(): app = create_app(TestConfig) - with app.app_context(): - yield app + yield app -@pytest.fixture(scope='session') -def db(flask_sql_alchemy_app): - _db.init_app(flask_sql_alchemy_app) - +@pytest.fixture +def setup_test_database(flask_sql_alchemy_app): with flask_sql_alchemy_app.app_context(): _db.create_all() - yield _db - _db.drop_all() - -@pytest.fixture(scope='function') -def session(db): - connection = db.engine.connect() - transaction = connection.begin() - - options = dict(bind=connection, binds={}) - session = db.create_scoped_session(options=options) + user_uuid1 = uuid.uuid4() + user_uuid2 = uuid.uuid4() + user_uuid3 = uuid.uuid4() + blog_post_uuid1 = uuid.uuid4() + blog_post_uuid2 = uuid.uuid4() + + _db.session.add_all([ + Users(user_id=user_uuid1, user_name='mark'), + Users(user_id=user_uuid2, user_name='john'), + Users(user_id=user_uuid3, user_name='peter') + ]) + + _db.session.add_all([ + BlogPosts(blog_post_id=blog_post_uuid1, blog_post_name='Post 1', user_id=user_uuid1), + BlogPosts(blog_post_id=blog_post_uuid2, blog_post_name='Post 2', user_id=user_uuid1) + ]) + + _db.session.add_all([ + Review(review_id=uuid.uuid4(), rating=4.0, blog_post_id=blog_post_uuid1, user_id=user_uuid2), + Review(review_id=uuid.uuid4(), rating=3.5, blog_post_id=blog_post_uuid1, user_id=user_uuid3) + ]) - db.session = session + _db.session.commit() - yield session + yield _db - transaction.rollback() - connection.close() - session.remove() + _db.session.remove() + _db.drop_all() -@pytest.fixture -def flask_sql_alchemy_test_client(flask_sql_alchemy_app): - return flask_sql_alchemy_app.test_client() \ No newline at end of file +@pytest.fixture(scope='function') +def flask_sql_alchemy_test_client(flask_sql_alchemy_app, setup_test_database): + return flask_sql_alchemy_app.test_client() diff --git a/tests/system_design/rest/flask/flask_sql_alchemy/routers/test_users_router.py b/tests/system_design/rest/flask/flask_sql_alchemy/routers/test_users_router.py new file mode 100644 index 0000000..6c71cd0 --- /dev/null +++ b/tests/system_design/rest/flask/flask_sql_alchemy/routers/test_users_router.py @@ -0,0 +1,8 @@ +def test_base_resource(flask_sql_alchemy_test_client): + res = flask_sql_alchemy_test_client.get('/users') + assert res.status_code == 200 + res_json = res.json + assert len(res_json) == 3 + assert res_json[0]['user_name'] == 'mark' + assert res_json[1]['user_name'] == 'john' + assert res_json[2]['user_name'] == 'peter' diff --git a/tests/system_design/rest/flask/flask_sql_alchemy/test_users_router.py b/tests/system_design/rest/flask/flask_sql_alchemy/test_users_router.py deleted file mode 100644 index 4846f92..0000000 --- a/tests/system_design/rest/flask/flask_sql_alchemy/test_users_router.py +++ /dev/null @@ -1,4 +0,0 @@ -def test_base_resource(flask_sql_alchemy_test_client): - res = flask_sql_alchemy_test_client.get('/users') - assert res.status_code == 200 - assert res.json == [] \ No newline at end of file From 9a678cbcc505190a96b632266ddde1d92c438150 Mon Sep 17 00:00:00 2001 From: Mark Barbaric Date: Wed, 4 Sep 2024 15:01:24 +0100 Subject: [PATCH 06/12] mysql - working on more complicated join to blog posts. --- .../flask_sqlalchemy/app/resources/blog_posts.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/system_design/rest/flask/flask_sqlalchemy/app/resources/blog_posts.py b/src/system_design/rest/flask/flask_sqlalchemy/app/resources/blog_posts.py index 78c7a6f..bdb580b 100644 --- a/src/system_design/rest/flask/flask_sqlalchemy/app/resources/blog_posts.py +++ b/src/system_design/rest/flask/flask_sqlalchemy/app/resources/blog_posts.py @@ -1,8 +1,9 @@ from flask_restful import Resource -from sqlalchemy.orm import joinedload +from sqlalchemy.orm import joinedload, aliased from sqlalchemy.orm.session import Session from ..model.blog_posts import BlogPosts from ..model.users import Users +from ..model.review import Review class BlogPostListResource(Resource): @@ -12,8 +13,13 @@ def __init__(self, **kwargs): def get(self): session: Session = self._db_instance.session - blog_posts = session.query(BlogPosts).join(Users).options(joinedload(BlogPosts.user)) - return [blog_post.to_dict() for blog_post in blog_posts] + user_alias = aliased(Users) + post_alias = aliased(BlogPosts) + review_alias = alised(Review) + query_results = ( + session.query(BlogPosts).join(Users).options(joinedload(BlogPosts.user)) + ) + return [result.to_dict() for result in query_results] class BlogPostResource(Resource): From ac2aa4942978f8f3c96141265170858aaa20737a Mon Sep 17 00:00:00 2001 From: Mark Barbaric Date: Wed, 4 Sep 2024 17:53:28 +0100 Subject: [PATCH 07/12] mysql - finished flask crud with sqlalchemy. --- sql_scripts/flask_sql_alchemy_setup.sql | 4 +- .../threading/asyncio_progress/main.py | 17 ++--- .../asyncio_queues/producer_consumer.py | 12 ++-- .../threading/asyncio_queues/progress.py | 2 +- .../flask/flask_sqlalchemy/app/__init__.py | 8 ++- .../rest/flask/flask_sqlalchemy/app/config.py | 2 +- .../flask_sqlalchemy/app/model/blog_posts.py | 12 +--- .../flask_sqlalchemy/app/model/model_class.py | 2 +- .../flask_sqlalchemy/app/model/review.py | 6 +- .../flask/flask_sqlalchemy/app/model/users.py | 9 +-- .../app/resources/blog_posts.py | 63 ++++++++++++++++--- .../flask_sqlalchemy/app/resources/users.py | 27 ++++++-- .../rest/flask/flask_sql_alchemy/conftest.py | 32 ++++++---- .../routers/test_blog_post_list_router.py | 13 ++++ .../routers/test_blog_post_router.py | 14 +++++ ...ers_router.py => test_user_list_router.py} | 2 +- .../routers/test_user_router.py | 12 ++++ 17 files changed, 172 insertions(+), 65 deletions(-) create mode 100644 tests/system_design/rest/flask/flask_sql_alchemy/routers/test_blog_post_list_router.py create mode 100644 tests/system_design/rest/flask/flask_sql_alchemy/routers/test_blog_post_router.py rename tests/system_design/rest/flask/flask_sql_alchemy/routers/{test_users_router.py => test_user_list_router.py} (83%) create mode 100644 tests/system_design/rest/flask/flask_sql_alchemy/routers/test_user_router.py diff --git a/sql_scripts/flask_sql_alchemy_setup.sql b/sql_scripts/flask_sql_alchemy_setup.sql index 276aed6..791ebe2 100644 --- a/sql_scripts/flask_sql_alchemy_setup.sql +++ b/sql_scripts/flask_sql_alchemy_setup.sql @@ -13,7 +13,7 @@ CREATE TABLE blog_posts( FOREIGN KEY (user_id) REFERENCES users(user_id) ); -CREATE TABLE user_reviews( +CREATE TABLE reviews( review_id VARCHAR(36) NOT NULL, rating FLOAT NOT NULL, blog_post_id VARCHAR(36) NOT NULL, @@ -44,7 +44,7 @@ INSERT INTO blog_posts(blog_post_id, blog_post_name, user_id) VALUES(@blog_post_id1, 'Post 1', @user_uuid1), (@blog_post_id2, 'Post 2', @user_uuid1); -INSERT INTO user_reviews(review_id, rating, user_id, blog_post_id) +INSERT INTO reviews(review_id, rating, user_id, blog_post_id) VALUES(uuid(), 4, @user_uuid2, @blog_post_id1), (uuid(), 3.5, @user_uuid3, @blog_post_id1); diff --git a/src/general_python/threading/asyncio_progress/main.py b/src/general_python/threading/asyncio_progress/main.py index e79c946..11b5758 100644 --- a/src/general_python/threading/asyncio_progress/main.py +++ b/src/general_python/threading/asyncio_progress/main.py @@ -2,17 +2,20 @@ # example of showing the progress of tasks using a callback import random import asyncio - + + # callback function to show the progress of tasks -def progress(task): +def progress(): # report progress of the task print('.', end='') - + + # coroutine task async def work(): # simulate effort await asyncio.sleep(random.random() * 10) - + + # main coroutine async def main(): # create and schedule many tasks @@ -24,8 +27,8 @@ async def main(): _ = await asyncio.wait(tasks) # report final message print('\nAll done.') - -# run the asyncio program + +# run the asyncio program if __name__ == "__main__": - asyncio.run(main()) \ No newline at end of file + asyncio.run(main()) diff --git a/src/general_python/threading/asyncio_queues/producer_consumer.py b/src/general_python/threading/asyncio_queues/producer_consumer.py index 14d2d6a..95ace69 100644 --- a/src/general_python/threading/asyncio_queues/producer_consumer.py +++ b/src/general_python/threading/asyncio_queues/producer_consumer.py @@ -3,9 +3,11 @@ from random import random import asyncio + # change this to alter behavior between consumer and producer MAX_QUEUE_SIZE = 10 + # coroutine to generate work async def producer(queue): print('Producer: Running') @@ -20,7 +22,8 @@ async def producer(queue): # send an all done signal await queue.put(None) print('Producer: Done') - + + # coroutine to consume work async def consumer(queue): print('Consumer: Running') @@ -35,13 +38,14 @@ async def consumer(queue): # report # all done print('Consumer: Done') - + + # entry point coroutine async def main(): # create the shared queue queue = asyncio.Queue(MAX_QUEUE_SIZE) # run the producer and consumers await asyncio.gather(producer(queue), consumer(queue)) - + # start the asyncio program -asyncio.run(main()) \ No newline at end of file +asyncio.run(main()) diff --git a/src/general_python/threading/asyncio_queues/progress.py b/src/general_python/threading/asyncio_queues/progress.py index bf5e35b..43bf665 100644 --- a/src/general_python/threading/asyncio_queues/progress.py +++ b/src/general_python/threading/asyncio_queues/progress.py @@ -50,4 +50,4 @@ async def main(): print(f'total expected sleep time: {total_sleep_time:.2f} seconds') -asyncio.run(main()) \ No newline at end of file +asyncio.run(main()) diff --git a/src/system_design/rest/flask/flask_sqlalchemy/app/__init__.py b/src/system_design/rest/flask/flask_sqlalchemy/app/__init__.py index ad06af9..cb56981 100644 --- a/src/system_design/rest/flask/flask_sqlalchemy/app/__init__.py +++ b/src/system_design/rest/flask/flask_sqlalchemy/app/__init__.py @@ -2,12 +2,11 @@ from flask import Flask from flask_restful import Api from .resources.blog_posts import BlogPostListResource, BlogPostResource -from .resources.users import UserListResource +from .resources.users import UserListResource, UserResource from .extensions import db from .config import Config - def create_app(config_class=Config): app = Flask(__name__) app.config.from_object(config_class) @@ -22,10 +21,13 @@ def create_app(config_class=Config): api.add_resource(UserListResource, '/users', resource_class_kwargs={'db': db}) + api.add_resource(UserResource, + '/users/', + resource_class_kwargs={'db': db}) return app -def main(): +def main(): parser = argparse.ArgumentParser(description='Run the Flask application.') parser.add_argument('--config', help='The configuration to use.') parser.add_argument('--host', default='127.0.0.1', help='The host to listen on.') diff --git a/src/system_design/rest/flask/flask_sqlalchemy/app/config.py b/src/system_design/rest/flask/flask_sqlalchemy/app/config.py index 774052e..507bf59 100644 --- a/src/system_design/rest/flask/flask_sqlalchemy/app/config.py +++ b/src/system_design/rest/flask/flask_sqlalchemy/app/config.py @@ -11,4 +11,4 @@ class Config: class TestConfig(Config): SQLALCHEMY_DATABASE_URI = 'sqlite:///:memory:' # In-memory SQLite database for testing TESTING = True - WTF_CSRF_ENABLED = False # Disable CSRF tokens in testing for simplicity \ No newline at end of file + WTF_CSRF_ENABLED = False # Disable CSRF tokens in testing for simplicity diff --git a/src/system_design/rest/flask/flask_sqlalchemy/app/model/blog_posts.py b/src/system_design/rest/flask/flask_sqlalchemy/app/model/blog_posts.py index 1823273..2859cc0 100644 --- a/src/system_design/rest/flask/flask_sqlalchemy/app/model/blog_posts.py +++ b/src/system_design/rest/flask/flask_sqlalchemy/app/model/blog_posts.py @@ -5,16 +5,8 @@ class BlogPosts(ModelBase): __tablename__ = "blog_posts" - blog_post_id = Column(UUID, primary_key=True) + blog_post_id = Column(UUID(as_uuid=False), primary_key=True) blog_post_name = Column(String, unique=True) - user_id = mapped_column(UUID, ForeignKey("users.user_id")) + user_id = mapped_column(UUID(as_uuid=False), ForeignKey("users.user_id")) user = relationship("Users") - - def to_dict(self): - return { - 'blog_post_id': str(self.blog_post_id), - 'blog_post_name': self.blog_post_name, - 'user_id': str(self.user_id), - 'user_name': self.user.user_name - } \ No newline at end of file diff --git a/src/system_design/rest/flask/flask_sqlalchemy/app/model/model_class.py b/src/system_design/rest/flask/flask_sqlalchemy/app/model/model_class.py index 710b933..21145b0 100644 --- a/src/system_design/rest/flask/flask_sqlalchemy/app/model/model_class.py +++ b/src/system_design/rest/flask/flask_sqlalchemy/app/model/model_class.py @@ -2,4 +2,4 @@ class Base(DeclarativeBase): - pass + ... diff --git a/src/system_design/rest/flask/flask_sqlalchemy/app/model/review.py b/src/system_design/rest/flask/flask_sqlalchemy/app/model/review.py index 6a9d8b7..23d0b44 100644 --- a/src/system_design/rest/flask/flask_sqlalchemy/app/model/review.py +++ b/src/system_design/rest/flask/flask_sqlalchemy/app/model/review.py @@ -5,9 +5,9 @@ class Review(ModelBase): __tablename__ = "reviews" - review_id = Column(UUID, primary_key=True) + review_id = Column(UUID(as_uuid=False), primary_key=True) rating = Column(Float) - blog_post_id = mapped_column(UUID, ForeignKey('blog_posts.blog_post_id')) + blog_post_id = mapped_column(UUID(as_uuid=False), ForeignKey('blog_posts.blog_post_id')) blog_post = relationship('BlogPosts') - user_id = mapped_column(UUID, ForeignKey('users.user_id')) + user_id = mapped_column(UUID(as_uuid=False), ForeignKey('users.user_id')) users = relationship('Users') diff --git a/src/system_design/rest/flask/flask_sqlalchemy/app/model/users.py b/src/system_design/rest/flask/flask_sqlalchemy/app/model/users.py index cb160d2..4a057ca 100644 --- a/src/system_design/rest/flask/flask_sqlalchemy/app/model/users.py +++ b/src/system_design/rest/flask/flask_sqlalchemy/app/model/users.py @@ -1,13 +1,8 @@ from sqlalchemy import UUID, Column, String from .model_class import Base as ModelBase + class Users(ModelBase): __tablename__ = "users" - user_id = Column(UUID, primary_key=True) + user_id = Column(UUID(as_uuid=False), primary_key=True) user_name = Column(String, unique=True) - - def to_dict(self): - return { - 'user_id' : str(self.user_id), - 'user_name' : self.user_name - } \ No newline at end of file diff --git a/src/system_design/rest/flask/flask_sqlalchemy/app/resources/blog_posts.py b/src/system_design/rest/flask/flask_sqlalchemy/app/resources/blog_posts.py index bdb580b..e5f3459 100644 --- a/src/system_design/rest/flask/flask_sqlalchemy/app/resources/blog_posts.py +++ b/src/system_design/rest/flask/flask_sqlalchemy/app/resources/blog_posts.py @@ -1,5 +1,6 @@ from flask_restful import Resource -from sqlalchemy.orm import joinedload, aliased +from sqlalchemy import func +from sqlalchemy.orm import aliased from sqlalchemy.orm.session import Session from ..model.blog_posts import BlogPosts from ..model.users import Users @@ -9,19 +10,65 @@ class BlogPostListResource(Resource): def __init__(self, **kwargs): - self._db_instance = kwargs['db'] - + self._db_instance = kwargs['db'] + def get(self): session: Session = self._db_instance.session user_alias = aliased(Users) post_alias = aliased(BlogPosts) - review_alias = alised(Review) + review_alias = aliased(Review) query_results = ( - session.query(BlogPosts).join(Users).options(joinedload(BlogPosts.user)) - ) - return [result.to_dict() for result in query_results] + session.query( + post_alias, + user_alias, + func.avg(review_alias.rating).label('average_rating') + ) + .join(user_alias, user_alias.user_id == post_alias.user_id) + .outerjoin(review_alias, review_alias.blog_post_id == post_alias.blog_post_id) + .group_by(post_alias.blog_post_id) + ).all() + + return [ + { + 'blog_post_id': str(blog_post.blog_post_id), + 'blog_post_name': blog_post.blog_post_name, + 'author': user.user_name, + 'average_rating': 0.0 if rating is None else rating + + } + for blog_post, user, rating in query_results + ] class BlogPostResource(Resource): + + def __init__(self, **kwargs): + self._db_instance = kwargs['db'] + def get(self, blog_post_id): - return self._db_session.query.filter_by(blog_post_id=blog_post_id) + session: Session = self._db_instance.session + user_alias = aliased(Users) + post_alias = aliased(BlogPosts) + review_alias = aliased(Review) + blog_post_query = session.query( + post_alias, + user_alias, + func.avg(review_alias.rating).label('average_rating') + ).filter_by( + blog_post_id=blog_post_id + ).join( + user_alias, post_alias.user_id == user_alias.user_id + ).outerjoin( + review_alias, review_alias.blog_post_id == post_alias.blog_post_id + ).first() + + if not blog_post_query[0] and not blog_post_query[1]: + return {"message": f"blog post with blog_post_id: {blog_post_id} not found"}, 404 + else: + blog_post, user, average_rating = blog_post_query + return { + 'blog_post_id': str(blog_post.blog_post_id), + 'blog_post_name': blog_post.blog_post_name, + 'author': user.user_name, + 'average_rating': 0.0 if average_rating is None else average_rating + } diff --git a/src/system_design/rest/flask/flask_sqlalchemy/app/resources/users.py b/src/system_design/rest/flask/flask_sqlalchemy/app/resources/users.py index 535053a..34149f8 100644 --- a/src/system_design/rest/flask/flask_sqlalchemy/app/resources/users.py +++ b/src/system_design/rest/flask/flask_sqlalchemy/app/resources/users.py @@ -1,4 +1,5 @@ from flask_restful import Resource +from sqlalchemy.orm import aliased from ..model.users import Users @@ -7,12 +8,15 @@ def __init__(self, **kwargs): self._db_session = kwargs['db'] def get(self): - users = self._db_session.session.query(Users).all() + users_query = self._db_session.session.query(Users).all() - if len(users) > 0: - return [user.to_dict() for user in users] - else: - return [] + return [ + { + 'user_id': str(user.user_id), + 'user_name': user.user_name + } + for user in users_query + ] class UserResource(Resource): @@ -20,4 +24,15 @@ def __init__(self, **kwargs): self._db_instance = kwargs['db'] def get(self, user_id): - user = self._db_instance.session.query(Users).get(user_id) \ No newline at end of file + user_alias = aliased(Users) + user = self._db_instance.session.query( + user_alias + ).filter_by(user_id=user_id).first() + + if not user: + return {"message": f"user with user_id: {user_id} not found"}, 404 + else: + return { + 'user_id': str(user.user_id), + 'user_name': str(user.user_name) + } diff --git a/tests/system_design/rest/flask/flask_sql_alchemy/conftest.py b/tests/system_design/rest/flask/flask_sql_alchemy/conftest.py index 8c12ba6..ceef338 100644 --- a/tests/system_design/rest/flask/flask_sql_alchemy/conftest.py +++ b/tests/system_design/rest/flask/flask_sql_alchemy/conftest.py @@ -1,6 +1,5 @@ import pytest import uuid -from sqlalchemy import create_engine from src.system_design.rest.flask.flask_sqlalchemy.app.config import TestConfig from src.system_design.rest.flask.flask_sqlalchemy.app.extensions import db as _db from src.system_design.rest.flask.flask_sqlalchemy.app.model.users import Users @@ -8,6 +7,7 @@ from src.system_design.rest.flask.flask_sqlalchemy.app.model.review import Review from src.system_design.rest.flask.flask_sqlalchemy.app import create_app + @pytest.fixture def flask_sql_alchemy_app(): app = create_app(TestConfig) @@ -15,30 +15,40 @@ def flask_sql_alchemy_app(): @pytest.fixture -def setup_test_database(flask_sql_alchemy_app): +def valid_user_id(): + return "091c7c77-6ad0-11ef-b000-0242ac130002" + + +@pytest.fixture +def valid_blog_post_id(): + return "091c7c77-6ad0-11ef-b000-0242ac130001" + + +@pytest.fixture +def setup_test_database(flask_sql_alchemy_app, valid_user_id, valid_blog_post_id): with flask_sql_alchemy_app.app_context(): _db.create_all() - user_uuid1 = uuid.uuid4() - user_uuid2 = uuid.uuid4() - user_uuid3 = uuid.uuid4() - blog_post_uuid1 = uuid.uuid4() - blog_post_uuid2 = uuid.uuid4() + user_uuid1 = valid_user_id + user_uuid2 = str(uuid.uuid4()) + user_uuid3 = str(uuid.uuid4()) + blog_post_uuid1 = valid_blog_post_id + blog_post_uuid2 = str(uuid.uuid4()) _db.session.add_all([ Users(user_id=user_uuid1, user_name='mark'), Users(user_id=user_uuid2, user_name='john'), Users(user_id=user_uuid3, user_name='peter') ]) - + _db.session.add_all([ BlogPosts(blog_post_id=blog_post_uuid1, blog_post_name='Post 1', user_id=user_uuid1), BlogPosts(blog_post_id=blog_post_uuid2, blog_post_name='Post 2', user_id=user_uuid1) ]) - + _db.session.add_all([ - Review(review_id=uuid.uuid4(), rating=4.0, blog_post_id=blog_post_uuid1, user_id=user_uuid2), - Review(review_id=uuid.uuid4(), rating=3.5, blog_post_id=blog_post_uuid1, user_id=user_uuid3) + Review(review_id=str(uuid.uuid4()), rating=4.0, blog_post_id=blog_post_uuid1, user_id=user_uuid2), + Review(review_id=str(uuid.uuid4()), rating=3.5, blog_post_id=blog_post_uuid1, user_id=user_uuid3) ]) _db.session.commit() diff --git a/tests/system_design/rest/flask/flask_sql_alchemy/routers/test_blog_post_list_router.py b/tests/system_design/rest/flask/flask_sql_alchemy/routers/test_blog_post_list_router.py new file mode 100644 index 0000000..a48e5a5 --- /dev/null +++ b/tests/system_design/rest/flask/flask_sql_alchemy/routers/test_blog_post_list_router.py @@ -0,0 +1,13 @@ +def test_200_response(flask_sql_alchemy_test_client): + res = flask_sql_alchemy_test_client.get('/posts') + assert res.status_code == 200 + res_json = res.json + assert len(res.json) == 2 + blog_post_1 = res_json[0] + assert blog_post_1['blog_post_name'] == 'Post 1' + assert blog_post_1['author'] == 'mark' + assert blog_post_1['average_rating'] == 3.75 + blog_post_2 = res_json[1] + assert blog_post_2['blog_post_name'] == 'Post 2' + assert blog_post_2['author'] == 'mark' + assert blog_post_2['average_rating'] == 0.0 diff --git a/tests/system_design/rest/flask/flask_sql_alchemy/routers/test_blog_post_router.py b/tests/system_design/rest/flask/flask_sql_alchemy/routers/test_blog_post_router.py new file mode 100644 index 0000000..474a782 --- /dev/null +++ b/tests/system_design/rest/flask/flask_sql_alchemy/routers/test_blog_post_router.py @@ -0,0 +1,14 @@ +def test_200_response(flask_sql_alchemy_test_client, valid_blog_post_id): + res = flask_sql_alchemy_test_client.get(f'/posts/{valid_blog_post_id}') + assert res.status_code == 200 + res_json = res.json + assert res_json['blog_post_name'] == 'Post 1' + assert res_json['author'] == 'mark' + assert res_json['average_rating'] == 3.75 + + +def test_404_response(flask_sql_alchemy_test_client): + res = flask_sql_alchemy_test_client.get('/posts/0001') + assert res.status_code == 404 + res_json = res.json + assert res_json == {'message': "blog post with blog_post_id: 0001 not found"} diff --git a/tests/system_design/rest/flask/flask_sql_alchemy/routers/test_users_router.py b/tests/system_design/rest/flask/flask_sql_alchemy/routers/test_user_list_router.py similarity index 83% rename from tests/system_design/rest/flask/flask_sql_alchemy/routers/test_users_router.py rename to tests/system_design/rest/flask/flask_sql_alchemy/routers/test_user_list_router.py index 6c71cd0..b41fa35 100644 --- a/tests/system_design/rest/flask/flask_sql_alchemy/routers/test_users_router.py +++ b/tests/system_design/rest/flask/flask_sql_alchemy/routers/test_user_list_router.py @@ -1,4 +1,4 @@ -def test_base_resource(flask_sql_alchemy_test_client): +def test_200_response(flask_sql_alchemy_test_client): res = flask_sql_alchemy_test_client.get('/users') assert res.status_code == 200 res_json = res.json diff --git a/tests/system_design/rest/flask/flask_sql_alchemy/routers/test_user_router.py b/tests/system_design/rest/flask/flask_sql_alchemy/routers/test_user_router.py new file mode 100644 index 0000000..264111d --- /dev/null +++ b/tests/system_design/rest/flask/flask_sql_alchemy/routers/test_user_router.py @@ -0,0 +1,12 @@ +def test_200_response(flask_sql_alchemy_test_client, valid_user_id): + res = flask_sql_alchemy_test_client.get(f'/users/{valid_user_id}') + assert res.status_code == 200 + res_json = res.json + assert res_json['user_name'] == 'mark' + + +def test_404_response(flask_sql_alchemy_test_client): + res = flask_sql_alchemy_test_client.get('/users/0001') + assert res.status_code == 404 + res_json = res.json + assert res_json == {'message': "user with user_id: 0001 not found"} From 78e5109461097fd819e1b6443a08977bb216b4cd Mon Sep 17 00:00:00 2001 From: Mark Barbaric Date: Thu, 5 Sep 2024 08:14:37 +0100 Subject: [PATCH 08/12] mysql - removed make badge github actions. --- .github/workflows/pytest.yml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 61b575c..048d624 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -62,15 +62,15 @@ jobs: echo "total=$TOTAL" >> $GITHUB_ENV echo "### Total coverage: ${TOTAL}%" >> $GITHUB_STEP_SUMMARY - - name: "Make badge" - uses: schneegans/dynamic-badges-action@v1.4.0 - with: - # GIST_TOKEN is a GitHub personal access token with scope "gist". - auth: ${{ secrets.GIST_TOKEN }} - gistID: d52efd902ac81cfcfd6248ffe036462c # replace with your real Gist id. - filename: covbadge.json - label: Coverage - message: ${{ env.total }}% - minColorRange: 50 - maxColorRange: 90 - valColorRange: ${{ env.total }} \ No newline at end of file + # - name: "Make badge" + # uses: schneegans/dynamic-badges-action@v1.4.0 + # with: + # # GIST_TOKEN is a GitHub personal access token with scope "gist". + # auth: ${{ secrets.GIST_TOKEN }} + # gistID: d52efd902ac81cfcfd6248ffe036462c # replace with your real Gist id. + # filename: covbadge.json + # label: Coverage + # message: ${{ env.total }}% + # minColorRange: 50 + # maxColorRange: 90 + # valColorRange: ${{ env.total }} \ No newline at end of file From fd8252503b75dbd5a686b0ead04e90c651fdc56c Mon Sep 17 00:00:00 2001 From: Mark Barbaric Date: Fri, 6 Sep 2024 18:53:02 +0100 Subject: [PATCH 09/12] ddd_domain_modeling - initial commit. --- src/domain_driven_design/cosmic_python/README.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/domain_driven_design/cosmic_python/README.md diff --git a/src/domain_driven_design/cosmic_python/README.md b/src/domain_driven_design/cosmic_python/README.md new file mode 100644 index 0000000..e69de29 From 82db4d42d4a61ffef762f83b7596a5eef5ccb69a Mon Sep 17 00:00:00 2001 From: Mark Barbaric Date: Sun, 8 Sep 2024 15:58:06 +0100 Subject: [PATCH 10/12] ddd_domain_modeling - working on cosmic python exercises. --- poetry.lock | 114 ++++- pylintrc | 399 ++++++++++++++++++ pyproject.toml | 1 + src/algos/array/trapping_rain_water.py | 22 +- src/algos/general/create_substeps.py | 1 + src/algos/general/decode.py | 3 +- src/algos/general/is_prime.py | 10 + .../linked_list/easy/delete_duplicates.py | 4 +- src/algos/linked_list/swap_in_pairs.py | 10 +- src/algos/trees/dfs/__init__.py | 2 +- src/algos/trees/dfs/lca.py | 6 - src/algos/trees/dfs/tree_depth.py | 4 - .../trees/traversal/iterative_traversal.py | 2 +- .../trees/traversal/recursive_traversal.py | 4 +- .../__pycache__/linked_list.cpython-310.pyc | Bin 632 -> 637 bytes src/data_structures/graphs/disjoint_set.py | 16 +- src/data_structures/linked_list.py | 4 +- .../cosmic_python/README.md | 1 + .../cosmic_python/domain_modeling/__init__.py | 0 .../cosmic_python/domain_modeling/model.py | 52 +++ tests/algos/linked_list/test_swap_in_pairs.py | 4 +- tests/data_structures/test_linked_list.py | 2 +- .../domain_modeling/test_batches.py | 43 ++ tests/test_helpers/linked_list_helpers.py | 6 +- 24 files changed, 659 insertions(+), 51 deletions(-) create mode 100644 pylintrc delete mode 100644 src/algos/trees/dfs/lca.py create mode 100644 src/domain_driven_design/cosmic_python/domain_modeling/__init__.py create mode 100644 src/domain_driven_design/cosmic_python/domain_modeling/model.py create mode 100644 tests/domain_driven_design/domain_modeling/test_batches.py diff --git a/poetry.lock b/poetry.lock index d7c8d1a..6d853eb 100644 --- a/poetry.lock +++ b/poetry.lock @@ -47,6 +47,20 @@ doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphin test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] trio = ["trio (>=0.23)"] +[[package]] +name = "astroid" +version = "3.2.4" +description = "An abstract syntax tree for Python with inference support." +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "astroid-3.2.4-py3-none-any.whl", hash = "sha256:413658a61eeca6202a59231abb473f932038fbcbf1666587f66d482083413a25"}, + {file = "astroid-3.2.4.tar.gz", hash = "sha256:0e14202810b30da1b735827f78f5157be2bbd4a7a59b7707ca0bfc2fb4c0063a"}, +] + +[package.dependencies] +typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.11\""} + [[package]] name = "asyncio" version = "3.4.3" @@ -290,6 +304,21 @@ files = [ docs = ["ipython", "matplotlib", "numpydoc", "sphinx"] tests = ["pytest", "pytest-cov", "pytest-xdist"] +[[package]] +name = "dill" +version = "0.3.8" +description = "serialize all of Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "dill-0.3.8-py3-none-any.whl", hash = "sha256:c36ca9ffb54365bdd2f8eb3eff7d2a21237f8452b57ace88b1ac615b7e815bd7"}, + {file = "dill-0.3.8.tar.gz", hash = "sha256:3ebe3c479ad625c4553aca177444d89b486b1d84982eeacded644afc0cf797ca"}, +] + +[package.extras] +graph = ["objgraph (>=1.7.2)"] +profile = ["gprof2dot (>=2022.7.29)"] + [[package]] name = "dnspython" version = "2.6.1" @@ -703,6 +732,20 @@ files = [ {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] +[[package]] +name = "isort" +version = "5.13.2" +description = "A Python utility / library to sort Python imports." +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, + {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, +] + +[package.extras] +colors = ["colorama (>=0.4.6)"] + [[package]] name = "itsdangerous" version = "2.2.0" @@ -1010,6 +1053,17 @@ python-dateutil = ">=2.7" [package.extras] dev = ["meson-python (>=0.13.1)", "numpy (>=1.25)", "pybind11 (>=2.6)", "setuptools (>=64)", "setuptools_scm (>=7)"] +[[package]] +name = "mccabe" +version = "0.7.0" +description = "McCabe checker, plugin for flake8" +optional = false +python-versions = ">=3.6" +files = [ + {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, + {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, +] + [[package]] name = "mdurl" version = "0.1.2" @@ -1264,6 +1318,22 @@ tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "pa typing = ["typing-extensions"] xmp = ["defusedxml"] +[[package]] +name = "platformdirs" +version = "4.3.1" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." +optional = false +python-versions = ">=3.8" +files = [ + {file = "platformdirs-4.3.1-py3-none-any.whl", hash = "sha256:facaa5a3c57aa1e053e3da7b49e0cc31fe0113ca42a4659d5c2e98e545624afe"}, + {file = "platformdirs-4.3.1.tar.gz", hash = "sha256:63b79589009fa8159973601dd4563143396b35c5f93a58b36f9049ff046949b1"}, +] + +[package.extras] +docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] +type = ["mypy (>=1.8)"] + [[package]] name = "pluggy" version = "1.5.0" @@ -1294,8 +1364,8 @@ files = [ annotated-types = ">=0.4.0" pydantic-core = "2.20.1" typing-extensions = [ - {version = ">=4.12.2", markers = "python_version >= \"3.13\""}, {version = ">=4.6.1", markers = "python_version < \"3.13\""}, + {version = ">=4.12.2", markers = "python_version >= \"3.13\""}, ] [package.extras] @@ -1436,6 +1506,35 @@ files = [ [package.extras] windows-terminal = ["colorama (>=0.4.6)"] +[[package]] +name = "pylint" +version = "3.2.7" +description = "python code static checker" +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "pylint-3.2.7-py3-none-any.whl", hash = "sha256:02f4aedeac91be69fb3b4bea997ce580a4ac68ce58b89eaefeaf06749df73f4b"}, + {file = "pylint-3.2.7.tar.gz", hash = "sha256:1b7a721b575eaeaa7d39db076b6e7743c993ea44f57979127c517c6c572c803e"}, +] + +[package.dependencies] +astroid = ">=3.2.4,<=3.3.0-dev0" +colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} +dill = [ + {version = ">=0.2", markers = "python_version < \"3.11\""}, + {version = ">=0.3.7", markers = "python_version >= \"3.12\""}, + {version = ">=0.3.6", markers = "python_version >= \"3.11\" and python_version < \"3.12\""}, +] +isort = ">=4.2.5,<5.13.0 || >5.13.0,<6" +mccabe = ">=0.6,<0.8" +platformdirs = ">=2.2.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +tomlkit = ">=0.10.1" + +[package.extras] +spelling = ["pyenchant (>=3.2,<4.0)"] +testutils = ["gitpython (>3)"] + [[package]] name = "pyparsing" version = "3.1.4" @@ -1730,6 +1829,17 @@ files = [ {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] +[[package]] +name = "tomlkit" +version = "0.13.2" +description = "Style preserving TOML library" +optional = false +python-versions = ">=3.8" +files = [ + {file = "tomlkit-0.13.2-py3-none-any.whl", hash = "sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde"}, + {file = "tomlkit-0.13.2.tar.gz", hash = "sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79"}, +] + [[package]] name = "typer" version = "0.12.5" @@ -2024,4 +2134,4 @@ watchdog = ["watchdog (>=2.3)"] [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "b3620e6ac7bb17b104b55d56b169fbe345c6860c06e7ae1f39073e579c609047" +content-hash = "4877e2c544375c0472477e65af2bb92ecb2875ed95e46bcf5d351b0b80cb914f" diff --git a/pylintrc b/pylintrc new file mode 100644 index 0000000..0529b01 --- /dev/null +++ b/pylintrc @@ -0,0 +1,399 @@ +# This Pylint rcfile contains a best-effort configuration to uphold the +# best-practices and style described in the Google Python style guide: +# https://google.github.io/styleguide/pyguide.html +# +# Its canonical open-source location is: +# https://google.github.io/styleguide/pylintrc + +[MAIN] + +# Files or directories to be skipped. They should be base names, not paths. +ignore=third_party + +# Files or directories matching the regex patterns are skipped. The regex +# matches against base names, not paths. +ignore-patterns= + +# Pickle collected data for later comparisons. +persistent=no + +# List of plugins (as comma separated values of python modules names) to load, +# usually to register additional checkers. +load-plugins= + +# Use multiple processes to speed up Pylint. +jobs=4 + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +unsafe-load-any-extension=no + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED +confidence= + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). See also the "--disable" option for examples. +#enable= + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once).You can also use "--disable=all" to +# disable everything first and then reenable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use"--disable=all --enable=classes +# --disable=W" +disable=R, + abstract-method, + apply-builtin, + arguments-differ, + attribute-defined-outside-init, + backtick, + bad-option-value, + basestring-builtin, + buffer-builtin, + c-extension-no-member, + consider-using-enumerate, + cmp-builtin, + cmp-method, + coerce-builtin, + coerce-method, + delslice-method, + div-method, + eq-without-hash, + execfile-builtin, + file-builtin, + filter-builtin-not-iterating, + fixme, + getslice-method, + global-statement, + hex-method, + idiv-method, + implicit-str-concat, + import-error, + import-self, + import-star-module-level, + input-builtin, + intern-builtin, + invalid-str-codec, + locally-disabled, + long-builtin, + long-suffix, + map-builtin-not-iterating, + misplaced-comparison-constant, + missing-function-docstring, + metaclass-assignment, + next-method-called, + next-method-defined, + no-absolute-import, + no-init, # added + no-member, + no-name-in-module, + no-self-use, + nonzero-method, + oct-method, + old-division, + old-ne-operator, + old-octal-literal, + old-raise-syntax, + parameter-unpacking, + print-statement, + raising-string, + range-builtin-not-iterating, + raw_input-builtin, + rdiv-method, + reduce-builtin, + relative-import, + reload-builtin, + round-builtin, + setslice-method, + signature-differs, + standarderror-builtin, + suppressed-message, + sys-max-int, + trailing-newlines, + unichr-builtin, + unicode-builtin, + unnecessary-pass, + unpacking-in-except, + useless-else-on-loop, + useless-suppression, + using-cmp-argument, + wrong-import-order, + xrange-builtin, + zip-builtin-not-iterating, + + +[REPORTS] + +# Set the output format. Available formats are text, parseable, colorized, msvs +# (visual studio) and html. You can also give a reporter class, eg +# mypackage.mymodule.MyReporterClass. +output-format=text + +# Tells whether to display a full report or only the messages +reports=no + +# Python expression which should return a note less than 10 (10 is the highest +# note). You have access to the variables errors warning, statement which +# respectively contain the number of errors / warnings messages and the total +# number of statements analyzed. This is used by the global evaluation report +# (RP0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details +#msg-template= + + +[BASIC] + +# Good variable names which should always be accepted, separated by a comma +good-names=main,_ + +# Bad variable names which should always be refused, separated by a comma +bad-names= + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Include a hint for the correct naming format with invalid-name +include-naming-hint=no + +# List of decorators that produce properties, such as abc.abstractproperty. Add +# to this list to register other decorators that produce valid properties. +property-classes=abc.abstractproperty,cached_property.cached_property,cached_property.threaded_cached_property,cached_property.cached_property_with_ttl,cached_property.threaded_cached_property_with_ttl + +# Regular expression matching correct function names +function-rgx=^(?:(?PsetUp|tearDown|setUpModule|tearDownModule)|(?P_?[A-Z][a-zA-Z0-9]*)|(?P_?[a-z][a-z0-9_]*))$ + +# Regular expression matching correct variable names +variable-rgx=^[a-z][a-z0-9_]*$ + +# Regular expression matching correct constant names +const-rgx=^(_?[A-Z][A-Z0-9_]*|__[a-z0-9_]+__|_?[a-z][a-z0-9_]*)$ + +# Regular expression matching correct attribute names +attr-rgx=^_{0,2}[a-z][a-z0-9_]*$ + +# Regular expression matching correct argument names +argument-rgx=^[a-z][a-z0-9_]*$ + +# Regular expression matching correct class attribute names +class-attribute-rgx=^(_?[A-Z][A-Z0-9_]*|__[a-z0-9_]+__|_?[a-z][a-z0-9_]*)$ + +# Regular expression matching correct inline iteration names +inlinevar-rgx=^[a-z][a-z0-9_]*$ + +# Regular expression matching correct class names +class-rgx=^_?[A-Z][a-zA-Z0-9]*$ + +# Regular expression matching correct module names +module-rgx=^(_?[a-z][a-z0-9_]*|__init__)$ + +# Regular expression matching correct method names +method-rgx=(?x)^(?:(?P_[a-z0-9_]+__|runTest|setUp|tearDown|setUpTestCase|tearDownTestCase|setupSelf|tearDownClass|setUpClass|(test|assert)_*[A-Z0-9][a-zA-Z0-9_]*|next)|(?P_{0,2}[A-Z][a-zA-Z0-9_]*)|(?P_{0,2}[a-z][a-z0-9_]*))$ + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=(__.*__|main|test.*|.*test|.*Test)$ + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=12 + + +[TYPECHECK] + +# List of decorators that produce context managers, such as +# contextlib.contextmanager. Add to this list to register other decorators that +# produce valid context managers. +contextmanager-decorators=contextlib.contextmanager,contextlib2.contextmanager + +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis. It +# supports qualified module names, as well as Unix pattern matching. +ignored-modules= + +# List of class names for which member attributes should not be checked (useful +# for classes with dynamically set attributes). This supports the use of +# qualified names. +ignored-classes=optparse.Values,thread._local,_thread._local + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +generated-members= + + +[FORMAT] + +# Maximum number of characters on a single line. +max-line-length=120 + +# TODO(https://github.com/pylint-dev/pylint/issues/3352): Direct pylint to exempt +# lines made too long by directives to pytype. + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=(?x)( + ^\s*(\#\ )??$| + ^\s*(from\s+\S+\s+)?import\s+.+$) + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=yes + +# Maximum number of lines in a module +max-module-lines=99999 + +# String used as indentation unit. The internal Google style guide mandates 2 +# spaces. Google's externaly-published style guide says 4, consistent with +# PEP 8. Here, we use 2 spaces, for conformity with many open-sourced Google +# projects (like TensorFlow). +indent-string=' ' + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=TODO + + +[STRING] + +# This flag controls whether inconsistent-quotes generates a warning when the +# character used as a quote delimiter is used inconsistently within a module. +check-quote-consistency=yes + + +[VARIABLES] + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# A regular expression matching the name of dummy variables (i.e. expectedly +# not used). +dummy-variables-rgx=^\*{0,2}(_$|unused_|dummy_) + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid to define new builtins when possible. +additional-builtins= + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_,_cb + +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules=six,six.moves,past.builtins,future.builtins,functools + + +[LOGGING] + +# Logging modules to check that the string format arguments are in logging +# function parameter format +logging-modules=logging,absl.logging,tensorflow.io.logging + + +[SIMILARITIES] + +# Minimum lines number of a similarity. +min-similarity-lines=4 + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes + +# Ignore imports when computing similarities. +ignore-imports=no + + +[SPELLING] + +# Spelling dictionary name. Available dictionaries: none. To make it working +# install python-enchant package. +spelling-dict= + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# A path to a file that contains private dictionary; one word per line. +spelling-private-dict-file= + +# Tells whether to store unknown words to indicated private dictionary in +# --spelling-private-dict-file option instead of raising a message. +spelling-store-unknown-words=no + + +[IMPORTS] + +# Deprecated modules which should not be used, separated by a comma +deprecated-modules=regsub, + TERMIOS, + Bastion, + rexec, + sets + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report RP0402 must not be disabled) +import-graph= + +# Create a graph of external dependencies in the given file (report RP0402 must +# not be disabled) +ext-import-graph= + +# Create a graph of internal dependencies in the given file (report RP0402 must +# not be disabled) +int-import-graph= + +# Force import order to recognize a module as part of the standard +# compatibility libraries. +known-standard-library= + +# Force import order to recognize a module as part of a third party library. +known-third-party=enchant, absl + +# Analyse import fallback blocks. This can be used to support both Python 2 and +# 3 compatible code, which means that the block might have code that exists +# only in one or another interpreter, leading to false positives when analysed. +analyse-fallback-blocks=no + + +[CLASSES] + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__, + __new__, + setUp + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict, + _fields, + _replace, + _source, + _make + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls, + class_ + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=mcs diff --git a/pyproject.toml b/pyproject.toml index 2ac29e6..c8038f9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,6 +24,7 @@ flask-sqlalchemy = "^3.1.1" mysql-connector-python = "^9.0.0" sqlalchemy = "^2.0.32" mysqlclient = "2.2.0" +pylint = "^3.2.7" [tool.poetry.group.jupyter.dependencies] matplotlib = "^3.9.2" diff --git a/src/algos/array/trapping_rain_water.py b/src/algos/array/trapping_rain_water.py index 2fdfce1..38745a8 100644 --- a/src/algos/array/trapping_rain_water.py +++ b/src/algos/array/trapping_rain_water.py @@ -2,34 +2,34 @@ def trap_rain_water(heights: list[int]) -> int: ans = 0 for i in range(1, len(heights)-1): - maxL, maxR = 0, 0 + max_l, max_r = 0, 0 for left in range(i, -1, -1): - maxL = max(maxL, heights[left]) + max_l = max(max_l, heights[left]) for right in range(i, len(heights)): - maxR = max(maxR, heights[right]) + max_r = max(max_r, heights[right]) - ans += min(maxL, maxR) - heights[i] + ans += min(max_l, max_r) - heights[i] return ans def trap_rain_water_dp(heights: list[int]) -> int: ans = 0 - leftMax = [0 for _ in range(len(heights))] - rightMax = [0 for _ in range(len(heights))] + left_max = [0 for _ in range(len(heights))] + right_max = [0 for _ in range(len(heights))] - leftMax[0] = heights[0] - rightMax[-1] = heights[-1] + left_max[0] = heights[0] + right_max[-1] = heights[-1] for i in range(1, len(heights)): - leftMax[i] = max(heights[i], leftMax[i - 1]) + left_max[i] = max(heights[i], left_max[i - 1]) for i in range(len(heights) - 2, 0, -1): - rightMax[i] = max(heights[i], rightMax[i+1]) + right_max[i] = max(heights[i], right_max[i+1]) for i in range(1, len(heights) - 1): - ans += min(leftMax[i], rightMax[i]) - heights[i] + ans += min(left_max[i], right_max[i]) - heights[i] return ans diff --git a/src/algos/general/create_substeps.py b/src/algos/general/create_substeps.py index 1848b9b..5b8805b 100644 --- a/src/algos/general/create_substeps.py +++ b/src/algos/general/create_substeps.py @@ -1,3 +1,4 @@ +"""_summary_""" def create_substeps(nums: list): index = 1 sub_steps = [] diff --git a/src/algos/general/decode.py b/src/algos/general/decode.py index f96fe5d..27e6107 100644 --- a/src/algos/general/decode.py +++ b/src/algos/general/decode.py @@ -1,6 +1,7 @@ +"""_summary_""" def decode(message_file: str): try: - with open(message_file, 'r') as file: + with open(message_file, 'r', encoding='.txt') as file: text_lines = file.read().splitlines() except FileNotFoundError: print(f"file doesn't exist: {message_file}") diff --git a/src/algos/general/is_prime.py b/src/algos/general/is_prime.py index 2a910c3..22521f5 100644 --- a/src/algos/general/is_prime.py +++ b/src/algos/general/is_prime.py @@ -1,7 +1,17 @@ +"""_summary_ +""" from math import sqrt def is_prime(num: int) -> bool: + """_summary_ + + Args: + num (int): _description_ + + Returns: + bool: _description_ + """ if num > 1: for i in range(2, int(sqrt(num)) + 1): if num % i == 0: diff --git a/src/algos/linked_list/easy/delete_duplicates.py b/src/algos/linked_list/easy/delete_duplicates.py index 8eab58d..725b701 100644 --- a/src/algos/linked_list/easy/delete_duplicates.py +++ b/src/algos/linked_list/easy/delete_duplicates.py @@ -6,14 +6,14 @@ def delete_duplicates_from_sorted_list(node: Optional[LinkedList]) -> Optional[L if not node: return None - cur = node.next + cur = node.left_next prev = node while cur: while cur and cur.val == prev.val: cur = cur.next - prev.next = cur + prev.left_next = cur prev = cur if cur: cur = cur.next diff --git a/src/algos/linked_list/swap_in_pairs.py b/src/algos/linked_list/swap_in_pairs.py index 2383970..c4f4f35 100644 --- a/src/algos/linked_list/swap_in_pairs.py +++ b/src/algos/linked_list/swap_in_pairs.py @@ -5,23 +5,23 @@ def swapPairs(head: Optional[LinkedList]) -> Optional[LinkedList]: prev = None left = head - right = None if not head else head.next + right = None if not head else head.left_next while left and right: cur_next = right.next - left.next = cur_next + left.left_next = cur_next right.next = left if not prev: head = right if prev: - prev.next = right + prev.left_next = right prev = left - left = left.next + left = left.left_next if left: - right = left.next + right = left.left_next return head diff --git a/src/algos/trees/dfs/__init__.py b/src/algos/trees/dfs/__init__.py index 65f011d..7b205e4 100644 --- a/src/algos/trees/dfs/__init__.py +++ b/src/algos/trees/dfs/__init__.py @@ -1,2 +1,2 @@ from .reversal import reverse_tree, invert_tree -from .tree_depth import max_depth, min_depth +from .tree_depth import max_depth diff --git a/src/algos/trees/dfs/lca.py b/src/algos/trees/dfs/lca.py deleted file mode 100644 index d127c28..0000000 --- a/src/algos/trees/dfs/lca.py +++ /dev/null @@ -1,6 +0,0 @@ -from typing import Optional -from src.data_structures.trees import TreeNode - - -def lowest_common_ancestor(root: Optional[TreeNode], a: int, b: int) -> Optional[TreeNode]: - return None diff --git a/src/algos/trees/dfs/tree_depth.py b/src/algos/trees/dfs/tree_depth.py index 650e47f..d0152cd 100644 --- a/src/algos/trees/dfs/tree_depth.py +++ b/src/algos/trees/dfs/tree_depth.py @@ -9,7 +9,3 @@ def max_depth(root: Optional[TreeNode]) -> int: left_max = max_depth(root.left) right_max = max_depth(root.right) return max(left_max, right_max) + 1 - - -def min_depth(root: Optional[TreeNode]) -> int: - ... diff --git a/src/algos/trees/traversal/iterative_traversal.py b/src/algos/trees/traversal/iterative_traversal.py index 2a76188..d3707a5 100644 --- a/src/algos/trees/traversal/iterative_traversal.py +++ b/src/algos/trees/traversal/iterative_traversal.py @@ -6,7 +6,7 @@ def iterative_inorder_traversal(root: Optional[TreeNode], ans: list[int]): cur = root stack = [] - while len(stack) or cur: + while len(stack) > 0 or cur: while cur: stack.append(cur) cur = cur.left diff --git a/src/algos/trees/traversal/recursive_traversal.py b/src/algos/trees/traversal/recursive_traversal.py index 49b8dc8..37d6f6c 100644 --- a/src/algos/trees/traversal/recursive_traversal.py +++ b/src/algos/trees/traversal/recursive_traversal.py @@ -7,8 +7,8 @@ def recursive_preorder_traversal(root: Optional[TreeNode], nums: list[int]): return nums.append(root.val) - recursive_preorder_traversal(root.left) - recursive_preorder_traversal(root.right) + recursive_preorder_traversal(root.left, nums) + recursive_preorder_traversal(root.right, nums) def recursive_inorder_traversal(root: Optional[TreeNode], nums: list[int]): diff --git a/src/data_structures/__pycache__/linked_list.cpython-310.pyc b/src/data_structures/__pycache__/linked_list.cpython-310.pyc index 63aff48ed7baa91d34236dfd5a1c5672afcf869e..ee15ee55b15327bb6ccb66680d00f64c66938164 100644 GIT binary patch delta 32 ncmeyt@|T4-pO=@50SG2+xs$egBJW8y&YaY=lK73+7cv3>ts@JG delta 27 hcmey%@`HsppO=@50SF8xg{Q5V$a|8JW#gTNi~wvu2xI^N diff --git a/src/data_structures/graphs/disjoint_set.py b/src/data_structures/graphs/disjoint_set.py index dcb51a8..376e0dc 100644 --- a/src/data_structures/graphs/disjoint_set.py +++ b/src/data_structures/graphs/disjoint_set.py @@ -7,12 +7,12 @@ def __find(self, x): return self._root[x] def union(self, x: int, y: int): - rootX = self.__find(x) - rootY = self.__find(y) + root_x = self.__find(x) + root_y = self.__find(y) for i in range(self.size): - if self._root[i] == rootY: - self._root[i] = rootX + if self._root[i] == root_y: + self._root[i] = root_x def connected(self, x: int, y: int) -> bool: return self.__find(x) == self.__find(y) @@ -33,11 +33,11 @@ def __find(self, x): return x def union(self, x: int, y: int): - rootX = self.__find(x) - rootY = self.__find(y) + root_x = self.__find(x) + root_y = self.__find(y) - if rootX != rootY: - self._root[rootY] = rootX + if root_x != root_y: + self._root[root_y] = root_x def connected(self, x: int, y: int) -> bool: return self.__find(x) == self.__find(y) diff --git a/src/data_structures/linked_list.py b/src/data_structures/linked_list.py index 5a24b85..7ce1c2b 100644 --- a/src/data_structures/linked_list.py +++ b/src/data_structures/linked_list.py @@ -1,8 +1,8 @@ class LinkedList: - def __init__(self, val: int, next=None): + def __init__(self, val: int, left_next=None): self._val = val - self.next = next + self.left_next = left_next @property def val(self): diff --git a/src/domain_driven_design/cosmic_python/README.md b/src/domain_driven_design/cosmic_python/README.md index e69de29..27049e8 100644 --- a/src/domain_driven_design/cosmic_python/README.md +++ b/src/domain_driven_design/cosmic_python/README.md @@ -0,0 +1 @@ +https://www.cosmicpython.com/book/chapter_01_domain_model.html \ No newline at end of file diff --git a/src/domain_driven_design/cosmic_python/domain_modeling/__init__.py b/src/domain_driven_design/cosmic_python/domain_modeling/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/domain_driven_design/cosmic_python/domain_modeling/model.py b/src/domain_driven_design/cosmic_python/domain_modeling/model.py new file mode 100644 index 0000000..e9de98c --- /dev/null +++ b/src/domain_driven_design/cosmic_python/domain_modeling/model.py @@ -0,0 +1,52 @@ +from typing import TypeVar +from dataclasses import dataclass +from typing import Optional +from datetime import date + + +SKU = TypeVar("SKU", bound=str) + + +@dataclass(frozen=True) +class OrderLine: + orderid: str + sku: SKU + qty: int + + +class Batch: + def __init__(self, + ref: str, + sku: SKU, + qty: int, + eta: Optional[date]): + self.reference = ref + self.sku = sku + self.eta = eta + self._purchased_quantity = qty + self._allocations = set() + + def allocate(self, line: OrderLine) -> bool: + if self.can_allocate(line): + self._allocations.add(line) + return True + else: + return False + + def deallocate(self, line: OrderLine) -> bool: + if line in self._allocations: + self._allocations.remove(line) + return True + else: + return False + + @property + def allocated_quantity(self) -> int: + return sum(line.qty for line in self._allocations) + + @property + def available_quantity(self) -> int: + return self._purchased_quantity - self.allocated_quantity + + def can_allocate(self, line: OrderLine) -> bool: + return self.sku == line.sku and self.available_quantity >= line.qty \ No newline at end of file diff --git a/tests/algos/linked_list/test_swap_in_pairs.py b/tests/algos/linked_list/test_swap_in_pairs.py index b38ec15..6e9ebba 100644 --- a/tests/algos/linked_list/test_swap_in_pairs.py +++ b/tests/algos/linked_list/test_swap_in_pairs.py @@ -4,5 +4,5 @@ def test_fixture(): root = list_to_linked_list([1, 2, 3, 4]) assert root.val == 1 - assert root.next.val == 2 - assert root.next.next.val == 3 + assert root.left_next.val == 2 + assert root.left_next.next.val == 3 diff --git a/tests/data_structures/test_linked_list.py b/tests/data_structures/test_linked_list.py index 4811d39..dfffdb3 100644 --- a/tests/data_structures/test_linked_list.py +++ b/tests/data_structures/test_linked_list.py @@ -8,4 +8,4 @@ def test_linked_list(): for n in list: assert n == list_head.val print(f"val {list_head.val}") - list_head = list_head.next + list_head = list_head.left_next diff --git a/tests/domain_driven_design/domain_modeling/test_batches.py b/tests/domain_driven_design/domain_modeling/test_batches.py new file mode 100644 index 0000000..53bfb8e --- /dev/null +++ b/tests/domain_driven_design/domain_modeling/test_batches.py @@ -0,0 +1,43 @@ +from datetime import date +from src.domain_driven_design.cosmic_python.domain_modeling.model import Batch, OrderLine + + +def make_batch_and_line(sku, batch_qty, line_qty): + return ( + Batch("batch-001", sku, batch_qty, eta=date.today()), + OrderLine("order-123", sku, line_qty) + ) + + +def test_can_allocate_if_available_greater_than_required(): + large_batch, small_line = make_batch_and_line("ELEGANT-LAMP", 20, 2) + assert large_batch.can_allocate(small_line) + assert large_batch.available_quantity == 20 + assert large_batch.allocate(small_line) == True + assert large_batch.available_quantity == 18 + + +def test_cannot_allocate_if_available_smaller_than_required(): + small_batch, large_line = make_batch_and_line("ELEGANT-LAMP", 2, 20) + assert small_batch.can_allocate(large_line) is False + + +def test_can_allocate_if_available_equal_to_required(): + batch, line = make_batch_and_line("ELEGANT-LAMP", 2, 2) + assert batch.can_allocate(line) + assert batch.allocate(line) == True + assert not batch.can_allocate(line) + + +def test_cannot_allocate_if_skus_do_not_match(): + batch = Batch("batch-001", "UNCOMFORTABLE-CHAIR", 100, eta=None) + different_sku_line = OrderLine("order-123", "EXPENSIVE-TOASTER", 10) + assert batch.can_allocate(different_sku_line) is False + + +def test_can_only_deallocated_allocated_lines(): + batch, unallocated_line = make_batch_and_line("DECORATIVE-TRINKET", 20, 2) + assert batch.deallocate(unallocated_line) == False + batch.allocate(unallocated_line) + assert batch.available_quantity == 18 + assert batch.deallocate(unallocated_line) == True diff --git a/tests/test_helpers/linked_list_helpers.py b/tests/test_helpers/linked_list_helpers.py index 1c9f61d..4846d8d 100644 --- a/tests/test_helpers/linked_list_helpers.py +++ b/tests/test_helpers/linked_list_helpers.py @@ -7,8 +7,8 @@ def list_to_linked_list(nums: list[int]) -> LinkedList: for i in range(1, len(nums)): new_node = LinkedList(nums[i]) - head.next = new_node - head = head.next + head.left_next = new_node + head = head.left_next return root @@ -18,6 +18,6 @@ def linked_list_to_list(root: LinkedList) -> list[int]: while cur: ans.append(cur.val) - cur = cur.next + cur = cur.left_next return ans From cd055d1571a47060568f7590c2ab159a7e69897d Mon Sep 17 00:00:00 2001 From: Mark Barbaric Date: Sun, 8 Sep 2024 16:06:14 +0100 Subject: [PATCH 11/12] ddd_domain_modeling - more work on chapter. --- .../cosmic_python/domain_modeling/error.py | 0 .../cosmic_python/domain_modeling/model.py | 5 ++++- .../domain_driven_design/domain_modeling/test_model.py | 10 ++++++++++ 3 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 src/domain_driven_design/cosmic_python/domain_modeling/error.py create mode 100644 tests/domain_driven_design/domain_modeling/test_model.py diff --git a/src/domain_driven_design/cosmic_python/domain_modeling/error.py b/src/domain_driven_design/cosmic_python/domain_modeling/error.py new file mode 100644 index 0000000..e69de29 diff --git a/src/domain_driven_design/cosmic_python/domain_modeling/model.py b/src/domain_driven_design/cosmic_python/domain_modeling/model.py index e9de98c..473cd90 100644 --- a/src/domain_driven_design/cosmic_python/domain_modeling/model.py +++ b/src/domain_driven_design/cosmic_python/domain_modeling/model.py @@ -9,10 +9,13 @@ @dataclass(frozen=True) class OrderLine: - orderid: str + order_id: str sku: SKU qty: int + def __eq__(self, other): + return self.order_id == other.order_id + class Batch: def __init__(self, diff --git a/tests/domain_driven_design/domain_modeling/test_model.py b/tests/domain_driven_design/domain_modeling/test_model.py new file mode 100644 index 0000000..fac3833 --- /dev/null +++ b/tests/domain_driven_design/domain_modeling/test_model.py @@ -0,0 +1,10 @@ +from src.domain_driven_design.cosmic_python.domain_modeling.model import OrderLine + + +def test_order_line_comparison(): + order_line_1 = OrderLine("ORD0001", "T-SHIRT", 100) + order_line_2 = OrderLine("ORD0001", "T-SHIRT", 200) + order_line_3 = OrderLine("ORD0002", "T-SHIRT", 100) + assert order_line_1 == order_line_2 + assert order_line_2 != order_line_3 + assert order_line_1 != order_line_3 From f0178656387cacf11952a3f877d7ace914259cae Mon Sep 17 00:00:00 2001 From: Mark Barbaric Date: Sun, 15 Sep 2024 09:33:19 +0100 Subject: [PATCH 12/12] ddd_domain_modeling - finished section 1. --- .github/workflows/pylint.yml | 15 +- poetry.lock | 60 ++- pylintrc | 399 ------------------ pyproject.toml | 8 + .../two_pointers/squares_of_sorted_array.py | 2 +- src/algos/general/create_substeps.py | 2 + src/algos/general/decode.py | 4 +- .../linked_list/easy/delete_duplicates.py | 4 +- src/algos/linked_list/swap_in_pairs.py | 10 +- src/algos/trees/dfs/sum.py | 2 +- .../__pycache__/linked_list.cpython-310.pyc | Bin 637 -> 638 bytes src/data_structures/linked_list.py | 4 +- .../cosmic_python/domain_modeling/model.py | 4 +- .../threading/asyncio_queues/__init__.py} | 0 .../consumer_producer_threading/__init__.py | 0 .../consumer_producer_threading/consumer.py | 16 - .../consumer_producer_threading/main.py | 28 +- .../consumer_producer_threading/producer.py | 14 - .../app/routers/fake_recipes_db.py | 4 +- tests/algos/linked_list/test_swap_in_pairs.py | 4 +- tests/data_structures/test_linked_list.py | 2 +- .../domain_modeling/test_batches.py | 12 +- tests/test_helpers/linked_list_helpers.py | 6 +- 23 files changed, 139 insertions(+), 461 deletions(-) delete mode 100644 pylintrc rename src/{domain_driven_design/cosmic_python/domain_modeling/error.py => general_python/threading/asyncio_queues/__init__.py} (100%) create mode 100644 src/general_python/threading/consumer_producer_threading/__init__.py delete mode 100644 src/general_python/threading/consumer_producer_threading/consumer.py delete mode 100644 src/general_python/threading/consumer_producer_threading/producer.py diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml index bc858aa..f3cc687 100644 --- a/.github/workflows/pylint.yml +++ b/.github/workflows/pylint.yml @@ -14,4 +14,17 @@ jobs: with: python-version: "3.11" - name: flake8 Lint - uses: py-actions/flake8@v2 \ No newline at end of file + uses: py-actions/flake8@v2 + mypy: + runs-on: ubuntu-latest + steps: + - name: Setup Python + uses: actions/setup-python@v4 + with: + python-version: "3.11" + - name: Checkout + uses: actions/checkout@v3 + - name: Install mypy + run: pip install mypy + - name: Run mypy + run: python -m mypy \ No newline at end of file diff --git a/poetry.lock b/poetry.lock index 6d853eb..748c34b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1075,6 +1075,64 @@ files = [ {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, ] +[[package]] +name = "mypy" +version = "1.11.2" +description = "Optional static typing for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "mypy-1.11.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d42a6dd818ffce7be66cce644f1dff482f1d97c53ca70908dff0b9ddc120b77a"}, + {file = "mypy-1.11.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:801780c56d1cdb896eacd5619a83e427ce436d86a3bdf9112527f24a66618fef"}, + {file = "mypy-1.11.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41ea707d036a5307ac674ea172875f40c9d55c5394f888b168033177fce47383"}, + {file = "mypy-1.11.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6e658bd2d20565ea86da7d91331b0eed6d2eee22dc031579e6297f3e12c758c8"}, + {file = "mypy-1.11.2-cp310-cp310-win_amd64.whl", hash = "sha256:478db5f5036817fe45adb7332d927daa62417159d49783041338921dcf646fc7"}, + {file = "mypy-1.11.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:75746e06d5fa1e91bfd5432448d00d34593b52e7e91a187d981d08d1f33d4385"}, + {file = "mypy-1.11.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a976775ab2256aadc6add633d44f100a2517d2388906ec4f13231fafbb0eccca"}, + {file = "mypy-1.11.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cd953f221ac1379050a8a646585a29574488974f79d8082cedef62744f0a0104"}, + {file = "mypy-1.11.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:57555a7715c0a34421013144a33d280e73c08df70f3a18a552938587ce9274f4"}, + {file = "mypy-1.11.2-cp311-cp311-win_amd64.whl", hash = "sha256:36383a4fcbad95f2657642a07ba22ff797de26277158f1cc7bd234821468b1b6"}, + {file = "mypy-1.11.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e8960dbbbf36906c5c0b7f4fbf2f0c7ffb20f4898e6a879fcf56a41a08b0d318"}, + {file = "mypy-1.11.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:06d26c277962f3fb50e13044674aa10553981ae514288cb7d0a738f495550b36"}, + {file = "mypy-1.11.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6e7184632d89d677973a14d00ae4d03214c8bc301ceefcdaf5c474866814c987"}, + {file = "mypy-1.11.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3a66169b92452f72117e2da3a576087025449018afc2d8e9bfe5ffab865709ca"}, + {file = "mypy-1.11.2-cp312-cp312-win_amd64.whl", hash = "sha256:969ea3ef09617aff826885a22ece0ddef69d95852cdad2f60c8bb06bf1f71f70"}, + {file = "mypy-1.11.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:37c7fa6121c1cdfcaac97ce3d3b5588e847aa79b580c1e922bb5d5d2902df19b"}, + {file = "mypy-1.11.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4a8a53bc3ffbd161b5b2a4fff2f0f1e23a33b0168f1c0778ec70e1a3d66deb86"}, + {file = "mypy-1.11.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ff93107f01968ed834f4256bc1fc4475e2fecf6c661260066a985b52741ddce"}, + {file = "mypy-1.11.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:edb91dded4df17eae4537668b23f0ff6baf3707683734b6a818d5b9d0c0c31a1"}, + {file = "mypy-1.11.2-cp38-cp38-win_amd64.whl", hash = "sha256:ee23de8530d99b6db0573c4ef4bd8f39a2a6f9b60655bf7a1357e585a3486f2b"}, + {file = "mypy-1.11.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:801ca29f43d5acce85f8e999b1e431fb479cb02d0e11deb7d2abb56bdaf24fd6"}, + {file = "mypy-1.11.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:af8d155170fcf87a2afb55b35dc1a0ac21df4431e7d96717621962e4b9192e70"}, + {file = "mypy-1.11.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f7821776e5c4286b6a13138cc935e2e9b6fde05e081bdebf5cdb2bb97c9df81d"}, + {file = "mypy-1.11.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:539c570477a96a4e6fb718b8d5c3e0c0eba1f485df13f86d2970c91f0673148d"}, + {file = "mypy-1.11.2-cp39-cp39-win_amd64.whl", hash = "sha256:3f14cd3d386ac4d05c5a39a51b84387403dadbd936e17cb35882134d4f8f0d24"}, + {file = "mypy-1.11.2-py3-none-any.whl", hash = "sha256:b499bc07dbdcd3de92b0a8b29fdf592c111276f6a12fe29c30f6c417dd546d12"}, + {file = "mypy-1.11.2.tar.gz", hash = "sha256:7f9993ad3e0ffdc95c2a14b66dee63729f021968bff8ad911867579c65d13a79"}, +] + +[package.dependencies] +mypy-extensions = ">=1.0.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = ">=4.6.0" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +install-types = ["pip"] +mypyc = ["setuptools (>=50)"] +reports = ["lxml"] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + [[package]] name = "mysql-connector-python" version = "9.0.0" @@ -2134,4 +2192,4 @@ watchdog = ["watchdog (>=2.3)"] [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "4877e2c544375c0472477e65af2bb92ecb2875ed95e46bcf5d351b0b80cb914f" +content-hash = "69fc9bff6cd96ea415cd0e4be7b47b39d74b0f7973cb30a76c9baa06297442bd" diff --git a/pylintrc b/pylintrc deleted file mode 100644 index 0529b01..0000000 --- a/pylintrc +++ /dev/null @@ -1,399 +0,0 @@ -# This Pylint rcfile contains a best-effort configuration to uphold the -# best-practices and style described in the Google Python style guide: -# https://google.github.io/styleguide/pyguide.html -# -# Its canonical open-source location is: -# https://google.github.io/styleguide/pylintrc - -[MAIN] - -# Files or directories to be skipped. They should be base names, not paths. -ignore=third_party - -# Files or directories matching the regex patterns are skipped. The regex -# matches against base names, not paths. -ignore-patterns= - -# Pickle collected data for later comparisons. -persistent=no - -# List of plugins (as comma separated values of python modules names) to load, -# usually to register additional checkers. -load-plugins= - -# Use multiple processes to speed up Pylint. -jobs=4 - -# Allow loading of arbitrary C extensions. Extensions are imported into the -# active Python interpreter and may run arbitrary code. -unsafe-load-any-extension=no - - -[MESSAGES CONTROL] - -# Only show warnings with the listed confidence levels. Leave empty to show -# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED -confidence= - -# Enable the message, report, category or checker with the given id(s). You can -# either give multiple identifier separated by comma (,) or put this option -# multiple time (only on the command line, not in the configuration file where -# it should appear only once). See also the "--disable" option for examples. -#enable= - -# Disable the message, report, category or checker with the given id(s). You -# can either give multiple identifiers separated by comma (,) or put this -# option multiple times (only on the command line, not in the configuration -# file where it should appear only once).You can also use "--disable=all" to -# disable everything first and then reenable specific checks. For example, if -# you want to run only the similarities checker, you can use "--disable=all -# --enable=similarities". If you want to run only the classes checker, but have -# no Warning level messages displayed, use"--disable=all --enable=classes -# --disable=W" -disable=R, - abstract-method, - apply-builtin, - arguments-differ, - attribute-defined-outside-init, - backtick, - bad-option-value, - basestring-builtin, - buffer-builtin, - c-extension-no-member, - consider-using-enumerate, - cmp-builtin, - cmp-method, - coerce-builtin, - coerce-method, - delslice-method, - div-method, - eq-without-hash, - execfile-builtin, - file-builtin, - filter-builtin-not-iterating, - fixme, - getslice-method, - global-statement, - hex-method, - idiv-method, - implicit-str-concat, - import-error, - import-self, - import-star-module-level, - input-builtin, - intern-builtin, - invalid-str-codec, - locally-disabled, - long-builtin, - long-suffix, - map-builtin-not-iterating, - misplaced-comparison-constant, - missing-function-docstring, - metaclass-assignment, - next-method-called, - next-method-defined, - no-absolute-import, - no-init, # added - no-member, - no-name-in-module, - no-self-use, - nonzero-method, - oct-method, - old-division, - old-ne-operator, - old-octal-literal, - old-raise-syntax, - parameter-unpacking, - print-statement, - raising-string, - range-builtin-not-iterating, - raw_input-builtin, - rdiv-method, - reduce-builtin, - relative-import, - reload-builtin, - round-builtin, - setslice-method, - signature-differs, - standarderror-builtin, - suppressed-message, - sys-max-int, - trailing-newlines, - unichr-builtin, - unicode-builtin, - unnecessary-pass, - unpacking-in-except, - useless-else-on-loop, - useless-suppression, - using-cmp-argument, - wrong-import-order, - xrange-builtin, - zip-builtin-not-iterating, - - -[REPORTS] - -# Set the output format. Available formats are text, parseable, colorized, msvs -# (visual studio) and html. You can also give a reporter class, eg -# mypackage.mymodule.MyReporterClass. -output-format=text - -# Tells whether to display a full report or only the messages -reports=no - -# Python expression which should return a note less than 10 (10 is the highest -# note). You have access to the variables errors warning, statement which -# respectively contain the number of errors / warnings messages and the total -# number of statements analyzed. This is used by the global evaluation report -# (RP0004). -evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) - -# Template used to display messages. This is a python new-style format string -# used to format the message information. See doc for all details -#msg-template= - - -[BASIC] - -# Good variable names which should always be accepted, separated by a comma -good-names=main,_ - -# Bad variable names which should always be refused, separated by a comma -bad-names= - -# Colon-delimited sets of names that determine each other's naming style when -# the name regexes allow several styles. -name-group= - -# Include a hint for the correct naming format with invalid-name -include-naming-hint=no - -# List of decorators that produce properties, such as abc.abstractproperty. Add -# to this list to register other decorators that produce valid properties. -property-classes=abc.abstractproperty,cached_property.cached_property,cached_property.threaded_cached_property,cached_property.cached_property_with_ttl,cached_property.threaded_cached_property_with_ttl - -# Regular expression matching correct function names -function-rgx=^(?:(?PsetUp|tearDown|setUpModule|tearDownModule)|(?P_?[A-Z][a-zA-Z0-9]*)|(?P_?[a-z][a-z0-9_]*))$ - -# Regular expression matching correct variable names -variable-rgx=^[a-z][a-z0-9_]*$ - -# Regular expression matching correct constant names -const-rgx=^(_?[A-Z][A-Z0-9_]*|__[a-z0-9_]+__|_?[a-z][a-z0-9_]*)$ - -# Regular expression matching correct attribute names -attr-rgx=^_{0,2}[a-z][a-z0-9_]*$ - -# Regular expression matching correct argument names -argument-rgx=^[a-z][a-z0-9_]*$ - -# Regular expression matching correct class attribute names -class-attribute-rgx=^(_?[A-Z][A-Z0-9_]*|__[a-z0-9_]+__|_?[a-z][a-z0-9_]*)$ - -# Regular expression matching correct inline iteration names -inlinevar-rgx=^[a-z][a-z0-9_]*$ - -# Regular expression matching correct class names -class-rgx=^_?[A-Z][a-zA-Z0-9]*$ - -# Regular expression matching correct module names -module-rgx=^(_?[a-z][a-z0-9_]*|__init__)$ - -# Regular expression matching correct method names -method-rgx=(?x)^(?:(?P_[a-z0-9_]+__|runTest|setUp|tearDown|setUpTestCase|tearDownTestCase|setupSelf|tearDownClass|setUpClass|(test|assert)_*[A-Z0-9][a-zA-Z0-9_]*|next)|(?P_{0,2}[A-Z][a-zA-Z0-9_]*)|(?P_{0,2}[a-z][a-z0-9_]*))$ - -# Regular expression which should only match function or class names that do -# not require a docstring. -no-docstring-rgx=(__.*__|main|test.*|.*test|.*Test)$ - -# Minimum line length for functions/classes that require docstrings, shorter -# ones are exempt. -docstring-min-length=12 - - -[TYPECHECK] - -# List of decorators that produce context managers, such as -# contextlib.contextmanager. Add to this list to register other decorators that -# produce valid context managers. -contextmanager-decorators=contextlib.contextmanager,contextlib2.contextmanager - -# List of module names for which member attributes should not be checked -# (useful for modules/projects where namespaces are manipulated during runtime -# and thus existing member attributes cannot be deduced by static analysis. It -# supports qualified module names, as well as Unix pattern matching. -ignored-modules= - -# List of class names for which member attributes should not be checked (useful -# for classes with dynamically set attributes). This supports the use of -# qualified names. -ignored-classes=optparse.Values,thread._local,_thread._local - -# List of members which are set dynamically and missed by pylint inference -# system, and so shouldn't trigger E1101 when accessed. Python regular -# expressions are accepted. -generated-members= - - -[FORMAT] - -# Maximum number of characters on a single line. -max-line-length=120 - -# TODO(https://github.com/pylint-dev/pylint/issues/3352): Direct pylint to exempt -# lines made too long by directives to pytype. - -# Regexp for a line that is allowed to be longer than the limit. -ignore-long-lines=(?x)( - ^\s*(\#\ )??$| - ^\s*(from\s+\S+\s+)?import\s+.+$) - -# Allow the body of an if to be on the same line as the test if there is no -# else. -single-line-if-stmt=yes - -# Maximum number of lines in a module -max-module-lines=99999 - -# String used as indentation unit. The internal Google style guide mandates 2 -# spaces. Google's externaly-published style guide says 4, consistent with -# PEP 8. Here, we use 2 spaces, for conformity with many open-sourced Google -# projects (like TensorFlow). -indent-string=' ' - -# Number of spaces of indent required inside a hanging or continued line. -indent-after-paren=4 - -# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. -expected-line-ending-format= - - -[MISCELLANEOUS] - -# List of note tags to take in consideration, separated by a comma. -notes=TODO - - -[STRING] - -# This flag controls whether inconsistent-quotes generates a warning when the -# character used as a quote delimiter is used inconsistently within a module. -check-quote-consistency=yes - - -[VARIABLES] - -# Tells whether we should check for unused import in __init__ files. -init-import=no - -# A regular expression matching the name of dummy variables (i.e. expectedly -# not used). -dummy-variables-rgx=^\*{0,2}(_$|unused_|dummy_) - -# List of additional names supposed to be defined in builtins. Remember that -# you should avoid to define new builtins when possible. -additional-builtins= - -# List of strings which can identify a callback function by name. A callback -# name must start or end with one of those strings. -callbacks=cb_,_cb - -# List of qualified module names which can have objects that can redefine -# builtins. -redefining-builtins-modules=six,six.moves,past.builtins,future.builtins,functools - - -[LOGGING] - -# Logging modules to check that the string format arguments are in logging -# function parameter format -logging-modules=logging,absl.logging,tensorflow.io.logging - - -[SIMILARITIES] - -# Minimum lines number of a similarity. -min-similarity-lines=4 - -# Ignore comments when computing similarities. -ignore-comments=yes - -# Ignore docstrings when computing similarities. -ignore-docstrings=yes - -# Ignore imports when computing similarities. -ignore-imports=no - - -[SPELLING] - -# Spelling dictionary name. Available dictionaries: none. To make it working -# install python-enchant package. -spelling-dict= - -# List of comma separated words that should not be checked. -spelling-ignore-words= - -# A path to a file that contains private dictionary; one word per line. -spelling-private-dict-file= - -# Tells whether to store unknown words to indicated private dictionary in -# --spelling-private-dict-file option instead of raising a message. -spelling-store-unknown-words=no - - -[IMPORTS] - -# Deprecated modules which should not be used, separated by a comma -deprecated-modules=regsub, - TERMIOS, - Bastion, - rexec, - sets - -# Create a graph of every (i.e. internal and external) dependencies in the -# given file (report RP0402 must not be disabled) -import-graph= - -# Create a graph of external dependencies in the given file (report RP0402 must -# not be disabled) -ext-import-graph= - -# Create a graph of internal dependencies in the given file (report RP0402 must -# not be disabled) -int-import-graph= - -# Force import order to recognize a module as part of the standard -# compatibility libraries. -known-standard-library= - -# Force import order to recognize a module as part of a third party library. -known-third-party=enchant, absl - -# Analyse import fallback blocks. This can be used to support both Python 2 and -# 3 compatible code, which means that the block might have code that exists -# only in one or another interpreter, leading to false positives when analysed. -analyse-fallback-blocks=no - - -[CLASSES] - -# List of method names used to declare (i.e. assign) instance attributes. -defining-attr-methods=__init__, - __new__, - setUp - -# List of member names, which should be excluded from the protected access -# warning. -exclude-protected=_asdict, - _fields, - _replace, - _source, - _make - -# List of valid names for the first argument in a class method. -valid-classmethod-first-arg=cls, - class_ - -# List of valid names for the first argument in a metaclass class method. -valid-metaclass-classmethod-first-arg=mcs diff --git a/pyproject.toml b/pyproject.toml index c8038f9..b8e9d31 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,6 +29,14 @@ pylint = "^3.2.7" [tool.poetry.group.jupyter.dependencies] matplotlib = "^3.9.2" + +[tool.poetry.group.dev.dependencies] +mypy = "^1.11.2" + [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" + +[tool.mypy] +files = ['src', 'tests'] +ignore_missing_imports = true \ No newline at end of file diff --git a/src/algos/array/two_pointers/squares_of_sorted_array.py b/src/algos/array/two_pointers/squares_of_sorted_array.py index 27d1cd7..7727b78 100644 --- a/src/algos/array/two_pointers/squares_of_sorted_array.py +++ b/src/algos/array/two_pointers/squares_of_sorted_array.py @@ -34,7 +34,7 @@ def return_squares(l1: list[int]) -> list[int]: ans[i] = negative_num ** 2 left -= 1 else: - if positive_num == 0 or (abs(positive_num) < abs(negative_num)): + if positive_num == 0 or int((abs(positive_num) < abs(negative_num))): ans[i] = positive_num ** 2 right += 1 else: diff --git a/src/algos/general/create_substeps.py b/src/algos/general/create_substeps.py index 5b8805b..f6adaf0 100644 --- a/src/algos/general/create_substeps.py +++ b/src/algos/general/create_substeps.py @@ -1,4 +1,6 @@ """_summary_""" + + def create_substeps(nums: list): index = 1 sub_steps = [] diff --git a/src/algos/general/decode.py b/src/algos/general/decode.py index 27e6107..2c9bc05 100644 --- a/src/algos/general/decode.py +++ b/src/algos/general/decode.py @@ -1,7 +1,9 @@ """_summary_""" + + def decode(message_file: str): try: - with open(message_file, 'r', encoding='.txt') as file: + with open(message_file, 'r', encoding='utf-8') as file: text_lines = file.read().splitlines() except FileNotFoundError: print(f"file doesn't exist: {message_file}") diff --git a/src/algos/linked_list/easy/delete_duplicates.py b/src/algos/linked_list/easy/delete_duplicates.py index 725b701..8eab58d 100644 --- a/src/algos/linked_list/easy/delete_duplicates.py +++ b/src/algos/linked_list/easy/delete_duplicates.py @@ -6,14 +6,14 @@ def delete_duplicates_from_sorted_list(node: Optional[LinkedList]) -> Optional[L if not node: return None - cur = node.left_next + cur = node.next prev = node while cur: while cur and cur.val == prev.val: cur = cur.next - prev.left_next = cur + prev.next = cur prev = cur if cur: cur = cur.next diff --git a/src/algos/linked_list/swap_in_pairs.py b/src/algos/linked_list/swap_in_pairs.py index c4f4f35..2383970 100644 --- a/src/algos/linked_list/swap_in_pairs.py +++ b/src/algos/linked_list/swap_in_pairs.py @@ -5,23 +5,23 @@ def swapPairs(head: Optional[LinkedList]) -> Optional[LinkedList]: prev = None left = head - right = None if not head else head.left_next + right = None if not head else head.next while left and right: cur_next = right.next - left.left_next = cur_next + left.next = cur_next right.next = left if not prev: head = right if prev: - prev.left_next = right + prev.next = right prev = left - left = left.left_next + left = left.next if left: - right = left.left_next + right = left.next return head diff --git a/src/algos/trees/dfs/sum.py b/src/algos/trees/dfs/sum.py index ae7613c..7499d84 100644 --- a/src/algos/trees/dfs/sum.py +++ b/src/algos/trees/dfs/sum.py @@ -24,6 +24,6 @@ def helper(root: Optional[TreeNode], cur_bits: list[int], ans: list[int]): rest = sum([2 ** (len(cur_bits) - i - 1) for i, bit in enumerate(cur_bits[:-1]) if bit == 1]) ans.append(rest + first_bit) - ans = [] + ans: list[int] = [] helper(root, [], ans) return sum(ans) diff --git a/src/data_structures/__pycache__/linked_list.cpython-310.pyc b/src/data_structures/__pycache__/linked_list.cpython-310.pyc index ee15ee55b15327bb6ccb66680d00f64c66938164..2c4ed91d9df297a63181a8bbf686e649264fdbcb 100644 GIT binary patch delta 156 zcmey%@{ff#pO=@50SGc?KTF#-k@ut`OI~V4i6-+cmg3Z$v?3;;)GbbsP<&2iamh-C zB94jQwsLa-g^R!h=j3gSAu1pvZgCV8M^NnAovj7V0|{Z`6;D2 OsdkJ&UNOi3MY_%i=&_@zaX`!q!OYYp$4i@pGjQ bool: if self.can_allocate(line): @@ -52,4 +52,4 @@ def available_quantity(self) -> int: return self._purchased_quantity - self.allocated_quantity def can_allocate(self, line: OrderLine) -> bool: - return self.sku == line.sku and self.available_quantity >= line.qty \ No newline at end of file + return self.sku == line.sku and self.available_quantity >= line.qty diff --git a/src/domain_driven_design/cosmic_python/domain_modeling/error.py b/src/general_python/threading/asyncio_queues/__init__.py similarity index 100% rename from src/domain_driven_design/cosmic_python/domain_modeling/error.py rename to src/general_python/threading/asyncio_queues/__init__.py diff --git a/src/general_python/threading/consumer_producer_threading/__init__.py b/src/general_python/threading/consumer_producer_threading/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/general_python/threading/consumer_producer_threading/consumer.py b/src/general_python/threading/consumer_producer_threading/consumer.py deleted file mode 100644 index 04cf1b8..0000000 --- a/src/general_python/threading/consumer_producer_threading/consumer.py +++ /dev/null @@ -1,16 +0,0 @@ -import logging -from settings import get_app_settings - - -def consumer(pipeline, event): - """Pretend we're saving a number in the database.""" - logger = logging.getLogger(get_app_settings().logger_name) - while not event.is_set() or not pipeline.empty(): - message = pipeline.get_message("Consumer") - logger.info( - "Consumer storing message: %s (queue size=%s)", - message, - pipeline.qsize(), - ) - - logger.info("Consumer received EXIT event. Exiting") diff --git a/src/general_python/threading/consumer_producer_threading/main.py b/src/general_python/threading/consumer_producer_threading/main.py index 7b44f51..f8bb47c 100644 --- a/src/general_python/threading/consumer_producer_threading/main.py +++ b/src/general_python/threading/consumer_producer_threading/main.py @@ -2,13 +2,37 @@ import threading import time from concurrent import futures +import random from logging_config import setup_custom_logger from settings import get_app_settings -from consumer import consumer -from producer import producer from pipeline import Pipeline +def consumer(pipeline, event): + """Pretend we're saving a number in the database.""" + logger = logging.getLogger(get_app_settings().logger_name) + while not event.is_set() or not pipeline.empty(): + message = pipeline.get_message("Consumer") + logger.info( + "Consumer storing message: %s (queue size=%s)", + message, + pipeline.qsize(), + ) + + logger.info("Consumer received EXIT event. Exiting") + + +def producer(pipeline, event): + """Pretend we're getting a number from the network.""" + logger = logging.getLogger(get_app_settings().logger_name) + while not event.is_set(): + message = random.randint(1, 101) + logger.info("Producer got message: %s", message) + pipeline.set_message(message, "Producer") + + logger.info("Producer received EXIT event. Exiting") + + if __name__ == "__main__": setup_custom_logger(get_app_settings().logger_name) logger = logging.getLogger(get_app_settings().logger_name) diff --git a/src/general_python/threading/consumer_producer_threading/producer.py b/src/general_python/threading/consumer_producer_threading/producer.py deleted file mode 100644 index 0e8ee3a..0000000 --- a/src/general_python/threading/consumer_producer_threading/producer.py +++ /dev/null @@ -1,14 +0,0 @@ -import random -import logging -from settings import get_app_settings - - -def producer(pipeline, event): - """Pretend we're getting a number from the network.""" - logger = logging.getLogger(get_app_settings().logger_name) - while not event.is_set(): - message = random.randint(1, 101) - logger.info("Producer got message: %s", message) - pipeline.set_message(message, "Producer") - - logger.info("Producer received EXIT event. Exiting") diff --git a/src/system_design/rest/fastapi_crud/app/routers/fake_recipes_db.py b/src/system_design/rest/fastapi_crud/app/routers/fake_recipes_db.py index 3feb7b7..560f1c9 100644 --- a/src/system_design/rest/fastapi_crud/app/routers/fake_recipes_db.py +++ b/src/system_design/rest/fastapi_crud/app/routers/fake_recipes_db.py @@ -1,5 +1,5 @@ from typing import Optional -from enum import Enum +from enum import IntEnum from ..model import RecipeModel @@ -21,7 +21,7 @@ ] -class RecipeSearchType(Enum): +class RecipeSearchType(IntEnum): id = 0 name = 1 diff --git a/tests/algos/linked_list/test_swap_in_pairs.py b/tests/algos/linked_list/test_swap_in_pairs.py index 6e9ebba..b38ec15 100644 --- a/tests/algos/linked_list/test_swap_in_pairs.py +++ b/tests/algos/linked_list/test_swap_in_pairs.py @@ -4,5 +4,5 @@ def test_fixture(): root = list_to_linked_list([1, 2, 3, 4]) assert root.val == 1 - assert root.left_next.val == 2 - assert root.left_next.next.val == 3 + assert root.next.val == 2 + assert root.next.next.val == 3 diff --git a/tests/data_structures/test_linked_list.py b/tests/data_structures/test_linked_list.py index dfffdb3..4811d39 100644 --- a/tests/data_structures/test_linked_list.py +++ b/tests/data_structures/test_linked_list.py @@ -8,4 +8,4 @@ def test_linked_list(): for n in list: assert n == list_head.val print(f"val {list_head.val}") - list_head = list_head.left_next + list_head = list_head.next diff --git a/tests/domain_driven_design/domain_modeling/test_batches.py b/tests/domain_driven_design/domain_modeling/test_batches.py index 53bfb8e..a06d886 100644 --- a/tests/domain_driven_design/domain_modeling/test_batches.py +++ b/tests/domain_driven_design/domain_modeling/test_batches.py @@ -13,31 +13,31 @@ def test_can_allocate_if_available_greater_than_required(): large_batch, small_line = make_batch_and_line("ELEGANT-LAMP", 20, 2) assert large_batch.can_allocate(small_line) assert large_batch.available_quantity == 20 - assert large_batch.allocate(small_line) == True + assert large_batch.allocate(small_line) assert large_batch.available_quantity == 18 def test_cannot_allocate_if_available_smaller_than_required(): small_batch, large_line = make_batch_and_line("ELEGANT-LAMP", 2, 20) - assert small_batch.can_allocate(large_line) is False + assert not small_batch.can_allocate(large_line) def test_can_allocate_if_available_equal_to_required(): batch, line = make_batch_and_line("ELEGANT-LAMP", 2, 2) assert batch.can_allocate(line) - assert batch.allocate(line) == True + assert batch.allocate(line) assert not batch.can_allocate(line) def test_cannot_allocate_if_skus_do_not_match(): batch = Batch("batch-001", "UNCOMFORTABLE-CHAIR", 100, eta=None) different_sku_line = OrderLine("order-123", "EXPENSIVE-TOASTER", 10) - assert batch.can_allocate(different_sku_line) is False + assert not batch.can_allocate(different_sku_line) def test_can_only_deallocated_allocated_lines(): batch, unallocated_line = make_batch_and_line("DECORATIVE-TRINKET", 20, 2) - assert batch.deallocate(unallocated_line) == False + assert not batch.deallocate(unallocated_line) batch.allocate(unallocated_line) assert batch.available_quantity == 18 - assert batch.deallocate(unallocated_line) == True + assert batch.deallocate(unallocated_line) diff --git a/tests/test_helpers/linked_list_helpers.py b/tests/test_helpers/linked_list_helpers.py index 4846d8d..1c9f61d 100644 --- a/tests/test_helpers/linked_list_helpers.py +++ b/tests/test_helpers/linked_list_helpers.py @@ -7,8 +7,8 @@ def list_to_linked_list(nums: list[int]) -> LinkedList: for i in range(1, len(nums)): new_node = LinkedList(nums[i]) - head.left_next = new_node - head = head.left_next + head.next = new_node + head = head.next return root @@ -18,6 +18,6 @@ def linked_list_to_list(root: LinkedList) -> list[int]: while cur: ans.append(cur.val) - cur = cur.left_next + cur = cur.next return ans