+
    4j:{                       R t ^ RIHt ^ RIt^ RIt^ RIt^ RIt^ RIt^ RIH	t	H
t
 ^ RIHt ^ RIHt ^ RIHt ^RIHtHtHt ^RIHtHtHtHtHtHtHtHtHtHtH t H!t!H"t" ^R	I#H$t$H%t%H&t&H't' ^R
I(H)t* ^RI(H+t, ^RI(H-t. ^RI/H0t0H1t1 ^RI2H3t3H4t4 ]'       d   ^ RI5H6t6 ^ RI7H8t8 ^RI2H9t9H:t: . R0Ot;]Px                  ! ]=4      t>]	! RR7       ! R R4      4       t?Rt@RtA]P                  ! R]P                  4      tD]P                  ! R4      tER1tF]G! ]H! ]F4      4      tIR R ltJR R ltKR R  ltLR! R" ltMR# R$ ltNR% R& ltOR' R( ltPR) R* ltQR+ R, ltRR- R. ltS ! R/ R4      tTR# )2a  Disk-cached PyPI Simple API client.

Drives an :class:`~nab_index.transport.AsyncHttpTransport` and consults a
:class:`~nab_index.cache.CacheBackend` before any HTTP call. Honors a small
subset of RFC 9111: fresh entries are served directly, stale entries are
revalidated with ``If-None-Match``, and PEP 658 metadata + sdist PKG-INFO are
treated as immutable (cached forever; never revalidated).
)annotationsN)	dataclassreplace)timezone)parsedate_to_datetime)TYPE_CHECKING)CacheBackendCachePolicyOfflineError)_HTTP_NOT_FOUNDDEFAULT_INDEXMalformedSimpleResponseError	SdistFile	WheelFile_extract_sdist_files_header_listing_body_parse_files_select_artifact_hash_verify_metadata_hashholds_unreadable_formatverify_sdist_hash)RangeCapabilityMemoRangeMetadataResultRangeOutcomeread_wheel_metadata_over_range)corruption_reason)decode)encode)SimpleSerializationsimple_accept_header)IDENTITY_HEADERSraise_unless_ok)NormalizedName)Self)AsyncHttpTransportHttpResponseCachedAsyncSimpleClientParsedCacheStatsT)slotsc                  F    ] tR t^Gt$ Rt^ tR]R&   ^ tR]R&   ^ tR]R&   Rt	R# )r(   ac  Mutable counters for parsed-listing cache outcomes on the fetcher thread.

One instance is shared across every per-index client in a run and read once
after it, so a benchmark can confirm a warm resolve serves parsed blobs
rather than reparsing.

Each fresh-or-offline consult of ``get_files`` bumps exactly one counter:
``hit`` when a present blob binds the policy's body and is served without
reading the raw body; ``miss`` when no blob was present; ``rebuild`` when a
blob was present but was not served (a stale digest, a different build,
corruption, or no records in it) and was rebuilt from the raw body.
inthitmissrebuild N)
__name__
__module____qualname____firstlineno____doc__r,   __annotations__r-   r.   __static_attributes__r/       i/home/user/pdf2html-billing-ledger-validate/.venv/lib/python3.14/site-packages/nab_index/cached_client.pyr(   r(   G   s%     CLD#MGSr7   iX  i0  zmax-age\s*=\s*"?(\d+)z\A\s*(\d+)\s*\Zc                    V ^8  d   QhRRRR/# )   digitsstrreturnr+   r/   )formats   "r8   __annotate__r?   d   s     6 63 63 6r7   c                    V P                  R4      p\        V4      \        8  d   \        # \	        \        T;'       g    R4      \        4      # )a  Return a ``delta-seconds`` digit run as an int.

RFC 9110 5.6.7 allows leading zeros, sets no length limit on the run, and
caps a value too large to represent at 2**31. ``int`` refuses a string past
4300 digits, so the padding is stripped before the run is measured.
0)lstriplen_SECONDS_CEILING_DIGITS_SECONDS_CEILINGminr+   )r;   trimmeds   & r8   _parse_secondsrH   d   s>     mmC G
7|--s7>>c"$455r7   c                    V ^8  d   QhRRRR/# )r:   cache_control
str | Noner=   
int | Noner/   )r>   s   "r8   r?   r?   r   s     * *j *Z *r7   c                x    V f   R# \         P                  V 4      pVf   R# \        VP                  ^4      4      # )zBReturn the ``max-age`` a Cache-Control field carries, or ``None``.N)_MAX_AGE_REsearchrH   group)rJ   matchs   & r8   _max_age_directiverR   r   s6    }-E}%++a.))r7   c                    V ^8  d   QhRRRR/# )r:   agerK   r=   r+   r/   )r>   s   "r8   r?   r?   |   s     
* 
*J 
*3 
*r7   c                x    V f   ^ # \         P                  V 4      pVf   ^ # \        VP                  ^4      4      # )zReturn the seconds a response spent in caches before nab received it.

