|
4 | 4 | import os
|
5 | 5 | import argparse
|
6 | 6 | import HTTPAuthOptions
|
| 7 | +import time |
7 | 8 |
|
8 | 9 | # OData service base URL
|
9 | 10 | #BASE_URL = "https://dhr1.cesnet.cz/odata/v2"
|
@@ -48,22 +49,39 @@ def get_products(auth, queries):
|
48 | 49 |
|
49 | 50 | return products_by_query
|
50 | 51 |
|
51 |
| -def download_value(entity, entity_id, auth, entity_type, node_ids=None): |
52 |
| - """Download entity's $value (binary content) to tmp.""" |
| 52 | +def download_value(entity, entity_id, auth, entity_type, node_ids=None, retries=3): |
| 53 | + """Download entity's $value (binary content) to tmp. Retries on HTTP 429.""" |
53 | 54 | if entity_type == 'Nodes':
|
54 | 55 | url = f"{BASE_URL}/Products({entity_id})/{nodes_to_url(node_ids)}/$value"
|
55 | 56 | else:
|
56 | 57 | url = f"{BASE_URL}/{entity_type}({entity_id})/$value"
|
57 |
| - response = requests.get(url, auth=auth, stream=True) |
58 |
| - |
59 |
| - if response.status_code == 200: |
60 |
| - file_path = os.path.join(DOWNLOAD_DIR, f"{entity['Name']}") |
61 |
| - with open(file_path, "wb") as f: |
62 |
| - for chunk in response.iter_content(chunk_size=8192): |
63 |
| - f.write(chunk) |
64 |
| - logging.info(f"Downloaded {entity_type} {entity['Id']} to {file_path}") |
65 |
| - else: |
66 |
| - logging.error(f"Failed to download {entity_type} {entity['Id']} value: {response.status_code} {requests.status_codes._codes[response.status_code][0]}") |
| 58 | + |
| 59 | + attempt = 0 |
| 60 | + while attempt <= retries: |
| 61 | + response = requests.get(url, auth=auth, stream=True) |
| 62 | + |
| 63 | + if response.status_code == 200: |
| 64 | + file_path = os.path.join(DOWNLOAD_DIR, f"{entity['Name']}") |
| 65 | + with open(file_path, "wb") as f: |
| 66 | + for chunk in response.iter_content(chunk_size=8192): |
| 67 | + f.write(chunk) |
| 68 | + logging.info(f"Downloaded {entity_type} {entity['Id']} to {file_path}") |
| 69 | + return # success, exit the function |
| 70 | + |
| 71 | + elif response.status_code == 429: |
| 72 | + attempt += 1 |
| 73 | + if attempt > retries: |
| 74 | + break |
| 75 | + wait_time = 20 * attempt # exponential backoff |
| 76 | + logging.warning(f"Received 429. Retrying in {wait_time} seconds... (Attempt {attempt}/{retries})") |
| 77 | + time.sleep(wait_time) |
| 78 | + else: |
| 79 | + break # don't retry on other status codes |
| 80 | + |
| 81 | + logging.error( |
| 82 | + f"Failed to download {entity_type} {entity['Id']} value: " |
| 83 | + f"{response.status_code} {requests.status_codes._codes[response.status_code][0]}" |
| 84 | + ) |
67 | 85 |
|
68 | 86 | def inspect_nodes(auth, product_id, node_id, depth=0, max_depth=1):
|
69 | 87 | """Recursively explore Nodes and download some of their $value."""
|
|
0 commit comments