diff --git a/README.md b/README.md index 0d5a5e4..67ba636 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,8 @@ py-aps is a Python SDK that provides a simple and intuitive interface for intera - **Authentication**: Easy OAuth2 authentication flow - **Data Management**: Access and manage files in BIM 360, ACC, and other Autodesk cloud storage -- **Automation**: Automate design and engineering workflows +- **Automation**: High-level workflow API for executing WorkItems with automatic file management and webhook support +- **Proxy Support**: HTTP/HTTPS proxy configuration for enterprise environments (v0.0.6+) ## Quick Start @@ -43,7 +44,33 @@ projects = list(dm.hubs.list_projects(hub_id)) contents = list(dm.folders.contents(project_id, folder_id)) ``` -### Design Automation +### Automation (High-Level Workflow) +```python +from pyaps.automation import AutomationWorkflow + +workflow = AutomationWorkflow( + automation_client=auto_client, + data_client=dm_client, + default_bucket="my-bucket", +) + +# Execute WorkItem with automatic file management +result = workflow.run_workitem_with_files( + activity_id="Owner.MyActivity+prod", + input_files={"inputFile": "path/to/input.rvt"}, + output_files={"outputFile": "output.rvt"}, +) + +# With webhooks (no polling required) +result = workflow.run_workitem_with_files( + activity_id="Owner.MyActivity+prod", + input_files={"inputFile": "input.rvt"}, + output_files={"outputFile": "output.rvt"}, + on_complete_url="https://myapp.com/webhook/complete", +) +``` + +### Automation (Low-Level API) ```python from pyaps.automation import AutomationClient @@ -52,25 +79,53 @@ auto = AutomationClient(token_provider=lambda: token.access_token) # List engines engines = auto.list_engines() -# Create and execute workitem +# Start workitem (manual setup required) workitem = auto.start_workitem({ 'activityId': 'Owner.MyActivity+prod', 'arguments': {...} }) ``` -For more examples, see `src/pyaps/auth/example.py`, `src/pyaps/datamanagement/example.py`, and `src/pyaps/automation/example.py`. +### Proxy Configuration (v0.0.6+) +```python +# Explicit proxy configuration +client = AuthClient( + client_id="...", + client_secret="...", + proxies={ + 'http': 'http://proxy.company.com:8080', + 'https': 'https://proxy.company.com:8080' + } +) + +# Or use environment variables (HTTP_PROXY, HTTPS_PROXY) +client = AuthClient( + client_id="...", + client_secret="...", + trust_env=True # Default - reads from environment +) +``` + +For more examples and detailed documentation: +- **AutomationWorkflow Guide**: `src/pyaps/automation/WORKFLOW.md` +- **Workflow Examples**: `src/pyaps/automation/workflow_example.py` +- **Low-Level Examples**: `src/pyaps/automation/example.py` +- **Auth Examples**: `src/pyaps/auth/example.py` +- **Data Management Examples**: `src/pyaps/datamanagement/example.py` +- **Proxy Examples**: `src/pyaps/http/proxy_example.py` ## Project Status -**Current version: v0.0.4** - Design Automation API support added +**Current version: v0.0.6** - Proxy support for enterprise environments This package is currently in early development. Active development is underway by **voidbox**.
Version History -- **v0.0.4** - Added Design Automation API client (Engines, AppBundles, Activities, WorkItems) +- **v0.0.6** - Added HTTP/HTTPS proxy support for enterprise environments (configurable via explicit settings or environment variables) +- **v0.0.5** - Added AutomationWorkflow high-level API with automatic file management, webhook support (onComplete/onProgress), batch processing, and comprehensive documentation +- **v0.0.4** - Added Automation API client (Engines, AppBundles, Activities, WorkItems) - **v0.0.3** - Added Data Management API client (Hubs, Projects, Folders, Items, Versions, Buckets, Objects) - **v0.0.2** - Added OAuth 2.0 authentication client with 2-legged/3-legged flows, PKCE support, and token management - **v0.0.1** - Initial package release (placeholder) diff --git a/pyproject.toml b/pyproject.toml index 5a44a48..981e1b4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "py-aps" -version = "0.0.5" +version = "0.0.6" description = "Autodesk Platform Service APIs Python SDK" readme = "README.md" requires-python = ">=3.9" diff --git a/src/pyaps/auth/client.py b/src/pyaps/auth/client.py index 9aee3a2..7cc37d2 100644 --- a/src/pyaps/auth/client.py +++ b/src/pyaps/auth/client.py @@ -37,7 +37,24 @@ def __init__( user_agent: str = "pyaps-auth", cache_prefix: str = "aps", session: Optional[requests.Session] = None, + proxies: Optional[Dict[str, str]] = None, + trust_env: bool = True, ) -> None: + """ + Args: + client_id: APS Client ID + client_secret: APS Client Secret (2-legged 필수) + redirect_uri: Redirect URI (3-legged 필수) + store: Token storage implementation + auth_base_url: Authentication API base URL + userprofile_base_url: UserProfile API base URL + timeout: 요청 타임아웃 (초) + user_agent: User-Agent 헤더 값 + cache_prefix: Cache key prefix + session: 커스텀 requests.Session (선택) + proxies: 프록시 설정 (선택) + trust_env: 환경 변수에서 프록시 읽기 (기본: True) + """ self.client_id = client_id self.client_secret = client_secret self.redirect_uri = redirect_uri @@ -56,6 +73,8 @@ def _token_provider() -> str: user_agent=user_agent, timeout=timeout, session=session, + proxies=proxies, + trust_env=trust_env, ) self.http_userprofile = HTTPClient( _token_provider, @@ -63,6 +82,8 @@ def _token_provider() -> str: user_agent=user_agent, timeout=timeout, session=session, + proxies=proxies, + trust_env=trust_env, ) # Public facades diff --git a/src/pyaps/automation/client.py b/src/pyaps/automation/client.py index 763b1c8..231a67e 100644 --- a/src/pyaps/automation/client.py +++ b/src/pyaps/automation/client.py @@ -27,7 +27,19 @@ def __init__( user_agent: str = "pyaps-automation", timeout: float = 30.0, session: Optional[requests.Session] = None, + proxies: Optional[Dict[str, str]] = None, + trust_env: bool = True, ) -> None: + """ + Args: + token_provider: APS 액세스 토큰 제공 함수 + region: Design Automation 리전 (us-east, eu-west) + user_agent: User-Agent 헤더 값 + timeout: 요청 타임아웃 (초) + session: 커스텀 requests.Session (선택) + proxies: 프록시 설정 (선택) + trust_env: 환경 변수에서 프록시 읽기 (기본: True) + """ base_url = f"https://developer.api.autodesk.com/da/{region}/v3" self.http = HTTPClient( token_provider, @@ -35,6 +47,8 @@ def __init__( user_agent=user_agent, timeout=timeout, session=session, + proxies=proxies, + trust_env=trust_env, ) # ------- ForgeApps ------- diff --git a/src/pyaps/datamanagement/client.py b/src/pyaps/datamanagement/client.py index ee40ef9..202dae4 100644 --- a/src/pyaps/datamanagement/client.py +++ b/src/pyaps/datamanagement/client.py @@ -27,18 +27,35 @@ def __init__( timeout: float = 30.0, user_agent: str = "pyaps-dm", session: Optional[requests.Session] = None, + proxies: Optional[Dict[str, str]] = None, + trust_env: bool = True, ) -> None: + """ + Args: + token_provider: APS 액세스 토큰 제공 함수 + project_base_url: Project API base URL + data_base_url: Data API base URL + oss_base_url: OSS API base URL + timeout: 요청 타임아웃 (초) + user_agent: User-Agent 헤더 값 + session: 커스텀 requests.Session (선택) + proxies: 프록시 설정 (선택) + trust_env: 환경 변수에서 프록시 읽기 (기본: True) + """ self.http_project = HTTPClient( token_provider, base_url=project_base_url, - user_agent=user_agent, timeout=timeout, session=session + user_agent=user_agent, timeout=timeout, session=session, + proxies=proxies, trust_env=trust_env ) self.http_data = HTTPClient( token_provider, base_url=data_base_url, - user_agent=user_agent, timeout=timeout, session=session + user_agent=user_agent, timeout=timeout, session=session, + proxies=proxies, trust_env=trust_env ) self.http_oss = HTTPClient( token_provider, base_url=oss_base_url, - user_agent=user_agent, timeout=timeout, session=session + user_agent=user_agent, timeout=timeout, session=session, + proxies=proxies, trust_env=trust_env ) # public facades diff --git a/src/pyaps/http/client.py b/src/pyaps/http/client.py index e86bbdc..3c54e0d 100644 --- a/src/pyaps/http/client.py +++ b/src/pyaps/http/client.py @@ -24,6 +24,7 @@ class HTTPClient: - 기본 JSON Accept/Content-Type 처리 - stream/raw/text/json 응답 모드 지원 - 간단한 재시도(backoff) 옵션 + - 프록시 지원 (환경 변수 자동 감지 또는 명시적 설정) """ def __init__( @@ -35,7 +36,22 @@ def __init__( timeout: float = 30.0, session: Optional[requests.Session] = None, default_headers: Optional[Dict[str, str]] = None, + proxies: Optional[Dict[str, str]] = None, + trust_env: bool = True, ) -> None: + """ + Args: + token_provider: APS 액세스 토큰 제공 함수 + base_url: API base URL (선택) + user_agent: User-Agent 헤더 값 + timeout: 요청 타임아웃 (초) + session: 커스텀 requests.Session (선택) + default_headers: 기본 헤더 (선택) + proxies: 프록시 설정 (선택) + 예: {'http': 'http://proxy.com:8080', 'https': 'https://proxy.com:8080'} + 예: {'http': 'http://user:pass@proxy.com:8080'} + trust_env: 환경 변수(HTTP_PROXY, HTTPS_PROXY)에서 프록시 읽기 (기본: True) + """ self._tp = token_provider self.base_url = base_url.rstrip("/") if base_url else None self.user_agent = user_agent @@ -43,6 +59,13 @@ def __init__( self.session = session or requests.Session() self.default_headers = default_headers or {} + # 프록시 설정 + if proxies: + self.session.proxies.update(proxies) + + # 환경 변수에서 프록시 읽기 (trust_env=False이면 비활성화) + self.session.trust_env = trust_env + # ------------ low-level ------------ def _auth_headers(self, json_ct: bool = True) -> Dict[str, str]: h = { diff --git a/src/pyaps/http/proxy_example.py b/src/pyaps/http/proxy_example.py new file mode 100644 index 0000000..2062eae --- /dev/null +++ b/src/pyaps/http/proxy_example.py @@ -0,0 +1,292 @@ +""" +Proxy Configuration Examples for pyaps + +This module demonstrates how to configure HTTP/HTTPS proxies for all pyaps clients. +Proxy support is available in v0.0.6+. +""" + +import os +from pyaps.auth import AuthClient, Scopes +from pyaps.datamanagement import DataManagementClient +from pyaps.automation import AutomationClient, AutomationWorkflow + + +# ============================================ +# Example 1: Explicit Proxy Configuration +# ============================================ +def example_explicit_proxy(): + """Configure clients with explicit proxy settings""" + + # Define proxy settings + proxies = { + 'http': 'http://proxy.company.com:8080', + 'https': 'https://proxy.company.com:8080', + } + + # AuthClient with proxy + auth_client = AuthClient( + client_id="your_client_id", + client_secret="your_client_secret", + proxies=proxies, + trust_env=False, # Don't read from environment variables + ) + + token = auth_client.two_legged.get_token([Scopes.DATA_READ, Scopes.CODE_ALL]) + + # DataManagementClient with proxy + dm_client = DataManagementClient( + token_provider=lambda: token.access_token, + proxies=proxies, + trust_env=False, + ) + + # AutomationClient with proxy + auto_client = AutomationClient( + token_provider=lambda: token.access_token, + proxies=proxies, + trust_env=False, + ) + + # AutomationWorkflow inherits proxy settings from clients + workflow = AutomationWorkflow( + automation_client=auto_client, + data_client=dm_client, + default_bucket="my-bucket", + ) + + print("All clients configured with explicit proxy settings") + + +# ============================================ +# Example 2: Environment Variable Proxy +# ============================================ +def example_environment_proxy(): + """Configure clients to use environment variable proxies (HTTP_PROXY, HTTPS_PROXY)""" + + # Set environment variables (in production, set these in your shell or .env file) + # os.environ['HTTP_PROXY'] = 'http://proxy.company.com:8080' + # os.environ['HTTPS_PROXY'] = 'https://proxy.company.com:8080' + # os.environ['NO_PROXY'] = 'localhost,127.0.0.1' + + # Clients will automatically use environment variables when trust_env=True (default) + auth_client = AuthClient( + client_id="your_client_id", + client_secret="your_client_secret", + trust_env=True, # This is the default + ) + + token = auth_client.two_legged.get_token([Scopes.DATA_READ]) + + dm_client = DataManagementClient( + token_provider=lambda: token.access_token, + trust_env=True, + ) + + print("Clients configured to use environment variable proxies") + + +# ============================================ +# Example 3: Proxy with Authentication +# ============================================ +def example_authenticated_proxy(): + """Configure proxy that requires username/password authentication""" + + proxy_user = "proxy_username" + proxy_pass = "proxy_password" + proxy_host = "proxy.company.com:8080" + + proxies = { + 'http': f'http://{proxy_user}:{proxy_pass}@{proxy_host}', + 'https': f'https://{proxy_user}:{proxy_pass}@{proxy_host}', + } + + auth_client = AuthClient( + client_id="your_client_id", + client_secret="your_client_secret", + proxies=proxies, + trust_env=False, + ) + + token = auth_client.two_legged.get_token([Scopes.CODE_ALL]) + + auto_client = AutomationClient( + token_provider=lambda: token.access_token, + proxies=proxies, + trust_env=False, + ) + + print("Clients configured with authenticated proxy") + + +# ============================================ +# Example 4: Mixed Configuration (Environment + Explicit) +# ============================================ +def example_mixed_proxy(): + """ + Use environment variables for most traffic, but override for specific clients. + Explicit proxy settings take precedence over environment variables. + """ + + # Most clients use environment variables + auth_client = AuthClient( + client_id="your_client_id", + client_secret="your_client_secret", + trust_env=True, + ) + + token = auth_client.two_legged.get_token([Scopes.DATA_READ, Scopes.CODE_ALL]) + + # Override for automation client (e.g., use different proxy for Design Automation) + special_proxies = { + 'http': 'http://automation-proxy.company.com:8080', + 'https': 'https://automation-proxy.company.com:8080', + } + + auto_client = AutomationClient( + token_provider=lambda: token.access_token, + proxies=special_proxies, # Explicit setting overrides environment + trust_env=False, + ) + + print("Mixed proxy configuration applied") + + +# ============================================ +# Example 5: No Proxy Configuration +# ============================================ +def example_no_proxy(): + """Disable proxy completely, even if environment variables are set""" + + # To disable proxy completely, set trust_env=False and don't provide proxies + auth_client = AuthClient( + client_id="your_client_id", + client_secret="your_client_secret", + proxies=None, + trust_env=False, # Ignore environment variables + ) + + token = auth_client.two_legged.get_token([Scopes.DATA_READ]) + + dm_client = DataManagementClient( + token_provider=lambda: token.access_token, + proxies=None, + trust_env=False, + ) + + print("Clients configured without any proxy") + + +# ============================================ +# Example 6: Complete Workflow with Proxy +# ============================================ +def example_complete_workflow_with_proxy(): + """Complete example: Authentication -> Data Management -> Automation with proxy""" + + # Configure proxy + proxies = { + 'http': 'http://proxy.company.com:8080', + 'https': 'https://proxy.company.com:8080', + } + + # Step 1: Authenticate + auth_client = AuthClient( + client_id=os.getenv("APS_CLIENT_ID"), + client_secret=os.getenv("APS_CLIENT_SECRET"), + proxies=proxies, + trust_env=False, + ) + + token = auth_client.two_legged.get_token([ + Scopes.DATA_READ, + Scopes.DATA_WRITE, + Scopes.BUCKET_CREATE, + Scopes.BUCKET_READ, + Scopes.CODE_ALL, + ]) + + # Step 2: Setup clients + dm_client = DataManagementClient( + token_provider=lambda: token.access_token, + proxies=proxies, + trust_env=False, + ) + + auto_client = AutomationClient( + token_provider=lambda: token.access_token, + proxies=proxies, + trust_env=False, + ) + + # Step 3: Run workflow + workflow = AutomationWorkflow( + automation_client=auto_client, + data_client=dm_client, + default_bucket="my-design-automation-bucket", + ) + + # Execute workitem (all HTTP requests will use proxy) + result = workflow.run_workitem_with_files( + activity_id="YourAlias.YourActivity+prod", + input_files={"inputFile": "path/to/input.rvt"}, + output_files={"outputFile": "output.rvt"}, + download_outputs=True, + output_dir="./results", + ) + + print(f"WorkItem completed: {result.status}") + print(f"Downloaded files: {result.downloaded_files}") + + +# ============================================ +# Proxy Configuration Best Practices +# ============================================ +""" +BEST PRACTICES: + +1. Security: + - Never hardcode proxy credentials in source code + - Use environment variables or secure configuration management + - Be cautious with proxy logs that might contain sensitive data + +2. Environment Variables: + - Set HTTP_PROXY and HTTPS_PROXY in your shell or .env file + - Use NO_PROXY to exclude certain hosts (e.g., localhost, internal APIs) + - Example: + export HTTP_PROXY=http://proxy.company.com:8080 + export HTTPS_PROXY=https://proxy.company.com:8080 + export NO_PROXY=localhost,127.0.0.1,.internal.com + +3. Corporate Environments: + - Contact your IT department for correct proxy settings + - Some proxies may require SSL certificate configuration + - Test proxy settings with simple requests before running workflows + +4. Troubleshooting: + - If requests hang, check proxy connectivity + - Verify proxy authentication credentials + - Check firewall rules and network policies + - Use requests library's built-in debugging: + import logging + logging.basicConfig(level=logging.DEBUG) + +5. Performance: + - Proxy adds latency to requests + - Consider connection pooling (handled automatically by requests.Session) + - Monitor timeout settings if proxy is slow +""" + + +if __name__ == "__main__": + print("Proxy Configuration Examples for pyaps") + print("=" * 60) + print() + + # Uncomment the example you want to run: + # example_explicit_proxy() + # example_environment_proxy() + # example_authenticated_proxy() + # example_mixed_proxy() + # example_no_proxy() + # example_complete_workflow_with_proxy() + + print("\nFor more information, see the README.md file")