
    Bi,X                     J   d Z ddlZddlZddlmZmZ ddlmZ ddlmZm	Z	 ddl
Z
ddlmZmZmZmZmZ ddlmZmZ dd	lmZmZ d
dlmZmZmZmZmZ  ej:                  e      Z G d de       Z! G d de!      Z" G d de!      Z# G d de!      Z$ G d de!      Z% G d d      Z&y)z
Napkin AI API client with async support and retry logic.

Provides a robust HTTP client for interacting with the Napkin API,
including authentication, rate limiting, and exponential backoff.
    N)datetimetimezone)Path)OptionalUnion)retryretry_if_exception_typestop_after_attemptwait_exponentialbefore_sleep_log   )Settingsget_settings)API_ENDPOINTSHTTP_STATUS   )RateLimitInfoRequestStatusStatusResponseVisualRequestVisualResponsec                   @     e Zd ZdZ	 ddedee   dee   f fdZ xZS )NapkinAPIErrorz%Base exception for Napkin API errors.messagecodedetailsc                 @    t         |   |       || _        || _        y N)super__init__r   r   )selfr   r   r   	__class__s       -/var/www/html/Napkin-AI-API/src/api/client.pyr    zNapkinAPIError.__init__(   s      	!	    NN)	__name__
__module____qualname____doc__strr   dictr    __classcell__r"   s   @r#   r   r   %   s7    / SW"*3-AI$ r$   r   c                       e Zd ZdZy)AuthenticationErrorzAuthentication failed.Nr&   r'   r(   r)    r$   r#   r/   r/   0   s     r$   r/   c                   ,     e Zd ZdZdedef fdZ xZS )RateLimitErrorzRate limit exceeded.r   retry_afterc                 2    t         |   |       || _        y r   )r   r    r4   )r!   r   r4   r"   s      r#   r    zRateLimitError.__init__9   s    !&r$   )r&   r'   r(   r)   r*   intr    r,   r-   s   @r#   r3   r3   6   s    ' '# ' 'r$   r3   c                       e Zd ZdZy)RequestErrorzRequest validation error.Nr0   r1   r$   r#   r8   r8   >   s    #r$   r8   c                       e Zd ZdZy)ProcessingErrorzVisual processing error.Nr0   r1   r$   r#   r:   r:   D   s    "r$   r:   c                      e Zd ZdZd3dee   fdZd Zd Zd Z	de
j                  d	ee   fd
Zde
j                  fdZ e ee
j"                         ed       eddd       eeej.                              deded	e
j                  fd       Zded	efdZded	efdZded	efdZ 	 d3dededee!   d	e"e#e!f   fdZ$	 	 	 d4dede"ee!f   d ee   dee   dee   d	e!fd!Z%dedee!   d	e"e#e!f   fd"Z&d#ee   d	ee   fd$Z'd%e!d	e!fd&Z(d'ee   d	ee   fd(Z)d)ed*e*d	e*fd+Z+d)ed*e,d	e,fd,Z-deded	e#fd-Z.ded	e#fd.Z/	 	 d5ded/ee0   d0ee*   d	efd1Z1d	ee   fd2Z2y)6NapkinAPIClientzAsync client for Napkin AI API.Nsettingsc                    |xs
 t               | _        | j                  j                   d| j                  j                   | _        | j                  j                         | _        d| _        t        j                  t        j                  | j                  j                  | j                  j                  | j                  j                  | j                  j                        | j                        | _        d| _        y)z
        Initialize API client.

        Args:
            settings: Configuration settings. If None, loads from environment.
        /T)connectreadwritepool)timeoutheadersN)r   r=   api_base_urlapi_versionbase_urlget_headersrE   _owns_clienthttpxAsyncClientTimeouttimeout_secondsclientrate_limit_info)r!   r=   s     r#   r    zNapkinAPIClient.__init__M   s     !2LN==556a8Q8Q7RS}}002 !''MM55]]22mm33]]22	 LL
 9=r$   c                    K   | S w)zAsync context manager entry.r1   r!   s    r#   