An absent header, or one that is not a bare run of digits, reads as 0.
)_AGE_RErQ   rH   rP   )rT   rQ   s   & r8   
_parse_agerW   |   s5    
 {MM#E}%++a.))r7   c                    V ^8  d   QhRRRR/# r:   responser&   r=   r+   r/   )r>   s   "r8   r?   r?      s     C C| C Cr7   c                r    \        \        P                  ! 4       4      \        \        V R4      4      ,
          # )a*  Return when ``response`` opened its freshness window.

A shared cache reports as Age how long ago the origin generated the
representation, so a relayed response arrives that far into its window.
Only the Age term of RFC 9111 4.2.3 is applied; the apparent age computed
from the Date header is not.
rT   )r+   timerW   r   rZ   s   &r8   _freshness_startr^      s&     tyy{j5)ABBBr7   c                    V ^8  d   QhRRRR/# )r:   valuerK   r=   rL   r/   )r>   s   "r8   r?   r?      s     # #j #Z #r7   c                    V f   R#  \        V 4      pTP                  f!   TP	                  \
        P                  R7      p\        TP                  4       4      #   \        \        3 d     R# i ; i)aM  Return an HTTP-date as Unix seconds, or ``None`` if it is not a date.

RFC 9110 5.6.7 writes every HTTP-date in GMT, so the zone-less obsolete
asctime form reads as GMT rather than as local time. A year, day, hour or
zone offset too large for a C int raises :class:`OverflowError` rather than
:class:`ValueError`; neither is a date.
N)tzinfo)	r   
ValueErrorOverflowErrorrb   r   r   utcr+   	timestamp)r`   parseds   & r8   _http_date_secondsrh      sh     }&u- }}x||4v!"" & s   A A/.A/c               $    V ^8  d   QhRRRRRR/# )r:   expiresr<   daterK   r=   r+   r/   )r>   s   "r8   r?   r?      s&     D Ds D* D Dr7   c                    \        V 4      pVf   ^ # \        V4      pVf   \        \        P                  ! 4       4      p\        \	        W#,
          ^ 4      \
        4      # )zReturn the freshness lifetime an Expires field grants.

RFC 9111 5.3: an Expires that is not an HTTP-date, ``0`` included, means
already expired. Date is the point the expiry is measured from; a response
that omits it is measured from its arrival.
)rh   r+   r\   rF   maxrE   )rj   rk   
expires_atgenerated_ats   &&  r8   _expires_lifetimerp      sO     $G,J%d+L499;'s:,a02BCCr7   c                    V ^8  d   QhRRRR/# )r:   rZ   r&   r=   boolr/   )r>   s   "r8   r?   r?      s        r7   c                J    \        V R4      RJ;'       g    \        V R4      RJ# )z:Whether ``response`` carries a freshness field of its own.cache-controlNrj   )r   r]   s   &r8   _states_freshnessru      s1     	/*$6 	4 	48Y't3r7   c                    V ^8  d   QhRRRR/# rY   r/   )r>   s   "r8   r?   r?      s     A A, A3 Ar7   c                    \        \        V R4      4      pVe   V# \        V R4      pVf   \        # \        V\        V R4      4      # )a  Return how long ``response`` may be served without revalidation.

RFC 9111 4.2.1 for a private cache: an explicit ``max-age``, else Expires
measured from Date, else the heuristic default. 4.2.2 reserves that default
for a response stating no expiry at all.
rt   rj   rk   )rR   r   _DEFAULT_MAX_AGErp   )rZ   max_agerj   s   &  r8   _freshness_lifetimerz      sK     !?!CDGh	*GWgh&?@@r7   c               $    V ^8  d   QhRRRRRR/# )r:   policyr	   page_urlr<   r=   rK   r/   )r>   s   "r8   r?   r?      s&     	G 	GK 	G3 	G: 	Gr7   c                >    WP                   8X  d   V P                  # R# )a  Return the body digest a 304 may carry forward, or ``None`` to retire it.

A 304 leaves the body alone, so the parsed blob bound to it still describes
that body, but only while the page it was parsed from is the same one: a
relative entry resolves against the page URL, so a move re-resolves every
file URL. Dropping the digest retires the blob, and the next read rebuilds
it against the new base.
N)r}   body_digest)r|   r}   s   &&r8   _carried_digestr      s     "*__!<6F$Fr7   c               (    V ^8  d   QhRRRRRRRR/# )	r:   cacher   packager<   offlinerr   r=   "list[WheelFile | SdistFile] | Noner/   )r>   s   "r8   r?   r?      s,     0 00"%0370'0r7   c                   V P                  V4      pVf   R# VP                  4       '       g   V'       g   R# V P                  V4      pVf   R# \        WC4      ;'       g    R# )aK  Read a fresh (or offline) parsed listing for ``package``, or ``None``.

The write-free subset of :meth:`CachedAsyncSimpleClient.get_files`' fresh-hit
branch: same policy, same ``is_fresh() or offline`` test, same blob, same
:func:`parsed_listing.decode`, so on a hit it returns the records
``get_files`` would. Every other case (no policy, stale-online, no blob, a
non-binding digest, a corrupt blob, a blob holding no records) declines to
``None`` and, unlike ``get_files``, never reads the raw body, revalidates,
rebuilds, writes, or logs. It never raises, so a caller's pending is never
stranded.

A blob that rehydrates to no records declines for the same reason
:meth:`CachedAsyncSimpleClient._parsed_hit` does: an empty listing also has
to say whether the page offered only formats nab does not read, and only the
raw body answers that.
N)get_simple_policyis_freshget_simple_parsed_decode_parsed)r   r   r   r|   blobs   &&$  r8   read_fresh_parsed_listingr      sY    & $$W-F~OO""7+D|$'//4/r7   c                  T   ] tR t^tRt]3RRRRR]P                  RRRR/R	 R
 llltR R lt	R R lt
R R ltR R ltR R ltR R ltR R ltR R ltR R ltR R ltR R  ltR!R/R" R# lltR$ R% ltR& R' ltR( R) ltR3R* R+ lltR4R, R- lltR4R. R/ lltR4R0 R1 lltR2tR# )5r'   zAsync PyPI Simple API client with on-disk caching.

Distinct from :class:`AsyncSimpleClient`: the interface mirrors what
:class:`FetchCoordinator` actually needs rather than the Simple API.
r   F
range_memoNserializationmin_fresh_secondsparsed_statsc               <    V ^8  d   QhRRRRRRRRR	R
RRRRRRRR/	# )r:   	transportr%   r   r   	index_urlr<   r   rr   r   zRangeCapabilityMemo | Noner   r   r   rL   r   zParsedCacheStats | Noner=   Noner/   )r>   s   "r8   r?   $CachedAsyncSimpleClient.__annotate__  sd     ,
 ,
%,
 ,
 	,
 ,
 /,
 +,
 &,
 .,
 
,
r7   c                   Wn         W n        VP                  R4      R,           V n        W@n        W`n        \        4       V n        Ve   TM	\        4       V n	        Wpn
        Ve
   VV n        R# \        4       V n        R# )a  Create a cached client wrapping ``transport``.

