
    (<i5c                       U d Z ddlmZ ddlZddlZddlZddlZddlZddlZddl	Z	ddl
Z
ddlZddl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mZ ddlmZmZ ddlmZ  ej>                  ej@                  d	
        ejB                  d      Z" ejF                         Z$ejJ                  jM                  d      xs dZ'de(d<   da)de(d<   da*de(d<   da+de(d<   dZ,dAdZ-dBdZ.ej^                  dCd       Z0 eddde0      Z1dhZ2h dZ3e1ji                  d      dDd       Z5dEd Z6dFd!Z7dGd"Z8dGd#Z9dHd$Z:dId%Z;dJd&Z<	 	 	 	 	 	 	 	 dKd'Z=dLd(Z>dMd)Z?dMd*Z@dNd+ZAdNd,ZB G d- d.e      ZC G d/ d0e      ZDe1jM                  d      dOd1       ZEe1j                  d2      dPd3       ZGe1j                  d4      dQd5       ZHe1j                  d6      dRd7       ZI	 	 	 	 	 	 	 	 	 	 dSd8ZJdTd9ZKeLd:k(  rddlMZM eMj                  d;d<d=d>d?@       yy)Uu   
server.py - Whisper GPU HTTP 서비스 (FastAPI + faster-whisper)

포트: 8200
모델: medium (기본, int8, cuda) + small (lazy 로딩)
    )annotationsN)AsyncGenerator)AnyOptional)FastAPIFileFormHTTPExceptionRequest
