Spaces:
				
			
			
	
			
			
		Paused
		
	
	
	
			
			
	
	
	
	
		
		
		Paused
		
	Update api_executor.py
Browse files- api_executor.py +155 -66
 
    	
        api_executor.py
    CHANGED
    
    | 
         @@ -8,6 +8,10 @@ from typing import Any, Dict, Optional, Union 
     | 
|
| 8 | 
         
             
            from logger import log_info, log_error, log_warning, log_debug
         
     | 
| 9 | 
         
             
            from config_provider import ConfigProvider, APIConfig
         
     | 
| 10 | 
         
             
            from session import Session
         
     | 
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 11 | 
         | 
| 12 | 
         
             
            _placeholder = re.compile(r"\{\{\s*([^\}]+?)\s*\}\}")
         
     | 
| 13 | 
         | 
| 
         @@ -213,13 +217,17 @@ def _ensure_token(api: APIConfig, session: Session) -> None: 
     | 
|
| 213 | 
         
             
                _fetch_token(api, session)
         
     | 
| 214 | 
         | 
| 215 | 
         
             
            def call_api(api: APIConfig, session: Session) -> requests.Response:
         
     | 
| 216 | 
         
            -
                """Execute API call with automatic token management"""
         
     | 
| 
         | 
|
| 217 | 
         
             
                # Ensure valid token
         
     | 
| 218 | 
         
             
                _ensure_token(api, session)
         
     | 
| 219 | 
         | 
| 220 | 
         
             
                # Prepare request
         
     | 
| 221 | 
         
            -
                headers = _render(api.headers, session, api.name) 
     | 
| 222 | 
         
            -
                body = _render_json(api.body_template, session, api.name) 
     | 
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 223 | 
         | 
| 224 | 
         
             
                # Handle proxy
         
     | 
| 225 | 
         
             
                proxies = None
         
     | 
| 
         @@ -235,7 +243,8 @@ def call_api(api: APIConfig, session: Session) -> requests.Response: 
     | 
|
| 235 | 
         
             
                    "method": api.method,
         
     | 
| 236 | 
         
             
                    "url": str(api.url),
         
     | 
| 237 | 
         
             
                    "headers": headers,
         
     | 
| 238 | 
         
            -
                    "timeout":  
     | 
| 
         | 
|
| 239 | 
         
             
                }
         
     | 
| 240 | 
         | 
| 241 | 
         
             
                # Add body based on method
         
     | 
| 
         @@ -254,84 +263,164 @@ def call_api(api: APIConfig, session: Session) -> requests.Response: 
     | 
|
| 254 | 
         | 
| 255 | 
         
             
                for attempt in range(retry_count + 1):
         
     | 
| 256 | 
         
             
                    try:
         
     | 
| 257 | 
         
            -
                         
     | 
| 258 | 
         
            -
                         
     | 
| 259 | 
         
            -
             
     | 
| 260 | 
         
            -
             
     | 
| 261 | 
         
            -
             
     | 
| 262 | 
         
            -
             
     | 
| 263 | 
         
            -
             
     | 
| 264 | 
         
            -
             
     | 
| 265 | 
         
            -
             
     | 
| 266 | 
         
            -
                             
     | 
| 267 | 
         
            -
                             
     | 
| 268 | 
         
            -
                             
     | 
| 269 | 
         
            -
             
     | 
| 270 | 
         
            -
             
     | 
| 271 | 
         
            -
             
     | 
| 272 | 
         
            -
             
     | 
| 273 | 
         
            -
             
     | 
| 274 | 
         
            -
             
     | 
| 275 | 
         
            -
                             
     | 
| 276 | 
         
            -
             
     | 
| 277 | 
         
            -
             
     | 
| 278 | 
         
            -
             
     | 
| 279 | 
         
            -
             
     | 
| 280 | 
         
            -
             
     | 
| 281 | 
         
            -
             
     | 
| 282 | 
         
            -
             
     | 
| 283 | 
         
            -
             
     | 
| 284 | 
         
            -
             
     | 
| 285 | 
         
            -
             
     | 
| 286 | 
         
            -
             
     | 
| 287 | 
         
            -
             
     | 
| 288 | 
         
            -
             
     | 
| 289 | 
         
            -
             
     | 
| 290 | 
         
            -
             
     | 
| 291 | 
         
            -
             
     | 
| 292 | 
         
            -
             
     | 
| 293 | 
         
            -
             
     | 
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 294 | 
         | 
| 295 | 
         
            -
                                         
     | 