``range_memo`` is the per-run range-capability memo shared across the
indexes' clients; the coordinator owns the shared instance and injects
it. A fresh memo is built when none is passed, so a stand-alone client
still learns each host's range behaviour within its own lifetime.

``serialization`` pins which Simple-API serialization this index is
asked for and read as.

``min_fresh_seconds`` is a read-time freshness floor for this index's
Simple listing. A stale positive listing or negative sentinel within
the floor is served without revalidation; it only extends freshness and
rewrites nothing on disk.

``parsed_stats`` is the per-run parsed-listing cache sink, shared the
same way so hit/miss/rebuild counts total across every index client. A
private sink is created when none is passed, so a stand-alone client
still counts its own outcomes.
/N)
_transport_cacherstrip
_index_url_offline_serializationset_unreadable_onlyr   _range_memo_min_fresh_secondsr(   _parsed_stats)	selfr   r   r   r   r   r   r   r   s	   &&&&$$$$$r8   __init__ CachedAsyncSimpleClient.__init__  sw    @ $#**3/#5+*-%$0J6I6K 	 #4(4L 	:J:L 	r7   c                   V ^8  d   QhRR/# )r:   r=   r   r/   )r>   s   "r8   r?   r   3  s     ' 'd 'r7   c                V   "   V P                   P                  4       G Rj  xL
  R#  L5i)zClose the underlying transport.N)r   acloser   s   &r8   r   CachedAsyncSimpleClient.aclose3  s     oo$$&&&s   )')c                   V ^8  d   QhRR/# )r:   r=   r$   r/   )r>   s   "r8   r?   r   7  s      $ r7   c                   "   V # 5i)z Enter the async context manager.r/   r   s   &r8   
