From 7cd8fe65413a89c76c7caf4f3b2d7ab719f1874d Mon Sep 17 00:00:00 2001 From: ph0sgene Date: Fri, 17 Oct 2025 09:29:40 +0000 Subject: [PATCH 01/43] Update README.md --- README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 53bf07e..0d718e2 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ -# Edderkop - -environment: poetry -code style: black - -built with - -- aiohttp -- squareup -- apscheduler +# Studspider + +environment: poetry +code style: black + +built with + +- aiohttp +- squareup +- apscheduler - aiosqlite \ No newline at end of file From 0c41ac2f84bde77a26599b73f3ae6e159ada320b Mon Sep 17 00:00:00 2001 From: ph0sgene Date: Fri, 17 Oct 2025 09:30:31 +0000 Subject: [PATCH 02/43] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 0d718e2..dfd8f32 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,10 @@ # Studspider environment: poetry + code style: black + built with - aiohttp From d9e64cf212728d56c78869f804072225901135c8 Mon Sep 17 00:00:00 2001 From: ph0sgene Date: Sun, 26 Oct 2025 22:44:35 +0000 Subject: [PATCH 03/43] update ignore before rebase --- .gitignore | 63 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 38 insertions(+), 25 deletions(-) diff --git a/.gitignore b/.gitignore index 7f82f1e..4a7cc4d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,25 +1,38 @@ -logs/ -.env -__pycache__/ -*.pyc -.vscode/ -.idea/ -.DS_Store -*.db -*.sqlite3 -*.log -poetry.lock -bin/build/ -.zip -snippet.py -doc/square-src/square-python-sdk-43.0.2.20250820/ - -doc/square-src/ - -doc/pw-inventory/pw-inventory.zip - -doc/pw-inventory/SQUARE_inventory_forautomation_2025-06-18-0139.csv - -doc/pw-inventory/square_inventory_export_2025-06-18-0137.xlsx - -square-src.code-workspace +logs/ +.env +__pycache__/ +*.pyc +.vscode/ +.idea/ +.DS_Store +*.db +*.sqlite3 +*.log +bin/build/ +.zip +snippet.py +doc/square-src/square-python-sdk-43.0.2.20250820/ + +doc/square-src/ + +doc/pw-inventory/pw-inventory.zip + +doc/pw-inventory/SQUARE_inventory_forautomation_2025-06-18-0139.csv + +doc/pw-inventory/square_inventory_export_2025-06-18-0137.xlsx + +square-src.code-workspace + +doc/pw-inventory/ + +studspider.db-journal +.venv +*.db +*.py[cod] +.web +.states +assets/external/ +__pycache__/ +win/ +.github/ +activate/ \ No newline at end of file From 46dda6166a1c5f53eb642e8370e56fca8728f3f5 Mon Sep 17 00:00:00 2001 From: C <0xicl33n@gmail.com> Date: Thu, 9 Oct 2025 12:49:27 -0700 Subject: [PATCH 04/43] refactor: Migrate build system to Hatch and integrate UV --- .gitignore | 2 ++ pyproject.toml | 65 ++++++++++++++++++++++++-------------------------- 2 files changed, 33 insertions(+), 34 deletions(-) diff --git a/.gitignore b/.gitignore index 4a7cc4d..aa8e739 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,8 @@ __pycache__/ *.db *.sqlite3 *.log +poetry.lock +uv.lock bin/build/ .zip snippet.py diff --git a/pyproject.toml b/pyproject.toml index ccfc4c8..440420f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,35 +1,32 @@ -[project] -name = "edderkop" -version = "0.1.0" -description = "" -authors = [ - {name = "CF", email = "chris-forst@pm.me"} -] -license = {text = "MIT"} -readme = "README.md" -requires-python = ">=3.13,<3.14" -dependencies = [ - "aiohttp (>=3.12.13,<4.0.0)", - "dotenv (>=0.9.9,<0.10.0)", - "pyinstaller (>=6.14.1,<7.0.0)", - "squareup(>=43.0.0.20250618,<44.0.0.0)", - "asyncio-throttle (>=1.0.2,<2.0.0)", - "requests (>=2.32.4,<3.0.0)", - "pytest (>=8.4.2,<9.0.0)", - "apscheduler (>=3.11.0,<4.0.0)", - "aiosqlite (>=0.21.0,<0.22.0)", -] - - -[build-system] -requires = ["poetry-core>=2.0.0,<3.0.0"] -build-backend = "poetry.core.masonry.api" - -[tool.poetry] -package-mode = false - -[tool.bricksetapi] -version = "0.1.0" - -[tool.abricksetapi] +[project] +name = "edderkop" +version = "0.1.0" +description = "" +authors = [{ name = "CF", email = "ph0sgene@pm.me" }] +requires-python = ">=3.13,<3.14" +readme = "README.md" +license = { text = "MIT" } +dependencies = [ + "aiohttp (>=3.12.13,<4.0.0)", + "dotenv (>=0.9.9,<0.10.0)", + "pyinstaller (>=6.14.1,<7.0.0)", + "squareup(>=43.0.0.20250618,<44.0.0.0)", + "asyncio-throttle (>=1.0.2,<2.0.0)", + "requests (>=2.32.4,<3.0.0)", + "pytest (>=8.4.2,<9.0.0)", + "apscheduler (>=3.11.0,<4.0.0)", + "aiosqlite (>=0.21.0,<0.22.0)", +] + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.uv] +package = false + +[tool.bricksetapi] +version = "0.1.0" + +[tool.abricksetapi] version = "0.1.0" \ No newline at end of file From 83769ce7db589d416c9d32e760da3d8eea6e4058 Mon Sep 17 00:00:00 2001 From: CF Date: Thu, 9 Oct 2025 12:54:32 -0700 Subject: [PATCH 05/43] rm 314t --- pyproject.toml | 62 +++++++++++++++++++++++++------------------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 440420f..1a16cbf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,32 +1,32 @@ -[project] -name = "edderkop" -version = "0.1.0" -description = "" -authors = [{ name = "CF", email = "ph0sgene@pm.me" }] -requires-python = ">=3.13,<3.14" -readme = "README.md" -license = { text = "MIT" } -dependencies = [ - "aiohttp (>=3.12.13,<4.0.0)", - "dotenv (>=0.9.9,<0.10.0)", - "pyinstaller (>=6.14.1,<7.0.0)", - "squareup(>=43.0.0.20250618,<44.0.0.0)", - "asyncio-throttle (>=1.0.2,<2.0.0)", - "requests (>=2.32.4,<3.0.0)", - "pytest (>=8.4.2,<9.0.0)", - "apscheduler (>=3.11.0,<4.0.0)", - "aiosqlite (>=0.21.0,<0.22.0)", -] - -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" - -[tool.uv] -package = false - -[tool.bricksetapi] -version = "0.1.0" - -[tool.abricksetapi] +[project] +name = "edderkop" +version = "0.1.0" +description = "" +authors = [{ name = "CF", email = "ph0sgene@pm.me" }] +requires-python = ">=3.13,<3.14" +readme = "README.md" +license = { text = "MIT" } +dependencies = [ + "aiohttp (>=3.12.13,<4.0.0)", + "dotenv (>=0.9.9,<0.10.0)", + "pyinstaller (>=6.14.1,<7.0.0)", + "squareup(>=43.0.0.20250618,<44.0.0.0)", + "asyncio-throttle (>=1.0.2,<2.0.0)", + "requests (>=2.32.4,<3.0.0)", + "pytest (>=8.4.2,<9.0.0)", + "apscheduler (>=3.11.0,<4.0.0)", + "aiosqlite (>=0.21.0,<0.22.0)", +] + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.uv] +package = false + +[tool.bricksetapi] +version = "0.1.0" + +[tool.abricksetapi] version = "0.1.0" \ No newline at end of file From 9b5d04183db736a0692c3e35e489435165dd06d5 Mon Sep 17 00:00:00 2001 From: C <0xicl33n@gmail.com> Date: Thu, 9 Oct 2025 13:33:51 -0700 Subject: [PATCH 06/43] added optional logging to brickset --- src/abricksetapi.py | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/src/abricksetapi.py b/src/abricksetapi.py index 5c4897e..2f7f7bc 100644 --- a/src/abricksetapi.py +++ b/src/abricksetapi.py @@ -17,7 +17,7 @@ dotenv.load_dotenv() class AsyncBricksetAPI: """Asynchronous client for the Brickset API""" - def __init__(self, debug: bool = False): + def __init__(self, debug: bool = False, logger=None): self.username = os.getenv("BRICKSET_USERNAME") self.password = os.getenv("BRICKSET_PASS") self.api_key = os.getenv("BRICKSET_API_KEY") @@ -25,6 +25,7 @@ class AsyncBricksetAPI: self.headers = {"UserAgent": "Edderkop/0.1"} self.throttler = Throttler(rate_limit=500, period=60) self.user_hash = None + self.logger = logger async def login(self) -> Dict[str, Any]: """Asynchronously authenticate with Brickset API""" @@ -41,10 +42,15 @@ class AsyncBricksetAPI: ) as response: if self.debug: print(f"[@DEBUG] Login response status: {response.status}") + if self.logger: + self.logger.debug(f"Login response status: {response.status}") data = await response.read() result = json.loads(data.decode("utf-8")) self.user_hash = result.get("hash") + + if self.logger: + self.logger.info("Successfully authenticated with Brickset API") return result async def get_set(self, set_number: str) -> Dict[str, Any]: @@ -73,13 +79,23 @@ class AsyncBricksetAPI: ) as response: if self.debug: print(f"[@DEBUG] Get set response status: {response.status}") + if self.logger: + self.logger.debug(f"Get set response status: {response.status}") if response.status == 200: data = await response.read() result = json.loads(data.decode("utf-8")) if not result.get("sets"): - raise ValueError(f"No data found for set {set_number}") + error_msg = f"No data found for set {set_number}" + if self.logger: + self.logger.error(error_msg) + raise ValueError(error_msg) + + if self.logger: + self.logger.info( + f"Successfully retrieved data for set {set_number}" + ) set_data = result["sets"][0] our_set = { @@ -111,11 +127,17 @@ class AsyncBricksetAPI: return our_set elif response.status == 500: - raise Exception( + error_msg = ( "Internal Server Error: Please check API key and parameters." ) + if self.logger: + self.logger.error(error_msg) + raise Exception(error_msg) else: - raise Exception(f"Unexpected response: {response.status}") + error_msg = f"Unexpected response: {response.status}" + if self.logger: + self.logger.error(error_msg) + raise Exception(error_msg) async def throttled_get_set(self, set_number: str) -> Dict[str, Any]: """Get set information with rate limiting""" From aa2dc74ff707c380b1e9fec5949cad2208959dc1 Mon Sep 17 00:00:00 2001 From: C <0xicl33n@gmail.com> Date: Thu, 9 Oct 2025 13:33:59 -0700 Subject: [PATCH 07/43] added rotating logs! epicsauce --- src/test_server.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/test_server.py b/src/test_server.py index bece01b..30e33f0 100644 --- a/src/test_server.py +++ b/src/test_server.py @@ -9,6 +9,7 @@ import dotenv import os import re import logging +from logging.handlers import RotatingFileHandler from pathlib import Path Path("./logs/").mkdir(parents=True, exist_ok=True) @@ -18,7 +19,6 @@ import get_sq_env import asquare as sq from abricksetapi import AsyncBricksetAPI -lego = AsyncBricksetAPI(debug=True) dotenv.load_dotenv() SQUARE_LEGO_CAT_ID = os.environ.get("SQUARE_LEGO_CAT_ID") @@ -26,16 +26,23 @@ SQUARE_LEGO_CAT_ID = os.environ.get("SQUARE_LEGO_CAT_ID") from asyncio_throttle.throttler import Throttler -logger = logging.getLogger("aiohttp.access") -logging.basicConfig( - filename="./logs/edderkop.log", encoding="utf-8", level=logging.DEBUG -) +# set up logger +logger = logging.getLogger("edderkop") +logger.setLevel(logging.DEBUG) +handler = RotatingFileHandler("./logs/", maxBytes=1024, backupCount=5) +logger.addHandler(handler) + +lego = AsyncBricksetAPI(debug=True, logger=logger) + +# logging.basicConfig( +# filename="./logs/edderkop.log", encoding="utf-8", level=logging.DEBUG +# ) UA = re.compile(r"^kopling\/?", re.IGNORECASE) async def run_update(): - set_list = await sq.acat2id(SQUARE_LEGO_CAT_ID) + set_list = await sq.acat2id(SQUARE_LEGO_CAT_ID) # type: ignore for l_set in set_list: print(f"[@DEBUG]{l_set}") l_setinfo = await lego.throttled_get_set(l_set["number"]) From 3683a6ffc17ff13f6048a67c89a55890cc250f6d Mon Sep 17 00:00:00 2001 From: C <0xicl33n@gmail.com> Date: Thu, 9 Oct 2025 17:13:56 -0700 Subject: [PATCH 08/43] handle no numbers in set name --- src/asquare.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/asquare.py b/src/asquare.py index c172348..1059b98 100644 --- a/src/asquare.py +++ b/src/asquare.py @@ -103,7 +103,11 @@ async def acat2id(cat_id: str): { "id": obj["id"], "name": obj.get("item_data", {}).get("name", "Unknown"), - "number": get_set_number(obj.get("item_data", {}).get("name", "")) + "number": None if not obj.get("item_data", {}).get("name", "") else ( + get_set_number(obj.get("item_data", {}).get("name", "")) + if re.findall(r"\d{5}", obj.get("item_data", {}).get("name", "")) + else None + ) } for obj in result_json.get("objects", []) ] From 7f5210ab571d3337ee4f747adb3c69204a365aa5 Mon Sep 17 00:00:00 2001 From: C <0xicl33n@gmail.com> Date: Thu, 9 Oct 2025 17:14:37 -0700 Subject: [PATCH 09/43] testing subclassed asquare --- src/asquareclass.py | 218 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 218 insertions(+) create mode 100644 src/asquareclass.py diff --git a/src/asquareclass.py b/src/asquareclass.py new file mode 100644 index 0000000..b9407b4 --- /dev/null +++ b/src/asquareclass.py @@ -0,0 +1,218 @@ +import os +import json +import asyncio +import re +from typing import List, Dict, Any, Optional +from square import AsyncSquare +import get_sq_env + + +class AsyncSquareClient: + """Asynchronous client for the Square API with focus on catalog operations""" + + def __init__( + self, + token: Optional[str] = None, + environment: Optional[str] = None, + logger=None, + ): + """ + Initialize the AsyncSquareClient. + + Args: + token (str, optional): Square API token. If not provided, fetches from environment. + environment (str, optional): Square environment (sandbox/production). If not provided, fetches from environment. + logger: Optional logger object for logging operations. + """ + self.token = token or get_sq_env.SQUARE_TOKEN + self.environment = environment or get_sq_env.environment + self.lego_cat_id = get_sq_env.LEGO_CAT_ID + self.client = AsyncSquare(token=self.token, environment=self.environment) # type: ignore + self.logger = logger + + @staticmethod + def get_set_number(text: str) -> int: + """ + Extract a five-digit LEGO set number from a string. + + Args: + text (str): The text to search for a set number. + + Returns: + int: The extracted set number. + + Raises: + ValueError: If no five-digit number is found. + """ + numbers = re.findall(r"\d{5}", text) + if not numbers: + raise ValueError(f"No five-digit number found in string {text}") + return int(numbers[0]) + + async def get_item(self, sku: str) -> Dict[str, str]: + """ + Asynchronously retrieves item details from Square Catalog using a SKU. + + Args: + sku (str): The SKU or text filter to search for the item. + + Returns: + dict: A dictionary containing the item's name and ID. + + Raises: + IndexError: If no items are found matching the SKU. + Exception: If there is an error communicating with the Square API. + """ + if self.logger: + self.logger.debug(f"Searching for item with SKU: {sku}") + + result = await self.client.catalog.search_items(text_filter=sku, limit=1) + result_json = json.loads(result.model_dump_json()) + + try: + name = result_json["items"][0]["item_data"]["name"] + itm_id = result_json["items"][0]["id"] + + if self.logger: + self.logger.info(f"Found item: {name} with ID: {itm_id}") + + return {"name": name, "id": itm_id} + except (KeyError, IndexError) as e: + if self.logger: + self.logger.error(f"Error retrieving item with SKU {sku}: {str(e)}") + raise + + async def update_item(self, lego_set: Dict[str, Any]) -> bool: + """ + Asynchronously updates an item in the Square catalog based on provided LEGO set information. + + Args: + lego_set (dict): A dictionary containing LEGO set details including: + - sets: List containing set information + - pw_name: PriceWise system name + - set_adjustredprice: Adjusted retail price + + Returns: + bool: True if update was successful. + + Raises: + KeyError: If required keys are missing from input dictionary. + Exception: For Square API communication errors. + """ + if self.logger: + self.logger.debug( + f"Updating item with set info: {lego_set['sets'][0]['number']}" + ) + + try: + set_name = lego_set["sets"][0]["name"] + set_number = lego_set["sets"][0]["number"] + set_upc = lego_set["sets"][0]["barcode"]["UPC"] + set_image = lego_set["sets"][0]["image"]["thumbnailURL"] + pw_name = lego_set["pw_name"] + set_adjustedprice = lego_set["set_adjustredprice"] + + result = await self.client.catalog.search_items( + text_filter=set_number, limit=1 + ) + itm_id = json.loads(result.model_dump_json())["items"][0]["id"] + + if self.logger: + self.logger.info(f"Successfully updated item {set_number}") + + # TODO: Implement actual update logic here + # await self.client.catalog.object.upsert() + return True + + except Exception as e: + if self.logger: + self.logger.error(f"Error updating item: {str(e)}") + raise + + async def get_category_items( + self, cat_id: Optional[str] = None + ) -> List[Dict[str, Any]]: + """ + Return a list of items within the specified category ID. + + Args: + cat_id (str, optional): The category ID to search for items. + If not provided, uses the default LEGO category ID. + + Returns: + list[dict]: A list of dictionaries, each containing: + - 'id': The item's ID + - 'name': The item's name + - 'number': The LEGO set number + """ + category_id = cat_id or self.lego_cat_id + + if self.logger: + self.logger.debug(f"Fetching items for category: {category_id}") + + try: + # Create a properly structured query for the Square API + search_request = { + "object_types": ["ITEM"], + "query": { + "exact_query": { + "attribute_name": "category_id", + "attribute_value": category_id, + } + }, + } + + result = await self.client.catalog.search(**search_request) + result_json = json.loads(result.model_dump_json()) + + items = [ + { + "id": obj["id"], + "name": obj.get("item_data", {}).get("name", "Unknown"), + "number": self.get_set_number( + obj.get("item_data", {}).get("name", "") + ), + } + for obj in result_json.get("objects", []) + ] + + if self.logger: + self.logger.info(f"Found {len(items)} items in category {category_id}") + + return items + + except Exception as e: + if self.logger: + self.logger.error(f"Error fetching category items: {str(e)}") + raise + + +# Provide acat2id as a module-level function for backward compatibility +async def acat2id(cat_id: str) -> List[Dict[str, Any]]: + """ + Backward compatibility function for getting category items. + + Args: + cat_id (str): The category ID to search for items. + + Returns: + list[dict]: A list of dictionaries containing item information. + """ + client = AsyncSquareClient() + return await client.get_category_items(cat_id) + + +async def test() -> None: + """Test function to demonstrate class usage""" + client = AsyncSquareClient() + print(f"@[DEBUG] ENV:\t{client.environment}") + print(f"@[DEBUG] CATID:\t{client.lego_cat_id}") + + result = await client.get_category_items() + print("\nOBJECTS IN LEGO CATEGORY:") + for item in result: + print(f"ID: {item['id']}, Name: {item['name']}") + + +if __name__ == "__main__": + asyncio.run(test()) From 43e19b200dcd770e4945eb7aa02e23c53b08ee8f Mon Sep 17 00:00:00 2001 From: C <0xicl33n@gmail.com> Date: Thu, 9 Oct 2025 17:14:53 -0700 Subject: [PATCH 10/43] add verbosity to startup --- src/test_server.py | 37 +++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/src/test_server.py b/src/test_server.py index 30e33f0..a88fdd8 100644 --- a/src/test_server.py +++ b/src/test_server.py @@ -11,42 +11,52 @@ import re import logging from logging.handlers import RotatingFileHandler from pathlib import Path - +print("Creating log directory if it doesn't exist...") Path("./logs/").mkdir(parents=True, exist_ok=True) import banner import get_sq_env + import asquare as sq + +# from asquareclass import AsyncSquareClient # type: ignore from abricksetapi import AsyncBricksetAPI - +print("Loading environment variables...") dotenv.load_dotenv() SQUARE_LEGO_CAT_ID = os.environ.get("SQUARE_LEGO_CAT_ID") - from asyncio_throttle.throttler import Throttler # set up logger +print("Setting up logger...") logger = logging.getLogger("edderkop") logger.setLevel(logging.DEBUG) -handler = RotatingFileHandler("./logs/", maxBytes=1024, backupCount=5) +handler = RotatingFileHandler("./logs/edderkop.log", maxBytes=125000, backupCount=5) logger.addHandler(handler) lego = AsyncBricksetAPI(debug=True, logger=logger) - +# sq = AsyncSquareClient(logger=logger) # logging.basicConfig( # filename="./logs/edderkop.log", encoding="utf-8", level=logging.DEBUG # ) - +print("Setting up rate limiter...") +print("Doing RE things...") UA = re.compile(r"^kopling\/?", re.IGNORECASE) async def run_update(): - set_list = await sq.acat2id(SQUARE_LEGO_CAT_ID) # type: ignore - for l_set in set_list: + set_list = await sq.acat2id(SQUARE_LEGO_CAT_ID) + print(f"[@DEBUG]\t {set_list}") # type: ignore + """for l_set in set_list: print(f"[@DEBUG]{l_set}") - l_setinfo = await lego.throttled_get_set(l_set["number"]) - print(f"[@DEBUG]{l_setinfo}") + try: + l_setinfo = await lego.throttled_get_set(l_set["number"]) + except Exception as e: + print(f"Error fetching set\t: {l_set}: {str(e)}") + continue + + print(f"[@DEBUG]{l_setinfo}")""" # await sq.update_itm(l_set, l_setinfo) @@ -101,11 +111,9 @@ async def request_handler(request: web.Request) -> web.Response: # Process the command if decoded_cmd == "RUNUPDATE": - # Add your command handling logic here - await run_update() response_data = {"status": "success", "message": "Update started"} + await run_update() logging.info(f"{time()}\tUpdate command processed successfully") - return web.json_response(response_data) else: return web.Response(status=400, text=f"Unknown command: {decoded_cmd}") @@ -118,7 +126,8 @@ async def request_handler(request: web.Request) -> web.Response: print(f"Request handler error: {str(e)}") return web.Response(status=500, text="Internal server error") - +print("Starting web server...") +os.system('cls' if os.name == 'nt' else 'clear') app = web.Application() app.router.add_post("/", request_handler) From 60b687305ec1730023a5fdeecae40751e859032c Mon Sep 17 00:00:00 2001 From: C <0xicl33n@gmail.com> Date: Thu, 9 Oct 2025 17:14:59 -0700 Subject: [PATCH 11/43] uv refresh --- pyproject.toml | 64 +++++++++++++++++++++++++------------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 1a16cbf..1baabfc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,32 +1,32 @@ -[project] -name = "edderkop" -version = "0.1.0" -description = "" -authors = [{ name = "CF", email = "ph0sgene@pm.me" }] -requires-python = ">=3.13,<3.14" -readme = "README.md" -license = { text = "MIT" } -dependencies = [ - "aiohttp (>=3.12.13,<4.0.0)", - "dotenv (>=0.9.9,<0.10.0)", - "pyinstaller (>=6.14.1,<7.0.0)", - "squareup(>=43.0.0.20250618,<44.0.0.0)", - "asyncio-throttle (>=1.0.2,<2.0.0)", - "requests (>=2.32.4,<3.0.0)", - "pytest (>=8.4.2,<9.0.0)", - "apscheduler (>=3.11.0,<4.0.0)", - "aiosqlite (>=0.21.0,<0.22.0)", -] - -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" - -[tool.uv] -package = false - -[tool.bricksetapi] -version = "0.1.0" - -[tool.abricksetapi] -version = "0.1.0" \ No newline at end of file +[project] +name = "edderkop" +version = "0.1.0" +description = "" +authors = [{ name = "CF", email = "ph0sgene@pm.me" }] +requires-python = ">=3.13,<3.14" +readme = "README.md" +license = { text = "MIT" } +dependencies = [ + "aiohttp (>=3.12.13,<4.0.0)", + "dotenv (>=0.9.9,<0.10.0)", + "pyinstaller (>=6.14.1,<7.0.0)", + "squareup(>=43.0.0.20250618,<44.0.0.0)", + "asyncio-throttle (>=1.0.2,<2.0.0)", + "requests (>=2.32.4,<3.0.0)", + "pytest (>=8.4.2,<9.0.0)", + "apscheduler (>=3.11.0,<4.0.0)", + "aiosqlite (>=0.21.0,<0.22.0)", +] + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.uv] +package = false + +[tool.bricksetapi] +version = "0.1.0" + +[tool.abricksetapi] +version = "0.1.0" From 72bac66682d1c62fe3d0b6b299c7695067087fcb Mon Sep 17 00:00:00 2001 From: C <0xicl33n@gmail.com> Date: Thu, 9 Oct 2025 17:15:12 -0700 Subject: [PATCH 12/43] add stuff todo for fixing database --- TODO.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/TODO.md b/TODO.md index d9bbabf..9eb4a53 100644 --- a/TODO.md +++ b/TODO.md @@ -1,4 +1,11 @@ - glue shit together - test sq - test inserting brickowl into sq -- test kopling \ No newline at end of file +- test kopling + + +square inv needs lego set category changed + +key lights +pastel wrist things +some sets \ No newline at end of file From bef441db882f1c220c14f9d0b40b0cab970270ac Mon Sep 17 00:00:00 2001 From: C <0xicl33n@gmail.com> Date: Fri, 10 Oct 2025 11:05:14 -0700 Subject: [PATCH 13/43] i guess this should be versioned --- .gitignore | 2 - uv.lock | 742 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 742 insertions(+), 2 deletions(-) create mode 100644 uv.lock diff --git a/.gitignore b/.gitignore index aa8e739..4a7cc4d 100644 --- a/.gitignore +++ b/.gitignore @@ -8,8 +8,6 @@ __pycache__/ *.db *.sqlite3 *.log -poetry.lock -uv.lock bin/build/ .zip snippet.py diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..071a7fb --- /dev/null +++ b/uv.lock @@ -0,0 +1,742 @@ +version = 1 +revision = 3 +requires-python = "==3.13.*" + +[[package]] +name = "aiohappyeyeballs" +version = "2.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/26/30/f84a107a9c4331c14b2b586036f40965c128aa4fee4dda5d3d51cb14ad54/aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558", size = 22760, upload-time = "2025-03-12T01:42:48.764Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8", size = 15265, upload-time = "2025-03-12T01:42:47.083Z" }, +] + +[[package]] +name = "aiohttp" +version = "3.12.15" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohappyeyeballs" }, + { name = "aiosignal" }, + { name = "attrs" }, + { name = "frozenlist" }, + { name = "multidict" }, + { name = "propcache" }, + { name = "yarl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9b/e7/d92a237d8802ca88483906c388f7c201bbe96cd80a165ffd0ac2f6a8d59f/aiohttp-3.12.15.tar.gz", hash = "sha256:4fc61385e9c98d72fcdf47e6dd81833f47b2f77c114c29cd64a361be57a763a2", size = 7823716, upload-time = "2025-07-29T05:52:32.215Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f2/33/918091abcf102e39d15aba2476ad9e7bd35ddb190dcdd43a854000d3da0d/aiohttp-3.12.15-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9f922ffd05034d439dde1c77a20461cf4a1b0831e6caa26151fe7aa8aaebc315", size = 696741, upload-time = "2025-07-29T05:51:19.021Z" }, + { url = "https://files.pythonhosted.org/packages/b5/2a/7495a81e39a998e400f3ecdd44a62107254803d1681d9189be5c2e4530cd/aiohttp-3.12.15-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2ee8a8ac39ce45f3e55663891d4b1d15598c157b4d494a4613e704c8b43112cd", size = 474407, upload-time = "2025-07-29T05:51:21.165Z" }, + { url = "https://files.pythonhosted.org/packages/49/fc/a9576ab4be2dcbd0f73ee8675d16c707cfc12d5ee80ccf4015ba543480c9/aiohttp-3.12.15-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3eae49032c29d356b94eee45a3f39fdf4b0814b397638c2f718e96cfadf4c4e4", size = 466703, upload-time = "2025-07-29T05:51:22.948Z" }, + { url = "https://files.pythonhosted.org/packages/09/2f/d4bcc8448cf536b2b54eed48f19682031ad182faa3a3fee54ebe5b156387/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b97752ff12cc12f46a9b20327104448042fce5c33a624f88c18f66f9368091c7", size = 1705532, upload-time = "2025-07-29T05:51:25.211Z" }, + { url = "https://files.pythonhosted.org/packages/f1/f3/59406396083f8b489261e3c011aa8aee9df360a96ac8fa5c2e7e1b8f0466/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:894261472691d6fe76ebb7fcf2e5870a2ac284c7406ddc95823c8598a1390f0d", size = 1686794, upload-time = "2025-07-29T05:51:27.145Z" }, + { url = "https://files.pythonhosted.org/packages/dc/71/164d194993a8d114ee5656c3b7ae9c12ceee7040d076bf7b32fb98a8c5c6/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5fa5d9eb82ce98959fc1031c28198b431b4d9396894f385cb63f1e2f3f20ca6b", size = 1738865, upload-time = "2025-07-29T05:51:29.366Z" }, + { url = "https://files.pythonhosted.org/packages/1c/00/d198461b699188a93ead39cb458554d9f0f69879b95078dce416d3209b54/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0fa751efb11a541f57db59c1dd821bec09031e01452b2b6217319b3a1f34f3d", size = 1788238, upload-time = "2025-07-29T05:51:31.285Z" }, + { url = "https://files.pythonhosted.org/packages/85/b8/9e7175e1fa0ac8e56baa83bf3c214823ce250d0028955dfb23f43d5e61fd/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5346b93e62ab51ee2a9d68e8f73c7cf96ffb73568a23e683f931e52450e4148d", size = 1710566, upload-time = "2025-07-29T05:51:33.219Z" }, + { url = "https://files.pythonhosted.org/packages/59/e4/16a8eac9df39b48ae102ec030fa9f726d3570732e46ba0c592aeeb507b93/aiohttp-3.12.15-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:049ec0360f939cd164ecbfd2873eaa432613d5e77d6b04535e3d1fbae5a9e645", size = 1624270, upload-time = "2025-07-29T05:51:35.195Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f8/cd84dee7b6ace0740908fd0af170f9fab50c2a41ccbc3806aabcb1050141/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b52dcf013b57464b6d1e51b627adfd69a8053e84b7103a7cd49c030f9ca44461", size = 1677294, upload-time = "2025-07-29T05:51:37.215Z" }, + { url = "https://files.pythonhosted.org/packages/ce/42/d0f1f85e50d401eccd12bf85c46ba84f947a84839c8a1c2c5f6e8ab1eb50/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:9b2af240143dd2765e0fb661fd0361a1b469cab235039ea57663cda087250ea9", size = 1708958, upload-time = "2025-07-29T05:51:39.328Z" }, + { url = "https://files.pythonhosted.org/packages/d5/6b/f6fa6c5790fb602538483aa5a1b86fcbad66244997e5230d88f9412ef24c/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ac77f709a2cde2cc71257ab2d8c74dd157c67a0558a0d2799d5d571b4c63d44d", size = 1651553, upload-time = "2025-07-29T05:51:41.356Z" }, + { url = "https://files.pythonhosted.org/packages/04/36/a6d36ad545fa12e61d11d1932eef273928b0495e6a576eb2af04297fdd3c/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:47f6b962246f0a774fbd3b6b7be25d59b06fdb2f164cf2513097998fc6a29693", size = 1727688, upload-time = "2025-07-29T05:51:43.452Z" }, + { url = "https://files.pythonhosted.org/packages/aa/c8/f195e5e06608a97a4e52c5d41c7927301bf757a8e8bb5bbf8cef6c314961/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:760fb7db442f284996e39cf9915a94492e1896baac44f06ae551974907922b64", size = 1761157, upload-time = "2025-07-29T05:51:45.643Z" }, + { url = "https://files.pythonhosted.org/packages/05/6a/ea199e61b67f25ba688d3ce93f63b49b0a4e3b3d380f03971b4646412fc6/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad702e57dc385cae679c39d318def49aef754455f237499d5b99bea4ef582e51", size = 1710050, upload-time = "2025-07-29T05:51:48.203Z" }, + { url = "https://files.pythonhosted.org/packages/b4/2e/ffeb7f6256b33635c29dbed29a22a723ff2dd7401fff42ea60cf2060abfb/aiohttp-3.12.15-cp313-cp313-win32.whl", hash = "sha256:f813c3e9032331024de2eb2e32a88d86afb69291fbc37a3a3ae81cc9917fb3d0", size = 422647, upload-time = "2025-07-29T05:51:50.718Z" }, + { url = "https://files.pythonhosted.org/packages/1b/8e/78ee35774201f38d5e1ba079c9958f7629b1fd079459aea9467441dbfbf5/aiohttp-3.12.15-cp313-cp313-win_amd64.whl", hash = "sha256:1a649001580bdb37c6fdb1bebbd7e3bc688e8ec2b5c6f52edbb664662b17dc84", size = 449067, upload-time = "2025-07-29T05:51:52.549Z" }, +] + +[[package]] +name = "aiosignal" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "frozenlist" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/62/06741b579156360248d1ec624842ad0edf697050bbaf7c3e46394e106ad1/aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7", size = 25007, upload-time = "2025-07-03T22:54:43.528Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" }, +] + +[[package]] +name = "aiosqlite" +version = "0.21.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/13/7d/8bca2bf9a247c2c5dfeec1d7a5f40db6518f88d314b8bca9da29670d2671/aiosqlite-0.21.0.tar.gz", hash = "sha256:131bb8056daa3bc875608c631c678cda73922a2d4ba8aec373b19f18c17e7aa3", size = 13454, upload-time = "2025-02-03T07:30:16.235Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f5/10/6c25ed6de94c49f88a91fa5018cb4c0f3625f31d5be9f771ebe5cc7cd506/aiosqlite-0.21.0-py3-none-any.whl", hash = "sha256:2549cf4057f95f53dcba16f2b64e8e2791d7e1adedb13197dd8ed77bb226d7d0", size = 15792, upload-time = "2025-02-03T07:30:13.6Z" }, +] + +[[package]] +name = "altgraph" +version = "0.17.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/de/a8/7145824cf0b9e3c28046520480f207df47e927df83aa9555fb47f8505922/altgraph-0.17.4.tar.gz", hash = "sha256:1b5afbb98f6c4dcadb2e2ae6ab9fa994bbb8c1d75f4fa96d340f9437ae454406", size = 48418, upload-time = "2023-09-25T09:04:52.164Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/3f/3bc3f1d83f6e4a7fcb834d3720544ca597590425be5ba9db032b2bf322a2/altgraph-0.17.4-py2.py3-none-any.whl", hash = "sha256:642743b4750de17e655e6711601b077bc6598dbfa3ba5fa2b2a35ce12b508dff", size = 21212, upload-time = "2023-09-25T09:04:50.691Z" }, +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + +[[package]] +name = "anyio" +version = "4.10.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "sniffio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f1/b4/636b3b65173d3ce9a38ef5f0522789614e590dab6a8d505340a4efe4c567/anyio-4.10.0.tar.gz", hash = "sha256:3f3fae35c96039744587aa5b8371e7e8e603c0702999535961dd336026973ba6", size = 213252, upload-time = "2025-08-04T08:54:26.451Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6f/12/e5e0282d673bb9746bacfb6e2dba8719989d3660cdb2ea79aee9a9651afb/anyio-4.10.0-py3-none-any.whl", hash = "sha256:60e474ac86736bbfd6f210f7a61218939c318f43f9972497381f1c5e930ed3d1", size = 107213, upload-time = "2025-08-04T08:54:24.882Z" }, +] + +[[package]] +name = "apscheduler" +version = "3.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "tzlocal" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4e/00/6d6814ddc19be2df62c8c898c4df6b5b1914f3bd024b780028caa392d186/apscheduler-3.11.0.tar.gz", hash = "sha256:4c622d250b0955a65d5d0eb91c33e6d43fd879834bf541e0a18661ae60460133", size = 107347, upload-time = "2024-11-24T19:39:26.463Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/ae/9a053dd9229c0fde6b1f1f33f609ccff1ee79ddda364c756a924c6d8563b/APScheduler-3.11.0-py3-none-any.whl", hash = "sha256:fc134ca32e50f5eadcc4938e3a4545ab19131435e851abb40b34d63d5141c6da", size = 64004, upload-time = "2024-11-24T19:39:24.442Z" }, +] + +[[package]] +name = "asyncio-throttle" +version = "1.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/6f/0e2d42c0e95d50edf63147b8a742703061945e02760f25d6a0e8f028ccb0/asyncio-throttle-1.0.2.tar.gz", hash = "sha256:2675282e99d9129ecc446f917e174bc205c65e36c602aa18603b4948567fcbd4", size = 3775, upload-time = "2021-04-07T13:37:50.114Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f6/6a/d18c93722fb56dc1ffed5fb7e7fcff4f8031f80f84c5cc0427363a036b0c/asyncio_throttle-1.0.2-py3-none-any.whl", hash = "sha256:4d4c1eb3250f735f59ce842d8d92cd2927c008bd52008797ba030b5787c41f3b", size = 4079, upload-time = "2021-04-07T13:37:48.63Z" }, +] + +[[package]] +name = "attrs" +version = "25.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032, upload-time = "2025-03-13T11:10:22.779Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815, upload-time = "2025-03-13T11:10:21.14Z" }, +] + +[[package]] +name = "beautifulsoup4" +version = "4.14.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "soupsieve" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/77/e9/df2358efd7659577435e2177bfa69cba6c33216681af51a707193dec162a/beautifulsoup4-4.14.2.tar.gz", hash = "sha256:2a98ab9f944a11acee9cc848508ec28d9228abfd522ef0fad6a02a72e0ded69e", size = 625822, upload-time = "2025-09-29T10:05:42.613Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/fe/3aed5d0be4d404d12d36ab97e2f1791424d9ca39c2f754a6285d59a3b01d/beautifulsoup4-4.14.2-py3-none-any.whl", hash = "sha256:5ef6fa3a8cbece8488d66985560f97ed091e22bbc4e9c2338508a9d5de6d4515", size = 106392, upload-time = "2025-09-29T10:05:43.771Z" }, +] + +[[package]] +name = "bs4" +version = "0.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "beautifulsoup4" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/aa/4acaf814ff901145da37332e05bb510452ebed97bc9602695059dd46ef39/bs4-0.0.2.tar.gz", hash = "sha256:a48685c58f50fe127722417bae83fe6badf500d54b55f7e39ffe43b798653925", size = 698, upload-time = "2024-01-17T18:15:47.371Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/bb/bf7aab772a159614954d84aa832c129624ba6c32faa559dfb200a534e50b/bs4-0.0.2-py2.py3-none-any.whl", hash = "sha256:abf8742c0805ef7f662dce4b51cca104cffe52b835238afc169142ab9b3fbccc", size = 1189, upload-time = "2024-01-17T18:15:48.613Z" }, +] + +[[package]] +name = "certifi" +version = "2025.8.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/67/960ebe6bf230a96cda2e0abcf73af550ec4f090005363542f0765df162e0/certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407", size = 162386, upload-time = "2025-08-03T03:07:47.08Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/48/1549795ba7742c948d2ad169c1c8cdbae65bc450d6cd753d124b17c8cd32/certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5", size = 161216, upload-time = "2025-08-03T03:07:45.777Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/83/2d/5fd176ceb9b2fc619e63405525573493ca23441330fcdaee6bef9460e924/charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14", size = 122371, upload-time = "2025-08-09T07:57:28.46Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/65/ca/2135ac97709b400c7654b4b764daf5c5567c2da45a30cdd20f9eefe2d658/charset_normalizer-3.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe", size = 205326, upload-time = "2025-08-09T07:56:24.721Z" }, + { url = "https://files.pythonhosted.org/packages/71/11/98a04c3c97dd34e49c7d247083af03645ca3730809a5509443f3c37f7c99/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8", size = 146008, upload-time = "2025-08-09T07:56:26.004Z" }, + { url = "https://files.pythonhosted.org/packages/60/f5/4659a4cb3c4ec146bec80c32d8bb16033752574c20b1252ee842a95d1a1e/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9", size = 159196, upload-time = "2025-08-09T07:56:27.25Z" }, + { url = "https://files.pythonhosted.org/packages/86/9e/f552f7a00611f168b9a5865a1414179b2c6de8235a4fa40189f6f79a1753/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31", size = 156819, upload-time = "2025-08-09T07:56:28.515Z" }, + { url = "https://files.pythonhosted.org/packages/7e/95/42aa2156235cbc8fa61208aded06ef46111c4d3f0de233107b3f38631803/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f", size = 151350, upload-time = "2025-08-09T07:56:29.716Z" }, + { url = "https://files.pythonhosted.org/packages/c2/a9/3865b02c56f300a6f94fc631ef54f0a8a29da74fb45a773dfd3dcd380af7/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927", size = 148644, upload-time = "2025-08-09T07:56:30.984Z" }, + { url = "https://files.pythonhosted.org/packages/77/d9/cbcf1a2a5c7d7856f11e7ac2d782aec12bdfea60d104e60e0aa1c97849dc/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9", size = 160468, upload-time = "2025-08-09T07:56:32.252Z" }, + { url = "https://files.pythonhosted.org/packages/f6/42/6f45efee8697b89fda4d50580f292b8f7f9306cb2971d4b53f8914e4d890/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5", size = 158187, upload-time = "2025-08-09T07:56:33.481Z" }, + { url = "https://files.pythonhosted.org/packages/70/99/f1c3bdcfaa9c45b3ce96f70b14f070411366fa19549c1d4832c935d8e2c3/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc", size = 152699, upload-time = "2025-08-09T07:56:34.739Z" }, + { url = "https://files.pythonhosted.org/packages/a3/ad/b0081f2f99a4b194bcbb1934ef3b12aa4d9702ced80a37026b7607c72e58/charset_normalizer-3.4.3-cp313-cp313-win32.whl", hash = "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce", size = 99580, upload-time = "2025-08-09T07:56:35.981Z" }, + { url = "https://files.pythonhosted.org/packages/9a/8f/ae790790c7b64f925e5c953b924aaa42a243fb778fed9e41f147b2a5715a/charset_normalizer-3.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef", size = 107366, upload-time = "2025-08-09T07:56:37.339Z" }, + { url = "https://files.pythonhosted.org/packages/8a/1f/f041989e93b001bc4e44bb1669ccdcf54d3f00e628229a85b08d330615c5/charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a", size = 53175, upload-time = "2025-08-09T07:57:26.864Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "dotenv" +version = "0.9.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dotenv" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/b2/b7/545d2c10c1fc15e48653c91efde329a790f2eecfbbf2bd16003b5db2bab0/dotenv-0.9.9-py2.py3-none-any.whl", hash = "sha256:29cf74a087b31dafdb5a446b6d7e11cbce8ed2741540e2339c69fbef92c94ce9", size = 1892, upload-time = "2025-02-19T22:15:01.647Z" }, +] + +[[package]] +name = "edderkop" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "aiohttp" }, + { name = "aiosqlite" }, + { name = "apscheduler" }, + { name = "asyncio-throttle" }, + { name = "bs4" }, + { name = "dotenv" }, + { name = "pyinstaller" }, + { name = "pytest" }, + { name = "requests" }, + { name = "squareup" }, +] + +[package.metadata] +requires-dist = [ + { name = "aiohttp", specifier = ">=3.12.13,<4.0.0" }, + { name = "aiosqlite", specifier = ">=0.21.0,<0.22.0" }, + { name = "apscheduler", specifier = ">=3.11.0,<4.0.0" }, + { name = "asyncio-throttle", specifier = ">=1.0.2,<2.0.0" }, + { name = "bs4", specifier = ">=0.0.2" }, + { name = "dotenv", specifier = ">=0.9.9,<0.10.0" }, + { name = "pyinstaller", specifier = ">=6.14.1,<7.0.0" }, + { name = "pytest", specifier = ">=8.4.2,<9.0.0" }, + { name = "requests", specifier = ">=2.32.4,<3.0.0" }, + { name = "squareup", specifier = ">=43.0.0.20250618,<44.0.0.0" }, +] + +[[package]] +name = "frozenlist" +version = "1.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/79/b1/b64018016eeb087db503b038296fd782586432b9c077fc5c7839e9cb6ef6/frozenlist-1.7.0.tar.gz", hash = "sha256:2e310d81923c2437ea8670467121cc3e9b0f76d3043cc1d2331d56c7fb7a3a8f", size = 45078, upload-time = "2025-06-09T23:02:35.538Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/24/90/6b2cebdabdbd50367273c20ff6b57a3dfa89bd0762de02c3a1eb42cb6462/frozenlist-1.7.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee80eeda5e2a4e660651370ebffd1286542b67e268aa1ac8d6dbe973120ef7ee", size = 79791, upload-time = "2025-06-09T23:01:09.368Z" }, + { url = "https://files.pythonhosted.org/packages/83/2e/5b70b6a3325363293fe5fc3ae74cdcbc3e996c2a11dde2fd9f1fb0776d19/frozenlist-1.7.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d1a81c85417b914139e3a9b995d4a1c84559afc839a93cf2cb7f15e6e5f6ed2d", size = 47165, upload-time = "2025-06-09T23:01:10.653Z" }, + { url = "https://files.pythonhosted.org/packages/f4/25/a0895c99270ca6966110f4ad98e87e5662eab416a17e7fd53c364bf8b954/frozenlist-1.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cbb65198a9132ebc334f237d7b0df163e4de83fb4f2bdfe46c1e654bdb0c5d43", size = 45881, upload-time = "2025-06-09T23:01:12.296Z" }, + { url = "https://files.pythonhosted.org/packages/19/7c/71bb0bbe0832793c601fff68cd0cf6143753d0c667f9aec93d3c323f4b55/frozenlist-1.7.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dab46c723eeb2c255a64f9dc05b8dd601fde66d6b19cdb82b2e09cc6ff8d8b5d", size = 232409, upload-time = "2025-06-09T23:01:13.641Z" }, + { url = "https://files.pythonhosted.org/packages/c0/45/ed2798718910fe6eb3ba574082aaceff4528e6323f9a8570be0f7028d8e9/frozenlist-1.7.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6aeac207a759d0dedd2e40745575ae32ab30926ff4fa49b1635def65806fddee", size = 225132, upload-time = "2025-06-09T23:01:15.264Z" }, + { url = "https://files.pythonhosted.org/packages/ba/e2/8417ae0f8eacb1d071d4950f32f229aa6bf68ab69aab797b72a07ea68d4f/frozenlist-1.7.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bd8c4e58ad14b4fa7802b8be49d47993182fdd4023393899632c88fd8cd994eb", size = 237638, upload-time = "2025-06-09T23:01:16.752Z" }, + { url = "https://files.pythonhosted.org/packages/f8/b7/2ace5450ce85f2af05a871b8c8719b341294775a0a6c5585d5e6170f2ce7/frozenlist-1.7.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04fb24d104f425da3540ed83cbfc31388a586a7696142004c577fa61c6298c3f", size = 233539, upload-time = "2025-06-09T23:01:18.202Z" }, + { url = "https://files.pythonhosted.org/packages/46/b9/6989292c5539553dba63f3c83dc4598186ab2888f67c0dc1d917e6887db6/frozenlist-1.7.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6a5c505156368e4ea6b53b5ac23c92d7edc864537ff911d2fb24c140bb175e60", size = 215646, upload-time = "2025-06-09T23:01:19.649Z" }, + { url = "https://files.pythonhosted.org/packages/72/31/bc8c5c99c7818293458fe745dab4fd5730ff49697ccc82b554eb69f16a24/frozenlist-1.7.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8bd7eb96a675f18aa5c553eb7ddc24a43c8c18f22e1f9925528128c052cdbe00", size = 232233, upload-time = "2025-06-09T23:01:21.175Z" }, + { url = "https://files.pythonhosted.org/packages/59/52/460db4d7ba0811b9ccb85af996019f5d70831f2f5f255f7cc61f86199795/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:05579bf020096fe05a764f1f84cd104a12f78eaab68842d036772dc6d4870b4b", size = 227996, upload-time = "2025-06-09T23:01:23.098Z" }, + { url = "https://files.pythonhosted.org/packages/ba/c9/f4b39e904c03927b7ecf891804fd3b4df3db29b9e487c6418e37988d6e9d/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:376b6222d114e97eeec13d46c486facd41d4f43bab626b7c3f6a8b4e81a5192c", size = 242280, upload-time = "2025-06-09T23:01:24.808Z" }, + { url = "https://files.pythonhosted.org/packages/b8/33/3f8d6ced42f162d743e3517781566b8481322be321b486d9d262adf70bfb/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:0aa7e176ebe115379b5b1c95b4096fb1c17cce0847402e227e712c27bdb5a949", size = 217717, upload-time = "2025-06-09T23:01:26.28Z" }, + { url = "https://files.pythonhosted.org/packages/3e/e8/ad683e75da6ccef50d0ab0c2b2324b32f84fc88ceee778ed79b8e2d2fe2e/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3fbba20e662b9c2130dc771e332a99eff5da078b2b2648153a40669a6d0e36ca", size = 236644, upload-time = "2025-06-09T23:01:27.887Z" }, + { url = "https://files.pythonhosted.org/packages/b2/14/8d19ccdd3799310722195a72ac94ddc677541fb4bef4091d8e7775752360/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:f3f4410a0a601d349dd406b5713fec59b4cee7e71678d5b17edda7f4655a940b", size = 238879, upload-time = "2025-06-09T23:01:29.524Z" }, + { url = "https://files.pythonhosted.org/packages/ce/13/c12bf657494c2fd1079a48b2db49fa4196325909249a52d8f09bc9123fd7/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e2cdfaaec6a2f9327bf43c933c0319a7c429058e8537c508964a133dffee412e", size = 232502, upload-time = "2025-06-09T23:01:31.287Z" }, + { url = "https://files.pythonhosted.org/packages/d7/8b/e7f9dfde869825489382bc0d512c15e96d3964180c9499efcec72e85db7e/frozenlist-1.7.0-cp313-cp313-win32.whl", hash = "sha256:5fc4df05a6591c7768459caba1b342d9ec23fa16195e744939ba5914596ae3e1", size = 39169, upload-time = "2025-06-09T23:01:35.503Z" }, + { url = "https://files.pythonhosted.org/packages/35/89/a487a98d94205d85745080a37860ff5744b9820a2c9acbcdd9440bfddf98/frozenlist-1.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:52109052b9791a3e6b5d1b65f4b909703984b770694d3eb64fad124c835d7cba", size = 43219, upload-time = "2025-06-09T23:01:36.784Z" }, + { url = "https://files.pythonhosted.org/packages/56/d5/5c4cf2319a49eddd9dd7145e66c4866bdc6f3dbc67ca3d59685149c11e0d/frozenlist-1.7.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a6f86e4193bb0e235ef6ce3dde5cbabed887e0b11f516ce8a0f4d3b33078ec2d", size = 84345, upload-time = "2025-06-09T23:01:38.295Z" }, + { url = "https://files.pythonhosted.org/packages/a4/7d/ec2c1e1dc16b85bc9d526009961953df9cec8481b6886debb36ec9107799/frozenlist-1.7.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:82d664628865abeb32d90ae497fb93df398a69bb3434463d172b80fc25b0dd7d", size = 48880, upload-time = "2025-06-09T23:01:39.887Z" }, + { url = "https://files.pythonhosted.org/packages/69/86/f9596807b03de126e11e7d42ac91e3d0b19a6599c714a1989a4e85eeefc4/frozenlist-1.7.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:912a7e8375a1c9a68325a902f3953191b7b292aa3c3fb0d71a216221deca460b", size = 48498, upload-time = "2025-06-09T23:01:41.318Z" }, + { url = "https://files.pythonhosted.org/packages/5e/cb/df6de220f5036001005f2d726b789b2c0b65f2363b104bbc16f5be8084f8/frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9537c2777167488d539bc5de2ad262efc44388230e5118868e172dd4a552b146", size = 292296, upload-time = "2025-06-09T23:01:42.685Z" }, + { url = "https://files.pythonhosted.org/packages/83/1f/de84c642f17c8f851a2905cee2dae401e5e0daca9b5ef121e120e19aa825/frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:f34560fb1b4c3e30ba35fa9a13894ba39e5acfc5f60f57d8accde65f46cc5e74", size = 273103, upload-time = "2025-06-09T23:01:44.166Z" }, + { url = "https://files.pythonhosted.org/packages/88/3c/c840bfa474ba3fa13c772b93070893c6e9d5c0350885760376cbe3b6c1b3/frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:acd03d224b0175f5a850edc104ac19040d35419eddad04e7cf2d5986d98427f1", size = 292869, upload-time = "2025-06-09T23:01:45.681Z" }, + { url = "https://files.pythonhosted.org/packages/a6/1c/3efa6e7d5a39a1d5ef0abeb51c48fb657765794a46cf124e5aca2c7a592c/frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2038310bc582f3d6a09b3816ab01737d60bf7b1ec70f5356b09e84fb7408ab1", size = 291467, upload-time = "2025-06-09T23:01:47.234Z" }, + { url = "https://files.pythonhosted.org/packages/4f/00/d5c5e09d4922c395e2f2f6b79b9a20dab4b67daaf78ab92e7729341f61f6/frozenlist-1.7.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b8c05e4c8e5f36e5e088caa1bf78a687528f83c043706640a92cb76cd6999384", size = 266028, upload-time = "2025-06-09T23:01:48.819Z" }, + { url = "https://files.pythonhosted.org/packages/4e/27/72765be905619dfde25a7f33813ac0341eb6b076abede17a2e3fbfade0cb/frozenlist-1.7.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:765bb588c86e47d0b68f23c1bee323d4b703218037765dcf3f25c838c6fecceb", size = 284294, upload-time = "2025-06-09T23:01:50.394Z" }, + { url = "https://files.pythonhosted.org/packages/88/67/c94103a23001b17808eb7dd1200c156bb69fb68e63fcf0693dde4cd6228c/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:32dc2e08c67d86d0969714dd484fd60ff08ff81d1a1e40a77dd34a387e6ebc0c", size = 281898, upload-time = "2025-06-09T23:01:52.234Z" }, + { url = "https://files.pythonhosted.org/packages/42/34/a3e2c00c00f9e2a9db5653bca3fec306349e71aff14ae45ecc6d0951dd24/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:c0303e597eb5a5321b4de9c68e9845ac8f290d2ab3f3e2c864437d3c5a30cd65", size = 290465, upload-time = "2025-06-09T23:01:53.788Z" }, + { url = "https://files.pythonhosted.org/packages/bb/73/f89b7fbce8b0b0c095d82b008afd0590f71ccb3dee6eee41791cf8cd25fd/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:a47f2abb4e29b3a8d0b530f7c3598badc6b134562b1a5caee867f7c62fee51e3", size = 266385, upload-time = "2025-06-09T23:01:55.769Z" }, + { url = "https://files.pythonhosted.org/packages/cd/45/e365fdb554159462ca12df54bc59bfa7a9a273ecc21e99e72e597564d1ae/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:3d688126c242a6fabbd92e02633414d40f50bb6002fa4cf995a1d18051525657", size = 288771, upload-time = "2025-06-09T23:01:57.4Z" }, + { url = "https://files.pythonhosted.org/packages/00/11/47b6117002a0e904f004d70ec5194fe9144f117c33c851e3d51c765962d0/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:4e7e9652b3d367c7bd449a727dc79d5043f48b88d0cbfd4f9f1060cf2b414104", size = 288206, upload-time = "2025-06-09T23:01:58.936Z" }, + { url = "https://files.pythonhosted.org/packages/40/37/5f9f3c3fd7f7746082ec67bcdc204db72dad081f4f83a503d33220a92973/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:1a85e345b4c43db8b842cab1feb41be5cc0b10a1830e6295b69d7310f99becaf", size = 282620, upload-time = "2025-06-09T23:02:00.493Z" }, + { url = "https://files.pythonhosted.org/packages/0b/31/8fbc5af2d183bff20f21aa743b4088eac4445d2bb1cdece449ae80e4e2d1/frozenlist-1.7.0-cp313-cp313t-win32.whl", hash = "sha256:3a14027124ddb70dfcee5148979998066897e79f89f64b13328595c4bdf77c81", size = 43059, upload-time = "2025-06-09T23:02:02.072Z" }, + { url = "https://files.pythonhosted.org/packages/bb/ed/41956f52105b8dbc26e457c5705340c67c8cc2b79f394b79bffc09d0e938/frozenlist-1.7.0-cp313-cp313t-win_amd64.whl", hash = "sha256:3bf8010d71d4507775f658e9823210b7427be36625b387221642725b515dcf3e", size = 47516, upload-time = "2025-06-09T23:02:03.779Z" }, + { url = "https://files.pythonhosted.org/packages/ee/45/b82e3c16be2182bff01179db177fe144d58b5dc787a7d4492c6ed8b9317f/frozenlist-1.7.0-py3-none-any.whl", hash = "sha256:9a5af342e34f7e97caf8c995864c7a396418ae2859cc6fdf1b1073020d516a7e", size = 13106, upload-time = "2025-06-09T23:02:34.204Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, +] + +[[package]] +name = "macholib" +version = "1.16.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "altgraph" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/95/ee/af1a3842bdd5902ce133bd246eb7ffd4375c38642aeb5dc0ae3a0329dfa2/macholib-1.16.3.tar.gz", hash = "sha256:07ae9e15e8e4cd9a788013d81f5908b3609aa76f9b1421bae9c4d7606ec86a30", size = 59309, upload-time = "2023-09-25T09:10:16.155Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/5d/c059c180c84f7962db0aeae7c3b9303ed1d73d76f2bfbc32bc231c8be314/macholib-1.16.3-py2.py3-none-any.whl", hash = "sha256:0e315d7583d38b8c77e815b1ecbdbf504a8258d8b3e17b61165c6feb60d18f2c", size = 38094, upload-time = "2023-09-25T09:10:14.188Z" }, +] + +[[package]] +name = "multidict" +version = "6.6.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/69/7f/0652e6ed47ab288e3756ea9c0df8b14950781184d4bd7883f4d87dd41245/multidict-6.6.4.tar.gz", hash = "sha256:d2d4e4787672911b48350df02ed3fa3fffdc2f2e8ca06dd6afdf34189b76a9dd", size = 101843, upload-time = "2025-08-11T12:08:48.217Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/5d/e1db626f64f60008320aab00fbe4f23fc3300d75892a3381275b3d284580/multidict-6.6.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f46a6e8597f9bd71b31cc708195d42b634c8527fecbcf93febf1052cacc1f16e", size = 75848, upload-time = "2025-08-11T12:07:19.912Z" }, + { url = "https://files.pythonhosted.org/packages/4c/aa/8b6f548d839b6c13887253af4e29c939af22a18591bfb5d0ee6f1931dae8/multidict-6.6.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:22e38b2bc176c5eb9c0a0e379f9d188ae4cd8b28c0f53b52bce7ab0a9e534657", size = 45060, upload-time = "2025-08-11T12:07:21.163Z" }, + { url = "https://files.pythonhosted.org/packages/eb/c6/f5e97e5d99a729bc2aa58eb3ebfa9f1e56a9b517cc38c60537c81834a73f/multidict-6.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5df8afd26f162da59e218ac0eefaa01b01b2e6cd606cffa46608f699539246da", size = 43269, upload-time = "2025-08-11T12:07:22.392Z" }, + { url = "https://files.pythonhosted.org/packages/dc/31/d54eb0c62516776f36fe67f84a732f97e0b0e12f98d5685bebcc6d396910/multidict-6.6.4-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:49517449b58d043023720aa58e62b2f74ce9b28f740a0b5d33971149553d72aa", size = 237158, upload-time = "2025-08-11T12:07:23.636Z" }, + { url = "https://files.pythonhosted.org/packages/c4/1c/8a10c1c25b23156e63b12165a929d8eb49a6ed769fdbefb06e6f07c1e50d/multidict-6.6.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae9408439537c5afdca05edd128a63f56a62680f4b3c234301055d7a2000220f", size = 257076, upload-time = "2025-08-11T12:07:25.049Z" }, + { url = "https://files.pythonhosted.org/packages/ad/86/90e20b5771d6805a119e483fd3d1e8393e745a11511aebca41f0da38c3e2/multidict-6.6.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:87a32d20759dc52a9e850fe1061b6e41ab28e2998d44168a8a341b99ded1dba0", size = 240694, upload-time = "2025-08-11T12:07:26.458Z" }, + { url = "https://files.pythonhosted.org/packages/e7/49/484d3e6b535bc0555b52a0a26ba86e4d8d03fd5587d4936dc59ba7583221/multidict-6.6.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:52e3c8d43cdfff587ceedce9deb25e6ae77daba560b626e97a56ddcad3756879", size = 266350, upload-time = "2025-08-11T12:07:27.94Z" }, + { url = "https://files.pythonhosted.org/packages/bf/b4/aa4c5c379b11895083d50021e229e90c408d7d875471cb3abf721e4670d6/multidict-6.6.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ad8850921d3a8d8ff6fbef790e773cecfc260bbfa0566998980d3fa8f520bc4a", size = 267250, upload-time = "2025-08-11T12:07:29.303Z" }, + { url = "https://files.pythonhosted.org/packages/80/e5/5e22c5bf96a64bdd43518b1834c6d95a4922cc2066b7d8e467dae9b6cee6/multidict-6.6.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:497a2954adc25c08daff36f795077f63ad33e13f19bfff7736e72c785391534f", size = 254900, upload-time = "2025-08-11T12:07:30.764Z" }, + { url = "https://files.pythonhosted.org/packages/17/38/58b27fed927c07035abc02befacab42491e7388ca105e087e6e0215ead64/multidict-6.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:024ce601f92d780ca1617ad4be5ac15b501cc2414970ffa2bb2bbc2bd5a68fa5", size = 252355, upload-time = "2025-08-11T12:07:32.205Z" }, + { url = "https://files.pythonhosted.org/packages/d0/a1/dad75d23a90c29c02b5d6f3d7c10ab36c3197613be5d07ec49c7791e186c/multidict-6.6.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:a693fc5ed9bdd1c9e898013e0da4dcc640de7963a371c0bd458e50e046bf6438", size = 250061, upload-time = "2025-08-11T12:07:33.623Z" }, + { url = "https://files.pythonhosted.org/packages/b8/1a/ac2216b61c7f116edab6dc3378cca6c70dc019c9a457ff0d754067c58b20/multidict-6.6.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:190766dac95aab54cae5b152a56520fd99298f32a1266d66d27fdd1b5ac00f4e", size = 249675, upload-time = "2025-08-11T12:07:34.958Z" }, + { url = "https://files.pythonhosted.org/packages/d4/79/1916af833b800d13883e452e8e0977c065c4ee3ab7a26941fbfdebc11895/multidict-6.6.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:34d8f2a5ffdceab9dcd97c7a016deb2308531d5f0fced2bb0c9e1df45b3363d7", size = 261247, upload-time = "2025-08-11T12:07:36.588Z" }, + { url = "https://files.pythonhosted.org/packages/c5/65/d1f84fe08ac44a5fc7391cbc20a7cedc433ea616b266284413fd86062f8c/multidict-6.6.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:59e8d40ab1f5a8597abcef00d04845155a5693b5da00d2c93dbe88f2050f2812", size = 257960, upload-time = "2025-08-11T12:07:39.735Z" }, + { url = "https://files.pythonhosted.org/packages/13/b5/29ec78057d377b195ac2c5248c773703a6b602e132a763e20ec0457e7440/multidict-6.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:467fe64138cfac771f0e949b938c2e1ada2b5af22f39692aa9258715e9ea613a", size = 250078, upload-time = "2025-08-11T12:07:41.525Z" }, + { url = "https://files.pythonhosted.org/packages/c4/0e/7e79d38f70a872cae32e29b0d77024bef7834b0afb406ddae6558d9e2414/multidict-6.6.4-cp313-cp313-win32.whl", hash = "sha256:14616a30fe6d0a48d0a48d1a633ab3b8bec4cf293aac65f32ed116f620adfd69", size = 41708, upload-time = "2025-08-11T12:07:43.405Z" }, + { url = "https://files.pythonhosted.org/packages/9d/34/746696dffff742e97cd6a23da953e55d0ea51fa601fa2ff387b3edcfaa2c/multidict-6.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:40cd05eaeb39e2bc8939451f033e57feaa2ac99e07dbca8afe2be450a4a3b6cf", size = 45912, upload-time = "2025-08-11T12:07:45.082Z" }, + { url = "https://files.pythonhosted.org/packages/c7/87/3bac136181e271e29170d8d71929cdeddeb77f3e8b6a0c08da3a8e9da114/multidict-6.6.4-cp313-cp313-win_arm64.whl", hash = "sha256:f6eb37d511bfae9e13e82cb4d1af36b91150466f24d9b2b8a9785816deb16605", size = 43076, upload-time = "2025-08-11T12:07:46.746Z" }, + { url = "https://files.pythonhosted.org/packages/64/94/0a8e63e36c049b571c9ae41ee301ada29c3fee9643d9c2548d7d558a1d99/multidict-6.6.4-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:6c84378acd4f37d1b507dfa0d459b449e2321b3ba5f2338f9b085cf7a7ba95eb", size = 82812, upload-time = "2025-08-11T12:07:48.402Z" }, + { url = "https://files.pythonhosted.org/packages/25/1a/be8e369dfcd260d2070a67e65dd3990dd635cbd735b98da31e00ea84cd4e/multidict-6.6.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0e0558693063c75f3d952abf645c78f3c5dfdd825a41d8c4d8156fc0b0da6e7e", size = 48313, upload-time = "2025-08-11T12:07:49.679Z" }, + { url = "https://files.pythonhosted.org/packages/26/5a/dd4ade298674b2f9a7b06a32c94ffbc0497354df8285f27317c66433ce3b/multidict-6.6.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3f8e2384cb83ebd23fd07e9eada8ba64afc4c759cd94817433ab8c81ee4b403f", size = 46777, upload-time = "2025-08-11T12:07:51.318Z" }, + { url = "https://files.pythonhosted.org/packages/89/db/98aa28bc7e071bfba611ac2ae803c24e96dd3a452b4118c587d3d872c64c/multidict-6.6.4-cp313-cp313t-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:f996b87b420995a9174b2a7c1a8daf7db4750be6848b03eb5e639674f7963773", size = 229321, upload-time = "2025-08-11T12:07:52.965Z" }, + { url = "https://files.pythonhosted.org/packages/c7/bc/01ddda2a73dd9d167bd85d0e8ef4293836a8f82b786c63fb1a429bc3e678/multidict-6.6.4-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc356250cffd6e78416cf5b40dc6a74f1edf3be8e834cf8862d9ed5265cf9b0e", size = 249954, upload-time = "2025-08-11T12:07:54.423Z" }, + { url = "https://files.pythonhosted.org/packages/06/78/6b7c0f020f9aa0acf66d0ab4eb9f08375bac9a50ff5e3edb1c4ccd59eafc/multidict-6.6.4-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:dadf95aa862714ea468a49ad1e09fe00fcc9ec67d122f6596a8d40caf6cec7d0", size = 228612, upload-time = "2025-08-11T12:07:55.914Z" }, + { url = "https://files.pythonhosted.org/packages/00/44/3faa416f89b2d5d76e9d447296a81521e1c832ad6e40b92f990697b43192/multidict-6.6.4-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7dd57515bebffd8ebd714d101d4c434063322e4fe24042e90ced41f18b6d3395", size = 257528, upload-time = "2025-08-11T12:07:57.371Z" }, + { url = "https://files.pythonhosted.org/packages/05/5f/77c03b89af0fcb16f018f668207768191fb9dcfb5e3361a5e706a11db2c9/multidict-6.6.4-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:967af5f238ebc2eb1da4e77af5492219fbd9b4b812347da39a7b5f5c72c0fa45", size = 256329, upload-time = "2025-08-11T12:07:58.844Z" }, + { url = "https://files.pythonhosted.org/packages/cf/e9/ed750a2a9afb4f8dc6f13dc5b67b514832101b95714f1211cd42e0aafc26/multidict-6.6.4-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2a4c6875c37aae9794308ec43e3530e4aa0d36579ce38d89979bbf89582002bb", size = 247928, upload-time = "2025-08-11T12:08:01.037Z" }, + { url = "https://files.pythonhosted.org/packages/1f/b5/e0571bc13cda277db7e6e8a532791d4403dacc9850006cb66d2556e649c0/multidict-6.6.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:7f683a551e92bdb7fac545b9c6f9fa2aebdeefa61d607510b3533286fcab67f5", size = 245228, upload-time = "2025-08-11T12:08:02.96Z" }, + { url = "https://files.pythonhosted.org/packages/f3/a3/69a84b0eccb9824491f06368f5b86e72e4af54c3067c37c39099b6687109/multidict-6.6.4-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:3ba5aaf600edaf2a868a391779f7a85d93bed147854925f34edd24cc70a3e141", size = 235869, upload-time = "2025-08-11T12:08:04.746Z" }, + { url = "https://files.pythonhosted.org/packages/a9/9d/28802e8f9121a6a0804fa009debf4e753d0a59969ea9f70be5f5fdfcb18f/multidict-6.6.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:580b643b7fd2c295d83cad90d78419081f53fd532d1f1eb67ceb7060f61cff0d", size = 243446, upload-time = "2025-08-11T12:08:06.332Z" }, + { url = "https://files.pythonhosted.org/packages/38/ea/6c98add069b4878c1d66428a5f5149ddb6d32b1f9836a826ac764b9940be/multidict-6.6.4-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:37b7187197da6af3ee0b044dbc9625afd0c885f2800815b228a0e70f9a7f473d", size = 252299, upload-time = "2025-08-11T12:08:07.931Z" }, + { url = "https://files.pythonhosted.org/packages/3a/09/8fe02d204473e14c0af3affd50af9078839dfca1742f025cca765435d6b4/multidict-6.6.4-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e1b93790ed0bc26feb72e2f08299691ceb6da5e9e14a0d13cc74f1869af327a0", size = 246926, upload-time = "2025-08-11T12:08:09.467Z" }, + { url = "https://files.pythonhosted.org/packages/37/3d/7b1e10d774a6df5175ecd3c92bff069e77bed9ec2a927fdd4ff5fe182f67/multidict-6.6.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a506a77ddee1efcca81ecbeae27ade3e09cdf21a8ae854d766c2bb4f14053f92", size = 243383, upload-time = "2025-08-11T12:08:10.981Z" }, + { url = "https://files.pythonhosted.org/packages/50/b0/a6fae46071b645ae98786ab738447de1ef53742eaad949f27e960864bb49/multidict-6.6.4-cp313-cp313t-win32.whl", hash = "sha256:f93b2b2279883d1d0a9e1bd01f312d6fc315c5e4c1f09e112e4736e2f650bc4e", size = 47775, upload-time = "2025-08-11T12:08:12.439Z" }, + { url = "https://files.pythonhosted.org/packages/b2/0a/2436550b1520091af0600dff547913cb2d66fbac27a8c33bc1b1bccd8d98/multidict-6.6.4-cp313-cp313t-win_amd64.whl", hash = "sha256:6d46a180acdf6e87cc41dc15d8f5c2986e1e8739dc25dbb7dac826731ef381a4", size = 53100, upload-time = "2025-08-11T12:08:13.823Z" }, + { url = "https://files.pythonhosted.org/packages/97/ea/43ac51faff934086db9c072a94d327d71b7d8b40cd5dcb47311330929ef0/multidict-6.6.4-cp313-cp313t-win_arm64.whl", hash = "sha256:756989334015e3335d087a27331659820d53ba432befdef6a718398b0a8493ad", size = 45501, upload-time = "2025-08-11T12:08:15.173Z" }, + { url = "https://files.pythonhosted.org/packages/fd/69/b547032297c7e63ba2af494edba695d781af8a0c6e89e4d06cf848b21d80/multidict-6.6.4-py3-none-any.whl", hash = "sha256:27d8f8e125c07cb954e54d75d04905a9bba8a439c1d84aca94949d4d03d8601c", size = 12313, upload-time = "2025-08-11T12:08:46.891Z" }, +] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, +] + +[[package]] +name = "pefile" +version = "2023.2.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/78/c5/3b3c62223f72e2360737fd2a57c30e5b2adecd85e70276879609a7403334/pefile-2023.2.7.tar.gz", hash = "sha256:82e6114004b3d6911c77c3953e3838654b04511b8b66e8583db70c65998017dc", size = 74854, upload-time = "2023-02-07T12:23:55.958Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/55/26/d0ad8b448476d0a1e8d3ea5622dc77b916db84c6aa3cb1e1c0965af948fc/pefile-2023.2.7-py3-none-any.whl", hash = "sha256:da185cd2af68c08a6cd4481f7325ed600a88f6a813bad9dea07ab3ef73d8d8d6", size = 71791, upload-time = "2023-02-07T12:28:36.678Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "propcache" +version = "0.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/16/43264e4a779dd8588c21a70f0709665ee8f611211bdd2c87d952cfa7c776/propcache-0.3.2.tar.gz", hash = "sha256:20d7d62e4e7ef05f221e0db2856b979540686342e7dd9973b815599c7057e168", size = 44139, upload-time = "2025-06-09T22:56:06.081Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/d1/8c747fafa558c603c4ca19d8e20b288aa0c7cda74e9402f50f31eb65267e/propcache-0.3.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ca592ed634a73ca002967458187109265e980422116c0a107cf93d81f95af945", size = 71286, upload-time = "2025-06-09T22:54:54.369Z" }, + { url = "https://files.pythonhosted.org/packages/61/99/d606cb7986b60d89c36de8a85d58764323b3a5ff07770a99d8e993b3fa73/propcache-0.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9ecb0aad4020e275652ba3975740f241bd12a61f1a784df044cf7477a02bc252", size = 42425, upload-time = "2025-06-09T22:54:55.642Z" }, + { url = "https://files.pythonhosted.org/packages/8c/96/ef98f91bbb42b79e9bb82bdd348b255eb9d65f14dbbe3b1594644c4073f7/propcache-0.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7f08f1cc28bd2eade7a8a3d2954ccc673bb02062e3e7da09bc75d843386b342f", size = 41846, upload-time = "2025-06-09T22:54:57.246Z" }, + { url = "https://files.pythonhosted.org/packages/5b/ad/3f0f9a705fb630d175146cd7b1d2bf5555c9beaed54e94132b21aac098a6/propcache-0.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1a342c834734edb4be5ecb1e9fb48cb64b1e2320fccbd8c54bf8da8f2a84c33", size = 208871, upload-time = "2025-06-09T22:54:58.975Z" }, + { url = "https://files.pythonhosted.org/packages/3a/38/2085cda93d2c8b6ec3e92af2c89489a36a5886b712a34ab25de9fbca7992/propcache-0.3.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a544caaae1ac73f1fecfae70ded3e93728831affebd017d53449e3ac052ac1e", size = 215720, upload-time = "2025-06-09T22:55:00.471Z" }, + { url = "https://files.pythonhosted.org/packages/61/c1/d72ea2dc83ac7f2c8e182786ab0fc2c7bd123a1ff9b7975bee671866fe5f/propcache-0.3.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:310d11aa44635298397db47a3ebce7db99a4cc4b9bbdfcf6c98a60c8d5261cf1", size = 215203, upload-time = "2025-06-09T22:55:01.834Z" }, + { url = "https://files.pythonhosted.org/packages/af/81/b324c44ae60c56ef12007105f1460d5c304b0626ab0cc6b07c8f2a9aa0b8/propcache-0.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c1396592321ac83157ac03a2023aa6cc4a3cc3cfdecb71090054c09e5a7cce3", size = 206365, upload-time = "2025-06-09T22:55:03.199Z" }, + { url = "https://files.pythonhosted.org/packages/09/73/88549128bb89e66d2aff242488f62869014ae092db63ccea53c1cc75a81d/propcache-0.3.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cabf5b5902272565e78197edb682017d21cf3b550ba0460ee473753f28d23c1", size = 196016, upload-time = "2025-06-09T22:55:04.518Z" }, + { url = "https://files.pythonhosted.org/packages/b9/3f/3bdd14e737d145114a5eb83cb172903afba7242f67c5877f9909a20d948d/propcache-0.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0a2f2235ac46a7aa25bdeb03a9e7060f6ecbd213b1f9101c43b3090ffb971ef6", size = 205596, upload-time = "2025-06-09T22:55:05.942Z" }, + { url = "https://files.pythonhosted.org/packages/0f/ca/2f4aa819c357d3107c3763d7ef42c03980f9ed5c48c82e01e25945d437c1/propcache-0.3.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:92b69e12e34869a6970fd2f3da91669899994b47c98f5d430b781c26f1d9f387", size = 200977, upload-time = "2025-06-09T22:55:07.792Z" }, + { url = "https://files.pythonhosted.org/packages/cd/4a/e65276c7477533c59085251ae88505caf6831c0e85ff8b2e31ebcbb949b1/propcache-0.3.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:54e02207c79968ebbdffc169591009f4474dde3b4679e16634d34c9363ff56b4", size = 197220, upload-time = "2025-06-09T22:55:09.173Z" }, + { url = "https://files.pythonhosted.org/packages/7c/54/fc7152e517cf5578278b242396ce4d4b36795423988ef39bb8cd5bf274c8/propcache-0.3.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4adfb44cb588001f68c5466579d3f1157ca07f7504fc91ec87862e2b8e556b88", size = 210642, upload-time = "2025-06-09T22:55:10.62Z" }, + { url = "https://files.pythonhosted.org/packages/b9/80/abeb4a896d2767bf5f1ea7b92eb7be6a5330645bd7fb844049c0e4045d9d/propcache-0.3.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fd3e6019dc1261cd0291ee8919dd91fbab7b169bb76aeef6c716833a3f65d206", size = 212789, upload-time = "2025-06-09T22:55:12.029Z" }, + { url = "https://files.pythonhosted.org/packages/b3/db/ea12a49aa7b2b6d68a5da8293dcf50068d48d088100ac016ad92a6a780e6/propcache-0.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4c181cad81158d71c41a2bce88edce078458e2dd5ffee7eddd6b05da85079f43", size = 205880, upload-time = "2025-06-09T22:55:13.45Z" }, + { url = "https://files.pythonhosted.org/packages/d1/e5/9076a0bbbfb65d1198007059c65639dfd56266cf8e477a9707e4b1999ff4/propcache-0.3.2-cp313-cp313-win32.whl", hash = "sha256:8a08154613f2249519e549de2330cf8e2071c2887309a7b07fb56098f5170a02", size = 37220, upload-time = "2025-06-09T22:55:15.284Z" }, + { url = "https://files.pythonhosted.org/packages/d3/f5/b369e026b09a26cd77aa88d8fffd69141d2ae00a2abaaf5380d2603f4b7f/propcache-0.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:e41671f1594fc4ab0a6dec1351864713cb3a279910ae8b58f884a88a0a632c05", size = 40678, upload-time = "2025-06-09T22:55:16.445Z" }, + { url = "https://files.pythonhosted.org/packages/a4/3a/6ece377b55544941a08d03581c7bc400a3c8cd3c2865900a68d5de79e21f/propcache-0.3.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:9a3cf035bbaf035f109987d9d55dc90e4b0e36e04bbbb95af3055ef17194057b", size = 76560, upload-time = "2025-06-09T22:55:17.598Z" }, + { url = "https://files.pythonhosted.org/packages/0c/da/64a2bb16418740fa634b0e9c3d29edff1db07f56d3546ca2d86ddf0305e1/propcache-0.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:156c03d07dc1323d8dacaa221fbe028c5c70d16709cdd63502778e6c3ccca1b0", size = 44676, upload-time = "2025-06-09T22:55:18.922Z" }, + { url = "https://files.pythonhosted.org/packages/36/7b/f025e06ea51cb72c52fb87e9b395cced02786610b60a3ed51da8af017170/propcache-0.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74413c0ba02ba86f55cf60d18daab219f7e531620c15f1e23d95563f505efe7e", size = 44701, upload-time = "2025-06-09T22:55:20.106Z" }, + { url = "https://files.pythonhosted.org/packages/a4/00/faa1b1b7c3b74fc277f8642f32a4c72ba1d7b2de36d7cdfb676db7f4303e/propcache-0.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f066b437bb3fa39c58ff97ab2ca351db465157d68ed0440abecb21715eb24b28", size = 276934, upload-time = "2025-06-09T22:55:21.5Z" }, + { url = "https://files.pythonhosted.org/packages/74/ab/935beb6f1756e0476a4d5938ff44bf0d13a055fed880caf93859b4f1baf4/propcache-0.3.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1304b085c83067914721e7e9d9917d41ad87696bf70f0bc7dee450e9c71ad0a", size = 278316, upload-time = "2025-06-09T22:55:22.918Z" }, + { url = "https://files.pythonhosted.org/packages/f8/9d/994a5c1ce4389610838d1caec74bdf0e98b306c70314d46dbe4fcf21a3e2/propcache-0.3.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ab50cef01b372763a13333b4e54021bdcb291fc9a8e2ccb9c2df98be51bcde6c", size = 282619, upload-time = "2025-06-09T22:55:24.651Z" }, + { url = "https://files.pythonhosted.org/packages/2b/00/a10afce3d1ed0287cef2e09506d3be9822513f2c1e96457ee369adb9a6cd/propcache-0.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fad3b2a085ec259ad2c2842666b2a0a49dea8463579c606426128925af1ed725", size = 265896, upload-time = "2025-06-09T22:55:26.049Z" }, + { url = "https://files.pythonhosted.org/packages/2e/a8/2aa6716ffa566ca57c749edb909ad27884680887d68517e4be41b02299f3/propcache-0.3.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:261fa020c1c14deafd54c76b014956e2f86991af198c51139faf41c4d5e83892", size = 252111, upload-time = "2025-06-09T22:55:27.381Z" }, + { url = "https://files.pythonhosted.org/packages/36/4f/345ca9183b85ac29c8694b0941f7484bf419c7f0fea2d1e386b4f7893eed/propcache-0.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:46d7f8aa79c927e5f987ee3a80205c987717d3659f035c85cf0c3680526bdb44", size = 268334, upload-time = "2025-06-09T22:55:28.747Z" }, + { url = "https://files.pythonhosted.org/packages/3e/ca/fcd54f78b59e3f97b3b9715501e3147f5340167733d27db423aa321e7148/propcache-0.3.2-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:6d8f3f0eebf73e3c0ff0e7853f68be638b4043c65a70517bb575eff54edd8dbe", size = 255026, upload-time = "2025-06-09T22:55:30.184Z" }, + { url = "https://files.pythonhosted.org/packages/8b/95/8e6a6bbbd78ac89c30c225210a5c687790e532ba4088afb8c0445b77ef37/propcache-0.3.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:03c89c1b14a5452cf15403e291c0ccd7751d5b9736ecb2c5bab977ad6c5bcd81", size = 250724, upload-time = "2025-06-09T22:55:31.646Z" }, + { url = "https://files.pythonhosted.org/packages/ee/b0/0dd03616142baba28e8b2d14ce5df6631b4673850a3d4f9c0f9dd714a404/propcache-0.3.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:0cc17efde71e12bbaad086d679ce575268d70bc123a5a71ea7ad76f70ba30bba", size = 268868, upload-time = "2025-06-09T22:55:33.209Z" }, + { url = "https://files.pythonhosted.org/packages/c5/98/2c12407a7e4fbacd94ddd32f3b1e3d5231e77c30ef7162b12a60e2dd5ce3/propcache-0.3.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:acdf05d00696bc0447e278bb53cb04ca72354e562cf88ea6f9107df8e7fd9770", size = 271322, upload-time = "2025-06-09T22:55:35.065Z" }, + { url = "https://files.pythonhosted.org/packages/35/91/9cb56efbb428b006bb85db28591e40b7736847b8331d43fe335acf95f6c8/propcache-0.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4445542398bd0b5d32df908031cb1b30d43ac848e20470a878b770ec2dcc6330", size = 265778, upload-time = "2025-06-09T22:55:36.45Z" }, + { url = "https://files.pythonhosted.org/packages/9a/4c/b0fe775a2bdd01e176b14b574be679d84fc83958335790f7c9a686c1f468/propcache-0.3.2-cp313-cp313t-win32.whl", hash = "sha256:f86e5d7cd03afb3a1db8e9f9f6eff15794e79e791350ac48a8c924e6f439f394", size = 41175, upload-time = "2025-06-09T22:55:38.436Z" }, + { url = "https://files.pythonhosted.org/packages/a4/ff/47f08595e3d9b5e149c150f88d9714574f1a7cbd89fe2817158a952674bf/propcache-0.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:9704bedf6e7cbe3c65eca4379a9b53ee6a83749f047808cbb5044d40d7d72198", size = 44857, upload-time = "2025-06-09T22:55:39.687Z" }, + { url = "https://files.pythonhosted.org/packages/cc/35/cc0aaecf278bb4575b8555f2b137de5ab821595ddae9da9d3cd1da4072c7/propcache-0.3.2-py3-none-any.whl", hash = "sha256:98f1ec44fb675f5052cccc8e609c46ed23a35a1cfd18545ad4e29002d858a43f", size = 12663, upload-time = "2025-06-09T22:56:04.484Z" }, +] + +[[package]] +name = "pydantic" +version = "2.11.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/00/dd/4325abf92c39ba8623b5af936ddb36ffcfe0beae70405d456ab1fb2f5b8c/pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db", size = 788350, upload-time = "2025-06-14T08:33:17.137Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/c0/ec2b1c8712ca690e5d61979dee872603e92b8a32f94cc1b72d53beab008a/pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b", size = 444782, upload-time = "2025-06-14T08:33:14.905Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.33.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195, upload-time = "2025-04-23T18:33:52.104Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/8c/99040727b41f56616573a28771b1bfa08a3d3fe74d3d513f01251f79f172/pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f", size = 2015688, upload-time = "2025-04-23T18:31:53.175Z" }, + { url = "https://files.pythonhosted.org/packages/3a/cc/5999d1eb705a6cefc31f0b4a90e9f7fc400539b1a1030529700cc1b51838/pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6", size = 1844808, upload-time = "2025-04-23T18:31:54.79Z" }, + { url = "https://files.pythonhosted.org/packages/6f/5e/a0a7b8885c98889a18b6e376f344da1ef323d270b44edf8174d6bce4d622/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef", size = 1885580, upload-time = "2025-04-23T18:31:57.393Z" }, + { url = "https://files.pythonhosted.org/packages/3b/2a/953581f343c7d11a304581156618c3f592435523dd9d79865903272c256a/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a", size = 1973859, upload-time = "2025-04-23T18:31:59.065Z" }, + { url = "https://files.pythonhosted.org/packages/e6/55/f1a813904771c03a3f97f676c62cca0c0a4138654107c1b61f19c644868b/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916", size = 2120810, upload-time = "2025-04-23T18:32:00.78Z" }, + { url = "https://files.pythonhosted.org/packages/aa/c3/053389835a996e18853ba107a63caae0b9deb4a276c6b472931ea9ae6e48/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a", size = 2676498, upload-time = "2025-04-23T18:32:02.418Z" }, + { url = "https://files.pythonhosted.org/packages/eb/3c/f4abd740877a35abade05e437245b192f9d0ffb48bbbbd708df33d3cda37/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d", size = 2000611, upload-time = "2025-04-23T18:32:04.152Z" }, + { url = "https://files.pythonhosted.org/packages/59/a7/63ef2fed1837d1121a894d0ce88439fe3e3b3e48c7543b2a4479eb99c2bd/pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56", size = 2107924, upload-time = "2025-04-23T18:32:06.129Z" }, + { url = "https://files.pythonhosted.org/packages/04/8f/2551964ef045669801675f1cfc3b0d74147f4901c3ffa42be2ddb1f0efc4/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5", size = 2063196, upload-time = "2025-04-23T18:32:08.178Z" }, + { url = "https://files.pythonhosted.org/packages/26/bd/d9602777e77fc6dbb0c7db9ad356e9a985825547dce5ad1d30ee04903918/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e", size = 2236389, upload-time = "2025-04-23T18:32:10.242Z" }, + { url = "https://files.pythonhosted.org/packages/42/db/0e950daa7e2230423ab342ae918a794964b053bec24ba8af013fc7c94846/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162", size = 2239223, upload-time = "2025-04-23T18:32:12.382Z" }, + { url = "https://files.pythonhosted.org/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", size = 1900473, upload-time = "2025-04-23T18:32:14.034Z" }, + { url = "https://files.pythonhosted.org/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", size = 1955269, upload-time = "2025-04-23T18:32:15.783Z" }, + { url = "https://files.pythonhosted.org/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", size = 1893921, upload-time = "2025-04-23T18:32:18.473Z" }, + { url = "https://files.pythonhosted.org/packages/a4/7d/e09391c2eebeab681df2b74bfe6c43422fffede8dc74187b2b0bf6fd7571/pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac", size = 1806162, upload-time = "2025-04-23T18:32:20.188Z" }, + { url = "https://files.pythonhosted.org/packages/f1/3d/847b6b1fed9f8ed3bb95a9ad04fbd0b212e832d4f0f50ff4d9ee5a9f15cf/pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5", size = 1981560, upload-time = "2025-04-23T18:32:22.354Z" }, + { url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777, upload-time = "2025-04-23T18:32:25.088Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pyinstaller" +version = "6.15.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "altgraph" }, + { name = "macholib", marker = "sys_platform == 'darwin'" }, + { name = "packaging" }, + { name = "pefile", marker = "sys_platform == 'win32'" }, + { name = "pyinstaller-hooks-contrib" }, + { name = "pywin32-ctypes", marker = "sys_platform == 'win32'" }, + { name = "setuptools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/64/17/b2bb4de22650adbeef401fa82a1b43028976547a8728602e4d29735b455e/pyinstaller-6.15.0.tar.gz", hash = "sha256:a48fc4644ee4aa2aa2a35e7b51f496f8fbd7eecf6a2150646bbf1613ad07bc2d", size = 4331521, upload-time = "2025-08-03T18:33:35.709Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/24/dd/d5c8a127446adda954f68ea7fac22772f7ab8656ad4b06df396d82574ca9/pyinstaller-6.15.0-py3-none-macosx_10_13_universal2.whl", hash = "sha256:9f00c71c40148cd1e61695b2c6f1e086693d3bcf9bfa22ab513aa4254c3b966f", size = 1016981, upload-time = "2025-08-03T18:31:52.034Z" }, + { url = "https://files.pythonhosted.org/packages/2d/2a/7b50593b419db43e48d9bdeebaac0ff92a5fe035f3c30f87ca3e1650d7e2/pyinstaller-6.15.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:cbcc8eb77320c60722030ac875883b564e00768fe3ff1721c7ba3ad0e0a277e9", size = 726337, upload-time = "2025-08-03T18:31:57.592Z" }, + { url = "https://files.pythonhosted.org/packages/77/83/7f498fba0154c57eb5fc93eb9680a2dbadb9f780a3389fb85b8d79683378/pyinstaller-6.15.0-py3-none-manylinux2014_i686.whl", hash = "sha256:c33e6302bc53db2df1104ed5566bd980b3e0ee7f18416a6e3caa908c12a54542", size = 737539, upload-time = "2025-08-03T18:32:02.221Z" }, + { url = "https://files.pythonhosted.org/packages/09/d6/e4477feab7c8379fb49e7ec95c82d0a69ad88f6ccc247f76bef3cb0e3432/pyinstaller-6.15.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:eb902d0fed3bb1f8b7190dc4df5c11f3b59505767e0d56d1ed782b853938bbf3", size = 735426, upload-time = "2025-08-03T18:32:06.485Z" }, + { url = "https://files.pythonhosted.org/packages/32/7e/ff25648276f15e2e77fc563d36d8cfcd917e077bf2a172420df3588601b4/pyinstaller-6.15.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:b4df862adae7cf1f08eff53c43ace283822447f7f528f72e4f94749062712f15", size = 732210, upload-time = "2025-08-03T18:32:21.667Z" }, + { url = "https://files.pythonhosted.org/packages/db/3d/267a7dddd0647de95d260780050ccd8228ab29d2b9edea54ed1f56800967/pyinstaller-6.15.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:b9ebf16ed0f99016ae8ae5746dee4cb244848a12941539e62ce2eea1df5a3f95", size = 732194, upload-time = "2025-08-03T18:32:29.536Z" }, + { url = "https://files.pythonhosted.org/packages/4d/61/962b2eb79ef225233e2d6e04600e998935328011dfb2fa775b1dd16b943a/pyinstaller-6.15.0-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:22193489e6a22435417103f61e7950363bba600ef36ec3ab1487303668c81092", size = 731256, upload-time = "2025-08-03T18:32:36.069Z" }, + { url = "https://files.pythonhosted.org/packages/67/5e/4e20e1c0e5791b09b69bef3ac921fd0cd25551b56879324ad999b92fa045/pyinstaller-6.15.0-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:18f743069849dbaee3e10900385f35795a5743eabab55e99dcc42f204e40a0db", size = 731148, upload-time = "2025-08-03T18:32:41.269Z" }, + { url = "https://files.pythonhosted.org/packages/88/31/28956c534991f289e2f981c715730b6241e75dc6295737a8cbd050a0cc8c/pyinstaller-6.15.0-py3-none-win32.whl", hash = "sha256:60da8f1b5071766b45c0f607d8bc3d7e59ba2c3b262d08f2e4066ba65f3544a2", size = 1312297, upload-time = "2025-08-03T18:32:50.572Z" }, + { url = "https://files.pythonhosted.org/packages/09/ab/6a45186c7f8e34c422faecd72580116a67d068158c57faa2d2f6d01faa7f/pyinstaller-6.15.0-py3-none-win_amd64.whl", hash = "sha256:cbea297e16eeda30b41c300d6ec2fd2abea4dbd8d8a32650eeec36431c94fcd9", size = 1373091, upload-time = "2025-08-03T18:32:58.133Z" }, + { url = "https://files.pythonhosted.org/packages/5b/86/72159af032b9db36f2470a3b085f79277ec1c38e7e48f8c5dc1ed16dc4e1/pyinstaller-6.15.0-py3-none-win_arm64.whl", hash = "sha256:f43c035621742cf2d19b84308c60e4e44e72c94786d176b8f6adcde351b5bd98", size = 1314305, upload-time = "2025-08-03T18:33:05.557Z" }, +] + +[[package]] +name = "pyinstaller-hooks-contrib" +version = "2025.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, + { name = "setuptools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/71/d6/e5b378b7d4add8c879295c531309b0320e9c07a70458665d091760ffdc87/pyinstaller_hooks_contrib-2025.8.tar.gz", hash = "sha256:3402ad41dfe9b5110af134422e37fc5d421ba342c6cb980bd67cb30b7415641c", size = 164214, upload-time = "2025-07-27T16:37:31.943Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/34/1d973d0dae849683e53fbcda84443ce016f315e6f4dc7605ede4f56a28c3/pyinstaller_hooks_contrib-2025.8-py3-none-any.whl", hash = "sha256:8d0b8cfa0cb689a619294ae200497374234bd4e3994b3ace2a4442274c899064", size = 442346, upload-time = "2025-07-27T16:37:30.268Z" }, +] + +[[package]] +name = "pytest" +version = "8.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" }, +] + +[[package]] +name = "python-dotenv" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/b0/4bc07ccd3572a2f9df7e6782f52b0c6c90dcbb803ac4a167702d7d0dfe1e/python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab", size = 41978, upload-time = "2025-06-24T04:21:07.341Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556, upload-time = "2025-06-24T04:21:06.073Z" }, +] + +[[package]] +name = "pywin32-ctypes" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/85/9f/01a1a99704853cb63f253eea009390c88e7131c67e66a0a02099a8c917cb/pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755", size = 29471, upload-time = "2024-08-14T10:15:34.626Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/3d/8161f7711c017e01ac9f008dfddd9410dff3674334c233bde66e7ba65bbf/pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8", size = 30756, upload-time = "2024-08-14T10:15:33.187Z" }, +] + +[[package]] +name = "requests" +version = "2.32.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, +] + +[[package]] +name = "setuptools" +version = "80.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c", size = 1319958, upload-time = "2025-05-27T00:56:51.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486, upload-time = "2025-05-27T00:56:49.664Z" }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, +] + +[[package]] +name = "soupsieve" +version = "2.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6d/e6/21ccce3262dd4889aa3332e5a119a3491a95e8f60939870a3a035aabac0d/soupsieve-2.8.tar.gz", hash = "sha256:e2dd4a40a628cb5f28f6d4b0db8800b8f581b65bb380b97de22ba5ca8d72572f", size = 103472, upload-time = "2025-08-27T15:39:51.78Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/a0/bb38d3b76b8cae341dad93a2dd83ab7462e6dbcdd84d43f54ee60a8dc167/soupsieve-2.8-py3-none-any.whl", hash = "sha256:0cc76456a30e20f5d7f2e14a98a4ae2ee4e5abdc7c5ea0aafe795f344bc7984c", size = 36679, upload-time = "2025-08-27T15:39:50.179Z" }, +] + +[[package]] +name = "squareup" +version = "43.0.2.20250820" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "httpx" }, + { name = "pydantic" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/35/b3/c5b6ad8b545da62d7bf4e6fbbf6a11cd44df0b2a3ff6016044889b3ad261/squareup-43.0.2.20250820.tar.gz", hash = "sha256:41cfebc86054e091c9d552903171db8cc095f8277628a6caeddbc35644cf6318", size = 743175, upload-time = "2025-08-20T15:13:34.604Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/aa/3e/162f70cdaa9f68864f77db4c29aaa70754800181b06b629dc66461c7135e/squareup-43.0.2.20250820-py3-none-any.whl", hash = "sha256:26de4e23c9bb735d4c5bb89a72ff1bd6678b03b39d30e01acd6047d5db9cfc9f", size = 2205057, upload-time = "2025-08-20T15:13:33.199Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f8/b1/0c11f5058406b3af7609f121aaa6b609744687f1d158b3c3a5bf4cc94238/typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28", size = 75726, upload-time = "2025-05-21T18:55:23.885Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552, upload-time = "2025-05-21T18:55:22.152Z" }, +] + +[[package]] +name = "tzdata" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" }, +] + +[[package]] +name = "tzlocal" +version = "5.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "tzdata", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8b/2e/c14812d3d4d9cd1773c6be938f89e5735a1f11a9f184ac3639b93cef35d5/tzlocal-5.3.1.tar.gz", hash = "sha256:cceffc7edecefea1f595541dbd6e990cb1ea3d19bf01b2809f362a03dd7921fd", size = 30761, upload-time = "2025-03-05T21:17:41.549Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/14/e2a54fabd4f08cd7af1c07030603c3356b74da07f7cc056e600436edfa17/tzlocal-5.3.1-py3-none-any.whl", hash = "sha256:eb1a66c3ef5847adf7a834f1be0800581b683b5608e74f86ecbcef8ab91bb85d", size = 18026, upload-time = "2025-03-05T21:17:39.857Z" }, +] + +[[package]] +name = "urllib3" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, +] + +[[package]] +name = "yarl" +version = "1.20.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "multidict" }, + { name = "propcache" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3c/fb/efaa23fa4e45537b827620f04cf8f3cd658b76642205162e072703a5b963/yarl-1.20.1.tar.gz", hash = "sha256:d017a4997ee50c91fd5466cef416231bb82177b93b029906cefc542ce14c35ac", size = 186428, upload-time = "2025-06-10T00:46:09.923Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/e1/2411b6d7f769a07687acee88a062af5833cf1966b7266f3d8dfb3d3dc7d3/yarl-1.20.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:0b5ff0fbb7c9f1b1b5ab53330acbfc5247893069e7716840c8e7d5bb7355038a", size = 131811, upload-time = "2025-06-10T00:44:18.933Z" }, + { url = "https://files.pythonhosted.org/packages/b2/27/584394e1cb76fb771371770eccad35de400e7b434ce3142c2dd27392c968/yarl-1.20.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:14f326acd845c2b2e2eb38fb1346c94f7f3b01a4f5c788f8144f9b630bfff9a3", size = 90078, upload-time = "2025-06-10T00:44:20.635Z" }, + { url = "https://files.pythonhosted.org/packages/bf/9a/3246ae92d4049099f52d9b0fe3486e3b500e29b7ea872d0f152966fc209d/yarl-1.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f60e4ad5db23f0b96e49c018596707c3ae89f5d0bd97f0ad3684bcbad899f1e7", size = 88748, upload-time = "2025-06-10T00:44:22.34Z" }, + { url = "https://files.pythonhosted.org/packages/a3/25/35afe384e31115a1a801fbcf84012d7a066d89035befae7c5d4284df1e03/yarl-1.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:49bdd1b8e00ce57e68ba51916e4bb04461746e794e7c4d4bbc42ba2f18297691", size = 349595, upload-time = "2025-06-10T00:44:24.314Z" }, + { url = "https://files.pythonhosted.org/packages/28/2d/8aca6cb2cabc8f12efcb82749b9cefecbccfc7b0384e56cd71058ccee433/yarl-1.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:66252d780b45189975abfed839616e8fd2dbacbdc262105ad7742c6ae58f3e31", size = 342616, upload-time = "2025-06-10T00:44:26.167Z" }, + { url = "https://files.pythonhosted.org/packages/0b/e9/1312633d16b31acf0098d30440ca855e3492d66623dafb8e25b03d00c3da/yarl-1.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59174e7332f5d153d8f7452a102b103e2e74035ad085f404df2e40e663a22b28", size = 361324, upload-time = "2025-06-10T00:44:27.915Z" }, + { url = "https://files.pythonhosted.org/packages/bc/a0/688cc99463f12f7669eec7c8acc71ef56a1521b99eab7cd3abb75af887b0/yarl-1.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e3968ec7d92a0c0f9ac34d5ecfd03869ec0cab0697c91a45db3fbbd95fe1b653", size = 359676, upload-time = "2025-06-10T00:44:30.041Z" }, + { url = "https://files.pythonhosted.org/packages/af/44/46407d7f7a56e9a85a4c207724c9f2c545c060380718eea9088f222ba697/yarl-1.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1a4fbb50e14396ba3d375f68bfe02215d8e7bc3ec49da8341fe3157f59d2ff5", size = 352614, upload-time = "2025-06-10T00:44:32.171Z" }, + { url = "https://files.pythonhosted.org/packages/b1/91/31163295e82b8d5485d31d9cf7754d973d41915cadce070491778d9c9825/yarl-1.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11a62c839c3a8eac2410e951301309426f368388ff2f33799052787035793b02", size = 336766, upload-time = "2025-06-10T00:44:34.494Z" }, + { url = "https://files.pythonhosted.org/packages/b4/8e/c41a5bc482121f51c083c4c2bcd16b9e01e1cf8729e380273a952513a21f/yarl-1.20.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:041eaa14f73ff5a8986b4388ac6bb43a77f2ea09bf1913df7a35d4646db69e53", size = 364615, upload-time = "2025-06-10T00:44:36.856Z" }, + { url = "https://files.pythonhosted.org/packages/e3/5b/61a3b054238d33d70ea06ebba7e58597891b71c699e247df35cc984ab393/yarl-1.20.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:377fae2fef158e8fd9d60b4c8751387b8d1fb121d3d0b8e9b0be07d1b41e83dc", size = 360982, upload-time = "2025-06-10T00:44:39.141Z" }, + { url = "https://files.pythonhosted.org/packages/df/a3/6a72fb83f8d478cb201d14927bc8040af901811a88e0ff2da7842dd0ed19/yarl-1.20.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1c92f4390e407513f619d49319023664643d3339bd5e5a56a3bebe01bc67ec04", size = 369792, upload-time = "2025-06-10T00:44:40.934Z" }, + { url = "https://files.pythonhosted.org/packages/7c/af/4cc3c36dfc7c077f8dedb561eb21f69e1e9f2456b91b593882b0b18c19dc/yarl-1.20.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d25ddcf954df1754ab0f86bb696af765c5bfaba39b74095f27eececa049ef9a4", size = 382049, upload-time = "2025-06-10T00:44:42.854Z" }, + { url = "https://files.pythonhosted.org/packages/19/3a/e54e2c4752160115183a66dc9ee75a153f81f3ab2ba4bf79c3c53b33de34/yarl-1.20.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:909313577e9619dcff8c31a0ea2aa0a2a828341d92673015456b3ae492e7317b", size = 384774, upload-time = "2025-06-10T00:44:45.275Z" }, + { url = "https://files.pythonhosted.org/packages/9c/20/200ae86dabfca89060ec6447649f219b4cbd94531e425e50d57e5f5ac330/yarl-1.20.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:793fd0580cb9664548c6b83c63b43c477212c0260891ddf86809e1c06c8b08f1", size = 374252, upload-time = "2025-06-10T00:44:47.31Z" }, + { url = "https://files.pythonhosted.org/packages/83/75/11ee332f2f516b3d094e89448da73d557687f7d137d5a0f48c40ff211487/yarl-1.20.1-cp313-cp313-win32.whl", hash = "sha256:468f6e40285de5a5b3c44981ca3a319a4b208ccc07d526b20b12aeedcfa654b7", size = 81198, upload-time = "2025-06-10T00:44:49.164Z" }, + { url = "https://files.pythonhosted.org/packages/ba/ba/39b1ecbf51620b40ab402b0fc817f0ff750f6d92712b44689c2c215be89d/yarl-1.20.1-cp313-cp313-win_amd64.whl", hash = "sha256:495b4ef2fea40596bfc0affe3837411d6aa3371abcf31aac0ccc4bdd64d4ef5c", size = 86346, upload-time = "2025-06-10T00:44:51.182Z" }, + { url = "https://files.pythonhosted.org/packages/43/c7/669c52519dca4c95153c8ad96dd123c79f354a376346b198f438e56ffeb4/yarl-1.20.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:f60233b98423aab21d249a30eb27c389c14929f47be8430efa7dbd91493a729d", size = 138826, upload-time = "2025-06-10T00:44:52.883Z" }, + { url = "https://files.pythonhosted.org/packages/6a/42/fc0053719b44f6ad04a75d7f05e0e9674d45ef62f2d9ad2c1163e5c05827/yarl-1.20.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6f3eff4cc3f03d650d8755c6eefc844edde99d641d0dcf4da3ab27141a5f8ddf", size = 93217, upload-time = "2025-06-10T00:44:54.658Z" }, + { url = "https://files.pythonhosted.org/packages/4f/7f/fa59c4c27e2a076bba0d959386e26eba77eb52ea4a0aac48e3515c186b4c/yarl-1.20.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:69ff8439d8ba832d6bed88af2c2b3445977eba9a4588b787b32945871c2444e3", size = 92700, upload-time = "2025-06-10T00:44:56.784Z" }, + { url = "https://files.pythonhosted.org/packages/2f/d4/062b2f48e7c93481e88eff97a6312dca15ea200e959f23e96d8ab898c5b8/yarl-1.20.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cf34efa60eb81dd2645a2e13e00bb98b76c35ab5061a3989c7a70f78c85006d", size = 347644, upload-time = "2025-06-10T00:44:59.071Z" }, + { url = "https://files.pythonhosted.org/packages/89/47/78b7f40d13c8f62b499cc702fdf69e090455518ae544c00a3bf4afc9fc77/yarl-1.20.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8e0fe9364ad0fddab2688ce72cb7a8e61ea42eff3c7caeeb83874a5d479c896c", size = 323452, upload-time = "2025-06-10T00:45:01.605Z" }, + { url = "https://files.pythonhosted.org/packages/eb/2b/490d3b2dc66f52987d4ee0d3090a147ea67732ce6b4d61e362c1846d0d32/yarl-1.20.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f64fbf81878ba914562c672024089e3401974a39767747691c65080a67b18c1", size = 346378, upload-time = "2025-06-10T00:45:03.946Z" }, + { url = "https://files.pythonhosted.org/packages/66/ad/775da9c8a94ce925d1537f939a4f17d782efef1f973039d821cbe4bcc211/yarl-1.20.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6342d643bf9a1de97e512e45e4b9560a043347e779a173250824f8b254bd5ce", size = 353261, upload-time = "2025-06-10T00:45:05.992Z" }, + { url = "https://files.pythonhosted.org/packages/4b/23/0ed0922b47a4f5c6eb9065d5ff1e459747226ddce5c6a4c111e728c9f701/yarl-1.20.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56dac5f452ed25eef0f6e3c6a066c6ab68971d96a9fb441791cad0efba6140d3", size = 335987, upload-time = "2025-06-10T00:45:08.227Z" }, + { url = "https://files.pythonhosted.org/packages/3e/49/bc728a7fe7d0e9336e2b78f0958a2d6b288ba89f25a1762407a222bf53c3/yarl-1.20.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7d7f497126d65e2cad8dc5f97d34c27b19199b6414a40cb36b52f41b79014be", size = 329361, upload-time = "2025-06-10T00:45:10.11Z" }, + { url = "https://files.pythonhosted.org/packages/93/8f/b811b9d1f617c83c907e7082a76e2b92b655400e61730cd61a1f67178393/yarl-1.20.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:67e708dfb8e78d8a19169818eeb5c7a80717562de9051bf2413aca8e3696bf16", size = 346460, upload-time = "2025-06-10T00:45:12.055Z" }, + { url = "https://files.pythonhosted.org/packages/70/fd/af94f04f275f95da2c3b8b5e1d49e3e79f1ed8b6ceb0f1664cbd902773ff/yarl-1.20.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:595c07bc79af2494365cc96ddeb772f76272364ef7c80fb892ef9d0649586513", size = 334486, upload-time = "2025-06-10T00:45:13.995Z" }, + { url = "https://files.pythonhosted.org/packages/84/65/04c62e82704e7dd0a9b3f61dbaa8447f8507655fd16c51da0637b39b2910/yarl-1.20.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7bdd2f80f4a7df852ab9ab49484a4dee8030023aa536df41f2d922fd57bf023f", size = 342219, upload-time = "2025-06-10T00:45:16.479Z" }, + { url = "https://files.pythonhosted.org/packages/91/95/459ca62eb958381b342d94ab9a4b6aec1ddec1f7057c487e926f03c06d30/yarl-1.20.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c03bfebc4ae8d862f853a9757199677ab74ec25424d0ebd68a0027e9c639a390", size = 350693, upload-time = "2025-06-10T00:45:18.399Z" }, + { url = "https://files.pythonhosted.org/packages/a6/00/d393e82dd955ad20617abc546a8f1aee40534d599ff555ea053d0ec9bf03/yarl-1.20.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:344d1103e9c1523f32a5ed704d576172d2cabed3122ea90b1d4e11fe17c66458", size = 355803, upload-time = "2025-06-10T00:45:20.677Z" }, + { url = "https://files.pythonhosted.org/packages/9e/ed/c5fb04869b99b717985e244fd93029c7a8e8febdfcffa06093e32d7d44e7/yarl-1.20.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:88cab98aa4e13e1ade8c141daeedd300a4603b7132819c484841bb7af3edce9e", size = 341709, upload-time = "2025-06-10T00:45:23.221Z" }, + { url = "https://files.pythonhosted.org/packages/24/fd/725b8e73ac2a50e78a4534ac43c6addf5c1c2d65380dd48a9169cc6739a9/yarl-1.20.1-cp313-cp313t-win32.whl", hash = "sha256:b121ff6a7cbd4abc28985b6028235491941b9fe8fe226e6fdc539c977ea1739d", size = 86591, upload-time = "2025-06-10T00:45:25.793Z" }, + { url = "https://files.pythonhosted.org/packages/94/c3/b2e9f38bc3e11191981d57ea08cab2166e74ea770024a646617c9cddd9f6/yarl-1.20.1-cp313-cp313t-win_amd64.whl", hash = "sha256:541d050a355bbbc27e55d906bc91cb6fe42f96c01413dd0f4ed5a5240513874f", size = 93003, upload-time = "2025-06-10T00:45:27.752Z" }, + { url = "https://files.pythonhosted.org/packages/b4/2d/2345fce04cfd4bee161bf1e7d9cdc702e3e16109021035dbb24db654a622/yarl-1.20.1-py3-none-any.whl", hash = "sha256:83b8eb083fe4683c6115795d9fc1cfaf2cbbefb19b3a1cb68f6527460f483a77", size = 46542, upload-time = "2025-06-10T00:46:07.521Z" }, +] From ae459d21710c6d2dc9700e623f20644f993251d9 Mon Sep 17 00:00:00 2001 From: C <0xicl33n@gmail.com> Date: Fri, 10 Oct 2025 11:05:21 -0700 Subject: [PATCH 14/43] bs4 for testing --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 1baabfc..e5f4fca 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,6 +16,7 @@ dependencies = [ "pytest (>=8.4.2,<9.0.0)", "apscheduler (>=3.11.0,<4.0.0)", "aiosqlite (>=0.21.0,<0.22.0)", + "bs4>=0.0.2", ] [build-system] From 9a106d366b2abdc20cd1e10cd97f37fae99fbe29 Mon Sep 17 00:00:00 2001 From: C <0xicl33n@gmail.com> Date: Fri, 10 Oct 2025 11:05:39 -0700 Subject: [PATCH 15/43] add rate limit handling to synch version :( --- src/bricksetapi.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/bricksetapi.py b/src/bricksetapi.py index a8341b1..44c7bf6 100644 --- a/src/bricksetapi.py +++ b/src/bricksetapi.py @@ -33,6 +33,11 @@ def get_set(set_number: int) -> dict: response = requests.get(url) if response.status_code == 200: data = response.json() + if not data["sets"] and if data["message"] == "API limit exceeded": + raise Exception("API limit exceeded. Please try again later.") + break + + print(f"[@DEBUG]\t{data}") our_set = { "set_num": data["sets"][0]["number"], "set_name": data["sets"][0]["name"], From f0e1d79e926370dc4d86cd10dd3ece811b0a1cf8 Mon Sep 17 00:00:00 2001 From: C <0xicl33n@gmail.com> Date: Sat, 11 Oct 2025 11:33:00 -0700 Subject: [PATCH 16/43] big sexy sqlite db --- src/db.py | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 src/db.py diff --git a/src/db.py b/src/db.py new file mode 100644 index 0000000..5e5f4de --- /dev/null +++ b/src/db.py @@ -0,0 +1,53 @@ +import aiosqlite +from time import time +time = int(time()) # we dont need no stinkin floats + +# db = await aiosqlite.connect("edderkop.db") # type: ignore +async def fetch(sqobj): + """ + Asynchronously fetch a record from the REVIEW table by sq_object. + + Args: + sqobj (str): The sq_object identifier to search for. + Returns: + dict or None: The fetched record as a dictionary, or None if not found. + """ + async with aiosqlite.connect("edderkop.db") as db: # type: ignore + db.row_factory = aiosqlite.Row # type: ignore + async with db.execute( + "SELECT * FROM REVIEW WHERE sq_object = ?", (sqobj,) + ) as cursor: + row = await cursor.fetchone() + if row: + return dict(row) + return None + +async def upsert(table: str, data: dict, unique_key: str) -> None: + """ + Asynchronously insert or update a record in the specified table. + + Args: + sq_ib (aiosqlite.Connection): The SQLite database connection. + table (str): The name of the table to upsert into. + data (dict): A dictionary of column-value pairs to insert or update. + unique_key (str): The column name that serves as the unique key for upsert. + + Returns: + None + """ + print(f"Upserting into {table}: {data}") + + columns = ', '.join(data.keys()) + placeholders = ', '.join('?' * len(data)) + updates = ', '.join(f"{col}=excluded.{col}" for col in data.keys() if col != unique_key) + + sql = f""" + INSERT INTO {table} ({columns}) + VALUES ({placeholders}) + ON CONFLICT({unique_key}) DO UPDATE SET + {updates}; + """ + async with aiosqlite.connect("edderkop.db") as db: # type: ignore + async with db.execute(sql, tuple(data.values())) as cursor: + await db.commit() + #await db.close() \ No newline at end of file From 0f60a31ef885ffa47dfc05f3e85c647df51fee0d Mon Sep 17 00:00:00 2001 From: C <0xicl33n@gmail.com> Date: Sat, 11 Oct 2025 11:33:14 -0700 Subject: [PATCH 17/43] this is the schedule manager not the db --- src/{db_manager.py => scheduler.py} | 442 ++++++++++++++-------------- 1 file changed, 221 insertions(+), 221 deletions(-) rename src/{db_manager.py => scheduler.py} (97%) diff --git a/src/db_manager.py b/src/scheduler.py similarity index 97% rename from src/db_manager.py rename to src/scheduler.py index 0c2f35e..2b56e90 100644 --- a/src/db_manager.py +++ b/src/scheduler.py @@ -1,221 +1,221 @@ -import aiosqlite -import logging -from typing import Optional, List, Dict, Any -import json -import os - -logger = logging.getLogger(__name__) - -# Get the absolute path to edderkop.db in the project root -DEFAULT_DB_PATH = os.path.join( - os.path.dirname(os.path.dirname(__file__)), "edderkop.db" -) - -if not os.path.exists(DEFAULT_DB_PATH): - raise FileNotFoundError(f"Database file not found at {DEFAULT_DB_PATH}") - print('Please run "src/scripts/EDDERKOP.SQL" to setup the database.') - os.exit(0) - - -class AsyncSchedulerDB: - """ - Asynchronous SQLite database manager for APScheduler. - Handles job storage, retrieval, and database operations using the main edderkop.db database. - """ - - def __init__(self, db_path: str = DEFAULT_DB_PATH): - """ - Initialize the database manager. - - Args: - db_path (str, optional): Path to the SQLite database file. - Defaults to edderkop.db in project root. - """ - self.db_path = db_path - self._initialized = False - logger.info(f"Using database at: {self.db_path}") - - async def initialize(self): - """Initialize the database schema if it doesn't exist.""" - if self._initialized: - return - - async with aiosqlite.connect(self.db_path) as db: - # Create apscheduler jobs table if it doesn't exist - await db.execute( - """ - CREATE TABLE IF NOT EXISTS apscheduler_jobs ( - id VARCHAR(191) NOT NULL, - next_run_time FLOAT, - job_state BLOB NOT NULL, - PRIMARY KEY (id) - ) - """ - ) - await db.commit() - - self._initialized = True - logger.info(f"Initialized scheduler database at {self.db_path}") - - async def get_all_jobs(self) -> List[Dict[str, Any]]: - """ - Retrieve all jobs from the database. - - Returns: - List[Dict[str, Any]]: List of job dictionaries - """ - await self.initialize() - async with aiosqlite.connect(self.db_path) as db: - db.row_factory = aiosqlite.Row - async with db.execute( - "SELECT * FROM apscheduler_jobs ORDER BY next_run_time" - ) as cursor: - rows = await cursor.fetchall() - return [ - { - "id": row["id"], - "next_run_time": row["next_run_time"], - "job_state": json.loads(row["job_state"]), - } - for row in rows - ] - - async def get_job(self, job_id: str) -> Optional[Dict[str, Any]]: - """ - Retrieve a specific job by ID. - - Args: - job_id (str): The ID of the job to retrieve - - Returns: - Optional[Dict[str, Any]]: Job dictionary if found, None otherwise - """ - await self.initialize() - async with aiosqlite.connect(self.db_path) as db: - db.row_factory = aiosqlite.Row - async with db.execute( - "SELECT * FROM apscheduler_jobs WHERE id = ?", (job_id,) - ) as cursor: - row = await cursor.fetchone() - if row: - return { - "id": row["id"], - "next_run_time": row["next_run_time"], - "job_state": json.loads(row["job_state"]), - } - return None - - async def add_job( - self, job_id: str, next_run_time: float, job_state: Dict[str, Any] - ) -> bool: - """ - Add a new job to the database. - - Args: - job_id (str): Unique identifier for the job - next_run_time (float): Unix timestamp for next scheduled run - job_state (Dict[str, Any]): Job state dictionary - - Returns: - bool: True if successful, False otherwise - """ - await self.initialize() - try: - async with aiosqlite.connect(self.db_path) as db: - await db.execute( - "INSERT INTO apscheduler_jobs (id, next_run_time, job_state) VALUES (?, ?, ?)", - (job_id, next_run_time, json.dumps(job_state)), - ) - await db.commit() - return True - except aiosqlite.IntegrityError: - logger.error(f"Job with ID {job_id} already exists") - return False - except Exception as e: - logger.error(f"Error adding job: {e}") - return False - - async def update_job( - self, job_id: str, next_run_time: float, job_state: Dict[str, Any] - ) -> bool: - """ - Update an existing job in the database. - - Args: - job_id (str): ID of the job to update - next_run_time (float): New next run time - job_state (Dict[str, Any]): Updated job state - - Returns: - bool: True if successful, False if job not found - """ - await self.initialize() - async with aiosqlite.connect(self.db_path) as db: - cursor = await db.execute( - """UPDATE apscheduler_jobs - SET next_run_time = ?, job_state = ? - WHERE id = ?""", - (next_run_time, json.dumps(job_state), job_id), - ) - await db.commit() - return cursor.rowcount > 0 - - async def remove_job(self, job_id: str) -> bool: - """ - Remove a job from the database. - - Args: - job_id (str): ID of the job to remove - - Returns: - bool: True if job was removed, False if not found - """ - await self.initialize() - async with aiosqlite.connect(self.db_path) as db: - cursor = await db.execute( - "DELETE FROM apscheduler_jobs WHERE id = ?", (job_id,) - ) - await db.commit() - return cursor.rowcount > 0 - - async def remove_all_jobs(self) -> int: - """ - Remove all jobs from the database. - - Returns: - int: Number of jobs removed - """ - await self.initialize() - async with aiosqlite.connect(self.db_path) as db: - cursor = await db.execute("DELETE FROM apscheduler_jobs") - await db.commit() - return cursor.rowcount - - async def get_due_jobs(self, now: float) -> List[Dict[str, Any]]: - """ - Get all jobs that are due to run. - - Args: - now (float): Current timestamp - - Returns: - List[Dict[str, Any]]: List of due jobs - """ - await self.initialize() - async with aiosqlite.connect(self.db_path) as db: - db.row_factory = aiosqlite.Row - async with db.execute( - """SELECT * FROM apscheduler_jobs - WHERE next_run_time <= ? - ORDER BY next_run_time""", - (now,), - ) as cursor: - rows = await cursor.fetchall() - return [ - { - "id": row["id"], - "next_run_time": row["next_run_time"], - "job_state": json.loads(row["job_state"]), - } - for row in rows - ] +import aiosqlite +import logging +from typing import Optional, List, Dict, Any +import json +import os + +logger = logging.getLogger(__name__) + +# Get the absolute path to edderkop.db in the project root +DEFAULT_DB_PATH = os.path.join( + os.path.dirname(os.path.dirname(__file__)), "edderkop.db" +) + +if not os.path.exists(DEFAULT_DB_PATH): + raise FileNotFoundError(f"Database file not found at {DEFAULT_DB_PATH}") + print('Please run "src/scripts/EDDERKOP.SQL" to setup the database.') + os.exit(0) + + +class AsyncSchedulerDB: + """ + Asynchronous SQLite database manager for APScheduler. + Handles job storage, retrieval, and database operations using the main edderkop.db database. + """ + + def __init__(self, db_path: str = DEFAULT_DB_PATH): + """ + Initialize the database manager. + + Args: + db_path (str, optional): Path to the SQLite database file. + Defaults to edderkop.db in project root. + """ + self.db_path = db_path + self._initialized = False + logger.info(f"Using database at: {self.db_path}") + + async def initialize(self): + """Initialize the database schema if it doesn't exist.""" + if self._initialized: + return + + async with aiosqlite.connect(self.db_path) as db: + # Create apscheduler jobs table if it doesn't exist + await db.execute( + """ + CREATE TABLE IF NOT EXISTS apscheduler_jobs ( + id VARCHAR(191) NOT NULL, + next_run_time FLOAT, + job_state BLOB NOT NULL, + PRIMARY KEY (id) + ) + """ + ) + await db.commit() + + self._initialized = True + logger.info(f"Initialized scheduler database at {self.db_path}") + + async def get_all_jobs(self) -> List[Dict[str, Any]]: + """ + Retrieve all jobs from the database. + + Returns: + List[Dict[str, Any]]: List of job dictionaries + """ + await self.initialize() + async with aiosqlite.connect(self.db_path) as db: + db.row_factory = aiosqlite.Row + async with db.execute( + "SELECT * FROM apscheduler_jobs ORDER BY next_run_time" + ) as cursor: + rows = await cursor.fetchall() + return [ + { + "id": row["id"], + "next_run_time": row["next_run_time"], + "job_state": json.loads(row["job_state"]), + } + for row in rows + ] + + async def get_job(self, job_id: str) -> Optional[Dict[str, Any]]: + """ + Retrieve a specific job by ID. + + Args: + job_id (str): The ID of the job to retrieve + + Returns: + Optional[Dict[str, Any]]: Job dictionary if found, None otherwise + """ + await self.initialize() + async with aiosqlite.connect(self.db_path) as db: + db.row_factory = aiosqlite.Row + async with db.execute( + "SELECT * FROM apscheduler_jobs WHERE id = ?", (job_id,) + ) as cursor: + row = await cursor.fetchone() + if row: + return { + "id": row["id"], + "next_run_time": row["next_run_time"], + "job_state": json.loads(row["job_state"]), + } + return None + + async def add_job( + self, job_id: str, next_run_time: float, job_state: Dict[str, Any] + ) -> bool: + """ + Add a new job to the database. + + Args: + job_id (str): Unique identifier for the job + next_run_time (float): Unix timestamp for next scheduled run + job_state (Dict[str, Any]): Job state dictionary + + Returns: + bool: True if successful, False otherwise + """ + await self.initialize() + try: + async with aiosqlite.connect(self.db_path) as db: + await db.execute( + "INSERT INTO apscheduler_jobs (id, next_run_time, job_state) VALUES (?, ?, ?)", + (job_id, next_run_time, json.dumps(job_state)), + ) + await db.commit() + return True + except aiosqlite.IntegrityError: + logger.error(f"Job with ID {job_id} already exists") + return False + except Exception as e: + logger.error(f"Error adding job: {e}") + return False + + async def update_job( + self, job_id: str, next_run_time: float, job_state: Dict[str, Any] + ) -> bool: + """ + Update an existing job in the database. + + Args: + job_id (str): ID of the job to update + next_run_time (float): New next run time + job_state (Dict[str, Any]): Updated job state + + Returns: + bool: True if successful, False if job not found + """ + await self.initialize() + async with aiosqlite.connect(self.db_path) as db: + cursor = await db.execute( + """UPDATE apscheduler_jobs + SET next_run_time = ?, job_state = ? + WHERE id = ?""", + (next_run_time, json.dumps(job_state), job_id), + ) + await db.commit() + return cursor.rowcount > 0 + + async def remove_job(self, job_id: str) -> bool: + """ + Remove a job from the database. + + Args: + job_id (str): ID of the job to remove + + Returns: + bool: True if job was removed, False if not found + """ + await self.initialize() + async with aiosqlite.connect(self.db_path) as db: + cursor = await db.execute( + "DELETE FROM apscheduler_jobs WHERE id = ?", (job_id,) + ) + await db.commit() + return cursor.rowcount > 0 + + async def remove_all_jobs(self) -> int: + """ + Remove all jobs from the database. + + Returns: + int: Number of jobs removed + """ + await self.initialize() + async with aiosqlite.connect(self.db_path) as db: + cursor = await db.execute("DELETE FROM apscheduler_jobs") + await db.commit() + return cursor.rowcount + + async def get_due_jobs(self, now: float) -> List[Dict[str, Any]]: + """ + Get all jobs that are due to run. + + Args: + now (float): Current timestamp + + Returns: + List[Dict[str, Any]]: List of due jobs + """ + await self.initialize() + async with aiosqlite.connect(self.db_path) as db: + db.row_factory = aiosqlite.Row + async with db.execute( + """SELECT * FROM apscheduler_jobs + WHERE next_run_time <= ? + ORDER BY next_run_time""", + (now,), + ) as cursor: + rows = await cursor.fetchall() + return [ + { + "id": row["id"], + "next_run_time": row["next_run_time"], + "job_state": json.loads(row["job_state"]), + } + for row in rows + ] From 3b8a01160d2014498c0157c0e04e0523c5b68325 Mon Sep 17 00:00:00 2001 From: C <0xicl33n@gmail.com> Date: Sat, 11 Oct 2025 11:38:54 -0700 Subject: [PATCH 18/43] move throttler example. we dont need it in src --- src/{ => scripts}/throttler_example.py | 30 +++++++++++++------------- 1 file changed, 15 insertions(+), 15 deletions(-) rename src/{ => scripts}/throttler_example.py (96%) diff --git a/src/throttler_example.py b/src/scripts/throttler_example.py similarity index 96% rename from src/throttler_example.py rename to src/scripts/throttler_example.py index 70ba7e3..0a9756f 100644 --- a/src/throttler_example.py +++ b/src/scripts/throttler_example.py @@ -1,15 +1,15 @@ -import aiohttp -import asyncio - -from asyncio_throttle.throttler import Throttler - -throttler = Throttler(rate_limit=500, period=60) # 500 requests per minute - - -async def worker(throttler, session): - while True: - async with throttler: - async with session.get("http://example.com") as resp: - do_some_job_with(await resp.text()) - - await asyncio.sleep(0.05) +import aiohttp +import asyncio + +from asyncio_throttle.throttler import Throttler + +throttler = Throttler(rate_limit=500, period=60) # 500 requests per minute + + +async def worker(throttler, session): + while True: + async with throttler: + async with session.get("http://example.com") as resp: + do_some_job_with(await resp.text()) + + await asyncio.sleep(0.05) From 912ad15c288ffc2ff181dc1431663e8c5aed3425 Mon Sep 17 00:00:00 2001 From: C <0xicl33n@gmail.com> Date: Sat, 11 Oct 2025 11:39:12 -0700 Subject: [PATCH 19/43] rate limit --- src/bricksetapi.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bricksetapi.py b/src/bricksetapi.py index 44c7bf6..fd5a361 100644 --- a/src/bricksetapi.py +++ b/src/bricksetapi.py @@ -33,9 +33,9 @@ def get_set(set_number: int) -> dict: response = requests.get(url) if response.status_code == 200: data = response.json() - if not data["sets"] and if data["message"] == "API limit exceeded": + if not data["sets"] and data["message"] == "API limit exceeded": raise Exception("API limit exceeded. Please try again later.") - break + return False print(f"[@DEBUG]\t{data}") our_set = { From 12f03e8bc978ff95b863b6803d105ddb872f0278 Mon Sep 17 00:00:00 2001 From: C <0xicl33n@gmail.com> Date: Sat, 11 Oct 2025 11:39:29 -0700 Subject: [PATCH 20/43] add initial review testing for sql --- src/asquare.py | 37 +++++++++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/src/asquare.py b/src/asquare.py index 1059b98..2fdfd14 100644 --- a/src/asquare.py +++ b/src/asquare.py @@ -4,6 +4,12 @@ import json import asyncio import uuid import re +from time import time +import re +# we dont need no stinkin floats +time = int(time()) + +import db import get_sq_env @@ -114,15 +120,37 @@ async def acat2id(cat_id: str): return items +async def do_review() -> bool: + result = await acat2id(LEGO_CAT_ID) + for item in result: + if not re.match(r".*\d{5}.*", item["name"]): + missing = [] + print(f"{item} is missing its set number!") + print(f"[@DEBUG] Upserting {item['id']} into REVIEW table") + item_sanitized = { + "sq_object": item["id"], + "set_name": item["name"], + "set_number": None, + "time_added": time + } + missing.append(item_sanitized) + for item in missing: + sq_item = await db.fetch(item["id"]) # + if item["id"] != sq_item["sq_object"]: + await db.upsert("REVIEW", item_sanitized, "sq_object") + print(f"[@DEBUG] Upserted {item['id']} into REVIEW table") + print(f"[@DEBUG] {item['id']} already in REVIEW table") + return True + async def test() -> None: task = asyncio.create_task(acat2id(LEGO_CAT_ID)) # type: ignore + await do_review() await asyncio.sleep(1) # Give the task some time to complete result = await task - print("OBJECTS IN LEGO CATEGORY:") - for item in result: - print(f"ID: {item['id']}, Name: {item['name']}") - # eturn + #print("OBJECTS IN LEGO CATEGORY:") + #print(f"ID: {item['id']}, Name: {item['name']}") + return True # pass @@ -134,6 +162,7 @@ async def test() -> None: if __name__ == "__main__": + print("Spooling up..") print(f"@[DEBUG] ENV:\t{ENVIRONMENT}") print(f"@[DEBUG] CATID:\t{LEGO_CAT_ID}") asyncio.run(test()) From a85e06a19bbb62ef0de7a7d3801bbb7d8b4fb4a7 Mon Sep 17 00:00:00 2001 From: C <0xicl33n@gmail.com> Date: Sat, 11 Oct 2025 13:55:15 -0700 Subject: [PATCH 21/43] global throttler implementation --- src/throttler.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 src/throttler.py diff --git a/src/throttler.py b/src/throttler.py new file mode 100644 index 0000000..219e740 --- /dev/null +++ b/src/throttler.py @@ -0,0 +1,33 @@ +import asyncio +from asyncio_throttle import Throttler as AsyncThrottler + +class GlobalThrottler: + _instance = None + _lock = asyncio.Lock() + + def __init__(self, rate_limit: int = 500, period: int = 60) -> None: + if not hasattr(self, '_initialized'): + self.rate_limit = rate_limit + self.period = period + self.throttler = AsyncThrottler(rate_limit=self.rate_limit, period=self.period) + self._initialized = True + + @classmethod + async def get_instance(cls, rate_limit: int = 500, period: int = 60) -> 'GlobalThrottler': + async with cls._lock: + if not cls._instance: + cls._instance = cls(rate_limit, period) + return cls._instance + + async def acquire(self): + await self.throttler.acquire() + + def release(self): + self.throttler.release() + + async def __aenter__(self): + await self.acquire() + return self + + async def __aexit__(self, exc_type, exc_val, exc_tb): + self.release() From 1c43e5ba063ca6c68a99d3cbefe28d83051592af Mon Sep 17 00:00:00 2001 From: C <0xicl33n@gmail.com> Date: Sat, 11 Oct 2025 13:55:39 -0700 Subject: [PATCH 22/43] make middleware module (glue) --- src/glue.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 src/glue.py diff --git a/src/glue.py b/src/glue.py new file mode 100644 index 0000000..db53fc8 --- /dev/null +++ b/src/glue.py @@ -0,0 +1,32 @@ +import asyncio +import json +import get_sq_env +import aiosqlite + +import asquare +import db +import abricksetapi +from . throttler import GlobalThrottler + +async def do_review() -> bool: + result = await asquare.acat2id(LEGO_CAT_ID) + for item in result: + if not re.match(r".*\d{5}.*", item["name"]): + missing = [] + print(f"{item} is missing its set number!") + print(f"[@DEBUG] Upserting {item['id']} into REVIEW table") + item_sanitized = { + "sq_object": item["id"], + "set_name": item["name"], + "set_number": None, + "time_added": time + } + missing.append(item_sanitized) + for item in missing: + sq_item = await db.fetch(item["id"]) # + if item["id"] != sq_item["sq_object"]: + await db.upsert("REVIEW", item_sanitized, "sq_object") + print(f"[@DEBUG] Upserted {item['id']} into REVIEW table") + print(f"[@DEBUG] {item['id']} already in REVIEW table") + return True + From 4dfe50db93df45915ccd82e43b53a34e9e63ebd6 Mon Sep 17 00:00:00 2001 From: C <0xicl33n@gmail.com> Date: Sat, 11 Oct 2025 13:56:01 -0700 Subject: [PATCH 23/43] remove previous stuff from asquare and put it in glue --- src/asquare.py | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/src/asquare.py b/src/asquare.py index 2fdfd14..6241c88 100644 --- a/src/asquare.py +++ b/src/asquare.py @@ -120,28 +120,6 @@ async def acat2id(cat_id: str): return items -async def do_review() -> bool: - result = await acat2id(LEGO_CAT_ID) - for item in result: - if not re.match(r".*\d{5}.*", item["name"]): - missing = [] - print(f"{item} is missing its set number!") - print(f"[@DEBUG] Upserting {item['id']} into REVIEW table") - item_sanitized = { - "sq_object": item["id"], - "set_name": item["name"], - "set_number": None, - "time_added": time - } - missing.append(item_sanitized) - for item in missing: - sq_item = await db.fetch(item["id"]) # - if item["id"] != sq_item["sq_object"]: - await db.upsert("REVIEW", item_sanitized, "sq_object") - print(f"[@DEBUG] Upserted {item['id']} into REVIEW table") - print(f"[@DEBUG] {item['id']} already in REVIEW table") - return True - async def test() -> None: task = asyncio.create_task(acat2id(LEGO_CAT_ID)) # type: ignore @@ -162,7 +140,6 @@ async def test() -> None: if __name__ == "__main__": - print("Spooling up..") print(f"@[DEBUG] ENV:\t{ENVIRONMENT}") print(f"@[DEBUG] CATID:\t{LEGO_CAT_ID}") asyncio.run(test()) From 9804b6d673bd4bd164891bec18928bb6bd0670aa Mon Sep 17 00:00:00 2001 From: C <0xicl33n@gmail.com> Date: Sat, 11 Oct 2025 13:56:13 -0700 Subject: [PATCH 24/43] fix up db fetch and insert --- src/db.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/db.py b/src/db.py index 5e5f4de..02bbbec 100644 --- a/src/db.py +++ b/src/db.py @@ -3,7 +3,11 @@ from time import time time = int(time()) # we dont need no stinkin floats # db = await aiosqlite.connect("edderkop.db") # type: ignore -async def fetch(sqobj): + +async def close(db): + await db.close() + +async def fetch(table, sqobj): """ Asynchronously fetch a record from the REVIEW table by sq_object. @@ -13,21 +17,17 @@ async def fetch(sqobj): dict or None: The fetched record as a dictionary, or None if not found. """ async with aiosqlite.connect("edderkop.db") as db: # type: ignore - db.row_factory = aiosqlite.Row # type: ignore - async with db.execute( - "SELECT * FROM REVIEW WHERE sq_object = ?", (sqobj,) - ) as cursor: - row = await cursor.fetchone() - if row: - return dict(row) - return None + async with db.execute(f"SELECT * FROM {table} WHERE sq_object = {sqobj}") as cursor: + async for row in cursor: + return row -async def upsert(table: str, data: dict, unique_key: str) -> None: + return None + +async def upsert(table: str, data: dict, unique_key: str) -> bool: """ Asynchronously insert or update a record in the specified table. Args: - sq_ib (aiosqlite.Connection): The SQLite database connection. table (str): The name of the table to upsert into. data (dict): A dictionary of column-value pairs to insert or update. unique_key (str): The column name that serves as the unique key for upsert. @@ -50,4 +50,4 @@ async def upsert(table: str, data: dict, unique_key: str) -> None: async with aiosqlite.connect("edderkop.db") as db: # type: ignore async with db.execute(sql, tuple(data.values())) as cursor: await db.commit() - #await db.close() \ No newline at end of file + return True From fb908e00b7843dfc008d89145f5d539dd1b58f8d Mon Sep 17 00:00:00 2001 From: C <0xicl33n@gmail.com> Date: Sat, 11 Oct 2025 13:56:34 -0700 Subject: [PATCH 25/43] misc fixes --- src/abricksetapi.py | 3 ++- src/test_server.py | 17 ++++++++++++++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/abricksetapi.py b/src/abricksetapi.py index 2f7f7bc..7d6fda5 100644 --- a/src/abricksetapi.py +++ b/src/abricksetapi.py @@ -53,7 +53,7 @@ class AsyncBricksetAPI: self.logger.info("Successfully authenticated with Brickset API") return result - async def get_set(self, set_number: str) -> Dict[str, Any]: + async def get_set(self, set_number: int) -> Dict[str, Any]: """ Fetch LEGO set information asynchronously @@ -72,6 +72,7 @@ class AsyncBricksetAPI: "userHash": self.user_hash, "params": json.dumps({"setNumber": f"{set_number}-1"}), } + print(f"[@DEBUG] {params}...") async with aiohttp.ClientSession() as session: async with session.get( diff --git a/src/test_server.py b/src/test_server.py index a88fdd8..e378d42 100644 --- a/src/test_server.py +++ b/src/test_server.py @@ -11,8 +11,8 @@ import re import logging from logging.handlers import RotatingFileHandler from pathlib import Path -print("Creating log directory if it doesn't exist...") -Path("./logs/").mkdir(parents=True, exist_ok=True) +#print("Creating log directory if it doesn't exist...") +#Path("./logs/").mkdir(parents=True, exist_ok=True) import banner import get_sq_env @@ -47,7 +47,18 @@ UA = re.compile(r"^kopling\/?", re.IGNORECASE) async def run_update(): set_list = await sq.acat2id(SQUARE_LEGO_CAT_ID) - print(f"[@DEBUG]\t {set_list}") # type: ignore + #print(f"[@DEBUG]\t {set_list}") # type: ignore + for l_set in set_list: + #print(f"[@DEBUG]{l_set}") + try: + l_setinfo = await lego.throttled_get_set(l_set["number"]) + print(f"[@DEBUG] Found {l_setinfo["pw_name"]}") + except Exception as e: + print(f"Error fetching set\t: {l_set}: {str(e)}") + continue + + #print(f"[@DEBUG]{l_setinfo}") + #await sq.update_itm(l_set, l_setinfo) """for l_set in set_list: print(f"[@DEBUG]{l_set}") try: From 80250035e5179e3040237d48a75a634ffb1b4a83 Mon Sep 17 00:00:00 2001 From: C <0xicl33n@gmail.com> Date: Sat, 11 Oct 2025 13:56:53 -0700 Subject: [PATCH 26/43] modulize it >:( stupid wsl --- src/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/__init__.py diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 0000000..e69de29 From 4e808c3be93b616d5406441e968447915a6d6816 Mon Sep 17 00:00:00 2001 From: C <0xicl33n@gmail.com> Date: Sat, 11 Oct 2025 13:57:02 -0700 Subject: [PATCH 27/43] mk test in test because test --- {src => tests}/test.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) rename {src => tests}/test.py (96%) diff --git a/src/test.py b/tests/test.py similarity index 96% rename from src/test.py rename to tests/test.py index 73fb108..d4d7c87 100644 --- a/src/test.py +++ b/tests/test.py @@ -1,6 +1,6 @@ -import src.libsquare as sq -import src.bricksetapi as lego - -oursq = sq.get_itm("71417") -ourlego = lego.get_set(71417) -print(f"{oursq}\n\n{ourlego}\n\n") +import src.libsquare as sq +import src.bricksetapi as lego + +oursq = sq.get_itm("71417") +ourlego = lego.get_set(71417) +print(f"{oursq}\n\n{ourlego}\n\n") From a62b93692030efa194d6501568746cee305b8dfc Mon Sep 17 00:00:00 2001 From: C <0xicl33n@gmail.com> Date: Sat, 11 Oct 2025 14:17:51 -0700 Subject: [PATCH 28/43] spooder --- img/spider-by-hedu88.png | Bin 0 -> 69625 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 img/spider-by-hedu88.png diff --git a/img/spider-by-hedu88.png b/img/spider-by-hedu88.png new file mode 100644 index 0000000000000000000000000000000000000000..68d465f4329c502d87c238e81cc6fa5921a690d4 GIT binary patch literal 69625 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 B5#8zs)>i}i|{ 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 QN8qEPsHspR_bTlF02ONaUH||9 literal 0 HcmV?d00001 From ca99cd385378c65fe22b4eceeb0946bc836c2223 Mon Sep 17 00:00:00 2001 From: C <0xicl33n@gmail.com> Date: Sat, 11 Oct 2025 14:26:30 -0700 Subject: [PATCH 29/43] fancy new readme --- README.md | 35 +++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index dfd8f32..5f7932e 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,40 @@ # Studspider -environment: poetry +is a tool used to talk to brickset.com and a square inventory to pull lego set information for entering in the square db +## Run Locally -code style: black +Clone the project + +```bash + git clone http://git.traps.moe/ph0sgene/edderkop.git +``` + +Go to the project directory + +```bash + cd cd edderkop +``` + +Install dependencies + +```bash + uv sync && uv lock +``` + +Start the server + +```bash + uvx run edderkop.py +``` -built with +## Tech Stack +**Client:** micropython + +**Server:** - aiohttp - squareup - apscheduler -- aiosqlite \ No newline at end of file +- aiosqlite + From 784517e886df76feaa1faeba9f9104183f1c41bc Mon Sep 17 00:00:00 2001 From: ph0sgene Date: Sat, 11 Oct 2025 21:40:10 +0000 Subject: [PATCH 30/43] Update README.md --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5f7932e..54fe189 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,10 @@ # Studspider +![Static Badge](https://img.shields.io/badge/python-3.13.7-blue?logo=python) ![Static Badge](https://img.shields.io/badge/code_style-black-black) ![Static Badge](https://img.shields.io/badge/license-MPL-orange) + + + + is a tool used to talk to brickset.com and a square inventory to pull lego set information for entering in the square db ## Run Locally @@ -12,7 +17,7 @@ Clone the project Go to the project directory ```bash - cd cd edderkop + cd edderkop ``` Install dependencies From 2fc7e773e08a06adf3a3dfe8b37fea51f10dbf84 Mon Sep 17 00:00:00 2001 From: ph0sgene Date: Sat, 11 Oct 2025 21:42:02 +0000 Subject: [PATCH 31/43] Update README.md --- README.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 54fe189..d41bd38 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ is a tool used to talk to brickset.com and a square inventory to pull lego set information for entering in the square db -## Run Locally +## Run me Clone the project @@ -33,11 +33,8 @@ Start the server ``` -## Tech Stack +## Tech -**Client:** micropython - -**Server:** - aiohttp - squareup - apscheduler From 7bbb46398e6685f51e66a3187e11bc879fe5d013 Mon Sep 17 00:00:00 2001 From: ph0sgene Date: Sat, 11 Oct 2025 21:42:35 +0000 Subject: [PATCH 32/43] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d41bd38..b3a457e 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ is a tool used to talk to brickset.com and a square inventory to pull lego set i Clone the project ```bash - git clone http://git.traps.moe/ph0sgene/edderkop.git + git clone http://git.traps.moe/ph0sgene/edderkop.git -b uv ``` Go to the project directory From 88dcb8a47542fad53edf72f9c05173468cb9ea3f Mon Sep 17 00:00:00 2001 From: ph0sgene Date: Sat, 11 Oct 2025 21:43:11 +0000 Subject: [PATCH 33/43] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b3a457e..2af8d8e 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,8 @@ is a tool used to talk to brickset.com and a square inventory to pull lego set i Clone the project ```bash - git clone http://git.traps.moe/ph0sgene/edderkop.git -b uv + git clone http://git.traps.moe/ph0sgene/edderkop.git + git checkout uv ``` Go to the project directory From 0e3117e40816e082fa45e04727af2b378a4c3b01 Mon Sep 17 00:00:00 2001 From: C <0xicl33n@gmail.com> Date: Sun, 12 Oct 2025 11:51:49 -0700 Subject: [PATCH 34/43] time stuff --- src/glue.py | 9 +++++++-- src/test_server.py | 17 +++++++++-------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/glue.py b/src/glue.py index db53fc8..f52eb01 100644 --- a/src/glue.py +++ b/src/glue.py @@ -1,15 +1,20 @@ import asyncio import json +import re import get_sq_env import aiosqlite +from time import time import asquare import db import abricksetapi -from . throttler import GlobalThrottler +from throttler import GlobalThrottler +LEGO_CAT_ID = get_sq_env.LEGO_CAT_ID + +# big glue boy async def do_review() -> bool: - result = await asquare.acat2id(LEGO_CAT_ID) + result = await asquare.acat2id(LEGO_CAT_ID) # type: ignore for item in result: if not re.match(r".*\d{5}.*", item["name"]): missing = [] diff --git a/src/test_server.py b/src/test_server.py index e378d42..cbae6df 100644 --- a/src/test_server.py +++ b/src/test_server.py @@ -4,19 +4,20 @@ from aiohttp import web from base64 import b64decode from typing import Dict, Any import uuid -from time import time + import dotenv import os import re import logging from logging.handlers import RotatingFileHandler from pathlib import Path -#print("Creating log directory if it doesn't exist...") -#Path("./logs/").mkdir(parents=True, exist_ok=True) +print("Creating log directory if it doesn't exist...") +os.makedirs("./logs/", exist_ok=True) import banner import get_sq_env - +from time import time +time = int(time()) import asquare as sq # from asquareclass import AsyncSquareClient # type: ignore @@ -46,7 +47,7 @@ UA = re.compile(r"^kopling\/?", re.IGNORECASE) async def run_update(): - set_list = await sq.acat2id(SQUARE_LEGO_CAT_ID) + set_list = await sq.acat2id(SQUARE_LEGO_CAT_ID) # type: ignore #print(f"[@DEBUG]\t {set_list}") # type: ignore for l_set in set_list: #print(f"[@DEBUG]{l_set}") @@ -85,7 +86,7 @@ async def request_handler(request: web.Request) -> web.Response: # Verify request method if request.method != "POST": logger.error( - f"{time()}\tInvalid request method from %s at ", request.remote + f"{time}\tInvalid request method from %s at ", request.remote ) return web.Response(status=405, text="Method not allowed. Use POST.") @@ -114,10 +115,10 @@ async def request_handler(request: web.Request) -> web.Response: try: decoded_cmd = b64decode(data["CMD"]).decode("utf-8") print( - f"""At: {time()}\nReceived command: {decoded_cmd}\nUser-Agent: {request.headers.get('User-Agent')}\nIdempotency-Key: {request.headers.get('Idempotency-Key')}""" + f"""At: {time}\nReceived command: {decoded_cmd}\nUser-Agent: {request.headers.get('User-Agent')}\nIdempotency-Key: {request.headers.get('Idempotency-Key')}""" ) logging.info( - f"{time()}\tCommand: {decoded_cmd}\tFrom: {request.remote}\tUA: {request.headers.get('User-Agent')}\tIdempotency-Key: {request.headers.get('Idempotency-Key')}" + f"{time}\tCommand: {decoded_cmd}\tFrom: {request.remote}\tUA: {request.headers.get('User-Agent')}\tIdempotency-Key: {request.headers.get('Idempotency-Key')}" ) # Process the command From 0fc3ad49ea6e54e2a37c6cc8badd27469fcb66c2 Mon Sep 17 00:00:00 2001 From: C <0xicl33n@gmail.com> Date: Sun, 12 Oct 2025 11:52:04 -0700 Subject: [PATCH 35/43] global throttle things --- src/asquare.py | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/src/asquare.py b/src/asquare.py index 6241c88..1f12e2d 100644 --- a/src/asquare.py +++ b/src/asquare.py @@ -5,9 +5,6 @@ import asyncio import uuid import re from time import time -import re -# we dont need no stinkin floats -time = int(time()) import db @@ -113,7 +110,9 @@ async def acat2id(cat_id: str): get_set_number(obj.get("item_data", {}).get("name", "")) if re.findall(r"\d{5}", obj.get("item_data", {}).get("name", "")) else None - ) + ), + "updated_at": obj.get("updated_at"), + "version": obj.get("version"), } for obj in result_json.get("objects", []) ] @@ -122,14 +121,10 @@ async def acat2id(cat_id: str): async def test() -> None: - task = asyncio.create_task(acat2id(LEGO_CAT_ID)) # type: ignore - await do_review() - await asyncio.sleep(1) # Give the task some time to complete - result = await task - #print("OBJECTS IN LEGO CATEGORY:") - #print(f"ID: {item['id']}, Name: {item['name']}") - return True - # pass + result = await acat2id(LEGO_CAT_ID) # type: ignore + print("OBJECTS IN LEGO CATEGORY:") + for item in result: + print(f"ID: {item['id']}, Name: {item['name']}, Updated: {item['updated_at']}") """ From 3e03fd7e8cde4cb07f8665a934fd4ba9546af03a Mon Sep 17 00:00:00 2001 From: C <0xicl33n@gmail.com> Date: Tue, 14 Oct 2025 15:52:22 -0700 Subject: [PATCH 36/43] add reflex for web stuff! --- .gitignore | 5 +- pyproject.toml | 1 + uv.lock | 394 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 396 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 4a7cc4d..93b0904 100644 --- a/.gitignore +++ b/.gitignore @@ -32,7 +32,4 @@ studspider.db-journal .web .states assets/external/ -__pycache__/ -win/ -.github/ -activate/ \ No newline at end of file +__pycache__/ \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index e5f4fca..24cd6c6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,6 +17,7 @@ dependencies = [ "apscheduler (>=3.11.0,<4.0.0)", "aiosqlite (>=0.21.0,<0.22.0)", "bs4>=0.0.2", + "reflex>=0.8.14.post1", ] [build-system] diff --git a/uv.lock b/uv.lock index 071a7fb..f990e3c 100644 --- a/uv.lock +++ b/uv.lock @@ -69,6 +69,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f5/10/6c25ed6de94c49f88a91fa5018cb4c0f3625f31d5be9f771ebe5cc7cd506/aiosqlite-0.21.0-py3-none-any.whl", hash = "sha256:2549cf4057f95f53dcba16f2b64e8e2791d7e1adedb13197dd8ed77bb226d7d0", size = 15792, upload-time = "2025-02-03T07:30:13.6Z" }, ] +[[package]] +name = "alembic" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mako" }, + { name = "sqlalchemy" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6b/45/6f4555f2039f364c3ce31399529dcf48dd60726ff3715ad67f547d87dfd2/alembic-1.17.0.tar.gz", hash = "sha256:4652a0b3e19616b57d652b82bfa5e38bf5dbea0813eed971612671cb9e90c0fe", size = 1975526, upload-time = "2025-10-11T18:40:13.585Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/1f/38e29b06bfed7818ebba1f84904afdc8153ef7b6c7e0d8f3bc6643f5989c/alembic-1.17.0-py3-none-any.whl", hash = "sha256:80523bc437d41b35c5db7e525ad9d908f79de65c27d6a5a5eab6df348a352d99", size = 247449, upload-time = "2025-10-11T18:40:16.288Z" }, +] + [[package]] name = "altgraph" version = "0.17.4" @@ -143,6 +157,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/94/fe/3aed5d0be4d404d12d36ab97e2f1791424d9ca39c2f754a6285d59a3b01d/beautifulsoup4-4.14.2-py3-none-any.whl", hash = "sha256:5ef6fa3a8cbece8488d66985560f97ed091e22bbc4e9c2338508a9d5de6d4515", size = 106392, upload-time = "2025-09-29T10:05:43.771Z" }, ] +[[package]] +name = "bidict" +version = "0.23.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/6e/026678aa5a830e07cd9498a05d3e7e650a4f56a42f267a53d22bcda1bdc9/bidict-0.23.1.tar.gz", hash = "sha256:03069d763bc387bbd20e7d49914e75fc4132a41937fa3405417e1a5a2d006d71", size = 29093, upload-time = "2024-02-18T19:09:05.748Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl", hash = "sha256:5dae8d4d79b552a71cbabc7deb25dfe8ce710b17ff41711e13010ead2abfc3e5", size = 32764, upload-time = "2024-02-18T19:09:04.156Z" }, +] + [[package]] name = "bs4" version = "0.0.2" @@ -184,6 +207,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8a/1f/f041989e93b001bc4e44bb1669ccdcf54d3f00e628229a85b08d330615c5/charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a", size = 53175, upload-time = "2025-08-09T07:57:26.864Z" }, ] +[[package]] +name = "click" +version = "8.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/46/61/de6cd827efad202d7057d93e0fed9294b96952e188f7384832791c7b2254/click-8.3.0.tar.gz", hash = "sha256:e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4", size = 276943, upload-time = "2025-09-18T17:32:23.696Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/d3/9dcc0f5797f070ec8edf30fbadfb200e71d9db6b84d211e3b2085a7589a0/click-8.3.0-py3-none-any.whl", hash = "sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc", size = 107295, upload-time = "2025-09-18T17:32:22.42Z" }, +] + [[package]] name = "colorama" version = "0.4.6" @@ -217,6 +252,7 @@ dependencies = [ { name = "dotenv" }, { name = "pyinstaller" }, { name = "pytest" }, + { name = "reflex" }, { name = "requests" }, { name = "squareup" }, ] @@ -231,6 +267,7 @@ requires-dist = [ { name = "dotenv", specifier = ">=0.9.9,<0.10.0" }, { name = "pyinstaller", specifier = ">=6.14.1,<7.0.0" }, { name = "pytest", specifier = ">=8.4.2,<9.0.0" }, + { name = "reflex", specifier = ">=0.8.14.post1" }, { name = "requests", specifier = ">=2.32.4,<3.0.0" }, { name = "squareup", specifier = ">=43.0.0.20250618,<44.0.0.0" }, ] @@ -278,6 +315,57 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ee/45/b82e3c16be2182bff01179db177fe144d58b5dc787a7d4492c6ed8b9317f/frozenlist-1.7.0-py3-none-any.whl", hash = "sha256:9a5af342e34f7e97caf8c995864c7a396418ae2859cc6fdf1b1073020d516a7e", size = 13106, upload-time = "2025-06-09T23:02:34.204Z" }, ] +[[package]] +name = "granian" +version = "2.5.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/07/85/3f5a1258567718c75719f5206d33457f7bd2b091b0fee0a618a395fda758/granian-2.5.5.tar.gz", hash = "sha256:da785fae71cb45e92ce3fbb8633dc48b12f6a5055a7358226d78176967a5d2c9", size = 112143, upload-time = "2025-10-07T17:39:38.751Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/fc/8024558e038477531cdbf996f94ff9d64c008a33ffd33077b38d43451193/granian-2.5.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:49fd6a3f5d9302b8c8da56fcbf29faa7edc5847a460635356c5052584fa7c4b2", size = 2833726, upload-time = "2025-10-07T17:37:56.296Z" }, + { url = "https://files.pythonhosted.org/packages/d9/6e/baae1148dc834bbdf07ca45920641c23ff167b2af338cfcd3551e063aee1/granian-2.5.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:62e7d9dd5b21d7637e062f7ce20d1887069d64d06e16c7bac40e01de4cb54b63", size = 2517287, upload-time = "2025-10-07T17:37:57.949Z" }, + { url = "https://files.pythonhosted.org/packages/77/23/65398e16a121cdab95ac6a31c48172f86ff333ac01dbc8d57c2e9496c668/granian-2.5.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:307e09e1bf5b7f869b9dccfca7af253bbb74bf4cb0ba2d972043334a889b048f", size = 3009557, upload-time = "2025-10-07T17:37:59.664Z" }, + { url = "https://files.pythonhosted.org/packages/24/01/1ec3bb03528cf5330a5b1d2c77f1a4d224a34a3c73ea47a860ef4d72146b/granian-2.5.5-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6a847903664275e6bede3ef178642379c6e1c43767e3980b2b6d68c62e6b14b5", size = 2833794, upload-time = "2025-10-07T17:38:00.993Z" }, + { url = "https://files.pythonhosted.org/packages/1e/a5/fe976a36945cea22b7c4a9eb8ddd9866644c12f766b9f3ab8bd575e9d611/granian-2.5.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e21cd8ee95cd2017328043ec31c455ef69c9466617a9d51e4b1ca0ff0203d873", size = 3117180, upload-time = "2025-10-07T17:38:02.573Z" }, + { url = "https://files.pythonhosted.org/packages/a2/f2/0a7450990a5ec23a3dc5c464886ece21593dc4edd027e3a6f5367fbb0cd4/granian-2.5.5-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:f47b9ecf635ef727b1f399cd2e65329010ab2c48a5381be0d928a538ed8e9158", size = 2917766, upload-time = "2025-10-07T17:38:04.327Z" }, + { url = "https://files.pythonhosted.org/packages/93/81/1238e871ef9e609e12b9ada411793c87343c46905f8f56c8a6d4088d9ae6/granian-2.5.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:59e868dcc7ca469aa099ca9456204c2ff5e6c7e98bbafb66921b911c2b5e4c15", size = 2981663, upload-time = "2025-10-07T17:38:05.722Z" }, + { url = "https://files.pythonhosted.org/packages/b0/3c/0ad5915c0a96d2c4de13e51f107d11013ed9a48e01524ec8cc096afdc639/granian-2.5.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:c0a1ef4bac9a59d52a80f81153a15c82a6876591e9ab8e330e096f339d06211d", size = 3146507, upload-time = "2025-10-07T17:38:07.078Z" }, + { url = "https://files.pythonhosted.org/packages/75/f7/2d3d3372cf81775e72196a1a7b26cf563dbe35ec5cc197dd4f9e3df5d050/granian-2.5.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:748efbb431d46aff3b9ef88b68fddfc7b29e7738292fca841b1b925f67e328a4", size = 3207738, upload-time = "2025-10-07T17:38:08.345Z" }, + { url = "https://files.pythonhosted.org/packages/8d/1b/d663e071cac94f3be515c3650aa6675432d3a9ccbb4c419a20bb31083d92/granian-2.5.5-cp313-cp313-win_amd64.whl", hash = "sha256:fa87dd2d4b914e6d539bf18968daad3d34bb6877ab90b1408c009c3714a0213c", size = 2178462, upload-time = "2025-10-07T17:38:09.708Z" }, + { url = "https://files.pythonhosted.org/packages/9d/9f/edec61a604d919042b435eb6f40217c7ff565fde91e9670183c895eaf4e1/granian-2.5.5-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:647cb53789cbf369e895341152a9822f8238830fc939b85d2844151d1f0da32e", size = 2759098, upload-time = "2025-10-07T17:38:10.962Z" }, + { url = "https://files.pythonhosted.org/packages/52/76/f96df41fbc72b4c9a9f0994f2ae274080a3b47b8209989140a1f98ba27a9/granian-2.5.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:39f1d5398d31df3375e10524aa00f33d65b7d051551c4876251e9eec4ddc2c11", size = 2477953, upload-time = "2025-10-07T17:38:12.329Z" }, + { url = "https://files.pythonhosted.org/packages/e9/70/0bfa32321501756357efebc45668c9ba054f8b8af1c63590d5af2aaed59c/granian-2.5.5-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61308acf1943d06c4c4b48f79697a97f233ce85a07801e95e01c85647fd10eb5", size = 2997992, upload-time = "2025-10-07T17:38:13.627Z" }, + { url = "https://files.pythonhosted.org/packages/3a/a1/9a961b05c6cde2b6b27abae5da9e5efdfe3c7e0fc8b6f63433473861c2ff/granian-2.5.5-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:dfd11cb106d9c68c6682a0554142f7383ff99789e1ecef3087c3e13ac50fde24", size = 2784260, upload-time = "2025-10-07T17:38:15.32Z" }, + { url = "https://files.pythonhosted.org/packages/e2/1a/9eada34cb30c4cd17e8201082b2a5f4d159ed275fdbc072355bc8ab32753/granian-2.5.5-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:d05801e405f4000837bba9eea7cdef0486a6c8a2aff073e75be0a7055c2c07c6", size = 2973232, upload-time = "2025-10-07T17:38:16.717Z" }, + { url = "https://files.pythonhosted.org/packages/5d/24/bbdc6dcb4197da663b2e8f4442f234ccfcd6fd883a9448a0fa3629cd6fe1/granian-2.5.5-cp313-cp313t-musllinux_1_1_armv7l.whl", hash = "sha256:28c61a87c766fc22f5d1ad640e6a4f2a4c14ff86c629c7fa8c9cc0bbc756a18c", size = 3132458, upload-time = "2025-10-07T17:38:18.134Z" }, + { url = "https://files.pythonhosted.org/packages/0f/b9/a44efa4771859bd65ff1e103b57f1c010b8d3434b20120fc0e44d0f28b41/granian-2.5.5-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:1cf9431bc5096e2f38aa8b8c762ad4e5e2f1e119ca7381a3414e1ce64690ab5c", size = 3194088, upload-time = "2025-10-07T17:38:19.408Z" }, + { url = "https://files.pythonhosted.org/packages/f9/03/3d33933328a2003ebd45c5d781387df59b60bc2465c9c5ea308930509ca7/granian-2.5.5-cp313-cp313t-win_amd64.whl", hash = "sha256:63a4910032589d25ce09bc2d1e5164db941710c17fe91a543f8bd3e3b9a5d522", size = 2170457, upload-time = "2025-10-07T17:38:21.077Z" }, +] + +[package.optional-dependencies] +reload = [ + { name = "watchfiles" }, +] + +[[package]] +name = "greenlet" +version = "3.2.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/03/b8/704d753a5a45507a7aab61f18db9509302ed3d0a27ac7e0359ec2905b1a6/greenlet-3.2.4.tar.gz", hash = "sha256:0dca0d95ff849f9a364385f36ab49f50065d76964944638be9691e1832e9f86d", size = 188260, upload-time = "2025-08-07T13:24:33.51Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/e8/58c7f85958bda41dafea50497cbd59738c5c43dbbea5ee83d651234398f4/greenlet-3.2.4-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:1a921e542453fe531144e91e1feedf12e07351b1cf6c9e8a3325ea600a715a31", size = 272814, upload-time = "2025-08-07T13:15:50.011Z" }, + { url = "https://files.pythonhosted.org/packages/62/dd/b9f59862e9e257a16e4e610480cfffd29e3fae018a68c2332090b53aac3d/greenlet-3.2.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd3c8e693bff0fff6ba55f140bf390fa92c994083f838fece0f63be121334945", size = 641073, upload-time = "2025-08-07T13:42:57.23Z" }, + { url = "https://files.pythonhosted.org/packages/f7/0b/bc13f787394920b23073ca3b6c4a7a21396301ed75a655bcb47196b50e6e/greenlet-3.2.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:710638eb93b1fa52823aa91bf75326f9ecdfd5e0466f00789246a5280f4ba0fc", size = 655191, upload-time = "2025-08-07T13:45:29.752Z" }, + { url = "https://files.pythonhosted.org/packages/f2/d6/6adde57d1345a8d0f14d31e4ab9c23cfe8e2cd39c3baf7674b4b0338d266/greenlet-3.2.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c5111ccdc9c88f423426df3fd1811bfc40ed66264d35aa373420a34377efc98a", size = 649516, upload-time = "2025-08-07T13:53:16.314Z" }, + { url = "https://files.pythonhosted.org/packages/7f/3b/3a3328a788d4a473889a2d403199932be55b1b0060f4ddd96ee7cdfcad10/greenlet-3.2.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d76383238584e9711e20ebe14db6c88ddcedc1829a9ad31a584389463b5aa504", size = 652169, upload-time = "2025-08-07T13:18:32.861Z" }, + { url = "https://files.pythonhosted.org/packages/ee/43/3cecdc0349359e1a527cbf2e3e28e5f8f06d3343aaf82ca13437a9aa290f/greenlet-3.2.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23768528f2911bcd7e475210822ffb5254ed10d71f4028387e5a99b4c6699671", size = 610497, upload-time = "2025-08-07T13:18:31.636Z" }, + { url = "https://files.pythonhosted.org/packages/b8/19/06b6cf5d604e2c382a6f31cafafd6f33d5dea706f4db7bdab184bad2b21d/greenlet-3.2.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:00fadb3fedccc447f517ee0d3fd8fe49eae949e1cd0f6a611818f4f6fb7dc83b", size = 1121662, upload-time = "2025-08-07T13:42:41.117Z" }, + { url = "https://files.pythonhosted.org/packages/a2/15/0d5e4e1a66fab130d98168fe984c509249c833c1a3c16806b90f253ce7b9/greenlet-3.2.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d25c5091190f2dc0eaa3f950252122edbbadbb682aa7b1ef2f8af0f8c0afefae", size = 1149210, upload-time = "2025-08-07T13:18:24.072Z" }, + { url = "https://files.pythonhosted.org/packages/0b/55/2321e43595e6801e105fcfdee02b34c0f996eb71e6ddffca6b10b7e1d771/greenlet-3.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:554b03b6e73aaabec3745364d6239e9e012d64c68ccd0b8430c64ccc14939a8b", size = 299685, upload-time = "2025-08-07T13:24:38.824Z" }, +] + [[package]] name = "h11" version = "0.16.0" @@ -345,6 +433,69 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/5d/c059c180c84f7962db0aeae7c3b9303ed1d73d76f2bfbc32bc231c8be314/macholib-1.16.3-py2.py3-none-any.whl", hash = "sha256:0e315d7583d38b8c77e815b1ecbdbf504a8258d8b3e17b61165c6feb60d18f2c", size = 38094, upload-time = "2023-09-25T09:10:14.188Z" }, ] +[[package]] +name = "mako" +version = "1.3.10" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9e/38/bd5b78a920a64d708fe6bc8e0a2c075e1389d53bef8413725c63ba041535/mako-1.3.10.tar.gz", hash = "sha256:99579a6f39583fa7e5630a28c3c1f440e4e97a414b80372649c0ce338da2ea28", size = 392474, upload-time = "2025-04-10T12:44:31.16Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/fb/99f81ac72ae23375f22b7afdb7642aba97c00a713c217124420147681a2f/mako-1.3.10-py3-none-any.whl", hash = "sha256:baef24a52fc4fc514a0887ac600f9f1cff3d82c61d4d700a1fa84d597b88db59", size = 78509, upload-time = "2025-04-10T12:50:53.297Z" }, +] + +[[package]] +name = "markdown-it-py" +version = "4.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, +] + [[package]] name = "multidict" version = "6.6.4" @@ -408,6 +559,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/55/26/d0ad8b448476d0a1e8d3ea5622dc77b916db84c6aa3cb1e1c0965af948fc/pefile-2023.2.7-py3-none-any.whl", hash = "sha256:da185cd2af68c08a6cd4481f7325ed600a88f6a813bad9dea07ab3ef73d8d8d6", size = 71791, upload-time = "2023-02-07T12:28:36.678Z" }, ] +[[package]] +name = "platformdirs" +version = "4.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/61/33/9611380c2bdb1225fdef633e2a9610622310fed35ab11dac9620972ee088/platformdirs-4.5.0.tar.gz", hash = "sha256:70ddccdd7c99fc5942e9fc25636a8b34d04c24b335100223152c2803e4063312", size = 21632, upload-time = "2025-10-08T17:44:48.791Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl", hash = "sha256:e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3", size = 18651, upload-time = "2025-10-08T17:44:47.223Z" }, +] + [[package]] name = "pluggy" version = "1.6.0" @@ -458,6 +618,17 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cc/35/cc0aaecf278bb4575b8555f2b137de5ab821595ddae9da9d3cd1da4072c7/propcache-0.3.2-py3-none-any.whl", hash = "sha256:98f1ec44fb675f5052cccc8e609c46ed23a35a1cfd18545ad4e29002d858a43f", size = 12663, upload-time = "2025-06-09T22:56:04.484Z" }, ] +[[package]] +name = "psutil" +version = "7.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/31/4723d756b59344b643542936e37a31d1d3204bcdc42a7daa8ee9eb06fb50/psutil-7.1.0.tar.gz", hash = "sha256:655708b3c069387c8b77b072fc429a57d0e214221d01c0a772df7dfedcb3bcd2", size = 497660, upload-time = "2025-09-17T20:14:52.902Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/87/157c8e7959ec39ced1b11cc93c730c4fb7f9d408569a6c59dbd92ceb35db/psutil-7.1.0-cp37-abi3-win32.whl", hash = "sha256:09ad740870c8d219ed8daae0ad3b726d3bf9a028a198e7f3080f6a1888b99bca", size = 244812, upload-time = "2025-09-17T20:15:07.462Z" }, + { url = "https://files.pythonhosted.org/packages/bf/e9/b44c4f697276a7a95b8e94d0e320a7bf7f3318521b23de69035540b39838/psutil-7.1.0-cp37-abi3-win_amd64.whl", hash = "sha256:57f5e987c36d3146c0dd2528cd42151cf96cd359b9d67cfff836995cc5df9a3d", size = 247965, upload-time = "2025-09-17T20:15:09.673Z" }, + { url = "https://files.pythonhosted.org/packages/26/65/1070a6e3c036f39142c2820c4b52e9243246fcfc3f96239ac84472ba361e/psutil-7.1.0-cp37-abi3-win_arm64.whl", hash = "sha256:6937cb68133e7c97b6cc9649a570c9a18ba0efebed46d8c5dae4c07fa1b67a07", size = 244971, upload-time = "2025-09-17T20:15:12.262Z" }, +] + [[package]] name = "pydantic" version = "2.11.7" @@ -576,6 +747,40 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556, upload-time = "2025-06-24T04:21:06.073Z" }, ] +[[package]] +name = "python-engineio" +version = "4.12.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "simple-websocket" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/d8/63e5535ab21dc4998ba1cfe13690ccf122883a38f025dca24d6e56c05eba/python_engineio-4.12.3.tar.gz", hash = "sha256:35633e55ec30915e7fc8f7e34ca8d73ee0c080cec8a8cd04faf2d7396f0a7a7a", size = 91910, upload-time = "2025-09-28T06:31:36.765Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d8/f0/c5aa0a69fd9326f013110653543f36ece4913c17921f3e1dbd78e1b423ee/python_engineio-4.12.3-py3-none-any.whl", hash = "sha256:7c099abb2a27ea7ab429c04da86ab2d82698cdd6c52406cb73766fe454feb7e1", size = 59637, upload-time = "2025-09-28T06:31:35.354Z" }, +] + +[[package]] +name = "python-multipart" +version = "0.0.20" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/87/f44d7c9f274c7ee665a29b885ec97089ec5dc034c7f3fafa03da9e39a09e/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13", size = 37158, upload-time = "2024-12-16T19:45:46.972Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546, upload-time = "2024-12-16T19:45:44.423Z" }, +] + +[[package]] +name = "python-socketio" +version = "5.14.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "bidict" }, + { name = "python-engineio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/05/c2/a9ae3d0eb4488748a2d9c15defddb7277a852234e29e50c73136834dff1b/python_socketio-5.14.1.tar.gz", hash = "sha256:bf49657073b90ee09e4cbd6651044b46bb526694276621e807a1b8fcc0c1b25b", size = 123068, upload-time = "2025-10-02T18:44:54.467Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d5/5e/302c3499a134a52b68e4e6fb345cea52ab1c41460949bcdb09f8bd0e3594/python_socketio-5.14.1-py3-none-any.whl", hash = "sha256:3419f5917f0e3942317836a77146cb4caa23ad804c8fd1a7e3f44a6657a8406e", size = 78496, upload-time = "2025-10-02T18:44:52.649Z" }, +] + [[package]] name = "pywin32-ctypes" version = "0.2.3" @@ -585,6 +790,59 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/de/3d/8161f7711c017e01ac9f008dfddd9410dff3674334c233bde66e7ba65bbf/pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8", size = 30756, upload-time = "2024-08-14T10:15:33.187Z" }, ] +[[package]] +name = "redis" +version = "6.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/d6/e8b92798a5bd67d659d51a18170e91c16ac3b59738d91894651ee255ed49/redis-6.4.0.tar.gz", hash = "sha256:b01bc7282b8444e28ec36b261df5375183bb47a07eb9c603f284e89cbc5ef010", size = 4647399, upload-time = "2025-08-07T08:10:11.441Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/02/89e2ed7e85db6c93dfa9e8f691c5087df4e3551ab39081a4d7c6d1f90e05/redis-6.4.0-py3-none-any.whl", hash = "sha256:f0544fa9604264e9464cdf4814e7d4830f74b165d52f2a330a760a88dd248b7f", size = 279847, upload-time = "2025-08-07T08:10:09.84Z" }, +] + +[[package]] +name = "reflex" +version = "0.8.14.post1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "alembic" }, + { name = "click" }, + { name = "granian", extra = ["reload"] }, + { name = "httpx" }, + { name = "packaging" }, + { name = "platformdirs" }, + { name = "psutil", marker = "sys_platform == 'win32'" }, + { name = "pydantic" }, + { name = "python-multipart" }, + { name = "python-socketio" }, + { name = "redis" }, + { name = "reflex-hosting-cli" }, + { name = "rich" }, + { name = "sqlmodel" }, + { name = "starlette" }, + { name = "typing-extensions" }, + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/78/fe/b08a7cd740474ef63386c4d69c36b30e0eb4644f97cfcd179c996581f1e8/reflex-0.8.14.post1.tar.gz", hash = "sha256:d78dac6c7176477b68dcb84720efe7972a4b3f52f4ab0d3a95cac3cab02ed4ac", size = 604830, upload-time = "2025-10-06T21:30:35.535Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/b3/f8e2a07c94396285cff5e585bce3a3071fe4e9fb112ad8b521b47677cec2/reflex-0.8.14.post1-py3-none-any.whl", hash = "sha256:3bd986cd9e8e6481d14276e95f82125acd5ca3a7492dcaf57c317211b20354b6", size = 894684, upload-time = "2025-10-06T21:30:33.189Z" }, +] + +[[package]] +name = "reflex-hosting-cli" +version = "0.1.56" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "httpx" }, + { name = "packaging" }, + { name = "platformdirs" }, + { name = "rich" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/98/51/029207a63593f25cddb9cfe5c59147cf1e5de9a967dfa87cd8b6296e7c41/reflex_hosting_cli-0.1.56.tar.gz", hash = "sha256:b29bb0a2bee0ee59ac4f1c7beecc727e914c3c0ab73fc4a536a5b33c237f40b7", size = 35889, upload-time = "2025-09-25T03:28:29.977Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/10/ab023eabe375eed3a0e0ac3350f007465e10a59ca6d70dbde455abcb404d/reflex_hosting_cli-0.1.56-py3-none-any.whl", hash = "sha256:9c6e5f0a26ab61fa20a89a4ea053d36d94e3134507fc1fca428500a0738139b2", size = 45420, upload-time = "2025-09-25T03:28:28.685Z" }, +] + [[package]] name = "requests" version = "2.32.5" @@ -600,6 +858,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, ] +[[package]] +name = "rich" +version = "14.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fb/d2/8920e102050a0de7bfabeb4c4614a49248cf8d5d7a8d01885fbb24dc767a/rich-14.2.0.tar.gz", hash = "sha256:73ff50c7c0c1c77c8243079283f4edb376f0f6442433aecb8ce7e6d0b92d1fe4", size = 219990, upload-time = "2025-10-09T14:16:53.064Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl", hash = "sha256:76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd", size = 243393, upload-time = "2025-10-09T14:16:51.245Z" }, +] + [[package]] name = "setuptools" version = "80.9.0" @@ -609,6 +880,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486, upload-time = "2025-05-27T00:56:49.664Z" }, ] +[[package]] +name = "simple-websocket" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wsproto" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b0/d4/bfa032f961103eba93de583b161f0e6a5b63cebb8f2c7d0c6e6efe1e3d2e/simple_websocket-1.1.0.tar.gz", hash = "sha256:7939234e7aa067c534abdab3a9ed933ec9ce4691b0713c78acb195560aa52ae4", size = 17300, upload-time = "2024-10-10T22:39:31.412Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl", hash = "sha256:4af6069630a38ed6c561010f0e11a5bc0d4ca569b36306eb257cd9a192497c8c", size = 13842, upload-time = "2024-10-10T22:39:29.645Z" }, +] + [[package]] name = "sniffio" version = "1.3.1" @@ -627,6 +910,40 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/14/a0/bb38d3b76b8cae341dad93a2dd83ab7462e6dbcdd84d43f54ee60a8dc167/soupsieve-2.8-py3-none-any.whl", hash = "sha256:0cc76456a30e20f5d7f2e14a98a4ae2ee4e5abdc7c5ea0aafe795f344bc7984c", size = 36679, upload-time = "2025-08-27T15:39:50.179Z" }, ] +[[package]] +name = "sqlalchemy" +version = "2.0.44" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f0/f2/840d7b9496825333f532d2e3976b8eadbf52034178aac53630d09fe6e1ef/sqlalchemy-2.0.44.tar.gz", hash = "sha256:0ae7454e1ab1d780aee69fd2aae7d6b8670a581d8847f2d1e0f7ddfbf47e5a22", size = 9819830, upload-time = "2025-10-10T14:39:12.935Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/45/d3/c67077a2249fdb455246e6853166360054c331db4613cda3e31ab1cadbef/sqlalchemy-2.0.44-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ff486e183d151e51b1d694c7aa1695747599bb00b9f5f604092b54b74c64a8e1", size = 2135479, upload-time = "2025-10-10T16:03:37.671Z" }, + { url = "https://files.pythonhosted.org/packages/2b/91/eabd0688330d6fd114f5f12c4f89b0d02929f525e6bf7ff80aa17ca802af/sqlalchemy-2.0.44-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0b1af8392eb27b372ddb783b317dea0f650241cea5bd29199b22235299ca2e45", size = 2123212, upload-time = "2025-10-10T16:03:41.755Z" }, + { url = "https://files.pythonhosted.org/packages/b0/bb/43e246cfe0e81c018076a16036d9b548c4cc649de241fa27d8d9ca6f85ab/sqlalchemy-2.0.44-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b61188657e3a2b9ac4e8f04d6cf8e51046e28175f79464c67f2fd35bceb0976", size = 3255353, upload-time = "2025-10-10T15:35:31.221Z" }, + { url = "https://files.pythonhosted.org/packages/b9/96/c6105ed9a880abe346b64d3b6ddef269ddfcab04f7f3d90a0bf3c5a88e82/sqlalchemy-2.0.44-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b87e7b91a5d5973dda5f00cd61ef72ad75a1db73a386b62877d4875a8840959c", size = 3260222, upload-time = "2025-10-10T15:43:50.124Z" }, + { url = "https://files.pythonhosted.org/packages/44/16/1857e35a47155b5ad927272fee81ae49d398959cb749edca6eaa399b582f/sqlalchemy-2.0.44-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:15f3326f7f0b2bfe406ee562e17f43f36e16167af99c4c0df61db668de20002d", size = 3189614, upload-time = "2025-10-10T15:35:32.578Z" }, + { url = "https://files.pythonhosted.org/packages/88/ee/4afb39a8ee4fc786e2d716c20ab87b5b1fb33d4ac4129a1aaa574ae8a585/sqlalchemy-2.0.44-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1e77faf6ff919aa8cd63f1c4e561cac1d9a454a191bb864d5dd5e545935e5a40", size = 3226248, upload-time = "2025-10-10T15:43:51.862Z" }, + { url = "https://files.pythonhosted.org/packages/32/d5/0e66097fc64fa266f29a7963296b40a80d6a997b7ac13806183700676f86/sqlalchemy-2.0.44-cp313-cp313-win32.whl", hash = "sha256:ee51625c2d51f8baadf2829fae817ad0b66b140573939dd69284d2ba3553ae73", size = 2101275, upload-time = "2025-10-10T15:03:26.096Z" }, + { url = "https://files.pythonhosted.org/packages/03/51/665617fe4f8c6450f42a6d8d69243f9420f5677395572c2fe9d21b493b7b/sqlalchemy-2.0.44-cp313-cp313-win_amd64.whl", hash = "sha256:c1c80faaee1a6c3428cecf40d16a2365bcf56c424c92c2b6f0f9ad204b899e9e", size = 2127901, upload-time = "2025-10-10T15:03:27.548Z" }, + { url = "https://files.pythonhosted.org/packages/9c/5e/6a29fa884d9fb7ddadf6b69490a9d45fded3b38541713010dad16b77d015/sqlalchemy-2.0.44-py3-none-any.whl", hash = "sha256:19de7ca1246fbef9f9d1bff8f1ab25641569df226364a0e40457dc5457c54b05", size = 1928718, upload-time = "2025-10-10T15:29:45.32Z" }, +] + +[[package]] +name = "sqlmodel" +version = "0.0.27" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "sqlalchemy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/90/5a/693d90866233e837d182da76082a6d4c2303f54d3aaaa5c78e1238c5d863/sqlmodel-0.0.27.tar.gz", hash = "sha256:ad1227f2014a03905aef32e21428640848ac09ff793047744a73dfdd077ff620", size = 118053, upload-time = "2025-10-08T16:39:11.938Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8c/92/c35e036151fe53822893979f8a13e6f235ae8191f4164a79ae60a95d66aa/sqlmodel-0.0.27-py3-none-any.whl", hash = "sha256:667fe10aa8ff5438134668228dc7d7a08306f4c5c4c7e6ad3ad68defa0e7aa49", size = 29131, upload-time = "2025-10-08T16:39:10.917Z" }, +] + [[package]] name = "squareup" version = "43.0.2.20250820" @@ -642,6 +959,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/aa/3e/162f70cdaa9f68864f77db4c29aaa70754800181b06b629dc66461c7135e/squareup-43.0.2.20250820-py3-none-any.whl", hash = "sha256:26de4e23c9bb735d4c5bb89a72ff1bd6678b03b39d30e01acd6047d5db9cfc9f", size = 2205057, upload-time = "2025-08-20T15:13:33.199Z" }, ] +[[package]] +name = "starlette" +version = "0.48.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a7/a5/d6f429d43394057b67a6b5bbe6eae2f77a6bf7459d961fdb224bf206eee6/starlette-0.48.0.tar.gz", hash = "sha256:7e8cee469a8ab2352911528110ce9088fdc6a37d9876926e73da7ce4aa4c7a46", size = 2652949, upload-time = "2025-09-13T08:41:05.699Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/72/2db2f49247d0a18b4f1bb9a5a39a0162869acf235f3a96418363947b3d46/starlette-0.48.0-py3-none-any.whl", hash = "sha256:0764ca97b097582558ecb498132ed0c7d942f233f365b86ba37770e026510659", size = 73736, upload-time = "2025-09-13T08:41:03.869Z" }, +] + [[package]] name = "typing-extensions" version = "4.15.0" @@ -693,6 +1022,71 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, ] +[[package]] +name = "watchfiles" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c2/c9/8869df9b2a2d6c59d79220a4db37679e74f807c559ffe5265e08b227a210/watchfiles-1.1.1.tar.gz", hash = "sha256:a173cb5c16c4f40ab19cecf48a534c409f7ea983ab8fed0741304a1c0a31b3f2", size = 94440, upload-time = "2025-10-14T15:06:21.08Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bb/f4/f750b29225fe77139f7ae5de89d4949f5a99f934c65a1f1c0b248f26f747/watchfiles-1.1.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:130e4876309e8686a5e37dba7d5e9bc77e6ed908266996ca26572437a5271e18", size = 404321, upload-time = "2025-10-14T15:05:02.063Z" }, + { url = "https://files.pythonhosted.org/packages/2b/f9/f07a295cde762644aa4c4bb0f88921d2d141af45e735b965fb2e87858328/watchfiles-1.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5f3bde70f157f84ece3765b42b4a52c6ac1a50334903c6eaf765362f6ccca88a", size = 391783, upload-time = "2025-10-14T15:05:03.052Z" }, + { url = "https://files.pythonhosted.org/packages/bc/11/fc2502457e0bea39a5c958d86d2cb69e407a4d00b85735ca724bfa6e0d1a/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14e0b1fe858430fc0251737ef3824c54027bedb8c37c38114488b8e131cf8219", size = 449279, upload-time = "2025-10-14T15:05:04.004Z" }, + { url = "https://files.pythonhosted.org/packages/e3/1f/d66bc15ea0b728df3ed96a539c777acfcad0eb78555ad9efcaa1274688f0/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f27db948078f3823a6bb3b465180db8ebecf26dd5dae6f6180bd87383b6b4428", size = 459405, upload-time = "2025-10-14T15:05:04.942Z" }, + { url = "https://files.pythonhosted.org/packages/be/90/9f4a65c0aec3ccf032703e6db02d89a157462fbb2cf20dd415128251cac0/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:059098c3a429f62fc98e8ec62b982230ef2c8df68c79e826e37b895bc359a9c0", size = 488976, upload-time = "2025-10-14T15:05:05.905Z" }, + { url = "https://files.pythonhosted.org/packages/37/57/ee347af605d867f712be7029bb94c8c071732a4b44792e3176fa3c612d39/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfb5862016acc9b869bb57284e6cb35fdf8e22fe59f7548858e2f971d045f150", size = 595506, upload-time = "2025-10-14T15:05:06.906Z" }, + { url = "https://files.pythonhosted.org/packages/a8/78/cc5ab0b86c122047f75e8fc471c67a04dee395daf847d3e59381996c8707/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:319b27255aacd9923b8a276bb14d21a5f7ff82564c744235fc5eae58d95422ae", size = 474936, upload-time = "2025-10-14T15:05:07.906Z" }, + { url = "https://files.pythonhosted.org/packages/62/da/def65b170a3815af7bd40a3e7010bf6ab53089ef1b75d05dd5385b87cf08/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c755367e51db90e75b19454b680903631d41f9e3607fbd941d296a020c2d752d", size = 456147, upload-time = "2025-10-14T15:05:09.138Z" }, + { url = "https://files.pythonhosted.org/packages/57/99/da6573ba71166e82d288d4df0839128004c67d2778d3b566c138695f5c0b/watchfiles-1.1.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c22c776292a23bfc7237a98f791b9ad3144b02116ff10d820829ce62dff46d0b", size = 630007, upload-time = "2025-10-14T15:05:10.117Z" }, + { url = "https://files.pythonhosted.org/packages/a8/51/7439c4dd39511368849eb1e53279cd3454b4a4dbace80bab88feeb83c6b5/watchfiles-1.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:3a476189be23c3686bc2f4321dd501cb329c0a0469e77b7b534ee10129ae6374", size = 622280, upload-time = "2025-10-14T15:05:11.146Z" }, + { url = "https://files.pythonhosted.org/packages/95/9c/8ed97d4bba5db6fdcdb2b298d3898f2dd5c20f6b73aee04eabe56c59677e/watchfiles-1.1.1-cp313-cp313-win32.whl", hash = "sha256:bf0a91bfb5574a2f7fc223cf95eeea79abfefa404bf1ea5e339c0c1560ae99a0", size = 272056, upload-time = "2025-10-14T15:05:12.156Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f3/c14e28429f744a260d8ceae18bf58c1d5fa56b50d006a7a9f80e1882cb0d/watchfiles-1.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:52e06553899e11e8074503c8e716d574adeeb7e68913115c4b3653c53f9bae42", size = 288162, upload-time = "2025-10-14T15:05:13.208Z" }, + { url = "https://files.pythonhosted.org/packages/dc/61/fe0e56c40d5cd29523e398d31153218718c5786b5e636d9ae8ae79453d27/watchfiles-1.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:ac3cc5759570cd02662b15fbcd9d917f7ecd47efe0d6b40474eafd246f91ea18", size = 277909, upload-time = "2025-10-14T15:05:14.49Z" }, + { url = "https://files.pythonhosted.org/packages/79/42/e0a7d749626f1e28c7108a99fb9bf524b501bbbeb9b261ceecde644d5a07/watchfiles-1.1.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:563b116874a9a7ce6f96f87cd0b94f7faf92d08d0021e837796f0a14318ef8da", size = 403389, upload-time = "2025-10-14T15:05:15.777Z" }, + { url = "https://files.pythonhosted.org/packages/15/49/08732f90ce0fbbc13913f9f215c689cfc9ced345fb1bcd8829a50007cc8d/watchfiles-1.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3ad9fe1dae4ab4212d8c91e80b832425e24f421703b5a42ef2e4a1e215aff051", size = 389964, upload-time = "2025-10-14T15:05:16.85Z" }, + { url = "https://files.pythonhosted.org/packages/27/0d/7c315d4bd5f2538910491a0393c56bf70d333d51bc5b34bee8e68e8cea19/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce70f96a46b894b36eba678f153f052967a0d06d5b5a19b336ab0dbbd029f73e", size = 448114, upload-time = "2025-10-14T15:05:17.876Z" }, + { url = "https://files.pythonhosted.org/packages/c3/24/9e096de47a4d11bc4df41e9d1e61776393eac4cb6eb11b3e23315b78b2cc/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cb467c999c2eff23a6417e58d75e5828716f42ed8289fe6b77a7e5a91036ca70", size = 460264, upload-time = "2025-10-14T15:05:18.962Z" }, + { url = "https://files.pythonhosted.org/packages/cc/0f/e8dea6375f1d3ba5fcb0b3583e2b493e77379834c74fd5a22d66d85d6540/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:836398932192dae4146c8f6f737d74baeac8b70ce14831a239bdb1ca882fc261", size = 487877, upload-time = "2025-10-14T15:05:20.094Z" }, + { url = "https://files.pythonhosted.org/packages/ac/5b/df24cfc6424a12deb41503b64d42fbea6b8cb357ec62ca84a5a3476f654a/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:743185e7372b7bc7c389e1badcc606931a827112fbbd37f14c537320fca08620", size = 595176, upload-time = "2025-10-14T15:05:21.134Z" }, + { url = "https://files.pythonhosted.org/packages/8f/b5/853b6757f7347de4e9b37e8cc3289283fb983cba1ab4d2d7144694871d9c/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:afaeff7696e0ad9f02cbb8f56365ff4686ab205fcf9c4c5b6fdfaaa16549dd04", size = 473577, upload-time = "2025-10-14T15:05:22.306Z" }, + { url = "https://files.pythonhosted.org/packages/e1/f7/0a4467be0a56e80447c8529c9fce5b38eab4f513cb3d9bf82e7392a5696b/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f7eb7da0eb23aa2ba036d4f616d46906013a68caf61b7fdbe42fc8b25132e77", size = 455425, upload-time = "2025-10-14T15:05:23.348Z" }, + { url = "https://files.pythonhosted.org/packages/8e/e0/82583485ea00137ddf69bc84a2db88bd92ab4a6e3c405e5fb878ead8d0e7/watchfiles-1.1.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:831a62658609f0e5c64178211c942ace999517f5770fe9436be4c2faeba0c0ef", size = 628826, upload-time = "2025-10-14T15:05:24.398Z" }, + { url = "https://files.pythonhosted.org/packages/28/9a/a785356fccf9fae84c0cc90570f11702ae9571036fb25932f1242c82191c/watchfiles-1.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:f9a2ae5c91cecc9edd47e041a930490c31c3afb1f5e6d71de3dc671bfaca02bf", size = 622208, upload-time = "2025-10-14T15:05:25.45Z" }, +] + +[[package]] +name = "wrapt" +version = "1.17.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/8f/aeb76c5b46e273670962298c23e7ddde79916cb74db802131d49a85e4b7d/wrapt-1.17.3.tar.gz", hash = "sha256:f66eb08feaa410fe4eebd17f2a2c8e2e46d3476e9f8c783daa8e09e0faa666d0", size = 55547, upload-time = "2025-08-12T05:53:21.714Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fc/f6/759ece88472157acb55fc195e5b116e06730f1b651b5b314c66291729193/wrapt-1.17.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a47681378a0439215912ef542c45a783484d4dd82bac412b71e59cf9c0e1cea0", size = 54003, upload-time = "2025-08-12T05:51:48.627Z" }, + { url = "https://files.pythonhosted.org/packages/4f/a9/49940b9dc6d47027dc850c116d79b4155f15c08547d04db0f07121499347/wrapt-1.17.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:54a30837587c6ee3cd1a4d1c2ec5d24e77984d44e2f34547e2323ddb4e22eb77", size = 39025, upload-time = "2025-08-12T05:51:37.156Z" }, + { url = "https://files.pythonhosted.org/packages/45/35/6a08de0f2c96dcdd7fe464d7420ddb9a7655a6561150e5fc4da9356aeaab/wrapt-1.17.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:16ecf15d6af39246fe33e507105d67e4b81d8f8d2c6598ff7e3ca1b8a37213f7", size = 39108, upload-time = "2025-08-12T05:51:58.425Z" }, + { url = "https://files.pythonhosted.org/packages/0c/37/6faf15cfa41bf1f3dba80cd3f5ccc6622dfccb660ab26ed79f0178c7497f/wrapt-1.17.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6fd1ad24dc235e4ab88cda009e19bf347aabb975e44fd5c2fb22a3f6e4141277", size = 88072, upload-time = "2025-08-12T05:52:37.53Z" }, + { url = "https://files.pythonhosted.org/packages/78/f2/efe19ada4a38e4e15b6dff39c3e3f3f73f5decf901f66e6f72fe79623a06/wrapt-1.17.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ed61b7c2d49cee3c027372df5809a59d60cf1b6c2f81ee980a091f3afed6a2d", size = 88214, upload-time = "2025-08-12T05:52:15.886Z" }, + { url = "https://files.pythonhosted.org/packages/40/90/ca86701e9de1622b16e09689fc24b76f69b06bb0150990f6f4e8b0eeb576/wrapt-1.17.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:423ed5420ad5f5529db9ce89eac09c8a2f97da18eb1c870237e84c5a5c2d60aa", size = 87105, upload-time = "2025-08-12T05:52:17.914Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e0/d10bd257c9a3e15cbf5523025252cc14d77468e8ed644aafb2d6f54cb95d/wrapt-1.17.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e01375f275f010fcbf7f643b4279896d04e571889b8a5b3f848423d91bf07050", size = 87766, upload-time = "2025-08-12T05:52:39.243Z" }, + { url = "https://files.pythonhosted.org/packages/e8/cf/7d848740203c7b4b27eb55dbfede11aca974a51c3d894f6cc4b865f42f58/wrapt-1.17.3-cp313-cp313-win32.whl", hash = "sha256:53e5e39ff71b3fc484df8a522c933ea2b7cdd0d5d15ae82e5b23fde87d44cbd8", size = 36711, upload-time = "2025-08-12T05:53:10.074Z" }, + { url = "https://files.pythonhosted.org/packages/57/54/35a84d0a4d23ea675994104e667ceff49227ce473ba6a59ba2c84f250b74/wrapt-1.17.3-cp313-cp313-win_amd64.whl", hash = "sha256:1f0b2f40cf341ee8cc1a97d51ff50dddb9fcc73241b9143ec74b30fc4f44f6cb", size = 38885, upload-time = "2025-08-12T05:53:08.695Z" }, + { url = "https://files.pythonhosted.org/packages/01/77/66e54407c59d7b02a3c4e0af3783168fff8e5d61def52cda8728439d86bc/wrapt-1.17.3-cp313-cp313-win_arm64.whl", hash = "sha256:7425ac3c54430f5fc5e7b6f41d41e704db073309acfc09305816bc6a0b26bb16", size = 36896, upload-time = "2025-08-12T05:52:55.34Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f6/a933bd70f98e9cf3e08167fc5cd7aaaca49147e48411c0bd5ae701bb2194/wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22", size = 23591, upload-time = "2025-08-12T05:53:20.674Z" }, +] + +[[package]] +name = "wsproto" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/4a/44d3c295350d776427904d73c189e10aeae66d7f555bb2feee16d1e4ba5a/wsproto-1.2.0.tar.gz", hash = "sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065", size = 53425, upload-time = "2022-08-23T19:58:21.447Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/58/e860788190eba3bcce367f74d29c4675466ce8dddfba85f7827588416f01/wsproto-1.2.0-py3-none-any.whl", hash = "sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736", size = 24226, upload-time = "2022-08-23T19:58:19.96Z" }, +] + [[package]] name = "yarl" version = "1.20.1" From f514a577c8b8e4b8d0377a089c94373a3c149b7a Mon Sep 17 00:00:00 2001 From: C <0xicl33n@gmail.com> Date: Tue, 14 Oct 2025 15:52:42 -0700 Subject: [PATCH 37/43] all the reflex web junk lole --- src/status/assets/favicon.ico | Bin 0 -> 4286 bytes src/status/requirements.txt | 2 ++ src/status/rxconfig.py | 9 +++++++++ src/status/web/__init__.py | 0 src/status/web/web.py | 36 ++++++++++++++++++++++++++++++++++ 5 files changed, 47 insertions(+) create mode 100644 src/status/assets/favicon.ico create mode 100644 src/status/requirements.txt create mode 100644 src/status/rxconfig.py create mode 100644 src/status/web/__init__.py create mode 100644 src/status/web/web.py diff --git a/src/status/assets/favicon.ico b/src/status/assets/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..166ae995eaa63fc96771410a758282dc30e925cf GIT binary patch literal 4286 zcmeHL>rYc>81ELdEe;}zmYd}cUgmJRfwjUwD1`#s5KZP>mMqza#Viv|_7|8f+0+bX zHuqusuw-7Ca`DTu#4U4^o2bjO#K>4%N?Wdi*wZ3Vx%~Ef4}D1`U_EMRg3u z#2#M|V>}}q-@IaO@{9R}d*u7f&~5HfxSkmHVcazU#i30H zAGxQ5Spe!j9`KuGqR@aExK`-}sH1jvqoIp3C7Vm)9Tu=UPE;j^esN~a6^a$ZILngo;^ zGLXl(ZFyY&U!li`6}y-hUQ99v?s`U4O!kgog74FPw-9g+V)qs!jFGEQyvBf><U|E2vRmx|+(VI~S=lT?@~C5pvZOd`x{Q_+3tG6H=gtdWcf z)+7-Zp=UqH^J4sk^>_G-Ufn-2Hz z2mN12|C{5}U`^eCQuFz=F%wp@}SzA1MHEaM^CtJs<{}Tzu$bx2orTKiedgmtVGM{ zdd#vX`&cuiec|My_KW;y{Ryz2kFu9}=~us6hvx1ZqQCk(d+>HP>ks>mmHCjjDh{pe zKQkKpk0SeDX#XMqf$}QV{z=xrN!mQczJAvud@;zFqaU1ocq==Py)qsa=8UKrt!J7r z{RsTo^rgtZo%$rak)DN*D)!(Y^$@yL6Nd=#eu&?unzhH8yq>v{gkt8xcG3S%H)-y_ zqQ1|v|JT$0R~Y}omg2Y+nDvR+K|kzR5i^fmKF>j~N;A35Vr`JWh4yRqKl#P|qlx?` z@|CmBiP}ysYO%m2{eBG6&ix5 zr#u((F2{vb=W4jNmTQh3M^F2o80T49?w>*rv0mt)-o1y!{hRk`E#UVPdna6jnz`rw dKpn)r^--YJZpr;bYU`N~>#v3X5BRU&{{=gv-{1fM literal 0 HcmV?d00001 diff --git a/src/status/requirements.txt b/src/status/requirements.txt new file mode 100644 index 0000000..d9fa1f4 --- /dev/null +++ b/src/status/requirements.txt @@ -0,0 +1,2 @@ + +reflex==0.8.14.post1 \ No newline at end of file diff --git a/src/status/rxconfig.py b/src/status/rxconfig.py new file mode 100644 index 0000000..442ee0e --- /dev/null +++ b/src/status/rxconfig.py @@ -0,0 +1,9 @@ +import reflex as rx + +config = rx.Config( + app_name="web", + plugins=[ + rx.plugins.SitemapPlugin(), + rx.plugins.TailwindV4Plugin(), + ] +) \ No newline at end of file diff --git a/src/status/web/__init__.py b/src/status/web/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/status/web/web.py b/src/status/web/web.py new file mode 100644 index 0000000..4d7059d --- /dev/null +++ b/src/status/web/web.py @@ -0,0 +1,36 @@ +"""Welcome to Reflex! This file outlines the steps to create a basic app.""" + +import reflex as rx + +from rxconfig import config + + +class State(rx.State): + """The app state.""" + + +def index() -> rx.Component: + # Welcome Page (Index) + return rx.container( + rx.color_mode.button(position="top-right"), + rx.vstack( + rx.heading("Welcome to Reflex!", size="9"), + rx.text( + "Get started by editing ", + rx.code(f"{config.app_name}/{config.app_name}.py"), + size="5", + ), + rx.link( + rx.button("Check out our docs!"), + href="https://reflex.dev/docs/getting-started/introduction/", + is_external=True, + ), + spacing="5", + justify="center", + min_height="85vh", + ), + ) + + +app = rx.App() +app.add_page(index) From e9d2bef2ae8e5432f871661683a9cd12c1e8661f Mon Sep 17 00:00:00 2001 From: C <0xicl33n@gmail.com> Date: Tue, 14 Oct 2025 15:52:59 -0700 Subject: [PATCH 38/43] rename sql -> sqlite --- src/scripts/edderkop.sql | 7 ------- src/scripts/edderkop.sqlite | 15 +++++++++++++++ 2 files changed, 15 insertions(+), 7 deletions(-) delete mode 100644 src/scripts/edderkop.sql create mode 100644 src/scripts/edderkop.sqlite diff --git a/src/scripts/edderkop.sql b/src/scripts/edderkop.sql deleted file mode 100644 index 27c52d0..0000000 --- a/src/scripts/edderkop.sql +++ /dev/null @@ -1,7 +0,0 @@ -CREATE TABLE "REVIEW" ( - "sq_object" TEXT NOT NULL UNIQUE, - "time_added" INTEGER, - "set_number" INTEGER, - "time_modified" INTEGER, - PRIMARY KEY("sq_object") -) \ No newline at end of file diff --git a/src/scripts/edderkop.sqlite b/src/scripts/edderkop.sqlite new file mode 100644 index 0000000..81290bc --- /dev/null +++ b/src/scripts/edderkop.sqlite @@ -0,0 +1,15 @@ +CREATE TABLE "REVIEW" ( + "sq_object" TEXT NOT NULL UNIQUE, + "price" INTEGER NOT NULL, + "date_modified" INTEGER, + PRIMARY KEY("sq_object") +) + +CREATE TABLE "MISSING" ( + "sq_object" TEXT NOT NULL UNIQUE, + "time_added" INTEGER, + "set_number" INTEGER, + "time_modified" INTEGER, + "set_name" TEXT, + PRIMARY KEY("sq_object") +) \ No newline at end of file From 77db374418216bd694f5adb841ed95d6a82e6f59 Mon Sep 17 00:00:00 2001 From: C <0xicl33n@gmail.com> Date: Tue, 14 Oct 2025 15:53:08 -0700 Subject: [PATCH 39/43] wtf is going on with these imports man --- src/abricksetapi.py | 3 ++- src/asquare.py | 4 ++-- src/glue.py | 28 ++++++++++++++++------------ 3 files changed, 20 insertions(+), 15 deletions(-) diff --git a/src/abricksetapi.py b/src/abricksetapi.py index 7d6fda5..6e1c03b 100644 --- a/src/abricksetapi.py +++ b/src/abricksetapi.py @@ -9,7 +9,8 @@ import aiohttp import json import dotenv from typing import Dict, Any -from asyncio_throttle.throttler import Throttler +from . import throttler +Throttler = throttler.GlobalThrottler dotenv.load_dotenv() diff --git a/src/asquare.py b/src/asquare.py index 1f12e2d..578a7b2 100644 --- a/src/asquare.py +++ b/src/asquare.py @@ -6,9 +6,9 @@ import uuid import re from time import time -import db +from . import db -import get_sq_env +from . import get_sq_env def get_set_number(text: str) -> int: diff --git a/src/glue.py b/src/glue.py index f52eb01..09003bd 100644 --- a/src/glue.py +++ b/src/glue.py @@ -1,14 +1,16 @@ import asyncio import json import re -import get_sq_env +from . import get_sq_env import aiosqlite from time import time -import asquare -import db -import abricksetapi -from throttler import GlobalThrottler +from . import asquare +from . import db +from . import abricksetapi +from . import throttler +Throttler = throttler.GlobalThrottler + LEGO_CAT_ID = get_sq_env.LEGO_CAT_ID @@ -19,7 +21,7 @@ async def do_review() -> bool: if not re.match(r".*\d{5}.*", item["name"]): missing = [] print(f"{item} is missing its set number!") - print(f"[@DEBUG] Upserting {item['id']} into REVIEW table") + #print(f"[@DEBUG] Upserting {item['id']} into REVIEW table") item_sanitized = { "sq_object": item["id"], "set_name": item["name"], @@ -27,11 +29,13 @@ async def do_review() -> bool: "time_added": time } missing.append(item_sanitized) + for item in missing: - sq_item = await db.fetch(item["id"]) # + print(item) + # sq_item = await db.fetch(item["id"]) if item["id"] != sq_item["sq_object"]: - await db.upsert("REVIEW", item_sanitized, "sq_object") - print(f"[@DEBUG] Upserted {item['id']} into REVIEW table") - print(f"[@DEBUG] {item['id']} already in REVIEW table") - return True - + print(f"[@DEBUG] {item['id']} is missing)") + #await db.upsert("MISSING", item_sanitized, "sq_object") + # #print(f"[@DEBUG] Upserted {item['id']} into REVIEW table") + #print(f"[@DEBUG] {item['id']} already in REVIEW table") + return True \ No newline at end of file From 0bc06358e8cb66387deda6d059978d976c07d0dd Mon Sep 17 00:00:00 2001 From: C <0xicl33n@gmail.com> Date: Thu, 16 Oct 2025 17:03:34 -0700 Subject: [PATCH 40/43] BIG rename --- .gitignore | 3 +- README.md | 8 +- pyproject.toml | 2 +- setup.py | 20 +-- src/abricksetapi.py | 323 +++++++++++++++++----------------- src/asquare.py | 16 +- src/bricksetapi.py | 4 +- src/db.py | 22 ++- src/ebricksetapi.py | 2 +- src/scheduler.py | 10 +- src/server.py | 4 +- src/status/assets/favicon.ico | Bin 4286 -> 0 bytes src/status/requirements.txt | 2 - src/status/rxconfig.py | 9 - src/status/web/__init__.py | 0 src/status/web/web.py | 36 ---- src/test_server.py | 27 +-- edderkop.py => studspider.py | 28 +-- uv.lock | 66 +++---- 19 files changed, 275 insertions(+), 307 deletions(-) delete mode 100644 src/status/assets/favicon.ico delete mode 100644 src/status/requirements.txt delete mode 100644 src/status/rxconfig.py delete mode 100644 src/status/web/__init__.py delete mode 100644 src/status/web/web.py rename edderkop.py => studspider.py (65%) diff --git a/.gitignore b/.gitignore index 93b0904..935a2c5 100644 --- a/.gitignore +++ b/.gitignore @@ -32,4 +32,5 @@ studspider.db-journal .web .states assets/external/ -__pycache__/ \ No newline at end of file +__pycache__/ +win/ \ No newline at end of file diff --git a/README.md b/README.md index 2af8d8e..7569660 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Studspider +# studspider ![Static Badge](https://img.shields.io/badge/python-3.13.7-blue?logo=python) ![Static Badge](https://img.shields.io/badge/code_style-black-black) ![Static Badge](https://img.shields.io/badge/license-MPL-orange) @@ -11,14 +11,14 @@ is a tool used to talk to brickset.com and a square inventory to pull lego set i Clone the project ```bash - git clone http://git.traps.moe/ph0sgene/edderkop.git + git clone http://git.traps.moe/ph0sgene/studspider.git git checkout uv ``` Go to the project directory ```bash - cd edderkop + cd studspider ``` Install dependencies @@ -30,7 +30,7 @@ Install dependencies Start the server ```bash - uvx run edderkop.py + uvx run studspider.py ``` diff --git a/pyproject.toml b/pyproject.toml index 24cd6c6..554fa02 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [project] -name = "edderkop" +name = "studpider" version = "0.1.0" description = "" authors = [{ name = "CF", email = "ph0sgene@pm.me" }] diff --git a/setup.py b/setup.py index 1f0f559..2dfd5c5 100644 --- a/setup.py +++ b/setup.py @@ -1,19 +1,19 @@ from setuptools import setup setup( - name='Edderkop', - version='0.1', - description='Bricket + Square integration application', - author='CF', - author_email='chris-forst@pm.me', - packages=['src'], - package_dir={'': 'src'}, + name="studspider", + version="0.1", + description="Bricket + Square integration application", + author="CF", + author_email="chris-forst@pm.me", + packages=["src"], + package_dir={"": "src"}, install_requires=[ # List your project dependencies here ], entry_points={ - 'console_scripts': [ - 'main=main:main', # Adjust this line based on your main function + "console_scripts": [ + "main=main:main", # Adjust this line based on your main function ], }, -) \ No newline at end of file +) diff --git a/src/abricksetapi.py b/src/abricksetapi.py index 6e1c03b..12bf2a5 100644 --- a/src/abricksetapi.py +++ b/src/abricksetapi.py @@ -1,161 +1,162 @@ -""" -abricksetapi.py - Asynchronous Brickset API Client -This module provides asynchronous interactions with the Brickset API. -""" - -import asyncio -import os -import aiohttp -import json -import dotenv -from typing import Dict, Any -from . import throttler -Throttler = throttler.GlobalThrottler - -dotenv.load_dotenv() - - -class AsyncBricksetAPI: - """Asynchronous client for the Brickset API""" - - def __init__(self, debug: bool = False, logger=None): - self.username = os.getenv("BRICKSET_USERNAME") - self.password = os.getenv("BRICKSET_PASS") - self.api_key = os.getenv("BRICKSET_API_KEY") - self.debug = debug - self.headers = {"UserAgent": "Edderkop/0.1"} - self.throttler = Throttler(rate_limit=500, period=60) - self.user_hash = None - self.logger = logger - - async def login(self) -> Dict[str, Any]: - """Asynchronously authenticate with Brickset API""" - url = "https://brickset.com/api/v3.asmx/login" - params = { - "username": self.username, - "password": self.password, - "apiKey": self.api_key, - } - - async with aiohttp.ClientSession() as session: - async with session.get( - url, params=params, headers=self.headers # type: ignore - ) as response: - if self.debug: - print(f"[@DEBUG] Login response status: {response.status}") - if self.logger: - self.logger.debug(f"Login response status: {response.status}") - - data = await response.read() - result = json.loads(data.decode("utf-8")) - self.user_hash = result.get("hash") - - if self.logger: - self.logger.info("Successfully authenticated with Brickset API") - return result - - async def get_set(self, set_number: int) -> Dict[str, Any]: - """ - Fetch LEGO set information asynchronously - - Args: - set_number (str): LEGO set number - - Returns: - Dict containing set information - """ - if not self.user_hash: - await self.login() - - url = "https://brickset.com/api/v3.asmx/getSets" - params = { - "apiKey": self.api_key, - "userHash": self.user_hash, - "params": json.dumps({"setNumber": f"{set_number}-1"}), - } - print(f"[@DEBUG] {params}...") - - async with aiohttp.ClientSession() as session: - async with session.get( - url, params=params, headers=self.headers - ) as response: - if self.debug: - print(f"[@DEBUG] Get set response status: {response.status}") - if self.logger: - self.logger.debug(f"Get set response status: {response.status}") - - if response.status == 200: - data = await response.read() - result = json.loads(data.decode("utf-8")) - - if not result.get("sets"): - error_msg = f"No data found for set {set_number}" - if self.logger: - self.logger.error(error_msg) - raise ValueError(error_msg) - - if self.logger: - self.logger.info( - f"Successfully retrieved data for set {set_number}" - ) - - set_data = result["sets"][0] - our_set = { - "set_num": set_data["number"], - "set_name": set_data["name"], - "set_theme": set_data["theme"], - "set_image": set_data["image"]["thumbnailURL"], - "set_barcode": set_data["barcode"]["UPC"], - "set_retailprice": round( - set_data["LEGOCom"]["US"]["retailPrice"] - ), - } - - # Calculate adjusted price (10% markup) - retail_price = our_set["set_retailprice"] - our_set["set_adjustredprice"] = retail_price + ( - (10 / 100) * retail_price - ) - - # Set PW inventory name with theme - our_set["pw_name"] = ( - f"{our_set['set_num']} - {our_set['set_theme'].upper()} {our_set['set_name']}" - ) - - # Flag for manual price review if needed - if our_set["set_retailprice"] >= 110: - our_set["requires_manual_price"] = True - - return our_set - - elif response.status == 500: - error_msg = ( - "Internal Server Error: Please check API key and parameters." - ) - if self.logger: - self.logger.error(error_msg) - raise Exception(error_msg) - else: - error_msg = f"Unexpected response: {response.status}" - if self.logger: - self.logger.error(error_msg) - raise Exception(error_msg) - - async def throttled_get_set(self, set_number: str) -> Dict[str, Any]: - """Get set information with rate limiting""" - async with self.throttler: - return await self.get_set(set_number) - - -async def main(): - """Example usage""" - client = AsyncBricksetAPI(debug=True) - try: - set_info = await client.throttled_get_set("10349") - print(json.dumps(set_info, indent=2)) - except Exception as e: - print(f"Error: {e}") - - -if __name__ == "__main__": - asyncio.run(main()) +""" +abricksetapi.py - Asynchronous Brickset API Client +This module provides asynchronous interactions with the Brickset API. +""" + +import asyncio +import os +import aiohttp +import json +import dotenv +from typing import Dict, Any +import throttler + +Throttler = throttler.GlobalThrottler + +dotenv.load_dotenv() + + +class AsyncBricksetAPI: + """Asynchronous client for the Brickset API""" + + def __init__(self, debug: bool = False, logger=None): + self.username = os.getenv("BRICKSET_USERNAME") + self.password = os.getenv("BRICKSET_PASS") + self.api_key = os.getenv("BRICKSET_API_KEY") + self.debug = debug + self.headers = {"UserAgent": "studspider/0.1"} + self.throttler = Throttler(rate_limit=500, period=60) + self.user_hash = None + self.logger = logger + + async def login(self) -> Dict[str, Any]: + """Asynchronously authenticate with Brickset API""" + url = "https://brickset.com/api/v3.asmx/login" + params = { + "username": self.username, + "password": self.password, + "apiKey": self.api_key, + } + + async with aiohttp.ClientSession() as session: + async with session.get( + url, params=params, headers=self.headers # type: ignore + ) as response: + if self.debug: + print(f"[@DEBUG] Login response status: {response.status}") + if self.logger: + self.logger.debug(f"Login response status: {response.status}") + + data = await response.read() + result = json.loads(data.decode("utf-8")) + self.user_hash = result.get("hash") + + if self.logger: + self.logger.info("Successfully authenticated with Brickset API") + return result + + async def get_set(self, set_number: int) -> Dict[str, Any]: + """ + Fetch LEGO set information asynchronously + + Args: + set_number (str): LEGO set number + + Returns: + Dict containing set information + """ + if not self.user_hash: + await self.login() + + url = "https://brickset.com/api/v3.asmx/getSets" + params = { + "apiKey": self.api_key, + "userHash": self.user_hash, + "params": json.dumps({"setNumber": f"{set_number}-1"}), + } + print(f"[@DEBUG] {params}...") + + async with aiohttp.ClientSession() as session: + async with session.get( + url, params=params, headers=self.headers + ) as response: + if self.debug: + print(f"[@DEBUG] Get set response status: {response.status}") + if self.logger: + self.logger.debug(f"Get set response status: {response.status}") + + if response.status == 200: + data = await response.read() + result = json.loads(data.decode("utf-8")) + + if not result.get("sets"): + error_msg = f"No data found for set {set_number}" + if self.logger: + self.logger.error(error_msg) + raise ValueError(error_msg) + + if self.logger: + self.logger.info( + f"Successfully retrieved data for set {set_number}" + ) + + set_data = result["sets"][0] + our_set = { + "set_num": set_data["number"], + "set_name": set_data["name"], + "set_theme": set_data["theme"], + "set_image": set_data["image"]["thumbnailURL"], + "set_barcode": set_data["barcode"]["UPC"], + "set_retailprice": round( + set_data["LEGOCom"]["US"]["retailPrice"] + ), + } + + # Calculate adjusted price (10% markup) + retail_price = our_set["set_retailprice"] + our_set["set_adjustredprice"] = retail_price + ( + (10 / 100) * retail_price + ) + + # Set PW inventory name with theme + our_set["pw_name"] = ( + f"{our_set['set_num']} - {our_set['set_theme'].upper()} {our_set['set_name']}" + ) + + # Flag for manual price review if needed + if our_set["set_retailprice"] >= 110: + our_set["requires_manual_price"] = True + + return our_set + + elif response.status == 500: + error_msg = ( + "Internal Server Error: Please check API key and parameters." + ) + if self.logger: + self.logger.error(error_msg) + raise Exception(error_msg) + else: + error_msg = f"Unexpected response: {response.status}" + if self.logger: + self.logger.error(error_msg) + raise Exception(error_msg) + + async def throttled_get_set(self, set_number: str) -> Dict[str, Any]: + """Get set information with rate limiting""" + async with self.throttler: + return await self.get_set(set_number) + + +async def main(): + """Example usage""" + client = AsyncBricksetAPI(debug=True) + try: + set_info = await client.throttled_get_set("10349") + print(json.dumps(set_info, indent=2)) + except Exception as e: + print(f"Error: {e}") + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/src/asquare.py b/src/asquare.py index 578a7b2..a963139 100644 --- a/src/asquare.py +++ b/src/asquare.py @@ -6,9 +6,9 @@ import uuid import re from time import time -from . import db +import db -from . import get_sq_env +import get_sq_env def get_set_number(text: str) -> int: @@ -106,10 +106,14 @@ async def acat2id(cat_id: str): { "id": obj["id"], "name": obj.get("item_data", {}).get("name", "Unknown"), - "number": None if not obj.get("item_data", {}).get("name", "") else ( - get_set_number(obj.get("item_data", {}).get("name", "")) - if re.findall(r"\d{5}", obj.get("item_data", {}).get("name", "")) - else None + "number": ( + None + if not obj.get("item_data", {}).get("name", "") + else ( + get_set_number(obj.get("item_data", {}).get("name", "")) + if re.findall(r"\d{5}", obj.get("item_data", {}).get("name", "")) + else None + ) ), "updated_at": obj.get("updated_at"), "version": obj.get("version"), diff --git a/src/bricksetapi.py b/src/bricksetapi.py index fd5a361..5d427a9 100644 --- a/src/bricksetapi.py +++ b/src/bricksetapi.py @@ -14,9 +14,9 @@ debug = False # Set to True to enable debug output def login(username, password): url = "https://brickset.com/api/v3.asmx/login" - headers = {"UserAgent": "Edderkop/0.1"} + headers = {"UserAgent": "studspider/0.1"} params = { - "UserAgent": "Edderkop/0.1", + "UserAgent": "studspider/0.1", "username": username, "password": password, "apiKey": BRICKSET_API_KEY, diff --git a/src/db.py b/src/db.py index 02bbbec..a33dfd4 100644 --- a/src/db.py +++ b/src/db.py @@ -1,12 +1,15 @@ import aiosqlite from time import time + time = int(time()) # we dont need no stinkin floats -# db = await aiosqlite.connect("edderkop.db") # type: ignore +# db = await aiosqlite.connect("studspider.db") # type: ignore + async def close(db): await db.close() + async def fetch(table, sqobj): """ Asynchronously fetch a record from the REVIEW table by sq_object. @@ -16,13 +19,16 @@ async def fetch(table, sqobj): Returns: dict or None: The fetched record as a dictionary, or None if not found. """ - async with aiosqlite.connect("edderkop.db") as db: # type: ignore - async with db.execute(f"SELECT * FROM {table} WHERE sq_object = {sqobj}") as cursor: + async with aiosqlite.connect("studspider.db") as db: # type: ignore + async with db.execute( + f"SELECT * FROM {table} WHERE sq_object = {sqobj}" + ) as cursor: async for row in cursor: return row return None + async def upsert(table: str, data: dict, unique_key: str) -> bool: """ Asynchronously insert or update a record in the specified table. @@ -37,9 +43,11 @@ async def upsert(table: str, data: dict, unique_key: str) -> bool: """ print(f"Upserting into {table}: {data}") - columns = ', '.join(data.keys()) - placeholders = ', '.join('?' * len(data)) - updates = ', '.join(f"{col}=excluded.{col}" for col in data.keys() if col != unique_key) + columns = ", ".join(data.keys()) + placeholders = ", ".join("?" * len(data)) + updates = ", ".join( + f"{col}=excluded.{col}" for col in data.keys() if col != unique_key + ) sql = f""" INSERT INTO {table} ({columns}) @@ -47,7 +55,7 @@ async def upsert(table: str, data: dict, unique_key: str) -> bool: ON CONFLICT({unique_key}) DO UPDATE SET {updates}; """ - async with aiosqlite.connect("edderkop.db") as db: # type: ignore + async with aiosqlite.connect("studspider.db") as db: # type: ignore async with db.execute(sql, tuple(data.values())) as cursor: await db.commit() return True diff --git a/src/ebricksetapi.py b/src/ebricksetapi.py index a894b62..f291363 100644 --- a/src/ebricksetapi.py +++ b/src/ebricksetapi.py @@ -15,7 +15,7 @@ class BricksetAPI: self.password = os.getenv("BRICKSET_PASS") self.api_key = os.getenv("BRICKSET_API_KEY") self.debug = debug - self.headers = {"UserAgent": "Edderkop/0.1"} + self.headers = {"UserAgent": "studspider/0.1"} self.user_hash = None def login(self) -> Dict[str, Any]: diff --git a/src/scheduler.py b/src/scheduler.py index 2b56e90..3c56c6a 100644 --- a/src/scheduler.py +++ b/src/scheduler.py @@ -6,21 +6,21 @@ import os logger = logging.getLogger(__name__) -# Get the absolute path to edderkop.db in the project root +# Get the absolute path to studspider.db in the project root DEFAULT_DB_PATH = os.path.join( - os.path.dirname(os.path.dirname(__file__)), "edderkop.db" + os.path.dirname(os.path.dirname(__file__)), "studspider.db" ) if not os.path.exists(DEFAULT_DB_PATH): raise FileNotFoundError(f"Database file not found at {DEFAULT_DB_PATH}") - print('Please run "src/scripts/EDDERKOP.SQL" to setup the database.') + print('Please run "src/scripts/studspider.SQL" to setup the database.') os.exit(0) class AsyncSchedulerDB: """ Asynchronous SQLite database manager for APScheduler. - Handles job storage, retrieval, and database operations using the main edderkop.db database. + Handles job storage, retrieval, and database operations using the main studspider.db database. """ def __init__(self, db_path: str = DEFAULT_DB_PATH): @@ -29,7 +29,7 @@ class AsyncSchedulerDB: Args: db_path (str, optional): Path to the SQLite database file. - Defaults to edderkop.db in project root. + Defaults to studspider.db in project root. """ self.db_path = db_path self._initialized = False diff --git a/src/server.py b/src/server.py index 50cc72d..ba4eaa0 100644 --- a/src/server.py +++ b/src/server.py @@ -74,7 +74,7 @@ async def handle_post(request): return True else: return aiohttp.web.Response(status=400, text="400 Bad Request.") - result = {"message": f"Edderkop/0.1 received command: {reqdata}"} + result = {"message": f"studspider/0.1 received command: {reqdata}"} return aiohttp.web.json_response(status=200, data=result) @@ -108,6 +108,6 @@ async def main(): if __name__ == "__main__": - print(generate_toilet_banner("Edderkop v0.1")) + print(generate_toilet_banner("studspider v0.1")) print("Built for PLAY-WELL TEKNOLOGIES by contact@traps.moe\n\n") aiohttp.web.run_app(app, port=7432) diff --git a/src/status/assets/favicon.ico b/src/status/assets/favicon.ico deleted file mode 100644 index 166ae995eaa63fc96771410a758282dc30e925cf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4286 zcmeHL>rYc>81ELdEe;}zmYd}cUgmJRfwjUwD1`#s5KZP>mMqza#Viv|_7|8f+0+bX zHuqusuw-7Ca`DTu#4U4^o2bjO#K>4%N?Wdi*wZ3Vx%~Ef4}D1`U_EMRg3u z#2#M|V>}}q-@IaO@{9R}d*u7f&~5HfxSkmHVcazU#i30H zAGxQ5Spe!j9`KuGqR@aExK`-}sH1jvqoIp3C7Vm)9Tu=UPE;j^esN~a6^a$ZILngo;^ zGLXl(ZFyY&U!li`6}y-hUQ99v?s`U4O!kgog74FPw-9g+V)qs!jFGEQyvBf><U|E2vRmx|+(VI~S=lT?@~C5pvZOd`x{Q_+3tG6H=gtdWcf z)+7-Zp=UqH^J4sk^>_G-Ufn-2Hz z2mN12|C{5}U`^eCQuFz=F%wp@}SzA1MHEaM^CtJs<{}Tzu$bx2orTKiedgmtVGM{ zdd#vX`&cuiec|My_KW;y{Ryz2kFu9}=~us6hvx1ZqQCk(d+>HP>ks>mmHCjjDh{pe zKQkKpk0SeDX#XMqf$}QV{z=xrN!mQczJAvud@;zFqaU1ocq==Py)qsa=8UKrt!J7r z{RsTo^rgtZo%$rak)DN*D)!(Y^$@yL6Nd=#eu&?unzhH8yq>v{gkt8xcG3S%H)-y_ zqQ1|v|JT$0R~Y}omg2Y+nDvR+K|kzR5i^fmKF>j~N;A35Vr`JWh4yRqKl#P|qlx?` z@|CmBiP}ysYO%m2{eBG6&ix5 zr#u((F2{vb=W4jNmTQh3M^F2o80T49?w>*rv0mt)-o1y!{hRk`E#UVPdna6jnz`rw dKpn)r^--YJZpr;bYU`N~>#v3X5BRU&{{=gv-{1fM diff --git a/src/status/requirements.txt b/src/status/requirements.txt deleted file mode 100644 index d9fa1f4..0000000 --- a/src/status/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ - -reflex==0.8.14.post1 \ No newline at end of file diff --git a/src/status/rxconfig.py b/src/status/rxconfig.py deleted file mode 100644 index 442ee0e..0000000 --- a/src/status/rxconfig.py +++ /dev/null @@ -1,9 +0,0 @@ -import reflex as rx - -config = rx.Config( - app_name="web", - plugins=[ - rx.plugins.SitemapPlugin(), - rx.plugins.TailwindV4Plugin(), - ] -) \ No newline at end of file diff --git a/src/status/web/__init__.py b/src/status/web/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/status/web/web.py b/src/status/web/web.py deleted file mode 100644 index 4d7059d..0000000 --- a/src/status/web/web.py +++ /dev/null @@ -1,36 +0,0 @@ -"""Welcome to Reflex! This file outlines the steps to create a basic app.""" - -import reflex as rx - -from rxconfig import config - - -class State(rx.State): - """The app state.""" - - -def index() -> rx.Component: - # Welcome Page (Index) - return rx.container( - rx.color_mode.button(position="top-right"), - rx.vstack( - rx.heading("Welcome to Reflex!", size="9"), - rx.text( - "Get started by editing ", - rx.code(f"{config.app_name}/{config.app_name}.py"), - size="5", - ), - rx.link( - rx.button("Check out our docs!"), - href="https://reflex.dev/docs/getting-started/introduction/", - is_external=True, - ), - spacing="5", - justify="center", - min_height="85vh", - ), - ) - - -app = rx.App() -app.add_page(index) diff --git a/src/test_server.py b/src/test_server.py index cbae6df..16c8c90 100644 --- a/src/test_server.py +++ b/src/test_server.py @@ -11,12 +11,14 @@ import re import logging from logging.handlers import RotatingFileHandler from pathlib import Path + print("Creating log directory if it doesn't exist...") os.makedirs("./logs/", exist_ok=True) import banner import get_sq_env from time import time + time = int(time()) import asquare as sq @@ -31,15 +33,15 @@ from asyncio_throttle.throttler import Throttler # set up logger print("Setting up logger...") -logger = logging.getLogger("edderkop") +logger = logging.getLogger("studspider") logger.setLevel(logging.DEBUG) -handler = RotatingFileHandler("./logs/edderkop.log", maxBytes=125000, backupCount=5) +handler = RotatingFileHandler("./logs/studspider.log", maxBytes=125000, backupCount=5) logger.addHandler(handler) lego = AsyncBricksetAPI(debug=True, logger=logger) # sq = AsyncSquareClient(logger=logger) # logging.basicConfig( -# filename="./logs/edderkop.log", encoding="utf-8", level=logging.DEBUG +# filename="./logs/studspider.log", encoding="utf-8", level=logging.DEBUG # ) print("Setting up rate limiter...") print("Doing RE things...") @@ -47,10 +49,10 @@ UA = re.compile(r"^kopling\/?", re.IGNORECASE) async def run_update(): - set_list = await sq.acat2id(SQUARE_LEGO_CAT_ID) # type: ignore - #print(f"[@DEBUG]\t {set_list}") # type: ignore + set_list = await sq.acat2id(SQUARE_LEGO_CAT_ID) # type: ignore + # print(f"[@DEBUG]\t {set_list}") # type: ignore for l_set in set_list: - #print(f"[@DEBUG]{l_set}") + # print(f"[@DEBUG]{l_set}") try: l_setinfo = await lego.throttled_get_set(l_set["number"]) print(f"[@DEBUG] Found {l_setinfo["pw_name"]}") @@ -58,8 +60,8 @@ async def run_update(): print(f"Error fetching set\t: {l_set}: {str(e)}") continue - #print(f"[@DEBUG]{l_setinfo}") - #await sq.update_itm(l_set, l_setinfo) + # print(f"[@DEBUG]{l_setinfo}") + # await sq.update_itm(l_set, l_setinfo) """for l_set in set_list: print(f"[@DEBUG]{l_set}") try: @@ -85,9 +87,7 @@ async def request_handler(request: web.Request) -> web.Response: try: # Verify request method if request.method != "POST": - logger.error( - f"{time}\tInvalid request method from %s at ", request.remote - ) + logger.error(f"{time}\tInvalid request method from %s at ", request.remote) return web.Response(status=405, text="Method not allowed. Use POST.") # Get JSON data from request @@ -138,12 +138,13 @@ async def request_handler(request: web.Request) -> web.Response: print(f"Request handler error: {str(e)}") return web.Response(status=500, text="Internal server error") + print("Starting web server...") -os.system('cls' if os.name == 'nt' else 'clear') +os.system("cls" if os.name == "nt" else "clear") app = web.Application() app.router.add_post("/", request_handler) if __name__ == "__main__": - print(banner.generate("Edderkop v0.1")) + print(banner.generate("studspider v0.1")) print("Built for PLAY-WELL TEKNOLOGIES by contact@traps.moe\n\n") web.run_app(app, host="localhost", port=7432) diff --git a/edderkop.py b/studspider.py similarity index 65% rename from edderkop.py rename to studspider.py index caaba51..61efcbd 100644 --- a/edderkop.py +++ b/studspider.py @@ -1,14 +1,14 @@ -import src.test_server - - -class edderkop: - def __init__(self): - self.name = "Edderkop" - self.version = "0.0.1" - self.description = """ - Edderkop is an application designed to interact with the Brickset API and directly with Square. - It allows users to search for items, manage collections, and perform various operations related to bricksets. - """ - - def run(self): - print(f"{self.name} v{self.version} is running.") +import src.test_server + + +class Studspider: + def __init__(self): + self.name = "Studspider" + self.version = "0.0.1" + self.description = """ + studspider is an application designed to interact with the Brickset API and directly with Square. + It allows users to search for items, manage collections, and perform various operations related to bricksets. + """ + + def run(self): + print(f"{self.name} v{self.version} is running.") diff --git a/uv.lock b/uv.lock index f990e3c..d806520 100644 --- a/uv.lock +++ b/uv.lock @@ -239,39 +239,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b2/b7/545d2c10c1fc15e48653c91efde329a790f2eecfbbf2bd16003b5db2bab0/dotenv-0.9.9-py2.py3-none-any.whl", hash = "sha256:29cf74a087b31dafdb5a446b6d7e11cbce8ed2741540e2339c69fbef92c94ce9", size = 1892, upload-time = "2025-02-19T22:15:01.647Z" }, ] -[[package]] -name = "edderkop" -version = "0.1.0" -source = { virtual = "." } -dependencies = [ - { name = "aiohttp" }, - { name = "aiosqlite" }, - { name = "apscheduler" }, - { name = "asyncio-throttle" }, - { name = "bs4" }, - { name = "dotenv" }, - { name = "pyinstaller" }, - { name = "pytest" }, - { name = "reflex" }, - { name = "requests" }, - { name = "squareup" }, -] - -[package.metadata] -requires-dist = [ - { name = "aiohttp", specifier = ">=3.12.13,<4.0.0" }, - { name = "aiosqlite", specifier = ">=0.21.0,<0.22.0" }, - { name = "apscheduler", specifier = ">=3.11.0,<4.0.0" }, - { name = "asyncio-throttle", specifier = ">=1.0.2,<2.0.0" }, - { name = "bs4", specifier = ">=0.0.2" }, - { name = "dotenv", specifier = ">=0.9.9,<0.10.0" }, - { name = "pyinstaller", specifier = ">=6.14.1,<7.0.0" }, - { name = "pytest", specifier = ">=8.4.2,<9.0.0" }, - { name = "reflex", specifier = ">=0.8.14.post1" }, - { name = "requests", specifier = ">=2.32.4,<3.0.0" }, - { name = "squareup", specifier = ">=43.0.0.20250618,<44.0.0.0" }, -] - [[package]] name = "frozenlist" version = "1.7.0" @@ -971,6 +938,39 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/be/72/2db2f49247d0a18b4f1bb9a5a39a0162869acf235f3a96418363947b3d46/starlette-0.48.0-py3-none-any.whl", hash = "sha256:0764ca97b097582558ecb498132ed0c7d942f233f365b86ba37770e026510659", size = 73736, upload-time = "2025-09-13T08:41:03.869Z" }, ] +[[package]] +name = "studpider" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "aiohttp" }, + { name = "aiosqlite" }, + { name = "apscheduler" }, + { name = "asyncio-throttle" }, + { name = "bs4" }, + { name = "dotenv" }, + { name = "pyinstaller" }, + { name = "pytest" }, + { name = "reflex" }, + { name = "requests" }, + { name = "squareup" }, +] + +[package.metadata] +requires-dist = [ + { name = "aiohttp", specifier = ">=3.12.13,<4.0.0" }, + { name = "aiosqlite", specifier = ">=0.21.0,<0.22.0" }, + { name = "apscheduler", specifier = ">=3.11.0,<4.0.0" }, + { name = "asyncio-throttle", specifier = ">=1.0.2,<2.0.0" }, + { name = "bs4", specifier = ">=0.0.2" }, + { name = "dotenv", specifier = ">=0.9.9,<0.10.0" }, + { name = "pyinstaller", specifier = ">=6.14.1,<7.0.0" }, + { name = "pytest", specifier = ">=8.4.2,<9.0.0" }, + { name = "reflex", specifier = ">=0.8.14.post1" }, + { name = "requests", specifier = ">=2.32.4,<3.0.0" }, + { name = "squareup", specifier = ">=43.0.0.20250618,<44.0.0.0" }, +] + [[package]] name = "typing-extensions" version = "4.15.0" From 1824c9fe5929101bd4797b199d6d70e5b13daec6 Mon Sep 17 00:00:00 2001 From: C <0xicl33n@gmail.com> Date: Thu, 23 Oct 2025 14:42:41 -0700 Subject: [PATCH 41/43] add arguments for host and port!!! yay!! --- src/test_server.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/test_server.py b/src/test_server.py index 16c8c90..376a632 100644 --- a/src/test_server.py +++ b/src/test_server.py @@ -1,3 +1,4 @@ +import argparse import asyncio import json from aiohttp import web @@ -7,6 +8,7 @@ import uuid import dotenv import os + import re import logging from logging.handlers import RotatingFileHandler @@ -144,7 +146,14 @@ os.system("cls" if os.name == "nt" else "clear") app = web.Application() app.router.add_post("/", request_handler) +parser = argparse.ArgumentParser(description="studspider") +parser.add_argument( + "--host", type=str, default="localhost", help="IP address to listen on" +) +parser.add_argument("--port", type=int, default=7432, help="Port to listen on") +args = parser.parse_args() + if __name__ == "__main__": print(banner.generate("studspider v0.1")) print("Built for PLAY-WELL TEKNOLOGIES by contact@traps.moe\n\n") - web.run_app(app, host="localhost", port=7432) + web.run_app(app, host=args.host, port=args.port) From e6937617a1ef6d4105407686be2a67256fd216d0 Mon Sep 17 00:00:00 2001 From: C <0xicl33n@gmail.com> Date: Sun, 26 Oct 2025 15:33:02 -0700 Subject: [PATCH 42/43] add initial web hook handling --- src/test_server.py | 94 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 90 insertions(+), 4 deletions(-) diff --git a/src/test_server.py b/src/test_server.py index 376a632..6725d83 100644 --- a/src/test_server.py +++ b/src/test_server.py @@ -84,6 +84,47 @@ def valid_uuid(val): return False +async def hook_handler(request: web.Request) -> web.Response: + """Handle incoming JSON webhook POSTs on /hook endpoint""" + try: + # Verify request method + if request.method != "POST": + logger.error( + f"{time}\tInvalid request method to /hook from %s", request.remote + ) + return web.Response(status=405, text="Method not allowed. Use POST.") + + # Get JSON data from request + try: + data = await request.json() + except json.JSONDecodeError: + logger.error("Invalid JSON to /hook from %s", request.remote) + return web.Response(status=400, text="Invalid JSON in request body") + + # Log the received webhook + print(f"[@DEBUG] Webhook received at {time} from {request.remote}") + logger.info( + f"{time}\tWebhook received from {request.remote}: {json.dumps(data)}" + ) + + # Process the webhook data here + # TODO: Add your webhook processing logic + + response_data = { + "status": "success", + "message": "Webhook received", + "timestamp": time, + "data_received": data, + } + + return web.json_response(response_data) + + except Exception as e: + logger.error(f"Webhook handler error: {str(e)}") + print(f"Webhook handler error: {str(e)}") + return web.Response(status=500, text="Internal server error") + + async def request_handler(request: web.Request) -> web.Response: """Handle incoming POST requests with command processing""" try: @@ -144,16 +185,61 @@ async def request_handler(request: web.Request) -> web.Response: print("Starting web server...") os.system("cls" if os.name == "nt" else "clear") app = web.Application() -app.router.add_post("/", request_handler) +app.router.add_post("/api", request_handler) +app.router.add_post("/hook", hook_handler) parser = argparse.ArgumentParser(description="studspider") parser.add_argument( - "--host", type=str, default="localhost", help="IP address to listen on" + "--host", + type=str, + default="localhost", + help="IP address to listen on. default: localhost", +) +parser.add_argument( + "--port", type=int, default=7432, help="Port to listen on. default: 7432" +) +parser.add_argument( + "--hook-port", + type=int, + default=8080, + help="Optional separate port for webhook endpoint. default: 8080", ) -parser.add_argument("--port", type=int, default=7432, help="Port to listen on") args = parser.parse_args() if __name__ == "__main__": print(banner.generate("studspider v0.1")) print("Built for PLAY-WELL TEKNOLOGIES by contact@traps.moe\n\n") - web.run_app(app, host=args.host, port=args.port) + + # If hook-port is specified, run a separate server for webhooks + if args.hook_port: + print(f"Starting main server on {args.host}:{args.port}") + print(f"Starting webhook server on {args.host}:{args.hook_port}") + + # Create separate app for webhook + hook_app = web.Application() + hook_app.router.add_post("/hook", hook_handler) + + # Run both servers concurrently + async def run_servers(): + runner1 = web.AppRunner(app) + await runner1.setup() + site1 = web.TCPSite(runner1, args.host, args.port) + + runner2 = web.AppRunner(hook_app) + await runner2.setup() + site2 = web.TCPSite(runner2, args.host, args.hook_port) + + await site1.start() + await site2.start() + + print(f"Main server running on http://{args.host}:{args.port}") + print(f"Webhook server running on http://{args.host}:{args.hook_port}/hook") + + # Keep running + await asyncio.Event().wait() + + asyncio.run(run_servers()) + else: + print(f"Server running on {args.host}:{args.port}") + print(f"Webhook endpoint: http://{args.host}:{args.port}/hook") + web.run_app(app, host=args.host, port=args.port) From ae55f36a0aad437e1255b0a39d744b07feb1464a Mon Sep 17 00:00:00 2001 From: C <0xicl33n@gmail.com> Date: Sun, 26 Oct 2025 15:51:58 -0700 Subject: [PATCH 43/43] ignore github --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 935a2c5..6e46a8e 100644 --- a/.gitignore +++ b/.gitignore @@ -33,4 +33,5 @@ studspider.db-journal .states assets/external/ __pycache__/ -win/ \ No newline at end of file +win/ +.github/