pytrends-modern 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,27 @@
1
+ """
2
+ pytrends-modern: Modern Google Trends API
3
+ """
4
+
5
+ __version__ = "1.0.0"
6
+ __author__ = "pytrends-modern contributors"
7
+ __license__ = "MIT"
8
+
9
+ from pytrends_modern.request import TrendReq
10
+ from pytrends_modern.rss import TrendsRSS
11
+ from pytrends_modern.exceptions import (
12
+ TooManyRequestsError,
13
+ ResponseError,
14
+ InvalidParameterError,
15
+ BrowserError,
16
+ DownloadError,
17
+ )
18
+
19
+ __all__ = [
20
+ "TrendReq",
21
+ "TrendsRSS",
22
+ "TooManyRequestsError",
23
+ "ResponseError",
24
+ "InvalidParameterError",
25
+ "BrowserError",
26
+ "DownloadError",
27
+ ]
pytrends_modern/cli.py ADDED
@@ -0,0 +1,352 @@
1
+ """
2
+ Command-line interface for pytrends-modern
3
+ """
4
+
5
+ import json
6
+ import sys
7
+ from typing import List, Optional
8
+
9
+ try:
10
+ import click
11
+ from rich.console import Console
12
+ from rich.table import Table
13
+ from rich.progress import Progress
14
+
15
+ CLI_AVAILABLE = True
16
+ except ImportError:
17
+ CLI_AVAILABLE = False
18
+
19
+ from pytrends_modern import TrendReq, TrendsRSS
20
+ from pytrends_modern.exceptions import PyTrendsPlusError
21
+
22
+
23
+ if CLI_AVAILABLE:
24
+ console = Console()
25
+
26
+ def error(msg: str) -> None:
27
+ """Print error message and exit"""
28
+ console.print(f"[bold red]Error:[/bold red] {msg}")
29
+ sys.exit(1)
30
+
31
+ def success(msg: str) -> None:
32
+ """Print success message"""
33
+ console.print(f"[bold green]✓[/bold green] {msg}")
34
+
35
+ def info(msg: str) -> None:
36
+ """Print info message"""
37
+ console.print(f"[bold blue]ℹ[/bold blue] {msg}")
38
+
39
+ @click.group()
40
+ @click.version_option(version="1.0.0", prog_name="pytrends-modern")
41
+ def cli() -> None:
42
+ """
43
+ pytrends-modern: Modern Google Trends API
44
+
45
+ A next-generation library combining the best features from pytrends,
46
+ trendspyg, and more. Get interest over time, by region, related topics,
47
+ trending searches, and real-time RSS feeds.
48
+ """
49
+ pass
50
+
51
+ @cli.command()
52
+ @click.option("--keywords", "-k", required=True, help="Comma-separated keywords (max 5)")
53
+ @click.option("--timeframe", "-t", default="today 12-m", help='Time range (e.g., "today 12-m")')
54
+ @click.option("--geo", "-g", default="", help='Geographic location (e.g., "US", "GB")')
55
+ @click.option("--category", "-c", default=0, help="Category ID (0 for all)")
56
+ @click.option(
57
+ "--property", "-p", default="", help='Property: "", "images", "news", "youtube", "froogle"'
58
+ )
59
+ @click.option("--output", "-o", type=click.Path(), help="Output file path (CSV, JSON)")
60
+ @click.option(
61
+ "--format",
62
+ "-f",
63
+ type=click.Choice(["csv", "json", "table"]),
64
+ default="table",
65
+ help="Output format",
66
+ )
67
+ def interest(
68
+ keywords: str,
69
+ timeframe: str,
70
+ geo: str,
71
+ category: int,
72
+ property: str,
73
+ output: Optional[str],
74
+ format: str,
75
+ ) -> None:
76
+ """
77
+ Get interest over time for keywords
78
+
79
+ Example:
80
+ pytrends-modern interest -k "Python,JavaScript" -t "today 12-m"
81
+ """
82
+ try:
83
+ # Parse keywords
84
+ kw_list = [k.strip() for k in keywords.split(",")]
85
+
86
+ if len(kw_list) > 5:
87
+ error("Maximum 5 keywords allowed")
88
+
89
+ # Initialize client
90
+ info(f"Fetching interest over time for: {', '.join(kw_list)}")
91
+ pytrends = TrendReq()
92
+
93
+ # Build payload
94
+ pytrends.build_payload(
95
+ kw_list=kw_list, timeframe=timeframe, geo=geo, cat=category, gprop=property
96
+ )
97
+
98
+ # Get data
99
+ df = pytrends.interest_over_time()
100
+
101
+ if df.empty:
102
+ error("No data returned. Try different keywords or timeframe.")
103
+
104
+ # Remove isPartial column for display
105
+ if "isPartial" in df.columns:
106
+ df = df.drop("isPartial", axis=1)
107
+
108
+ # Output
109
+ if output:
110
+ if output.endswith(".json"):
111
+ df.to_json(output, orient="records", date_format="iso")
112
+ success(f"Data saved to {output}")
113
+ else:
114
+ df.to_csv(output)
115
+ success(f"Data saved to {output}")
116
+ else:
117
+ if format == "json":
118
+ console.print_json(df.to_json(orient="records", date_format="iso"))
119
+ elif format == "csv":
120
+ console.print(df.to_csv())
121
+ else:
122
+ # Display as table
123
+ table = Table(title=f"Interest Over Time: {', '.join(kw_list)}")
124
+ table.add_column("Date", style="cyan")
125
+ for kw in kw_list:
126
+ table.add_column(kw, style="magenta")
127
+
128
+ # Show last 10 rows
129
+ for idx, row in df.tail(10).iterrows():
130
+ table.add_row(str(idx.date()), *[str(int(row[kw])) for kw in kw_list])
131
+
132
+ console.print(table)
133
+ info(f"Showing last 10 of {len(df)} data points")
134
+
135
+ except PyTrendsPlusError as e:
136
+ error(str(e))
137
+ except Exception as e:
138
+ error(f"Unexpected error: {str(e)}")
139
+
140
+ @cli.command()
141
+ @click.option("--keywords", "-k", required=True, help="Comma-separated keywords")
142
+ @click.option("--geo", "-g", default="", help="Geographic location")
143
+ @click.option(
144
+ "--resolution", "-r", default="COUNTRY", help="Resolution: COUNTRY, REGION, CITY, DMA"
145
+ )
146
+ @click.option("--output", "-o", type=click.Path(), help="Output file path")
147
+ @click.option("--format", "-f", type=click.Choice(["csv", "json", "table"]), default="table")
148
+ def region(
149
+ keywords: str, geo: str, resolution: str, output: Optional[str], format: str
150
+ ) -> None:
151
+ """
152
+ Get interest by region for keywords
153
+
154
+ Example:
155
+ pytrends-modern region -k "Python" -g "US" -r "REGION"
156
+ """
157
+ try:
158
+ kw_list = [k.strip() for k in keywords.split(",")]
159
+
160
+ info(f"Fetching interest by region for: {', '.join(kw_list)}")
161
+ pytrends = TrendReq()
162
+
163
+ pytrends.build_payload(kw_list=kw_list, geo=geo)
164
+ df = pytrends.interest_by_region(resolution=resolution)
165
+
166
+ if df.empty:
167
+ error("No data returned")
168
+
169
+ # Output
170
+ if output:
171
+ if output.endswith(".json"):
172
+ df.to_json(output, orient="index")
173
+ success(f"Data saved to {output}")
174
+ else:
175
+ df.to_csv(output)
176
+ success(f"Data saved to {output}")
177
+ else:
178
+ if format == "json":
179
+ console.print_json(df.to_json(orient="index"))
180
+ elif format == "csv":
181
+ console.print(df.to_csv())
182
+ else:
183
+ # Display as table
184
+ table = Table(title=f"Interest by Region: {', '.join(kw_list)}")
185
+ table.add_column("Region", style="cyan")
186
+ for kw in kw_list:
187
+ table.add_column(kw, style="magenta")
188
+
189
+ # Show top 10 regions
190
+ df_sorted = df.sort_values(by=kw_list[0], ascending=False)
191
+ for region, row in df_sorted.head(10).iterrows():
192
+ table.add_row(str(region), *[str(int(row[kw])) for kw in kw_list])
193
+
194
+ console.print(table)
195
+ info(f"Showing top 10 of {len(df)} regions")
196
+
197
+ except PyTrendsPlusError as e:
198
+ error(str(e))
199
+ except Exception as e:
200
+ error(f"Unexpected error: {str(e)}")
201
+
202
+ @cli.command()
203
+ @click.option("--geo", "-g", default="US", help="Country code (e.g., US, GB, CA)")
204
+ @click.option("--output", "-o", type=click.Path(), help="Output file path")
205
+ @click.option("--format", "-f", type=click.Choice(["csv", "json", "table"]), default="table")
206
+ @click.option("--images/--no-images", default=True, help="Include images")
207
+ @click.option("--articles/--no-articles", default=True, help="Include articles")
208
+ def rss(geo: str, output: Optional[str], format: str, images: bool, articles: bool) -> None:
209
+ """
210
+ Get real-time trending searches from RSS feed (fast!)
211
+
212
+ Example:
213
+ pytrends-modern rss -g US
214
+ pytrends-modern rss -g GB --format json -o trends.json
215
+ """
216
+ try:
217
+ info(f"Fetching RSS trends for {geo}...")
218
+
219
+ rss_client = TrendsRSS()
220
+ trends = rss_client.get_trends(
221
+ geo=geo, output_format="dict", include_images=images, include_articles=articles
222
+ )
223
+
224
+ if not trends:
225
+ error("No trends returned")
226
+
227
+ # Output
228
+ if output:
229
+ if output.endswith(".json"):
230
+ with open(output, "w") as f:
231
+ json.dump(trends, f, indent=2, default=str)
232
+ success(f"Data saved to {output}")
233
+ else:
234
+ # Save as CSV
235
+ df = rss_client.get_trends(geo=geo, output_format="dataframe")
236
+ df.to_csv(output, index=False)
237
+ success(f"Data saved to {output}")
238
+ else:
239
+ if format == "json":
240
+ console.print_json(json.dumps(trends, default=str))
241
+ elif format == "csv":
242
+ df = rss_client.get_trends(geo=geo, output_format="dataframe")
243
+ console.print(df.to_csv(index=False))
244
+ else:
245
+ # Display as table
246
+ table = Table(title=f"Trending Searches: {geo}")
247
+ table.add_column("Title", style="cyan", no_wrap=False)
248
+ table.add_column("Traffic", style="magenta")
249
+ if articles:
250
+ table.add_column("Articles", style="green")
251
+
252
+ for trend in trends[:15]: # Show top 15
253
+ row = [trend.get("title", "N/A")[:60], str(trend.get("traffic", "N/A"))]
254
+ if articles:
255
+ row.append(str(trend.get("article_count", 0)))
256
+ table.add_row(*row)
257
+
258
+ console.print(table)
259
+ info(f"Showing {min(15, len(trends))} of {len(trends)} trends")
260
+
261
+ except PyTrendsPlusError as e:
262
+ error(str(e))
263
+ except Exception as e:
264
+ error(f"Unexpected error: {str(e)}")
265
+
266
+ @cli.command()
267
+ @click.option("--keyword", "-k", required=True, help="Keyword to get suggestions for")
268
+ def suggest(keyword: str) -> None:
269
+ """
270
+ Get keyword suggestions from Google Trends
271
+
272
+ Example:
273
+ pytrends-modern suggest -k "python"
274
+ """
275
+ try:
276
+ info(f"Getting suggestions for: {keyword}")
277
+ pytrends = TrendReq()
278
+ suggestions = pytrends.suggestions(keyword)
279
+
280
+ if not suggestions:
281
+ error("No suggestions found")
282
+
283
+ table = Table(title=f"Suggestions for '{keyword}'")
284
+ table.add_column("Title", style="cyan")
285
+ table.add_column("Type", style="magenta")
286
+
287
+ for suggestion in suggestions[:10]:
288
+ table.add_row(suggestion.get("title", "N/A"), suggestion.get("type", "N/A"))
289
+
290
+ console.print(table)
291
+
292
+ except PyTrendsPlusError as e:
293
+ error(str(e))
294
+ except Exception as e:
295
+ error(f"Unexpected error: {str(e)}")
296
+
297
+ @cli.command()
298
+ @click.option("--country", "-c", default="united_states", help="Country name")
299
+ @click.option("--output", "-o", type=click.Path(), help="Output file path")
300
+ def trending(country: str, output: Optional[str]) -> None:
301
+ """
302
+ Get trending searches for a country
303
+
304
+ Example:
305
+ pytrends-modern trending -c united_states
306
+ """
307
+ try:
308
+ info(f"Getting trending searches for: {country}")
309
+ pytrends = TrendReq()
310
+ df = pytrends.trending_searches(pn=country)
311
+
312
+ if df.empty:
313
+ error("No trending searches found")
314
+
315
+ if output:
316
+ df.to_csv(output, index=False, header=False)
317
+ success(f"Data saved to {output}")
318
+ else:
319
+ table = Table(title=f"Trending Searches: {country}")
320
+ table.add_column("#", style="cyan")
321
+ table.add_column("Search Term", style="magenta")
322
+
323
+ for idx, term in enumerate(df.iloc[:, 0].head(20), 1):
324
+ table.add_row(str(idx), str(term))
325
+
326
+ console.print(table)
327
+
328
+ except PyTrendsPlusError as e:
329
+ error(str(e))
330
+ except Exception as e:
331
+ error(f"Unexpected error: {str(e)}")
332
+
333
+ def main() -> None:
334
+ """Main entry point for CLI"""
335
+ if not CLI_AVAILABLE:
336
+ print("Error: CLI dependencies not installed.")
337
+ print("Install with: pip install pytrends-modern[cli]")
338
+ sys.exit(1)
339
+
340
+ cli()
341
+
342
+ else:
343
+ # CLI not available
344
+ def main() -> None:
345
+ """Main entry point when CLI dependencies are missing"""
346
+ print("Error: CLI dependencies not installed.")
347
+ print("Install with: pip install pytrends-modern[cli]")
348
+ sys.exit(1)
349
+
350
+
351
+ if __name__ == "__main__":
352
+ main()
@@ -0,0 +1,196 @@
1
+ """
2
+ Configuration and constants for pytrends-modern
3
+ """
4
+
5
+ from typing import Dict
6
+
7
+ # Base URL for Google Trends
8
+ BASE_TRENDS_URL = "https://trends.google.com/trends"
9
+
10
+ # API Endpoints
11
+ GENERAL_URL = f"{BASE_TRENDS_URL}/api/explore"
12
+ INTEREST_OVER_TIME_URL = f"{BASE_TRENDS_URL}/api/widgetdata/multiline"
13
+ MULTIRANGE_INTEREST_OVER_TIME_URL = f"{BASE_TRENDS_URL}/api/widgetdata/multirange"
14
+ INTEREST_BY_REGION_URL = f"{BASE_TRENDS_URL}/api/widgetdata/comparedgeo"
15
+ RELATED_QUERIES_URL = f"{BASE_TRENDS_URL}/api/widgetdata/relatedsearches"
16
+ TRENDING_SEARCHES_URL = f"{BASE_TRENDS_URL}/hottrends/visualize/internal/data"
17
+ TOP_CHARTS_URL = f"{BASE_TRENDS_URL}/api/topcharts"
18
+ SUGGESTIONS_URL = f"{BASE_TRENDS_URL}/api/autocomplete/"
19
+ CATEGORIES_URL = f"{BASE_TRENDS_URL}/api/explore/pickers/category"
20
+ TODAY_SEARCHES_URL = f"{BASE_TRENDS_URL}/api/dailytrends"
21
+ REALTIME_TRENDING_SEARCHES_URL = f"{BASE_TRENDS_URL}/api/realtimetrends"
22
+
23
+ # HTTP Error codes to retry
24
+ ERROR_CODES = (500, 502, 504, 429)
25
+
26
+ # Default values
27
+ DEFAULT_TIMEOUT = (10, 25) # (connect, read) in seconds
28
+ DEFAULT_RETRIES = 3
29
+ DEFAULT_BACKOFF_FACTOR = 0.3
30
+ DEFAULT_HL = "en-US"
31
+ DEFAULT_TZ = 360
32
+ DEFAULT_GEO = ""
33
+
34
+ # Valid gprop values
35
+ VALID_GPROP = ["", "images", "news", "youtube", "froogle"]
36
+
37
+ # User agents for rotation
38
+ USER_AGENTS = [
39
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
40
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
41
+ "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
42
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0",
43
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 14.1; rv:121.0) Gecko/20100101 Firefox/121.0",
44
+ ]
45
+
46
+ # Countries for RSS and geo filtering
47
+ COUNTRIES: Dict[str, str] = {
48
+ "US": "United States",
49
+ "GB": "United Kingdom",
50
+ "CA": "Canada",
51
+ "AU": "Australia",
52
+ "IN": "India",
53
+ "DE": "Germany",
54
+ "FR": "France",
55
+ "ES": "Spain",
56
+ "IT": "Italy",
57
+ "BR": "Brazil",
58
+ "MX": "Mexico",
59
+ "AR": "Argentina",
60
+ "JP": "Japan",
61
+ "KR": "South Korea",
62
+ "CN": "China",
63
+ "RU": "Russia",
64
+ "ZA": "South Africa",
65
+ "NL": "Netherlands",
66
+ "SE": "Sweden",
67
+ "NO": "Norway",
68
+ "DK": "Denmark",
69
+ "FI": "Finland",
70
+ "PL": "Poland",
71
+ "TR": "Turkey",
72
+ "SA": "Saudi Arabia",
73
+ "AE": "United Arab Emirates",
74
+ "SG": "Singapore",
75
+ "MY": "Malaysia",
76
+ "TH": "Thailand",
77
+ "ID": "Indonesia",
78
+ "PH": "Philippines",
79
+ "VN": "Vietnam",
80
+ "NZ": "New Zealand",
81
+ "IE": "Ireland",
82
+ "BE": "Belgium",
83
+ "CH": "Switzerland",
84
+ "AT": "Austria",
85
+ "PT": "Portugal",
86
+ "GR": "Greece",
87
+ "CZ": "Czech Republic",
88
+ "RO": "Romania",
89
+ "HU": "Hungary",
90
+ "IL": "Israel",
91
+ "EG": "Egypt",
92
+ "NG": "Nigeria",
93
+ "KE": "Kenya",
94
+ "CL": "Chile",
95
+ "CO": "Colombia",
96
+ "PE": "Peru",
97
+ "VE": "Venezuela",
98
+ }
99
+
100
+ # US States
101
+ US_STATES: Dict[str, str] = {
102
+ "US-AL": "Alabama",
103
+ "US-AK": "Alaska",
104
+ "US-AZ": "Arizona",
105
+ "US-AR": "Arkansas",
106
+ "US-CA": "California",
107
+ "US-CO": "Colorado",
108
+ "US-CT": "Connecticut",
109
+ "US-DE": "Delaware",
110
+ "US-FL": "Florida",
111
+ "US-GA": "Georgia",
112
+ "US-HI": "Hawaii",
113
+ "US-ID": "Idaho",
114
+ "US-IL": "Illinois",
115
+ "US-IN": "Indiana",
116
+ "US-IA": "Iowa",
117
+ "US-KS": "Kansas",
118
+ "US-KY": "Kentucky",
119
+ "US-LA": "Louisiana",
120
+ "US-ME": "Maine",
121
+ "US-MD": "Maryland",
122
+ "US-MA": "Massachusetts",
123
+ "US-MI": "Michigan",
124
+ "US-MN": "Minnesota",
125
+ "US-MS": "Mississippi",
126
+ "US-MO": "Missouri",
127
+ "US-MT": "Montana",
128
+ "US-NE": "Nebraska",
129
+ "US-NV": "Nevada",
130
+ "US-NH": "New Hampshire",
131
+ "US-NJ": "New Jersey",
132
+ "US-NM": "New Mexico",
133
+ "US-NY": "New York",
134
+ "US-NC": "North Carolina",
135
+ "US-ND": "North Dakota",
136
+ "US-OH": "Ohio",
137
+ "US-OK": "Oklahoma",
138
+ "US-OR": "Oregon",
139
+ "US-PA": "Pennsylvania",
140
+ "US-RI": "Rhode Island",
141
+ "US-SC": "South Carolina",
142
+ "US-SD": "South Dakota",
143
+ "US-TN": "Tennessee",
144
+ "US-TX": "Texas",
145
+ "US-UT": "Utah",
146
+ "US-VT": "Vermont",
147
+ "US-VA": "Virginia",
148
+ "US-WA": "Washington",
149
+ "US-WV": "West Virginia",
150
+ "US-WI": "Wisconsin",
151
+ "US-WY": "Wyoming",
152
+ "US-DC": "District of Columbia",
153
+ }
154
+
155
+ # Categories mapping
156
+ CATEGORIES: Dict[str, int] = {
157
+ "all": 0,
158
+ "arts_entertainment": 3,
159
+ "autos_vehicles": 47,
160
+ "beauty_fitness": 44,
161
+ "books_literature": 22,
162
+ "business_industrial": 7,
163
+ "computers_electronics": 5,
164
+ "finance": 7,
165
+ "food_drink": 71,
166
+ "games": 8,
167
+ "health": 45,
168
+ "hobbies_leisure": 65,
169
+ "home_garden": 11,
170
+ "internet_telecom": 13,
171
+ "jobs_education": 958,
172
+ "law_government": 19,
173
+ "news": 16,
174
+ "online_communities": 299,
175
+ "people_society": 14,
176
+ "pets_animals": 66,
177
+ "real_estate": 29,
178
+ "reference": 533,
179
+ "science": 174,
180
+ "shopping": 18,
181
+ "sports": 20,
182
+ "travel": 67,
183
+ }
184
+
185
+ # Time periods for validation
186
+ VALID_TIME_PERIODS = [
187
+ "now 1-H",
188
+ "now 4-H",
189
+ "now 1-d",
190
+ "now 7-d",
191
+ "today 1-m",
192
+ "today 3-m",
193
+ "today 12-m",
194
+ "today 5-y",
195
+ "all",
196
+ ]
@@ -0,0 +1,68 @@
1
+ """
2
+ Exceptions for pytrends-modern
3
+ """
4
+
5
+ from typing import Optional
6
+ from requests import Response
7
+
8
+
9
+ class PyTrendsPlusError(Exception):
10
+ """Base exception for pytrends-modern"""
11
+
12
+ pass
13
+
14
+
15
+ class ResponseError(PyTrendsPlusError):
16
+ """Exception for HTTP response errors"""
17
+
18
+ def __init__(self, message: str, response: Optional[Response] = None):
19
+ super().__init__(message)
20
+ self.response = response
21
+
22
+ @classmethod
23
+ def from_response(cls, response: Response) -> "ResponseError":
24
+ """Create ResponseError from requests.Response object"""
25
+ message = (
26
+ f"The request failed: Google returned status code {response.status_code}. "
27
+ f"URL: {response.url}"
28
+ )
29
+ if response.text:
30
+ message += f"\nResponse: {response.text[:200]}"
31
+ return cls(message, response)
32
+
33
+
34
+ class TooManyRequestsError(ResponseError):
35
+ """Exception for rate limiting (HTTP 429)"""
36
+
37
+ @classmethod
38
+ def from_response(cls, response: Response) -> "TooManyRequestsError":
39
+ """Create TooManyRequestsError from requests.Response object"""
40
+ message = (
41
+ "You have reached your quota limit. Google is rate limiting your requests. "
42
+ "Please try again later or use proxies."
43
+ )
44
+ return cls(message, response)
45
+
46
+
47
+ class InvalidParameterError(PyTrendsPlusError):
48
+ """Exception for invalid parameters"""
49
+
50
+ pass
51
+
52
+
53
+ class BrowserError(PyTrendsPlusError):
54
+ """Exception for Selenium browser errors"""
55
+
56
+ pass
57
+
58
+
59
+ class DownloadError(PyTrendsPlusError):
60
+ """Exception for download errors"""
61
+
62
+ pass
63
+
64
+
65
+ class ConfigurationError(PyTrendsPlusError):
66
+ """Exception for configuration errors"""
67
+
68
+ pass
File without changes