UploadFile)JSONResponsePlainTextResponse)	BaseModelz1%(asctime)s [%(levelname)s] %(name)s: %(message)s)levelformatzwhisper-gpuWHISPER_API_KEYOptional[str]r   medium_modelsmall_model        float
_last_usediX  c                    ddl m} t        j                  d|         || dd      }t        j                  d|        |S )u9   WhisperModel을 CUDA + int8 양자화로 로드합니다.r   )WhisperModelu:   WhisperModel 로딩: size=%s device=cuda compute_type=int8cudaint8)devicecompute_typeu#   WhisperModel 로딩 완료: size=%s)faster_whisperr   loggerinfo)
model_sizer   models      L/home/jay/workspace/.worktrees/task-2057-dev2/services/whisper-gpu/server.py_load_whisper_modelr%   ;   s6    +
KKLjYFHE
KK5zBL    c                 X    t         t        d      a t        j                         at         S )u2   small 모델을 lazy 로딩으로 반환합니다.small)r   r%   timer    r&   r$   get_small_modelr+   E   s%     )'2Jr&   c               V  K   t         j                  dt               t        j                  t                     }d |j                          t        j                  t        j                        5  | d{    ddd       t         j                  d       y7 "# 1 sw Y   !xY ww)u@   FastAPI lifespan: lazy-load 모드 + 자동 언로드 태스크.uX   서버 시작: lazy-load 모드 (모델 미로딩, %d초 미사용 시 자동 언로드)Nu   서버 종료)
r    r!   _UNLOAD_TIMEOUTasynciocreate_task_unload_checkercancel
contextlibsuppressCancelledError)applicationunload_tasks     r$   lifespanr7   S   s}      KKjl{|%%o&78K				W33	4 
KK  	 s0   A0B)2B8B9B=B)BB&"B)zWhisper GPU HTTP Serviceu0   faster-whisper 기반 오디오 전사 서비스z1.0.0)titledescriptionversionr7   z
/v1/health>   ::1	127.0.0.1	localhosthttpc                  K   t          ||        d{   S | j                  j                  t        v r ||        d{   S | j                  r| j                  j
                  nd}|t        v r ||        d{   S | j                  j                  dd      }t        |      xr6 t        j                  |j                         t         j                               }|st        dddi      S  ||        d{   S 7 7 7 7 
w)uc  X-API-Key 헤더를 검증하는 인증 미들웨어.

    다음 경우에는 인증을 건너뜁니다:
    - WHISPER_API_KEY 환경변수가 설정되지 않은 경우 (인증 비활성화)
    - 요청 경로가 _AUTH_EXEMPT_PATHS에 포함된 경우 (/v1/health 등)
    - 요청 출처가 localhost/127.0.0.1인 경우 (로컬 테스트 호환)
    N z	X-API-Keyi  detailzInvalid or missing API key)status_codecontent)r   urlpath_AUTH_EXEMPT_PATHSclienthost_LOCALHOST_HOSTSheadersgetboolhmaccompare_digestencoder   )request	call_nextclient_hostprovided_key	key_valids        r$   api_key_auth_middlewarerU   o   s      w''' {{--w''' *1'..%%RK&&w''' ??&&{B7L\"kt':':<;N;N;PRaRhRhRj'kI;<
 	

 7###) ( (
 ( $sF   D	D+D	 D;D	<D=A?D	<D=D	D	D	D	c                    d}| |k  ryy)u   오디오 길이에 따라 적절한 모델 크기를 반환합니다.

    Args:
        duration_seconds: 오디오 길이 (초)

    Returns:
        "small" (30분 미만) 또는 "medium" (30분 이상)
    g      @r(   mediumr*   )duration_secondsthreshold_30mins     r$   select_model_for_durationrZ      s      O/)r&   c                v    t        j                          a| dk(  r
t               S t        t	        d      at        S )uP   모델 이름에 해당하는 인스턴스를 반환합니다. lazy-load 적용.r(   rW   )r)   r   r+   r   r%   )
model_names    r$   get_model_instancer]      s6     JW  *84r&   c                 N   	 ddl } g }t        bda|j                  d       t
        bda|j                  d       |r@| j                  j                          t        j                  ddj                  |             yy# t        $ r t        j                  d       dadaY yw xY w)u6   모든 모델을 CUDA 메모리에서 해제합니다.r   Nu0   torch 미설치 — CUDA 캐시 정리 건너뜀rW   r(   u/   모델 언로드 완료: %s, CUDA 캐시 정리z, )torchImportErrorr    warningr   r   appendr   empty_cacher!   join)r_   unloadeds     r$   _unload_modelsrf      s     H! 

 EtyyQYGZ[   IJ	s   A? ?"B$#B$c                   K   	 t        j                  d       d{    t        dkD  rt        j                         t        z
  t        kD  rt
        t        t        j                  dt        j                         t        z
         t        4 d{    t        j                         } | j                  dt               d{    ddd      d{    7 7 I7 7 # 1 d{  7  sw Y   xY ww)u=   백그라운드 태스크: 10분 미사용 시 모델 해제.<   Nr   u*   자동 언로드 시작: %.0f초 미사용)r.   sleepr   r)   r-   r   r   r    r!   	_gpu_lockget_event_looprun_in_executorrf   )loops    r$   r0   r0      s     
mmB>diikJ6H';+B@IIK*, % E E"113D..t^DDDE E EDE E E Esj   C6CA4C6CC6.C!CC!C6CC6C6C!C6!C3'C*(C3/C6c           
     "   	 t        j                  ddddddd| gddd	
      }|j                  j                         }|r|dk7  rt	        |      S y# t         j
                  t        t        f$ r }t        j                  d|       Y d}~yd}~ww xY w)u>   ffprobe로 오디오 파일의 길이(초)를 반환합니다.ffprobez-verrorz-show_entrieszformat=durationz-ofz"default=noprint_wrappers=1:nokey=1T   capture_outputtexttimeoutzN/Au"   ffprobe duration 조회 실패: %sNr   )

