main.py 2.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485
  1. import requests
  2. import threading
  3. from concurrent.futures import ThreadPoolExecutor, as_completed
  4. from urllib.parse import urlparse, parse_qs, urlencode, urlunparse
  5. from credentials import Credentials
  6. class AcumaticaODataClient:
  7. def __init__(self, base_url, username, password, page_size=1000, max_workers=8):
  8. self.base_url = self._add_format_and_top(base_url, page_size)
  9. self.page_size = page_size
  10. self.session = requests.Session()
  11. self.session.auth = (username, password)
  12. self.session.headers.update({
  13. "Accept": "application/json"
  14. })
  15. self.lock = threading.Lock()
  16. self.max_workers = max_workers
  17. self.results = []
  18. def _add_format_and_top(self, url, top):
  19. # Add or update $format and $top in query string
  20. parts = list(urlparse(url))
  21. query = parse_qs(parts[4])
  22. query['$format'] = ['json']
  23. query['$top'] = [str(top)]
  24. parts[4] = urlencode(query, doseq=True)
  25. return urlunparse(parts)
  26. def _fetch_initial_links(self):
  27. """Collect all paginated URLs before multithreaded fetch."""
  28. next_url = self.base_url
  29. urls = []
  30. while next_url:
  31. print(f"[DISCOVER] {next_url}")
  32. resp = self.session.get(next_url)
  33. resp.raise_for_status()
  34. data = resp.json()
  35. urls.append(next_url)
  36. next_url = data.get('@odata.nextLink')
  37. return urls
  38. def _fetch_page(self, url):
  39. try:
  40. resp = self.session.get(url)
  41. resp.raise_for_status()
  42. data = resp.json()
  43. records = data.get('value', [])
  44. with self.lock:
  45. self.results.extend(records)
  46. print(f"[FETCHED] {len(records)} records from {url}")
  47. except Exception as e:
  48. print(f"[ERROR] {url} -> {e}")
  49. def fetch_all(self):
  50. page_urls = self._fetch_initial_links()
  51. with ThreadPoolExecutor(max_workers=self.max_workers) as executor:
  52. futures = [executor.submit(self._fetch_page, url) for url in page_urls]
  53. for _ in as_completed(futures):
  54. pass
  55. return self.results
  56. if __name__ == "__main__":
  57. # 🔧 Replace with your actual OData endpoint
  58. odata_url = "https://acumatica.conciseit.net/Odata/Project%20UDF%20Example"
  59. creds = Credentials()
  60. client = AcumaticaODataClient(
  61. base_url=odata_url,
  62. username=creds.username,
  63. password=creds.password,
  64. page_size=500, # Adjustable page size
  65. max_workers=6 # Adjustable thread count
  66. )
  67. records = client.fetch_all()
  68. print(f"\n✅ Total records retrieved: {len(records)}\n")
  69. for i, record in enumerate(records, 1):
  70. print(f"[{i}] {record}")