localdex 0.1.1a6__py3-none-any.whl → 0.1.1a10__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.
localdex/download_data.py CHANGED
@@ -12,6 +12,8 @@ import requests
12
12
  from pathlib import Path
13
13
  from typing import Dict, List, Any, Optional
14
14
  import time
15
+ from concurrent.futures import ThreadPoolExecutor, as_completed
16
+ import threading
15
17
 
16
18
  from .exceptions import DataLoadError
17
19
 
@@ -24,12 +26,13 @@ class DataDownloader:
24
26
  then converts it to the format expected by LocalDex.
25
27
  """
26
28
 
27
- def __init__(self, output_dir: str = "localdex/data"):
29
+ def __init__(self, output_dir: str = "localdex/data", max_workers: int = 5):
28
30
  """
29
31
  Initialize the data downloader.
30
32
 
31
33
  Args:
32
34
  output_dir: Directory to save downloaded data
35
+ max_workers: Maximum number of concurrent downloads
33
36
  """
34
37
  self.output_dir = Path(output_dir)
35
38
  self.base_url = "https://pokeapi.co/api/v2"
@@ -43,6 +46,14 @@ class DataDownloader:
43
46
 
44
47
  for directory in [self.pokemon_dir, self.moves_dir, self.abilities_dir, self.items_dir]:
45
48
  directory.mkdir(parents=True, exist_ok=True)
49
+
50
+ self.max_workers = max_workers
51
+ self._session_lock = threading.Lock()
52
+
53
+ def _get_session(self) -> requests.Session:
54
+ """Get a thread-safe session."""
55
+ with self._session_lock:
56
+ return self.session
46
57
 
47
58
  def download_pokemon_data(self, limit: Optional[int] = None) -> None:
48
59
  """
@@ -58,7 +69,8 @@ class DataDownloader:
58
69
  if limit:
59
70
  url += f"?limit={limit}"
60
71
 
61
- response = self.session.get(url)
72
+ session = self._get_session()
73
+ response = session.get(url)
62
74
  response.raise_for_status()
63
75
 
64
76
  pokemon_list = response.json()["results"]
@@ -66,13 +78,16 @@ class DataDownloader:
66
78
 
67
79
  print(f"Found {total} Pokemon to download")
68
80
 
69
- for i, pokemon_info in enumerate(pokemon_list, 1):
81
+ # Thread-safe counter for progress tracking
82
+ completed_count = 0
83
+ lock = threading.Lock()
84
+
85
+ def download_and_save_pokemon(pokemon_info):
86
+ nonlocal completed_count
70
87
  try:
71
88
  pokemon_id = pokemon_info["url"].split("/")[-2]
72
89
  pokemon_name = pokemon_info["name"]
73
90
 
74
- print(f"Downloading {pokemon_name} ({i}/{total})")
75
-
76
91
  # Download detailed Pokemon data
77
92
  pokemon_data = self._download_pokemon_detail(pokemon_id)
78
93
 
@@ -81,12 +96,23 @@ class DataDownloader:
81
96
  with open(output_file, 'w', encoding='utf-8') as f:
82
97
  json.dump(pokemon_data, f, indent=2, ensure_ascii=False)
83
98
 
84
- # Rate limiting
85
- time.sleep(0.1)
99
+ # Update progress
100
+ with lock:
101
+ completed_count += 1
102
+ print(f"Downloaded {pokemon_name} ({completed_count}/{total})")
103
+
104
+ return pokemon_name
86
105
 
87
106
  except Exception as e:
88
- print(f"Error downloading {pokemon_info['name']}: {e}")
89
- continue
107
+ with lock:
108
+ completed_count += 1
109
+ print(f"Error downloading {pokemon_info['name']}: {e}")
110
+ return None
111
+
112
+ with ThreadPoolExecutor(max_workers=self.max_workers) as executor:
113
+ futures = [executor.submit(download_and_save_pokemon, pokemon_info) for pokemon_info in pokemon_list]
114
+ for future in as_completed(futures):
115
+ future.result() # Wait for completion and handle any exceptions
90
116
 
91
117
  def download_move_data(self, limit: Optional[int] = None) -> None:
