From f18e6363547e2f1b932aa7574066f19ad0cc1311 Mon Sep 17 00:00:00 2001 From: sascha Date: Tue, 12 Mar 2024 09:24:03 +0000 Subject: [PATCH] login --- .../cachelib-0.12.0.dist-info/INSTALLER | 1 + .../cachelib-0.12.0.dist-info/LICENSE.rst | 28 + .../cachelib-0.12.0.dist-info/METADATA | 64 ++ .../cachelib-0.12.0.dist-info/RECORD | 27 + .../cachelib-0.12.0.dist-info/WHEEL | 5 + .../cachelib-0.12.0.dist-info/top_level.txt | 1 + .../site-packages/cachelib/__init__.py | 22 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 714 bytes .../cachelib/__pycache__/base.cpython-310.pyc | Bin 0 -> 7831 bytes .../__pycache__/dynamodb.cpython-310.pyc | Bin 0 -> 7600 bytes .../cachelib/__pycache__/file.cpython-310.pyc | Bin 0 -> 10065 bytes .../__pycache__/memcached.cpython-310.pyc | Bin 0 -> 7036 bytes .../__pycache__/mongodb.cpython-310.pyc | Bin 0 -> 7432 bytes .../__pycache__/redis.cpython-310.pyc | Bin 0 -> 6661 bytes .../__pycache__/serializers.cpython-310.pyc | Bin 0 -> 3796 bytes .../__pycache__/simple.cpython-310.pyc | Bin 0 -> 4450 bytes .../__pycache__/uwsgi.cpython-310.pyc | Bin 0 -> 2988 bytes .../python3.10/site-packages/cachelib/base.py | 185 +++++ .../site-packages/cachelib/dynamodb.py | 226 ++++++ .../python3.10/site-packages/cachelib/file.py | 333 +++++++++ .../site-packages/cachelib/memcached.py | 196 +++++ .../site-packages/cachelib/mongodb.py | 202 +++++ .../site-packages/cachelib/py.typed | 0 .../site-packages/cachelib/redis.py | 159 ++++ .../site-packages/cachelib/serializers.py | 112 +++ .../site-packages/cachelib/simple.py | 100 +++ .../site-packages/cachelib/uwsgi.py | 83 +++ .../flask_session-0.6.0.dist-info/INSTALLER | 1 + .../flask_session-0.6.0.dist-info/LICENSE.rst | 28 + .../flask_session-0.6.0.dist-info/METADATA | 61 ++ .../flask_session-0.6.0.dist-info/RECORD | 10 + .../flask_session-0.6.0.dist-info/REQUESTED | 0 .../flask_session-0.6.0.dist-info/WHEEL | 4 + .../site-packages/flask_session/__init__.py | 134 ++++ .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 3344 bytes .../__pycache__/sessions.cpython-310.pyc | Bin 0 -> 18135 bytes .../site-packages/flask_session/sessions.py | 697 ++++++++++++++++++ src9/login/__pycache__/app.cpython-310.pyc | Bin 0 -> 987 bytes .../2029240f6d1128be89ddc32729463129 | Bin 0 -> 9 bytes .../f8a6ee825797497a3461c2ce9264fd05 | Bin 0 -> 35 bytes 40 files changed, 2679 insertions(+) create mode 100644 froshims/.venv/lib/python3.10/site-packages/cachelib-0.12.0.dist-info/INSTALLER create mode 100644 froshims/.venv/lib/python3.10/site-packages/cachelib-0.12.0.dist-info/LICENSE.rst create mode 100644 froshims/.venv/lib/python3.10/site-packages/cachelib-0.12.0.dist-info/METADATA create mode 100644 froshims/.venv/lib/python3.10/site-packages/cachelib-0.12.0.dist-info/RECORD create mode 100644 froshims/.venv/lib/python3.10/site-packages/cachelib-0.12.0.dist-info/WHEEL create mode 100644 froshims/.venv/lib/python3.10/site-packages/cachelib-0.12.0.dist-info/top_level.txt create mode 100644 froshims/.venv/lib/python3.10/site-packages/cachelib/__init__.py create mode 100644 froshims/.venv/lib/python3.10/site-packages/cachelib/__pycache__/__init__.cpython-310.pyc create mode 100644 froshims/.venv/lib/python3.10/site-packages/cachelib/__pycache__/base.cpython-310.pyc create mode 100644 froshims/.venv/lib/python3.10/site-packages/cachelib/__pycache__/dynamodb.cpython-310.pyc create mode 100644 froshims/.venv/lib/python3.10/site-packages/cachelib/__pycache__/file.cpython-310.pyc create mode 100644 froshims/.venv/lib/python3.10/site-packages/cachelib/__pycache__/memcached.cpython-310.pyc create mode 100644 froshims/.venv/lib/python3.10/site-packages/cachelib/__pycache__/mongodb.cpython-310.pyc create mode 100644 froshims/.venv/lib/python3.10/site-packages/cachelib/__pycache__/redis.cpython-310.pyc create mode 100644 froshims/.venv/lib/python3.10/site-packages/cachelib/__pycache__/serializers.cpython-310.pyc create mode 100644 froshims/.venv/lib/python3.10/site-packages/cachelib/__pycache__/simple.cpython-310.pyc create mode 100644 froshims/.venv/lib/python3.10/site-packages/cachelib/__pycache__/uwsgi.cpython-310.pyc create mode 100644 froshims/.venv/lib/python3.10/site-packages/cachelib/base.py create mode 100644 froshims/.venv/lib/python3.10/site-packages/cachelib/dynamodb.py create mode 100644 froshims/.venv/lib/python3.10/site-packages/cachelib/file.py create mode 100644 froshims/.venv/lib/python3.10/site-packages/cachelib/memcached.py create mode 100644 froshims/.venv/lib/python3.10/site-packages/cachelib/mongodb.py create mode 100644 froshims/.venv/lib/python3.10/site-packages/cachelib/py.typed create mode 100644 froshims/.venv/lib/python3.10/site-packages/cachelib/redis.py create mode 100644 froshims/.venv/lib/python3.10/site-packages/cachelib/serializers.py create mode 100644 froshims/.venv/lib/python3.10/site-packages/cachelib/simple.py create mode 100644 froshims/.venv/lib/python3.10/site-packages/cachelib/uwsgi.py create mode 100644 froshims/.venv/lib/python3.10/site-packages/flask_session-0.6.0.dist-info/INSTALLER create mode 100644 froshims/.venv/lib/python3.10/site-packages/flask_session-0.6.0.dist-info/LICENSE.rst create mode 100644 froshims/.venv/lib/python3.10/site-packages/flask_session-0.6.0.dist-info/METADATA create mode 100644 froshims/.venv/lib/python3.10/site-packages/flask_session-0.6.0.dist-info/RECORD create mode 100644 froshims/.venv/lib/python3.10/site-packages/flask_session-0.6.0.dist-info/REQUESTED create mode 100644 froshims/.venv/lib/python3.10/site-packages/flask_session-0.6.0.dist-info/WHEEL create mode 100644 froshims/.venv/lib/python3.10/site-packages/flask_session/__init__.py create mode 100644 froshims/.venv/lib/python3.10/site-packages/flask_session/__pycache__/__init__.cpython-310.pyc create mode 100644 froshims/.venv/lib/python3.10/site-packages/flask_session/__pycache__/sessions.cpython-310.pyc create mode 100644 froshims/.venv/lib/python3.10/site-packages/flask_session/sessions.py create mode 100644 src9/login/__pycache__/app.cpython-310.pyc create mode 100644 src9/login/flask_session/2029240f6d1128be89ddc32729463129 create mode 100644 src9/login/flask_session/f8a6ee825797497a3461c2ce9264fd05 diff --git a/froshims/.venv/lib/python3.10/site-packages/cachelib-0.12.0.dist-info/INSTALLER b/froshims/.venv/lib/python3.10/site-packages/cachelib-0.12.0.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/froshims/.venv/lib/python3.10/site-packages/cachelib-0.12.0.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/froshims/.venv/lib/python3.10/site-packages/cachelib-0.12.0.dist-info/LICENSE.rst b/froshims/.venv/lib/python3.10/site-packages/cachelib-0.12.0.dist-info/LICENSE.rst new file mode 100644 index 0000000..8f60923 --- /dev/null +++ b/froshims/.venv/lib/python3.10/site-packages/cachelib-0.12.0.dist-info/LICENSE.rst @@ -0,0 +1,28 @@ +Copyright 2018 Pallets + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/froshims/.venv/lib/python3.10/site-packages/cachelib-0.12.0.dist-info/METADATA b/froshims/.venv/lib/python3.10/site-packages/cachelib-0.12.0.dist-info/METADATA new file mode 100644 index 0000000..eed4b71 --- /dev/null +++ b/froshims/.venv/lib/python3.10/site-packages/cachelib-0.12.0.dist-info/METADATA @@ -0,0 +1,64 @@ +Metadata-Version: 2.1 +Name: cachelib +Version: 0.12.0 +Summary: A collection of cache libraries in the same API interface. +Home-page: https://github.com/pallets-eco/cachelib/ +Maintainer: Pallets +Maintainer-email: contact@palletsprojects.com +License: BSD-3-Clause +Project-URL: Donate, https://palletsprojects.com/donate +Project-URL: Documentation, https://cachelib.readthedocs.io/ +Project-URL: Changes, https://cachelib.readthedocs.io/changes/ +Project-URL: Source Code, https://github.com/pallets-eco/cachelib/ +Project-URL: Issue Tracker, https://github.com/pallets-eco/cachelib/issues/ +Project-URL: Twitter, https://twitter.com/PalletsTeam +Project-URL: Chat, https://discord.gg/pallets +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: BSD License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Requires-Python: >=3.8 +Description-Content-Type: text/x-rst +License-File: LICENSE.rst + +CacheLib +======== + +A collection of cache libraries in the same API interface. Extracted +from Werkzeug. + + +Installing +---------- + +Install and update using `pip`_: + +.. code-block:: text + + $ pip install -U cachelib + +.. _pip: https://pip.pypa.io/en/stable/getting-started/ + + +Donate +------ + +The Pallets organization develops and supports Flask and the libraries +it uses. In order to grow the community of contributors and users, and +allow the maintainers to devote more time to the projects, `please +donate today`_. + +.. _please donate today: https://palletsprojects.com/donate + + +Links +----- + +- Documentation: https://cachelib.readthedocs.io/ +- Changes: https://cachelib.readthedocs.io/changes/ +- PyPI Releases: https://pypi.org/project/cachelib/ +- Source Code: https://github.com/pallets/cachelib/ +- Issue Tracker: https://github.com/pallets/cachelib/issues/ +- Twitter: https://twitter.com/PalletsTeam +- Chat: https://discord.gg/pallets diff --git a/froshims/.venv/lib/python3.10/site-packages/cachelib-0.12.0.dist-info/RECORD b/froshims/.venv/lib/python3.10/site-packages/cachelib-0.12.0.dist-info/RECORD new file mode 100644 index 0000000..1caa508 --- /dev/null +++ b/froshims/.venv/lib/python3.10/site-packages/cachelib-0.12.0.dist-info/RECORD @@ -0,0 +1,27 @@ +cachelib-0.12.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +cachelib-0.12.0.dist-info/LICENSE.rst,sha256=zUGBIIEtwmJiga4CfoG2SCKdFmtaynRyzs1RADjTbn0,1475 +cachelib-0.12.0.dist-info/METADATA,sha256=5rWdhpMckpSSZve1XYviRLBz_oHi5lAGknNHmWJ5V8g,1960 +cachelib-0.12.0.dist-info/RECORD,, +cachelib-0.12.0.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92 +cachelib-0.12.0.dist-info/top_level.txt,sha256=AYC4q8wgGd_hR_F2YcDkmtQm41gv9-5AThKuQtNPEXk,9 +cachelib/__init__.py,sha256=LqvUrhckxpFJyQcJ1eWsDhYZjzqjpovzipSTfw1dvjE,575 +cachelib/__pycache__/__init__.cpython-310.pyc,, +cachelib/__pycache__/base.cpython-310.pyc,, +cachelib/__pycache__/dynamodb.cpython-310.pyc,, +cachelib/__pycache__/file.cpython-310.pyc,, +cachelib/__pycache__/memcached.cpython-310.pyc,, +cachelib/__pycache__/mongodb.cpython-310.pyc,, +cachelib/__pycache__/redis.cpython-310.pyc,, +cachelib/__pycache__/serializers.cpython-310.pyc,, +cachelib/__pycache__/simple.cpython-310.pyc,, +cachelib/__pycache__/uwsgi.cpython-310.pyc,, +cachelib/base.py,sha256=3_B-cB1VEh_x-VzH9g3qvzdqCxDX2ywDzQ7a_aYFJlE,6731 +cachelib/dynamodb.py,sha256=fSmp8G7V0yBcRC2scdIhz8d0D2-9OMZEwQ9AcBONyC8,8512 +cachelib/file.py,sha256=V8uPVfgn5YK7PcCvQlignH5QCTdupEpJ9chucr3XVmM,11678 +cachelib/memcached.py,sha256=KyUN4wblVPf2XNLYk15kwN9QTfkFK6jrpVGrj4NAoFA,7160 +cachelib/mongodb.py,sha256=b9l8fTKMFm8hAXFn748GKertHUASSVDBgPgrtGaZ6cA,6901 +cachelib/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +cachelib/redis.py,sha256=hSKV9fVD7gzk1X_B3Ac9XJYN3G3F6eYBoreqDo9pces,6295 +cachelib/serializers.py,sha256=MXk1moN6ljOPUFQ0E0D129mZlrDDwuQ5DvInNkitvoI,3343 +cachelib/simple.py,sha256=8UPp95_oc3bLeW_gzFUzdppIKV8hV_o_yQYoKb8gVMk,3422 +cachelib/uwsgi.py,sha256=4DX3C9QGvB6mVcg1d7qpLIEkI6bccuq-8M6I_YbPicY,2563 diff --git a/froshims/.venv/lib/python3.10/site-packages/cachelib-0.12.0.dist-info/WHEEL b/froshims/.venv/lib/python3.10/site-packages/cachelib-0.12.0.dist-info/WHEEL new file mode 100644 index 0000000..98c0d20 --- /dev/null +++ b/froshims/.venv/lib/python3.10/site-packages/cachelib-0.12.0.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.42.0) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/froshims/.venv/lib/python3.10/site-packages/cachelib-0.12.0.dist-info/top_level.txt b/froshims/.venv/lib/python3.10/site-packages/cachelib-0.12.0.dist-info/top_level.txt new file mode 100644 index 0000000..6e3a9b9 --- /dev/null +++ b/froshims/.venv/lib/python3.10/site-packages/cachelib-0.12.0.dist-info/top_level.txt @@ -0,0 +1 @@ +cachelib diff --git a/froshims/.venv/lib/python3.10/site-packages/cachelib/__init__.py b/froshims/.venv/lib/python3.10/site-packages/cachelib/__init__.py new file mode 100644 index 0000000..3647386 --- /dev/null +++ b/froshims/.venv/lib/python3.10/site-packages/cachelib/__init__.py @@ -0,0 +1,22 @@ +from cachelib.base import BaseCache +from cachelib.base import NullCache +from cachelib.dynamodb import DynamoDbCache +from cachelib.file import FileSystemCache +from cachelib.memcached import MemcachedCache +from cachelib.mongodb import MongoDbCache +from cachelib.redis import RedisCache +from cachelib.simple import SimpleCache +from cachelib.uwsgi import UWSGICache + +__all__ = [ + "BaseCache", + "NullCache", + "SimpleCache", + "FileSystemCache", + "MemcachedCache", + "RedisCache", + "UWSGICache", + "DynamoDbCache", + "MongoDbCache", +] +__version__ = "0.12.0" diff --git a/froshims/.venv/lib/python3.10/site-packages/cachelib/__pycache__/__init__.cpython-310.pyc b/froshims/.venv/lib/python3.10/site-packages/cachelib/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ca92cbd7f31fd051c76b88d9575a16e33c0459da GIT binary patch literal 714 zcmY*VyKdVs6eVR_*3))Uq-$pn5uF6xinK_B78#NuMp1wZA&ZPfgp>@?cHp94ldZqR zwNtmunMyK{;!x1Rx##je4(l|HfI0BP6Z`A|@Ye?W$7tY-`PFX&6i_IDgcw50J;Och zIo=aJ*~fjy`(hw{>^nXXLmA+}abJWo!ja=c5z7Q8jt3%@BRq0E6k|ET69|@oMvo95 z&=?=mWDch3KeGXtLLFY@iha$ClG)&9BSagdm%A#L_2r@!#^1PL^PO5V*-E2pCJS3k zTP3-!tL0u%bjv7LZ8n~Bxf0#H=*Q3b_p6s=*x1);dKN+p0}H-|z=D0*rf*?r;Fmwk zPENDg&Gem4-3Ed$vW4;T+D0FA3F@rSg=?@^aAzF3LHe+}D_cu$xSqTwtTWTY&ex+( zZmdDKqaJsnaz34ry{nrCwdBr;9ukrZL5PkC*|J9Qx+3HcXpXEy3(vQxtR+J#S4Ekl zLVcW}yQWqpmkMQDR&9~7j8?mKSyyM-$qXsJX2+|%xX+hNp>`ES2(S2>kZiTn!6#YM UjbP`{Oi=F)Bp`urzrYLP|6>ljZU6uP literal 0 HcmV?d00001 diff --git a/froshims/.venv/lib/python3.10/site-packages/cachelib/__pycache__/base.cpython-310.pyc b/froshims/.venv/lib/python3.10/site-packages/cachelib/__pycache__/base.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dfdf3ae3975b5391dcc1b074e262f61a852dd5d2 GIT binary patch literal 7831 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 literal 0 HcmV?d00001 diff --git a/froshims/.venv/lib/python3.10/site-packages/cachelib/__pycache__/dynamodb.cpython-310.pyc b/froshims/.venv/lib/python3.10/site-packages/cachelib/__pycache__/dynamodb.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b930ff477da08b4da5b10042a62ac58a0a271b86 GIT binary patch literal 7600 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 D0mk*7 literal 0 HcmV?d00001 diff --git a/froshims/.venv/lib/python3.10/site-packages/cachelib/__pycache__/file.cpython-310.pyc b/froshims/.venv/lib/python3.10/site-packages/cachelib/__pycache__/file.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..544bc5fad1e62a943bd7891bc2393ec6ee9e5135 GIT binary patch literal 10065 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 oA4kL)%ZWq-;M}8KYF){|^CnQ51k literal 0 HcmV?d00001 diff --git a/froshims/.venv/lib/python3.10/site-packages/cachelib/__pycache__/memcached.cpython-310.pyc b/froshims/.venv/lib/python3.10/site-packages/cachelib/__pycache__/memcached.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b31ffa54e8668c5dc66dfbe104f22feef782423a GIT binary patch literal 7036 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 literal 0 HcmV?d00001 diff --git a/froshims/.venv/lib/python3.10/site-packages/cachelib/__pycache__/mongodb.cpython-310.pyc b/froshims/.venv/lib/python3.10/site-packages/cachelib/__pycache__/mongodb.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..08f89bd148ccba301af318dcc2cd55d2d24f11d4 GIT binary patch literal 7432 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 a&UmVF@1|DJyJSH6az~kAi?z&i)^7kU!sr74 literal 0 HcmV?d00001 diff --git a/froshims/.venv/lib/python3.10/site-packages/cachelib/__pycache__/redis.cpython-310.pyc b/froshims/.venv/lib/python3.10/site-packages/cachelib/__pycache__/redis.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a72517255272df2b4b659035657349a865ce1521 GIT binary patch literal 6661 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 literal 0 HcmV?d00001 diff --git a/froshims/.venv/lib/python3.10/site-packages/cachelib/__pycache__/serializers.cpython-310.pyc b/froshims/.venv/lib/python3.10/site-packages/cachelib/__pycache__/serializers.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..be155d98e08a4d7cef8beff027bd976443f9049f GIT binary patch literal 3796 zcmai1&2JmW72nyNT`nm~qHV>o`{AtJIw{+PblS8BqbTf1sckfn0?Uq2UYOO+P+WP* zWoMRFNl-xnyU4ZoqJVYOzqHq$ax37QQP};xSy80Envve)yxBK9?`z(Bn=UR^EDZL+ zf8_F_W&N9o`N2Wl1Lz5mumnr2R`JVP%+Pkrd0+`!I4>>Xys%rmY=P#&1?@`y!fCmn zJy8N(GPDQU7iG|8Lzh5T!~*CAL;IksViEMB^s&Dz*rruq`W_Nk>k;AkQ3i1jp#K3R zEt0Bb3)XUkE%~l19Kkz~MtCoI%fm{k!ytz*d|BSDKmy-z_(lS^wP4n(!okx;>?1jN zYpHA<@5FksOD=aju_TsXx~-b1$>rlcm&9f4xgF z_|LjB*v7LU?q^bU!bk?4R0Uy@1VVPgVUh*HwgCP zBr%(&uglV45J%4vIoo$c6MrL^N)`>3lKm_hncYJzMUeKBQLsG<{un26bEGrbTMZ*9 zu-A>FZa~|0lZ1g;uLSaj>PmKE8XuAIQH(a*)MoKj?-!*p{*%s4ylW#Zbll9h_{mqG>vi zDkR>!LEX+vNxHLx<-D{Psy-%I<60&i<>1UiOZoVD^R(Gbd$OrR9d*NIq8uR~VzZ+jU zWeqy{M4*rnri#B_12Ogs#ShrNy=6`8O~er?J-P{XVF;tH1Eb8;b-s0u)WwX{RpgX9 zSIhXxIYqI$glVA`FVrGYVTmvc&sB+H_Q!zNq?^sEjJvh}R@f4$_4bTfbVNiUHytPZ z`6X7z<*7dp?0pA{Ia9@sym|Zod6T$j-YnE}3-w%cRSW$tia&J z>HuBnyTrJ2_3a8nP?Nr=gW8;#d)p?eFZ8FPm^k4J*JPBl&bUsD?-3#+o(``cVeJmB zUj;fNi+N@Ja_+a={jew7?Yz=%_fj!TXkKl%|1u1dqbH?yTclCDZA>>4BdGb-vAXeP?jVQfb{p_$!^C&)e;)TkHF~s>JNNq|MFEn#+oMcsMaEMk<(%Y{ zd6c;wlnW_A=vzQO^2ADsd;Fcv>~|bMzqVa@d)2qgH{5F_Mf?%q#9*NA0ZcreEQ&~^ zPQLtfYxBXQxvF$)?M%rr)yVLzQc7#AA96)8u6{!3r-W`0GNR9rQQ{*!pyc$LF060) zOwr1PVyX}E6k^pj=!m>KE94t{lI_g9r4Gf>jaqgEFklO5B_r zA6+2FjQIy;`4vw8)3Gc>osoqy=skdb2sH0D>SWNj*wWiAq0jIImt}Mx)YZ7HidkQf zror~OU_zF#mOm31mIWcm8ri12`vsM8n1qe3F zSMQrzJ-)w2O#zydAl=@TQATY}8idpkP-Ui8N0C$?2SZH@MYW+Gws-RPZS%r>x=?s(I2WQ_Uu_>H{%obV#?u;J}sV4msPi{Z zpP+qG^0`zxOoFr%-0jJ%d-ofXuw9t!o1>Pj7w>d4bUHzV<`8|(UYvDJLlt1Gr+rzp zSB6rbK!xwga&-@B9Q7*G#9tWEWF1r70&wJ&uc^;4Mf}j7Gwms>(yp%onkGKytio)z zg6ifK$q4n%w~_bHSlcAq_4?wud4tNW`hd_)LO&-I5V}o>Jem6&rqK(Ni__)D;M)Lb zl9|%w4f_UHv>E|U3<_f!x&btQyQW_X=9cmAEZj8ueyO&+@8IE933tfLFRzVI7Sgrt zxodR$f~+uQoxe$R`kBCUut=b(7Ejo&wa+s4!lFPsppPD=NY=VzE@STMA^O?E7Qe>& zA+YFTHb396 z@UW}@l)s<1tbfqU?Bn3&Lo`jDump>(U539r<_X{BjAV9fC(f>8#!g&I++CMhpIV|O z+-H_>rSsI@ofB-wYR$cX7Hhl33MY>eiNE!aLM`uwogTd`?Ld^Gr zhm8FYO)oLalE#!>F4(S(O|0Eoo!4z~T{zMeHR*O8;flFuHEixluggVUG@d!Tb#YEM zgeRK#J}(v^IVWz2MX`iY6QgCZg3-JKhqrEZJ#ksQ0b5pIJJZ9QH^mjqxiHzo!wzqWtJvYL0az9s*ES2S%KhV+-HPogiAh-sxXhTRMVG!rggLjGf8in z`El0S*Aw#*C{ZWMhsLtKLGDMn-^m8C@OxpuFH?Vz3Ru*W;lU6KVMX2xbAMkBp;|$u z5K{QbAkHI%oKNAX+YO`bc0W{MVvN_lEEcyhlrZzFC>bPvI!N}QIO}3ZnP~H^#8u~+ zlG@*s09YDLgj)51l{ybqt^r>xbFkp+ERpTmjfL!nuqz;&vO!*MKM@*3AuPQC)&LQh zcL|=!d@%5u%}Y~sUv{Eygk@Rk-%e!SyZz-KPSUWgW&UN`zk8xB>;6}5n|_pvs1xS! z+#pO?m3X8G5QPBhM}VdD@WsE7w=Zs@E9w*+2H#@wjK>{&Cg*8PRp=h2y^k)p4%yfm z^CMKB$QoM#AG3Z_Ko;1-LK^UGl7vIRC;ok`gCsi8{d1C@4w)5$|)By@?x z5(YVZf5@J~?}vQf*|DCpZFpnHGF9jBSvAo;X9`ul^vJlS7V%mv1{tV1MZt$qlkz1t zdZS`dbzWho+2idF&73|&O%08p*560>XRFHu6a1M6!$8%M^|AGoJ>g^ivGqm$iEX5O z462C0M+=5Bd%NN*DW!Ivl=|o%z}GLDzvE9B8ix}87i-^AVE*s?5#O0{u;moIy;-pR zVr~+71q%Sf1!$Q;ps^LBu644cf9E=FLN58%Xrto1Le)HwL8(Wyw@`n=Sl_{5y0&E- ztc#kd1%=(u`UpD%Ic->YR1!#uBI3*aFVkp3Lv@F1_@XHg*OV7hrqL>V{9EdN0xq@&hj1W(TX9* zt&&4qQPb4c(EW+XnIm$bj{V94t5640mhsW}445v`m_R7lL5TxpS&2h^>w!#xvbxiP z%pk!3=vW?W9FJ~(6C)_fQqAx&U0#7?nlL-0lagT!P;i>_bt)3}_6wSzOh=~BrG!{K zYn-6&o^oJE7Yx`140xvMSVZW z6r97X(}+1mYCf36fI;9pSfN+YxonlW$DX}_xN#Xjc3zIwUu$6nB=U#IG;y088J^zDAJ~`Qb(q>_-Q7bMb!=ZYLLoXm}a;k zyBo~oO^W)JitT1%F*iBgD#eHPqdH-%e+Prf*}#qvjAN9ceMc?lJhz`B=Ev+3d4zlD zbDR*w`}BlZ3DQg@=E^xebPGF5bK}p{3!?vw7wRTtKSDDh+XVOv%weOqD(1hUTHU~k ziW}qtrM+>3l6Q2Os^l2aIxTl_h3n-pCtBdt8oqO+t$oBk2N9neDjX|!hSwR$Vb*Vq zN+z{BXJ$8`@ ztT=!f72E0XKC%5Hy!5Mmq<9li zCwC-Fd#!WVXn`pfrxnA-MfWqvHHna6I9D3*(FAJ6Pt2Cs~nnohnFJBsU^Vj0z_IXxgxt`MDAhDuR6QFQM)V~YAQWP50uP-(!7 z(eiAbnbp9b!c)zm!Y7lG}~17wRi^qt>Xed8;n3JI9<3J~WRS zZ@j$jtzgE=oYByiTy@&rol)n~MPh_WcQkQs;Mc-s< W;cb<8-XY#Kf#q3E*2H19YX1i%-{R~5 literal 0 HcmV?d00001 diff --git a/froshims/.venv/lib/python3.10/site-packages/cachelib/__pycache__/uwsgi.cpython-310.pyc b/froshims/.venv/lib/python3.10/site-packages/cachelib/__pycache__/uwsgi.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7d75a8e4628dce527f760ea0d598a2ec5f12cc31 GIT binary patch literal 2988 zcmZ`*Uys{15+^B2mc3r@pKQ{my`t>(-#OT`lM9N&Es8d6&@1wi1Wk(qeyFO5^jcY2 z@{!VRZFmodCLd(|nD5a~q1Qh3?SOkbw9U;>w$@I2T8K*yXNJF-`ElfId%H#8p>O_M zIWYSX7wZp)#T|IfhcHMYX+j3oe>z~WW{ETA15Q!qB<|Q7c$9ojB$w_pB3;FwI)jF! zk4V@12|SSdU0OCj4UM`RMk57_ov*%r^!dFuznX2(xgrlhXBX_+Lng1vYv6rv?Yvjyt038z z9mvG%Wy=O_QxJZ`2)Q?&BxxneW)zOh%q2$X^4C+Z}&?tpX@fe*cDb7YJ6}m`M>o}E4i-*&P(?d{*LeQ{>+XHK7 zpFF%L;uX3ukF!(^v&u}sF-aJmpfg0BDkb5NVUb3t2j$GCOOQvC5MT?bhGCK9K^~7) zR^&$jRjq_#DHL%kjEb^Unw~|mw6!^@EKG7+XijVJk3m|VjFhmgED8r8nWDojmP z9B~#W$+Bhu9#df**O+?Z-X&OtX{*#~X9feRBM2)JoGE=WQ^m0eCzC@l%?gpF$yAKO zQyb(&XQ#2OO14Z$?XTugmBF75DIp~(cV5bW>Dn|z4qU9&_yKolJ15TLU&ADVx>GyQjl#V2cd+yAgISIV4NxH{L-k9E?D3R`B8v1}Y?8hMn5 zabkKJf+e%REuCdWBCQv=1Hj`<=Sm)m5cDyln|HyQeB5nnxRIpvgCI`hJP1mEl7u-B zXv-E)i%biTUNFXf_5a0QwA|U1 zudwgj#YWVt6XBewY3XT|7dnkFNvj7LgkK8Fnm@s0PM#odfH_atf}OKD)V?6PgB1sv zbHV3q0bhmZ^nTaXJ77^ZE$HA_rWl$a{sa*XPeEbKGJD{_@HAL0!{n9-RlR}k^WfzPvo2v&AB3CTR_h7P~-3tdZ zIVX@&2me2D7yO*h`3kpJA#PXMEhaL|l}09%t}MnAQ|<>TP}_cdSB-f|)3ua;V*~j7?mHvzCkK`4x=rbfBrM~HYxyW8?j-WSgty@%?eaJf#}?2blw7b$tt#n zegjN5N&^0EboyP~!DLmz@ZSsOH$j9_eg-CH7Y0T>xD-_w>}URBBMeZCu+OX>oYxm% z9~N~OK=uW_gv{3(~M>1=n6L3Z;utggWztJnFm zz?)cS(Ai*z`YtxPu~77zH3o~Ox?rQwe1Y<}VA#k9Y;>7MqG}HZdu6U*55;d2^w3ri zj5ApzIBy5RKZ-C}?lgh`8hH?C2MsMVojA=)2R^*EZIvv5)hFpx{{b~@$py?x|G}jC zp`o$m=o>g3;?Tq4GKqBndc`*L7trttr(TC~=!3st=VMOmmxEXLJ;tFgLO0~_`uMWe zukUPiTGt$F-}UttSeITtoxl$R{Q)kvZ7HGS_8!A;+v)>yP|GemnW>^g9KQ#!^gpb! T^br!uVAJ-APur|b51ju2{jlIh literal 0 HcmV?d00001 diff --git a/froshims/.venv/lib/python3.10/site-packages/cachelib/base.py b/froshims/.venv/lib/python3.10/site-packages/cachelib/base.py new file mode 100644 index 0000000..e6a6d7e --- /dev/null +++ b/froshims/.venv/lib/python3.10/site-packages/cachelib/base.py @@ -0,0 +1,185 @@ +import typing as _t + + +class BaseCache: + """Baseclass for the cache systems. All the cache systems implement this + API or a superset of it. + + :param default_timeout: the default timeout (in seconds) that is used if + no timeout is specified on :meth:`set`. A timeout + of 0 indicates that the cache never expires. + """ + + def __init__(self, default_timeout: int = 300): + self.default_timeout = default_timeout + + def _normalize_timeout(self, timeout: _t.Optional[int]) -> int: + if timeout is None: + timeout = self.default_timeout + return timeout + + def get(self, key: str) -> _t.Any: + """Look up key in the cache and return the value for it. + + :param key: the key to be looked up. + :returns: The value if it exists and is readable, else ``None``. + """ + return None + + def delete(self, key: str) -> bool: + """Delete `key` from the cache. + + :param key: the key to delete. + :returns: Whether the key existed and has been deleted. + :rtype: boolean + """ + return True + + def get_many(self, *keys: str) -> _t.List[_t.Any]: + """Returns a list of values for the given keys. + For each key an item in the list is created:: + + foo, bar = cache.get_many("foo", "bar") + + Has the same error handling as :meth:`get`. + + :param keys: The function accepts multiple keys as positional + arguments. + """ + return [self.get(k) for k in keys] + + def get_dict(self, *keys: str) -> _t.Dict[str, _t.Any]: + """Like :meth:`get_many` but return a dict:: + + d = cache.get_dict("foo", "bar") + foo = d["foo"] + bar = d["bar"] + + :param keys: The function accepts multiple keys as positional + arguments. + """ + return dict(zip(keys, self.get_many(*keys))) # noqa: B905 + + def set( + self, key: str, value: _t.Any, timeout: _t.Optional[int] = None + ) -> _t.Optional[bool]: + """Add a new key/value to the cache (overwrites value, if key already + exists in the cache). + + :param key: the key to set + :param value: the value for the key + :param timeout: the cache timeout for the key in seconds (if not + specified, it uses the default timeout). A timeout of + 0 indicates that the cache never expires. + :returns: ``True`` if key has been updated, ``False`` for backend + errors. Pickling errors, however, will raise a subclass of + ``pickle.PickleError``. + :rtype: boolean + """ + return True + + def add(self, key: str, value: _t.Any, timeout: _t.Optional[int] = None) -> bool: + """Works like :meth:`set` but does not overwrite the values of already + existing keys. + + :param key: the key to set + :param value: the value for the key + :param timeout: the cache timeout for the key in seconds (if not + specified, it uses the default timeout). A timeout of + 0 indicates that the cache never expires. + :returns: Same as :meth:`set`, but also ``False`` for already + existing keys. + :rtype: boolean + """ + return True + + def set_many( + self, mapping: _t.Dict[str, _t.Any], timeout: _t.Optional[int] = None + ) -> _t.List[_t.Any]: + """Sets multiple keys and values from a mapping. + + :param mapping: a mapping with the keys/values to set. + :param timeout: the cache timeout for the key in seconds (if not + specified, it uses the default timeout). A timeout of + 0 indicates that the cache never expires. + :returns: A list containing all keys successfully set + :rtype: boolean + """ + set_keys = [] + for key, value in mapping.items(): + if self.set(key, value, timeout): + set_keys.append(key) + return set_keys + + def delete_many(self, *keys: str) -> _t.List[_t.Any]: + """Deletes multiple keys at once. + + :param keys: The function accepts multiple keys as positional + arguments. + :returns: A list containing all successfully deleted keys + :rtype: boolean + """ + deleted_keys = [] + for key in keys: + if self.delete(key): + deleted_keys.append(key) + return deleted_keys + + def has(self, key: str) -> bool: + """Checks if a key exists in the cache without returning it. This is a + cheap operation that bypasses loading the actual data on the backend. + + :param key: the key to check + """ + raise NotImplementedError( + "%s doesn't have an efficient implementation of `has`. That " + "means it is impossible to check whether a key exists without " + "fully loading the key's data. Consider using `self.get` " + "explicitly if you don't care about performance." + ) + + def clear(self) -> bool: + """Clears the cache. Keep in mind that not all caches support + completely clearing the cache. + + :returns: Whether the cache has been cleared. + :rtype: boolean + """ + return True + + def inc(self, key: str, delta: int = 1) -> _t.Optional[int]: + """Increments the value of a key by `delta`. If the key does + not yet exist it is initialized with `delta`. + + For supporting caches this is an atomic operation. + + :param key: the key to increment. + :param delta: the delta to add. + :returns: The new value or ``None`` for backend errors. + """ + value = (self.get(key) or 0) + delta + return value if self.set(key, value) else None + + def dec(self, key: str, delta: int = 1) -> _t.Optional[int]: + """Decrements the value of a key by `delta`. If the key does + not yet exist it is initialized with `-delta`. + + For supporting caches this is an atomic operation. + + :param key: the key to increment. + :param delta: the delta to subtract. + :returns: The new value or `None` for backend errors. + """ + value = (self.get(key) or 0) - delta + return value if self.set(key, value) else None + + +class NullCache(BaseCache): + """A cache that doesn't cache. This can be useful for unit testing. + + :param default_timeout: a dummy parameter that is ignored but exists + for API compatibility with other caches. + """ + + def has(self, key: str) -> bool: + return False diff --git a/froshims/.venv/lib/python3.10/site-packages/cachelib/dynamodb.py b/froshims/.venv/lib/python3.10/site-packages/cachelib/dynamodb.py new file mode 100644 index 0000000..5f7a55b --- /dev/null +++ b/froshims/.venv/lib/python3.10/site-packages/cachelib/dynamodb.py @@ -0,0 +1,226 @@ +import datetime +import typing as _t + +from cachelib.base import BaseCache +from cachelib.serializers import DynamoDbSerializer + +CREATED_AT_FIELD = "created_at" +RESPONSE_FIELD = "response" + + +class DynamoDbCache(BaseCache): + """ + Implementation of cachelib.BaseCache that uses an AWS DynamoDb table + as the backend. + + Your server process will require dynamodb:GetItem and dynamodb:PutItem + IAM permissions on the cache table. + + Limitations: DynamoDB table items are limited to 400 KB in size. Since + this class stores cached items in a table, the max size of a cache entry + will be slightly less than 400 KB, since the cache key and expiration + time fields are also part of the item. + + :param table_name: The name of the DynamoDB table to use + :param default_timeout: Set the timeout in seconds after which cache entries + expire + :param key_field: The name of the hash_key attribute in the DynamoDb + table. This must be a string attribute. + :param expiration_time_field: The name of the table attribute to store the + expiration time in. This will be an int + attribute. The timestamp will be stored as + seconds past the epoch. If you configure + this as the TTL field, then DynamoDB will + automatically delete expired entries. + :param key_prefix: A prefix that should be added to all keys. + + """ + + serializer = DynamoDbSerializer() + + def __init__( + self, + table_name: _t.Optional[str] = "python-cache", + default_timeout: int = 300, + key_field: _t.Optional[str] = "cache_key", + expiration_time_field: _t.Optional[str] = "expiration_time", + key_prefix: _t.Optional[str] = None, + **kwargs: _t.Any + ): + super().__init__(default_timeout) + + try: + import boto3 # type: ignore + except ImportError as err: + raise RuntimeError("no boto3 module found") from err + + self._table_name = table_name + self._key_field = key_field + self._expiration_time_field = expiration_time_field + self.key_prefix = key_prefix or "" + self._dynamo = boto3.resource("dynamodb", **kwargs) + self._attr = boto3.dynamodb.conditions.Attr + + try: + self._table = self._dynamo.Table(table_name) + self._table.load() + # catch this exception (triggered if the table doesn't exist) + except Exception: + table = self._dynamo.create_table( + AttributeDefinitions=[ + {"AttributeName": key_field, "AttributeType": "S"} + ], + TableName=table_name, + KeySchema=[ + {"AttributeName": key_field, "KeyType": "HASH"}, + ], + BillingMode="PAY_PER_REQUEST", + ) + table.wait_until_exists() + dynamo = boto3.client("dynamodb", **kwargs) + dynamo.update_time_to_live( + TableName=table_name, + TimeToLiveSpecification={ + "Enabled": True, + "AttributeName": expiration_time_field, + }, + ) + self._table = self._dynamo.Table(table_name) + self._table.load() + + def _utcnow(self) -> _t.Any: + """Return a tz-aware UTC datetime representing the current time""" + return datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc) + + def _get_item(self, key: str, attributes: _t.Optional[list] = None) -> _t.Any: + """ + Get an item from the cache table, optionally limiting the returned + attributes. + + :param key: The cache key of the item to fetch + + :param attributes: An optional list of attributes to fetch. If not + given, all attributes are fetched. The + expiration_time field will always be added to the + list of fetched attributes. + :return: The table item for key if it exists and is not expired, else + None + """ + kwargs = {} + if attributes: + if self._expiration_time_field not in attributes: + attributes = list(attributes) + [self._expiration_time_field] + kwargs = dict(ProjectionExpression=",".join(attributes)) + + response = self._table.get_item(Key={self._key_field: key}, **kwargs) + cache_item = response.get("Item") + + if cache_item: + now = int(self._utcnow().timestamp()) + if cache_item.get(self._expiration_time_field, now + 100) > now: + return cache_item + + return None + + def get(self, key: str) -> _t.Any: + """ + Get a cache item + + :param key: The cache key of the item to fetch + :return: cache value if not expired, else None + """ + cache_item = self._get_item(self.key_prefix + key) + if cache_item: + response = cache_item[RESPONSE_FIELD] + value = self.serializer.loads(response) + return value + return None + + def delete(self, key: str) -> bool: + """ + Deletes an item from the cache. This is a no-op if the item doesn't + exist + + :param key: Key of the item to delete. + :return: True if the key existed and was deleted + """ + try: + self._table.delete_item( + Key={self._key_field: self.key_prefix + key}, + ConditionExpression=self._attr(self._key_field).exists(), + ) + return True + except self._dynamo.meta.client.exceptions.ConditionalCheckFailedException: + return False + + def _set( + self, + key: str, + value: _t.Any, + timeout: _t.Optional[int] = None, + overwrite: _t.Optional[bool] = True, + ) -> _t.Any: + """ + Store a cache item, with the option to not overwrite existing items + + :param key: Cache key to use + :param value: a serializable object + :param timeout: The timeout in seconds for the cached item, to override + the default + :param overwrite: If true, overwrite any existing cache item with key. + If false, the new value will only be stored if no + non-expired cache item exists with key. + :return: True if the new item was stored. + """ + timeout = self._normalize_timeout(timeout) + now = self._utcnow() + + kwargs = {} + if not overwrite: + # Cause the put to fail if a non-expired item with this key + # already exists + + cond = self._attr(self._key_field).not_exists() | self._attr( + self._expiration_time_field + ).lte(int(now.timestamp())) + kwargs = dict(ConditionExpression=cond) + + try: + dump = self.serializer.dumps(value) + item = { + self._key_field: key, + CREATED_AT_FIELD: now.isoformat(), + RESPONSE_FIELD: dump, + } + if timeout > 0: + expiration_time = now + datetime.timedelta(seconds=timeout) + item[self._expiration_time_field] = int(expiration_time.timestamp()) + self._table.put_item(Item=item, **kwargs) + return True + except Exception: + return False + + def set(self, key: str, value: _t.Any, timeout: _t.Optional[int] = None) -> _t.Any: + return self._set(self.key_prefix + key, value, timeout=timeout, overwrite=True) + + def add(self, key: str, value: _t.Any, timeout: _t.Optional[int] = None) -> _t.Any: + return self._set(self.key_prefix + key, value, timeout=timeout, overwrite=False) + + def has(self, key: str) -> bool: + return ( + self._get_item(self.key_prefix + key, [self._expiration_time_field]) + is not None + ) + + def clear(self) -> bool: + paginator = self._dynamo.meta.client.get_paginator("scan") + pages = paginator.paginate( + TableName=self._table_name, ProjectionExpression=self._key_field + ) + + with self._table.batch_writer() as batch: + for page in pages: + for item in page["Items"]: + batch.delete_item(Key=item) + + return True diff --git a/froshims/.venv/lib/python3.10/site-packages/cachelib/file.py b/froshims/.venv/lib/python3.10/site-packages/cachelib/file.py new file mode 100644 index 0000000..02a3b0e --- /dev/null +++ b/froshims/.venv/lib/python3.10/site-packages/cachelib/file.py @@ -0,0 +1,333 @@ +import errno +import logging +import os +import platform +import stat +import struct +import tempfile +import typing as _t +from contextlib import contextmanager +from hashlib import md5 +from pathlib import Path +from time import sleep +from time import time + +from cachelib.base import BaseCache +from cachelib.serializers import FileSystemSerializer + + +class FileSystemCache(BaseCache): + """A cache that stores the items on the file system. This cache depends + on being the only user of the `cache_dir`. Make absolutely sure that + nobody but this cache stores files there or otherwise the cache will + randomly delete files therein. + + :param cache_dir: the directory where cache files are stored. + :param threshold: the maximum number of items the cache stores before + it starts deleting some. A threshold value of 0 + indicates no threshold. + :param default_timeout: the default timeout that is used if no timeout is + specified on :meth:`~BaseCache.set`. A timeout of + 0 indicates that the cache never expires. + :param mode: the file mode wanted for the cache files, default 0600 + :param hash_method: Default hashlib.md5. The hash method used to + generate the filename for cached results. + """ + + #: used for temporary files by the FileSystemCache + _fs_transaction_suffix = ".__wz_cache" + #: keep amount of files in a cache element + _fs_count_file = "__wz_cache_count" + + serializer = FileSystemSerializer() + + def __init__( + self, + cache_dir: str, + threshold: int = 500, + default_timeout: int = 300, + mode: _t.Optional[int] = None, + hash_method: _t.Any = md5, + ): + BaseCache.__init__(self, default_timeout) + self._path = cache_dir + self._threshold = threshold + self._hash_method = hash_method + + # Mode set by user takes precedence. If no mode has + # been given, we need to set the correct default based + # on user platform. + self._mode = mode + if self._mode is None: + self._mode = self._get_compatible_platform_mode() + + try: + os.makedirs(self._path) + except OSError as ex: + if ex.errno != errno.EEXIST: + raise + + # If there are many files and a zero threshold, + # the list_dir can slow initialisation massively + if self._threshold != 0: + self._update_count(value=len(list(self._list_dir()))) + + def _get_compatible_platform_mode(self) -> int: + mode = 0o600 # nix systems + if platform.system() == "Windows": + mode = stat.S_IWRITE + return mode + + @property + def _file_count(self) -> int: + return self.get(self._fs_count_file) or 0 + + def _update_count( + self, delta: _t.Optional[int] = None, value: _t.Optional[int] = None + ) -> None: + # If we have no threshold, don't count files + if self._threshold == 0: + return + if delta: + new_count = self._file_count + delta + else: + new_count = value or 0 + self.set(self._fs_count_file, new_count, mgmt_element=True) + + def _normalize_timeout(self, timeout: _t.Optional[int]) -> int: + timeout = BaseCache._normalize_timeout(self, timeout) + if timeout != 0: + timeout = int(time()) + timeout + return int(timeout) + + def _is_mgmt(self, name: str) -> bool: + fshash = self._get_filename(self._fs_count_file).split(os.sep)[-1] + return name == fshash or name.endswith(self._fs_transaction_suffix) + + def _list_dir(self) -> _t.Generator[str, None, None]: + """return a list of (fully qualified) cache filenames""" + return ( + os.path.join(self._path, fn) + for fn in os.listdir(self._path) + if not self._is_mgmt(fn) + ) + + def _over_threshold(self) -> bool: + return self._threshold != 0 and self._file_count > self._threshold + + def _remove_expired(self, now: float) -> None: + for fname in self._list_dir(): + try: + with self._safe_stream_open(fname, "rb") as f: + expires = struct.unpack("I", f.read(4))[0] + if expires != 0 and expires < now: + os.remove(fname) + self._update_count(delta=-1) + except FileNotFoundError: + pass + except (OSError, EOFError, struct.error): + logging.warning( + "Exception raised while handling cache file '%s'", + fname, + exc_info=True, + ) + + def _remove_older(self) -> bool: + exp_fname_tuples = [] + for fname in self._list_dir(): + try: + with self._safe_stream_open(fname, "rb") as f: + timestamp = struct.unpack("I", f.read(4))[0] + exp_fname_tuples.append((timestamp, fname)) + except FileNotFoundError: + pass + except (OSError, EOFError, struct.error): + logging.warning( + "Exception raised while handling cache file '%s'", + fname, + exc_info=True, + ) + fname_sorted = ( + fname for _, fname in sorted(exp_fname_tuples, key=lambda item: item[0]) + ) + for fname in fname_sorted: + try: + os.remove(fname) + self._update_count(delta=-1) + except FileNotFoundError: + pass + except OSError: + logging.warning( + "Exception raised while handling cache file '%s'", + fname, + exc_info=True, + ) + return False + if not self._over_threshold(): + break + return True + + def _prune(self) -> None: + if self._over_threshold(): + now = time() + self._remove_expired(now) + # if still over threshold + if self._over_threshold(): + self._remove_older() + + def clear(self) -> bool: + for i, fname in enumerate(self._list_dir()): + try: + os.remove(fname) + except FileNotFoundError: + pass + except OSError: + logging.warning( + "Exception raised while handling cache file '%s'", + fname, + exc_info=True, + ) + self._update_count(delta=-i) + return False + self._update_count(value=0) + return True + + def _get_filename(self, key: str) -> str: + if isinstance(key, str): + bkey = key.encode("utf-8") # XXX unicode review + bkey_hash = self._hash_method(bkey).hexdigest() + else: + raise TypeError(f"Key must be a string, received type {type(key)}") + return os.path.join(self._path, bkey_hash) + + def get(self, key: str) -> _t.Any: + filename = self._get_filename(key) + try: + with self._safe_stream_open(filename, "rb") as f: + pickle_time = struct.unpack("I", f.read(4))[0] + if pickle_time == 0 or pickle_time >= time(): + return self.serializer.load(f) + except FileNotFoundError: + pass + except (OSError, EOFError, struct.error): + logging.warning( + "Exception raised while handling cache file '%s'", + filename, + exc_info=True, + ) + return None + + def add(self, key: str, value: _t.Any, timeout: _t.Optional[int] = None) -> bool: + filename = self._get_filename(key) + if not os.path.exists(filename): + return self.set(key, value, timeout) + return False + + def set( + self, + key: str, + value: _t.Any, + timeout: _t.Optional[int] = None, + mgmt_element: bool = False, + ) -> bool: + # Management elements have no timeout + if mgmt_element: + timeout = 0 + # Don't prune on management element update, to avoid loop + else: + self._prune() + + timeout = self._normalize_timeout(timeout) + filename = self._get_filename(key) + overwrite = os.path.isfile(filename) + + try: + fd, tmp = tempfile.mkstemp( + suffix=self._fs_transaction_suffix, dir=self._path + ) + with os.fdopen(fd, "wb") as f: + f.write(struct.pack("I", timeout)) + self.serializer.dump(value, f) + + self._run_safely(os.replace, tmp, filename) + self._run_safely(os.chmod, filename, self._mode) + + fsize = Path(filename).stat().st_size + except OSError: + logging.warning( + "Exception raised while handling cache file '%s'", + filename, + exc_info=True, + ) + return False + else: + # Management elements should not count towards threshold + if not overwrite and not mgmt_element: + self._update_count(delta=1) + return fsize > 0 # function should fail if file is empty + + def delete(self, key: str, mgmt_element: bool = False) -> bool: + try: + os.remove(self._get_filename(key)) + except FileNotFoundError: # if file doesn't exist we consider it deleted + return True + except OSError: + logging.warning("Exception raised while handling cache file", exc_info=True) + return False + else: + # Management elements should not count towards threshold + if not mgmt_element: + self._update_count(delta=-1) + return True + + def has(self, key: str) -> bool: + filename = self._get_filename(key) + try: + with self._safe_stream_open(filename, "rb") as f: + pickle_time = struct.unpack("I", f.read(4))[0] + if pickle_time == 0 or pickle_time >= time(): + return True + else: + return False + except FileNotFoundError: # if there is no file there is no key + return False + except (OSError, EOFError, struct.error): + logging.warning( + "Exception raised while handling cache file '%s'", + filename, + exc_info=True, + ) + return False + + def _run_safely(self, fn: _t.Callable, *args: _t.Any, **kwargs: _t.Any) -> _t.Any: + """On Windows os.replace, os.chmod and open can yield + permission errors if executed by two different processes.""" + if platform.system() == "Windows": + output = None + wait_step = 0.001 + max_sleep_time = 10.0 + total_sleep_time = 0.0 + + while total_sleep_time < max_sleep_time: + try: + output = fn(*args, **kwargs) + except PermissionError: + sleep(wait_step) + total_sleep_time += wait_step + wait_step *= 2 + else: + break + else: + output = fn(*args, **kwargs) + + return output + + @contextmanager + def _safe_stream_open(self, path: str, mode: str) -> _t.Generator: + fs = self._run_safely(open, path, mode) + if fs is None: + raise OSError + try: + yield fs + finally: + fs.close() diff --git a/froshims/.venv/lib/python3.10/site-packages/cachelib/memcached.py b/froshims/.venv/lib/python3.10/site-packages/cachelib/memcached.py new file mode 100644 index 0000000..7c2f70a --- /dev/null +++ b/froshims/.venv/lib/python3.10/site-packages/cachelib/memcached.py @@ -0,0 +1,196 @@ +import re +import typing as _t +from time import time + +from cachelib.base import BaseCache + + +_test_memcached_key = re.compile(r"[^\x00-\x21\xff]{1,250}$").match + + +class MemcachedCache(BaseCache): + """A cache that uses memcached as backend. + + The first argument can either be an object that resembles the API of a + :class:`memcache.Client` or a tuple/list of server addresses. In the + event that a tuple/list is passed, Werkzeug tries to import the best + available memcache library. + + This cache looks into the following packages/modules to find bindings for + memcached: + + - ``pylibmc`` + - ``google.appengine.api.memcached`` + - ``memcached`` + - ``libmc`` + + Implementation notes: This cache backend works around some limitations in + memcached to simplify the interface. For example unicode keys are encoded + to utf-8 on the fly. Methods such as :meth:`~BaseCache.get_dict` return + the keys in the same format as passed. Furthermore all get methods + silently ignore key errors to not cause problems when untrusted user data + is passed to the get methods which is often the case in web applications. + + :param servers: a list or tuple of server addresses or alternatively + a :class:`memcache.Client` or a compatible client. + :param default_timeout: the default timeout that is used if no timeout is + specified on :meth:`~BaseCache.set`. A timeout of + 0 indicates that the cache never expires. + :param key_prefix: a prefix that is added before all keys. This makes it + possible to use the same memcached server for different + applications. Keep in mind that + :meth:`~BaseCache.clear` will also clear keys with a + different prefix. + """ + + def __init__( + self, + servers: _t.Any = None, + default_timeout: int = 300, + key_prefix: _t.Optional[str] = None, + ): + BaseCache.__init__(self, default_timeout) + if servers is None or isinstance(servers, (list, tuple)): + if servers is None: + servers = ["127.0.0.1:11211"] + self._client = self.import_preferred_memcache_lib(servers) + if self._client is None: + raise RuntimeError("no memcache module found") + else: + # NOTE: servers is actually an already initialized memcache + # client. + self._client = servers + + self.key_prefix = key_prefix + + def _normalize_key(self, key: str) -> str: + if self.key_prefix: + key = self.key_prefix + key + return key + + def _normalize_timeout(self, timeout: _t.Optional[int]) -> int: + timeout = BaseCache._normalize_timeout(self, timeout) + if timeout > 0: + timeout = int(time()) + timeout + return timeout + + def get(self, key: str) -> _t.Any: + key = self._normalize_key(key) + # memcached doesn't support keys longer than that. Because often + # checks for so long keys can occur because it's tested from user + # submitted data etc we fail silently for getting. + if _test_memcached_key(key): + return self._client.get(key) + + def get_dict(self, *keys: str) -> _t.Dict[str, _t.Any]: + key_mapping = {} + for key in keys: + encoded_key = self._normalize_key(key) + if _test_memcached_key(key): + key_mapping[encoded_key] = key + _keys = list(key_mapping) + d = rv = self._client.get_multi(_keys) # type: _t.Dict[str, _t.Any] + if self.key_prefix: + rv = {} + for key, value in d.items(): + rv[key_mapping[key]] = value + if len(rv) < len(keys): + for key in keys: + if key not in rv: + rv[key] = None + return rv + + def add(self, key: str, value: _t.Any, timeout: _t.Optional[int] = None) -> bool: + key = self._normalize_key(key) + timeout = self._normalize_timeout(timeout) + return bool(self._client.add(key, value, timeout)) + + def set( + self, key: str, value: _t.Any, timeout: _t.Optional[int] = None + ) -> _t.Optional[bool]: + key = self._normalize_key(key) + timeout = self._normalize_timeout(timeout) + return bool(self._client.set(key, value, timeout)) + + def get_many(self, *keys: str) -> _t.List[_t.Any]: + d = self.get_dict(*keys) + return [d[key] for key in keys] + + def set_many( + self, mapping: _t.Dict[str, _t.Any], timeout: _t.Optional[int] = None + ) -> _t.List[_t.Any]: + new_mapping = {} + for key, value in mapping.items(): + key = self._normalize_key(key) + new_mapping[key] = value + + timeout = self._normalize_timeout(timeout) + failed_keys = self._client.set_multi( + new_mapping, timeout + ) # type: _t.List[_t.Any] + k_normkey = zip(mapping.keys(), new_mapping.keys()) # noqa: B905 + return [k for k, nkey in k_normkey if nkey not in failed_keys] + + def delete(self, key: str) -> bool: + key = self._normalize_key(key) + if _test_memcached_key(key): + return bool(self._client.delete(key)) + return False + + def delete_many(self, *keys: str) -> _t.List[_t.Any]: + new_keys = [] + for key in keys: + key = self._normalize_key(key) + if _test_memcached_key(key): + new_keys.append(key) + self._client.delete_multi(new_keys) + return [k for k in new_keys if not self.has(k)] + + def has(self, key: str) -> bool: + key = self._normalize_key(key) + if _test_memcached_key(key): + return bool(self._client.append(key, "")) + return False + + def clear(self) -> bool: + return bool(self._client.flush_all()) + + def inc(self, key: str, delta: int = 1) -> _t.Optional[int]: + key = self._normalize_key(key) + value = (self._client.get(key) or 0) + delta + return value if self.set(key, value) else None + + def dec(self, key: str, delta: int = 1) -> _t.Optional[int]: + key = self._normalize_key(key) + value = (self._client.get(key) or 0) - delta + return value if self.set(key, value) else None + + def import_preferred_memcache_lib(self, servers: _t.Any) -> _t.Any: + """Returns an initialized memcache client. Used by the constructor.""" + try: + import pylibmc # type: ignore + except ImportError: + pass + else: + return pylibmc.Client(servers) + + try: + from google.appengine.api import memcache # type: ignore + except ImportError: + pass + else: + return memcache.Client() + + try: + import memcache # type: ignore + except ImportError: + pass + else: + return memcache.Client(servers) + + try: + import libmc # type: ignore + except ImportError: + pass + else: + return libmc.Client(servers) diff --git a/froshims/.venv/lib/python3.10/site-packages/cachelib/mongodb.py b/froshims/.venv/lib/python3.10/site-packages/cachelib/mongodb.py new file mode 100644 index 0000000..ac18642 --- /dev/null +++ b/froshims/.venv/lib/python3.10/site-packages/cachelib/mongodb.py @@ -0,0 +1,202 @@ +import datetime +import logging +import typing as _t + +from cachelib.base import BaseCache +from cachelib.serializers import BaseSerializer + + +class MongoDbCache(BaseCache): + """ + Implementation of cachelib.BaseCache that uses mongodb collection + as the backend. + + Limitations: maximum MongoDB document size is 16mb + + :param client: mongodb client or connection string + :param db: mongodb database name + :param collection: mongodb collection name + :param default_timeout: Set the timeout in seconds after which cache entries + expire + :param key_prefix: A prefix that should be added to all keys. + + """ + + serializer = BaseSerializer() + + def __init__( + self, + client: _t.Any = None, + db: _t.Optional[str] = "cache-db", + collection: _t.Optional[str] = "cache-collection", + default_timeout: int = 300, + key_prefix: _t.Optional[str] = None, + **kwargs: _t.Any + ): + super().__init__(default_timeout) + try: + import pymongo # type: ignore + except ImportError: + logging.warning("no pymongo module found") + + if client is None or isinstance(client, str): + client = pymongo.MongoClient(host=client) + self.client = client[db][collection] + index_info = self.client.index_information() + all_keys = { + subkey[0] for value in index_info.values() for subkey in value["key"] + } + if "id" not in all_keys: + self.client.create_index("id", unique=True) + self.key_prefix = key_prefix or "" + self.collection = collection + + def _utcnow(self) -> _t.Any: + """Return a tz-aware UTC datetime representing the current time""" + return datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc) + + def _expire_records(self) -> _t.Any: + res = self.client.delete_many({"expiration": {"$lte": self._utcnow()}}) + return res + + def get(self, key: str) -> _t.Any: + """ + Get a cache item + + :param key: The cache key of the item to fetch + :return: cache value if not expired, else None + """ + self._expire_records() + record = self.client.find_one({"id": self.key_prefix + key}) + value = None + if record: + value = self.serializer.loads(record["val"]) + return value + + def delete(self, key: str) -> bool: + """ + Deletes an item from the cache. This is a no-op if the item doesn't + exist + + :param key: Key of the item to delete. + :return: True if the key existed and was deleted + """ + res = self.client.delete_one({"id": self.key_prefix + key}) + deleted = bool(res.deleted_count > 0) + return deleted + + def _set( + self, + key: str, + value: _t.Any, + timeout: _t.Optional[int] = None, + overwrite: _t.Optional[bool] = True, + ) -> _t.Any: + """ + Store a cache item, with the option to not overwrite existing items + + :param key: Cache key to use + :param value: a serializable object + :param timeout: The timeout in seconds for the cached item, to override + the default + :param overwrite: If true, overwrite any existing cache item with key. + If false, the new value will only be stored if no + non-expired cache item exists with key. + :return: True if the new item was stored. + """ + timeout = self._normalize_timeout(timeout) + now = self._utcnow() + + if not overwrite: + # fail if a non-expired item with this key + # already exists + if self.has(key): + return False + + dump = self.serializer.dumps(value) + record = {"id": self.key_prefix + key, "val": dump} + + if timeout > 0: + record["expiration"] = now + datetime.timedelta(seconds=timeout) + self.client.update_one({"id": self.key_prefix + key}, {"$set": record}, True) + return True + + def set(self, key: str, value: _t.Any, timeout: _t.Optional[int] = None) -> _t.Any: + self._expire_records() + return self._set(key, value, timeout=timeout, overwrite=True) + + def set_many( + self, mapping: _t.Dict[str, _t.Any], timeout: _t.Optional[int] = None + ) -> _t.List[_t.Any]: + self._expire_records() + from pymongo import UpdateOne + + operations = [] + now = self._utcnow() + timeout = self._normalize_timeout(timeout) + for key, val in mapping.items(): + dump = self.serializer.dumps(val) + + record = {"id": self.key_prefix + key, "val": dump} + + if timeout > 0: + record["expiration"] = now + datetime.timedelta(seconds=timeout) + operations.append( + UpdateOne({"id": self.key_prefix + key}, {"$set": record}, upsert=True), + ) + + result = self.client.bulk_write(operations) + keys = list(mapping.keys()) + if result.bulk_api_result["nUpserted"] != len(keys): + query = self.client.find( + {"id": {"$in": [self.key_prefix + key for key in keys]}} + ) + keys = [] + for item in query: + keys.append(item["id"]) + return keys + + def get_many(self, *keys: str) -> _t.List[_t.Any]: + results = self.get_dict(*keys) + values = [] + for key in keys: + values.append(results.get(key, None)) + return values + + def get_dict(self, *keys: str) -> _t.Dict[str, _t.Any]: + self._expire_records() + query = self.client.find( + {"id": {"$in": [self.key_prefix + key for key in keys]}} + ) + results = dict.fromkeys(keys, None) + for item in query: + value = self.serializer.loads(item["val"]) + results[item["id"][len(self.key_prefix) :]] = value + return results + + def add(self, key: str, value: _t.Any, timeout: _t.Optional[int] = None) -> _t.Any: + self._expire_records() + return self._set(key, value, timeout=timeout, overwrite=False) + + def has(self, key: str) -> bool: + self._expire_records() + record = self.get(key) + return record is not None + + def delete_many(self, *keys: str) -> _t.List[_t.Any]: + self._expire_records() + res = list(keys) + filter = {"id": {"$in": [self.key_prefix + key for key in keys]}} + result = self.client.delete_many(filter) + + if result.deleted_count != len(keys): + existing_keys = [ + item["id"][len(self.key_prefix) :] for item in self.client.find(filter) + ] + res = [item for item in keys if item not in existing_keys] + + return res + + def clear(self) -> bool: + self.client.drop() + return True diff --git a/froshims/.venv/lib/python3.10/site-packages/cachelib/py.typed b/froshims/.venv/lib/python3.10/site-packages/cachelib/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/froshims/.venv/lib/python3.10/site-packages/cachelib/redis.py b/froshims/.venv/lib/python3.10/site-packages/cachelib/redis.py new file mode 100644 index 0000000..8e38115 --- /dev/null +++ b/froshims/.venv/lib/python3.10/site-packages/cachelib/redis.py @@ -0,0 +1,159 @@ +import typing as _t + +from cachelib.base import BaseCache +from cachelib.serializers import RedisSerializer + + +class RedisCache(BaseCache): + """Uses the Redis key-value store as a cache backend. + + The first argument can be either a string denoting address of the Redis + server or an object resembling an instance of a redis.Redis class. + + Note: Python Redis API already takes care of encoding unicode strings on + the fly. + + :param host: address of the Redis server or an object which API is + compatible with the official Python Redis client (redis-py). + :param port: port number on which Redis server listens for connections. + :param password: password authentication for the Redis server. + :param db: db (zero-based numeric index) on Redis Server to connect. + :param default_timeout: the default timeout that is used if no timeout is + specified on :meth:`~BaseCache.set`. A timeout of + 0 indicates that the cache never expires. + :param key_prefix: A prefix that should be added to all keys. + + Any additional keyword arguments will be passed to ``redis.Redis``. + """ + + _read_client: _t.Any = None + _write_client: _t.Any = None + serializer = RedisSerializer() + + def __init__( + self, + host: _t.Any = "localhost", + port: int = 6379, + password: _t.Optional[str] = None, + db: int = 0, + default_timeout: int = 300, + key_prefix: _t.Optional[_t.Union[str, _t.Callable[[], str]]] = None, + **kwargs: _t.Any, + ): + BaseCache.__init__(self, default_timeout) + if host is None: + raise ValueError("RedisCache host parameter may not be None") + if isinstance(host, str): + try: + import redis + except ImportError as err: + raise RuntimeError("no redis module found") from err + if kwargs.get("decode_responses", None): + raise ValueError("decode_responses is not supported by RedisCache.") + self._write_client = self._read_client = redis.Redis( + host=host, port=port, password=password, db=db, **kwargs + ) + else: + self._read_client = self._write_client = host + self.key_prefix = key_prefix or "" + + def _get_prefix(self) -> str: + return ( + self.key_prefix if isinstance(self.key_prefix, str) else self.key_prefix() + ) + + def _normalize_timeout(self, timeout: _t.Optional[int]) -> int: + """Normalize timeout by setting it to default of 300 if + not defined (None) or -1 if explicitly set to zero. + + :param timeout: timeout to normalize. + """ + timeout = BaseCache._normalize_timeout(self, timeout) + if timeout == 0: + timeout = -1 + return timeout + + def get(self, key: str) -> _t.Any: + return self.serializer.loads( + self._read_client.get(f"{self._get_prefix()}{key}") + ) + + def get_many(self, *keys: str) -> _t.List[_t.Any]: + if self.key_prefix: + prefixed_keys = [f"{self._get_prefix()}{key}" for key in keys] + else: + prefixed_keys = list(keys) + return [self.serializer.loads(x) for x in self._read_client.mget(prefixed_keys)] + + def set(self, key: str, value: _t.Any, timeout: _t.Optional[int] = None) -> _t.Any: + timeout = self._normalize_timeout(timeout) + dump = self.serializer.dumps(value) + if timeout == -1: + result = self._write_client.set( + name=f"{self._get_prefix()}{key}", value=dump + ) + else: + result = self._write_client.setex( + name=f"{self._get_prefix()}{key}", value=dump, time=timeout + ) + return result + + def add(self, key: str, value: _t.Any, timeout: _t.Optional[int] = None) -> _t.Any: + timeout = self._normalize_timeout(timeout) + dump = self.serializer.dumps(value) + created = self._write_client.setnx( + name=f"{self._get_prefix()}{key}", value=dump + ) + # handle case where timeout is explicitly set to zero + if created and timeout != -1: + self._write_client.expire(name=f"{self._get_prefix()}{key}", time=timeout) + return created + + def set_many( + self, mapping: _t.Dict[str, _t.Any], timeout: _t.Optional[int] = None + ) -> _t.List[_t.Any]: + timeout = self._normalize_timeout(timeout) + # Use transaction=False to batch without calling redis MULTI + # which is not supported by twemproxy + pipe = self._write_client.pipeline(transaction=False) + + for key, value in mapping.items(): + dump = self.serializer.dumps(value) + if timeout == -1: + pipe.set(name=f"{self._get_prefix()}{key}", value=dump) + else: + pipe.setex(name=f"{self._get_prefix()}{key}", value=dump, time=timeout) + results = pipe.execute() + return [k for k, was_set in zip(mapping.keys(), results) if was_set] + + def delete(self, key: str) -> bool: + return bool(self._write_client.delete(f"{self._get_prefix()}{key}")) + + def delete_many(self, *keys: str) -> _t.List[_t.Any]: + if not keys: + return [] + if self.key_prefix: + prefixed_keys = [f"{self._get_prefix()}{key}" for key in keys] + else: + prefixed_keys = [k for k in keys] + self._write_client.delete(*prefixed_keys) + return [k for k in prefixed_keys if not self.has(k)] + + def has(self, key: str) -> bool: + return bool(self._read_client.exists(f"{self._get_prefix()}{key}")) + + def clear(self) -> bool: + status = 0 + if self.key_prefix: + keys = self._read_client.keys(self._get_prefix() + "*") + if keys: + status = self._write_client.delete(*keys) + else: + status = self._write_client.flushdb() + return bool(status) + + def inc(self, key: str, delta: int = 1) -> _t.Any: + return self._write_client.incr(name=f"{self._get_prefix()}{key}", amount=delta) + + def dec(self, key: str, delta: int = 1) -> _t.Any: + return self._write_client.incr(name=f"{self._get_prefix()}{key}", amount=-delta) diff --git a/froshims/.venv/lib/python3.10/site-packages/cachelib/serializers.py b/froshims/.venv/lib/python3.10/site-packages/cachelib/serializers.py new file mode 100644 index 0000000..370e4fd --- /dev/null +++ b/froshims/.venv/lib/python3.10/site-packages/cachelib/serializers.py @@ -0,0 +1,112 @@ +import logging +import pickle +import typing as _t + + +class BaseSerializer: + """This is the base interface for all default serializers. + + BaseSerializer.load and BaseSerializer.dump will + default to pickle.load and pickle.dump. This is currently + used only by FileSystemCache which dumps/loads to/from a file stream. + """ + + def _warn(self, e: pickle.PickleError) -> None: + logging.warning( + f"An exception has been raised during a pickling operation: {e}" + ) + + def dump( + self, value: int, f: _t.IO, protocol: int = pickle.HIGHEST_PROTOCOL + ) -> None: + try: + pickle.dump(value, f, protocol) + except (pickle.PickleError, pickle.PicklingError) as e: + self._warn(e) + + def load(self, f: _t.BinaryIO) -> _t.Any: + try: + data = pickle.load(f) + except pickle.PickleError as e: + self._warn(e) + return None + else: + return data + + """BaseSerializer.loads and BaseSerializer.dumps + work on top of pickle.loads and pickle.dumps. Dumping/loading + strings and byte strings is the default for most cache types. + """ + + def dumps(self, value: _t.Any, protocol: int = pickle.HIGHEST_PROTOCOL) -> bytes: + try: + serialized = pickle.dumps(value, protocol) + except (pickle.PickleError, pickle.PicklingError) as e: + self._warn(e) + return serialized + + def loads(self, bvalue: bytes) -> _t.Any: + try: + data = pickle.loads(bvalue) + except pickle.PickleError as e: + self._warn(e) + return None + else: + return data + + +"""Default serializers for each cache type. + +The following classes can be used to further customize +serialiation behaviour. Alternatively, any serializer can be +overriden in order to use a custom serializer with a different +strategy altogether. +""" + + +class UWSGISerializer(BaseSerializer): + """Default serializer for UWSGICache.""" + + +class SimpleSerializer(BaseSerializer): + """Default serializer for SimpleCache.""" + + +class FileSystemSerializer(BaseSerializer): + """Default serializer for FileSystemCache.""" + + +class RedisSerializer(BaseSerializer): + """Default serializer for RedisCache.""" + + def dumps(self, value: _t.Any, protocol: int = pickle.HIGHEST_PROTOCOL) -> bytes: + """Dumps an object into a string for redis, using pickle by default.""" + return b"!" + pickle.dumps(value, protocol) + + def loads(self, value: _t.Optional[bytes]) -> _t.Any: + """The reversal of :meth:`dump_object`. This might be called with + None. + """ + if value is None: + return None + if value.startswith(b"!"): + try: + return pickle.loads(value[1:]) + except pickle.PickleError: + return None + try: + return int(value) + except ValueError: + # before 0.8 we did not have serialization. Still support that. + return value + + +class DynamoDbSerializer(RedisSerializer): + """Default serializer for DynamoDbCache.""" + + def loads(self, value: _t.Any) -> _t.Any: + """The reversal of :meth:`dump_object`. This might be called with + None. + """ + value = value.value + return super().loads(value) diff --git a/froshims/.venv/lib/python3.10/site-packages/cachelib/simple.py b/froshims/.venv/lib/python3.10/site-packages/cachelib/simple.py new file mode 100644 index 0000000..b1b713b --- /dev/null +++ b/froshims/.venv/lib/python3.10/site-packages/cachelib/simple.py @@ -0,0 +1,100 @@ +import typing as _t +from time import time + +from cachelib.base import BaseCache +from cachelib.serializers import SimpleSerializer + + +class SimpleCache(BaseCache): + """Simple memory cache for single process environments. This class exists + mainly for the development server and is not 100% thread safe. It tries + to use as many atomic operations as possible and no locks for simplicity + but it could happen under heavy load that keys are added multiple times. + + :param threshold: the maximum number of items the cache stores before + it starts deleting some. + :param default_timeout: the default timeout that is used if no timeout is + specified on :meth:`~BaseCache.set`. A timeout of + 0 indicates that the cache never expires. + """ + + serializer = SimpleSerializer() + + def __init__( + self, + threshold: int = 500, + default_timeout: int = 300, + ): + BaseCache.__init__(self, default_timeout) + self._cache: _t.Dict[str, _t.Any] = {} + self._threshold = threshold or 500 # threshold = 0 + + def _over_threshold(self) -> bool: + return len(self._cache) > self._threshold + + def _remove_expired(self, now: float) -> None: + toremove = [k for k, (expires, _) in self._cache.items() if expires < now] + for k in toremove: + self._cache.pop(k, None) + + def _remove_older(self) -> None: + k_ordered = ( + k for k, v in sorted(self._cache.items(), key=lambda item: item[1][0]) + ) + for k in k_ordered: + self._cache.pop(k, None) + if not self._over_threshold(): + break + + def _prune(self) -> None: + if self._over_threshold(): + now = time() + self._remove_expired(now) + # remove older items if still over threshold + if self._over_threshold(): + self._remove_older() + + def _normalize_timeout(self, timeout: _t.Optional[int]) -> int: + timeout = BaseCache._normalize_timeout(self, timeout) + if timeout > 0: + timeout = int(time()) + timeout + return timeout + + def get(self, key: str) -> _t.Any: + try: + expires, value = self._cache[key] + if expires == 0 or expires > time(): + return self.serializer.loads(value) + except KeyError: + return None + + def set( + self, key: str, value: _t.Any, timeout: _t.Optional[int] = None + ) -> _t.Optional[bool]: + expires = self._normalize_timeout(timeout) + self._prune() + self._cache[key] = (expires, self.serializer.dumps(value)) + return True + + def add(self, key: str, value: _t.Any, timeout: _t.Optional[int] = None) -> bool: + expires = self._normalize_timeout(timeout) + self._prune() + item = (expires, self.serializer.dumps(value)) + if key in self._cache: + return False + self._cache.setdefault(key, item) + return True + + def delete(self, key: str) -> bool: + return self._cache.pop(key, None) is not None + + def has(self, key: str) -> bool: + try: + expires, value = self._cache[key] + return bool(expires == 0 or expires > time()) + except KeyError: + return False + + def clear(self) -> bool: + self._cache.clear() + return not bool(self._cache) diff --git a/froshims/.venv/lib/python3.10/site-packages/cachelib/uwsgi.py b/froshims/.venv/lib/python3.10/site-packages/cachelib/uwsgi.py new file mode 100644 index 0000000..9d2600c --- /dev/null +++ b/froshims/.venv/lib/python3.10/site-packages/cachelib/uwsgi.py @@ -0,0 +1,83 @@ +import platform +import typing as _t + +from cachelib.base import BaseCache +from cachelib.serializers import UWSGISerializer + + +class UWSGICache(BaseCache): + """Implements the cache using uWSGI's caching framework. + + .. note:: + This class cannot be used when running under PyPy, because the uWSGI + API implementation for PyPy is lacking the needed functionality. + + :param default_timeout: The default timeout in seconds. + :param cache: The name of the caching instance to connect to, for + example: mycache@localhost:3031, defaults to an empty string, which + means uWSGI will cache in the local instance. If the cache is in the + same instance as the werkzeug app, you only have to provide the name of + the cache. + """ + + serializer = UWSGISerializer() + + def __init__( + self, + default_timeout: int = 300, + cache: str = "", + ): + BaseCache.__init__(self, default_timeout) + + if platform.python_implementation() == "PyPy": + raise RuntimeError( + "uWSGI caching does not work under PyPy, see " + "the docs for more details." + ) + + try: + import uwsgi # type: ignore + + self._uwsgi = uwsgi + except ImportError as err: + raise RuntimeError( + "uWSGI could not be imported, are you running under uWSGI?" + ) from err + + self.cache = cache + + def get(self, key: str) -> _t.Any: + rv = self._uwsgi.cache_get(key, self.cache) + if rv is None: + return + return self.serializer.loads(rv) + + def delete(self, key: str) -> bool: + return bool(self._uwsgi.cache_del(key, self.cache)) + + def set( + self, key: str, value: _t.Any, timeout: _t.Optional[int] = None + ) -> _t.Optional[bool]: + result = self._uwsgi.cache_update( + key, + self.serializer.dumps(value), + self._normalize_timeout(timeout), + self.cache, + ) # type: bool + return result + + def add(self, key: str, value: _t.Any, timeout: _t.Optional[int] = None) -> bool: + return bool( + self._uwsgi.cache_set( + key, + self.serializer.dumps(value), + self._normalize_timeout(timeout), + self.cache, + ) + ) + + def clear(self) -> bool: + return bool(self._uwsgi.cache_clear(self.cache)) + + def has(self, key: str) -> bool: + return self._uwsgi.cache_exists(key, self.cache) is not None diff --git a/froshims/.venv/lib/python3.10/site-packages/flask_session-0.6.0.dist-info/INSTALLER b/froshims/.venv/lib/python3.10/site-packages/flask_session-0.6.0.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/froshims/.venv/lib/python3.10/site-packages/flask_session-0.6.0.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/froshims/.venv/lib/python3.10/site-packages/flask_session-0.6.0.dist-info/LICENSE.rst b/froshims/.venv/lib/python3.10/site-packages/flask_session-0.6.0.dist-info/LICENSE.rst new file mode 100644 index 0000000..6a65079 --- /dev/null +++ b/froshims/.venv/lib/python3.10/site-packages/flask_session-0.6.0.dist-info/LICENSE.rst @@ -0,0 +1,28 @@ +Copyright 2014 Pallets Community Ecosystem + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/froshims/.venv/lib/python3.10/site-packages/flask_session-0.6.0.dist-info/METADATA b/froshims/.venv/lib/python3.10/site-packages/flask_session-0.6.0.dist-info/METADATA new file mode 100644 index 0000000..738c1b2 --- /dev/null +++ b/froshims/.venv/lib/python3.10/site-packages/flask_session-0.6.0.dist-info/METADATA @@ -0,0 +1,61 @@ +Metadata-Version: 2.1 +Name: Flask-Session +Version: 0.6.0 +Summary: Server-side session support for Flask +Author-email: Shipeng Feng +Maintainer-email: Pallets Community Ecosystem +Requires-Python: >=3.7 +Description-Content-Type: text/x-rst +Classifier: Development Status :: 4 - Beta +Classifier: Environment :: Web Environment +Classifier: Framework :: Flask +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: BSD License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Topic :: Internet :: WWW/HTTP :: Session +Classifier: Topic :: Internet :: WWW/HTTP :: WSGI +Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Application +Classifier: Topic :: Software Development :: Libraries :: Application Frameworks +Requires-Dist: flask>=2.2 +Requires-Dist: cachelib +Project-URL: Changes, https://flask-session.readthedocs.io/changes.html +Project-URL: Chat, https://discord.gg/pallets +Project-URL: Documentation, https://flask-session.readthedocs.io +Project-URL: Issue Tracker, https://github.com/pallets-eco/flask-session/issues/ +Project-URL: Source Code, https://github.com/pallets-eco/flask-session/ + +Flask-Session +============= + +Flask-Session is an extension for Flask that adds support for server-side sessions to +your application. + + +.. image:: https://github.com/pallets-eco/flask-session/actions/workflows/test.yaml/badge.svg?branch=development + :target: https://github.com/pallets-eco/flask-session/actions/workflows/test.yaml?query=workflow%3ACI+branch%3Adeveloment + :alt: Tests + +.. image:: https://readthedocs.org/projects/flask-session/badge/?version=stable&style=flat + :target: https://flask-session.readthedocs.io + :alt: docs + +.. image:: https://img.shields.io/github/license/pallets-eco/flask-session + :target: ./LICENSE + :alt: BSD-3 Clause License + +.. image:: https://img.shields.io/pypi/v/flask-session.svg? + :target: https://pypi.org/project/flask-session + :alt: PyPI + +.. image:: https://img.shields.io/badge/dynamic/json?query=info.requires_python&label=python&url=https%3A%2F%2Fpypi.org%2Fpypi%2Fflask-session%2Fjson + :target: https://pypi.org/project/Flask-Session/ + :alt: PyPI - Python Version + +.. image:: https://img.shields.io/github/v/release/pallets-eco/flask-session?include_prereleases&label=latest-prerelease + :target: https://github.com/pallets-eco/flask-session/releases + :alt: pre-release + +.. image:: https://codecov.io/gh/pallets-eco/flask-session/branch/master/graph/badge.svg?token=yenl5fzxxr + :target: https://codecov.io/gh/pallets-eco/flask-session + :alt: codecov diff --git a/froshims/.venv/lib/python3.10/site-packages/flask_session-0.6.0.dist-info/RECORD b/froshims/.venv/lib/python3.10/site-packages/flask_session-0.6.0.dist-info/RECORD new file mode 100644 index 0000000..99678ff --- /dev/null +++ b/froshims/.venv/lib/python3.10/site-packages/flask_session-0.6.0.dist-info/RECORD @@ -0,0 +1,10 @@ +flask_session-0.6.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +flask_session-0.6.0.dist-info/LICENSE.rst,sha256=avK7glmtsxOGN0YcECHYPdjG2EMHdV_HnTtZP0uD4RE,1495 +flask_session-0.6.0.dist-info/METADATA,sha256=tF1yWEoeJTuiyERanugn5n5Ad8gJopzRLN8v5pdx8zg,2665 +flask_session-0.6.0.dist-info/RECORD,, +flask_session-0.6.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +flask_session-0.6.0.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81 +flask_session/__init__.py,sha256=TlU8TXAiVYnP1jDI5NUD5Jy4FHZdH3E1kvhOFCxxla8,4553 +flask_session/__pycache__/__init__.cpython-310.pyc,, +flask_session/__pycache__/sessions.cpython-310.pyc,, +flask_session/sessions.py,sha256=EdobpyuF0pK0d-iNKF6j9LaZVSYO64Jxqbx7uTC7Gww,25596 diff --git a/froshims/.venv/lib/python3.10/site-packages/flask_session-0.6.0.dist-info/REQUESTED b/froshims/.venv/lib/python3.10/site-packages/flask_session-0.6.0.dist-info/REQUESTED new file mode 100644 index 0000000..e69de29 diff --git a/froshims/.venv/lib/python3.10/site-packages/flask_session-0.6.0.dist-info/WHEEL b/froshims/.venv/lib/python3.10/site-packages/flask_session-0.6.0.dist-info/WHEEL new file mode 100644 index 0000000..3b5e64b --- /dev/null +++ b/froshims/.venv/lib/python3.10/site-packages/flask_session-0.6.0.dist-info/WHEEL @@ -0,0 +1,4 @@ +Wheel-Version: 1.0 +Generator: flit 3.9.0 +Root-Is-Purelib: true +Tag: py3-none-any diff --git a/froshims/.venv/lib/python3.10/site-packages/flask_session/__init__.py b/froshims/.venv/lib/python3.10/site-packages/flask_session/__init__.py new file mode 100644 index 0000000..60fb9fc --- /dev/null +++ b/froshims/.venv/lib/python3.10/site-packages/flask_session/__init__.py @@ -0,0 +1,134 @@ +import os + +from .sessions import ( + FileSystemSessionInterface, + MemcachedSessionInterface, + MongoDBSessionInterface, + NullSessionInterface, + RedisSessionInterface, + SqlAlchemySessionInterface, +) + +__version__ = "0.6.0" + + +class Session: + """This class is used to add Server-side Session to one or more Flask + applications. + + There are two usage modes. One is initialize the instance with a very + specific Flask application:: + + app = Flask(__name__) + Session(app) + + The second possibility is to create the object once and configure the + application later:: + + sess = Session() + + def create_app(): + app = Flask(__name__) + sess.init_app(app) + return app + + By default Flask-Session will use :class:`NullSessionInterface`, you + really should configurate your app to use a different SessionInterface. + + .. note:: + + You can not use ``Session`` instance directly, what ``Session`` does + is just change the :attr:`~flask.Flask.session_interface` attribute on + your Flask applications. + """ + + def __init__(self, app=None): + self.app = app + if app is not None: + self.init_app(app) + + def init_app(self, app): + """This is used to set up session for your app object. + + :param app: the Flask app object with proper configuration. + """ + app.session_interface = self._get_interface(app) + + def _get_interface(self, app): + config = app.config.copy() + + # Flask-session specific settings + config.setdefault("SESSION_TYPE", "null") + config.setdefault("SESSION_PERMANENT", True) + config.setdefault("SESSION_USE_SIGNER", False) + config.setdefault("SESSION_KEY_PREFIX", "session:") + config.setdefault("SESSION_ID_LENGTH", 32) + + # Redis settings + config.setdefault("SESSION_REDIS", None) + + # Memcached settings + config.setdefault("SESSION_MEMCACHED", None) + + # Filesystem settings + config.setdefault( + "SESSION_FILE_DIR", os.path.join(os.getcwd(), "flask_session") + ) + config.setdefault("SESSION_FILE_THRESHOLD", 500) + config.setdefault("SESSION_FILE_MODE", 384) + + # MongoDB settings + config.setdefault("SESSION_MONGODB", None) + config.setdefault("SESSION_MONGODB_DB", "flask_session") + config.setdefault("SESSION_MONGODB_COLLECT", "sessions") + + # SQLAlchemy settings + config.setdefault("SESSION_SQLALCHEMY", None) + config.setdefault("SESSION_SQLALCHEMY_TABLE", "sessions") + config.setdefault("SESSION_SQLALCHEMY_SEQUENCE", None) + config.setdefault("SESSION_SQLALCHEMY_SCHEMA", None) + config.setdefault("SESSION_SQLALCHEMY_BIND_KEY", None) + + common_params = { + "key_prefix": config["SESSION_KEY_PREFIX"], + "use_signer": config["SESSION_USE_SIGNER"], + "permanent": config["SESSION_PERMANENT"], + "sid_length": config["SESSION_ID_LENGTH"], + } + + if config["SESSION_TYPE"] == "redis": + session_interface = RedisSessionInterface( + config["SESSION_REDIS"], **common_params + ) + elif config["SESSION_TYPE"] == "memcached": + session_interface = MemcachedSessionInterface( + config["SESSION_MEMCACHED"], **common_params + ) + elif config["SESSION_TYPE"] == "filesystem": + session_interface = FileSystemSessionInterface( + config["SESSION_FILE_DIR"], + config["SESSION_FILE_THRESHOLD"], + config["SESSION_FILE_MODE"], + **common_params, + ) + elif config["SESSION_TYPE"] == "mongodb": + session_interface = MongoDBSessionInterface( + config["SESSION_MONGODB"], + config["SESSION_MONGODB_DB"], + config["SESSION_MONGODB_COLLECT"], + **common_params, + ) + elif config["SESSION_TYPE"] == "sqlalchemy": + session_interface = SqlAlchemySessionInterface( + app, + config["SESSION_SQLALCHEMY"], + config["SESSION_SQLALCHEMY_TABLE"], + config["SESSION_SQLALCHEMY_SEQUENCE"], + config["SESSION_SQLALCHEMY_SCHEMA"], + config["SESSION_SQLALCHEMY_BIND_KEY"], + **common_params, + ) + else: + session_interface = NullSessionInterface() + + return session_interface diff --git a/froshims/.venv/lib/python3.10/site-packages/flask_session/__pycache__/__init__.cpython-310.pyc b/froshims/.venv/lib/python3.10/site-packages/flask_session/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5cdafae4abe9af5e968be56055d702cc825c54d3 GIT binary patch literal 3344 zcmaJEO>Z1YwZG=mGmh@je4*gtSK6q8)q&C|y#^Ud|FsIRQREvtu15xQ77>3y7<0p*8$^VTZHMsn z9`PO9nGk$o>1Hnsj>68?g@tSVnde==Uf(C!Wf#yJgD2jm2MYZ8yK5(fd(F?9_Z(qt z{>eh|UBD7$q+(^HVRfYA3f8glLLC{g%cBA=;qnV*RKyy%UFD@jh1~-g!->mG$FmtT zVK8F^n^9=m7@Gq^pAvePxtPFMMno)y0Wm{r`XMFe4)8s$n()}uspmR&1Yu*%szePZ z1W0Unqh}#VwvPyKVZxfGxd%c(og283YkTeq&`bamu*eP^Vm@=DiD{dlelA6^DRJDf z>%Q9sllQ0=4dJtH3r_s)g z6{~>yKkf($A-O4HDA}cB0-ZjTL^B!)IkCT6bD`0mc~KmsyNO88}K-uBU6qtcvuOrlMDh`P z6wXY?4g_9GIXFlxJvdm@2zDuC#hZU(KAYH)nWw=aVOc04oyRj4na;!xpaMmmwe2XP z?So&AMfo;m5jRCFp?Hy-Zu$dLP`QUQa4QTlaneJ{k}pH3c@g!#qvf`!7>ixH4KFhR zoT{e^0*IBFN|#TSm+Gl{rb2^K2J(HS_8S`4Az{3nruCJ^4Mw~%T>=5G6nX{z-u$dJ z34PLHHUqC(4*S8q)|iHD;`*%Bd`g0+Ezdn{P3O@h3_fjs?_P_!5xG0HoyV{VSxfqj z5|3L5e1;N2&FP$%)5%>>!^!}PYFS-YjkhN&Ne-GR$@_g>0Y2~!JUs&skt*A*cJgc4 zYjRzN5y3=eG&AwOk$P;GufZAX-sK2B{bWjvLVH} z5i&%0m(dZ47HQ_I7cx)lV80A>41_zh2H^YfGLhAl!Xg}LqRxe*07N2sGWFlU33I9* zL%PqD=h~_E652G@&NNk#V`$dd7|y6{3{5*5S8z2OFJUDc*K*@}q#qkE3z7b^c&6ax zvxSB$cqQXl#cSF4YL5Gw;7BaGj&Ee+n>m_WIsAGKzmXf?PRF>k4*ubSgt~;4bp?Ox z_r`M_@G6Wyc&yRiMJ1Rmtt(^IRZjIksJ~I48>hxV@Cup_f##1<8D_Qj(tHFoe~KzV zQbgO-Xx8!gUEZB)kpW#q-*t z+E+^KOXK4{uMMohpu5*c!$%J-ZUk^U;HxR_p|#)J>|6cekguhx(a~{h?PbV%;!!^)Z>FDJ5g~7rY8*8e;BIKq#+3pYiPgPBvbJI}-9pa1>2=fr<+r zulVVWkypoXv1IaM$xFVtE#gC9dE(jeg|AWP#rR6W4JU*qT!p@v9MoJ388@aj2sVyG zH{eBRUd}U2OORh`2p~=vD*6o-4Tly1GzxTCjKz^bKLx<+PUwSbBzq{MSH*%jNpcNt z4W>VYm;Dw%RjuhYHTmf^-O$(I43M}OpRE@4qAGu1*Gd|UjJE*a8XwpGnwx!C`@5Kl zpYdPKD1L2h)c)}f=4$OcZ=T>~*<5h&< zk~#C>?#F8gJ(<~FvQk0_hYms%J}lyXPKD38ILL^|ZeM`~F=Il0Suxb=2jxPoLp)Bgv4 CE0S#h literal 0 HcmV?d00001 diff --git a/froshims/.venv/lib/python3.10/site-packages/flask_session/__pycache__/sessions.cpython-310.pyc b/froshims/.venv/lib/python3.10/site-packages/flask_session/__pycache__/sessions.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3857ea97a99386e46b5e76d654ff2850aa5f0a4a GIT binary patch literal 18135 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 bool: + return bool(dict(self)) and self.keys() != {"_permanent"} + + def __init__(self, initial=None, sid=None, permanent=None): + def on_update(self): + self.modified = True + + CallbackDict.__init__(self, initial, on_update) + self.sid = sid + if permanent: + self.permanent = permanent + self.modified = False + + +class RedisSession(ServerSideSession): + pass + + +class MemcachedSession(ServerSideSession): + pass + + +class FileSystemSession(ServerSideSession): + pass + + +class MongoDBSession(ServerSideSession): + pass + + +class SqlAlchemySession(ServerSideSession): + pass + + +class SessionInterface(FlaskSessionInterface): + def _generate_sid(self, session_id_length): + return secrets.token_urlsafe(session_id_length) + + def __get_signer(self, app): + if not hasattr(app, "secret_key") or not app.secret_key: + raise KeyError("SECRET_KEY must be set when SESSION_USE_SIGNER=True") + return Signer(app.secret_key, salt="flask-session", key_derivation="hmac") + + def _unsign(self, app, sid): + signer = self.__get_signer(app) + sid_as_bytes = signer.unsign(sid) + sid = sid_as_bytes.decode() + return sid + + def _sign(self, app, sid): + signer = self.__get_signer(app) + sid_as_bytes = want_bytes(sid) + return signer.sign(sid_as_bytes).decode("utf-8") + + +class NullSessionInterface(SessionInterface): + """Used to open a :class:`flask.sessions.NullSession` instance. + + If you do not configure a different ``SESSION_TYPE``, this will be used to + generate nicer error messages. Will allow read-only access to the empty + session but fail on setting. + """ + + def open_session(self, app, request): + return None + + +class ServerSideSessionInterface(SessionInterface, ABC): + """Used to open a :class:`flask.sessions.ServerSideSessionInterface` instance.""" + + def __init__(self, db, key_prefix, use_signer=False, permanent=True, sid_length=32): + self.db = db + self.key_prefix = key_prefix + self.use_signer = use_signer + self.permanent = permanent + self.sid_length = sid_length + self.has_same_site_capability = hasattr(self, "get_cookie_samesite") + + def set_cookie_to_response(self, app, session, response, expires): + session_id = self._sign(app, session.sid) if self.use_signer else session.sid + domain = self.get_cookie_domain(app) + path = self.get_cookie_path(app) + httponly = self.get_cookie_httponly(app) + secure = self.get_cookie_secure(app) + samesite = None + if self.has_same_site_capability: + samesite = self.get_cookie_samesite(app) + + response.set_cookie( + app.config["SESSION_COOKIE_NAME"], + session_id, + expires=expires, + httponly=httponly, + domain=domain, + path=path, + secure=secure, + samesite=samesite, + ) + + def open_session(self, app, request): + sid = request.cookies.get(app.config["SESSION_COOKIE_NAME"]) + if not sid: + sid = self._generate_sid(self.sid_length) + return self.session_class(sid=sid, permanent=self.permanent) + if self.use_signer: + try: + sid = self._unsign(app, sid) + except BadSignature: + sid = self._generate_sid(self.sid_length) + return self.session_class(sid=sid, permanent=self.permanent) + return self.fetch_session(sid) + + def fetch_session(self, sid): + raise NotImplementedError() + + +class RedisSessionInterface(ServerSideSessionInterface): + """Uses the Redis key-value store as a session backend. (`redis-py` required) + + :param redis: A ``redis.Redis`` instance. + :param key_prefix: A prefix that is added to all Redis store keys. + :param use_signer: Whether to sign the session id cookie or not. + :param permanent: Whether to use permanent session or not. + :param sid_length: The length of the generated session id in bytes. + + .. versionadded:: 0.6 + The `sid_length` parameter was added. + + .. versionadded:: 0.2 + The `use_signer` parameter was added. + """ + + serializer = pickle + session_class = RedisSession + + def __init__(self, redis, key_prefix, use_signer, permanent, sid_length): + if redis is None: + from redis import Redis + + redis = Redis() + self.redis = redis + super().__init__(redis, key_prefix, use_signer, permanent, sid_length) + + def fetch_session(self, sid): + # Get the saved session (value) from the database + prefixed_session_id = self.key_prefix + sid + value = self.redis.get(prefixed_session_id) + + # If the saved session still exists and hasn't auto-expired, load the session data from the document + if value is not None: + try: + session_data = self.serializer.loads(value) + return self.session_class(session_data, sid=sid) + except pickle.UnpicklingError: + return self.session_class(sid=sid, permanent=self.permanent) + + # If the saved session does not exist, create a new session + return self.session_class(sid=sid, permanent=self.permanent) + + def save_session(self, app, session, response): + if not self.should_set_cookie(app, session): + return + + # Get the domain and path for the cookie from the app config + domain = self.get_cookie_domain(app) + path = self.get_cookie_path(app) + + # If the session is empty, do not save it to the database or set a cookie + if not session: + # If the session was deleted (empty and modified), delete the saved session from the database and tell the client to delete the cookie + if session.modified: + self.redis.delete(self.key_prefix + session.sid) + response.delete_cookie( + app.config["SESSION_COOKIE_NAME"], domain=domain, path=path + ) + return + + # Get the new expiration time for the session + expiration_datetime = self.get_expiration_time(app, session) + + # Serialize the session data + serialized_session_data = self.serializer.dumps(dict(session)) + + # Update existing or create new session in the database + self.redis.set( + name=self.key_prefix + session.sid, + value=serialized_session_data, + ex=total_seconds(app.permanent_session_lifetime), + ) + + # Set the browser cookie + self.set_cookie_to_response(app, session, response, expiration_datetime) + + +class MemcachedSessionInterface(ServerSideSessionInterface): + """A Session interface that uses memcached as backend. (`pylibmc` or `python-memcached` or `pymemcache` required) + + :param client: A ``memcache.Client`` instance. + :param key_prefix: A prefix that is added to all Memcached store keys. + :param use_signer: Whether to sign the session id cookie or not. + :param permanent: Whether to use permanent session or not. + :param sid_length: The length of the generated session id in bytes. + + .. versionadded:: 0.6 + The `sid_length` parameter was added. + + .. versionadded:: 0.2 + The `use_signer` parameter was added. + + """ + + serializer = pickle + session_class = MemcachedSession + + def __init__(self, client, key_prefix, use_signer, permanent, sid_length): + if client is None: + client = self._get_preferred_memcache_client() + self.client = client + super().__init__(client, key_prefix, use_signer, permanent, sid_length) + + def _get_preferred_memcache_client(self): + clients = [ + ("pylibmc", ["127.0.0.1:11211"]), + ("memcache", ["127.0.0.1:11211"]), + ("pymemcache.client.base", "127.0.0.1:11211"), + ] + + for module_name, server in clients: + try: + module = __import__(module_name) + ClientClass = module.Client + return ClientClass(server) + except ImportError: + continue + + raise ImportError("No memcache module found") + + def _get_memcache_timeout(self, timeout): + """ + Memcached deals with long (> 30 days) timeouts in a special + way. Call this function to obtain a safe value for your timeout. + """ + if timeout > 2592000: # 60*60*24*30, 30 days + # Switch to absolute timestamps. + timeout += int(time.time()) + return timeout + + def fetch_session(self, sid): + # Get the saved session (item) from the database + prefixed_session_id = self.key_prefix + sid + item = self.client.get(prefixed_session_id) + + # If the saved session still exists and hasn't auto-expired, load the session data from the document + if item is not None: + try: + session_data = self.serializer.loads(want_bytes(item)) + return self.session_class(session_data, sid=sid) + except pickle.UnpicklingError: + return self.session_class(sid=sid, permanent=self.permanent) + + # If the saved session does not exist, create a new session + return self.session_class(sid=sid, permanent=self.permanent) + + def save_session(self, app, session, response): + if not self.should_set_cookie(app, session): + return + + # Get the domain and path for the cookie from the app config + domain = self.get_cookie_domain(app) + path = self.get_cookie_path(app) + + # Generate a prefixed session id from the session id as a storage key + prefixed_session_id = self.key_prefix + session.sid + + # If the session is empty, do not save it to the database or set a cookie + if not session: + # If the session was deleted (empty and modified), delete the saved session from the database and tell the client to delete the cookie + if session.modified: + self.client.delete(prefixed_session_id) + response.delete_cookie( + app.config["SESSION_COOKIE_NAME"], domain=domain, path=path + ) + return + + # Get the new expiration time for the session + expiration_datetime = self.get_expiration_time(app, session) + + # Serialize the session data + serialized_session_data = self.serializer.dumps(dict(session)) + + # Update existing or create new session in the database + self.client.set( + prefixed_session_id, + serialized_session_data, + self._get_memcache_timeout(total_seconds(app.permanent_session_lifetime)), + ) + + # Set the browser cookie + self.set_cookie_to_response(app, session, response, expiration_datetime) + + +class FileSystemSessionInterface(ServerSideSessionInterface): + """Uses the :class:`cachelib.file.FileSystemCache` as a session backend. + + :param cache_dir: the directory where session files are stored. + :param threshold: the maximum number of items the session stores before it + starts deleting some. + :param mode: the file mode wanted for the session files, default 0600 + :param key_prefix: A prefix that is added to FileSystemCache store keys. + :param use_signer: Whether to sign the session id cookie or not. + :param permanent: Whether to use permanent session or not. + :param sid_length: The length of the generated session id in bytes. + + .. versionadded:: 0.6 + The `sid_length` parameter was added. + + .. versionadded:: 0.2 + The `use_signer` parameter was added. + """ + + session_class = FileSystemSession + + def __init__( + self, + cache_dir, + threshold, + mode, + key_prefix, + use_signer, + permanent, + sid_length, + ): + from cachelib.file import FileSystemCache + + self.cache = FileSystemCache(cache_dir, threshold=threshold, mode=mode) + super().__init__(self.cache, key_prefix, use_signer, permanent, sid_length) + + def fetch_session(self, sid): + # Get the saved session (item) from the database + prefixed_session_id = self.key_prefix + sid + item = self.cache.get(prefixed_session_id) + + # If the saved session exists and has not auto-expired, load the session data from the item + if item is not None: + return self.session_class(item, sid=sid) + + # If the saved session does not exist, create a new session + return self.session_class(sid=sid, permanent=self.permanent) + + def save_session(self, app, session, response): + if not self.should_set_cookie(app, session): + return + + # Get the domain and path for the cookie from the app config + domain = self.get_cookie_domain(app) + path = self.get_cookie_path(app) + + # Generate a prefixed session id from the session id as a storage key + prefixed_session_id = self.key_prefix + session.sid + + # If the session is empty, do not save it to the database or set a cookie + if not session: + # If the session was deleted (empty and modified), delete the saved session from the database and tell the client to delete the cookie + if session.modified: + self.cache.delete(prefixed_session_id) + response.delete_cookie( + app.config["SESSION_COOKIE_NAME"], domain=domain, path=path + ) + return + + # Get the new expiration time for the session + expiration_datetime = self.get_expiration_time(app, session) + + # Serialize the session data (or just cast into dictionary in this case) + session_data = dict(session) + + # Update existing or create new session in the database + self.cache.set( + prefixed_session_id, + session_data, + total_seconds(app.permanent_session_lifetime), + ) + + # Set the browser cookie + self.set_cookie_to_response(app, session, response, expiration_datetime) + + +class MongoDBSessionInterface(ServerSideSessionInterface): + """A Session interface that uses mongodb as backend. (`pymongo` required) + + :param client: A ``pymongo.MongoClient`` instance. + :param db: The database you want to use. + :param collection: The collection you want to use. + :param key_prefix: A prefix that is added to all MongoDB store keys. + :param use_signer: Whether to sign the session id cookie or not. + :param permanent: Whether to use permanent session or not. + :param sid_length: The length of the generated session id in bytes. + + .. versionadded:: 0.6 + The `sid_length` parameter was added. + + .. versionadded:: 0.2 + The `use_signer` parameter was added. + """ + + serializer = pickle + session_class = MongoDBSession + + def __init__( + self, + client, + db, + collection, + key_prefix, + use_signer, + permanent, + sid_length, + ): + import pymongo + + if client is None: + client = pymongo.MongoClient() + + self.client = client + self.store = client[db][collection] + self.use_deprecated_method = int(pymongo.version.split(".")[0]) < 4 + super().__init__(self.store, key_prefix, use_signer, permanent, sid_length) + + def fetch_session(self, sid): + # Get the saved session (document) from the database + prefixed_session_id = self.key_prefix + sid + document = self.store.find_one({"id": prefixed_session_id}) + + # If the expiration time is less than or equal to the current time (expired), delete the document + if document is not None: + expiration_datetime = document.get("expiration") + # tz_aware mongodb fix + expiration_datetime_tz_aware = expiration_datetime.replace( + tzinfo=timezone.utc + ) + now_datetime_tz_aware = datetime.utcnow().replace(tzinfo=timezone.utc) + if expiration_datetime is None or ( + expiration_datetime_tz_aware <= now_datetime_tz_aware + ): + if self.use_deprecated_method: + self.store.remove({"id": prefixed_session_id}) + else: + self.store.delete_one({"id": prefixed_session_id}) + document = None + + # If the saved session still exists after checking for expiration, load the session data from the document + if document is not None: + try: + session_data = self.serializer.loads(want_bytes(document["val"])) + return self.session_class(session_data, sid=sid) + except pickle.UnpicklingError: + return self.session_class(sid=sid, permanent=self.permanent) + + # If the saved session does not exist, create a new session + return self.session_class(sid=sid, permanent=self.permanent) + + def save_session(self, app, session, response): + if not self.should_set_cookie(app, session): + return + + # Get the domain and path for the cookie from the app config + domain = self.get_cookie_domain(app) + path = self.get_cookie_path(app) + + # Generate a prefixed session id from the session id as a storage key + prefixed_session_id = self.key_prefix + session.sid + + # If the session is empty, do not save it to the database or set a cookie + if not session: + # If the session was deleted (empty and modified), delete the saved session from the database and tell the client to delete the cookie + if session.modified: + if self.use_deprecated_method: + self.store.remove({"id": prefixed_session_id}) + else: + self.store.delete_one({"id": prefixed_session_id}) + response.delete_cookie( + app.config["SESSION_COOKIE_NAME"], domain=domain, path=path + ) + return + + # Get the new expiration time for the session + expiration_datetime = self.get_expiration_time(app, session) + + # Serialize the session data + serialized_session_data = self.serializer.dumps(dict(session)) + + # Update existing or create new session in the database + if self.use_deprecated_method: + self.store.update( + {"id": prefixed_session_id}, + { + "id": prefixed_session_id, + "val": serialized_session_data, + "expiration": expiration_datetime, + }, + True, + ) + else: + self.store.update_one( + {"id": prefixed_session_id}, + { + "$set": { + "id": prefixed_session_id, + "val": serialized_session_data, + "expiration": expiration_datetime, + } + }, + True, + ) + + # Set the browser cookie + self.set_cookie_to_response(app, session, response, expiration_datetime) + + +class SqlAlchemySessionInterface(ServerSideSessionInterface): + """Uses the Flask-SQLAlchemy from a flask app as a session backend. + + :param app: A Flask app instance. + :param db: A Flask-SQLAlchemy instance. + :param table: The table name you want to use. + :param key_prefix: A prefix that is added to all store keys. + :param use_signer: Whether to sign the session id cookie or not. + :param permanent: Whether to use permanent session or not. + :param sid_length: The length of the generated session id in bytes. + :param sequence: The sequence to use for the primary key if needed. + :param schema: The db schema to use + :param bind_key: The db bind key to use + + .. versionadded:: 0.6 + The `sid_length`, `sequence`, `schema` and `bind_key` parameters were added. + + .. versionadded:: 0.2 + The `use_signer` parameter was added. + """ + + serializer = pickle + session_class = SqlAlchemySession + + def __init__( + self, + app, + db, + table, + sequence, + schema, + bind_key, + key_prefix, + use_signer, + permanent, + sid_length, + ): + if db is None: + from flask_sqlalchemy import SQLAlchemy + + db = SQLAlchemy(app) + + self.db = db + self.sequence = sequence + self.schema = schema + self.bind_key = bind_key + super().__init__(self.db, key_prefix, use_signer, permanent, sid_length) + + # Create the Session database model + class Session(self.db.Model): + __tablename__ = table + + if self.schema is not None: + __table_args__ = {"schema": self.schema, "keep_existing": True} + else: + __table_args__ = {"keep_existing": True} + + if self.bind_key is not None: + __bind_key__ = self.bind_key + + # Set the database columns, support for id sequences + if sequence: + id = self.db.Column( + self.db.Integer, self.db.Sequence(sequence), primary_key=True + ) + else: + id = self.db.Column(self.db.Integer, primary_key=True) + session_id = self.db.Column(self.db.String(255), unique=True) + data = self.db.Column(self.db.LargeBinary) + expiry = self.db.Column(self.db.DateTime) + + def __init__(self, session_id, data, expiry): + self.session_id = session_id + self.data = data + self.expiry = expiry + + def __repr__(self): + return "" % self.data + + with app.app_context(): + self.db.create_all() + + self.sql_session_model = Session + + def fetch_session(self, sid): + # Get the saved session (record) from the database + store_id = self.key_prefix + sid + record = self.sql_session_model.query.filter_by(session_id=store_id).first() + + # If the expiration time is less than or equal to the current time (expired), delete the document + if record is not None: + expiration_datetime = record.expiry + if expiration_datetime is None or expiration_datetime <= datetime.utcnow(): + self.db.session.delete(record) + self.db.session.commit() + record = None + + # If the saved session still exists after checking for expiration, load the session data from the document + if record: + try: + session_data = self.serializer.loads(want_bytes(record.data)) + return self.session_class(session_data, sid=sid) + except pickle.UnpicklingError: + return self.session_class(sid=sid, permanent=self.permanent) + return self.session_class(sid=sid, permanent=self.permanent) + + def save_session(self, app, session, response): + if not self.should_set_cookie(app, session): + return + + # Get the domain and path for the cookie from the app + domain = self.get_cookie_domain(app) + path = self.get_cookie_path(app) + + # Generate a prefixed session id + prefixed_session_id = self.key_prefix + session.sid + + # If the session is empty, do not save it to the database or set a cookie + if not session: + # If the session was deleted (empty and modified), delete the saved session from the database and tell the client to delete the cookie + if session.modified: + self.sql_session_model.query.filter_by( + session_id=prefixed_session_id + ).delete() + self.db.session.commit() + response.delete_cookie( + app.config["SESSION_COOKIE_NAME"], domain=domain, path=path + ) + return + + # Serialize session data + serialized_session_data = self.serializer.dumps(dict(session)) + + # Get the new expiration time for the session + expiration_datetime = self.get_expiration_time(app, session) + + # Update existing or create new session in the database + record = self.sql_session_model.query.filter_by( + session_id=prefixed_session_id + ).first() + if record: + record.data = serialized_session_data + record.expiry = expiration_datetime + else: + record = self.sql_session_model( + session_id=prefixed_session_id, + data=serialized_session_data, + expiry=expiration_datetime, + ) + self.db.session.add(record) + self.db.session.commit() + + # Set the browser cookie + self.set_cookie_to_response(app, session, response, expiration_datetime) diff --git a/src9/login/__pycache__/app.cpython-310.pyc b/src9/login/__pycache__/app.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0aa23b31f88a6763afe1c1f1201e8b0eb591dee0 GIT binary patch literal 987 zcmZuvJ8#rL5Z;Gh=X>Tr2q8KuQ^YhBkxmGNE{Fmrim$DV_srU=2m|gQYDXit$SnNO zlZ@0fqqUPcwVS!v#7O1Ue&%DuF%Pyl>oCeY=OJ?@h`HADCW!mIv&E3p*>#zJhgrZn z-wLbnj1i_W5*rkK@v`-Cr&o-oq=$U=)a?@QyG7fawhZpiXift^m;2!=+DZB(np>xde1rZ0}ksUQu zl*7mbDVNbLPLcAMWA?~RgduIYh_2}yT5y(W_S&x6nKz;q2C3yMOw*SBU&<}{ zq4~?Tg-mrBfC@Eh}D# z7RUb}cx{tsP)eFH1qAv(1t*Dy+MSzQCrraVCWdfZyl>R^bJW8D4QXi90XuTQ^9T4B3MkTS literal 0 HcmV?d00001 diff --git a/src9/login/flask_session/2029240f6d1128be89ddc32729463129 b/src9/login/flask_session/2029240f6d1128be89ddc32729463129 new file mode 100644 index 0000000000000000000000000000000000000000..8b04914a5e6ad4858df0019a6abe09326d3863de GIT binary patch literal 9 QcmZQzU|?uq^=8xq00XW800000 literal 0 HcmV?d00001 diff --git a/src9/login/flask_session/f8a6ee825797497a3461c2ce9264fd05 b/src9/login/flask_session/f8a6ee825797497a3461c2ce9264fd05 new file mode 100644 index 0000000000000000000000000000000000000000..d3504f669fcbc1b42c6d719781de4e4e67868c50 GIT binary patch literal 35 mcmbR6S0b%}b*cyh1k_IHVaZF(O`X!i7Mxg|oRK)CSPuZb