__aenter__zNapkinAPIClient.__aenter__h   s     s   c                 @   K   | j                          d{    y7 w)zAsync context manager exit.N)close)r!   exc_typeexc_valexc_tbs       r#   	__aexit__zNapkinAPIClient.__aexit__l   s     jjls   c                 p   K   t        | dd      r#| j                  j                          d{    yy7 w)zClose HTTP client.rJ   TN)getattrrO   acloserR   s    r#   rU   zNapkinAPIClient.closep   s2      4.++$$&&& /&s   +646responsereturnc                    	 |j                   }d|v sd|v r	 t        |j                  dd            }	 t        |j                  dd            }d}|j                  d	      }|j                  d      }	 |t        j                  t        |            }d}		 |t        |      }	|t        ||||	      S y	 y# t        t        f$ r d}Y w xY w# t        t        f$ r d}Y w xY w# t        t        t        f$ r |	 t        |      }t        j                  t        t        j                  t        j                        j                               |z   t        j                  
      }n# t        t        f$ r d}Y nw xY wY w xY w# t        t        f$ r d}	Y w xY w# t        $ r"}
t        j                  d|
        Y d}
~
yd}
~
ww xY w)z5Extract rate limit information from response headers.zX-RateLimit-LimitRetry-After60<   zX-RateLimit-Remaining0r   NzX-RateLimit-Reset)tz)limit	remainingresetr4   z$Failed to parse rate limit headers: )rE   r6   get
ValueError	TypeErrorr   fromtimestampOSErrornowr   utc	timestampr   	Exceptionloggerwarning)r!   r]   rE   re   rf   rg   	reset_hdrretry_after_valrar4   es              r#   _extract_rate_limit_infoz(NapkinAPIClient._extract_rate_limit_infov   s   6	G&&G"g-'1I,? FGE" #GKK0G$M NI
 #KK(;<	")++m"<) , ( 6 6s9~ F #'&2&)/&: $(#"+#$/	   c 2Jh a #I. E #I. " !I" #Iw7 )&2)!$_!5B$,$:$: #HLL$>$H$H$J Kb P#+<<%E !+I6 )$(E))$ #I. '"&K'  	GNNA!EFF	Gs   F B7 C $F 3 C% F F #F 7CF 