92
118
  """
@@ -102,7 +128,8 @@ class DataDownloader:
102
128
  if limit:
103
129
  url += f"?limit={limit}"
104
130
 
105
- response = self.session.get(url)
131
+ session = self._get_session()
132
+ response = session.get(url)
106
133
  response.raise_for_status()
107
134
 
108
135
  move_list = response.json()["results"]
@@ -110,13 +137,16 @@ class DataDownloader:
110
137
 
111
138
  print(f"Found {total} moves to download")
112
139
 
113
- for i, move_info in enumerate(move_list, 1):
140
+ # Thread-safe counter for progress tracking
141
+ completed_count = 0
142
+ lock = threading.Lock()
143
+
144
+ def download_and_save_move(move_info):
145
+ nonlocal completed_count
114
146
  try:
115
147
  move_id = move_info["url"].split("/")[-2]
116
148
  move_name = move_info["name"]
117
149
 
118
- print(f"Downloading {move_name} ({i}/{total})")
119
-
120
150
  # Download detailed move data
121
151
  move_data = self._download_move_detail(move_id)
122
152
 
@@ -125,12 +155,23 @@ class DataDownloader:
125
155
  with open(output_file, 'w', encoding='utf-8') as f:
126
156
  json.dump(move_data, f, indent=2, ensure_ascii=False)
127
157
 
128
- # Rate limiting
129
- time.sleep(0.1)
158
+ # Update progress
159
+ with lock:
160
+ completed_count += 1
161
+ print(f"Downloaded {move_name} ({completed_count}/{total})")
162
+
163
+ return move_name
130
164
 
131
165
  except Exception as e:
132
- print(f"Error downloading {move_info['name']}: {e}")
133
- continue
166
+ with lock:
167
+ completed_count += 1
168
+ print(f"Error downloading {move_info['name']}: {e}")
169
+ return None
170
+
171
+ with ThreadPoolExecutor(max_workers=self.max_workers) as executor:
172
+ futures = [executor.submit(download_and_save_move, move_info) for move_info in move_list]
173
+ for future in as_completed(futures):
174
+ future.result() # Wait for completion and handle any exceptions
134
175
 
135
176
  def download_ability_data(self, limit: Optional[int] = None) -> None:
136
177
  """
@@ -146,7 +187,8 @@ class DataDownloader:
146
187
  if limit:
147
188
  url += f"?limit={limit}"
148
189
 
149
- response = self.session.get(url)
190
+ session = self._get_session()
191
+ response = session.get(url)
150
192
  response.raise_for_status()
151
193
 
152
194
  ability_list = response.json()["results"]
@@ -154,13 +196,16 @@ class DataDownloader:
154
196
 
155
197
  print(f"Found {total} abilities to download")
156
198
 
157
- for i, ability_info in enumerate(ability_list, 1):
199
+ # Thread-safe counter for progress tracking
200
+ completed_count = 0
201
+ lock = threading.Lock()
202
+
203
+ def download_and_save_ability(ability_info):
204
+ nonlocal completed_count
158
205
  try:
159
206
  ability_id = ability_info["url"].split("/")[-2]
160
207
  ability_name = ability_info["name"]
161
208
 
162
- print(f"Downloading {ability_name} ({i}/{total})")
163
-
164
209
  # Download detailed ability data
165
210
  ability_data = self._download_ability_detail(ability_id)
166
211
 
@@ -169,12 +214,23 @@ class DataDownloader:
169
214
  with open(output_file, 'w', encoding='utf-8') as f:
170
215
  json.dump(ability_data, f, indent=2, ensure_ascii=False)
171
216
 
172
- # Rate limiting
173
- time.sleep(0.1)
217
+ # Update progress
218
+ with lock:
219
+ completed_count += 1
220
+ print(f"Downloaded {ability_name} ({completed_count}/{total})")
221
+
222
+ return ability_name
174
223
 
175
224
  except Exception as e:
176
- print(f"Error downloading {ability_info['name']}: {e}")
177
- continue
225
+ with lock:
226
+ completed_count += 1
227
+ print(f"Error downloading {ability_info['name']}: {e}")
228
+ return None
229
+
230
+ with ThreadPoolExecutor(max_workers=self.max_workers) as executor:
231
+ futures = [executor.submit(download_and_save_ability, ability_info) for ability_info in ability_list]
232
+ for future in as_completed(futures):
233
+ future.result() # Wait for completion and handle any exceptions
178
234
 
179
235
  def download_item_data(self, limit: Optional[int] = None) -> None:
