diff --git a/poetry.lock b/poetry.lock index 17a8481e..9231c3a0 100644 --- a/poetry.lock +++ b/poetry.lock @@ -6,6 +6,14 @@ category = "main" optional = true python-versions = "*" +[[package]] +name = "appdirs" +version = "1.4.4" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "main" +optional = false +python-versions = "*" + [[package]] name = "appnope" version = "0.1.2" @@ -92,17 +100,16 @@ optional = false python-versions = "*" [[package]] -name = "cachy" -version = "0.3.0" -description = "Cachy provides a simple yet effective caching library." +name = "cattrs" +version = "1.10.0" +description = "Composable complex class support for attrs and dataclasses." category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.7,<4.0" -[package.extras] -redis = ["redis (>=3.3.6,<4.0.0)"] -memcached = ["python-memcached (>=1.59,<2.0)"] -msgpack = ["msgpack-python (>=0.5,<0.6)"] +[package.dependencies] +attrs = ">=20" +typing_extensions = {version = "*", markers = "python_version >= \"3.7\" and python_version < \"3.8\""} [[package]] name = "certifi" @@ -858,6 +865,33 @@ urllib3 = ">=1.21.1,<1.27" socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"] +[[package]] +name = "requests-cache" +version = "0.9.3" +description = "A transparent persistent cache for the requests library" +category = "main" +optional = false +python-versions = ">=3.7,<4.0" + +[package.dependencies] +appdirs = ">=1.4.4,<2.0.0" +attrs = ">=21.2,<22.0" +cattrs = ">=1.8,<2.0" +requests = ">=2.22,<3.0" +url-normalize = ">=1.4,<2.0" +urllib3 = ">=1.25.5,<2.0.0" + +[package.extras] +dynamodb = ["boto3 (>=1.15,<2.0)", "botocore (>=1.18,<2.0)"] +all = ["boto3 (>=1.15,<2.0)", "botocore (>=1.18,<2.0)", "pymongo (>=3,<5)", "redis (>=3,<5)", "itsdangerous (>=2.0,<3.0)", "pyyaml (>=5.4)", "ujson (>=4.0)"] +mongodb = ["pymongo (>=3,<5)"] +redis = ["redis (>=3,<5)"] +bson = ["bson (>=0.5)"] +security = ["itsdangerous (>=2.0,<3.0)"] +yaml = ["pyyaml (>=5.4)"] +json = ["ujson (>=4.0)"] +docs = ["furo (>=2021.9.8)", "linkify-it-py (>=1.0.1,<2.0.0)", "myst-parser (>=0.15.1,<0.16.0)", "sphinx (==4.3.0)", "sphinx-autodoc-typehints (>=1.11,<2.0)", "sphinx-automodapi (>=0.13,<0.15)", "sphinx-copybutton (>=0.3,<0.5)", "sphinx-inline-tabs (>=2022.1.2b11)", "sphinx-notfound-page", "sphinx-panels (>=0.6,<0.7)", "sphinxcontrib-apidoc (>=0.3,<0.4)"] + [[package]] name = "responses" version = "0.18.0" @@ -1136,6 +1170,17 @@ category = "main" optional = false python-versions = ">=3.6" +[[package]] +name = "url-normalize" +version = "1.4.3" +description = "URL normalization for Python" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" + +[package.dependencies] +six = "*" + [[package]] name = "urllib3" version = "1.26.8" @@ -1229,13 +1274,17 @@ test = ["pytest", "pytest-cov", "testfixtures", "responses", "freezegun", "pytes [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "45ad55a867ae4f88bbc775c46abfc658cd94eff1d567992e8ce5a84de5e50ce8" +content-hash = "b2da64bae19266c575921d91c841c78a74d3b332a579bf10e484e05d262105f7" [metadata.files] alabaster = [ {file = "alabaster-0.7.12-py2.py3-none-any.whl", hash = "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359"}, {file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"}, ] +appdirs = [ + {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, + {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, +] appnope = [ {file = "appnope-0.1.2-py2.py3-none-any.whl", hash = "sha256:93aa393e9d6c54c5cd570ccadd8edad61ea0c4b9ea7a01409020c9aa019eb442"}, {file = "appnope-0.1.2.tar.gz", hash = "sha256:dd83cd4b5b460958838f6eb3000c660b1f9caf2a5b1de4264e941512f603258a"}, @@ -1269,9 +1318,9 @@ backcall = [ {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"}, {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"}, ] -cachy = [ - {file = "cachy-0.3.0-py2.py3-none-any.whl", hash = "sha256:338ca09c8860e76b275aff52374330efedc4d5a5e45dc1c5b539c1ead0786fe7"}, - {file = "cachy-0.3.0.tar.gz", hash = "sha256:186581f4ceb42a0bbe040c407da73c14092379b1e4c0e327fdb72ae4a9b269b1"}, +cattrs = [ + {file = "cattrs-1.10.0-py3-none-any.whl", hash = "sha256:35dd9063244263e63bd0bd24ea61e3015b00272cead084b2c40d788b0f857c46"}, + {file = "cattrs-1.10.0.tar.gz", hash = "sha256:211800f725cdecedcbcf4c753bbd22d248312b37d130f06045434acb7d9b34e1"}, ] certifi = [ {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"}, @@ -1476,9 +1525,6 @@ markupsafe = [ {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad"}, {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d"}, {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4dc8f9fb58f7364b63fd9f85013b780ef83c11857ae79f2feda41e270468dd9b"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:20dca64a3ef2d6e4d5d615a3fd418ad3bde77a47ec8a23d984a12b5b4c74491a"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cdfba22ea2f0029c9261a4bd07e830a8da012291fbe44dc794e488b6c9bb353a"}, {file = "MarkupSafe-2.0.1-cp310-cp310-win32.whl", hash = "sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28"}, {file = "MarkupSafe-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51"}, @@ -1490,9 +1536,6 @@ markupsafe = [ {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:deb993cacb280823246a026e3b2d81c493c53de6acfd5e6bfe31ab3402bb37dd"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:63f3268ba69ace99cab4e3e3b5840b03340efed0948ab8f78d2fd87ee5442a4f"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:8d206346619592c6200148b01a2142798c989edcb9c896f9ac9722a99d4e77e6"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-win32.whl", hash = "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567"}, @@ -1504,9 +1547,6 @@ markupsafe = [ {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d6c7ebd4e944c85e2c3421e612a7057a2f48d478d79e61800d81468a8d842207"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f0567c4dc99f264f49fe27da5f735f414c4e7e7dd850cfd8e69f0862d7c74ea9"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:89c687013cb1cd489a0f0ac24febe8c7a666e6e221b783e53ac50ebf68e45d86"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914"}, {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9"}, @@ -1519,9 +1559,6 @@ markupsafe = [ {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:aca6377c0cb8a8253e493c6b451565ac77e98c2951c45f913e0b52facdcff83f"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:04635854b943835a6ea959e948d19dcd311762c5c0c6e1f0e16ee57022669194"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6300b8454aa6930a24b9618fbb54b5a68135092bc666f7b06901f897fa5c2fee"}, {file = "MarkupSafe-2.0.1-cp38-cp38-win32.whl", hash = "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64"}, {file = "MarkupSafe-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833"}, {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26"}, @@ -1534,9 +1571,6 @@ markupsafe = [ {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1"}, {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac"}, {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4296f2b1ce8c86a6aea78613c34bb1a672ea0e3de9c6ba08a960efe0b0a09047"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f02365d4e99430a12647f09b6cc8bab61a6564363f313126f775eb4f6ef798e"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5b6d930f030f8ed98e3e6c98ffa0652bdb82601e7a016ec2ab5d7ff23baa78d1"}, {file = "MarkupSafe-2.0.1-cp39-cp39-win32.whl", hash = "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74"}, {file = "MarkupSafe-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8"}, {file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"}, @@ -1685,6 +1719,10 @@ requests = [ {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"}, {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"}, ] +requests-cache = [ + {file = "requests-cache-0.9.3.tar.gz", hash = "sha256:b32f8afba2439e1b3e12cba511c8f579271eff827f063210d62f9efa5bed6564"}, + {file = "requests_cache-0.9.3-py3-none-any.whl", hash = "sha256:d8b32405b2725906aa09810f4796e54cc03029de269381b404c426bae927bada"}, +] responses = [ {file = "responses-0.18.0-py3-none-any.whl", hash = "sha256:15c63ad16de13ee8e7182d99c9334f64fd81f1ee79f90748d527c28f7ca9dd51"}, {file = "responses-0.18.0.tar.gz", hash = "sha256:380cad4c1c1dc942e5e8a8eaae0b4d4edf708f4f010db8b7bcfafad1fcd254ff"}, @@ -1694,6 +1732,10 @@ responses = [ {file = "ruamel.yaml-0.17.20.tar.gz", hash = "sha256:4b8a33c1efb2b443a93fcaafcfa4d2e445f8e8c29c528d9f5cdafb7cc9e4004c"}, ] "ruamel.yaml.clib" = [ + {file = "ruamel.yaml.clib-0.2.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6e7be2c5bcb297f5b82fee9c665eb2eb7001d1050deaba8471842979293a80b0"}, + {file = "ruamel.yaml.clib-0.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:221eca6f35076c6ae472a531afa1c223b9c29377e62936f61bc8e6e8bdc5f9e7"}, + {file = "ruamel.yaml.clib-0.2.6-cp310-cp310-win32.whl", hash = "sha256:1070ba9dd7f9370d0513d649420c3b362ac2d687fe78c6e888f5b12bf8bc7bee"}, + {file = "ruamel.yaml.clib-0.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:77df077d32921ad46f34816a9a16e6356d8100374579bc35e15bab5d4e9377de"}, {file = "ruamel.yaml.clib-0.2.6-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:cfdb9389d888c5b74af297e51ce357b800dd844898af9d4a547ffc143fa56751"}, {file = "ruamel.yaml.clib-0.2.6-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:7b2927e92feb51d830f531de4ccb11b320255ee95e791022555971c466af4527"}, {file = "ruamel.yaml.clib-0.2.6-cp35-cp35m-win32.whl", hash = "sha256:ada3f400d9923a190ea8b59c8f60680c4ef8a4b0dfae134d2f2ff68429adfab5"}, @@ -1821,6 +1863,10 @@ typing-extensions = [ {file = "typing_extensions-4.0.1-py3-none-any.whl", hash = "sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b"}, {file = "typing_extensions-4.0.1.tar.gz", hash = "sha256:4ca091dea149f945ec56afb48dae714f21e8692ef22a395223bcd328961b6a0e"}, ] +url-normalize = [ + {file = "url-normalize-1.4.3.tar.gz", hash = "sha256:d23d3a070ac52a67b83a1c59a0e68f8608d1cd538783b401bc9de2c0fac999b2"}, + {file = "url_normalize-1.4.3-py2.py3-none-any.whl", hash = "sha256:ec3c301f04e5bb676d333a7fa162fa977ad2ca04b7e652bfc9fac4e405728eed"}, +] urllib3 = [ {file = "urllib3-1.26.8-py2.py3-none-any.whl", hash = "sha256:000ca7f471a233c2251c6c7023ee85305721bfdf18621ebff4fd17a8653427ed"}, {file = "urllib3-1.26.8.tar.gz", hash = "sha256:0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c"}, diff --git a/pyproject.toml b/pyproject.toml index 2ad6ed17..ae7f3c7d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -76,7 +76,6 @@ pluggy = "*" autorepr = "*" loguru = "*" ConfigUpdater = "*" -cachy = "*" importlib-resources = { version = "*", python = ">=3.7, <3.9" } flatten-dict = "*" dpath = "*" @@ -103,6 +102,7 @@ pytest-datadir = { version = "*", optional = true } sphinx = { version = "*", optional = true } sphinx_rtd_theme = { version = "*", optional = true } sphobjinv = { version = "*", optional = true } +requests-cache = "*" [tool.poetry.extras] lint = ["pylint"] diff --git a/src/nitpick/style/cache.py b/src/nitpick/style/cache.py index ae842740..f5beb01b 100644 --- a/src/nitpick/style/cache.py +++ b/src/nitpick/style/cache.py @@ -1,36 +1,44 @@ """Cache functions and configuration for styles.""" +from __future__ import annotations + import re from datetime import timedelta -from typing import Tuple from loguru import logger +from requests_cache.cache_control import DO_NOT_CACHE, NEVER_EXPIRE from nitpick.enums import CachingEnum REGEX_CACHE_UNIT = re.compile(r"(?P\d+)\s+(?P(minute|hour|day|week))", re.IGNORECASE) +EXPIRES_DEFAULTS = { + CachingEnum.NEVER: DO_NOT_CACHE, + CachingEnum.FOREVER: NEVER_EXPIRE, + CachingEnum.EXPIRES: timedelta(hours=1), +} -def parse_cache_option(cache_option: str) -> Tuple[CachingEnum, timedelta]: +def parse_cache_option(cache_option: str) -> tuple[CachingEnum, timedelta | int]: """Parse the cache option provided on pyproject.toml. If no cache if provided or is invalid, the default is *one hour*. + """ - clean_cache_option = cache_option.strip().lower() if cache_option else "" - mapping = {CachingEnum.NEVER.name.lower(): CachingEnum.NEVER, CachingEnum.FOREVER.name.lower(): CachingEnum.FOREVER} - simple_cache = mapping.get(clean_cache_option) - if simple_cache: - logger.info(f"Simple cache option: {simple_cache.name}") - return simple_cache, timedelta() - - default = CachingEnum.EXPIRES, timedelta(hours=1) - if not clean_cache_option: - return default - - for match in REGEX_CACHE_UNIT.finditer(clean_cache_option): - plural_unit = match.group("unit") + "s" - number = int(match.group("number")) - logger.info(f"Cache option with unit: {number} {plural_unit}") - return CachingEnum.EXPIRES, timedelta(**{plural_unit: number}) - - logger.warning(f"Invalid cache option: {clean_cache_option}. Defaulting to 1 hour") - return default + clean_cache_option = cache_option.strip().upper() if cache_option else "" + try: + caching = CachingEnum[clean_cache_option] + logger.info(f"Simple cache option: {caching.name}") + except KeyError: + caching = CachingEnum.EXPIRES + + expires_after = EXPIRES_DEFAULTS[caching] + if caching is CachingEnum.EXPIRES and clean_cache_option: + for match in REGEX_CACHE_UNIT.finditer(clean_cache_option): + plural_unit = match.group("unit").lower() + "s" + number = int(match.group("number")) + logger.info(f"Cache option with unit: {number} {plural_unit}") + expires_after = timedelta(**{plural_unit: number}) + break + else: + logger.warning(f"Invalid cache option: {clean_cache_option}. Defaulting to 1 hour") + + return caching, expires_after diff --git a/src/nitpick/style/fetchers/__init__.py b/src/nitpick/style/fetchers/__init__.py index 10cd636d..bfcb8dab 100644 --- a/src/nitpick/style/fetchers/__init__.py +++ b/src/nitpick/style/fetchers/__init__.py @@ -8,10 +8,12 @@ from typing import TYPE_CHECKING, Optional, Tuple from urllib.parse import urlparse, uses_netloc, uses_relative -from cachy import CacheManager, Repository +from requests_cache import CachedSession from strenum import LowercaseStrEnum +from nitpick.enums import CachingEnum from nitpick.generic import is_url +from nitpick.style import parse_cache_option if TYPE_CHECKING: from nitpick.style.fetchers.base import FetchersType @@ -38,13 +40,19 @@ class StyleFetcherManager: cache_dir: str cache_option: str - cache_repository: Repository = field(init=False) + session: CachedSession = field(init=False) fetchers: FetchersType = field(init=False) def __post_init__(self): """Initialize dependant properties.""" - self.cache_repository = CacheManager({"stores": {"file": {"driver": "file", "path": self.cache_dir}}}).store() - self.fetchers = _get_fetchers(self.cache_repository, self.cache_option) + caching, expire_after = parse_cache_option(self.cache_option) + # honour caching headers on the response when an expiration time has + # been set meaning that the server can dictate cache expiration + # overriding the local expiration time. This may need to become a + # separate configuration option in future. + cache_control = caching is CachingEnum.EXPIRES + self.session = CachedSession(self.cache_dir / "styles", expire_after=expire_after, cache_control=cache_control) + self.fetchers = _get_fetchers(self.session) def fetch(self, url) -> StyleInfo: """Determine which fetcher to be used and fetch from it. @@ -86,15 +94,16 @@ def _get_domain_scheme(url: str) -> tuple[str, str]: return "", "file" -def _get_fetchers(cache_repository, cache_option) -> FetchersType: +def _get_fetchers(session: CachedSession) -> FetchersType: # pylint: disable=import-outside-toplevel + from nitpick.style.fetchers.base import StyleFetcher from nitpick.style.fetchers.file import FileFetcher from nitpick.style.fetchers.github import GitHubFetcher from nitpick.style.fetchers.http import HttpFetcher from nitpick.style.fetchers.pypackage import PythonPackageFetcher - def _factory(klass): - return klass(cache_repository, cache_option) + def _factory(klass: type[StyleFetcher]) -> StyleFetcher: + return klass(session) if klass.requires_connection else klass() fetchers = (_factory(FileFetcher), _factory(HttpFetcher), _factory(GitHubFetcher), _factory(PythonPackageFetcher)) pairs = _fetchers_to_pairs(fetchers) diff --git a/src/nitpick/style/fetchers/base.py b/src/nitpick/style/fetchers/base.py index e138a158..8e3ba31b 100644 --- a/src/nitpick/style/fetchers/base.py +++ b/src/nitpick/style/fetchers/base.py @@ -2,45 +2,38 @@ from __future__ import annotations from dataclasses import dataclass -from datetime import datetime from pathlib import Path -from typing import Dict +from typing import ClassVar, Dict -from cachy import CacheManager -from loguru import logger +from requests_cache import CachedSession from slugify import slugify -from nitpick.enums import CachingEnum from nitpick.generic import is_url -from nitpick.style import parse_cache_option from nitpick.style.fetchers import StyleInfo @dataclass(repr=True) class StyleFetcher: - """Base class of all fetchers, it encapsulate get/fetch from cache.""" + """Base class of all fetchers, it encapsulates get/fetch from a specific source.""" - cache_manager: CacheManager - cache_option: str + requires_connection: ClassVar[bool] = False - requires_connection = False - protocols: tuple = () + # only set when requires_connection is True + session: CachedSession | None = None + protocols: tuple[str, ...] = () domains: tuple[str, ...] = () - def fetch(self, url) -> StyleInfo: - """Fetch a style form cache or from a specific fetcher.""" - caching, caching_delta = parse_cache_option(self.cache_option) - path = self._get_output_path(url) - cached = self._get_from_cache(caching, url) - if cached: - return path, cached + def __post_init__(self): + """Validate that session has been passed in for requires_connection == True.""" + if self.requires_connection and self.session is None: + raise ValueError("session is required") + def fetch(self, url) -> StyleInfo: + """Fetch a style from a specific fetcher.""" contents = self._do_fetch(url) if not contents: return None, "" - - self._save_to_cache(caching, caching_delta, url, contents) - return path, contents + return self._get_output_path(url), contents @staticmethod def _get_output_path(url) -> Path: @@ -49,28 +42,8 @@ def _get_output_path(url) -> Path: return Path(url) - def _get_from_cache(self, caching, url): - if caching == CachingEnum.NEVER: - return None - - cached_value = self.cache_manager.get(url) - if cached_value is not None: - logger.debug(f"Using cached value for URL {url}") - return cached_value - - return None - def _do_fetch(self, url): raise NotImplementedError() - def _save_to_cache(self, caching, caching_delta, url, contents): - if caching == CachingEnum.FOREVER: - logger.debug(f"Caching forever the contents of {url}") - self.cache_manager.forever(str(url), contents) - elif caching == CachingEnum.EXPIRES: - future = datetime.now() + caching_delta - logger.debug(f"Caching the contents of {url} to expire in {future}") - self.cache_manager.put(str(url), contents, future) - FetchersType = Dict[str, "StyleFetcher"] diff --git a/src/nitpick/style/fetchers/github.py b/src/nitpick/style/fetchers/github.py index 57501c83..55139761 100644 --- a/src/nitpick/style/fetchers/github.py +++ b/src/nitpick/style/fetchers/github.py @@ -7,7 +7,7 @@ from functools import lru_cache from furl import furl -from requests import Session, get as requests_get +from requests import get as requests_get from nitpick.constants import GIT_AT_REFERENCE, SLASH from nitpick.style.fetchers import Scheme @@ -31,7 +31,6 @@ class GitHubURL: def __post_init__(self): """Remove the initial slash from the path.""" - self._session = Session() self.path = self.path.lstrip(SLASH) @mypy_property @@ -170,7 +169,7 @@ def get_default_branch(api_url: str) -> str: class GitHubFetcher(HttpFetcher): # pylint: disable=too-few-public-methods """Fetch styles from GitHub repositories.""" - protocols: tuple = (Scheme.GH, Scheme.GITHUB) + protocols: tuple[str, ...] = (Scheme.GH, Scheme.GITHUB) # type: ignore domains: tuple[str, ...] = (GITHUB_COM,) def _download(self, url, **kwargs) -> str: diff --git a/src/nitpick/style/fetchers/http.py b/src/nitpick/style/fetchers/http.py index c73b1fa8..f95a163a 100644 --- a/src/nitpick/style/fetchers/http.py +++ b/src/nitpick/style/fetchers/http.py @@ -1,12 +1,11 @@ """Base HTTP fetcher, other fetchers can inherit from this to wrap http errors.""" from __future__ import annotations -from dataclasses import dataclass, field +from dataclasses import dataclass import click import requests from loguru import logger -from requests.sessions import Session from nitpick.enums import OptionEnum from nitpick.style.fetchers import Scheme @@ -17,18 +16,9 @@ class HttpFetcher(StyleFetcher): """Fetch a style from an http/https server.""" - _session: Session = field(init=False) - requires_connection = True - protocols: tuple = (Scheme.HTTP, Scheme.HTTPS) - - def __post_init__(self): - """Sessions should be per class as children can have custom headers or authentication.""" - self._session = Session() - self._post_hooks() - def _post_hooks(self): - """Dataclasses won't call post init here if another class extends it and override.""" + protocols: tuple[str, ...] = (Scheme.HTTP, Scheme.HTTPS) # type: ignore def _do_fetch(self, url) -> str: try: @@ -47,6 +37,8 @@ def _do_fetch(self, url) -> str: def _download(self, url, **kwargs) -> str: logger.info(f"Downloading style from {url}") - response = self._session.get(url, **kwargs) + if self.session is None: + raise RuntimeError("No session provided to fetcher") + response = self.session.get(url, **kwargs) response.raise_for_status() return response.text diff --git a/tests/test_cache.py b/tests/test_cache.py index 56531a59..0337fc51 100644 --- a/tests/test_cache.py +++ b/tests/test_cache.py @@ -3,6 +3,7 @@ import pytest from freezegun import freeze_time +from requests_cache.cache_control import DO_NOT_CACHE, NEVER_EXPIRE from nitpick.enums import CachingEnum from nitpick.style.cache import parse_cache_option @@ -52,10 +53,10 @@ def test_never(project_remote): @pytest.mark.parametrize( "cache_option,expected_enum,expected_timedelta", [ - ("never", CachingEnum.NEVER, timedelta()), - (" NEVER\n ", CachingEnum.NEVER, timedelta()), - ("forever", CachingEnum.FOREVER, timedelta()), - ("\t Forever \n", CachingEnum.FOREVER, timedelta()), + ("never", CachingEnum.NEVER, DO_NOT_CACHE), + (" NEVER\n ", CachingEnum.NEVER, DO_NOT_CACHE), + ("forever", CachingEnum.FOREVER, NEVER_EXPIRE), + ("\t Forever \n", CachingEnum.FOREVER, NEVER_EXPIRE), (" 15 minutes garbage", CachingEnum.EXPIRES, timedelta(minutes=15)), (" 20 minute ", CachingEnum.EXPIRES, timedelta(minutes=20)), (" 3 hours ", CachingEnum.EXPIRES, timedelta(hours=3)),