| 296 | 
         
            -
                                             
     | 
| 297 | 
         
            -
                                             
     | 
| 298 | 
         
            -
             
     | 
| 299 | 
         
            -
                                            elif var_type == 'float':
         
     | 
| 300 | 
         
            -
                                                value = float(value)
         
     | 
| 301 | 
         
            -
                                            elif var_type == 'bool':
         
     | 
| 302 | 
         
            -
                                                value = bool(value)
         
     | 
| 303 | 
         
            -
                                            elif var_type == 'date':
         
     | 
| 304 | 
         
            -
                                                # ISO format'ta sakla
         
     | 
| 305 | 
         
            -
                                                value = str(value)
         
     | 
| 306 | 
         
            -
                                            else:  # str
         
     | 
| 307 | 
         
            -
                                                value = str(value)
         
     | 
| 308 | 
         | 
| 309 | 
         
            -
                                             
     | 
| 310 | 
         
            -
             
     | 
| 311 | 
         
            -
                                            log_info(f"๐ Mapped response value: {var_name} = {value}")
         
     | 
| 312 | 
         | 
| 313 | 
         
            -
             
     | 
| 314 | 
         
            -
             
     | 
| 315 | 
         
            -
             
     | 
| 316 | 
         
            -
             
     | 
| 317 | 
         
            -
             
     | 
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 318 | 
         
             
                    except requests.exceptions.Timeout as e:
         
     | 
| 319 | 
         
             
                        last_error = e
         
     | 