180
236
  """
@@ -190,7 +246,8 @@ class DataDownloader:
190
246
  if limit:
191
247
  url += f"?limit={limit}"
192
248
 
193
- response = self.session.get(url)
249
+ session = self._get_session()
250
+ response = session.get(url)
194
251
  response.raise_for_status()
195
252
 
196
253
  item_list = response.json()["results"]
@@ -198,13 +255,16 @@ class DataDownloader:
198
255
 
199
256
  print(f"Found {total} items to download")
200
257
 
201
- for i, item_info in enumerate(item_list, 1):
258
+ # Thread-safe counter for progress tracking
259
+ completed_count = 0
260
+ lock = threading.Lock()
261
+
262
+ def download_and_save_item(item_info):
263
+ nonlocal completed_count
202
264
  try:
203
265
  item_id = item_info["url"].split("/")[-2]
204
266
  item_name = item_info["name"]
205
267
 
206
- print(f"Downloading {item_name} ({i}/{total})")
207
-
208
268
  # Download detailed item data
209
269
  item_data = self._download_item_detail(item_id)
210
270
 
@@ -213,17 +273,29 @@ class DataDownloader:
213
273
  with open(output_file, 'w', encoding='utf-8') as f:
214
274
  json.dump(item_data, f, indent=2, ensure_ascii=False)
215
275
 
216
- # Rate limiting
217
- time.sleep(0.1)
276
+ # Update progress
277
+ with lock:
278
+ completed_count += 1
279
+ print(f"Downloaded {item_name} ({completed_count}/{total})")
280
+
281
+ return item_name
218
282
 
219
283
  except Exception as e:
220
- print(f"Error downloading {item_info['name']}: {e}")
221
- continue
284
+ with lock:
285
+ completed_count += 1
286
+ print(f"Error downloading {item_info['name']}: {e}")
287
+ return None
288
+
289
+ with ThreadPoolExecutor(max_workers=self.max_workers) as executor:
290
+ futures = [executor.submit(download_and_save_item, item_info) for item_info in item_list]
291
+ for future in as_completed(futures):
292
+ future.result() # Wait for completion and handle any exceptions
222
293
 
223
294
  def _download_pokemon_detail(self, pokemon_id: str) -> Dict[str, Any]:
224
295
  """Download detailed Pokemon data."""
225
296
  url = f"{self.base_url}/pokemon/{pokemon_id}"
226
- response = self.session.get(url)
297
+ session = self._get_session()
298
+ response = session.get(url)
227
299
  response.raise_for_status()
228
300
 
229
301
  data = response.json()
@@ -263,7 +335,8 @@ class DataDownloader:
263
335
  def _download_move_detail(self, move_id: str) -> Dict[str, Any]:
264
336
  """Download detailed move data."""
265
337
  url = f"{self.base_url}/move/{move_id}"
266
- response = self.session.get(url)
338
+ session = self._get_session()
339
+ response = session.get(url)
267
340
  response.raise_for_status()
268
341
 
269
342
  data = response.json()
@@ -292,7 +365,8 @@ class DataDownloader:
292
365
  def _download_ability_detail(self, ability_id: str) -> Dict[str, Any]:
293
366
  """Download detailed ability data."""
294
367
  url = f"{self.base_url}/ability/{ability_id}"
295
- response = self.session.get(url)
368
+ session = self._get_session()
369
+ response = session.get(url)
296
370
  response.raise_for_status()
297
371
 
298
372
  data = response.json()
@@ -315,7 +389,8 @@ class DataDownloader:
315
389
  def _download_item_detail(self, item_id: str) -> Dict[str, Any]:
316
390
  """Download detailed item data."""
317
391
  url = f"{self.base_url}/item/{item_id}"
318
- response = self.session.get(url)
392
+ session = self._get_session()
393
+ response = session.get(url)
319
394
  response.raise_for_status()
320
395
 
321
396
  data = response.json()
@@ -389,6 +464,7 @@ def main():
389
464
 
390
465
  parser = argparse.ArgumentParser(description="Download Pokemon data for LocalDex")
391
466
  parser.add_argument("--output", default="localdex/data", help="Output directory")
467
+ parser.add_argument("--max-workers", type=int, default=5, help="Maximum number of concurrent downloads (default: 5)")
392
468
  parser.add_argument("--pokemon-limit", type=int, help="Limit number of Pokemon to download (default: all)")
393
469
  parser.add_argument("--move-limit", type=int, help="Limit number of moves to download (default: all)")
394
470
  parser.add_argument("--ability-limit", type=int, help="Limit number of abilities to download (default: all)")
@@ -400,7 +476,7 @@ def main():
400
476
 
401
477
  args = parser.parse_args()
402
478
 
403
- downloader = DataDownloader(args.output)
479
+ downloader = DataDownloader(args.output, max_workers=args.max_workers)
404
480
 
405
481
  try:
406
482
  if args.pokemon_only:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: localdex
3
- Version: 0.1.1a6
3
+ Version: 0.1.1a10
4
4
  Summary: A local Pokemon data repository/Pokedex with fast offline access
5
5
  Home-page: https://github.com/yourusername/localdex
6
6
  Author: LocalDex Team
@@ -2,7 +2,7 @@ localdex/__init__.py,sha256=rAVSDHi5KVKNiMaKgZZ04_Kl7j2KPCZuWO7OuDU6id8,748
2
2
  localdex/cli.py,sha256=79NpeQQYwuDuM6wODUX1AOUppTPULD5T0qgTH5uKd2c,16797
3
3
  localdex/core.py,sha256=74ia5z5tQarbWQpbZQPz3L9i3FGEZrCxIy65H90KU6c,20254
4
4
  localdex/data_loader.py,sha256=hi9aSTto5Ti-OBGOgrQ-XwD5hmivsUwS1uC4rulhwQI,11366
5
- localdex/download_data.py,sha256=yOFaS0U5Qq9gFcCzIH-yFA_DGu7Iniq7qRFPy6Ofj1E,15479
5
+ localdex/download_data.py,sha256=ibAHDxL60sV4LVN9isCmf8vvd_aI9IQbyjJpU0FHGUo,18869
6
6
  localdex/exceptions.py,sha256=Z02-8Kci6jFDk2nnGdVSHZJMDDWE9vuwuASs4VM3To8,2777
7
7
  localdex/data/abilities/adaptability.json,sha256=FGEEZmL80YVcIXHV-h287Wu-UJycM1QEJxiOHK2I4mY,289
8
8
  localdex/data/abilities/aerilate.json,sha256=KRZOVH2KGdEQ_3UktFoXoagUOaC8jIPZ6Ti-YGzW44s,301
@@ -4790,8 +4790,8 @@ localdex/models/ability.py,sha256=AQzv3XUHHl4sustMJjPDDjJOjXu2GMLTfcM3-tqQ_1w,30
4790
4790
  localdex/models/item.py,sha256=zXao8F-jBPUGq_YLeGeYeK_dZVI7aZMXtWOPwR3qusY,4677
4791
4791
  localdex/models/move.py,sha256=hfgcWI4ziz5MMvc9ddmkotxzYYdrSUqZZQ72IU5tucs,7629
4792
4792
  localdex/models/pokemon.py,sha256=v5zkxY1NGGR-MczFCZe9fHt6u_BAqAjMiQZlpcLXVGc,5408
4793
- localdex-0.1.1a6.dist-info/METADATA,sha256=IsdXp13Gu_tjAzvAB3RKRmZ_Tj1VEKAiXDJt8H5mPUU,11110
4794
- localdex-0.1.1a6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
4795
- localdex-0.1.1a6.dist-info/entry_points.txt,sha256=n5GxSeQo-MRuvrT2wVk7hOzEFFsWf6tkBjkzmGIYJe4,47
4796
- localdex-0.1.1a6.dist-info/top_level.txt,sha256=vtupDMH-IaxVCoEZrmE0QzdTwhaKzngVJbTA1NkR_MY,9
4797
- localdex-0.1.1a6.dist-info/RECORD,,
4793
+ localdex-0.1.1a10.dist-info/METADATA,sha256=6vPmzgnk5KrYx0HVTsPCj69yWMM2DeK4-JuQBGcitDc,11111
4794
+ localdex-0.1.1a10.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
4795
+ localdex-0.1.1a10.dist-info/entry_points.txt,sha256=n5GxSeQo-MRuvrT2wVk7hOzEFFsWf6tkBjkzmGIYJe4,47
4796
+ localdex-0.1.1a10.dist-info/top_level.txt,sha256=vtupDMH-IaxVCoEZrmE0QzdTwhaKzngVJbTA1NkR_MY,9
4797
+ localdex-0.1.1a10.dist-info/RECORD,,