subprocessrunstdoutstripr   TimeoutExpired
ValueErrorFileNotFoundErrorr    ra   )	file_pathresultduration_stres       r$   get_audio_durationr      s    @!4	  
 }}**,LE1&&  %%z3DE @;Q??@s   AA B.B		Bc                  K   t         j                  d|        t        j                  d      4 d{   }|j	                  |        d{   }|j                          ddd      d{    t        |       }t        j                  |dd      5 }|j                  j                         |j                  cddd       S 7 7 z7 \# 1 d{  7  sw Y   lxY w# 1 sw Y   yxY ww)uR   URL에서 오디오를 다운로드하여 임시 파일 경로를 반환합니다.u$   URL에서 오디오 다운로드: %sg      ^@ru   NFwhisper_url_suffixdeleteprefix)r    r!   httpxAsyncClientrK   raise_for_status_get_suffix_from_urltempfileNamedTemporaryFilewriterC   name)	audio_urlhttp_clientresponser   tmps        r$   download_audio_from_urlr      s     
KK6	B  / $ $;$33!!#$ $ "),F		$	$F5	X \_		(""#xx $3$ $ $ $
 sn   1C,CC,CCC!C,,C	-'C,'C ;C,C	C,CCCC, C)%C,c                    | j                  d      d   j                  d      d   }d|v rd|j                  dd      d   z   S y)u.   URL에서 파일 확장자를 추출합니다.?r   /.   .audio)splitrsplit)rD   rE   s     r$   r   r      sI    99S>!""3'+D
d{T[[a(,,,r&   c           	        K   t        j                         }t        4 d{    t        j	                  d|        t        |      |j                  d fd       d{   \  }}|D cg c]H  }t        |j                  d      t        |j                  d      |j                  j                         dJ }}dj                  d |D              }t        |d      xs }	t        t        |d	d
            }
ddd      d{    t        j	                  d	
t                     ||	|
dS 7 7 c c}w 7 6# 1 d{  7  sw Y   FxY ww)u4   GPU Lock을 획득한 후 전사를 실행합니다.Nu+   전사 시작: model=%s language=%s file=%sc                 @    j                   rd      S d d      S )N   )language	beam_size)
transcribe)r}   r   r#   s   r$   <lambda>z#run_transcription.<locals>.<lambda>  s4    E$$%- %  37 %  r&      )startendrt    c              3  &   K   | ]	  }|d      yw)rt   Nr*   ).0segs     r$   	<genexpr>z$run_transcription.<locals>.<genexpr>!  s     =SS[=s   r   durationr   u5   전사 완료: language=%s duration=%.1fs segments=%d)rt   segmentsr   r   )r.   rk   rj   r    r!   r]   rl   roundr   r   rt   ry   rd   getattrr   len)r}   r   r\   rm   segments_rawr!   r   r   	full_textdetected_languager   r#   s   ``         @r$   run_transcriptionr     sS     !!#D 9 9A:xYbc":.#'#7#7$
 
d $
 	 syy!,SWWa((
 
 HH=H==	#D*h?K8z378+9 9. KK?H	 %	 ;9

9 9 9 9sj   ED8E>E&D;'E2AD=?AE EE-E;E=EEE
EEEc                 N   	 t        j                  g dddd      } | j                  j                         j	                  d      d   j                         }|rt        |      S 	 y# t         j                  t        t        f$ r }t        j                  d|       Y d}~yd}~ww xY w)	uD   nvidia-smi로 현재 GPU 메모리 사용량(MB)을 반환합니다.)z
nvidia-smiz--query-gpu=memory.usedz--format=csv,noheader,nounitsT
   rr   