__aenter__"CachedAsyncSimpleClient.__aenter__7  s
     s   c                    V ^8  d   QhRRRR/# )r:   argsobjectr=   r   r/   )r>   s   "r8   r?   r   ;  s      V  r7   c                B   "   V P                  4       G Rj  xL
  R#  L5i)z7Exit the async context manager and close the transport.N)r   )r   r   s   &*r8   	__aexit__!CachedAsyncSimpleClient.__aexit__;  s     kkms   c                    V ^8  d   QhRRRR/# r:   r   r<   r=   list[WheelFile | SdistFile]r/   )r>   s   "r8   r?   r   ?  s     B1 B1s B1/J B1r7   c                r  "   V P                   P                  V4      pRpVe   VP                  4       ;'       g'    V P                  ;'       g    V P	                  W!R4      pV'       d   V P                  W4      pVe   V# V P                   P                  V4      pVeo   Vw  rrV P                  Wq4      pVeT   V P                  WVP                  R7      p	V'       d   V P                  WW)4       V	# V P                  WV4      G Rj  xL
 # RpV'       gb   V P                   P                  V4      p
V
eC   V
P                  4       '       g+   V P                  '       g   V P	                  WR4      '       d   . # V P                  '       d   RV R2p\        V4      hV P                  V4      G Rj  xL
 #  L L5i)	a2  Return parsed Simple API file list for ``package``.

Cache hit served from disk (fresh, offline, or kept fresh by the
read-time floor): the parsed-listing blob is rehydrated without reading
the large raw body; on a blob miss, build/digest mismatch, corruption,
or a blob holding no records the raw body is reparsed and the blob
rebuilt (a WARNING self-heal on genuine corruption).
Cache hit + stale + online: conditional revalidation; on 304 the body
(and its parsed blob) are reused, on 200 both are replaced.
Cache miss + offline: raises :class:`OfflineError`.
Cache miss + online: fetches, caches, returns.

The policy sidecar is read first: it carries the freshness window and
the ``body_digest`` that gates the parsed blob, without the raw body. An
absent policy is a full miss.

A positive entry beats the negative sentinel, so the sentinel is
consulted only on a positive miss. A fresh sentinel, or any sentinel
offline, answers an absent name empty with no transport call; a
stale sentinel online falls through to a fetch. A 404 records a
sentinel.

A cached body that will not decode as JSON is a corrupt positive:
re-fetched online, raising :class:`OfflineError` offline. It is reached
only after a parsed-blob miss reads the body, so the sentinel is not
consulted then and a corrupt body never answers the name absent.
FNlistingr}   Tzabsent-name sentinelzNo cached listing for  (offline mode))r   r   r   r   _floor_keeps_fresh_parsed_hit
get_simple_decode_cached_listing_parse_bodyr}   _rebuild_parsed_revalidate_simpleget_negativer
   _fetch_simple)r   r   r|   corrupt_positiveserve_cachedr,   cachedbodydatafilesnegativemsgs   &&          r8   	get_files!CachedAsyncSimpleClient.get_files?  s    8 ..w7 ! G G==G G**6IF 
 &&w7?J [[++G4F!%224A# ,,TV__,UE#,,WFJ$!%!8!8!OOO#' {{//8H#!!##===**8>TUU	===*7)?CCs##''000 P 1sM   8F7F7F7*BF7F3F74F7F7+F7	%F7.F5/F75F7c               (    V ^8  d   QhRRRRRRRR/# )r:   r|   r	   r   r<   kindr=   rr   r/   )r>   s   "r8   r?   r     s)       s # RV r7   c           	        V P                   f   R# \        \        P                  ! 4       4      VP                  ,
          pW@P                   8  d   R# \        P                  RV P                   VVV P                  VVP                  4       R# )zWhether the read-time freshness floor still covers a stale entry.

Consulted only after the stored policy has said stale and offline is
false.
Fznassume-fresh-seconds=%d: %s for %r from %s kept fresh at age %ds (server freshness %ds); skipping revalidationT)r   r+   r\   
fetched_atloggerdebugr   ry   )r   r|   r   r   rT   s   &&&& r8   r   *CachedAsyncSimpleClient._floor_keeps_fresh  ss     ""*$))+!2!22)))<##OONN		
 r7   c               $    V ^8  d   QhRRRRRR/# )r:   r   r<   r|   r	   r=   r   r/   )r>   s   "r8   r?   r     s$     * **$/*	+*r7   c                   V P                   P                  V4      pVf&   V P                  ;P                  ^,          un        R# \	        W24      pV'       d&   V P                  ;P
                  ^,          un        V# V P                  ;P                  ^,          un        Ve   R# \        V4      pVe#   \        P                  RVV P                  V4       R# )a  Rehydrate the parsed blob for ``package``, or ``None`` on any miss.