| 320 | 
         
            -
                        log_warning( 
     | 
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 321 | 
         | 
| 322 | 
         
             
                    except requests.exceptions.RequestException as e:
         
     | 
| 323 | 
         
             
                        last_error = e
         
     | 
| 324 | 
         
            -
                        log_error( 
     | 
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 325 | 
         | 
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 326 | 
         
             
                    # Retry backoff
         
     | 
| 327 | 
         
             
                    if attempt < retry_count:
         
     | 
| 328 | 
         
             
                        backoff = api.retry.backoff_seconds if api.retry else 2
         
     | 
| 329 | 
         
             
                        if api.retry and api.retry.strategy == "exponential":
         
     | 
| 330 | 
         
             
                            backoff = backoff * (2 ** attempt)
         
     | 
| 331 | 
         
            -
                        log_info(f"โณ  
     | 
| 332 | 
         
             
                        time.sleep(backoff)
         
     | 
| 333 | 
         | 
| 334 | 
         
             
                # All retries failed
         
     | 
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 335 | 
         
             
                if last_error:
         
     | 
| 336 | 
         
             
                    raise last_error
         
     | 
| 337 | 
         
            -
                raise requests.exceptions.RequestException( 
     | 
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
| 
         | 
|
| 8 | 
         
             
            from logger import log_info, log_error, log_warning, log_debug
         
     | 
| 9 | 
         
             
            from config_provider import ConfigProvider, APIConfig
         
     | 
| 10 | 
         
             
            from session import Session
         
     | 
| 11 | 
         
            +
            import os
         
     | 
| 12 | 
         
            +
             
     | 
| 13 | 
         
            +
            MAX_RESPONSE_SIZE = 10 * 1024 * 1024  # 10MB
         
     | 
| 14 | 
         
            +
            DEFAULT_TIMEOUT = int(os.getenv("API_TIMEOUT_SECONDS", "30"))
         
     | 
| 15 | 
         | 
| 16 | 
         
             
            _placeholder = re.compile(r"\{\{\s*([^\}]+?)\s*\}\}")
         
     | 
| 17 | 
         | 
| 
         | 
|
| 217 | 
         
             
                _fetch_token(api, session)
         
     | 
| 218 | 
         | 
| 219 | 
         
             
            def call_api(api: APIConfig, session: Session) -> requests.Response:
         
     | 
| 220 | 
         
            +
                """Execute API call with automatic token management and better error handling"""
         
     | 
| 221 | 
         
            +
                
         
     | 
| 222 | 
         
             
                # Ensure valid token
         
     | 
| 223 | 
         
             
                _ensure_token(api, session)
         
     | 
| 224 | 
         | 
| 225 | 
         
             
                # Prepare request
         
     | 
| 226 | 
         
            +
                headers = _render(api.headers, session, api.name)
         
     | 
| 227 | 
         
            +
                body = _render_json(api.body_template, session, api.name)
         
     | 
| 228 | 
         
            +
                
         
     | 
| 229 | 
         
            +
                # Get timeout with fallback
         
     | 
| 230 | 
         
            +
                timeout = api.timeout_seconds if api.timeout_seconds else DEFAULT_TIMEOUT
         
     | 
| 231 | 
         | 
| 232 | 
         
             
                # Handle proxy
         
     | 
| 233 | 
         
             
                proxies = None
         
     | 
| 
         | 
|
| 243 | 
         
             
                    "method": api.method,
         
     | 
| 244 | 
         
             
                    "url": str(api.url),
         
     | 
| 245 | 
         
             
                    "headers": headers,
         
     | 
| 246 | 
         
            +
                    "timeout": timeout,  # Use configured timeout
         
     | 
| 247 | 
         
            +
                    "stream": True  # Enable streaming for large responses
         
     | 
| 248 | 
         
             
                }
         
     | 
| 249 | 
         | 
| 250 | 
         
             
                # Add body based on method
         
     | 
| 
         | 
|
| 263 | 
         | 
| 264 | 
         
             
                for attempt in range(retry_count + 1):
         
     | 
| 265 | 
         
             
                    try:
         
     | 
| 266 | 
         
            +
                        # Use LogTimer for performance tracking
         
     | 
| 267 | 
         
            +
                        with LogTimer(f"API call {api.name}", attempt=attempt + 1):
         
     | 
| 268 | 
         
            +
                            log_info(
         
     | 
| 269 | 
         
            +
                                f"๐ API call starting",
         
     | 
| 270 | 
         
            +
                                api=api.name,
         
     | 
| 271 | 
         
            +
                                method=api.method,
         
     | 
| 272 | 
         
            +
                                url=api.url,
         
     | 
| 273 | 
         
            +
                                attempt=f"{attempt + 1}/{retry_count + 1}",
         
     | 
| 274 | 
         
            +
                                timeout=timeout
         
     | 
| 275 | 
         
            +
                            )
         
     | 
| 276 | 
         
            +
                            
         
     | 
| 277 | 
         
            +
                            if body:
         
     | 
| 278 | 
         
            +
                                log_debug(f"๐ Request body", body=json.dumps(body, ensure_ascii=False)[:500])
         
     | 
| 279 | 
         
            +
                            
         
     | 
| 280 | 
         
            +
                            # Make request with streaming
         
     | 
| 281 | 
         
            +
                            response = requests.request(**request_params)
         
     | 
| 282 | 
         
            +
                            
         
     | 
| 283 | 
         
            +
                            # Check response size from headers
         
     | 
| 284 | 
         
            +
                            content_length = response.headers.get('content-length')
         
     | 
| 285 | 
         
            +
                            if content_length and int(content_length) > MAX_RESPONSE_SIZE:
         
     | 
| 286 | 
         
            +
                                response.close()
         
     | 
| 287 | 
         
            +
                                raise ValueError(f"Response too large: {int(content_length)} bytes (max: {MAX_RESPONSE_SIZE})")
         
     | 
| 288 | 
         
            +
                            
         
     | 
| 289 | 
         
            +
                            # Handle 401 Unauthorized
         
     | 
| 290 | 
         
            +
                            if response.status_code == 401 and api.auth and api.auth.enabled and attempt < retry_count:
         
     | 
| 291 | 
         
            +
                                log_warning(f"๐ Got 401, refreshing token", api=api.name)
         
     | 
| 292 | 
         
            +
                                _fetch_token(api, session)
         
     | 
| 293 | 
         
            +
                                headers = _render(api.headers, session, api.name)
         
     | 
| 294 | 
         
            +
                                request_params["headers"] = headers
         
     | 
| 295 | 
         
            +
                                response.close()
         
     | 
| 296 | 
         
            +
                                continue
         
     | 
| 297 | 
         
            +
                            
         
     | 
| 298 | 
         
            +
                            # Read response with size limit
         
     | 
| 299 | 
         
            +
                            content_size = 0
         
     | 
| 300 | 
         
            +
                            chunks = []
         
     | 
| 301 | 
         
            +
                            
         
     | 
| 302 | 
         
            +
                            for chunk in response.iter_content(chunk_size=8192):
         
     | 
| 303 | 
         
            +
                                chunks.append(chunk)
         
     | 
| 304 | 
         
            +
                                content_size += len(chunk)
         
     | 
| 305 | 
         
            +
                                
         
     | 
| 306 | 
         
            +
                                if content_size > MAX_RESPONSE_SIZE:
         
     | 
| 307 | 
         
            +
                                    response.close()
         
     | 
| 308 | 
         
            +
                                    raise ValueError(f"Response exceeded size limit: {content_size} bytes")
         
     | 
| 309 | 
         
            +
                            
         
     | 
| 310 | 
         
            +
                            # Reconstruct response content
         
     | 
| 311 | 
         
            +
                            response._content = b''.join(chunks)
         
     | 
| 312 | 
         
            +
                            response._content_consumed = True
         
     | 
| 313 | 
         
            +
                            
         
     | 
| 314 | 
         
            +
                            # Check status
         
     | 
| 315 | 
         
            +
                            response.raise_for_status()
         
     | 
| 316 | 
         
            +
                            
         
     | 
| 317 | 
         
            +
                            log_info(
         
     | 
| 318 | 
         
            +
                                f"โ
 API call successful",
         
     | 
| 319 | 
         
            +
                                api=api.name,
         
     | 
| 320 | 
         
            +
                                status_code=response.status_code,
         
     | 
| 321 | 
         
            +
                                response_size=content_size,
         
     | 
| 322 | 
         
            +
                                duration_ms=f"{response.elapsed.total_seconds() * 1000:.2f}"
         
     | 
| 323 | 
         
            +
                            )
         
     | 
| 324 | 
         
            +
                            
         
     | 
| 325 | 
         
            +
                            # Mevcut response mapping iลlemi korunacak
         
     | 
| 326 | 
         
            +
                            if response.status_code in (200, 201, 202, 204) and hasattr(api, 'response_mappings') and api.response_mappings:
         
     | 
| 327 | 
         
            +
                                try:
         
     | 
| 328 | 
         
            +
                                    if response.status_code != 204 and response.content:
         
     | 
| 329 | 
         
            +
                                        response_json = response.json()
         
     | 
| 330 | 
         | 
| 331 | 
         
            +
                                        for mapping in api.response_mappings:
         
     | 
| 332 | 
         
            +
                                            var_name = mapping.get('variable_name')
         
     | 
| 333 | 
         
            +
                                            var_type = mapping.get('type', 'str')
         
     | 
| 334 | 
         
            +
                                            json_path = mapping.get('json_path')
         
     | 
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 335 | 
         | 
| 336 | 
         
            +
                                            if not all([var_name, json_path]):
         
     | 
| 337 | 
         
            +
                                                continue
         
     | 
| 
         | 
|
| 338 | 
         | 
| 339 | 
         
            +
                                            # JSON path'ten deฤeri al
         
     | 
| 340 | 
         
            +
                                            value = response_json
         
     | 
| 341 | 
         
            +
                                            for path_part in json_path.split('.'):
         
     | 
| 342 | 
         
            +
                                                if isinstance(value, dict):
         
     | 
| 343 | 
         
            +
                                                    value = value.get(path_part)
         
     | 
| 344 | 
         
            +
                                                    if value is None:
         
     | 
| 345 | 
         
            +
                                                        break
         
     | 
| 346 | 
         
            +
                                            
         
     | 
| 347 | 
         
            +
                                            if value is not None:
         
     | 
| 348 | 
         
            +
                                                # Type conversion
         
     | 
| 349 | 
         
            +
                                                if var_type == 'int':
         
     | 
| 350 | 
         
            +
                                                    value = int(value)
         
     | 
| 351 | 
         
            +
                                                elif var_type == 'float':
         
     | 
| 352 | 
         
            +
                                                    value = float(value)
         
     | 
| 353 | 
         
            +
                                                elif var_type == 'bool':
         
     | 
| 354 | 
         
            +
                                                    value = bool(value)
         
     | 
| 355 | 
         
            +
                                                elif var_type == 'date':
         
     | 
| 356 | 
         
            +
                                                    value = str(value)
         
     | 
| 357 | 
         
            +
                                                else:  # str
         
     | 
| 358 | 
         
            +
                                                    value = str(value)
         
     | 
| 359 | 
         
            +
                                                
         
     | 
| 360 | 
         
            +
                                                # Session'a kaydet
         
     | 
| 361 | 
         
            +
                                                session.variables[var_name] = value
         
     | 
| 362 | 
         
            +
                                                log_info(f"๐ Mapped response", variable=var_name, value=value)
         
     | 
| 363 | 
         
            +
                                                
         
     | 
| 364 | 
         
            +
                                except Exception as e:
         
     | 
| 365 | 
         
            +
                                    log_error("โ ๏ธ Response mapping error", error=str(e))
         
     | 
| 366 | 
         
            +
                            
         
     | 
| 367 | 
         
            +
                            return response
         
     | 
| 368 | 
         
            +
                            
         
     | 
| 369 | 
         
             
                    except requests.exceptions.Timeout as e:
         
     | 
| 370 | 
         
             
                        last_error = e
         
     | 
| 371 | 
         
            +
                        log_warning(
         
     | 
| 372 | 
         
            +
                            f"โฑ๏ธ API timeout",
         
     | 
| 373 | 
         
            +
                            api=api.name,
         
     | 
| 374 | 
         
            +
                            attempt=attempt + 1,
         
     | 
| 375 | 
         
            +
                            timeout=timeout
         
     | 
| 376 | 
         
            +
                        )
         
     | 
| 377 | 
         | 
| 378 | 
         
             
                    except requests.exceptions.RequestException as e:
         
     | 
| 379 | 
         
             
                        last_error = e
         
     | 
| 380 | 
         
            +
                        log_error(
         
     | 
| 381 | 
         
            +
                            f"โ API request error",
         
     | 
| 382 | 
         
            +
                            api=api.name,
         
     | 
| 383 | 
         
            +
                            error=str(e),
         
     | 
| 384 | 
         
            +
                            attempt=attempt + 1
         
     | 
| 385 | 
         
            +
                        )
         
     | 
| 386 | 
         
            +
                        
         
     | 
| 387 | 
         
            +
                    except ValueError as e:  # Size limit exceeded
         
     | 
| 388 | 
         
            +
                        log_error(
         
     | 
| 389 | 
         
            +
                            f"โ Response size error",
         
     | 
| 390 | 
         
            +
                            api=api.name,
         
     | 
| 391 | 
         
            +
                            error=str(e)
         
     | 
| 392 | 
         
            +
                        )
         
     | 
| 393 | 
         
            +
                        raise  # Don't retry for size errors
         
     | 
| 394 | 
         | 
| 395 | 
         
            +
                    except Exception as e:
         
     | 
| 396 | 
         
            +
                        last_error = e
         
     | 
| 397 | 
         
            +
                        log_error(
         
     | 
| 398 | 
         
            +
                            f"โ Unexpected API error",
         
     | 
| 399 | 
         
            +
                            api=api.name,
         
     | 
| 400 | 
         
            +
                            error=str(e),
         
     | 
| 401 | 
         
            +
                            attempt=attempt + 1
         
     | 
| 402 | 
         
            +
                        )
         
     | 
| 403 | 
         
            +
                    
         
     | 
| 404 | 
         
             
                    # Retry backoff
         
     | 
| 405 | 
         
             
                    if attempt < retry_count:
         
     | 
| 406 | 
         
             
                        backoff = api.retry.backoff_seconds if api.retry else 2
         
     | 
| 407 | 
         
             
                        if api.retry and api.retry.strategy == "exponential":
         
     | 
| 408 | 
         
             
                            backoff = backoff * (2 ** attempt)
         
     | 
| 409 | 
         
            +
                        log_info(f"โณ Retry backoff", wait_seconds=backoff, next_attempt=attempt + 2)
         
     | 
| 410 | 
         
             
                        time.sleep(backoff)
         
     | 
| 411 | 
         | 
| 412 | 
         
             
                # All retries failed
         
     | 
| 413 | 
         
            +
                error_msg = f"API call failed after {retry_count + 1} attempts"
         
     | 
| 414 | 
         
            +
                log_error(error_msg, api=api.name, last_error=str(last_error))
         
     | 
| 415 | 
         
            +
                
         
     | 
| 416 | 
         
             
                if last_error:
         
     | 
| 417 | 
         
             
                    raise last_error
         
     | 
| 418 | 
         
            +
                raise requests.exceptions.RequestException(error_msg)
         
     | 
| 419 | 
         
            +
             
     | 
| 420 | 
         
            +
            def format_size(size_bytes: int) -> str:
         
     | 
| 421 | 
         
            +
                """Format bytes to human readable format"""
         
     | 
| 422 | 
         
            +
                for unit in ['B', 'KB', 'MB', 'GB']:
         
     | 
| 423 | 
         
            +
                    if size_bytes < 1024.0:
         
     | 
| 424 | 
         
            +
                        return f"{size_bytes:.2f} {unit}"
         
     | 
| 425 | 
         
            +
                    size_bytes /= 1024.0
         
     | 
| 426 | 
         
            +
                return f"{size_bytes:.2f} TB"
         
     |