r   u   GPU 메모리 조회 실패: %sN)rv   rw   rx   ry   r   intrz   r{   r|   r    ra   )r~   	value_strr   s      r$   get_gpu_memory_used_mbr   3  s    =
  	
 MM'')//5a8>>@	y>!   %%z3DE =8!<<=s   A!A& &B$BB$c                |    t        | dz        }|dz  }|dz  }|dz  }|dz  }|dz  }|dz  }|dd|dd|dd|dS )uE   초를 SRT 타임스탬프 형식 HH:MM:SS,mmm으로 변환합니다.  rh   02d:,03dr   secondstotal_msmstotal_sstotal_mmhs           r$   seconds_to_srt_timestampr   M  l    Wt^$H	DB$G"AmG"A2AWAaWAaWAbX..r&   c                |    t        | dz        }|dz  }|dz  }|dz  }|dz  }|dz  }|dz  }|dd|dd|dd|dS )uE   초를 VTT 타임스탬프 형식 HH:MM:SS.mmm으로 변환합니다.r   rh   r   r   r   r   r   r   s           r$   seconds_to_vtt_timestampr   Y  r   r&   c                    g }t        | d      D ]A  \  }}t        |d         }t        |d         }|j                  | d| d| d|d    d       C dj                  |      S )uF   세그먼트 목록을 SRT 자막 형식 문자열로 변환합니다.r   )r   r   r   r    --> rt   )	enumerater   rb   rd   )r   linesir   start_tsend_tss         r$   segments_to_srtr   e  sy    EHA. G3+CL9)#e*5s"XJeF82c&k]"EFG 99Ur&   c           	         dg}| D ];  }t        |d         }t        |d         }|j                  | d| d|d    d       = dj                  |      S )uI   세그먼트 목록을 WebVTT 자막 형식 문자열로 변환합니다.zWEBVTT
r   r   r   r   rt   )r   rb   rd   )r   r   r   r   r   s        r$   segments_to_vttr   o  sj    "|E B+CL9)#e*5zvhbVR@AB 99Ur&   c                  B    e Zd ZU ded<   dZded<   dZded<   dZded<   y	)
TranscribeURLRequeststrr   kor   rW   r#   jsonr   N)__name__
__module____qualname____annotations__r   r#   r   r*   r&   r$   r   r   ~  s%    NHcE3FCr&   r   c                  &    e Zd ZU ded<   dZded<   y)YouTubeTranscribeRequestr   video_idr   r   N)r   r   r   r   r   r*   r&   r$   r   r     s    MHcr&   r   c            	        K   t               } t        dd| t        dndt        dnddt        dkD  rt        ndt
        d	      S w)
uP   서비스 상태, 모델 로딩 상태, GPU 메모리 정보를 반환합니다.okr   Nloadedre   )rW   r(   r   )statusr   gpu_memory_used_mbmodels	last_usedunload_timeout_secrC   )r   r   r   r   r   r-   )
gpu_mem_mbs    r$   healthr     sT      ()J",&2&>(J%0%<* (2A~4"1

 s   AAz/v1/transcribec                z	  K   | j                   j                  dd      }d|v r	 | j                          d{   }|j                  d      }|st	        dd	      |j                  d