Returns the records only when a present blob decodes to at least one
record and its header binds ``policy``'s body. An absent blob, a
build/digest mismatch, or a corrupt blob all return ``None`` so the
caller reparses the raw body; genuine corruption (garbage/truncated
bytes) is logged at WARNING as a self-heal, while a build/digest
mismatch is a silent rebuild.

A blob that rehydrates to no records also declines. An empty listing
carries a second fact the blob does not hold: whether the page offered
only formats nab does not read, which decides between the "no such
package" and the "nothing nab reads" report. Only the raw body answers
that, and an empty listing is small enough to reparse.

This is the one place that reads the blob, so it records the outcome: a
served blob counts a ``hit``, an absent one a ``miss``, and a
present-but-not-served one a ``rebuild``.
NzRCorrupt parsed-listing cache blob for %r from %s: %s; rebuilding from the raw body)r   r   r   r-   r   r,   r.   _parsed_corruptionr   warningr   )r   r   r|   r   recordsreasons   &&&   r8   r   #CachedAsyncSimpleClient._parsed_hit  s    , {{,,W5<##q(# .""a'"N""a'"#D)NN/ r7   c               (    V ^8  d   QhRRRRRRRR/# )	r:   r   r<   digestrK   r   r   r=   r   r/   )r>   s   "r8   r?   r     s2     N NN$.N7RN	Nr7   c                l    Ve	   V'       g   R# V P                   P                  V\        W24      4       R# )ax  Write the parsed blob for ``files``, when there is one worth writing.

``digest`` is ``None`` when the body write did not land, so no blob ever
claims to describe a body the store does not hold. A listing with no
records is skipped too: :meth:`_parsed_hit` declines a blob holding
none, so writing one would rebuild and rewrite it on every later read
without ever serving it.
N)r   put_simple_parsed_encode_parsed)r   r   r   r   s   &&&&r8   _store_parsed%CachedAsyncSimpleClient._store_parsed  s'     >%%g~e/LMr7   c          
     ,    V ^8  d   QhRRRRRRRRR	R
/# )r:   r   r<   r   bytesr|   r	   r   r   r=   r   r/   )r>   s   "r8   r?   r     s<     3 33 3 	3
 +3 
3r7   c                    V'       g   R# VP                   pVfK   \        P                  ! V4      P                  4       pV P                  P                  V\        W5R7      4       V P                  WV4       R# )a2  Write a parsed blob for ``files`` bound to the on-disk ``body``.

An older policy carries no ``body_digest``; the blob would never bind,
so the digest is computed here and stamped into the refreshed policy so
the next read hits. A policy that already carries a digest reuses it,
sparing the body a rehash.
N)r   )r   hashlibsha256	hexdigestr   refresh_simple_policyr   r   )r   r   r   r|   r   r   s   &&&&& r8   r   'CachedAsyncSimpleClient._rebuild_parsed  s_     ##>^^D)335FKK--< 	7E2r7   c                    V ^8  d   QhRRRR/# )r:   rZ   r&   r=   r	   r/   )r>   s   "r8   r?   r     s     
 
 
+ 
r7   c                b    \        \        V4      \        4      p\        \	        V4      VRR7      # )z?Freshness policy for a name-level 404, clamped to the 600s cap.N)r   ry   etag)rF   rz   rx   r	   r^   )r   rZ   ry   s   && r8   _negative_policy(CachedAsyncSimpleClient._negative_policy  s.    )(35EF'17
 	
r7   c               $    V ^8  d   QhRRRRRR/# )r:   r   r   r   r<   r=   zobject | Noner/   )r>   s   "r8   r?   r     s!      5 3 = r7   c                     \         P                  ! V4      pV#   \         d&    \        P	                  RTT P
                  4        R# i ; i)a  Return the parsed JSON of a cached Simple body, or ``None``.

