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 166ae99..0000000 Binary files a/src/status/assets/favicon.ico and /dev/null differ 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"