d      }|j                  dd      }|j                  dd      }d}	 t        |       d{   }t        ||||       d{   |r7t        j                  j                  |      r	 t        j                  |       S S S 	 | j'                          d{   }
|
j                  d      }|t	        dd      |}t        |
j                  d
d            }t        |
j                  dd            }t        |
j                  dd            }d}	 t)        |j*                        }t-        j.                  |dd      5 }|j1                          d{   }|j3                  |       |j4                  }ddd       t        ||||       d{   |r7t        j                  j                  |      r	 t        j                  |       S S S 7 	# t        $ r}t	        ddt        |             |d}~ww xY w7 7 # t        $ r }	t        j                  d|	       Y d}	~	S d}	~	ww xY w# t        $ r  t        j                   $ r5}t        j#                  d|       t	        ddt        |             |d}~wt        $ r5}t        j%                  d|       t	        ddt        |             |d}~ww xY w# |rct        j                  j                  |      rC	 t        j                  |       w # t        $ r }	t        j                  d|	       Y d}	~	w d}	~	ww xY ww w xY w7 # t        $ r}t	        ddt        |             |d}~ww xY w7 # 1 sw Y   xY w7 # t        $ r }	t        j                  d|	       Y d}	~	S d}	~	ww xY w# t        $ r  t        $ r5}t        j%                  d|       t	        ddt        |             |d}~ww xY w# |rct        j                  j                  |      rC	 t        j                  |       w # t        $ r }	t        j                  d|	       Y d}	~	w d}	~	ww xY ww w xY ww)ut  오디오 파일을 전사하여 결과를 반환합니다.

    multipart/form-data로 파일을 업로드하거나
    JSON body로 audio_url을 전달할 수 있습니다.

    form-data:
        - file: 오디오 파일 (필수, audio_url 없을 때)
        - language: 언어 코드 (기본: ko)
        - model: 모델 크기 (기본: medium)
        - format: 출력 형식 text|json|srt|vtt (기본: json)

    JSON body:
        - audio_url: 오디오 파일 URL (필수)
        - language: 언어 코드 (기본: ko)
        - model: 모델 크기 (기본: medium)
        - format: 출력 형식 (기본: json)
    zcontent-typer@   zapplication/jsonN  u   JSON 파싱 실패: rB   rA   r   u1   JSON body에 audio_url 필드가 필요합니다.r   r   r#   rW   r   r      임시 파일 삭제 실패: %s%   오디오 URL 다운로드 실패: %s   오디오 URL 접근 실패: u    URL 전사 처리 중 오류: %s     전사 처리 실패: u   폼 데이터 파싱 실패: fileu8   파일(file) 또는 audio_url을 제공해야 합니다.Fwhisper_upload_r   u#   전사 처리 중 오류 발생: %s)rJ   rK   r   	Exceptionr
   r   r   _process_transcriptionosrE   existsunlinkOSErrorr    ra   r   HTTPStatusErrorrp   	exceptionform_get_suffix_from_uploadfilenamer   r   readr   r   )rP   content_typebodyr   r   r   r#   output_formattmp_path