A body that will not decode as JSON is logged and treated as a miss.
A body that decodes but is the wrong shape is not caught here:
:func:`_parse_files` raises on it, the same as on the wire path.
zaCorrupt cached Simple-API body for %r from %s: not valid JSON; treating as a miss and re-fetchingN)jsonloadsrc   r   r   r   )r   r   r   decodeds   &&& r8   r   .CachedAsyncSimpleClient._decode_cached_listing  sM    		"jj.G   	NN5	 	s    ,A
	A
c               (    V ^8  d   QhRRRRRRRR/# )r:   r   r   r   r<   r}   r=   r   r/   )r>   s   "r8   r?   r   	  s2     B BB$'B36B	$Br7   c                     \         P                  ! V4      pT P                  YBTR7      #   \         d%   pT P                   RT: R2p\	        T4      ThRp?ii ; i)ai  Parse a Simple-API listing body served from ``page_url``.

A body that is not valid JSON raises the same
:class:`MalformedSimpleResponseError` as a valid-JSON body of the
wrong shape, not a raw decode error. ``json.loads`` raises a
:class:`ValueError` for every body it rejects, including non-UTF-8
bytes and an integer literal past CPython's conversion limit.
z, served a malformed Simple-API response for z: body is not valid JSONNr   )r   r   rc   r   r   r   )r   r   r   r}   r   excr   s   &&&&   r8   _parse_listing&CachedAsyncSimpleClient._parse_listing	  sm    	=::d#D AA  	=??##O+57  /s3<	=s   + AAAr}   c               (    V ^8  d   QhRRRRRRRR/# )	r:   r   r   r   r<   r}   rK   r=   r   r/   )r>   s   "r8   r?   r     s,      %(7A	$r7   c                   \        WP                  W#R7      pV'       g-   \        V4      '       d   V P                  P	                  V4       V# )zParse a decoded listing body, marking one with no readable file.

``page_url`` is the URL the body was served from, which its relative
entries resolve against.
r   )r   r   r   r   add)r   r   r   r}   r   s   &&&$ r8   r   #CachedAsyncSimpleClient._parse_body  s;     T??GO066!!%%g.r7   c                    V ^8  d   QhRRRR/# )r:   r   r<   r=   rr   r/   )r>   s   "r8   r?   r   +  s     0 0c 0d 0r7   c                    WP                   9   # )zBWhether a listing for ``package`` held only files nab cannot read.)r   )r   r   s   &&r8   served_unreadable_only.CachedAsyncSimpleClient.served_unreadable_only+  s    ////r7   c               (    V ^8  d   QhRRRRRRRR/# )	r:   r   r<   r   r   r|   r	   r=   r   r/   )r>   s   "r8   r?   r   /  s,     + ++"'+1<+	$+r7   c           
     	H  "   V P                    V R 2pR\        V P                  4      /pVP                  e   VP                  VR&   V P                  P                  WER7      G Rj  xL
 pVP                  \        8X  d   \        \        V4      \        V4      '       d   \        V4      MVP                  \        VR4      ;'       g    VP                  VP                  \        W6P                  4      R7      pV P                   P#                  W4       V P%                  W!VP                  4      # VP                  \&        8X  d-   V P                   P)                  WP+                  V4      4       . # \-        Wd4       \/        W`P                   WP                  4      pV P%                  WVP                  4      p	\        \        V4      \        V4      \        VR4      VP                  R7      pV P                   P1                  WV4      p