CF C"F !C""F %F<A+E('F(E<9F;E<<F?F FF FF FF 	G%GGc                 "   	 |j                         }|j                  dd      }|j                  d      }|j                  d      }|j                  t
        d   k(  rt        |||      |j                  t
        d	   k(  r1t        |j                  j                  d
d            }t        ||      |j                  t
        d   k(  rt        |||      t        |||      # t        $ r% |j                  xs d|j                   d}d}d}Y w xY w)zHandle API error responses.errorUnknown errorr   r   zHTTP z errorNunauthorized
rate_limitr`   ra   bad_request)jsonrh   rp   textstatus_coder   r/   r6   rE   r3   r8   r   )r!   r]   
error_data	error_msg
error_coder   r4   s          r#   _handle_error_responsez&NapkinAPIClient._handle_error_response   s	   	!J"w@I#/J nnY/G ;~#>>%iWEE!![%>>h..22=$GHK K88!![%??y*g>> J@@  	 M51E1E0Ff)MIJG	s   AC   +DD   r   r   
   )
multiplierminmax)r   stopwaitbefore_sleepmethodendpointc                   K   | j                    | }t        j                  | d|         | j                  j                  ||fi | d{   }| j                  |      | _        |j                  dk\  r| j                  |       |S 7 <w)z#Make HTTP request with retry logic. N  )	rH   rq   debugrO   requestrw   rP   r   r   )r!   r   r   kwargsurlr]   s         r#   _make_requestzNapkinAPIClient._make_request   s      z*xq&',,,VSCFCC  $<<XF 3&''1 Ds   ABB=Br   c           	      b  K   t         d   }|j                  d      }	 t        t        |dd      xs d      }t
        j                  d|       | j                  d||	       d
{   }|j                         }t
        j                  d|        t        |j                  d      xs |j                  d      t        |j                  dd            d|v r| j                  |d         g       S t        j                  t         j"                        g       S # t        $ r d}Y w xY w7 ƭw)a  
        Create a new visual generation request.

        Args:
            request: Visual generation parameters.

        Returns:
            VisualResponse with request ID and initial status.

        Raises:
            NapkinAPIError: If request fails.
        create_visualT)exclude_nonecontent r   z+Creating visual request (content_length=%d)POST)r~   NzCreate visual response: id
request_idstatuspending
created_at)r   r   r   files)r   
model_dumplenr[   rp   rq   infor   r~   r   r   rh   r   _parse_iso8601r   rm   r   rn   )r!   r   r   request_datacontent_lenr]   response_datas          r#   r   zNapkinAPIClient.create_visual   s9      !1 ))t)<	ggy"=CDK 	A;O ++ , 
 
 !/?@ $((.Q-2C2CL2Q !2!28Y!GH}, **=+FG 
 	

 hll+
 	
!  	K	

s4   D/D -D/&D-'B5D/D*'D/)D**D/sc                 Z   	 |j                  d      r%t        j                  |j                  dd            S t        j                  |      }|j                  r|S |j                  t
        j                        S # t        $ r& t        j                  t
        j                        cY S w xY w)z\Parse ISO8601 timestamps, supporting trailing 'Z', returning timezone-aware UTC on fallback.Zz+00:00)tzinfo)	endswithr   fromisoformatreplacer   r   rn   rp   rm   )r!   r   dts      r#   r   zNapkinAPIClient._parse_iso8601  s    	.zz#--aiiX.FGG''*B2G

(,,
(GG 	.<<--	.s   5A; "A; A; ;,B*)B*r   c                    K   t         d   j                  |      }| j                  d|       d{   }|j                         }t        j                  d||       |j                  d      }|j                  d      }|j                  d      }d}t        |t              r|}n4t        |t              r|}n!t        |t              r|D 	cg c]  }	d	|	i }}	|t        |      }
t        |      }n>t        |j                  d
d      xs d      }
t        |j                  dd      xs d      }t        |t        |d         |j                  d      |j                  d      |
||j                  d            }|||_        |S 7 Uc c}	w w)a  
        Check status of a visual generation request.

        Args:
            request_id: The request ID to check.

        Returns:
            StatusResponse with current status and progress.

        Raises:
            NapkinAPIError: If request fails.
        
get_status)r   GETNzStatus response for %s: %sgenerated_filesr   urlsr   files_readyr   files_totalr   progressr   ry   )r   r   r   r   r   r   ry   )r   formatr   r~   rq   r   rh   
isinstancelistr   r6   r   r   r   )r!   r   r   r]   r   raw_generated	raw_filesraw_urls	candidateur   r   status_resps                r#   r   zNapkinAPIClient.get_status"  s      !.555L ++E8<< !1:}M &))*;<!%%g.	 $$V, 	mT*%I	4(!I$'-56X%XI6 i.Ki.Km//qAFQGKm//qAFQGK$! x!89"&&z2!%%i0####G,
   )K[ =, 7s#   /FFBFF	B0F	Ffile_id	save_pathc                    K   t         d   j                  ||      }| j                   | }| j                  ||       d{   S 7 w)z
        Download a generated file by request/file ID.
        Streams to disk if save_path is provided to avoid large memory usage.
        get_filer   r   N)r   r   rH   _stream_or_bytes)r!   r   r   r   r   r   s         r#   download_filezNapkinAPIClient.download_filed  sU      !,33! 4 
 z***3	::::s   A A	AA	r   
output_dirinferred_namec                 P  K   t        |      }|j                  dd       | j                  j                  d|| j                        4 d{   }|j
                  dk\  r`|j                          d{   }t        j                  |j
                  |j                  ||j                        }| j                  |       |j                  j                  dd	      xs |j                  j                  d
d	      }	| j                  |	      }
|
s|}
|
s| j                  |j                  j                  d            }d}|r
|r| d| }n|r|}|s7	 t        t        j                  |      j                        j                   xs d}|r|j%                  d|       s| d| n|r|nd}
||
z  }| j'                  dd      }|j)                         r|s| j+                  |      }| j-                  dd      }|j/                  d      5 }|j1                  |      2 3 d{   }|j3                  |       7 7 # t"        $ r d}Y w xY w7 -6 	 ddd       n# 1 sw Y   nxY wddd      d{  7   n# 1 d{  7  sw Y   nxY wt4        j7                  d       |S w)aQ  
        Stream a file from a signed/authorized URL to disk with filename inference.
        - Parses Content-Disposition for filename (RFC 6266/5987)
        - Falls back to provided inferred_name, then API metadata (request_id/file_id), then URL path
        - Respects NAPKIN_DOWNLOAD_CHUNK_SIZE and NAPKIN_DOWNLOAD_OVERWRITE
        Tparentsexist_okr   rE   Nr   r   rE   r   r   zContent-Dispositionr   zcontent-dispositionzContent-Type_download.NAPKIN_DOWNLOAD_OVERWRITEF)defaultNAPKIN_DOWNLOAD_CHUNK_SIZEi   wb)
