Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 0 additions & 8 deletions .dockerignore

This file was deleted.

4 changes: 0 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,6 @@
*.pyo
*.pyd
__pycache__/
**/__pycache__/
*.egg-info/
dist/
build/
*.log

# === SQLite & output files ===
Expand Down
18 changes: 0 additions & 18 deletions Dockerfile

This file was deleted.

1 change: 0 additions & 1 deletion accounts/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +0,0 @@
# test
5 changes: 2 additions & 3 deletions accounts/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@ def validate_email(value):

class UserAccount(models.Model):
email = models.EmailField(max_length=50, unique=True)
password_hash = models.CharField(max_length=100)
# password_hash = models.CharField(max_length=100, default='defaultpass123') # added default
password_hash = models.CharField(max_length=100, default='defaultpass123') # added default

def clean(self):
validate_email(self.email)
Expand All @@ -28,7 +27,7 @@ class Product(models.Model):
product_name = models.CharField(max_length=255, default='Unnamed Product')
price = models.DecimalField(max_digits=10, decimal_places=2, default=0.00)
url = models.TextField(default='https://example.com')
user = models.ForeignKey(UserAccount, on_delete=models.CASCADE, null=True)
user = models.ForeignKey(UserAccount, on_delete=models.CASCADE, null=True) # optional if user not yet created

def __str__(self):
return self.product_name
77 changes: 0 additions & 77 deletions dockerREADME.md

This file was deleted.

1 change: 0 additions & 1 deletion environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,3 @@ dependencies:
- pip:
- beautifulsoup4
- lxml
- gunicorn
29 changes: 0 additions & 29 deletions setup.py

This file was deleted.

41 changes: 24 additions & 17 deletions webscraper/api/EbayAPI.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,12 @@
logger = logging.getLogger(__name__)

class EbayItem:
def __init__(self, name, price, currency, url, user_id=None):
def __init__(self, name, price, currency, url, date, user_id=None):
self.name = name
self.price = price
self.currency = currency
self.url = url
self.date = date
self.user_id = user_id

class EbayAPI(EbayABC):
Expand All @@ -28,30 +29,36 @@ class EbayAPI(EbayABC):
get_user_key = HTTPBasicAuth(client_id_key, client_secret_key)

@staticmethod
def search_item(query: str) -> EbayItem:
"""Search for an item on eBay using the query string."""
def search_item(query: str) -> list[EbayItem]:
"""Search for items on eBay and return a list of EbayItem objects."""
if not isinstance(query, str) or not query.strip():
logger.warning("Invalid query input.")
raise ValueError("Query must be a non-empty string.")

logger.info(f"Searching eBay for: {query}")
response_json = EbayAPI.retrieve_ebay_response(
"https://api.sandbox.ebay.com/buy/browse/v1/item_summary/search", query
)

results = []
try:
item = response_json["itemSummaries"][0]
logger.debug(f"Item found: {item}")
return EbayItem(
name=item.get("title"),
price=float(item["price"]["value"]),
currency=item["price"]["currency"],
url=item.get("itemWebUrl"),
user_id=None
)
except (KeyError, IndexError) as e:
logger.error(f"Item not found or response invalid: {response_json}")
raise Exception("Could not parse item from eBay response.") from e
item_summaries = response_json["itemSummaries"]
for item in item_summaries:
ebay_item = EbayItem(
name=item.get("title"),
price=float(item["price"]["value"]),
currency=item["price"]["currency"],
url=item.get("itemWebUrl"),
date=item.get("itemCreationDate"),
user_id=None
)
results.append(ebay_item)
return results
except (KeyError, IndexError, TypeError) as e:
logger.error(f"Item list not found or response invalid: {response_json}")
raise Exception("Could not parse items from eBay response.") from e
finally:
logger.debug(f"Search attempt complete for query: {query}")

@staticmethod
def retrieve_access_token() -> str:
Expand Down Expand Up @@ -100,4 +107,4 @@ def retrieve_ebay_response(httprequest: str, query: str) -> dict:
return response.json()
except requests.exceptions.RequestException as e:
logger.exception("Error retrieving eBay response.")
raise
raise Exception(f"Error retrieving eBay response: {str(e)}") from e
42 changes: 36 additions & 6 deletions webscraper/api/tests/test_ebay_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,42 @@ def test_retrieve_access_token_real(self):
self.assertGreater(len(token), 0)

def test_search_item_real(self):
item = self.EbayAPI.search_item("macbook")
self.assertIsInstance(item.name, str)
self.assertIsInstance(item.price, float)
self.assertIsInstance(item.currency, str)
self.assertTrue(item.url.startswith("http"))

items = self.EbayAPI.search_item("macbook")
self.assertIsInstance(items, list)
self.assertGreater(len(items), 0)
self.assertIsInstance(items[0].name, str)
self.assertIsInstance(items[0].price, float)
self.assertIsInstance(items[0].currency, str)
self.assertTrue(items[0].url.startswith("http"))

@patch("webscraper.api.EbayAPI.requests.get")
def test_search_item_http_500(self, mock_get):
mock_get.return_value.status_code = 500
mock_get.return_value.raise_for_status.side_effect = requests.exceptions.HTTPError("Internal Server Error")

with self.assertRaises(Exception) as context:
self.EbayAPI.search_item("macbook")

self.assertIn("Error retrieving eBay response", str(context.exception))

@patch("webscraper.api.EbayAPI.requests.get")
def test_search_item_http_404(self, mock_get):
mock_get.return_value.status_code = 404
mock_get.return_value.raise_for_status.side_effect = requests.exceptions.HTTPError("Not Found")

with self.assertRaises(Exception):
self.EbayAPI.search_item("macbook")

@patch("webscraper.api.EbayAPI.EbayAPI.retrieve_ebay_response")
def test_search_item_no_items_in_response(self, mock_response):
mock_response.return_value = {} # Missing 'itemSummaries'

with self.assertRaises(Exception) as context:
self.EbayAPI.search_item("macbook")

self.assertIn("Could not parse items", str(context.exception))


def test_search_item_not_found(self):
with self.assertRaises(Exception) as context:
self.EbayAPI.search_item("asdkfjasldfjalskdfj") # nonsense query
Expand Down
19 changes: 11 additions & 8 deletions webscraper/main.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
import json
#import time # for testing
# i added these imports below because when i ran it it wasnt finding the folders
#import time // for testing
# i added htese imports below becasue when i ran it it wasnt finding the folders
import sys
import os
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
from webscraper.src.Cheaper_Scraper import CheaperScraper
from src.Cheaper_Scraper import CheaperScraper


def main():




# Set up the scraper for a simple legal-to-scrape website
scraper = CheaperScraper("https://books.toscrape.com",
user_agent="CheaperBot/0.1",
delay=2.0)

scraper = CheaperScraper("https://books.toscrape.com", user_agent="CheaperBot/0.1", delay=2.0)
# Define which pages you want to scrape (you can use "/" for homepage)
pages = ["/"]

# Use the scraper to fetch and parse the pages
results = scraper.scrape(pages)
results = scraper.scrape(pages)

# Show the output in the terminal
for path, items in results.items():
Expand All @@ -31,3 +32,5 @@ def main():

if __name__ == "__main__":
main()


9 changes: 0 additions & 9 deletions wsgi_entry.py

This file was deleted.