33from __future__ import annotations
44
55import atexit
6+ import functools
67import os
78import re
89import tempfile
910import warnings
1011from contextlib import suppress
1112from pathlib import Path
12- from typing import TYPE_CHECKING , Iterable , Literal , overload
13-
14- import requests
13+ from typing import TYPE_CHECKING , Literal , overload
1514
1615from ._cache import CACHE_DISABLED , _SVGCache , cache_key , svg_cache
1716
1817if TYPE_CHECKING :
18+ from collections .abc import Iterable
1919 from typing import Callable , TypeVar
2020
21+ import requests
22+
2123 F = TypeVar ("F" , bound = Callable )
2224
2325 from .iconify_types import (
3032 Rotation ,
3133 )
3234
33- def lru_cache (maxsize : int | None = None ) -> Callable [[F ], F ]:
34- """Dummy lru_cache decorator for type checking."""
35-
36- else :
37- from functools import lru_cache
3835
3936ROOT = "https://api.iconify.design"
4037
4138
42- @lru_cache (maxsize = None )
39+ @functools .cache
40+ def _session () -> requests .Session :
41+ """Return a requests session."""
42+ import requests
43+
44+ session = requests .Session ()
45+ session .headers .update ({"User-Agent" : "pyconify" })
46+ return session
47+
48+
49+ @functools .cache
4350def collections (* prefixes : str ) -> dict [str , IconifyInfo ]:
4451 """Return collections where key is icon set prefix, value is IconifyInfo object.
4552
@@ -55,12 +62,12 @@ def collections(*prefixes: str) -> dict[str, IconifyInfo]:
5562 end with "-", such as "mdi-" matches "mdi-light".
5663 """
5764 query_params = {"prefixes" : "," .join (prefixes )}
58- resp = requests .get (f"{ ROOT } /collections" , params = query_params )
65+ resp = _session () .get (f"{ ROOT } /collections" , params = query_params , timeout = 2 )
5966 resp .raise_for_status ()
6067 return resp .json () # type: ignore
6168
6269
63- @lru_cache ( maxsize = None )
70+ @functools . cache
6471def collection (
6572 prefix : str ,
6673 info : bool = False ,
@@ -86,18 +93,19 @@ def collection(
8693 query_params ["chars" ] = 1
8794 if info :
8895 query_params ["info" ] = 1
89- resp = requests .get (f"{ ROOT } /collection?prefix={ prefix } " , params = query_params )
90- resp .raise_for_status ()
91- if (content := resp .json ()) == 404 :
92- raise requests .HTTPError (
96+ resp = _session ().get (
97+ f"{ ROOT } /collection?prefix={ prefix } " , params = query_params , timeout = 2
98+ )
99+ if 400 <= resp .status_code < 500 :
100+ raise OSError (
93101 f"Icon set { prefix !r} not found. "
94102 "Search for icons at https://icon-sets.iconify.design" ,
95- response = resp ,
96103 )
97- return content # type: ignore
104+ resp .raise_for_status ()
105+ return resp .json () # type: ignore
98106
99107
100- @lru_cache ( maxsize = None )
108+ @functools . cache
101109def last_modified (* prefixes : str ) -> dict [str , int ]:
102110 """Return last modified date for icon sets.
103111
@@ -120,7 +128,7 @@ def last_modified(*prefixes: str) -> dict[str, int]:
120128 UTC integer timestamp.
121129 """
122130 query_params = {"prefixes" : "," .join (prefixes )}
123- resp = requests .get (f"{ ROOT } /last-modified" , params = query_params )
131+ resp = _session () .get (f"{ ROOT } /last-modified" , params = query_params , timeout = 2 )
124132 resp .raise_for_status ()
125133 if "lastModified" not in (content := resp .json ()): # pragma: no cover
126134 raise ValueError (
@@ -200,14 +208,13 @@ def svg(
200208 }
201209 if box :
202210 query_params ["box" ] = 1
203- resp = requests .get (f"{ ROOT } /{ prefix } /{ name } .svg" , params = query_params )
204- resp .raise_for_status ()
205- if resp .content == b"404" :
206- raise requests .HTTPError (
211+ resp = _session ().get (f"{ ROOT } /{ prefix } /{ name } .svg" , params = query_params , timeout = 2 )
212+ if 400 <= resp .status_code < 500 :
213+ raise OSError (
207214 f"Icon '{ prefix } :{ name } ' not found. "
208215 f"Search for icons at https://icon-sets.iconify.design?query={ name } " ,
209- response = resp ,
210216 )
217+ resp .raise_for_status ()
211218
212219 # cache response and return
213220 cache [svg_cache_key ] = resp .content
@@ -253,7 +260,7 @@ def _cached_svg_path(svg_cache_key: str) -> Path | None:
253260 return None # pragma: no cover
254261
255262
256- @lru_cache ( maxsize = None )
263+ @functools . cache
257264def svg_path (
258265 * key : str ,
259266 color : str | None = None ,
@@ -320,7 +327,7 @@ def _remove_tmp_svg() -> None:
320327 return Path (tmp_name )
321328
322329
323- @lru_cache ( maxsize = None )
330+ @functools . cache
324331def css (
325332 * keys : str ,
326333 selector : str | None = None ,
@@ -389,14 +396,15 @@ def css(
389396 if square :
390397 params ["square" ] = 1
391398
392- resp = requests .get (f"{ ROOT } /{ prefix } .css?icons={ ',' .join (icons )} " , params = params )
393- resp .raise_for_status ()
394- if resp .text == "404" :
395- raise requests .HTTPError (
399+ resp = _session ().get (
400+ f"{ ROOT } /{ prefix } .css?icons={ ',' .join (icons )} " , params = params , timeout = 2
401+ )
402+ if 400 <= resp .status_code < 500 :
403+ raise OSError (
396404 f"Icon set { prefix !r} not found. "
397405 "Search for icons at https://icon-sets.iconify.design" ,
398- response = resp ,
399406 )
407+ resp .raise_for_status ()
400408 if missing := set (re .findall (r"Could not find icon: ([^\s]*) " , resp .text )):
401409 warnings .warn (
402410 f"Icon(s) { sorted (missing )} not found. "
@@ -425,14 +433,13 @@ def icon_data(*keys: str) -> IconifyJSON:
425433 Icon name(s).
426434 """
427435 prefix , names = _split_prefix_name (keys , allow_many = True )
428- resp = requests .get (f"{ ROOT } /{ prefix } .json?icons={ ',' .join (names )} " )
429- resp .raise_for_status ()
436+ resp = _session ().get (f"{ ROOT } /{ prefix } .json?icons={ ',' .join (names )} " , timeout = 2 )
430437 if (content := resp .json ()) == 404 :
431- raise requests . HTTPError (
438+ raise OSError (
432439 f"Icon set { prefix !r} not found. "
433440 "Search for icons at https://icon-sets.iconify.design" ,
434- response = resp ,
435441 )
442+ resp .raise_for_status ()
436443 return content # type: ignore
437444
438445
@@ -499,7 +506,7 @@ def search(
499506 params ["prefixes" ] = "," .join (prefixes )
500507 if category is not None :
501508 params ["category" ] = category
502- resp = requests .get (f"{ ROOT } /search?query={ query } " , params = params )
509+ resp = _session () .get (f"{ ROOT } /search?query={ query } " , params = params , timeout = 2 )
503510 resp .raise_for_status ()
504511 return resp .json () # type: ignore
505512
@@ -536,12 +543,12 @@ def keywords(
536543 params = {"keyword" : keyword }
537544 else :
538545 params = {}
539- resp = requests .get (f"{ ROOT } /keywords" , params = params )
546+ resp = _session () .get (f"{ ROOT } /keywords" , params = params , timeout = 2 )
540547 resp .raise_for_status ()
541548 return resp .json () # type: ignore
542549
543550
544- @lru_cache ( maxsize = None )
551+ @functools . cache
545552def iconify_version () -> str :
546553 """Return version of iconify API.
547554
@@ -556,7 +563,7 @@ def iconify_version() -> str:
556563 >>> iconify_version()
557564 'Iconify API version 3.0.0-beta.1'
558565 """
559- resp = requests .get (f"{ ROOT } /version" )
566+ resp = _session () .get (f"{ ROOT } /version" , timeout = 2 )
560567 resp .raise_for_status ()
561568 return resp .text
562569
0 commit comments