|
1 | 1 | from logging import Logger, getLogger |
| 2 | +from typing import Any |
2 | 3 |
|
3 | | -from curl_cffi import Response, Session |
| 4 | +from curl_cffi import Response as CurlResponse, Session |
4 | 5 | from fastapi import Depends, FastAPI, HTTPException |
5 | | -from fastapi.responses import JSONResponse |
| 6 | +from fastapi.responses import HTMLResponse, JSONResponse, Response |
6 | 7 |
|
7 | 8 | from config.constants import DEFAULT_USER_AGENT |
8 | | -from models.extended_feed import ExtendedJsonFeedItem, ExtendedJsonFeedTopLevel |
9 | | -from models.feed import JsonFeedItem, JsonFeedTopLevel |
| 9 | +from models.feed import JsonFeedTopLevel |
10 | 10 | from models.query import ( |
11 | 11 | AmazonAsinQuery, |
12 | 12 | AmazonKeywordQuery, |
|
18 | 18 | from parsers.item_parser import parse_item_details |
19 | 19 | from parsers.search_parser import parse_search_results |
20 | 20 | from services.item_generator import get_top_level_feed |
| 21 | +from services.ld_generator import get_html |
21 | 22 | from services.response_handler import get_response |
22 | 23 | from services.url_builder import get_dimension_url, get_search_url |
23 | 24 |
|
|
26 | 27 |
|
27 | 28 |
|
28 | 29 | class AmazonFeedGenerator: |
29 | | - def __init__(self) -> None: |
30 | | - """ |
31 | | - Initialize the Amazon Feed Generator with configuration and setup. |
32 | | - """ |
33 | | - # Setup can be done here if needed |
34 | | - |
35 | 30 | def create_query_config(self) -> QueryConfig: |
36 | 31 | return QueryConfig( |
37 | 32 | session=Session(), |
38 | 33 | logger=logger, |
39 | 34 | useragent=DEFAULT_USER_AGENT, |
40 | 35 | ) |
41 | 36 |
|
| 37 | + def process_query( |
| 38 | + self, |
| 39 | + params: QueryParams, |
| 40 | + query_class: type[AmazonKeywordQuery | AmazonAsinQuery], |
| 41 | + url_builder_func, |
| 42 | + parser_func, |
| 43 | + ) -> Response: |
| 44 | + try: |
| 45 | + config: QueryConfig = self.create_query_config() |
| 46 | + |
| 47 | + query: AmazonAsinQuery | AmazonKeywordQuery = query_class( |
| 48 | + status=QueryStatus(), |
| 49 | + query_str=params.q, |
| 50 | + locale=convert_to_locale(value=params.country), |
| 51 | + min_price=params.min_price, |
| 52 | + max_price=params.max_price, |
| 53 | + jsonld=params.jsonld, |
| 54 | + config=config, |
| 55 | + ) |
42 | 56 |
|
43 | | -feed_generator: AmazonFeedGenerator = AmazonFeedGenerator() |
44 | | - |
45 | | - |
46 | | -@app.get(path="/") |
47 | | -@app.get(path="/query") |
48 | | -async def keyword_search(params: QueryParams = Depends()) -> JSONResponse: |
49 | | - """ |
50 | | - Handle keyword search requests. |
51 | | - """ |
52 | | - try: |
53 | | - config: QueryConfig = feed_generator.create_query_config() |
54 | | - |
55 | | - query: AmazonKeywordQuery = AmazonKeywordQuery( |
56 | | - status=QueryStatus(), |
57 | | - query_str=params.q, |
58 | | - locale=convert_to_locale(value=params.country), |
59 | | - min_price=params.min_price, |
60 | | - max_price=params.max_price, |
61 | | - strict=params.strict, |
62 | | - config=config, |
63 | | - ) |
64 | | - |
65 | | - base_url: str = f"https://{query.locale.domain}" |
66 | | - search_url: str = get_search_url(base_url, query) |
67 | | - |
68 | | - response: Response | JSONResponse = get_response(url=search_url, query=query) |
| 57 | + base_url: str = f"https://{query.locale.domain}" |
| 58 | + search_url: Any = url_builder_func(base_url, query) |
69 | 59 |
|
70 | | - if isinstance(response, Response): |
71 | | - feed_items: list[JsonFeedItem] = parse_search_results( |
72 | | - response.content, query, base_url |
| 60 | + response: CurlResponse | JSONResponse = get_response( |
| 61 | + url=search_url, query=query |
73 | 62 | ) |
74 | 63 |
|
75 | | - json_feed: JsonFeedTopLevel = get_top_level_feed( |
76 | | - base_url, query, feed_items |
77 | | - ) |
| 64 | + if isinstance(response, CurlResponse): |
| 65 | + feed_items: list = parser_func( |
| 66 | + response.content or response.json(), query, base_url |
| 67 | + ) |
78 | 68 |
|
79 | | - return JSONResponse(content=json_feed.model_dump(exclude_none=True)) |
80 | | - else: |
81 | | - return response |
82 | | - |
83 | | - except Exception as e: |
84 | | - logger.error(msg=f"Keyword search error: {e}") |
85 | | - raise HTTPException(status_code=500, detail=f"Keyword search error: {e}") |
| 69 | + if params.jsonld: |
| 70 | + html_text: str = get_html(feed_items) |
| 71 | + return HTMLResponse(content=html_text) |
| 72 | + else: |
| 73 | + json_feed: JsonFeedTopLevel = get_top_level_feed( |
| 74 | + base_url, query, feed_items |
| 75 | + ) |
| 76 | + return JSONResponse(content=json_feed.model_dump(exclude_none=True)) |
86 | 77 |
|
| 78 | + return response |
87 | 79 |
|
88 | | -@app.get(path="/asin") |
89 | | -async def asin_lookup(params: QueryParams = Depends()) -> JSONResponse: |
90 | | - """ |
91 | | - Handle ASIN lookup requests. |
92 | | - """ |
93 | | - try: |
94 | | - config: QueryConfig = feed_generator.create_query_config() |
95 | | - |
96 | | - query: AmazonAsinQuery = AmazonAsinQuery( |
97 | | - status=QueryStatus(), |
98 | | - query_str=params.q, |
99 | | - locale=convert_to_locale(value=params.country), |
100 | | - min_price=params.min_price, |
101 | | - max_price=params.max_price, |
102 | | - config=config, |
103 | | - ) |
| 80 | + except Exception as e: |
| 81 | + error_msg: str = f"{'Keyword' if query_class is AmazonKeywordQuery else 'ASIN'} lookup error: {e}" |
| 82 | + logger.error(msg=error_msg) |
| 83 | + raise HTTPException(status_code=500, detail=error_msg) |
104 | 84 |
|
105 | | - base_url: str = f"https://{query.locale.domain}" |
106 | | - search_url: str = get_dimension_url(query) |
107 | 85 |
|
108 | | - response: Response | JSONResponse = get_response(url=search_url, query=query) |
| 86 | +feed_generator: AmazonFeedGenerator = AmazonFeedGenerator() |
109 | 87 |
|
110 | | - if isinstance(response, Response): |
111 | | - feed_items: list[JsonFeedItem | ExtendedJsonFeedItem] = parse_item_details( |
112 | | - response.json(), query, base_url |
113 | | - ) |
114 | 88 |
|
115 | | - json_feed: ExtendedJsonFeedTopLevel = get_top_level_feed( |
116 | | - base_url, query, feed_items |
117 | | - ) |
| 89 | +@app.get(path="/") |
| 90 | +@app.get(path="/query") |
| 91 | +async def keyword_search(params: QueryParams = Depends()) -> Response: |
| 92 | + return feed_generator.process_query( |
| 93 | + params, |
| 94 | + query_class=AmazonKeywordQuery, |
| 95 | + url_builder_func=get_search_url, |
| 96 | + parser_func=parse_search_results, |
| 97 | + ) |
118 | 98 |
|
119 | | - return JSONResponse(content=json_feed.model_dump(exclude_none=True)) |
120 | | - else: |
121 | | - return response |
122 | 99 |
|
123 | | - except Exception as e: |
124 | | - logger.error(msg=f"ASIN lookup error: {e}") |
125 | | - raise HTTPException(status_code=500, detail=f"ASIN lookup error: {e}") |
| 100 | +@app.get(path="/asin") |
| 101 | +async def asin_lookup(params: QueryParams = Depends()) -> Response: |
| 102 | + return feed_generator.process_query( |
| 103 | + params, |
| 104 | + query_class=AmazonAsinQuery, |
| 105 | + url_builder_func=get_dimension_url, |
| 106 | + parser_func=parse_item_details, |
| 107 | + ) |
126 | 108 |
|
127 | 109 |
|
128 | 110 | @app.get(path="/healthcheck") |
|
0 commit comments