diff --git a/.flake8 b/.flake8 index 45e695a..b570817 100644 --- a/.flake8 +++ b/.flake8 @@ -7,4 +7,4 @@ application-import-names = meta_memcache,tests import-order-style = cryptography per-file-ignores = __init__.py:F401 - tests/*:S101,S403 \ No newline at end of file + tests/*:S101,S403 diff --git a/README.md b/README.md index dd75f31..fad3879 100644 --- a/README.md +++ b/README.md @@ -97,14 +97,26 @@ will be gone or present, since they are stored in the same server). Note this is also risky, if you place all keys of a user in the same server, and the server goes down, the user life will be miserable. -### Unicode keys: -Unicode keys are supported, the keys will be hashed according to Meta commands +### Custom domains: +You can add a domain to keys. This domain can be used for custom per-domain +metrics like hit ratios or to control serialization of the values. +```python: +Key("key:1:2", domain="example") +``` +For example the ZstdSerializer allows to configure different dictionaries by +domain, so you can compress more efficiently data of different domains. + +### Unicode/binary keys: +Both unicode and binary keys are supported, the keys will be hashed/encoded according to Meta commands [binary encoded keys](https://github.com/memcached/memcached/wiki/MetaCommands#binary-encoded-keys) specification. -To use this, mark the key as unicode: +Using binary keys can have benefits, saving space in memory. While over the wire the key +is transmited b64 encoded, the memcache server will use the byte representation, so it will +not have the 1/4 overhead of b64 encoding. + ```python: -Key("đŸș", unicode=True) +Key("đŸș") ``` ### Large keys: diff --git a/poetry.lock b/poetry.lock index eb89ce1..5fa0e8d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,68 @@ -# This file is automatically @generated by Poetry 1.7.0 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.1 and should not be changed by hand. + +[[package]] +name = "cffi" +version = "1.16.0" +description = "Foreign Function Interface for Python calling C code." +optional = false +python-versions = ">=3.8" +files = [ + {file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"}, + {file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614"}, + {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743"}, + {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d"}, + {file = "cffi-1.16.0-cp310-cp310-win32.whl", hash = "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a"}, + {file = "cffi-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1"}, + {file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"}, + {file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"}, + {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"}, + {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"}, + {file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"}, + {file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"}, + {file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"}, + {file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"}, + {file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"}, + {file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"}, + {file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"}, + {file = "cffi-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324"}, + {file = "cffi-1.16.0-cp38-cp38-win32.whl", hash = "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a"}, + {file = "cffi-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36"}, + {file = "cffi-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed"}, + {file = "cffi-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098"}, + {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000"}, + {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe"}, + {file = "cffi-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4"}, + {file = "cffi-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8"}, + {file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"}, +] + +[package.dependencies] +pycparser = "*" [[package]] name = "marisa-trie" @@ -77,58 +141,56 @@ test = ["hypothesis", "pytest", "readme-renderer"] [[package]] name = "meta-memcache-socket" -version = "0.1.1" +version = "0.1.3" description = "" optional = false python-versions = ">=3.7" files = [ - {file = "meta_memcache_socket-0.1.1-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:e58c0b96d58789bcd8018c3930436f8a1bdf0cec81e40c9872ab7fd09fc086ad"}, - {file = "meta_memcache_socket-0.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:101a29d94c2de5a9562c1ef0a1decda2a48b14a127a4aa560297cfab63b5076c"}, - {file = "meta_memcache_socket-0.1.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:641dbae8ae836a5cc2789e9b3554ee2a07ad4427cf2a401209c8086252dc9143"}, - {file = "meta_memcache_socket-0.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46e9bc46bab15bffc0e5ebc5cd2fc08beff3c4b6e58f3b0c152057a7440ac552"}, - {file = "meta_memcache_socket-0.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:de836ed9764197f02201cc77076fc3f41aaddfce266057c16f6b85cf8fe308a8"}, - {file = "meta_memcache_socket-0.1.1-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:2d484467397f9a506fa6a78dc5ff15d19f8292d774b04cab69d6be3e01966e1f"}, - {file = "meta_memcache_socket-0.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7ac0fc9ccc6e31e3af91ea727a5df19a61a6f07b3b4086902c31fa1cf0bb1cac"}, - {file = "meta_memcache_socket-0.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fda29ca3aa25330355808f5ad26c803c36cadbd162f24b953ca3256f920653e9"}, - {file = "meta_memcache_socket-0.1.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5c971a94d2213ffaaa1932116c881f8bb2c9c8d1f5d38f14769aa6493aaed980"}, - {file = "meta_memcache_socket-0.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5a682628d040e3c6500501a84b9d64a870fcdd781585fa2449c6c09e7f02f60"}, - {file = "meta_memcache_socket-0.1.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:26bc83b7ae9c359ae9a1e3a3670adf27a891b4b4d38aaa209e9d0116402448d5"}, - {file = "meta_memcache_socket-0.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2a8e1384f7e015ccf60e0d401545b8e694d4973f0d0a51d1b40d7fbd8de48024"}, - {file = "meta_memcache_socket-0.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98084e3f37de9e1be657ca00cc8fabaa9d532c1316ea4db83d5633a8f01d70c9"}, - {file = "meta_memcache_socket-0.1.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8a0bf4dd2150c36cc80b4b5c356cf2adf1d6c599b005b9b09f9228f17f57fbfe"}, - {file = "meta_memcache_socket-0.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e1515cee9705b5411a2b50ec4f088561367d4d7d828c0effb8a9da8f8bc015f"}, - {file = "meta_memcache_socket-0.1.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d90b39eab5213ecbe21d298fde6b7a05d07bcf8707bcd389de94e62c54d36967"}, - {file = "meta_memcache_socket-0.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:432d24b52dbaa3feb537cb398e329620b0aef77cc59da3a9f5c712f7e8b4dddc"}, - {file = "meta_memcache_socket-0.1.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5cd55ed3fccf72a1a9fb6c0647d2c63d499776ba55c94f3116b818cbe16db102"}, - {file = "meta_memcache_socket-0.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d331be18a7f5b351326b46a5f9393cc7343bfe4fad158dbfbc2ef9c30ec4c66"}, - {file = "meta_memcache_socket-0.1.1-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:71667a4b26ff306e6cf59d0d1a792b5f3bfe2de2ee55cb187545329e4cb62252"}, - {file = "meta_memcache_socket-0.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:232cad68095402fdadfb8eaa1576e1ea2e1a5f21117242f14442081b98e871da"}, - {file = "meta_memcache_socket-0.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7bddfdee0abead430d573decaa1a52e1a417ac2e803c52d02d8f8bcf0c28523e"}, - {file = "meta_memcache_socket-0.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b424f67273ab0b7356e0b992a8e8d6ddd313c7d3cb3171777f7173af4fa61611"}, - {file = "meta_memcache_socket-0.1.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:305174c2f22cebcac557fdc708bca334eea994c539c18463cece381371fff50a"}, - {file = "meta_memcache_socket-0.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406b3bcc854ea825a54c6cd04449263e5700fbd07c8f8b515b753305e185c1ee"}, - {file = "meta_memcache_socket-0.1.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c3985e25d04bc3bf3cb0602512dca21372a51a7e7975ecca266f1252a09ac1f8"}, - {file = "meta_memcache_socket-0.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:358abe4222f1ef10e907bfefd3aba71a2a2337af43817144a2eb25501b4184d8"}, - {file = "meta_memcache_socket-0.1.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2803528ad935f769367001210ddff68420d4da23800422ccea7d343fd2c45e52"}, - {file = "meta_memcache_socket-0.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c6f93c03d3239af3bab766ccdb297f216ffaab6061596d09eb9f9ee2b30ed7b"}, - {file = "meta_memcache_socket-0.1.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d929b77fe8edf8685ed55345d425f0db80256b13f2b4132386e3def246484b18"}, - {file = "meta_memcache_socket-0.1.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9e741669984d69ac48c78bb5595e36f5205d3bdaa5f259547f5a651a71debb5"}, - {file = "meta_memcache_socket-0.1.1-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8c31615685db11603e5e32d07163029660100d0ae1c96b963f78b8a6336eba90"}, - {file = "meta_memcache_socket-0.1.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:690761adc29ad7a516374544483b46059e5bf83afb11e5aa49723fd971a077a4"}, - {file = "meta_memcache_socket-0.1.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fde68dde315f98190aae95448cdb60a1862b12b01b9e944530add32368a64efa"}, - {file = "meta_memcache_socket-0.1.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:060abd5a8f8a721623cc2a26d7e99174b79c9ff538dde8d015e26513ae7f1a13"}, - {file = "meta_memcache_socket-0.1.1-pp37-pypy37_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff43c1344d0fbff82a808866378d02c7261fa1e63c5b4a3606d02cefb0802f04"}, - {file = "meta_memcache_socket-0.1.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf21246a4acc9e3157cddef646e4920d6e40645fc9143de5c3b82de251a7c5d3"}, - {file = "meta_memcache_socket-0.1.1-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:74c96deefc79c59262f1c63e6431e8d733f6ee646239f72be593784916e1faa2"}, - {file = "meta_memcache_socket-0.1.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab8b8f9b51a86bb36bb8c4c60dab19bbcb43ddc26ae8c22502d4c3b6a17955a8"}, - {file = "meta_memcache_socket-0.1.1-pp38-pypy38_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:96dea2e9ad7a0c7d36d912be5c8ceb26de4a6c0d126666d6f52a586ddc34b279"}, - {file = "meta_memcache_socket-0.1.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd1cf2de55ef6c4bcdce2f09bee06cf4d2f5a4c76ea126b5f9e876dba158637f"}, - {file = "meta_memcache_socket-0.1.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5bf457e3bd82c6bd7f186f549a27e924e0cec02fb3ab54c1a840a6cf5e80db3a"}, - {file = "meta_memcache_socket-0.1.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ac9bf7728be05b4e010dab3ab2c426aa26bab5d76176cf70b292fc40ad2e14b"}, - {file = "meta_memcache_socket-0.1.1-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e34f7f8f3f520e2ec2270ad04d53fa75bad3a0b7e4331f5f8cd6c35cff3de7a5"}, - {file = "meta_memcache_socket-0.1.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a74654809003ef52171c9da320a60d9128c59a917a626d726b09a1411e15b933"}, - {file = "meta_memcache_socket-0.1.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:912ee61b16973a8b48e3bbfab9af99121e1ec4dfbcc77633f67cb705574abca5"}, - {file = "meta_memcache_socket-0.1.1.tar.gz", hash = "sha256:d7e450e88c168b694ae540202522c7b12e56b3a6e26969d59be43da6739e2743"}, + {file = "meta_memcache_socket-0.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9174d414cd1a843c1ab345fd4c7a6c5cdd9096f7317829edbd2d759271e33520"}, + {file = "meta_memcache_socket-0.1.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7eb3673dec5b98ef114f9b77ff2474381732804a128638f4419874d338d3c828"}, + {file = "meta_memcache_socket-0.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03fb42564d08dae9ce1c33629aa61b5db430b8cb812d4063b5fa7ec410514a41"}, + {file = "meta_memcache_socket-0.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:20e3b9465fc1b64be33f557ec27ae5463ed28f2749dfc2a6cae1e6f398f702fa"}, + {file = "meta_memcache_socket-0.1.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:f2a1bcc8317804a6cc4a629ffce2c790120f28257e1419605e21cd194cd7ba0a"}, + {file = "meta_memcache_socket-0.1.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e7d5198eac22db11ed0dc427cbb415a55869160f40ab9c0017ed69da110acce7"}, + {file = "meta_memcache_socket-0.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6f8a12abad527e8a14227b39629c3641ea6a1884f795c1fd7337958910dd2e6"}, + {file = "meta_memcache_socket-0.1.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6cf3ef38438f507f5598021328d5a7248ca8946e9ee54aaf9178bcf8d961c137"}, + {file = "meta_memcache_socket-0.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d36aff04f4ce121531d99f8f922fe00bc2aeed87cb3a2b239d63c89d90318323"}, + {file = "meta_memcache_socket-0.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a82b9fc9a6a2dbfadc81b8c7ae84cabc911e012d07dc88650d16d828b9fdf9a7"}, + {file = "meta_memcache_socket-0.1.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:777a274b6f2ee5854e25b9800852b5f3dcc06e949f2c582bd1b5d4dc1cb776e4"}, + {file = "meta_memcache_socket-0.1.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:646b6b728e8da57c9b40d9b770b7e604bf4601c89e7903368c63e241143005ad"}, + {file = "meta_memcache_socket-0.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9e3af5f9fc4a47882b5d3fe5571575506b77a5e6590cfca1fa21870f90b4d698"}, + {file = "meta_memcache_socket-0.1.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4a44ed8383e5c942197fe20d389126f6e6bc6339a1bf75b54e598edb2d8732bf"}, + {file = "meta_memcache_socket-0.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:43da158b6e5ffa9b15b955850e330802a368424319709b445df81eea360ff9d2"}, + {file = "meta_memcache_socket-0.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:10db259605f76accbc74f8cd4b3b9c7a40697454a14eb565a4ccfb23fb4e0403"}, + {file = "meta_memcache_socket-0.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d5f683aa323d411a8cbd28bca8cd02bb925e807224803ac353856f21f01c736"}, + {file = "meta_memcache_socket-0.1.3-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b0538791b688f216b104203712c005115c583c90bc580105016a3396be35d93f"}, + {file = "meta_memcache_socket-0.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b1751a1e8af35a1f235adac5f15966e8b041b31df8a3ce2b6b75fee053f2153"}, + {file = "meta_memcache_socket-0.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:722305fd69c758f15668ecc2906fffaac0d97a78deeab741681a3e93ab90420a"}, + {file = "meta_memcache_socket-0.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5eeec05c2ef7c32cf7ee39fad435efadd7a96332ab608b15633f64ae50332cc8"}, + {file = "meta_memcache_socket-0.1.3-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3f5540bbee6fbf7b88769005a097e1d8dfa9dcbf738502d991c7b7f63e3e9452"}, + {file = "meta_memcache_socket-0.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:393f9833530ef741251a13c1da3b47a3e71fcd84ca67016da644bfa083e3b557"}, + {file = "meta_memcache_socket-0.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cd08417ac7c806144892d6ff14dd2f06964fec1db7f0ef921c797d041cabfa20"}, + {file = "meta_memcache_socket-0.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa9830dbd568ee406925375b3159b4eb3d01a4d6dee6b4ded170c0f2d7ee472a"}, + {file = "meta_memcache_socket-0.1.3-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4e3a8494c4873dc2dc00449199981cd725830bceb8606df9852d1fc563f35477"}, + {file = "meta_memcache_socket-0.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0d2bbbaac00d5f088a4742a32beb9cbc9edc8af528ddf47333868004c715ead"}, + {file = "meta_memcache_socket-0.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b3de450a51f8597b4e271b8ac67bf259d50530993cd359aa372ea05007e402ac"}, + {file = "meta_memcache_socket-0.1.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6401b287e6a13134bb20b0467e914f1594c32d43162b3905f3e7b828137c9d7"}, + {file = "meta_memcache_socket-0.1.3-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:437fd8b930af8d51d4303d45fd095e64b44a3ded895da260df503ee782adeb23"}, + {file = "meta_memcache_socket-0.1.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ba59467026c573720722c10b3de82491766d3e263ef9a15fed1ad71dee35767"}, + {file = "meta_memcache_socket-0.1.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2c2ae75f88207fcc65a0f24174ae07099f47d5470e84ceca7843495854c4cfdd"}, + {file = "meta_memcache_socket-0.1.3-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea33ba1237e694285eb2e3af9fb21e56685d20ad5ae05426f247cf4238e1aaee"}, + {file = "meta_memcache_socket-0.1.3-pp37-pypy37_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6f8036a511e0757725355153b8f760f348d71d299a34c96389f18ea2be3e00ba"}, + {file = "meta_memcache_socket-0.1.3-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e125adfcb9b2d866e6cd6933bae456187fd7cbf8414eb4c3cd1e7ae0495611aa"}, + {file = "meta_memcache_socket-0.1.3-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:148815ecfa25a19badc0e4bdfafb944de113e9c28365ed4d9008c95ad52c472f"}, + {file = "meta_memcache_socket-0.1.3-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1dc31bee923ed3f80d112b1f55d415c0b01d08d07dcfb7fb6f4605d62f8636d"}, + {file = "meta_memcache_socket-0.1.3-pp38-pypy38_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6a89b09bd8610cb45270720ee8b1460f96ac566085b1569ee4f296c369d7f1ad"}, + {file = "meta_memcache_socket-0.1.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ceb6f72cfb224c4d9b74ff681ea369b5a02639996e331be14b11d6da51ea6035"}, + {file = "meta_memcache_socket-0.1.3-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:700018613f33f7fcdecf4e0a56e801e73450d2491ad25a44ee7c27ea7c9a2b83"}, + {file = "meta_memcache_socket-0.1.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3572c9faabc60d27757b29f10218a51e69d4c5beeacee61875509c6ddf982de2"}, + {file = "meta_memcache_socket-0.1.3-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1ca6daeb6fe239e293af26fc2d3ed37e77197f8f87ad4299eaa0072730a30929"}, + {file = "meta_memcache_socket-0.1.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:403e86606c0a5788033f87be3b58d436997b7c19afab2d430a7699b21acf27eb"}, + {file = "meta_memcache_socket-0.1.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2cf358fd7b75f6a0d634640b6aea5c8806bc4260afc248f033672bb496387fc9"}, + {file = "meta_memcache_socket-0.1.3.tar.gz", hash = "sha256:0f8072c4ae8cde332e00efb61dea25f3e7a8a562698b8fa1c455b7b7136eedb3"}, ] [[package]] @@ -145,6 +207,17 @@ files = [ [package.extras] twisted = ["twisted"] +[[package]] +name = "pycparser" +version = "2.21" +description = "C parser in Python" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, + {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, +] + [[package]] name = "setuptools" version = "68.2.2" @@ -172,7 +245,68 @@ files = [ {file = "uhashring-2.3.tar.gz", hash = "sha256:9f76187e8d8e82f6e5519c995eef1f1bf44d4a5e0fc4fdd1219a044b10040612"}, ] +[[package]] +name = "zstandard" +version = "0.22.0" +description = "Zstandard bindings for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "zstandard-0.22.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:275df437ab03f8c033b8a2c181e51716c32d831082d93ce48002a5227ec93019"}, + {file = "zstandard-0.22.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2ac9957bc6d2403c4772c890916bf181b2653640da98f32e04b96e4d6fb3252a"}, + {file = "zstandard-0.22.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe3390c538f12437b859d815040763abc728955a52ca6ff9c5d4ac707c4ad98e"}, + {file = "zstandard-0.22.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1958100b8a1cc3f27fa21071a55cb2ed32e9e5df4c3c6e661c193437f171cba2"}, + {file = "zstandard-0.22.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93e1856c8313bc688d5df069e106a4bc962eef3d13372020cc6e3ebf5e045202"}, + {file = "zstandard-0.22.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:1a90ba9a4c9c884bb876a14be2b1d216609385efb180393df40e5172e7ecf356"}, + {file = "zstandard-0.22.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3db41c5e49ef73641d5111554e1d1d3af106410a6c1fb52cf68912ba7a343a0d"}, + {file = "zstandard-0.22.0-cp310-cp310-win32.whl", hash = "sha256:d8593f8464fb64d58e8cb0b905b272d40184eac9a18d83cf8c10749c3eafcd7e"}, + {file = "zstandard-0.22.0-cp310-cp310-win_amd64.whl", hash = "sha256:f1a4b358947a65b94e2501ce3e078bbc929b039ede4679ddb0460829b12f7375"}, + {file = "zstandard-0.22.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:589402548251056878d2e7c8859286eb91bd841af117dbe4ab000e6450987e08"}, + {file = "zstandard-0.22.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a97079b955b00b732c6f280d5023e0eefe359045e8b83b08cf0333af9ec78f26"}, + {file = "zstandard-0.22.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:445b47bc32de69d990ad0f34da0e20f535914623d1e506e74d6bc5c9dc40bb09"}, + {file = "zstandard-0.22.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33591d59f4956c9812f8063eff2e2c0065bc02050837f152574069f5f9f17775"}, + {file = "zstandard-0.22.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:888196c9c8893a1e8ff5e89b8f894e7f4f0e64a5af4d8f3c410f0319128bb2f8"}, + {file = "zstandard-0.22.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:53866a9d8ab363271c9e80c7c2e9441814961d47f88c9bc3b248142c32141d94"}, + {file = "zstandard-0.22.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4ac59d5d6910b220141c1737b79d4a5aa9e57466e7469a012ed42ce2d3995e88"}, + {file = "zstandard-0.22.0-cp311-cp311-win32.whl", hash = "sha256:2b11ea433db22e720758cba584c9d661077121fcf60ab43351950ded20283440"}, + {file = "zstandard-0.22.0-cp311-cp311-win_amd64.whl", hash = "sha256:11f0d1aab9516a497137b41e3d3ed4bbf7b2ee2abc79e5c8b010ad286d7464bd"}, + {file = "zstandard-0.22.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6c25b8eb733d4e741246151d895dd0308137532737f337411160ff69ca24f93a"}, + {file = "zstandard-0.22.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f9b2cde1cd1b2a10246dbc143ba49d942d14fb3d2b4bccf4618d475c65464912"}, + {file = "zstandard-0.22.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a88b7df61a292603e7cd662d92565d915796b094ffb3d206579aaebac6b85d5f"}, + {file = "zstandard-0.22.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:466e6ad8caefb589ed281c076deb6f0cd330e8bc13c5035854ffb9c2014b118c"}, + {file = "zstandard-0.22.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a1d67d0d53d2a138f9e29d8acdabe11310c185e36f0a848efa104d4e40b808e4"}, + {file = "zstandard-0.22.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:39b2853efc9403927f9065cc48c9980649462acbdf81cd4f0cb773af2fd734bc"}, + {file = "zstandard-0.22.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8a1b2effa96a5f019e72874969394edd393e2fbd6414a8208fea363a22803b45"}, + {file = "zstandard-0.22.0-cp312-cp312-win32.whl", hash = "sha256:88c5b4b47a8a138338a07fc94e2ba3b1535f69247670abfe422de4e0b344aae2"}, + {file = "zstandard-0.22.0-cp312-cp312-win_amd64.whl", hash = "sha256:de20a212ef3d00d609d0b22eb7cc798d5a69035e81839f549b538eff4105d01c"}, + {file = "zstandard-0.22.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d75f693bb4e92c335e0645e8845e553cd09dc91616412d1d4650da835b5449df"}, + {file = "zstandard-0.22.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:36a47636c3de227cd765e25a21dc5dace00539b82ddd99ee36abae38178eff9e"}, + {file = "zstandard-0.22.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68953dc84b244b053c0d5f137a21ae8287ecf51b20872eccf8eaac0302d3e3b0"}, + {file = "zstandard-0.22.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2612e9bb4977381184bb2463150336d0f7e014d6bb5d4a370f9a372d21916f69"}, + {file = "zstandard-0.22.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:23d2b3c2b8e7e5a6cb7922f7c27d73a9a615f0a5ab5d0e03dd533c477de23004"}, + {file = "zstandard-0.22.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:1d43501f5f31e22baf822720d82b5547f8a08f5386a883b32584a185675c8fbf"}, + {file = "zstandard-0.22.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a493d470183ee620a3df1e6e55b3e4de8143c0ba1b16f3ded83208ea8ddfd91d"}, + {file = "zstandard-0.22.0-cp38-cp38-win32.whl", hash = "sha256:7034d381789f45576ec3f1fa0e15d741828146439228dc3f7c59856c5bcd3292"}, + {file = "zstandard-0.22.0-cp38-cp38-win_amd64.whl", hash = "sha256:d8fff0f0c1d8bc5d866762ae95bd99d53282337af1be9dc0d88506b340e74b73"}, + {file = "zstandard-0.22.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2fdd53b806786bd6112d97c1f1e7841e5e4daa06810ab4b284026a1a0e484c0b"}, + {file = "zstandard-0.22.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:73a1d6bd01961e9fd447162e137ed949c01bdb830dfca487c4a14e9742dccc93"}, + {file = "zstandard-0.22.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9501f36fac6b875c124243a379267d879262480bf85b1dbda61f5ad4d01b75a3"}, + {file = "zstandard-0.22.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48f260e4c7294ef275744210a4010f116048e0c95857befb7462e033f09442fe"}, + {file = "zstandard-0.22.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:959665072bd60f45c5b6b5d711f15bdefc9849dd5da9fb6c873e35f5d34d8cfb"}, + {file = "zstandard-0.22.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d22fdef58976457c65e2796e6730a3ea4a254f3ba83777ecfc8592ff8d77d303"}, + {file = "zstandard-0.22.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a7ccf5825fd71d4542c8ab28d4d482aace885f5ebe4b40faaa290eed8e095a4c"}, + {file = "zstandard-0.22.0-cp39-cp39-win32.whl", hash = "sha256:f058a77ef0ece4e210bb0450e68408d4223f728b109764676e1a13537d056bb0"}, + {file = "zstandard-0.22.0-cp39-cp39-win_amd64.whl", hash = "sha256:e9e9d4e2e336c529d4c435baad846a181e39a982f823f7e4495ec0b0ec8538d2"}, + {file = "zstandard-0.22.0.tar.gz", hash = "sha256:8226a33c542bcb54cd6bd0a366067b610b41713b64c9abec1bc4533d69f51e70"}, +] + +[package.dependencies] +cffi = {version = ">=1.11", markers = "platform_python_implementation == \"PyPy\""} + +[package.extras] +cffi = ["cffi (>=1.11)"] + [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "28444a6c5cbc6d48a1bcb8cc13e85b6ea1a2bd1d5880b31cf16d7737ab8f491d" +content-hash = "cf322463aadf9e9b187ed0f60ce8ec6fbd54666664dfd69410343edd2f6b1385" diff --git a/pyproject.toml b/pyproject.toml index d3d855a..08eb17c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,7 +14,8 @@ packages = [{include = "meta_memcache", from="src"}] python = "^3.8" uhashring = "^2.1" marisa-trie = "^1.0.0" -meta-memcache-socket = "0.1.1" +meta-memcache-socket = "0.1.3" +zstandard = "^0.22.0" [tool.poetry.group.extras.dependencies] prometheus-client = "^0.17.1" @@ -27,7 +28,7 @@ testpaths = [ [tool.isort] profile = "black" -known_third_party = ["uhashring", "pytest", "pytest_mock", "marisa-trie"] +known_third_party = ["uhashring", "pytest", "pytest_mock", "marisa-trie", "zstandard"] [tool.coverage.paths] source = ["src", "*/site-packages"] diff --git a/src/meta_memcache/base/base_serializer.py b/src/meta_memcache/base/base_serializer.py index 1fe44b2..d017d72 100644 --- a/src/meta_memcache/base/base_serializer.py +++ b/src/meta_memcache/base/base_serializer.py @@ -1,7 +1,7 @@ from abc import ABC, abstractmethod from typing import Any, NamedTuple -from meta_memcache.protocol import Blob +from meta_memcache.protocol import Blob, Key class EncodedValue(NamedTuple): @@ -13,6 +13,7 @@ class BaseSerializer(ABC): @abstractmethod def serialize( self, + key: Key, value: Any, ) -> EncodedValue: ... diff --git a/src/meta_memcache/executors/default.py b/src/meta_memcache/executors/default.py index 7be50a2..b7b36f2 100644 --- a/src/meta_memcache/executors/default.py +++ b/src/meta_memcache/executors/default.py @@ -68,10 +68,11 @@ def _build_cmd( def _prepare_serialized_value_and_flags( self, + key: Key, value: ValueContainer, flags: Optional[RequestFlags], ) -> Tuple[Optional[bytes], RequestFlags]: - encoded_value = self._serializer.serialize(value.value) + encoded_value = self._serializer.serialize(key, value.value) flags = flags if flags is not None else RequestFlags() flags.client_flag = encoded_value.encoding_id return encoded_value.data, flags @@ -106,7 +107,7 @@ def exec_on_pool( cmd_value, flags = ( (None, flags) if value is None - else self._prepare_serialized_value_and_flags(value, flags) + else self._prepare_serialized_value_and_flags(key, value, flags) ) try: conn = pool.pop_connection() @@ -159,7 +160,7 @@ def exec_multi_on_pool( # noqa: C901 cmd_value, flags = ( (None, flags) if value is None - else self._prepare_serialized_value_and_flags(value, flags) + else self._prepare_serialized_value_and_flags(key, value, flags) ) self._conn_send_cmd( diff --git a/src/meta_memcache/protocol.py b/src/meta_memcache/protocol.py index 138b1ef..4284806 100644 --- a/src/meta_memcache/protocol.py +++ b/src/meta_memcache/protocol.py @@ -22,20 +22,23 @@ @dataclass class Key: - __slots__ = ("key", "routing_key", "is_unicode") + __slots__ = ("key", "routing_key", "domain", "disable_compression") key: str routing_key: Optional[str] - is_unicode: bool + domain: Optional[str] + disable_compression: bool def __init__( self, key: str, routing_key: Optional[str] = None, - is_unicode: bool = False, + domain: Optional[str] = None, + disabled_compression: bool = False, ) -> None: self.key = key self.routing_key = routing_key - self.is_unicode = is_unicode + self.domain = domain + self.disable_compression = disabled_compression def __hash__(self) -> int: return hash((self.key, self.routing_key)) diff --git a/src/meta_memcache/serializer.py b/src/meta_memcache/serializer.py index 375c053..816ad7c 100644 --- a/src/meta_memcache/serializer.py +++ b/src/meta_memcache/serializer.py @@ -1,9 +1,10 @@ import pickle # noqa: S403 import zlib -from typing import Any +from typing import Any, Dict, List, NamedTuple, Optional, Tuple from meta_memcache.base.base_serializer import BaseSerializer, EncodedValue -from meta_memcache.protocol import Blob +from meta_memcache.protocol import Blob, Key +import zstandard as zstd class MixedSerializer(BaseSerializer): @@ -13,6 +14,7 @@ class MixedSerializer(BaseSerializer): LONG = 4 ZLIB_COMPRESSED = 8 BINARY = 16 + COMPRESSION_THRESHOLD = 128 def __init__(self, pickle_protocol: int = 0) -> None: @@ -20,6 +22,7 @@ def __init__(self, pickle_protocol: int = 0) -> None: def serialize( self, + key: Key, value: Any, ) -> EncodedValue: if isinstance(value, bytes): @@ -53,3 +56,174 @@ def unserialize(self, data: Blob, encoding_id: int) -> Any: return bytes(data) else: return pickle.loads(data) # noqa: S301 + + +class DictionaryMapping(NamedTuple): + dictionary: bytes + active_domains: List[str] + + +class ZstdSerializer(BaseSerializer): + STR = 0 + PICKLE = 1 + INT = 2 + LONG = 4 + ZLIB_COMPRESSED = 8 + BINARY = 16 + ZSTD_COMPRESSED = 32 + + ZSTD_MAGIC = b"(\xb5/\xfd" + DEFAULT_PICKLE_PROTOCOL = 5 + DEFAULT_COMPRESSION_LEVEL = 9 + DEFAULT_COMPRESSION_THRESHOLD = 128 + DEFAULT_DICT_COMPRESSION_THRESHOLD = 64 + + _pickle_protocol: int + _compression_level: int + _default_compression_threshold: int + _dict_compression_threshold: int + _zstd_compressors: Dict[int, zstd.ZstdCompressor] + _zstd_decompressors: Dict[int, zstd.ZstdDecompressor] + _domain_to_dict_id: Dict[str, int] + _default_zstd_compressor: Optional[zstd.ZstdCompressor] + + def __init__( + self, + pickle_protocol: int = DEFAULT_PICKLE_PROTOCOL, + compression_level: int = DEFAULT_COMPRESSION_LEVEL, + compression_threshold: int = DEFAULT_COMPRESSION_THRESHOLD, + dict_compression_threshold: int = DEFAULT_DICT_COMPRESSION_THRESHOLD, + dictionary_mappings: Optional[List[DictionaryMapping]] = None, + default_dictionary: Optional[bytes] = None, + default_zstd: bool = True, + ) -> None: + self._pickle_protocol = pickle_protocol + self._compression_level = compression_level + self._default_compression_threshold = ( + compression_threshold + if not default_dictionary + else dict_compression_threshold + ) + self._dict_compression_threshold = dict_compression_threshold + self._zstd_compressors = {} + self._zstd_decompressors = {} + self._domain_to_dict_id = {} + + compression_params = zstd.ZstdCompressionParameters.from_level( + compression_level, + format=zstd.FORMAT_ZSTD1_MAGICLESS, + write_content_size=True, + write_checksum=False, + write_dict_id=True, + ) + + if dictionary_mappings: + for dictionary_mapping in dictionary_mappings: + dict_id, zstd_dict = self._build_dict(dictionary_mapping.dictionary) + self._add_dict_decompressor(dict_id, zstd_dict) + if dictionary_mapping.active_domains: + # The dictionary is active for some domains + self._add_dict_compressor(dict_id, zstd_dict, compression_params) + for domain in dictionary_mapping.active_domains: + self._domain_to_dict_id[domain] = dict_id + + if default_dictionary: + dict_id, zstd_dict = self._build_dict(default_dictionary) + self._add_dict_decompressor(dict_id, zstd_dict) + + self._default_zstd_compressor = self._add_dict_compressor( + dict_id, zstd_dict, compression_params + ) + elif default_zstd: + self._default_zstd_compressor = zstd.ZstdCompressor( + compression_params=compression_params + ) + else: + self._default_zstd_compressor = None + + self._zstd_decompressors[0] = zstd.ZstdDecompressor() + + def _build_dict(self, dictionary: bytes) -> Tuple[int, zstd.ZstdCompressionDict]: + zstd_dict = zstd.ZstdCompressionDict(dictionary) + dict_id = zstd_dict.dict_id() + return dict_id, zstd_dict + + def _add_dict_decompressor( + self, dict_id: int, zstd_dict: zstd.ZstdCompressionDict + ) -> zstd.ZstdDecompressor: + self._zstd_decompressors[dict_id] = zstd.ZstdDecompressor(dict_data=zstd_dict) + return self._zstd_decompressors[dict_id] + + def _add_dict_compressor( + self, + dict_id: int, + zstd_dict: zstd.ZstdCompressionDict, + compression_params: zstd.ZstdCompressionParameters, + ) -> zstd.ZstdCompressor: + self._zstd_compressors[dict_id] = zstd.ZstdCompressor( + dict_data=zstd_dict, compression_params=compression_params + ) + return self._zstd_compressors[dict_id] + + def _compress(self, key: Key, data: bytes) -> Tuple[bytes, int]: + if key.domain and (dict_id := self._domain_to_dict_id.get(key.domain)): + return self._zstd_compressors[dict_id].compress(data), self.ZSTD_COMPRESSED + elif self._default_zstd_compressor: + return self._default_zstd_compressor.compress(data), self.ZSTD_COMPRESSED + else: + return zlib.compress(data), self.ZLIB_COMPRESSED + + def _decompress(self, data: bytes) -> bytes: + data = self.ZSTD_MAGIC + data + dict_id = zstd.get_frame_parameters(data).dict_id + if decompressor := self._zstd_decompressors.get(dict_id): + return decompressor.decompress(data) + raise ValueError(f"Unknown dictionary id: {dict_id}") + + def _should_compress(self, key: Key, data: bytes) -> bool: + data_len = len(data) + if data_len >= self._default_compression_threshold: + return True + elif data_len >= self._dict_compression_threshold: + return bool(key.domain and self._domain_to_dict_id.get(key.domain)) + return False + + def serialize( + self, + key: Key, + value: Any, + ) -> EncodedValue: + if isinstance(value, bytes): + data = value + encoding_id = self.BINARY + elif isinstance(value, int) and not isinstance(value, bool): + data = str(value).encode("ascii") + encoding_id = self.INT + elif isinstance(value, str): + data = str(value).encode() + encoding_id = self.STR + else: + data = pickle.dumps(value, protocol=self._pickle_protocol) + encoding_id = self.PICKLE + + if not key.disable_compression and self._should_compress(key, data): + data, compression_flag = self._compress(key, data) + encoding_id |= compression_flag + return EncodedValue(data=data, encoding_id=encoding_id) + + def unserialize(self, data: Blob, encoding_id: int) -> Any: + if encoding_id & self.ZLIB_COMPRESSED: + data = zlib.decompress(data) + encoding_id ^= self.ZLIB_COMPRESSED + elif encoding_id & self.ZSTD_COMPRESSED: + data = self._decompress(data) + encoding_id ^= self.ZSTD_COMPRESSED + + if encoding_id == self.STR: + return bytes(data).decode() + elif encoding_id in (self.INT, self.LONG): + return int(data) + elif encoding_id == self.BINARY: + return bytes(data) + else: + return pickle.loads(data) # noqa: S301 diff --git a/tests/commands_test.py b/tests/commands_test.py index cafb033..90e901a 100644 --- a/tests/commands_test.py +++ b/tests/commands_test.py @@ -551,7 +551,7 @@ def test_get_cmd(memcache_socket: MemcacheSocket, cache_client: CacheClient) -> memcache_socket.sendall.reset_mock() cache_client.get( - key=Key("Ășníçod⍷", is_unicode=True), + key=Key("Ășníçod⍷"), touch_ttl=300, recache_policy=RecachePolicy(), ) @@ -614,7 +614,7 @@ def test_get_miss(memcache_socket: MemcacheSocket, cache_client: CacheClient) -> def test_get_value(memcache_socket: MemcacheSocket, cache_client: CacheClient) -> None: expected_cas_token = 123 expected_value = Foo("hello world") - encoded_value = MixedSerializer().serialize(expected_value) + encoded_value = MixedSerializer().serialize(Key("foo"), expected_value) memcache_socket.get_response.return_value = Value( size=len(encoded_value.data), value=None, @@ -659,7 +659,7 @@ def test_value_wrong_type( ) -> None: expected_cas_token = 123 expected_value = Foo("hello world") - encoded_value = MixedSerializer().serialize(expected_value) + encoded_value = MixedSerializer().serialize(Key("foo"), expected_value) memcache_socket.get_response.return_value = Value( size=len(encoded_value.data), value=None, @@ -722,7 +722,7 @@ def test_recache_win_returns_miss( ) -> None: expected_cas_token = 123 expected_value = Foo("hello world") - encoded_value = MixedSerializer().serialize(expected_value) + encoded_value = MixedSerializer().serialize(Key("foo"), expected_value) memcache_socket.get_response.return_value = Value( size=len(encoded_value.data), value=None, @@ -745,7 +745,7 @@ def test_recache_lost_returns_stale_value( ) -> None: expected_cas_token = 123 expected_value = Foo("hello world") - encoded_value = MixedSerializer().serialize(expected_value) + encoded_value = MixedSerializer().serialize(Key("foo"), expected_value) memcache_socket.get_response.return_value = Value( size=len(encoded_value.data), value=None, @@ -768,7 +768,7 @@ def test_get_or_lease_hit( ) -> None: expected_cas_token = 123 expected_value = Foo("hello world") - encoded_value = MixedSerializer().serialize(expected_value) + encoded_value = MixedSerializer().serialize(Key("foo"), expected_value) memcache_socket.get_response.return_value = Value( size=len(encoded_value.data), value=None, @@ -822,7 +822,7 @@ def test_get_or_lease_miss_lost_then_data( ) -> None: expected_cas_token = 123 expected_value = Foo("hello world") - encoded_value = MixedSerializer().serialize(expected_value) + encoded_value = MixedSerializer().serialize(Key("foo"), expected_value) memcache_socket.get_response.side_effect = [ Value( size=0, diff --git a/tests/migrating_cache_client_test.py b/tests/migrating_cache_client_test.py index eb0b588..d0772ad 100644 --- a/tests/migrating_cache_client_test.py +++ b/tests/migrating_cache_client_test.py @@ -87,7 +87,7 @@ def _set_cache_client_mock_get_return_values(client: Mock, ttl: int = 10) -> Non ), ) client.meta_multiget.return_value = { - Key(key="foo", routing_key=None, is_unicode=False): Value( + Key(key="foo", routing_key=None): Value( size=3, value="bar", flags=ResponseFlags( @@ -164,7 +164,7 @@ def test_migration_mode_origin_only( # Sets migration_client_origin_only.set(key="foo", value="bar", ttl=10) origin_client.meta_set.assert_called_once_with( - key=Key(key="foo", routing_key=None, is_unicode=False), + key=Key(key="foo", routing_key=None), value="bar", ttl=10, flags=RequestFlags(cache_ttl=10), @@ -175,7 +175,7 @@ def test_migration_mode_origin_only( # Deletes migration_client_origin_only.delete(key="foo") origin_client.meta_delete.assert_called_once_with( - key=Key(key="foo", routing_key=None, is_unicode=False), + key=Key(key="foo", routing_key=None), flags=RequestFlags(), failure_handling=DEFAULT_FAILURE_HANDLING, ) @@ -184,7 +184,7 @@ def test_migration_mode_origin_only( # Arithmetic migration_client_origin_only.delta(key="foo", delta=1) origin_client.meta_arithmetic.assert_called_once_with( - key=Key(key="foo", routing_key=None, is_unicode=False), + key=Key(key="foo", routing_key=None), flags=RequestFlags(ma_delta_value=1), failure_handling=DEFAULT_FAILURE_HANDLING, ) @@ -221,7 +221,7 @@ def test_migration_mode_destination_only( migration_client_destination_only.set(key="foo", value="bar", ttl=10) origin_client.meta_set.assert_not_called() destination_client.meta_set.assert_called_once_with( - key=Key(key="foo", routing_key=None, is_unicode=False), + key=Key(key="foo", routing_key=None), value="bar", ttl=10, flags=RequestFlags(cache_ttl=10), @@ -232,7 +232,7 @@ def test_migration_mode_destination_only( migration_client_destination_only.delete(key="foo") origin_client.meta_delete.assert_not_called() destination_client.meta_delete.assert_called_once_with( - key=Key(key="foo", routing_key=None, is_unicode=False), + key=Key(key="foo", routing_key=None), flags=RequestFlags(), failure_handling=DEFAULT_FAILURE_HANDLING, ) @@ -241,7 +241,7 @@ def test_migration_mode_destination_only( migration_client_destination_only.delta(key="foo", delta=1) origin_client.meta_arithmetic.assert_not_called() destination_client.meta_arithmetic.assert_called_once_with( - key=Key(key="foo", routing_key=None, is_unicode=False), + key=Key(key="foo", routing_key=None), flags=RequestFlags(ma_delta_value=1), failure_handling=DEFAULT_FAILURE_HANDLING, ) @@ -280,14 +280,14 @@ def test_migration_mode_populate_writes( # Sets (both receive writes) migration_client.set(key="foo", value="bar", ttl=10) origin_client.meta_set.assert_called_once_with( - key=Key(key="foo", routing_key=None, is_unicode=False), + key=Key(key="foo", routing_key=None), value="bar", ttl=10, flags=RequestFlags(cache_ttl=10), failure_handling=DEFAULT_FAILURE_HANDLING, ) destination_client.meta_set.assert_called_once_with( - key=Key(key="foo", routing_key=None, is_unicode=False), + key=Key(key="foo", routing_key=None), value="bar", ttl=10, flags=RequestFlags(cache_ttl=10), @@ -297,12 +297,12 @@ def test_migration_mode_populate_writes( # Deletes (both receive writes) migration_client.delete(key="foo") origin_client.meta_delete.assert_called_once_with( - key=Key(key="foo", routing_key=None, is_unicode=False), + key=Key(key="foo", routing_key=None), flags=RequestFlags(), failure_handling=DEFAULT_FAILURE_HANDLING, ) destination_client.meta_delete.assert_called_once_with( - key=Key(key="foo", routing_key=None, is_unicode=False), + key=Key(key="foo", routing_key=None), flags=RequestFlags(), failure_handling=DEFAULT_FAILURE_HANDLING, ) @@ -310,7 +310,7 @@ def test_migration_mode_populate_writes( # Arithmetic migration_client.delta(key="foo", delta=1) origin_client.meta_arithmetic.assert_called_once_with( - key=Key(key="foo", routing_key=None, is_unicode=False), + key=Key(key="foo", routing_key=None), flags=RequestFlags(ma_delta_value=1), failure_handling=DEFAULT_FAILURE_HANDLING, ) @@ -347,7 +347,7 @@ def test_migration_mode_populate_reads_handles_non_expiring_keys( destination_client.meta_get.assert_not_called() origin_client.set.assert_not_called() destination_client.refill.assert_called_once_with( - key=Key(key="foo", routing_key=None, is_unicode=False), + key=Key(key="foo", routing_key=None), value="bar", ttl=0, no_reply=True, @@ -360,7 +360,7 @@ def test_migration_mode_populate_reads_handles_non_expiring_keys( destination_client.meta_multiget.assert_not_called() origin_client.set.assert_not_called() destination_client.refill.assert_called_once_with( - key=Key(key="foo", routing_key=None, is_unicode=False), + key=Key(key="foo", routing_key=None), value="bar", ttl=0, no_reply=True, @@ -411,7 +411,7 @@ def test_migration_mode_populate_writes_and_reads_1pct( destination_client.meta_get.assert_not_called() origin_client.refill.assert_not_called() destination_client.refill.assert_called_once_with( - key=Key(key="foo", routing_key=None, is_unicode=False), + key=Key(key="foo", routing_key=None), value="bar", ttl=10, no_reply=True, @@ -424,7 +424,7 @@ def test_migration_mode_populate_writes_and_reads_1pct( destination_client.meta_multiget.assert_not_called() origin_client.refill.assert_not_called() destination_client.refill.assert_called_once_with( - key=Key(key="foo", routing_key=None, is_unicode=False), + key=Key(key="foo", routing_key=None), value="bar", ttl=10, no_reply=True, @@ -433,14 +433,14 @@ def test_migration_mode_populate_writes_and_reads_1pct( # Sets (both receive writes) migration_client.set(key="foo", value="bar", ttl=10) origin_client.meta_set.assert_called_once_with( - key=Key(key="foo", routing_key=None, is_unicode=False), + key=Key(key="foo", routing_key=None), value="bar", ttl=10, flags=RequestFlags(cache_ttl=10), failure_handling=DEFAULT_FAILURE_HANDLING, ) destination_client.meta_set.assert_called_once_with( - key=Key(key="foo", routing_key=None, is_unicode=False), + key=Key(key="foo", routing_key=None), value="bar", ttl=10, flags=RequestFlags(cache_ttl=10), @@ -450,12 +450,12 @@ def test_migration_mode_populate_writes_and_reads_1pct( # Deletes (both receive writes) migration_client.delete(key="foo") origin_client.meta_delete.assert_called_once_with( - key=Key(key="foo", routing_key=None, is_unicode=False), + key=Key(key="foo", routing_key=None), flags=RequestFlags(), failure_handling=DEFAULT_FAILURE_HANDLING, ) destination_client.meta_delete.assert_called_once_with( - key=Key(key="foo", routing_key=None, is_unicode=False), + key=Key(key="foo", routing_key=None), flags=RequestFlags(), failure_handling=DEFAULT_FAILURE_HANDLING, ) @@ -463,7 +463,7 @@ def test_migration_mode_populate_writes_and_reads_1pct( # Arithmetic migration_client.delta(key="foo", delta=1) origin_client.meta_arithmetic.assert_called_once_with( - key=Key(key="foo", routing_key=None, is_unicode=False), + key=Key(key="foo", routing_key=None), flags=RequestFlags(ma_delta_value=1), failure_handling=DEFAULT_FAILURE_HANDLING, ) @@ -519,7 +519,7 @@ def test_migration_mode_populate_writes_and_reads_10pct( destination_client.meta_get.assert_not_called() origin_client.refill.assert_not_called() destination_client.refill.assert_called_once_with( - key=Key(key="foo", routing_key=None, is_unicode=False), + key=Key(key="foo", routing_key=None), value="bar", ttl=10, no_reply=True, @@ -532,7 +532,7 @@ def test_migration_mode_populate_writes_and_reads_10pct( destination_client.meta_multiget.assert_not_called() origin_client.refill.assert_not_called() destination_client.refill.assert_called_once_with( - key=Key(key="foo", routing_key=None, is_unicode=False), + key=Key(key="foo", routing_key=None), value="bar", ttl=10, no_reply=True, @@ -541,14 +541,14 @@ def test_migration_mode_populate_writes_and_reads_10pct( # Sets (both receive writes) migration_client.set(key="foo", value="bar", ttl=10) origin_client.meta_set.assert_called_once_with( - key=Key(key="foo", routing_key=None, is_unicode=False), + key=Key(key="foo", routing_key=None), value="bar", ttl=10, flags=RequestFlags(cache_ttl=10), failure_handling=DEFAULT_FAILURE_HANDLING, ) destination_client.meta_set.assert_called_once_with( - key=Key(key="foo", routing_key=None, is_unicode=False), + key=Key(key="foo", routing_key=None), value="bar", ttl=10, flags=RequestFlags(cache_ttl=10), @@ -558,12 +558,12 @@ def test_migration_mode_populate_writes_and_reads_10pct( # Deletes (both receive writes) migration_client.delete(key="foo") origin_client.meta_delete.assert_called_once_with( - key=Key(key="foo", routing_key=None, is_unicode=False), + key=Key(key="foo", routing_key=None), flags=RequestFlags(), failure_handling=DEFAULT_FAILURE_HANDLING, ) destination_client.meta_delete.assert_called_once_with( - key=Key(key="foo", routing_key=None, is_unicode=False), + key=Key(key="foo", routing_key=None), flags=RequestFlags(), failure_handling=DEFAULT_FAILURE_HANDLING, ) @@ -571,7 +571,7 @@ def test_migration_mode_populate_writes_and_reads_10pct( # Arithmetic migration_client.delta(key="foo", delta=1) origin_client.meta_arithmetic.assert_called_once_with( - key=Key(key="foo", routing_key=None, is_unicode=False), + key=Key(key="foo", routing_key=None), flags=RequestFlags(ma_delta_value=1), failure_handling=DEFAULT_FAILURE_HANDLING, ) @@ -614,14 +614,14 @@ def test_migration_mode_use_destination_update_origin( # Sets (both receive writes) migration_client.set(key="foo", value="bar", ttl=10) origin_client.meta_set.assert_called_once_with( - key=Key(key="foo", routing_key=None, is_unicode=False), + key=Key(key="foo", routing_key=None), value="bar", ttl=10, flags=RequestFlags(cache_ttl=10), failure_handling=DEFAULT_FAILURE_HANDLING, ) destination_client.meta_set.assert_called_once_with( - key=Key(key="foo", routing_key=None, is_unicode=False), + key=Key(key="foo", routing_key=None), value="bar", ttl=10, flags=RequestFlags(cache_ttl=10), @@ -631,12 +631,12 @@ def test_migration_mode_use_destination_update_origin( # Deletes (both receive writes) migration_client.delete(key="foo") origin_client.meta_delete.assert_called_once_with( - key=Key(key="foo", routing_key=None, is_unicode=False), + key=Key(key="foo", routing_key=None), flags=RequestFlags(), failure_handling=DEFAULT_FAILURE_HANDLING, ) destination_client.meta_delete.assert_called_once_with( - key=Key(key="foo", routing_key=None, is_unicode=False), + key=Key(key="foo", routing_key=None), flags=RequestFlags(), failure_handling=DEFAULT_FAILURE_HANDLING, ) @@ -645,7 +645,7 @@ def test_migration_mode_use_destination_update_origin( migration_client.delta(key="foo", delta=1) origin_client.meta_arithmetic.assert_not_called() destination_client.meta_arithmetic.assert_called_once_with( - key=Key(key="foo", routing_key=None, is_unicode=False), + key=Key(key="foo", routing_key=None), flags=RequestFlags(ma_delta_value=1), failure_handling=DEFAULT_FAILURE_HANDLING, ) diff --git a/tests/serializer_test.py b/tests/serializer_test.py new file mode 100644 index 0000000..e9957cd --- /dev/null +++ b/tests/serializer_test.py @@ -0,0 +1,269 @@ +import pickle +import pytest +from meta_memcache.serializer import ( + MixedSerializer, + ZstdSerializer, + DictionaryMapping, + EncodedValue, + Key, +) +import zstandard as zstd + + +@pytest.fixture +def default_dictionary() -> zstd.ZstdCompressionDict: + return zstd.train_dictionary(100 * 1024, [b"default", b"test", b"dictionary"] * 100) + + +@pytest.fixture +def custom_dictionary() -> zstd.ZstdCompressionDict: + return zstd.train_dictionary(100 * 1024, [b"custom", b"test", b"dictionary"] * 100) + + +@pytest.fixture +def dictionary_mapping(custom_dictionary): + return [ + DictionaryMapping( + dictionary=custom_dictionary.as_bytes(), active_domains=["example"] + ) + ] + + +def get_compression_dict(compressor: zstd.ZstdCompressor) -> int: + return get_data_compression_dict(compressor.compress(b"test")) + + +def get_data_compression_dict(data: bytes) -> int: + return zstd.get_frame_parameters(ZstdSerializer.ZSTD_MAGIC + data).dict_id + + +def test_zstd_serializer_initialization( + dictionary_mapping, default_dictionary, custom_dictionary +): + default_dictionary_id = default_dictionary.dict_id() + custom_dictionary_id = custom_dictionary.dict_id() + + # Test with dictionary mappings, default to zstd without dictionary + serializer = ZstdSerializer(dictionary_mappings=dictionary_mapping) + assert serializer._pickle_protocol == ZstdSerializer.DEFAULT_PICKLE_PROTOCOL + assert serializer._compression_level == ZstdSerializer.DEFAULT_COMPRESSION_LEVEL + assert ( + serializer._default_compression_threshold + == ZstdSerializer.DEFAULT_COMPRESSION_THRESHOLD + ) + assert ( + serializer._dict_compression_threshold + == ZstdSerializer.DEFAULT_DICT_COMPRESSION_THRESHOLD + ) + assert isinstance(serializer._default_zstd_compressor, zstd.ZstdCompressor) + assert get_compression_dict(serializer._default_zstd_compressor) == 0 + assert set(serializer._zstd_compressors.keys()) == {custom_dictionary_id} + assert set(serializer._zstd_decompressors.keys()) == {0, custom_dictionary_id} + assert serializer._domain_to_dict_id == {"example": custom_dictionary_id} + + # Test with dictionary mappings, default to zstd with default_dictionary + serializer = ZstdSerializer( + dictionary_mappings=dictionary_mapping, + default_dictionary=default_dictionary.as_bytes(), + ) + assert serializer._pickle_protocol == ZstdSerializer.DEFAULT_PICKLE_PROTOCOL + assert serializer._compression_level == ZstdSerializer.DEFAULT_COMPRESSION_LEVEL + assert ( + serializer._default_compression_threshold + == ZstdSerializer.DEFAULT_DICT_COMPRESSION_THRESHOLD + ) + assert ( + serializer._dict_compression_threshold + == ZstdSerializer.DEFAULT_DICT_COMPRESSION_THRESHOLD + ) + assert isinstance(serializer._default_zstd_compressor, zstd.ZstdCompressor) + assert ( + get_compression_dict(serializer._default_zstd_compressor) + == default_dictionary_id + ) + assert set(serializer._zstd_compressors.keys()) == { + custom_dictionary_id, + default_dictionary_id, + } + assert set(serializer._zstd_decompressors.keys()) == { + 0, + custom_dictionary_id, + default_dictionary_id, + } + assert serializer._domain_to_dict_id == {"example": custom_dictionary_id} + + # Test without dictionary mapping and default to zlib + serializer = ZstdSerializer( + default_zstd=False, + ) + assert serializer._pickle_protocol == ZstdSerializer.DEFAULT_PICKLE_PROTOCOL + assert serializer._compression_level == ZstdSerializer.DEFAULT_COMPRESSION_LEVEL + assert ( + serializer._default_compression_threshold + == ZstdSerializer.DEFAULT_COMPRESSION_THRESHOLD + ) + assert ( + serializer._dict_compression_threshold + == ZstdSerializer.DEFAULT_DICT_COMPRESSION_THRESHOLD + ) + assert serializer._default_zstd_compressor is None + assert not serializer._zstd_compressors + assert set(serializer._zstd_decompressors.keys()) == {0} + assert not serializer._domain_to_dict_id + + +@pytest.mark.parametrize("serializer_class", [ZstdSerializer, MixedSerializer]) +def test_serialize_bytes(serializer_class): + serializer = serializer_class() + data = b"test data" + key = Key("foo") + encoded_value = serializer.serialize(key, data) + assert isinstance(encoded_value, EncodedValue) + assert encoded_value.encoding_id == serializer.BINARY + assert encoded_value.data == data + assert serializer.unserialize(encoded_value.data, encoded_value.encoding_id) == data + + +@pytest.mark.parametrize("serializer_class", [ZstdSerializer, MixedSerializer]) +def test_serialize_int(serializer_class): + serializer = serializer_class() + data = 123 + key = Key("foo") + encoded_value = serializer.serialize(key, data) + assert isinstance(encoded_value, EncodedValue) + assert encoded_value.encoding_id == serializer.INT + assert encoded_value.data == b"123" + assert serializer.unserialize(encoded_value.data, encoded_value.encoding_id) == data + + +@pytest.mark.parametrize("serializer_class", [ZstdSerializer, MixedSerializer]) +def test_serialize_string(serializer_class): + serializer = serializer_class() + data = "test" + key = Key("foo") + encoded_value = serializer.serialize(key, data) + assert isinstance(encoded_value, EncodedValue) + assert encoded_value.encoding_id == serializer.STR + assert encoded_value.data == data.encode() + assert serializer.unserialize(encoded_value.data, encoded_value.encoding_id) == data + + +@pytest.mark.parametrize("serializer_class", [ZstdSerializer, MixedSerializer]) +def test_serialize_complex(serializer_class): + serializer = serializer_class() + data = [1, 2, 3] + key = Key("foo") + encoded_value = serializer.serialize(key, data) + assert isinstance(encoded_value, EncodedValue) + assert encoded_value.encoding_id == serializer.PICKLE + assert len(encoded_value.data) > 0 + assert pickle.loads(encoded_value.data) == data # noqa: S301 + assert serializer.unserialize(encoded_value.data, encoded_value.encoding_id) == data + + +@pytest.mark.parametrize("serializer_class", [ZstdSerializer, MixedSerializer]) +def test_serialize_compress(serializer_class): + serializer = serializer_class() + data = ["test"] * 100 + key = Key("foo") + encoded_value = serializer.serialize(key, data) + assert isinstance(encoded_value, EncodedValue) + if serializer_class == MixedSerializer: + assert ( + encoded_value.encoding_id == serializer.PICKLE | serializer.ZLIB_COMPRESSED + ) + else: + assert ( + encoded_value.encoding_id == serializer.PICKLE | serializer.ZSTD_COMPRESSED + ) + assert len(encoded_value.data) < 100 + assert serializer.unserialize(encoded_value.data, encoded_value.encoding_id) == data + + +def test_zstd_serializer_understands_zlib(): + mixed_serializer = MixedSerializer() + data = b"compressed with zlib" * 100 + encoded_value = mixed_serializer.serialize(Key("foo"), data) + print(MixedSerializer.__dict__) + assert encoded_value.encoding_id == ( + MixedSerializer.BINARY | MixedSerializer.ZLIB_COMPRESSED + ) + + zstd_serializer = ZstdSerializer() + assert ( + zstd_serializer.unserialize(encoded_value.data, encoded_value.encoding_id) + == data + ) + + +def test_zstd_default_dict_compression(dictionary_mapping, default_dictionary): + default_dictionary_id = default_dictionary.dict_id() + serializer = ZstdSerializer( + dictionary_mappings=dictionary_mapping, + default_dictionary=default_dictionary.as_bytes(), + compression_threshold=101, + dict_compression_threshold=51, + ) + + data = "test_" * 10 + + encoded_value = serializer.serialize(Key("foo"), data) + assert encoded_value.encoding_id == serializer.STR + + data = "test_" * 11 + + encoded_value = serializer.serialize(Key("foo"), data) + assert encoded_value.encoding_id == serializer.STR | serializer.ZSTD_COMPRESSED + assert get_data_compression_dict(encoded_value.data) == default_dictionary_id + + assert serializer.unserialize(encoded_value.data, encoded_value.encoding_id) == data + + +def test_zstd_no_default_dict_compression(dictionary_mapping): + serializer = ZstdSerializer( + dictionary_mappings=dictionary_mapping, + compression_threshold=101, + dict_compression_threshold=51, + ) + + data = "test_" * 10 + + encoded_value = serializer.serialize(Key("foo"), data) + assert encoded_value.encoding_id == serializer.STR + + data = "test_" * 11 + + encoded_value = serializer.serialize(Key("foo"), data) + assert encoded_value.encoding_id == serializer.STR + + data = "test_" * 21 + encoded_value = serializer.serialize(Key("foo"), data) + assert encoded_value.encoding_id == serializer.STR | serializer.ZSTD_COMPRESSED + assert get_data_compression_dict(encoded_value.data) == 0 + + assert serializer.unserialize(encoded_value.data, encoded_value.encoding_id) == data + + +def test_zstd_custom_dict_compression( + dictionary_mapping, default_dictionary, custom_dictionary +): + custom_dictionary_id = custom_dictionary.dict_id() + serializer = ZstdSerializer( + dictionary_mappings=dictionary_mapping, + default_dictionary=default_dictionary.as_bytes(), + compression_threshold=101, + dict_compression_threshold=51, + ) + + data = "test_" * 10 + + encoded_value = serializer.serialize(Key("foo", domain="example"), data) + assert encoded_value.encoding_id == serializer.STR + + data = "test_" * 11 + + encoded_value = serializer.serialize(Key("foo", domain="example"), data) + assert encoded_value.encoding_id == serializer.STR | serializer.ZSTD_COMPRESSED + assert get_data_compression_dict(encoded_value.data) == custom_dictionary_id + + assert serializer.unserialize(encoded_value.data, encoded_value.encoding_id) == data