unlink_errr  
file_fieldupload_filefile_tmp_pathr   r   rC   s                    r$   r   r     sd    & ??&&~r:L \)	` 'D $(88K#8	J  88J-(+62"&	R4Y??H/(E=YY BGGNN84RIIh' 5xe\\^# &!JM
 	

 )K488J-.H(+,E623M#'MN()=)=>((uM^_ 	%cf',,..GIIgHHM	%
 ,M8UMZZ RWW^^M:N		-( ;=G ( 	`C:NsSTvh8WX^__	` @Y  RNN#DjQQR  	$$ 	iLL@!DC:WX[\]X^W_8`aghh 	b?CC:PQTUVQWPX8YZ`aa	b BGGNN84RIIh' RNN#DjQQR 5x $ e6STWXYTZS[4\]cdde& /	% 	%
 [  N@*MMN   ^>B6LSQRVH4UV\]]^ RWW^^M:N		-( N@*MMN ;=s  !R;I I I AR;J  #I.$J  9I1:J  =!R;I44R;8N NN A5R;-P 3OO!O)P OP !R;(O=R; I 	I+I&&I++R;.J  1J  4	J=JR;JR; L-<0K,,L-80L((L--L0 0"NM)(N)	N2NNNNR;N 	O&O  OR;OO	P 	P$O?:R;?PR;Q0Q		QQ "R84R
	R8
	R3R.)R8.R33R88R;z/v1/transcribe/urlc                x  K   d}	 t        | j                         d{   }t        || j                  | j                  | j
                         d{   |r7t        j                  j                  |      r	 t        j                  |       S S S 7 q7 ?# t        $ r }t        j                  d|       Y d}~S d}~ww xY w# t        $ r  t        j                  $ r5}t        j!                  d|       t        ddt#        |             |d}~wt$        $ r5}t        j'                  d|       t        dd	t#        |             |d}~ww xY w# |rct        j                  j                  |      rC	 t        j                  |       w # t        $ r }t        j                  d|       Y d}~w d}~ww xY ww w xY ww)
uJ   JSON body의 audio_url로 오디오를 다운로드하여 전사합니다.Nr   r   r   r   r   u'   URL 전사 처리 중 오류 발생: %sr   r   )r   r   r   r   r#   r   r   rE   r   r   r  r    ra   r
   r   r  rp   r   r   r  )rP   r  r   s      r$   transcribe_urlr    s     #HE01B1BCC+Hg6F6FW^WeWeff x0E		(# 18 Df  E@!DDE     e<a@6STWXYTZS[4\]cdd ^BAF6LSQRVH4UV\]]^ x0E		(# E@!DDE 18s   F:C  B3C  BC  !F:8BF:C  C  	B=B83F:8B==F: E0DE0EEE "F73F	F7		F2F-(F7-F22F77F:z/v1/youtube-transcribec                Z  K   t        j                  d      }|j                  | j                        st	        dd      | j                  }d| d}d| }d}	 t
        j                  d	|       	 t        j                  d
dddd||t        j                  j                  t        j                  j                  	       d{   }t        j                  |j                         d       d{   \  }}|j                  dk7  r^|j!                  d      j#                         }
t
        j                  d|j                  |
       t	        dd|j                   d|
       t
        j                  d|       t%        j$                  d| d      }|st	        dd|       |d   }t
        j                  d|       t'        |      }t)        |      }t
        j                  d|||       	 t+        || j,                  |       d{   }t5        |d"   d#|d$   d%&      |rMt6        j8                  j;                  |      r-	 t7        j<                  |       t
        j                  d'|       S S S 7 7 # t        j                  $ r,}	t
        j                  d|       t	        dd|       |	d}	~	ww xY w7 # t.        $ r6}	t
        j1                  d||	       t	        d d!t3        |	             |	d}	~	ww xY w# t>        $ r }t
        jA                  d(|       Y d}~S d}~ww xY w# |ryt6        j8                  j;                  |      rY	 t7        j<                  |       t
        j                  d'|       w # t>        $ r }t
        jA                  d(|       Y d}~w d}~ww xY ww w xY ww))u  YouTube video_id로 오디오를 다운로드하여 전사합니다.

    yt-dlp로 베스트 오디오를 /tmp에 다운로드한 뒤 Whisper로 전사하고
    임시 파일을 삭제합니다.

    Request body:
        - video_id: YouTube 동영상 ID (영숫자 + 하이픈/언더스코어, 11자)
        - language: 언어 코드 (기본: ko)

    Returns:
        { "text": "...", "source": "whisper_local_ytdlp", "duration": 123.4 }
    z^[a-zA-Z0-9_-]{11}$r   uU   video_id는 영숫자·하이픈·언더스코어로 구성된 11자여야 합니다.r   z/tmp/whisper_yt_z.%(ext)szhttps://youtube.com/watch?v=Nu'   yt-dlp 다운로드 시작: video_id=%szyt-dlpz-fbaz--no-playlistz-o)rx   stderri,  r   u-   yt-dlp 다운로드 타임아웃: video_id=%su4   yt-dlp 다운로드 타임아웃 (300초): video_id=r   replace)errorsu&   yt-dlp 실패: returncode=%d stderr=%su'   yt-dlp 다운로드 실패 (returncode=z): u'   yt-dlp 다운로드 완료: video_id=%sz.*uD   yt-dlp 다운로드 후 파일을 찾을 수 없습니다: video_id=u$   다운로드된 오디오 파일: %su9   전사 모델 선택: model=%s duration=%.1fs video_id=%su#   전사 실패: video_id=%s error=%sr   r   rt   whisper_local_ytdlpr   )rt   sourcer   r   u   임시 파일 삭제 완료: %sr   )!recompilematchr   r
   r    r!   r.   create_subprocess_execrv   PIPEwait_forcommunicateTimeoutErrorrp   
returncodedecodery   globr   rZ   r   r   r   r  r   r   r   rE   r   r   r  ra   )rP   _VIDEO_ID_REr   output_templateyoutube_url
audio_pathprocrx   r  r   stderr_textmatchedduration_secr\   r~   r  s                   r$   youtube_transcriber,    s     ::45Lg../j
 	

 H(
(;O0
;K $JNN=xH	 77))..))..
 
D $+#3#3D4D4D4FPS#TTNFF ??a --y-9??AKLLA4??T_`@@QQTU`Tab 
 	=xH )).xj;<]^f]gh  QZ
:JG **5.|<
G		
	,Z9I9I:VVF v/":.
 "''..4N		*%=zJ 5:K
 U## 	LLH(SMhZX 	L W 	BHaP/Ax8 	,  N@*MMN	 "''..4N		*%=zJ N@*MMN	 5:s   AN+L+ 1AI9 I3,I9 /I60I9 7C7L+ /J= 	J;
J= L+ #!N++K?0N+3I9 6I9 9J8'J33J88L+ ;J= =	K<1K77K<<L+ ?	L(L#N+#L((N++"N(+M:9N(:	N#NN(N##N((N+c                ~  K   t        |       }t        |      }|dv r|n|}|dk(  r|dkD  r|}t        j                  d||||       t	        | ||       d{   }|dk(  rt        |d         S |dk(  rt        |d	         }t        |d
      S |dk(  rt        |d	         }	t        |	d
      S t        |      S 7 dw)u:   실제 전사 처리 및 포맷 변환을 수행합니다.)r(   rW   largerW   r   u7   모델 선택: hint=%s auto=%s chosen=%s duration=%.1fsNrt   r   srtr   z
text/plain)rC   
media_typevtt)	r   rZ   r    r!   r   r   r   r   r   )
r}   r   
model_hintr
  r   
auto_modelchosen_modelr~   srt_contentvtt_contents
             r$   r   r     s      "),H*84J ",/K!K:Q[LX(Q,!
KKA %Y,GGF  88	%	%fZ&89 NN	%	%fZ&89 NN F++ Hs   AB=B;A%B=c                @    | rd| v rd| j                  dd      d   z   S y)u7   업로드 파일명에서 확장자를 추출합니다.r   r   r   r   )r   )r  s    r$   r  r    s)    C8OX__S!,R000r&   __main__z
server:appz0.0.0.0i   r!   r   )rH   port	log_levelworkers)r"   r   returnr   )r<  r   )r5   r   r<  zAsyncGenerator[None, None])rP   r   rQ   r   r<  r   )rX   r   r<  r   )r\   r   r<  r   )r<  None)r}   r   r<  r   )r   r   r<  r   )rD   r   r<  r   )r}   r   r   r   r\   r   r<  zdict[str, Any])r<  r   )r   r   r<  r   )r   zlist[dict[str, Any]]r<  r   )r<  r   )rP   r   r<  r   )rP   r   r<  r   )rP   r   r<  r   )
r}   r   r   r   r2  r   r
  r   r<  r   )r  r   r<  r   )O__doc__
__future__r   r.   r2   r#  rM   loggingr   r  rv   r   r)   collections.abcr   typingr   r   r   fastapir   r   r	   r
   r   r   fastapi.responsesr   r   pydanticr   basicConfigINFO	getLoggerr    Lockrj   environrK   r   r   r   r   r   r-   r%   r+   asynccontextmanagerr7   apprF   rI   
middlewarerU   rZ   r]   rf   r0   r   r   r   r   r   r   r   r   r   r   r   r   postr   r  r,  r   r  r   uvicornrw   r*   r&   r$   <module>rP     s   #      	 	    *    K K =    
,,> 
		=	) GLLN	 "$0A!B!Jd J c S 
E  !  ! 
$B	 #^ 4  $ $L\0E4
*** * 	*Z4	/	/9 y   $ 
^N ^NB 
E  E, 
"#hN $hN`(,(,(, (, 	(,
 	(,V zGKK r&   