V P3                  WV	4       V	#  EL5i)r   AcceptNzIf-None-Matchheadersr   )r   ry   r   r}   r   r   ry   r   r}   )r   r    r   r   r   getstatus_code_HTTP_NOT_MODIFIEDr	   r^   ru   rz   ry   r   urlr   r   r   r   r   put_negativer   r"   r   
put_simpler   )r   r   r   r|   r  r	  rZ   
new_policynew_bodyr   r   s   &&&&       r8   r   *CachedAsyncSimpleClient._revalidate_simple/  s     !'!,1$2E2EFG;;"'-{{GO$,,S,BB#55$+H5 )22 (1Xv.==&++!+FLLA
J KK--gB&&thllCC?2KK$$W.C.CH.MNI& oow0C0C

 ##Hx||D '1'16*\\	

 '':F7E2I Cs   A%H"'H(A!H"
EH"c                    V ^8  d   QhRRRR/# r   r/   )r>   s   "r8   r?   r   \  s      3 3N r7   c                	  "   V P                    V R 2p\        V P                  4      pV P                  P	                  VRV/R7      G Rj  xL
 pVP
                  \        8X  d-   V P                  P                  WP                  V4      4       . # \        WB4       \        W@P                   WP                  4      pV P                  WQVP                  4      p\        \        V4      \!        V4      \#        VR4      VP                  R7      pV P                  P%                  WV4      pV P'                  WV4       V P                  P)                  V4       V#  EL5i)r   r  r  Nr   r
  )r   r    r   r   r  r  r   r   r  r   r"   r   r   r  r	   r^   rz   r   r  r   drop_negative)	r   r   r  acceptrZ   r   r   r|   r   s	   &&       r8   r   %CachedAsyncSimpleClient._fetch_simple\  s!    !'!,%d&9&9:,,S8V:L,MM?2KK$$W.C.CH.MNI&XATATU ##D8<<@'1'16*\\	
 ''v>7E2!!'*' Ns   A	EEDEc          
     ,    V ^8  d   QhRRRRRRRRRR/# )r:   r   r<   versionmetadata_urlmetadata_hashztuple[str, str] | Noner=   r/   )r>   s   "r8   r?   r   t  s<     0 00 0 	0
 .0 
0r7   c                  "   V P                   P                  W4      pVe   V# V P                  '       d   RV RV R2p\        V4      hV P                  P                  V4      G Rj  xL
 p\        Ws4       VP                  pVe   \        W4        VP                  R4      p	T P                   P                  YT	4       T	#  LZ  \         d   p
T RT RT R2p\        T4      T
hRp
?
ii ; i5i)a  Return the PEP 658 metadata text published at ``metadata_url``.

``version`` names the coordinate being resolved; the cache entry
stands for the sidecar at the URL.

Treated as immutable: cached forever, never revalidated.  A
cache hit is returned without re-checking, since it was
verified before being stored.  Cache miss + offline raises
:class:`OfflineError`.  When ``metadata_hash`` is given, the
fetched bytes are verified against it and a mismatch raises
:class:`MetadataHashMismatchError` before anything is cached.

Metadata is decoded from the hash-verified bytes as utf-8 rather
than the transport's ``.text``, which a backend may decode under
the response Content-Type charset. This keeps the parsed text
tied to the bytes the hash covers.  A body that is not valid utf-8
raises :class:`MalformedSimpleResponseError` (an :class:`HttpError`
subclass) rather than a raw :class:`UnicodeDecodeError`.
NzNo cached metadata for ==r   zutf-8z1 served a malformed PEP 658 metadata sidecar for z: body is not valid UTF-8)r   get_metadatar   r
   r   r  r"   contentr   r   UnicodeDecodeErrorr   put_metadata)r   r   r  r  r  r   r   rZ   r   textr   s   &&&&&      r8   get_metadata_text)CachedAsyncSimpleClient.get_metadata_textt  s     4 ))'@M===+G9BwiOCs##,,\::/""$!'9	=>>'*D 	  = ; " 	=. !ir'*CE  /s3<	=s:   3C/2C/(C)*C/C %C/C,C''C,,C/c               0    V ^8  d   QhRRRRRRRRRRR	R
/# )r:   r   r<   r  	wheel_urlcanonical_namer#   wheel_hashestuple[tuple[str, str], ...]r=   r   r/   )r>   s   "r8   r?   r     sF     ) )) ) 	)
 ') 2) 
)r7   c           	       "   V P                   P                  W4      pVe   \        V\        P                  4      # V P
                  '       d   RV RV R2p\        V4      h\        V P                  VVV P                  \        V4      R7      G Rj  xL
 pVP                  e'   V P                   P                  WVP                  4       V#  L:5i)a-  Recover a sidecar-less wheel's METADATA by HTTP range reads.

The recovered text is cached under the same immutable ``metadata-v1``
store the PEP 658 sidecar uses, keyed by the wheel URL. A cache hit is
returned without any transport call, so a warm or previously-warmed
offline resolve never re-ranges. A cold miss in offline mode raises
:class:`OfflineError`; otherwise the reader drives the transport with
the shared range-capability memo. Only a successful read is cached; an
``UNSUPPORTED`` or ``MISSING`` result writes nothing so the caller can
step to the sdist rung.

When ``wheel_hashes`` carries an acceptable published digest, a
full-body acquisition is verified against it before its METADATA is
read, mirroring the sdist path. A mismatch raises
:class:`WheelHashMismatchError` and nothing is cached.
NzNo cached range metadata for r  r   )
wheel_hash)r   r  r   r   PARTIALr   r
   r   r   r   r   r#  r"  )	r   r   r  r'  r(  r)  r   r   results	   &&&&&&   r8   get_range_metadata*CachedAsyncSimpleClient.get_range_metadata  s     0 ))'=&v|/C/CDD===1'"WI_UCs##5OO,\:
 
 ;;"KK$$WE