chunk_sizeSaved file to %s)r   mkdirrO   streamrE   r   areadrK   Responser   r   rh   (_infer_filename_from_content_disposition"_infer_extension_from_content_typeURLpathnamerp   r   _get_bool_envexists_dedupe_path_get_int_envopenaiter_bytesrB   rq   r   )r!   r   r   r   r   r   rbodyrespcdfilenameextbasedest	overwriter   fchunks                     r#   save_file_by_urlz NapkinAPIClient.save_file_by_urlu  sh     *%
5 ;;%%eS$,,%GG1}}#WWY~~ !II II	 ++D1 4b9 QYY]]%r>B DDRHH(==IIMM.1 '(\7)4D"D*#EIIcN$7$78==K
 4==1SE#; fAcUO"&$J  (D**+FPU*VI{{}Y((. **+GQV*WJ4A#$==J=#G #%GGENi H&> % *)*$##G !e HGGGGl 	&-s   AJ&H7J&#I94H:5CI96H=BI9	II I
!I$I7J&:I9=II9
II9II	I9I$	 I9'J&2I53J&9J?J JJ&c                   K   |rt        |      }|j                  j                  dd       | j                  j	                  d|| j
                        4 d{   }|j                  dk\  r`|j                          d{   }t        j                  |j                  |j
                  ||j                        }| j                  |       |j                  d      5 }|j                         2 3 d{   }|j                  |       | j                  j#                  || j
                         d{   }|j                  dk\  r| j                  |       |j$                  S 7 7 7 v6 	 ddd       n# 1 sw Y   nxY wddd      d{  7   n# 1 d{  7  sw Y   nxY wt        j!                  d	|       |S 7 w)
z\
        Helper to stream to disk if save_path is provided, otherwise return bytes.
        Tr   r   r   Nr   r   r   r   )r   parentr   rO   r   rE   r   r   rK   r   r   r   r   r   rB   rq   r   rh   r   )r!   r   r   r   r   r   r   r   s           r#   r   z NapkinAPIClient._stream_or_bytes  sX     YI""4$"?{{))%dll)KKq==C'!"?D >>$%MM !		 $ !			D //5^^D)Q'(}} 'e [[__S$,,_??s"''-||' L*' *)) LKKKK KK*I6?s   AGE&G#FE)AFE80E-4E+
5E-8E8*G5G61G)F+E--E8/	F8F	=FGFGF(FF($ Gcontent_typec                     |sy|j                  d      d   j                         j                         }|dk(  ry|dk(  ryy)zK
        Map MIME type to file extension; default to svg/png only.
        N;r   zimage/svg+xmlsvgz	image/pngpng)splitstriplower)r!   r   cts      r#   r   z2NapkinAPIClient._infer_extension_from_content_type  sJ     $Q'--/557 r$   r   c                     |j                         s|S |j                  }|j                  }|j                  }d}	 || d| | z  }|j                         s|S |dz  }$)zQ
        Generate a unique path by adding numeric suffix if file exists.
        r   r   )r   stemsuffixr   )r!   r   r  r  r   counternew_paths          r#   r   zNapkinAPIClient._dedupe_path  sk     {{}Kyy4&'6( ;;H??$qLG	 r$   r   c                    |sy	 |j                  d      D cg c]  }|j                          }}t        d |D        d      }|rq|j                         }|j                         j	                  d      r"ddl}|j                  j                  |dd       S |j                  d      j                  d      S t        d	 |D        d      }|r |j                  d      j                  d      S 	 yc c}w # t        $ r Y yw xY w)
zQ
        Parse Content-Disposition for filename* (RFC 5987) or filename.
        Nr   c              3      K   | ]8  }|j                         j                  d       r|j                  dd      d    : yw)z
filename*==r   Nr   
startswithr   .0ps     r#   	<genexpr>zKNapkinAPIClient._infer_filename_from_content_disposition.<locals>.<genexpr>  s;      "wwy++L9 GGCOA&"   >A zutf-8''r      "'c              3      K   | ]8  }|j                         j                  d       r|j                  dd      d    : yw)z	filename=r  r   Nr	  r  s     r#   r  zKNapkinAPIClient._infer_filename_from_content_disposition.<locals>.<genexpr>  s;      "wwy++K8 GGCOA&"r  )	r   r   nextr   r
  urllib.parseparseunquoterp   )r!   r   r  partsfilename_starvalurllibr   s           r#   r   z8NapkinAPIClient._infer_filename_from_content_disposition  s	    	(*61QWWYE6 "
 M #))+99;)))4'!<<//AB88yy~++C00"
 H ~~c*0055  = 78  		s.   C3 C.A'C3 C3 74C3 .C3 3	C?>C?keyr   c                 n    dd l }	 |j                  |      }|t        |      S |S # t        $ r |cY S w xY w)Nr   )osgetenvr6   rp   r!   r  r   r  r  s        r#   r   zNapkinAPIClient._get_int_env)  s>    	))C.C"3s8;G; 	N	s   & & 44c                 t    dd l }|j                  |      }||S |j                         j                         dv S )Nr   >   1onyestrue)r  r  r   r   r   s        r#   r   zNapkinAPIClient._get_bool_env2  s7    iin;Nyy{  "&@@@r$   c                    K   t         d   j                  ||      }| j                  d|       d{   }|j                  S 7 w)zt
        Download a generated file by its file ID using the API's file endpoint.
        Returns raw bytes.
        r   r   r   N)r   r   r   r   )r!   r   r   r   r]   s        r#   download_file_by_idz#NapkinAPIClient.download_file_by_id:  sP     
 !,33! 4 
 ++E8<< =s   0AAAc                    K   | j                   j                  || j                         d{   }|j                  dk\  r| j	                  |       |j
                  S 7 0w)z
        Download a generated file directly from a provided URL.
        Returns raw bytes. Uses binary-safe buffering.
        r   Nr   )rO   rh   rE   r   r   r   )r!   r   r   s      r#   download_file_by_urlz$NapkinAPIClient.download_file_by_urlF  sQ      [[__S$,,_??s"''-||	 @s   +A A1A poll_intervalmax_attemptsc                   K   |xs | j                   j                  }|xs | j                   j                  }d}|}||k  rn| j                  |       d{   }t        j                  d|dz    d|j                  j                   d|j                          |j                  t        j                  t        j                  t        j                  hv rc|j                  t        j                  k(  rt        d|j                  xs d       |j                  t        j                  k(  rt        d	      |S |j                  r4t        j!                  d
|j                  dd|j"                  xs d        t%        j&                  |       d{    t)        |dz  d      }|dz  }||k  rnt        d| d      7 i7 1w)a  
        Poll status until request completes.

        Args:
            request_id: The request ID to monitor.
            poll_interval: Seconds between polls (uses config default if None).
            max_attempts: Maximum polling attempts (uses config default if None).

        Returns:
            Final StatusResponse when complete.

        Raises:
            ProcessingError: If request fails or times out.
        r   NzPolling attempt r   z	: status=z, files_ready=zVisual generation failed: rz   zRequest expiredz
Progress: z.0fz% - r   g333333?   zTimeout after z	 attempts)r=   poll_interval_secondsmax_poll_attemptsr   rq   r   r   valuer   r   	COMPLETEDFAILEDEXPIREDr:   ry   r   r   r   asynciosleepr   )r!   r   r*  r+  attemptsdelayr   s          r#   wait_for_completionz#NapkinAPIClient.wait_for_completionR  s    ( &L)L)L#Ft}}'F'F%??:66F LL"8a<.	&--:M:M9Nn]c]o]o\pq
 }}''$$%%! 
 ==M$8$88)4V\\5T_4UV  ]]m&;&;;)*;<<   5T&..:NB9OP
 --&&&R(EMHA %D |nIFGGA 78 's+   AGF>D9GGG/GGc                     | j                   S )zz
        Get current rate limit status.

        Returns:
            RateLimitInfo if available, None otherwise.
        )rP   rR   s    r#   get_rate_limit_statusz%NapkinAPIClient.get_rate_limit_status  s     ###r$   r   )NNNr%   )3r&   r'   r(   r)   r   r   r    rS   rY   rU   rK   r   r   rw   r   r   r	   	HTTPErrorr
   r   r   rq   loggingWARNINGr*   r   r   r   r   r   r   r   r   r   r   bytesr   r   r   r   r   r   r6   r   boolr   r'  r)  floatr8  r:  r1   r$   r#   r<   r<   J   s   )=(!3 =6';;	-	 ;zAu~~ A. %eoo6"r:%fgoo>	 
 
,/
/
 
/
b	. 	. 	.@@ 
@L %)	;; ; D>	;
 
ud{	;* (,$(!%II #t)$I  }	I
 SMI #I 
IV#+D>	ud{	<$SM	# $ $(3-(	#(T c c A At A A
 C 
 # 
 % 
 
c 
e 
 *.&*	<H<H  <H sm	<H
 
<H|$x'> $r$   r<   )'r)   r4  r<  r   r   pathlibr   typingr   r   rK   tenacityr   r	   r
   r   r   utils.configr   r   utils.constantsr   r   modelsr   r   r   r   r   	getLoggerr&   rq   rp   r   r/   r3   r8   r:   r<   r1   r$   r#   <module>rH     s      '  "   2 8  
		8	$Y 	. 	'^ '	> 		n 	M	$ M	$r$   