odps_module.py 1.8 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061
  1. #!/usr/bin/env python3
  2. """Minimal PyODPS client configured only through environment variables."""
  3. from __future__ import annotations
  4. import os
  5. from pathlib import Path
  6. from odps import ODPS, options
  7. ENV_ACCESS_ID = "ODPS_ACCESS_ID"
  8. ENV_ACCESS_SECRET = "ODPS_ACCESS_SECRET"
  9. ENV_PROJECT = "ODPS_PROJECT"
  10. ENV_ENDPOINT = "ODPS_ENDPOINT"
  11. def require_env(name: str) -> str:
  12. value = os.getenv(name)
  13. if not value:
  14. raise RuntimeError(f"Missing required environment variable: {name}")
  15. return value
  16. class ODPSClient:
  17. def __init__(self, project: str | None = None, endpoint: str | None = None):
  18. access_id = require_env(ENV_ACCESS_ID)
  19. access_secret = require_env(ENV_ACCESS_SECRET)
  20. project_name = project or require_env(ENV_PROJECT)
  21. endpoint_url = endpoint or require_env(ENV_ENDPOINT)
  22. options.connect_timeout = 60
  23. options.read_timeout = 1200
  24. options.retry_times = 3
  25. self.odps = ODPS(
  26. access_id,
  27. access_secret,
  28. project=project_name,
  29. endpoint=endpoint_url,
  30. )
  31. def execute_sql(self, sql: str):
  32. if not sql.strip():
  33. raise ValueError("SQL must not be empty")
  34. instance = self.odps.run_sql(
  35. sql,
  36. hints={"odps.sql.submit.mode": "script"},
  37. )
  38. print(f"[ODPS] InstanceId: {instance.id}", flush=True)
  39. instance.wait_for_success()
  40. with instance.open_reader(tunnel=True) as reader:
  41. return reader.to_pandas()
  42. def execute_sql_result_save_file(self, sql: str, output_file: str | Path):
  43. output_path = Path(output_file).expanduser()
  44. output_path.parent.mkdir(parents=True, exist_ok=True)
  45. data = self.execute_sql(sql)
  46. data.to_csv(output_path, index=False, encoding="utf-8-sig")
  47. return output_path