s   ACACC;Cc          
     ,    V ^8  d   QhRRRRRRRRRR/# )	r:   r   r<   r  	sdist_urlsdist_hashesr*  r=   ztuple[str | None, str | None]r/   )r>   s   "r8   r?   r     s<     %* %*%* %* 	%*
 2%* 
'%*r7   c                  "   V P                   P                  W4      pVe   V# V P                  '       d   RV RV R2p\        V4      hV P                  P                  V\        R7      G Rj  xL
 p\        Ws4       \        V4      pVe   \        VP                  V4       \        VP                  4      w  rV	f   V
e   V P                   P                  WW4       W3#  Lr5i)a  Return ``(pkg_info, pyproject_toml)`` for an sdist, caching both.

Cache miss + offline raises :class:`OfflineError`.  Either
element may be ``None`` if the corresponding file is absent
from the archive (or the archive cannot be parsed).  Both are
treated as immutable: cached forever, never revalidated.

When ``sdist_hashes`` carries an acceptable published digest, the
downloaded archive is verified against it before extraction. A
mismatch raises :class:`SdistHashMismatchError` and nothing is
cached.
NzNo cached sdist PKG-INFO for r  r   r  )r   get_sdist_filesr   r
   r   r  r!   r"   r   r   r   r   put_sdist_files)r   r   r  r2  r3  r   r   rZ   selectedpkg_infopyproject_tomls   &&&&&      r8   r5  'CachedAsyncSimpleClient.get_sdist_files  s     & ,,W>M===1'"WI_UCs##,,Y@P,QQ,(6h..9#78H8H#I >#=KK''(S)) Rs   3C#8C#.C!/A3C#c          
     ,    V ^8  d   QhRRRRRRRRRR/# )	r:   r   r<   r  r2  r3  r*  r=   r   r/   )r>   s   "r8   r?   r     s<            	 
 2  
 r7   c                "  "   ??V P                   '       d   RV R2p\        V4      hV P                  P                  V\        R7      G Rj  xL
 p\        Wc4       \        V4      pVe   \        VP                  V4       VP                  #  L@5i)a  Return the raw bytes of an sdist archive.

Used by the ``BUILD_REMOTE`` path when a real backend invocation
is required.  No on-disk caching is performed: archives are
large, builds are rare, and the in-memory index already
deduplicates within a single resolve.  Offline mode raises
:class:`OfflineError` because there is no slot to read from.

When ``sdist_hashes`` carries an acceptable published digest, the
downloaded archive is verified before its bytes are returned. A
mismatch raises :class:`SdistHashMismatchError`.
z1sdist archive fetch unavailable in offline mode ()r  N)	r   r
   r   r  r!   r"   r   r   r   )r   r   r  r2  r3  r   rZ   r7  s   &&&&&   r8   get_sdist_archive)CachedAsyncSimpleClient.get_sdist_archive  s     & W===Ei[PQRCs##,,Y@P,QQ,(6h..9 Rs   A
BBAB)	r   r   r   r   r   r   r   r   r   )N)r/   )r0   r1   r2   r3   r4   r   r   	NEGOTIATEr   r   r   r   r   r   r   r   r   r   r   r   r   r  r   r   r$  r/  r5  r>  r6   r/   r7   r8   r'   r'      s     '	,
 ,
 26,
 .A-J-J,
 )-,
 15,
 ,
\'B1H.*XN34
&B*DH0+Z00d)V%*N   r7   )r'   r(   r   l        )Ur4   
__future__r   r   r   loggingrer\   dataclassesr   r   datetimer   email.utilsr   typingr   r   r   r	   r
   clientr   r   r   r   r   r   r   r   r   r   r   r   r   
lazy_wheelr   r   r   r   parsed_listingr   r   r   r   r   r   r   r   r    r   r!   r"   packaging.utilsr#   typing_extensionsr$   r%   r&   __all__	getLoggerr0   r   r(   rx   r  compile
IGNORECASErN   rV   rE   rC   r<   rD   rH   rR   rW   r^   rh   rp   ru   rz   r   r   r'   r/   r7   r8   <module>rQ     s4   #    	  *  -   : :     E 8.&; 
		8	$   &   jj12==A
**'
( c"234 6*
*C#*D$A"	G0<V  V r7   