diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 5f25907a0..49fb4148a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -29,7 +29,7 @@ jobs: with: name: coverage-reports - name: SonarCloud Scan - uses: SonarSource/sonarcloud-github-action@master + uses: SonarSource/sonarqube-scan-action@master env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} diff --git a/package-lock.json b/package-lock.json index 55aba3176..5f6151609 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@types/node": "^22.7.5", "bootstrap": "^5.3.3", "chart.js": "^4.4.6", + "prettier": "^3.4.2", "react": "^18.3.1", "react-bootstrap": "^2.10.5", "react-bootstrap-icons": "^1.11.4", @@ -2989,6 +2990,20 @@ "node": ">= 0.8.0" } }, + "node_modules/prettier": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz", + "integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", diff --git a/package.json b/package.json index cb402eef7..b630c12d4 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "@types/node": "^22.7.5", "bootstrap": "^5.3.3", "chart.js": "^4.4.6", + "prettier": "^3.4.2", "react": "^18.3.1", "react-bootstrap": "^2.10.5", "react-bootstrap-icons": "^1.11.4", diff --git a/poetry.lock b/poetry.lock index 0262a5b9b..9927d362a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.0.1 and should not be changed by hand. [[package]] name = "amqp" @@ -6,6 +6,7 @@ version = "5.3.1" description = "Low-level AMQP client for Python (fork of amqplib)." optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "amqp-5.3.1-py3-none-any.whl", hash = "sha256:43b3319e1b4e7d1251833a93d672b4af1e40f3d632d479b98661a95f117880a2"}, {file = "amqp-5.3.1.tar.gz", hash = "sha256:cddc00c725449522023bad949f70fff7b48f0b1ade74d170a6f10ab044739432"}, @@ -20,6 +21,7 @@ version = "0.7.0" description = "Reusable constraint types to use with typing.Annotated" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, @@ -31,6 +33,7 @@ version = "3.8.1" description = "ASGI specs, helper code, and adapters" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "asgiref-3.8.1-py3-none-any.whl", hash = "sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47"}, {file = "asgiref-3.8.1.tar.gz", hash = "sha256:c343bd80a0bec947a9860adb4c432ffa7db769836c64238fc34bdc3fec84d590"}, @@ -45,6 +48,7 @@ version = "3.0.0" description = "Annotate AST trees with source code positions" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2"}, {file = "asttokens-3.0.0.tar.gz", hash = "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7"}, @@ -56,13 +60,14 @@ test = ["astroid (>=2,<4)", "pytest", "pytest-cov", "pytest-xdist"] [[package]] name = "attrs" -version = "24.3.0" +version = "25.1.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ - {file = "attrs-24.3.0-py3-none-any.whl", hash = "sha256:ac96cd038792094f438ad1f6ff80837353805ac950cd2aa0e0625ef19850c308"}, - {file = "attrs-24.3.0.tar.gz", hash = "sha256:8f5c07333d543103541ba7be0e2ce16eeee8130cb0b3f9238ab904ce1e85baff"}, + {file = "attrs-25.1.0-py3-none-any.whl", hash = "sha256:c75a69e28a550a7e93789579c22aa26b0f5b83b75dc4e08fe092980051e1090a"}, + {file = "attrs-25.1.0.tar.gz", hash = "sha256:1c97078a80c814273a76b2a298a932eb681c87415c11dee0a6921de7f1b02c3e"}, ] [package.extras] @@ -75,13 +80,14 @@ tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] [[package]] name = "autopep8" -version = "2.3.1" +version = "2.3.2" description = "A tool that automatically formats Python code to conform to the PEP 8 style guide" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "autopep8-2.3.1-py2.py3-none-any.whl", hash = "sha256:a203fe0fcad7939987422140ab17a930f684763bf7335bdb6709991dd7ef6c2d"}, - {file = "autopep8-2.3.1.tar.gz", hash = "sha256:8d6c87eba648fdcfc83e29b788910b8643171c395d9c4bcf115ece035b9c9dda"}, + {file = "autopep8-2.3.2-py2.py3-none-any.whl", hash = "sha256:ce8ad498672c845a0c3de2629c15b635ec2b05ef8177a6e7c91c74f3e9b51128"}, + {file = "autopep8-2.3.2.tar.gz", hash = "sha256:89440a4f969197b69a995e4ce0661b031f455a9f776d2c5ba3dbd83466931758"}, ] [package.dependencies] @@ -93,6 +99,7 @@ version = "4.2.1" description = "Modern password hashing for your software and your servers" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "bcrypt-4.2.1-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:1340411a0894b7d3ef562fb233e4b6ed58add185228650942bdc885362f32c17"}, {file = "bcrypt-4.2.1-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1ee315739bc8387aa36ff127afc99120ee452924e0df517a8f3e4c0187a0f5f"}, @@ -131,6 +138,7 @@ version = "4.2.1" description = "Python multiprocessing fork with improvements and bugfixes" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "billiard-4.2.1-py3-none-any.whl", hash = "sha256:40b59a4ac8806ba2c2369ea98d876bc6108b051c227baffd928c644d15d8f3cb"}, {file = "billiard-4.2.1.tar.gz", hash = "sha256:12b641b0c539073fc8d3f5b8b7be998956665c4233c7c1fcd66a7e677c4fb36f"}, @@ -142,6 +150,7 @@ version = "24.10.0" description = "The uncompromising code formatter." optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "black-24.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6668650ea4b685440857138e5fe40cde4d652633b1bdffc62933d0db4ed9812"}, {file = "black-24.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1c536fcf674217e87b8cc3657b81809d3c085d7bf3ef262ead700da345bfa6ea"}, @@ -186,6 +195,8 @@ version = "1.1.0" description = "Python bindings for the Brotli compression library" optional = false python-versions = "*" +groups = ["main"] +markers = "platform_python_implementation == \"CPython\"" files = [ {file = "Brotli-1.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e1140c64812cb9b06c922e77f1c26a75ec5e3f0fb2bf92cc8c58720dec276752"}, {file = "Brotli-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c8fd5270e906eef71d4a8d19b7c6a43760c6abcfcc10c9101d14eb2357418de9"}, @@ -197,10 +208,6 @@ files = [ {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a37b8f0391212d29b3a91a799c8e4a2855e0576911cdfb2515487e30e322253d"}, {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e84799f09591700a4154154cab9787452925578841a94321d5ee8fb9a9a328f0"}, {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f66b5337fa213f1da0d9000bc8dc0cb5b896b726eefd9c6046f699b169c41b9e"}, - {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5dab0844f2cf82be357a0eb11a9087f70c5430b2c241493fc122bb6f2bb0917c"}, - {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e4fe605b917c70283db7dfe5ada75e04561479075761a0b3866c081d035b01c1"}, - {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:1e9a65b5736232e7a7f91ff3d02277f11d339bf34099a56cdab6a8b3410a02b2"}, - {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:58d4b711689366d4a03ac7957ab8c28890415e267f9b6589969e74b6e42225ec"}, {file = "Brotli-1.1.0-cp310-cp310-win32.whl", hash = "sha256:be36e3d172dc816333f33520154d708a2657ea63762ec16b62ece02ab5e4daf2"}, {file = "Brotli-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:0c6244521dda65ea562d5a69b9a26120769b7a9fb3db2fe9545935ed6735b128"}, {file = "Brotli-1.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a3daabb76a78f829cafc365531c972016e4aa8d5b4bf60660ad8ecee19df7ccc"}, @@ -213,14 +220,8 @@ files = [ {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:19c116e796420b0cee3da1ccec3b764ed2952ccfcc298b55a10e5610ad7885f9"}, {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:510b5b1bfbe20e1a7b3baf5fed9e9451873559a976c1a78eebaa3b86c57b4265"}, {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a1fd8a29719ccce974d523580987b7f8229aeace506952fa9ce1d53a033873c8"}, - {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c247dd99d39e0338a604f8c2b3bc7061d5c2e9e2ac7ba9cc1be5a69cb6cd832f"}, - {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1b2c248cd517c222d89e74669a4adfa5577e06ab68771a529060cf5a156e9757"}, - {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:2a24c50840d89ded6c9a8fdc7b6ed3692ed4e86f1c4a4a938e1e92def92933e0"}, - {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f31859074d57b4639318523d6ffdca586ace54271a73ad23ad021acd807eb14b"}, {file = "Brotli-1.1.0-cp311-cp311-win32.whl", hash = "sha256:39da8adedf6942d76dc3e46653e52df937a3c4d6d18fdc94a7c29d263b1f5b50"}, {file = "Brotli-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:aac0411d20e345dc0920bdec5548e438e999ff68d77564d5e9463a7ca9d3e7b1"}, - {file = "Brotli-1.1.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:32d95b80260d79926f5fab3c41701dbb818fde1c9da590e77e571eefd14abe28"}, - {file = "Brotli-1.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b760c65308ff1e462f65d69c12e4ae085cff3b332d894637f6273a12a482d09f"}, {file = "Brotli-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:316cc9b17edf613ac76b1f1f305d2a748f1b976b033b049a6ecdfd5612c70409"}, {file = "Brotli-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:caf9ee9a5775f3111642d33b86237b05808dafcd6268faa492250e9b78046eb2"}, {file = "Brotli-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70051525001750221daa10907c77830bc889cb6d865cc0b813d9db7fefc21451"}, @@ -231,24 +232,8 @@ files = [ {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:4093c631e96fdd49e0377a9c167bfd75b6d0bad2ace734c6eb20b348bc3ea180"}, {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7e4c4629ddad63006efa0ef968c8e4751c5868ff0b1c5c40f76524e894c50248"}, {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:861bf317735688269936f755fa136a99d1ed526883859f86e41a5d43c61d8966"}, - {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:87a3044c3a35055527ac75e419dfa9f4f3667a1e887ee80360589eb8c90aabb9"}, - {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c5529b34c1c9d937168297f2c1fde7ebe9ebdd5e121297ff9c043bdb2ae3d6fb"}, - {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:ca63e1890ede90b2e4454f9a65135a4d387a4585ff8282bb72964fab893f2111"}, - {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e79e6520141d792237c70bcd7a3b122d00f2613769ae0cb61c52e89fd3443839"}, {file = "Brotli-1.1.0-cp312-cp312-win32.whl", hash = "sha256:5f4d5ea15c9382135076d2fb28dde923352fe02951e66935a9efaac8f10e81b0"}, {file = "Brotli-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:906bc3a79de8c4ae5b86d3d75a8b77e44404b0f4261714306e3ad248d8ab0951"}, - {file = "Brotli-1.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8bf32b98b75c13ec7cf774164172683d6e7891088f6316e54425fde1efc276d5"}, - {file = "Brotli-1.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7bc37c4d6b87fb1017ea28c9508b36bbcb0c3d18b4260fcdf08b200c74a6aee8"}, - {file = "Brotli-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c0ef38c7a7014ffac184db9e04debe495d317cc9c6fb10071f7fefd93100a4f"}, - {file = "Brotli-1.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91d7cc2a76b5567591d12c01f019dd7afce6ba8cba6571187e21e2fc418ae648"}, - {file = "Brotli-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a93dde851926f4f2678e704fadeb39e16c35d8baebd5252c9fd94ce8ce68c4a0"}, - {file = "Brotli-1.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f0db75f47be8b8abc8d9e31bc7aad0547ca26f24a54e6fd10231d623f183d089"}, - {file = "Brotli-1.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6967ced6730aed543b8673008b5a391c3b1076d834ca438bbd70635c73775368"}, - {file = "Brotli-1.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7eedaa5d036d9336c95915035fb57422054014ebdeb6f3b42eac809928e40d0c"}, - {file = "Brotli-1.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d487f5432bf35b60ed625d7e1b448e2dc855422e87469e3f450aa5552b0eb284"}, - {file = "Brotli-1.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:832436e59afb93e1836081a20f324cb185836c617659b07b129141a8426973c7"}, - {file = "Brotli-1.1.0-cp313-cp313-win32.whl", hash = "sha256:43395e90523f9c23a3d5bdf004733246fba087f2948f87ab28015f12359ca6a0"}, - {file = "Brotli-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:9011560a466d2eb3f5a6e4929cf4a09be405c64154e12df0dd72713f6500e32b"}, {file = "Brotli-1.1.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a090ca607cbb6a34b0391776f0cb48062081f5f60ddcce5d11838e67a01928d1"}, {file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2de9d02f5bda03d27ede52e8cfe7b865b066fa49258cbab568720aa5be80a47d"}, {file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2333e30a5e00fe0fe55903c8832e08ee9c3b1382aacf4db26664a16528d51b4b"}, @@ -258,10 +243,6 @@ files = [ {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:fd5f17ff8f14003595ab414e45fce13d073e0762394f957182e69035c9f3d7c2"}, {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:069a121ac97412d1fe506da790b3e69f52254b9df4eb665cd42460c837193354"}, {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:e93dfc1a1165e385cc8239fab7c036fb2cd8093728cbd85097b284d7b99249a2"}, - {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_2_aarch64.whl", hash = "sha256:aea440a510e14e818e67bfc4027880e2fb500c2ccb20ab21c7a7c8b5b4703d75"}, - {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_2_i686.whl", hash = "sha256:6974f52a02321b36847cd19d1b8e381bf39939c21efd6ee2fc13a28b0d99348c"}, - {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_2_ppc64le.whl", hash = "sha256:a7e53012d2853a07a4a79c00643832161a910674a893d296c9f1259859a289d2"}, - {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_2_x86_64.whl", hash = "sha256:d7702622a8b40c49bffb46e1e3ba2e81268d5c04a34f460978c6b5517a34dd52"}, {file = "Brotli-1.1.0-cp36-cp36m-win32.whl", hash = "sha256:a599669fd7c47233438a56936988a2478685e74854088ef5293802123b5b2460"}, {file = "Brotli-1.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:d143fd47fad1db3d7c27a1b1d66162e855b5d50a89666af46e1679c496e8e579"}, {file = "Brotli-1.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:11d00ed0a83fa22d29bc6b64ef636c4552ebafcef57154b4ddd132f5638fbd1c"}, @@ -273,10 +254,6 @@ files = [ {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:919e32f147ae93a09fe064d77d5ebf4e35502a8df75c29fb05788528e330fe74"}, {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:23032ae55523cc7bccb4f6a0bf368cd25ad9bcdcc1990b64a647e7bbcce9cb5b"}, {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:224e57f6eac61cc449f498cc5f0e1725ba2071a3d4f48d5d9dffba42db196438"}, - {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:cb1dac1770878ade83f2ccdf7d25e494f05c9165f5246b46a621cc849341dc01"}, - {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:3ee8a80d67a4334482d9712b8e83ca6b1d9bc7e351931252ebef5d8f7335a547"}, - {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:5e55da2c8724191e5b557f8e18943b1b4839b8efc3ef60d65985bcf6f587dd38"}, - {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:d342778ef319e1026af243ed0a07c97acf3bad33b9f29e7ae6a1f68fd083e90c"}, {file = "Brotli-1.1.0-cp37-cp37m-win32.whl", hash = "sha256:587ca6d3cef6e4e868102672d3bd9dc9698c309ba56d41c2b9c85bbb903cdb95"}, {file = "Brotli-1.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:2954c1c23f81c2eaf0b0717d9380bd348578a94161a65b3a2afc62c86467dd68"}, {file = "Brotli-1.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:efa8b278894b14d6da122a72fefcebc28445f2d3f880ac59d46c90f4c13be9a3"}, @@ -289,10 +266,6 @@ files = [ {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ab4fbee0b2d9098c74f3057b2bc055a8bd92ccf02f65944a241b4349229185a"}, {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:141bd4d93984070e097521ed07e2575b46f817d08f9fa42b16b9b5f27b5ac088"}, {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fce1473f3ccc4187f75b4690cfc922628aed4d3dd013d047f95a9b3919a86596"}, - {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d2b35ca2c7f81d173d2fadc2f4f31e88cc5f7a39ae5b6db5513cf3383b0e0ec7"}, - {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:af6fa6817889314555aede9a919612b23739395ce767fe7fcbea9a80bf140fe5"}, - {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:2feb1d960f760a575dbc5ab3b1c00504b24caaf6986e2dc2b01c09c87866a943"}, - {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:4410f84b33374409552ac9b6903507cdb31cd30d2501fc5ca13d18f73548444a"}, {file = "Brotli-1.1.0-cp38-cp38-win32.whl", hash = "sha256:db85ecf4e609a48f4b29055f1e144231b90edc90af7481aa731ba2d059226b1b"}, {file = "Brotli-1.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:3d7954194c36e304e1523f55d7042c59dc53ec20dd4e9ea9d151f1b62b4415c0"}, {file = "Brotli-1.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5fb2ce4b8045c78ebbc7b8f3c15062e435d47e7393cc57c25115cfd49883747a"}, @@ -305,10 +278,6 @@ files = [ {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:949f3b7c29912693cee0afcf09acd6ebc04c57af949d9bf77d6101ebb61e388c"}, {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:89f4988c7203739d48c6f806f1e87a1d96e0806d44f0fba61dba81392c9e474d"}, {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:de6551e370ef19f8de1807d0a9aa2cdfdce2e85ce88b122fe9f6b2b076837e59"}, - {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0737ddb3068957cf1b054899b0883830bb1fec522ec76b1098f9b6e0f02d9419"}, - {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:4f3607b129417e111e30637af1b56f24f7a49e64763253bbc275c75fa887d4b2"}, - {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:6c6e0c425f22c1c719c42670d561ad682f7bfeeef918edea971a79ac5252437f"}, - {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:494994f807ba0b92092a163a0a283961369a65f6cbe01e8891132b7a320e61eb"}, {file = "Brotli-1.1.0-cp39-cp39-win32.whl", hash = "sha256:f0d8a7a6b5983c2496e364b969f0e526647a06b075d034f3297dc66f3b360c64"}, {file = "Brotli-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:cdad5b9014d83ca68c25d2e9444e28e967ef16e80f6b436918c700c117a85467"}, {file = "Brotli-1.1.0.tar.gz", hash = "sha256:81de08ac11bcb85841e440c13611c00b67d3bf82698314928d0b676362546724"}, @@ -320,6 +289,8 @@ version = "1.1.0.0" description = "Python CFFI bindings to the Brotli library" optional = false python-versions = ">=3.7" +groups = ["main"] +markers = "platform_python_implementation != \"CPython\"" files = [ {file = "brotlicffi-1.1.0.0-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:9b7ae6bd1a3f0df532b6d67ff674099a96d22bc0948955cb338488c31bfb8851"}, {file = "brotlicffi-1.1.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19ffc919fa4fc6ace69286e0a23b3789b4219058313cf9b45625016bf7ff996b"}, @@ -359,6 +330,7 @@ version = "5.4.0" description = "Distributed Task Queue." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "celery-5.4.0-py3-none-any.whl", hash = "sha256:369631eb580cf8c51a82721ec538684994f8277637edde2dfc0dacd73ed97f64"}, {file = "celery-5.4.0.tar.gz", hash = "sha256:504a19140e8d3029d5acad88330c541d4c3f64c789d85f94756762d8bca7e706"}, @@ -415,6 +387,7 @@ version = "2024.12.14" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" +groups = ["main", "dev"] files = [ {file = "certifi-2024.12.14-py3-none-any.whl", hash = "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56"}, {file = "certifi-2024.12.14.tar.gz", hash = "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db"}, @@ -426,6 +399,7 @@ version = "1.17.1" description = "Foreign Function Interface for Python calling C code." optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, @@ -495,6 +469,7 @@ files = [ {file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"}, {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"}, ] +markers = {dev = "os_name == \"nt\" and implementation_name != \"pypy\""} [package.dependencies] pycparser = "*" @@ -505,6 +480,7 @@ version = "3.4.0" description = "Validate configuration and produce human readable error messages." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, @@ -516,6 +492,7 @@ version = "3.4.1" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de"}, {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176"}, @@ -617,6 +594,7 @@ version = "8.1.8" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, @@ -631,6 +609,7 @@ version = "0.3.1" description = "Enables git-like *did-you-mean* feature in click" optional = false python-versions = ">=3.6.2" +groups = ["main"] files = [ {file = "click_didyoumean-0.3.1-py3-none-any.whl", hash = "sha256:5c4bb6007cfea5f2fd6583a2fb6701a22a41eb98957e63d0fac41c10e7c3117c"}, {file = "click_didyoumean-0.3.1.tar.gz", hash = "sha256:4f82fdff0dbe64ef8ab2279bd6aa3f6a99c3b28c05aa09cbfc07c9d7fbb5a463"}, @@ -645,6 +624,7 @@ version = "1.1.1" description = "An extension module for click to enable registering CLI commands via setuptools entry-points." optional = false python-versions = "*" +groups = ["main"] files = [ {file = "click-plugins-1.1.1.tar.gz", hash = "sha256:46ab999744a9d831159c3411bb0c79346d94a444df9a3a3742e9ed63645f264b"}, {file = "click_plugins-1.1.1-py2.py3-none-any.whl", hash = "sha256:5d262006d3222f5057fd81e1623d4443e41dcda5dc815c06b442aa3c02889fc8"}, @@ -662,6 +642,7 @@ version = "0.3.0" description = "REPL plugin for Click" optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "click-repl-0.3.0.tar.gz", hash = "sha256:17849c23dba3d667247dc4defe1757fff98694e90fe37474f3feebb69ced26a9"}, {file = "click_repl-0.3.0-py3-none-any.whl", hash = "sha256:fb7e06deb8da8de86180a33a9da97ac316751c094c6899382da7feeeeb51b812"}, @@ -680,10 +661,86 @@ version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["main", "dev"] files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +markers = {main = "platform_system == \"Windows\" or sys_platform == \"win32\""} + +[[package]] +name = "contourpy" +version = "1.3.1" +description = "Python library for calculating contours of 2D quadrilateral grids" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "contourpy-1.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a045f341a77b77e1c5de31e74e966537bba9f3c4099b35bf4c2e3939dd54cdab"}, + {file = "contourpy-1.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:500360b77259914f7805af7462e41f9cb7ca92ad38e9f94d6c8641b089338124"}, + {file = "contourpy-1.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2f926efda994cdf3c8d3fdb40b9962f86edbc4457e739277b961eced3d0b4c1"}, + {file = "contourpy-1.3.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:adce39d67c0edf383647a3a007de0a45fd1b08dedaa5318404f1a73059c2512b"}, + {file = "contourpy-1.3.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abbb49fb7dac584e5abc6636b7b2a7227111c4f771005853e7d25176daaf8453"}, + {file = "contourpy-1.3.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0cffcbede75c059f535725c1680dfb17b6ba8753f0c74b14e6a9c68c29d7ea3"}, + {file = "contourpy-1.3.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ab29962927945d89d9b293eabd0d59aea28d887d4f3be6c22deaefbb938a7277"}, + {file = "contourpy-1.3.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:974d8145f8ca354498005b5b981165b74a195abfae9a8129df3e56771961d595"}, + {file = "contourpy-1.3.1-cp310-cp310-win32.whl", hash = "sha256:ac4578ac281983f63b400f7fe6c101bedc10651650eef012be1ccffcbacf3697"}, + {file = "contourpy-1.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:174e758c66bbc1c8576992cec9599ce8b6672b741b5d336b5c74e35ac382b18e"}, + {file = "contourpy-1.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3e8b974d8db2c5610fb4e76307e265de0edb655ae8169e8b21f41807ccbeec4b"}, + {file = "contourpy-1.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:20914c8c973f41456337652a6eeca26d2148aa96dd7ac323b74516988bea89fc"}, + {file = "contourpy-1.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19d40d37c1c3a4961b4619dd9d77b12124a453cc3d02bb31a07d58ef684d3d86"}, + {file = "contourpy-1.3.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:113231fe3825ebf6f15eaa8bc1f5b0ddc19d42b733345eae0934cb291beb88b6"}, + {file = "contourpy-1.3.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4dbbc03a40f916a8420e420d63e96a1258d3d1b58cbdfd8d1f07b49fcbd38e85"}, + {file = "contourpy-1.3.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a04ecd68acbd77fa2d39723ceca4c3197cb2969633836ced1bea14e219d077c"}, + {file = "contourpy-1.3.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c414fc1ed8ee1dbd5da626cf3710c6013d3d27456651d156711fa24f24bd1291"}, + {file = "contourpy-1.3.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:31c1b55c1f34f80557d3830d3dd93ba722ce7e33a0b472cba0ec3b6535684d8f"}, + {file = "contourpy-1.3.1-cp311-cp311-win32.whl", hash = "sha256:f611e628ef06670df83fce17805c344710ca5cde01edfdc72751311da8585375"}, + {file = "contourpy-1.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:b2bdca22a27e35f16794cf585832e542123296b4687f9fd96822db6bae17bfc9"}, + {file = "contourpy-1.3.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0ffa84be8e0bd33410b17189f7164c3589c229ce5db85798076a3fa136d0e509"}, + {file = "contourpy-1.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:805617228ba7e2cbbfb6c503858e626ab528ac2a32a04a2fe88ffaf6b02c32bc"}, + {file = "contourpy-1.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ade08d343436a94e633db932e7e8407fe7de8083967962b46bdfc1b0ced39454"}, + {file = "contourpy-1.3.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:47734d7073fb4590b4a40122b35917cd77be5722d80683b249dac1de266aac80"}, + {file = "contourpy-1.3.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2ba94a401342fc0f8b948e57d977557fbf4d515f03c67682dd5c6191cb2d16ec"}, + {file = "contourpy-1.3.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efa874e87e4a647fd2e4f514d5e91c7d493697127beb95e77d2f7561f6905bd9"}, + {file = "contourpy-1.3.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1bf98051f1045b15c87868dbaea84f92408337d4f81d0e449ee41920ea121d3b"}, + {file = "contourpy-1.3.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:61332c87493b00091423e747ea78200659dc09bdf7fd69edd5e98cef5d3e9a8d"}, + {file = "contourpy-1.3.1-cp312-cp312-win32.whl", hash = "sha256:e914a8cb05ce5c809dd0fe350cfbb4e881bde5e2a38dc04e3afe1b3e58bd158e"}, + {file = "contourpy-1.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:08d9d449a61cf53033612cb368f3a1b26cd7835d9b8cd326647efe43bca7568d"}, + {file = "contourpy-1.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a761d9ccfc5e2ecd1bf05534eda382aa14c3e4f9205ba5b1684ecfe400716ef2"}, + {file = "contourpy-1.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:523a8ee12edfa36f6d2a49407f705a6ef4c5098de4f498619787e272de93f2d5"}, + {file = "contourpy-1.3.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece6df05e2c41bd46776fbc712e0996f7c94e0d0543af1656956d150c4ca7c81"}, + {file = "contourpy-1.3.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:573abb30e0e05bf31ed067d2f82500ecfdaec15627a59d63ea2d95714790f5c2"}, + {file = "contourpy-1.3.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9fa36448e6a3a1a9a2ba23c02012c43ed88905ec80163f2ffe2421c7192a5d7"}, + {file = "contourpy-1.3.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ea9924d28fc5586bf0b42d15f590b10c224117e74409dd7a0be3b62b74a501c"}, + {file = "contourpy-1.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5b75aa69cb4d6f137b36f7eb2ace9280cfb60c55dc5f61c731fdf6f037f958a3"}, + {file = "contourpy-1.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:041b640d4ec01922083645a94bb3b2e777e6b626788f4095cf21abbe266413c1"}, + {file = "contourpy-1.3.1-cp313-cp313-win32.whl", hash = "sha256:36987a15e8ace5f58d4d5da9dca82d498c2bbb28dff6e5d04fbfcc35a9cb3a82"}, + {file = "contourpy-1.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:a7895f46d47671fa7ceec40f31fae721da51ad34bdca0bee83e38870b1f47ffd"}, + {file = "contourpy-1.3.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:9ddeb796389dadcd884c7eb07bd14ef12408aaae358f0e2ae24114d797eede30"}, + {file = "contourpy-1.3.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:19c1555a6801c2f084c7ddc1c6e11f02eb6a6016ca1318dd5452ba3f613a1751"}, + {file = "contourpy-1.3.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:841ad858cff65c2c04bf93875e384ccb82b654574a6d7f30453a04f04af71342"}, + {file = "contourpy-1.3.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4318af1c925fb9a4fb190559ef3eec206845f63e80fb603d47f2d6d67683901c"}, + {file = "contourpy-1.3.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:14c102b0eab282427b662cb590f2e9340a9d91a1c297f48729431f2dcd16e14f"}, + {file = "contourpy-1.3.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05e806338bfeaa006acbdeba0ad681a10be63b26e1b17317bfac3c5d98f36cda"}, + {file = "contourpy-1.3.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4d76d5993a34ef3df5181ba3c92fabb93f1eaa5729504fb03423fcd9f3177242"}, + {file = "contourpy-1.3.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:89785bb2a1980c1bd87f0cb1517a71cde374776a5f150936b82580ae6ead44a1"}, + {file = "contourpy-1.3.1-cp313-cp313t-win32.whl", hash = "sha256:8eb96e79b9f3dcadbad2a3891672f81cdcab7f95b27f28f1c67d75f045b6b4f1"}, + {file = "contourpy-1.3.1-cp313-cp313t-win_amd64.whl", hash = "sha256:287ccc248c9e0d0566934e7d606201abd74761b5703d804ff3df8935f523d546"}, + {file = "contourpy-1.3.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b457d6430833cee8e4b8e9b6f07aa1c161e5e0d52e118dc102c8f9bd7dd060d6"}, + {file = "contourpy-1.3.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb76c1a154b83991a3cbbf0dfeb26ec2833ad56f95540b442c73950af2013750"}, + {file = "contourpy-1.3.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:44a29502ca9c7b5ba389e620d44f2fbe792b1fb5734e8b931ad307071ec58c53"}, + {file = "contourpy-1.3.1.tar.gz", hash = "sha256:dfd97abd83335045a913e3bcc4a09c0ceadbe66580cf573fe961f4a825efa699"}, +] + +[package.dependencies] +numpy = ">=1.23" + +[package.extras] +bokeh = ["bokeh", "selenium"] +docs = ["furo", "sphinx (>=7.2)", "sphinx-copybutton"] +mypy = ["contourpy[bokeh,docs]", "docutils-stubs", "mypy (==1.11.1)", "types-Pillow"] +test = ["Pillow", "contourpy[test-no-images]", "matplotlib"] +test-no-images = ["pytest", "pytest-cov", "pytest-rerunfailures", "pytest-xdist", "wurlitzer"] [[package]] name = "coverage" @@ -691,6 +748,7 @@ version = "7.6.10" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "coverage-7.6.10-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5c912978f7fbf47ef99cec50c4401340436d200d41d714c7a4766f377c5b7b78"}, {file = "coverage-7.6.10-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a01ec4af7dfeb96ff0078ad9a48810bb0cc8abcb0115180c6013a6b26237626c"}, @@ -765,6 +823,7 @@ version = "44.0.0" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = "!=3.9.0,!=3.9.1,>=3.7" +groups = ["main"] files = [ {file = "cryptography-44.0.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:84111ad4ff3f6253820e6d3e58be2cc2a00adb29335d4cacb5ab4d4d34f2a123"}, {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b15492a11f9e1b62ba9d73c210e2416724633167de94607ec6069ef724fad092"}, @@ -814,6 +873,7 @@ version = "1.15.1" description = "CSS unobfuscator and beautifier." optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "cssbeautifier-1.15.1.tar.gz", hash = "sha256:9f7064362aedd559c55eeecf6b6bed65e05f33488dcbe39044f0403c26e1c006"}, ] @@ -829,6 +889,7 @@ version = "0.7.0" description = "CSS selectors for Python ElementTree" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "cssselect2-0.7.0-py3-none-any.whl", hash = "sha256:fd23a65bfd444595913f02fc71f6b286c29261e354c41d722ca7a261a49b5969"}, {file = "cssselect2-0.7.0.tar.gz", hash = "sha256:1ccd984dab89fc68955043aca4e1b03e0cf29cad9880f6e28e3ba7a74b14aa5a"}, @@ -842,12 +903,29 @@ webencodings = "*" doc = ["sphinx", "sphinx_rtd_theme"] test = ["flake8", "isort", "pytest"] +[[package]] +name = "cycler" +version = "0.12.1" +description = "Composable style cycles" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30"}, + {file = "cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c"}, +] + +[package.extras] +docs = ["ipython", "matplotlib", "numpydoc", "sphinx"] +tests = ["pytest", "pytest-cov", "pytest-xdist"] + [[package]] name = "decorator" version = "5.1.1" description = "Decorators for Humans" optional = false python-versions = ">=3.5" +groups = ["main", "dev"] files = [ {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, @@ -855,20 +933,43 @@ files = [ [[package]] name = "deprecated" -version = "1.2.15" +version = "1.2.18" description = "Python @deprecated decorator to deprecate old python classes, functions or methods." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" +groups = ["main"] files = [ - {file = "Deprecated-1.2.15-py2.py3-none-any.whl", hash = "sha256:353bc4a8ac4bfc96800ddab349d89c25dec1079f65fd53acdcc1e0b975b21320"}, - {file = "deprecated-1.2.15.tar.gz", hash = "sha256:683e561a90de76239796e6b6feac66b99030d2dd3fcf61ef996330f14bbb9b0d"}, + {file = "Deprecated-1.2.18-py2.py3-none-any.whl", hash = "sha256:bd5011788200372a32418f888e326a09ff80d0214bd961147cfed01b5c018eec"}, + {file = "deprecated-1.2.18.tar.gz", hash = "sha256:422b6f6d859da6f2ef57857761bfb392480502a64c3028ca9bbe86085d72115d"}, ] [package.dependencies] wrapt = ">=1.10,<2" [package.extras] -dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "jinja2 (>=3.0.3,<3.1.0)", "setuptools", "sphinx (<2)", "tox"] +dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "setuptools", "tox"] + +[[package]] +name = "distinctipy" +version = "1.3.4" +description = "A lightweight package for generating visually distinct colours." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "distinctipy-1.3.4-py3-none-any.whl", hash = "sha256:2bf57d9d20dbc5c2fd462298573cc963c037f493d04ec61e94cb8d0bf5023c74"}, + {file = "distinctipy-1.3.4.tar.gz", hash = "sha256:fed97afff1afb73ecaa87c85461021f0ba89fae63067c0125b9673526510aac4"}, +] + +[package.dependencies] +matplotlib = {version = ">=3.1.0", optional = true, markers = "extra == \"extras\""} +numpy = ">=1.16.3" +pandas = {version = ">=0.24.2", optional = true, markers = "extra == \"extras\""} + +[package.extras] +docs = ["ipython (>=7.34.0)", "nbsphinx (>=0.8.0)", "sphinx-rtd-theme (>=0.2.3)"] +extras = ["matplotlib (>=3.1.0)", "pandas (>=0.24.2)"] +tests = ["black (==22.6.0)", "codecov (>=2.0.15)", "coverage (>=5.3.1)", "flake8 (==4.0.1)", "isort (==5.10.1)", "pytest (>=4.6.0)", "pytest-cov (>=2.9.0)"] [[package]] name = "distlib" @@ -876,6 +977,7 @@ version = "0.3.9" description = "Distribution utilities" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87"}, {file = "distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403"}, @@ -883,13 +985,14 @@ files = [ [[package]] name = "django" -version = "5.1.4" +version = "5.1.5" description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design." optional = false python-versions = ">=3.10" +groups = ["main", "dev"] files = [ - {file = "Django-5.1.4-py3-none-any.whl", hash = "sha256:236e023f021f5ce7dee5779de7b286565fdea5f4ab86bae5338e3f7b69896cf0"}, - {file = "Django-5.1.4.tar.gz", hash = "sha256:de450c09e91879fa5a307f696e57c851955c910a438a35e6b4c895e86bedc82a"}, + {file = "Django-5.1.5-py3-none-any.whl", hash = "sha256:c46eb936111fffe6ec4bc9930035524a8be98ec2f74d8a0ff351226a3e52f459"}, + {file = "Django-5.1.5.tar.gz", hash = "sha256:19bbca786df50b9eca23cee79d495facf55c8f5c54c529d9bf1fe7b5ea086af3"}, ] [package.dependencies] @@ -907,6 +1010,7 @@ version = "1.0.6" description = "A helper class for handling configuration defaults of packaged apps gracefully." optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "django-appconf-1.0.6.tar.gz", hash = "sha256:cfe87ea827c4ee04b9a70fab90b86d704cb02f2981f89da8423cb0fabf88efbf"}, {file = "django_appconf-1.0.6-py3-none-any.whl", hash = "sha256:c3ae442fba1ff7ec830412c5184b17169a7a1e71cf0864a4c3f93cf4c98a1993"}, @@ -921,6 +1025,7 @@ version = "4.8.0" description = "Django LDAP authentication backend" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "django-auth-ldap-4.8.0.tar.gz", hash = "sha256:604250938ddc9fda619f247c7a59b0b2f06e53a7d3f46a156f28aa30dd71a738"}, {file = "django_auth_ldap-4.8.0-py3-none-any.whl", hash = "sha256:4b4b944f3c28bce362f33fb6e8db68429ed8fd8f12f0c0c4b1a4344a7ef225ce"}, @@ -936,6 +1041,7 @@ version = "5.0.5" description = "Bootstrap3/Bootstrap4/Bootstrap5 DatePickerInput, TimePickerInput, DateTimePickerInput, MonthPickerInput, YearPickerInput" optional = false python-versions = ">=3.8,<4.0" +groups = ["main"] files = [ {file = "django_bootstrap_datepicker_plus-5.0.5-py3-none-any.whl", hash = "sha256:f0818dcaca6824f2bc3b42b53e2cd0e42e9ceb2c8802254f9d38d4cce04b31d8"}, {file = "django_bootstrap_datepicker_plus-5.0.5.tar.gz", hash = "sha256:ea5e2bc2137a72b57ba10d2a5f18373049b0d252052f921e5a76c260eaaa35ee"}, @@ -952,6 +1058,7 @@ version = "23.4" description = "Bootstrap 5 for Django" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "django-bootstrap5-23.4.tar.gz", hash = "sha256:fbf9942a17e1f48b4142e78df9e85afb65e4066a20e38ec7c497d36ae5ef7256"}, {file = "django_bootstrap5-23.4-py3-none-any.whl", hash = "sha256:5181bf1e97afae6211e963f28f48d4a90c937a3b036b3f752f52260f0029f3bc"}, @@ -966,6 +1073,7 @@ version = "2.3.0" description = "Django Chart.js and Hightchart ajax views" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "django-chartjs-2.3.0.tar.gz", hash = "sha256:576ad20cfb6d18efcd3142784752cc8f7445601180d74fbaa6af9fd2f866899b"}, {file = "django_chartjs-2.3.0-py3-none-any.whl", hash = "sha256:3cc1660ff1403c021b3ede163062120e36fde194e97e1a84a7eb1a8f09223183"}, @@ -977,6 +1085,7 @@ version = "4.6.0" description = "django-cors-headers is a Django application for handling the server headers required for Cross-Origin Resource Sharing (CORS)." optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "django_cors_headers-4.6.0-py3-none-any.whl", hash = "sha256:8edbc0497e611c24d5150e0055d3b178c6534b8ed826fb6f53b21c63f5d48ba3"}, {file = "django_cors_headers-4.6.0.tar.gz", hash = "sha256:14d76b4b4c8d39375baeddd89e4f08899051eeaf177cb02a29bd6eae8cf63aa8"}, @@ -992,6 +1101,7 @@ version = "0.11.2" description = "A package that allows you to utilize 12factor inspired environment variables to configure your Django application." optional = false python-versions = ">=3.6,<4" +groups = ["main"] files = [ {file = "django-environ-0.11.2.tar.gz", hash = "sha256:f32a87aa0899894c27d4e1776fa6b477e8164ed7f6b3e410a62a6d72caaf64be"}, {file = "django_environ-0.11.2-py2.py3-none-any.whl", hash = "sha256:0ff95ab4344bfeff693836aa978e6840abef2e2f1145adff7735892711590c05"}, @@ -1008,6 +1118,7 @@ version = "3.2.3" description = "Extensions for Django" optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "django-extensions-3.2.3.tar.gz", hash = "sha256:44d27919d04e23b3f40231c4ab7af4e61ce832ef46d610cc650d53e68328410a"}, {file = "django_extensions-3.2.3-py3-none-any.whl", hash = "sha256:9600b7562f79a92cbf1fde6403c04fee314608fefbb595502e34383ae8203401"}, @@ -1022,6 +1133,7 @@ version = "23.5" description = "Django-filter is a reusable Django application for allowing users to filter querysets dynamically." optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "django-filter-23.5.tar.gz", hash = "sha256:67583aa43b91fe8c49f74a832d95f4d8442be628fd4c6d65e9f811f5153a4e5c"}, {file = "django_filter-23.5-py3-none-any.whl", hash = "sha256:99122a201d83860aef4fe77758b69dda913e874cc5e0eaa50a86b0b18d708400"}, @@ -1036,6 +1148,7 @@ version = "8.0.0" description = "An international phone number field for django models." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "django_phonenumber_field-8.0.0-py3-none-any.whl", hash = "sha256:196c917b70c01a98e327f482eb8a4a4a55a29891db551f99078585397370b3ba"}, {file = "django_phonenumber_field-8.0.0.tar.gz", hash = "sha256:8a560fe1b01b94c9de8cde22bc373b695f023cc6df4baba00264cb079da9f631"}, @@ -1054,6 +1167,7 @@ version = "8.2.3" description = "This is a Django_ integration of Select2_." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "django_select2-8.2.3-py3-none-any.whl", hash = "sha256:a3bc3f3bfb21d73791be1bc0dd8cc674d11e167129d605847c257942e7a2143b"}, {file = "django_select2-8.2.3.tar.gz", hash = "sha256:1de547c89ee2f005f1d461f76db89b440897939fcea894db8ba756b6f1ad36c1"}, @@ -1074,6 +1188,7 @@ version = "5.3.2" description = "Silky smooth profiling for the Django Framework" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "django_silk-5.3.2-py3-none-any.whl", hash = "sha256:49f1caebfda28b1707f0cfef524e0476beb82b8c5e40f5ccff7f73a6b4f6d3ac"}, {file = "django_silk-5.3.2.tar.gz", hash = "sha256:b0db54eebedb8d16f572321bd6daccac0bd3f547ae2618bb45d96fe8fc02229d"}, @@ -1087,13 +1202,14 @@ sqlparse = "*" [[package]] name = "django-tables2" -version = "2.7.4" +version = "2.7.5" description = "Table/data-grid framework for Django" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "django_tables2-2.7.4-py3-none-any.whl", hash = "sha256:1f9373bdfea7f8ec5bef80542f58c1ca2eb19f86a8bec90936eccea6f1564347"}, - {file = "django_tables2-2.7.4.tar.gz", hash = "sha256:25c22986d88aa5d911a0f0175bcdfbeaeab4f5fc086ac58b02a29daa8bc3e5a8"}, + {file = "django_tables2-2.7.5-py3-none-any.whl", hash = "sha256:d9338937797207ffb6f481be2125c5ec3a0bb1858d409c672cc25fc5d654cb22"}, + {file = "django_tables2-2.7.5.tar.gz", hash = "sha256:fb5dcaa09379cf3947598ec7e1bd5f26ed63aafdee3b23963446763bbeac37bf"}, ] [package.dependencies] @@ -1108,6 +1224,7 @@ version = "1.22.2" description = "Automatically upgrade your Django project code." optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "django_upgrade-1.22.2-py3-none-any.whl", hash = "sha256:fb76b183b2e8186bb1157734083569a4b754dce2e812c2a723454bd248fb1373"}, {file = "django_upgrade-1.22.2.tar.gz", hash = "sha256:8643abdde2961e3c60173ebee84f48371ffbec5181095a38cac080947af0d2ae"}, @@ -1118,13 +1235,14 @@ tokenize-rt = ">=4.1" [[package]] name = "django-vite" -version = "3.0.5" +version = "3.0.6" description = "Integration of Vite in a Django project." optional = false python-versions = "*" +groups = ["main"] files = [ - {file = "django_vite-3.0.5-py3-none-any.whl", hash = "sha256:049b74f38c999cbfcf0e2c21b254c2e059bb97bfd7e4049caf2d0f9fba0b482f"}, - {file = "django_vite-3.0.5.tar.gz", hash = "sha256:431c1212e7627adc20666d150578f1a8983f043e90f3905778fb3c5c0ffe6963"}, + {file = "django_vite-3.0.6-py3-none-any.whl", hash = "sha256:3ad3716e2afe4569353cda8080f557bac37a5b29ab7a5826d5defd67bfbe8e72"}, + {file = "django_vite-3.0.6.tar.gz", hash = "sha256:363ca9fd774eb228b27f710a9179c68f61e27bb95b41b1f790ee5a0a0e215b14"}, ] [package.dependencies] @@ -1135,18 +1253,19 @@ dev = ["black"] [[package]] name = "django-weasyprint" -version = "2.3.0" +version = "2.3.1" description = "Django WeasyPrint CBV" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ - {file = "django-weasyprint-2.3.0.tar.gz", hash = "sha256:2f849e15bfd6c1b2a58512097b9042eddf3533651d37d2e096cd6f7d8be6442b"}, - {file = "django_weasyprint-2.3.0-py3-none-any.whl", hash = "sha256:807cb3b16332123d97c8bbe2ac9c70286103fe353235351803ffd33b67284735"}, + {file = "django_weasyprint-2.3.1-py3-none-any.whl", hash = "sha256:09cc1c40c92db34bed80154be7c959fea03d6001dc46fd599f3fd464d6a6dc72"}, + {file = "django_weasyprint-2.3.1.tar.gz", hash = "sha256:cd35b8bd24b28128a17a2416d0e6f3e64cb727f25c53467150b4be16ccd01c19"}, ] [package.dependencies] Django = ">=3.2" -WeasyPrint = ">=53" +WeasyPrint = ">=59" [[package]] name = "djangorestframework" @@ -1154,6 +1273,7 @@ version = "3.15.2" description = "Web APIs for Django, made easy." optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "djangorestframework-3.15.2-py3-none-any.whl", hash = "sha256:2b8871b062ba1aefc2de01f773875441a961fefbf79f5eed1e32b2f096944b20"}, {file = "djangorestframework-3.15.2.tar.gz", hash = "sha256:36fe88cd2d6c6bec23dca9804bab2ba5517a8bb9d8f47ebc68981b56840107ad"}, @@ -1168,6 +1288,7 @@ version = "1.36.4" description = "HTML Template Linter and Formatter" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "djlint-1.36.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a2dfb60883ceb92465201bfd392291a7597c6752baede6fbb6f1980cac8d6c5c"}, {file = "djlint-1.36.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4bc6a1320c0030244b530ac200642f883d3daa451a115920ef3d56d08b644292"}, @@ -1210,6 +1331,7 @@ version = "0.27.2" description = "Sane and flexible OpenAPI 3 schema generation for Django REST framework" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "drf-spectacular-0.27.2.tar.gz", hash = "sha256:a199492f2163c4101055075ebdbb037d59c6e0030692fc83a1a8c0fc65929981"}, {file = "drf_spectacular-0.27.2-py3-none-any.whl", hash = "sha256:b1c04bf8b2fbbeaf6f59414b4ea448c8787aba4d32f76055c3b13335cf7ec37b"}, @@ -1233,6 +1355,7 @@ version = "0.17.0" description = "EditorConfig File Locator and Interpreter for Python" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "EditorConfig-0.17.0-py3-none-any.whl", hash = "sha256:fe491719c5f65959ec00b167d07740e7ffec9a3f362038c72b289330b9991dfc"}, {file = "editorconfig-0.17.0.tar.gz", hash = "sha256:8739052279699840065d3a9f5c125d7d5a98daeefe53b0e5274261d77cb49aa2"}, @@ -1240,13 +1363,14 @@ files = [ [[package]] name = "executing" -version = "2.1.0" +version = "2.2.0" description = "Get the currently executing AST node of a frame, and other information" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ - {file = "executing-2.1.0-py2.py3-none-any.whl", hash = "sha256:8d63781349375b5ebccc3142f4b30350c0cd9c79f921cde38be2be4637e98eaf"}, - {file = "executing-2.1.0.tar.gz", hash = "sha256:8ea27ddd260da8150fa5a708269c4a10e76161e2496ec3e587da9e3c0fe4b9ab"}, + {file = "executing-2.2.0-py2.py3-none-any.whl", hash = "sha256:11387150cad388d62750327a53d3339fad4888b39a6fe233c3afbb54ecffd3aa"}, + {file = "executing-2.2.0.tar.gz", hash = "sha256:5d108c028108fe2551d1a7b2e8b713341e2cb4fc0aa7dcf966fa4327a5226755"}, ] [package.extras] @@ -1258,6 +1382,7 @@ version = "3.2.2" description = "High level SSH command execution" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "fabric-3.2.2-py3-none-any.whl", hash = "sha256:91c47c0be68b14936c88b34da8a1f55e5710fd28397dac5d4ff2e21558113a6f"}, {file = "fabric-3.2.2.tar.gz", hash = "sha256:8783ca42e3b0076f08b26901aac6b9d9b1f19c410074e7accfab902c184ff4a3"}, @@ -1278,6 +1403,7 @@ version = "3.3.1" description = "A versatile test fixtures replacement based on thoughtbot's factory_bot for Ruby." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "factory_boy-3.3.1-py2.py3-none-any.whl", hash = "sha256:7b1113c49736e1e9995bc2a18f4dbf2c52cf0f841103517010b1d825712ce3ca"}, {file = "factory_boy-3.3.1.tar.gz", hash = "sha256:8317aa5289cdfc45f9cae570feb07a6177316c82e34d14df3c2e1f22f26abef0"}, @@ -1292,13 +1418,14 @@ doc = ["Sphinx", "sphinx-rtd-theme", "sphinxcontrib-spelling"] [[package]] name = "faker" -version = "33.1.0" +version = "35.0.0" description = "Faker is a Python package that generates fake data for you." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ - {file = "Faker-33.1.0-py3-none-any.whl", hash = "sha256:d30c5f0e2796b8970de68978365247657486eb0311c5abe88d0b895b68dff05d"}, - {file = "faker-33.1.0.tar.gz", hash = "sha256:1c925fc0e86a51fc46648b504078c88d0cd48da1da2595c4e712841cab43a1e4"}, + {file = "Faker-35.0.0-py3-none-any.whl", hash = "sha256:926d2301787220e0554c2e39afc4dc535ce4b0a8d0a089657137999f66334ef4"}, + {file = "faker-35.0.0.tar.gz", hash = "sha256:42f2da8cf561e38c72b25e9891168b1e25fec42b6b0b5b0b6cd6041da54af885"}, ] [package.dependencies] @@ -1307,77 +1434,79 @@ typing-extensions = "*" [[package]] name = "filelock" -version = "3.16.1" +version = "3.17.0" description = "A platform independent file lock." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["dev"] files = [ - {file = "filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0"}, - {file = "filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435"}, + {file = "filelock-3.17.0-py3-none-any.whl", hash = "sha256:533dc2f7ba78dc2f0f531fc6c4940addf7b70a481e269a5a3b93be94ffbe8338"}, + {file = "filelock-3.17.0.tar.gz", hash = "sha256:ee4e77401ef576ebb38cd7f13b9b28893194acc20a8e68e18730ba9c0e54660e"}, ] [package.extras] -docs = ["furo (>=2024.8.6)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4.1)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.6.1)", "diff-cover (>=9.2)", "pytest (>=8.3.3)", "pytest-asyncio (>=0.24)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.26.4)"] +docs = ["furo (>=2024.8.6)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.6.10)", "diff-cover (>=9.2.1)", "pytest (>=8.3.4)", "pytest-asyncio (>=0.25.2)", "pytest-cov (>=6)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.28.1)"] typing = ["typing-extensions (>=4.12.2)"] [[package]] name = "fonttools" -version = "4.55.3" +version = "4.55.7" description = "Tools to manipulate font files" optional = false python-versions = ">=3.8" -files = [ - {file = "fonttools-4.55.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1dcc07934a2165ccdc3a5a608db56fb3c24b609658a5b340aee4ecf3ba679dc0"}, - {file = "fonttools-4.55.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f7d66c15ba875432a2d2fb419523f5d3d347f91f48f57b8b08a2dfc3c39b8a3f"}, - {file = "fonttools-4.55.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27e4ae3592e62eba83cd2c4ccd9462dcfa603ff78e09110680a5444c6925d841"}, - {file = "fonttools-4.55.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:62d65a3022c35e404d19ca14f291c89cc5890032ff04f6c17af0bd1927299674"}, - {file = "fonttools-4.55.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d342e88764fb201286d185093781bf6628bbe380a913c24adf772d901baa8276"}, - {file = "fonttools-4.55.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:dd68c87a2bfe37c5b33bcda0fba39b65a353876d3b9006fde3adae31f97b3ef5"}, - {file = "fonttools-4.55.3-cp310-cp310-win32.whl", hash = "sha256:1bc7ad24ff98846282eef1cbeac05d013c2154f977a79886bb943015d2b1b261"}, - {file = "fonttools-4.55.3-cp310-cp310-win_amd64.whl", hash = "sha256:b54baf65c52952db65df39fcd4820668d0ef4766c0ccdf32879b77f7c804d5c5"}, - {file = "fonttools-4.55.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8c4491699bad88efe95772543cd49870cf756b019ad56294f6498982408ab03e"}, - {file = "fonttools-4.55.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5323a22eabddf4b24f66d26894f1229261021dacd9d29e89f7872dd8c63f0b8b"}, - {file = "fonttools-4.55.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5480673f599ad410695ca2ddef2dfefe9df779a9a5cda89503881e503c9c7d90"}, - {file = "fonttools-4.55.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da9da6d65cd7aa6b0f806556f4985bcbf603bf0c5c590e61b43aa3e5a0f822d0"}, - {file = "fonttools-4.55.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e894b5bd60d9f473bed7a8f506515549cc194de08064d829464088d23097331b"}, - {file = "fonttools-4.55.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:aee3b57643827e237ff6ec6d28d9ff9766bd8b21e08cd13bff479e13d4b14765"}, - {file = "fonttools-4.55.3-cp311-cp311-win32.whl", hash = "sha256:eb6ca911c4c17eb51853143624d8dc87cdcdf12a711fc38bf5bd21521e79715f"}, - {file = "fonttools-4.55.3-cp311-cp311-win_amd64.whl", hash = "sha256:6314bf82c54c53c71805318fcf6786d986461622dd926d92a465199ff54b1b72"}, - {file = "fonttools-4.55.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f9e736f60f4911061235603a6119e72053073a12c6d7904011df2d8fad2c0e35"}, - {file = "fonttools-4.55.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7a8aa2c5e5b8b3bcb2e4538d929f6589a5c6bdb84fd16e2ed92649fb5454f11c"}, - {file = "fonttools-4.55.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:07f8288aacf0a38d174445fc78377a97fb0b83cfe352a90c9d9c1400571963c7"}, - {file = "fonttools-4.55.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8d5e8916c0970fbc0f6f1bece0063363bb5857a7f170121a4493e31c3db3314"}, - {file = "fonttools-4.55.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ae3b6600565b2d80b7c05acb8e24d2b26ac407b27a3f2e078229721ba5698427"}, - {file = "fonttools-4.55.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:54153c49913f45065c8d9e6d0c101396725c5621c8aee744719300f79771d75a"}, - {file = "fonttools-4.55.3-cp312-cp312-win32.whl", hash = "sha256:827e95fdbbd3e51f8b459af5ea10ecb4e30af50221ca103bea68218e9615de07"}, - {file = "fonttools-4.55.3-cp312-cp312-win_amd64.whl", hash = "sha256:e6e8766eeeb2de759e862004aa11a9ea3d6f6d5ec710551a88b476192b64fd54"}, - {file = "fonttools-4.55.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a430178ad3e650e695167cb53242dae3477b35c95bef6525b074d87493c4bf29"}, - {file = "fonttools-4.55.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:529cef2ce91dc44f8e407cc567fae6e49a1786f2fefefa73a294704c415322a4"}, - {file = "fonttools-4.55.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e75f12c82127486fac2d8bfbf5bf058202f54bf4f158d367e41647b972342ca"}, - {file = "fonttools-4.55.3-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:859c358ebf41db18fb72342d3080bce67c02b39e86b9fbcf1610cca14984841b"}, - {file = "fonttools-4.55.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:546565028e244a701f73df6d8dd6be489d01617863ec0c6a42fa25bf45d43048"}, - {file = "fonttools-4.55.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:aca318b77f23523309eec4475d1fbbb00a6b133eb766a8bdc401faba91261abe"}, - {file = "fonttools-4.55.3-cp313-cp313-win32.whl", hash = "sha256:8c5ec45428edaa7022f1c949a632a6f298edc7b481312fc7dc258921e9399628"}, - {file = "fonttools-4.55.3-cp313-cp313-win_amd64.whl", hash = "sha256:11e5de1ee0d95af4ae23c1a138b184b7f06e0b6abacabf1d0db41c90b03d834b"}, - {file = "fonttools-4.55.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:caf8230f3e10f8f5d7593eb6d252a37caf58c480b19a17e250a63dad63834cf3"}, - {file = "fonttools-4.55.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b586ab5b15b6097f2fb71cafa3c98edfd0dba1ad8027229e7b1e204a58b0e09d"}, - {file = "fonttools-4.55.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a8c2794ded89399cc2169c4d0bf7941247b8d5932b2659e09834adfbb01589aa"}, - {file = "fonttools-4.55.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf4fe7c124aa3f4e4c1940880156e13f2f4d98170d35c749e6b4f119a872551e"}, - {file = "fonttools-4.55.3-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:86721fbc389ef5cc1e2f477019e5069e8e4421e8d9576e9c26f840dbb04678de"}, - {file = "fonttools-4.55.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:89bdc5d88bdeec1b15af790810e267e8332d92561dce4f0748c2b95c9bdf3926"}, - {file = "fonttools-4.55.3-cp38-cp38-win32.whl", hash = "sha256:bc5dbb4685e51235ef487e4bd501ddfc49be5aede5e40f4cefcccabc6e60fb4b"}, - {file = "fonttools-4.55.3-cp38-cp38-win_amd64.whl", hash = "sha256:cd70de1a52a8ee2d1877b6293af8a2484ac82514f10b1c67c1c5762d38073e56"}, - {file = "fonttools-4.55.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bdcc9f04b36c6c20978d3f060e5323a43f6222accc4e7fcbef3f428e216d96af"}, - {file = "fonttools-4.55.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c3ca99e0d460eff46e033cd3992a969658c3169ffcd533e0a39c63a38beb6831"}, - {file = "fonttools-4.55.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22f38464daa6cdb7b6aebd14ab06609328fe1e9705bb0fcc7d1e69de7109ee02"}, - {file = "fonttools-4.55.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed63959d00b61959b035c7d47f9313c2c1ece090ff63afea702fe86de00dbed4"}, - {file = "fonttools-4.55.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5e8d657cd7326eeaba27de2740e847c6b39dde2f8d7cd7cc56f6aad404ddf0bd"}, - {file = "fonttools-4.55.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:fb594b5a99943042c702c550d5494bdd7577f6ef19b0bc73877c948a63184a32"}, - {file = "fonttools-4.55.3-cp39-cp39-win32.whl", hash = "sha256:dc5294a3d5c84226e3dbba1b6f61d7ad813a8c0238fceea4e09aa04848c3d851"}, - {file = "fonttools-4.55.3-cp39-cp39-win_amd64.whl", hash = "sha256:aedbeb1db64496d098e6be92b2e63b5fac4e53b1b92032dfc6988e1ea9134a4d"}, - {file = "fonttools-4.55.3-py3-none-any.whl", hash = "sha256:f412604ccbeee81b091b420272841e5ec5ef68967a9790e80bffd0e30b8e2977"}, - {file = "fonttools-4.55.3.tar.gz", hash = "sha256:3983313c2a04d6cc1fe9251f8fc647754cf49a61dac6cb1e7249ae67afaafc45"}, +groups = ["main"] +files = [ + {file = "fonttools-4.55.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c2680a3e6e2e2d104a7ea81fb89323e1a9122c23b03d6569d0768887d0d76e69"}, + {file = "fonttools-4.55.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a7831d16c95b60866772a15fdcc03772625c4bb6d858e0ad8ef3d6e48709b2ef"}, + {file = "fonttools-4.55.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:833927d089e6585019f2c85e3f8f7d87733e3fe81cd704ebaca7afa27e2e7113"}, + {file = "fonttools-4.55.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7858dc6823296a053d85b831fa8428781c6c6f06fca44582bf7b6b2ff32a9089"}, + {file = "fonttools-4.55.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:05568a66b090ed9d79aefdce2ceb180bb64fc856961deaedc29f5ad51355ce2c"}, + {file = "fonttools-4.55.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2dbc08e227fbeb716776905a7bd3c4fc62c8e37c8ef7d481acd10cb5fde12222"}, + {file = "fonttools-4.55.7-cp310-cp310-win32.whl", hash = "sha256:6eb93cbba484a463b5ee83f7dd3211905f27a3871d20d90fb72de84c6c5056e3"}, + {file = "fonttools-4.55.7-cp310-cp310-win_amd64.whl", hash = "sha256:7ff8e606f905048dc91a55a06d994b68065bf35752ae199df54a9bf30013dcaa"}, + {file = "fonttools-4.55.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:916e1d926823b4b3b3815c59fc79f4ed670696fdd5fd9a5e690a0503eef38f79"}, + {file = "fonttools-4.55.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b89da448e0073408d7b2c44935f9fdae4fdc93644899f99f6102ef883ecf083c"}, + {file = "fonttools-4.55.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:087ace2d06894ccdb03e6975d05da6bb9cec0c689b2a9983c059880e33a1464a"}, + {file = "fonttools-4.55.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:775ed0700ee6f781436641f18a0c61b1846a8c1aecae6da6b395c4417e2cb567"}, + {file = "fonttools-4.55.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9ec71d0cc0242899f87e4c230ed0b22c7b8681f288fb80e3d81c2c54c5bd2c79"}, + {file = "fonttools-4.55.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d4b1c5939c0521525f45522823508e6fad21175bca978583688ea3b3736e6625"}, + {file = "fonttools-4.55.7-cp311-cp311-win32.whl", hash = "sha256:23df0f1003abaf8a435543f59583fc247e7ae1b047ee2263510e0654a5f207e0"}, + {file = "fonttools-4.55.7-cp311-cp311-win_amd64.whl", hash = "sha256:82163d58b43eff6e2025a25c32905fdb9042a163cc1ff82dab393e7ffc77a7d5"}, + {file = "fonttools-4.55.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:12e81d44f762156d28b5c93a6b65d98ed73678be45b22546de8ed29736c3cb96"}, + {file = "fonttools-4.55.7-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c26445a7be689f8b70df7d5d2e2c85ec4407bdb769902a23dd45ac44f767575d"}, + {file = "fonttools-4.55.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2cbafedb9462be7cf68c66b6ca1d8309842fe36b729f1b1969595f5d660e5c2"}, + {file = "fonttools-4.55.7-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4bde87985012adbd7559bc363d802fb335e92a07ff86a76cf02bebb0b8566d1"}, + {file = "fonttools-4.55.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:69ed0660750993150f7c4d966c0c1ffaa0385f23ccef85c2ff108062d80dd7ea"}, + {file = "fonttools-4.55.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3098355e7a7b5ac48d5dc29684a65271187b865b85675033958b57c40364ee34"}, + {file = "fonttools-4.55.7-cp312-cp312-win32.whl", hash = "sha256:ee7aa8bb716318e3d835ef473978e22b7a39c0f1b3b08cc0b0ee1bba6f73bc1e"}, + {file = "fonttools-4.55.7-cp312-cp312-win_amd64.whl", hash = "sha256:e696d6e2baf4cc57ded34bb87e5d3a9e4da9732f3d9e8e2c6db0746e57a6dc0b"}, + {file = "fonttools-4.55.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e10c7fb80cdfdc32244514cbea0906e9f53e3cc80d64d3389da09502fd999b55"}, + {file = "fonttools-4.55.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1101976c703ff4008a928fc3fef42caf06d035bfc4614230d7e797cbe356feb0"}, + {file = "fonttools-4.55.7-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e6dffe9cbcd163ef617fab1f81682e4d1629b7a5b9c5e598274dc2d03e88bcd"}, + {file = "fonttools-4.55.7-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77e5115a425d53be6e31cd0fe9210f62a488bccf81eb113ab5dd7f4fa88e4d81"}, + {file = "fonttools-4.55.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f0c45eae32d090763820756b18322a70571dada3f1cbe003debc37a9c35bc260"}, + {file = "fonttools-4.55.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fd4ebc475d43f3de2b26e0cf551eff92c24e22d1aee03dc1b33adb52fc2e6cb2"}, + {file = "fonttools-4.55.7-cp313-cp313-win32.whl", hash = "sha256:371197de1283cc99f5f10eb91496520eb0e2d079312d014fd6cef9e802174c6a"}, + {file = "fonttools-4.55.7-cp313-cp313-win_amd64.whl", hash = "sha256:418ece624fbc04e199f58398ffef3eaad645baba65434871b09eb7350a3a346b"}, + {file = "fonttools-4.55.7-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:3976db357484bf4cb533dfd0d1a444b38ad06062458715ebf21e38c71aff325d"}, + {file = "fonttools-4.55.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:30c3501328363b73a90acc8a722dd199c993f2c4369ea16886128d94e91897ec"}, + {file = "fonttools-4.55.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f0899cd23967950e7b902ea75af06cfe5f59ac71eb38e98a774c9e596790e6aa"}, + {file = "fonttools-4.55.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f669910b64d27750398f6c56c651367d4954b05c86ff067af1c9949e109cf1e2"}, + {file = "fonttools-4.55.7-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:1d4be8354c245c00aecfc90f5d3da8606226f0ac22e1cb0837b39139e4c2df85"}, + {file = "fonttools-4.55.7-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:9074a2848ea5b607377e16998dfcf90cf5eb614d0c388541b9782d5cc038e149"}, + {file = "fonttools-4.55.7-cp38-cp38-win32.whl", hash = "sha256:5ff0daf8b2e0612e5761fed2e4a2f54eff9d9ec0aeb4091c9f3666f9a118325e"}, + {file = "fonttools-4.55.7-cp38-cp38-win_amd64.whl", hash = "sha256:0ed25d7b5fa4ae6a805c2a9cc0e5307d45cbb3b8e155584fe932d0f3b6a997bf"}, + {file = "fonttools-4.55.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8ef5ee98fc320c158e4e459a5ee40d1ac3728d4ce11c3c8dfd854aa0aa5c042f"}, + {file = "fonttools-4.55.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:09740feed51f9ed816aebf5d82071b7fecf693ac3a7e0fc8ea433f5dc3bd92f5"}, + {file = "fonttools-4.55.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3d19ea483b3cd8833e9e2ee8115f3d2044d55d3743d84f9c23b48b52d7516d8"}, + {file = "fonttools-4.55.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c135c91d47351b84893fb6fcbb8f178eba14f7cb195850264c0675c85e4238b6"}, + {file = "fonttools-4.55.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:bee4920ebeb540849bc3555d871e2a8487e39ce8263c281f74d5b6d44d2bf1df"}, + {file = "fonttools-4.55.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:f3b63648600dd0081bdd6856a86d014a7f1d2d11c3c974542f866478d832e103"}, + {file = "fonttools-4.55.7-cp39-cp39-win32.whl", hash = "sha256:d4bd27f0fa5120aaa39f76de5768959bc97300e0f59a3160d466b51436a38aea"}, + {file = "fonttools-4.55.7-cp39-cp39-win_amd64.whl", hash = "sha256:c665df9c9d99937a5bf807bace1c0c95bd13f55de8c82aaf9856b868dcbfe5d9"}, + {file = "fonttools-4.55.7-py3-none-any.whl", hash = "sha256:3304dfcf9ca204dd0ef691a287bd851ddd8e8250108658c0677c3fdfec853a20"}, + {file = "fonttools-4.55.7.tar.gz", hash = "sha256:6899e3d97225a8218f525e9754da0376e1c62953a0d57a76c5abaada51e0d140"}, ] [package.dependencies] @@ -1405,6 +1534,7 @@ version = "2024.6.6" description = "Generate a dot graph from the output of several profilers." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "gprof2dot-2024.6.6-py2.py3-none-any.whl", hash = "sha256:45b14ad7ce64e299c8f526881007b9eb2c6b75505d5613e96e66ee4d5ab33696"}, {file = "gprof2dot-2024.6.6.tar.gz", hash = "sha256:fa1420c60025a9eb7734f65225b4da02a10fc6dd741b37fa129bc6b41951e5ab"}, @@ -1416,6 +1546,7 @@ version = "22.0.0" description = "WSGI HTTP Server for UNIX" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "gunicorn-22.0.0-py3-none-any.whl", hash = "sha256:350679f91b24062c86e386e198a15438d53a7a8207235a78ba1b53df4c4378d9"}, {file = "gunicorn-22.0.0.tar.gz", hash = "sha256:4a0b436239ff76fb33f11c07a16482c521a7e09c1ce3cc293c2330afe01bec63"}, @@ -1437,6 +1568,7 @@ version = "0.14.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, @@ -1448,6 +1580,7 @@ version = "1.1" description = "HTML parser based on the WHATWG HTML specification" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +groups = ["main"] files = [ {file = "html5lib-1.1-py2.py3-none-any.whl", hash = "sha256:0d78f8fde1c230e99fe37986a60526d7049ed4bf8a9fadbad5f00e22e58e041d"}, {file = "html5lib-1.1.tar.gz", hash = "sha256:b2e5b40261e20f354d198eae92afc10d750afb487ed5e50f9c4eaf07c184146f"}, @@ -1465,30 +1598,32 @@ lxml = ["lxml"] [[package]] name = "icecream" -version = "2.1.3" +version = "2.1.4" description = "Never use print() to debug again; inspect variables, expressions, and program execution with a single, simple function call." optional = false python-versions = "*" +groups = ["dev"] files = [ - {file = "icecream-2.1.3-py2.py3-none-any.whl", hash = "sha256:757aec31ad4488b949bc4f499d18e6e5973c40cc4d4fc607229e78cfaec94c34"}, - {file = "icecream-2.1.3.tar.gz", hash = "sha256:0aa4a7c3374ec36153a1d08f81e3080e83d8ac1eefd97d2f4fe9544e8f9b49de"}, + {file = "icecream-2.1.4-py3-none-any.whl", hash = "sha256:7bb715f69102cae871b3a361c3b656536db02cfcadac9664c673581cac4df4fd"}, + {file = "icecream-2.1.4.tar.gz", hash = "sha256:58755e58397d5350a76f25976dee7b607f5febb3c6e1cddfe6b1951896e91573"}, ] [package.dependencies] asttokens = ">=2.0.1" colorama = ">=0.3.9" -executing = ">=0.3.1" +executing = ">=2.1.0" pygments = ">=2.2.0" [[package]] name = "identify" -version = "2.6.3" +version = "2.6.6" description = "File identification library for Python" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ - {file = "identify-2.6.3-py2.py3-none-any.whl", hash = "sha256:9edba65473324c2ea9684b1f944fe3191db3345e50b6d04571d10ed164f8d7bd"}, - {file = "identify-2.6.3.tar.gz", hash = "sha256:62f5dae9b5fef52c84cc188514e9ea4f3f636b1d8799ab5ebc475471f9e47a02"}, + {file = "identify-2.6.6-py2.py3-none-any.whl", hash = "sha256:cbd1810bce79f8b671ecb20f53ee0ae8e86ae84b557de31d89709dc2a48ba881"}, + {file = "identify-2.6.6.tar.gz", hash = "sha256:7bec12768ed44ea4761efb47806f0a41f86e7c0a5fdf5950d4648c90eca7e251"}, ] [package.extras] @@ -1500,6 +1635,7 @@ version = "3.10" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.6" +groups = ["main", "dev"] files = [ {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, @@ -1514,6 +1650,7 @@ version = "0.5.1" description = "A port of Ruby on Rails inflector to Python" optional = false python-versions = ">=3.5" +groups = ["dev"] files = [ {file = "inflection-0.5.1-py2.py3-none-any.whl", hash = "sha256:f38b2b640938a4f35ade69ac3d053042959b62a0f1076a5bbaa1b9526605a8a2"}, {file = "inflection-0.5.1.tar.gz", hash = "sha256:1a29730d366e996aaacffb2f1f1cb9593dc38e2ddd30c91250c6dde09ea9b417"}, @@ -1525,6 +1662,7 @@ version = "2.0.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, @@ -1536,6 +1674,7 @@ version = "2.2.0" description = "Pythonic task execution" optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "invoke-2.2.0-py3-none-any.whl", hash = "sha256:6ea924cc53d4f78e3d98bc436b08069a03077e6f85ad1ddaa8a116d7dad15820"}, {file = "invoke-2.2.0.tar.gz", hash = "sha256:ee6cbb101af1a859c7fe84f2a264c059020b0cb7fe3535f9424300ab568f6bd5"}, @@ -1547,6 +1686,7 @@ version = "8.31.0" description = "IPython: Productive Interactive Computing" optional = false python-versions = ">=3.10" +groups = ["main", "dev"] files = [ {file = "ipython-8.31.0-py3-none-any.whl", hash = "sha256:46ec58f8d3d076a61d128fe517a51eb730e3aaf0c184ea8c17d16e366660c6a6"}, {file = "ipython-8.31.0.tar.gz", hash = "sha256:b6a2274606bec6166405ff05e54932ed6e5cfecaca1fc05f2cacde7bb074d70b"}, @@ -1583,6 +1723,7 @@ version = "0.19.2" description = "An autocompletion tool for Python that can be used for text editors." optional = false python-versions = ">=3.6" +groups = ["main", "dev"] files = [ {file = "jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9"}, {file = "jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0"}, @@ -1602,6 +1743,7 @@ version = "1.15.1" description = "JavaScript unobfuscator and beautifier." optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "jsbeautifier-1.15.1.tar.gz", hash = "sha256:ebd733b560704c602d744eafc839db60a1ee9326e30a2a80c4adb8718adc1b24"}, ] @@ -1616,6 +1758,7 @@ version = "0.10.0" description = "A Python implementation of the JSON5 data format." optional = false python-versions = ">=3.8.0" +groups = ["dev"] files = [ {file = "json5-0.10.0-py3-none-any.whl", hash = "sha256:19b23410220a7271e8377f81ba8aacba2fdd56947fbb137ee5977cbe1f5e8dfa"}, {file = "json5-0.10.0.tar.gz", hash = "sha256:e66941c8f0a02026943c52c2eb34ebeb2a6f819a0be05920a6f5243cd30fd559"}, @@ -1630,6 +1773,7 @@ version = "4.23.0" description = "An implementation of JSON Schema validation for Python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "jsonschema-4.23.0-py3-none-any.whl", hash = "sha256:fbadb6f8b144a8f8cf9f0b89ba94501d143e50411a1278633f56a7acf7fd5566"}, {file = "jsonschema-4.23.0.tar.gz", hash = "sha256:d71497fef26351a33265337fa77ffeb82423f3ea21283cd9467bb03999266bc4"}, @@ -1651,6 +1795,7 @@ version = "2024.10.1" description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "jsonschema_specifications-2024.10.1-py3-none-any.whl", hash = "sha256:a09a0680616357d9a0ecf05c12ad234479f549239d0f5b55f3deea67475da9bf"}, {file = "jsonschema_specifications-2024.10.1.tar.gz", hash = "sha256:0f38b83639958ce1152d02a7f062902c41c8fd20d558b0c34344292d417ae272"}, @@ -1659,12 +1804,103 @@ files = [ [package.dependencies] referencing = ">=0.31.0" +[[package]] +name = "kiwisolver" +version = "1.4.8" +description = "A fast implementation of the Cassowary constraint solver" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "kiwisolver-1.4.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88c6f252f6816a73b1f8c904f7bbe02fd67c09a69f7cb8a0eecdbf5ce78e63db"}, + {file = "kiwisolver-1.4.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c72941acb7b67138f35b879bbe85be0f6c6a70cab78fe3ef6db9c024d9223e5b"}, + {file = "kiwisolver-1.4.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ce2cf1e5688edcb727fdf7cd1bbd0b6416758996826a8be1d958f91880d0809d"}, + {file = "kiwisolver-1.4.8-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c8bf637892dc6e6aad2bc6d4d69d08764166e5e3f69d469e55427b6ac001b19d"}, + {file = "kiwisolver-1.4.8-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:034d2c891f76bd3edbdb3ea11140d8510dca675443da7304205a2eaa45d8334c"}, + {file = "kiwisolver-1.4.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d47b28d1dfe0793d5e96bce90835e17edf9a499b53969b03c6c47ea5985844c3"}, + {file = "kiwisolver-1.4.8-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb158fe28ca0c29f2260cca8c43005329ad58452c36f0edf298204de32a9a3ed"}, + {file = "kiwisolver-1.4.8-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5536185fce131780ebd809f8e623bf4030ce1b161353166c49a3c74c287897f"}, + {file = "kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:369b75d40abedc1da2c1f4de13f3482cb99e3237b38726710f4a793432b1c5ff"}, + {file = "kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:641f2ddf9358c80faa22e22eb4c9f54bd3f0e442e038728f500e3b978d00aa7d"}, + {file = "kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d561d2d8883e0819445cfe58d7ddd673e4015c3c57261d7bdcd3710d0d14005c"}, + {file = "kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:1732e065704b47c9afca7ffa272f845300a4eb959276bf6970dc07265e73b605"}, + {file = "kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bcb1ebc3547619c3b58a39e2448af089ea2ef44b37988caf432447374941574e"}, + {file = "kiwisolver-1.4.8-cp310-cp310-win_amd64.whl", hash = "sha256:89c107041f7b27844179ea9c85d6da275aa55ecf28413e87624d033cf1f6b751"}, + {file = "kiwisolver-1.4.8-cp310-cp310-win_arm64.whl", hash = "sha256:b5773efa2be9eb9fcf5415ea3ab70fc785d598729fd6057bea38d539ead28271"}, + {file = "kiwisolver-1.4.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a4d3601908c560bdf880f07d94f31d734afd1bb71e96585cace0e38ef44c6d84"}, + {file = "kiwisolver-1.4.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:856b269c4d28a5c0d5e6c1955ec36ebfd1651ac00e1ce0afa3e28da95293b561"}, + {file = "kiwisolver-1.4.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c2b9a96e0f326205af81a15718a9073328df1173a2619a68553decb7097fd5d7"}, + {file = "kiwisolver-1.4.8-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5020c83e8553f770cb3b5fc13faac40f17e0b205bd237aebd21d53d733adb03"}, + {file = "kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dace81d28c787956bfbfbbfd72fdcef014f37d9b48830829e488fdb32b49d954"}, + {file = "kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:11e1022b524bd48ae56c9b4f9296bce77e15a2e42a502cceba602f804b32bb79"}, + {file = "kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b9b4d2892fefc886f30301cdd80debd8bb01ecdf165a449eb6e78f79f0fabd6"}, + {file = "kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a96c0e790ee875d65e340ab383700e2b4891677b7fcd30a699146f9384a2bb0"}, + {file = "kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:23454ff084b07ac54ca8be535f4174170c1094a4cff78fbae4f73a4bcc0d4dab"}, + {file = "kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:87b287251ad6488e95b4f0b4a79a6d04d3ea35fde6340eb38fbd1ca9cd35bbbc"}, + {file = "kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b21dbe165081142b1232a240fc6383fd32cdd877ca6cc89eab93e5f5883e1c25"}, + {file = "kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:768cade2c2df13db52475bd28d3a3fac8c9eff04b0e9e2fda0f3760f20b3f7fc"}, + {file = "kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d47cfb2650f0e103d4bf68b0b5804c68da97272c84bb12850d877a95c056bd67"}, + {file = "kiwisolver-1.4.8-cp311-cp311-win_amd64.whl", hash = "sha256:ed33ca2002a779a2e20eeb06aea7721b6e47f2d4b8a8ece979d8ba9e2a167e34"}, + {file = "kiwisolver-1.4.8-cp311-cp311-win_arm64.whl", hash = "sha256:16523b40aab60426ffdebe33ac374457cf62863e330a90a0383639ce14bf44b2"}, + {file = "kiwisolver-1.4.8-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d6af5e8815fd02997cb6ad9bbed0ee1e60014438ee1a5c2444c96f87b8843502"}, + {file = "kiwisolver-1.4.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bade438f86e21d91e0cf5dd7c0ed00cda0f77c8c1616bd83f9fc157fa6760d31"}, + {file = "kiwisolver-1.4.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b83dc6769ddbc57613280118fb4ce3cd08899cc3369f7d0e0fab518a7cf37fdb"}, + {file = "kiwisolver-1.4.8-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:111793b232842991be367ed828076b03d96202c19221b5ebab421ce8bcad016f"}, + {file = "kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:257af1622860e51b1a9d0ce387bf5c2c4f36a90594cb9514f55b074bcc787cfc"}, + {file = "kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:69b5637c3f316cab1ec1c9a12b8c5f4750a4c4b71af9157645bf32830e39c03a"}, + {file = "kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:782bb86f245ec18009890e7cb8d13a5ef54dcf2ebe18ed65f795e635a96a1c6a"}, + {file = "kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc978a80a0db3a66d25767b03688f1147a69e6237175c0f4ffffaaedf744055a"}, + {file = "kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:36dbbfd34838500a31f52c9786990d00150860e46cd5041386f217101350f0d3"}, + {file = "kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:eaa973f1e05131de5ff3569bbba7f5fd07ea0595d3870ed4a526d486fe57fa1b"}, + {file = "kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a66f60f8d0c87ab7f59b6fb80e642ebb29fec354a4dfad687ca4092ae69d04f4"}, + {file = "kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:858416b7fb777a53f0c59ca08190ce24e9abbd3cffa18886a5781b8e3e26f65d"}, + {file = "kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:085940635c62697391baafaaeabdf3dd7a6c3643577dde337f4d66eba021b2b8"}, + {file = "kiwisolver-1.4.8-cp312-cp312-win_amd64.whl", hash = "sha256:01c3d31902c7db5fb6182832713d3b4122ad9317c2c5877d0539227d96bb2e50"}, + {file = "kiwisolver-1.4.8-cp312-cp312-win_arm64.whl", hash = "sha256:a3c44cb68861de93f0c4a8175fbaa691f0aa22550c331fefef02b618a9dcb476"}, + {file = "kiwisolver-1.4.8-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1c8ceb754339793c24aee1c9fb2485b5b1f5bb1c2c214ff13368431e51fc9a09"}, + {file = "kiwisolver-1.4.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:54a62808ac74b5e55a04a408cda6156f986cefbcf0ada13572696b507cc92fa1"}, + {file = "kiwisolver-1.4.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:68269e60ee4929893aad82666821aaacbd455284124817af45c11e50a4b42e3c"}, + {file = "kiwisolver-1.4.8-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34d142fba9c464bc3bbfeff15c96eab0e7310343d6aefb62a79d51421fcc5f1b"}, + {file = "kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ddc373e0eef45b59197de815b1b28ef89ae3955e7722cc9710fb91cd77b7f47"}, + {file = "kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:77e6f57a20b9bd4e1e2cedda4d0b986ebd0216236f0106e55c28aea3d3d69b16"}, + {file = "kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08e77738ed7538f036cd1170cbed942ef749137b1311fa2bbe2a7fda2f6bf3cc"}, + {file = "kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5ce1e481a74b44dd5e92ff03ea0cb371ae7a0268318e202be06c8f04f4f1246"}, + {file = "kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:fc2ace710ba7c1dfd1a3b42530b62b9ceed115f19a1656adefce7b1782a37794"}, + {file = "kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:3452046c37c7692bd52b0e752b87954ef86ee2224e624ef7ce6cb21e8c41cc1b"}, + {file = "kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7e9a60b50fe8b2ec6f448fe8d81b07e40141bfced7f896309df271a0b92f80f3"}, + {file = "kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:918139571133f366e8362fa4a297aeba86c7816b7ecf0bc79168080e2bd79957"}, + {file = "kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e063ef9f89885a1d68dd8b2e18f5ead48653176d10a0e324e3b0030e3a69adeb"}, + {file = "kiwisolver-1.4.8-cp313-cp313-win_amd64.whl", hash = "sha256:a17b7c4f5b2c51bb68ed379defd608a03954a1845dfed7cc0117f1cc8a9b7fd2"}, + {file = "kiwisolver-1.4.8-cp313-cp313-win_arm64.whl", hash = "sha256:3cd3bc628b25f74aedc6d374d5babf0166a92ff1317f46267f12d2ed54bc1d30"}, + {file = "kiwisolver-1.4.8-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:370fd2df41660ed4e26b8c9d6bbcad668fbe2560462cba151a721d49e5b6628c"}, + {file = "kiwisolver-1.4.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:84a2f830d42707de1d191b9490ac186bf7997a9495d4e9072210a1296345f7dc"}, + {file = "kiwisolver-1.4.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7a3ad337add5148cf51ce0b55642dc551c0b9d6248458a757f98796ca7348712"}, + {file = "kiwisolver-1.4.8-cp313-cp313t-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7506488470f41169b86d8c9aeff587293f530a23a23a49d6bc64dab66bedc71e"}, + {file = "kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f0121b07b356a22fb0414cec4666bbe36fd6d0d759db3d37228f496ed67c880"}, + {file = "kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d6d6bd87df62c27d4185de7c511c6248040afae67028a8a22012b010bc7ad062"}, + {file = "kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:291331973c64bb9cce50bbe871fb2e675c4331dab4f31abe89f175ad7679a4d7"}, + {file = "kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:893f5525bb92d3d735878ec00f781b2de998333659507d29ea4466208df37bed"}, + {file = "kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b47a465040146981dc9db8647981b8cb96366fbc8d452b031e4f8fdffec3f26d"}, + {file = "kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:99cea8b9dd34ff80c521aef46a1dddb0dcc0283cf18bde6d756f1e6f31772165"}, + {file = "kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:151dffc4865e5fe6dafce5480fab84f950d14566c480c08a53c663a0020504b6"}, + {file = "kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:577facaa411c10421314598b50413aa1ebcf5126f704f1e5d72d7e4e9f020d90"}, + {file = "kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:be4816dc51c8a471749d664161b434912eee82f2ea66bd7628bd14583a833e85"}, + {file = "kiwisolver-1.4.8-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:e7a019419b7b510f0f7c9dceff8c5eae2392037eae483a7f9162625233802b0a"}, + {file = "kiwisolver-1.4.8-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:286b18e86682fd2217a48fc6be6b0f20c1d0ed10958d8dc53453ad58d7be0bf8"}, + {file = "kiwisolver-1.4.8-pp310-pypy310_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4191ee8dfd0be1c3666ccbac178c5a05d5f8d689bbe3fc92f3c4abec817f8fe0"}, + {file = "kiwisolver-1.4.8-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7cd2785b9391f2873ad46088ed7599a6a71e762e1ea33e87514b1a441ed1da1c"}, + {file = "kiwisolver-1.4.8-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c07b29089b7ba090b6f1a669f1411f27221c3662b3a1b7010e67b59bb5a6f10b"}, + {file = "kiwisolver-1.4.8-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:65ea09a5a3faadd59c2ce96dc7bf0f364986a315949dc6374f04396b0d60e09b"}, + {file = "kiwisolver-1.4.8.tar.gz", hash = "sha256:23d5f023bdc8c7e54eb65f03ca5d5bb25b601eac4d7f1a042888a1f45237987e"}, +] + [[package]] name = "kombu" version = "5.4.2" description = "Messaging library for Python." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "kombu-5.4.2-py3-none-any.whl", hash = "sha256:14212f5ccf022fc0a70453bb025a1dcc32782a588c49ea866884047d66e14763"}, {file = "kombu-5.4.2.tar.gz", hash = "sha256:eef572dd2fd9fc614b37580e3caeafdd5af46c1eff31e7fba89138cdb406f2cf"}, @@ -1698,6 +1934,7 @@ version = "3.0.2" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, @@ -1762,12 +1999,71 @@ files = [ {file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"}, ] +[[package]] +name = "matplotlib" +version = "3.10.0" +description = "Python plotting package" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "matplotlib-3.10.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2c5829a5a1dd5a71f0e31e6e8bb449bc0ee9dbfb05ad28fc0c6b55101b3a4be6"}, + {file = "matplotlib-3.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a2a43cbefe22d653ab34bb55d42384ed30f611bcbdea1f8d7f431011a2e1c62e"}, + {file = "matplotlib-3.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:607b16c8a73943df110f99ee2e940b8a1cbf9714b65307c040d422558397dac5"}, + {file = "matplotlib-3.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01d2b19f13aeec2e759414d3bfe19ddfb16b13a1250add08d46d5ff6f9be83c6"}, + {file = "matplotlib-3.10.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e6c6461e1fc63df30bf6f80f0b93f5b6784299f721bc28530477acd51bfc3d1"}, + {file = "matplotlib-3.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:994c07b9d9fe8d25951e3202a68c17900679274dadfc1248738dcfa1bd40d7f3"}, + {file = "matplotlib-3.10.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:fd44fc75522f58612ec4a33958a7e5552562b7705b42ef1b4f8c0818e304a363"}, + {file = "matplotlib-3.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c58a9622d5dbeb668f407f35f4e6bfac34bb9ecdcc81680c04d0258169747997"}, + {file = "matplotlib-3.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:845d96568ec873be63f25fa80e9e7fae4be854a66a7e2f0c8ccc99e94a8bd4ef"}, + {file = "matplotlib-3.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5439f4c5a3e2e8eab18e2f8c3ef929772fd5641876db71f08127eed95ab64683"}, + {file = "matplotlib-3.10.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4673ff67a36152c48ddeaf1135e74ce0d4bce1bbf836ae40ed39c29edf7e2765"}, + {file = "matplotlib-3.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:7e8632baebb058555ac0cde75db885c61f1212e47723d63921879806b40bec6a"}, + {file = "matplotlib-3.10.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4659665bc7c9b58f8c00317c3c2a299f7f258eeae5a5d56b4c64226fca2f7c59"}, + {file = "matplotlib-3.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d44cb942af1693cced2604c33a9abcef6205601c445f6d0dc531d813af8a2f5a"}, + {file = "matplotlib-3.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a994f29e968ca002b50982b27168addfd65f0105610b6be7fa515ca4b5307c95"}, + {file = "matplotlib-3.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b0558bae37f154fffda54d779a592bc97ca8b4701f1c710055b609a3bac44c8"}, + {file = "matplotlib-3.10.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:503feb23bd8c8acc75541548a1d709c059b7184cde26314896e10a9f14df5f12"}, + {file = "matplotlib-3.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:c40ba2eb08b3f5de88152c2333c58cee7edcead0a2a0d60fcafa116b17117adc"}, + {file = "matplotlib-3.10.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96f2886f5c1e466f21cc41b70c5a0cd47bfa0015eb2d5793c88ebce658600e25"}, + {file = "matplotlib-3.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:12eaf48463b472c3c0f8dbacdbf906e573013df81a0ab82f0616ea4b11281908"}, + {file = "matplotlib-3.10.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2fbbabc82fde51391c4da5006f965e36d86d95f6ee83fb594b279564a4c5d0d2"}, + {file = "matplotlib-3.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad2e15300530c1a94c63cfa546e3b7864bd18ea2901317bae8bbf06a5ade6dcf"}, + {file = "matplotlib-3.10.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3547d153d70233a8496859097ef0312212e2689cdf8d7ed764441c77604095ae"}, + {file = "matplotlib-3.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:c55b20591ced744aa04e8c3e4b7543ea4d650b6c3c4b208c08a05b4010e8b442"}, + {file = "matplotlib-3.10.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:9ade1003376731a971e398cc4ef38bb83ee8caf0aee46ac6daa4b0506db1fd06"}, + {file = "matplotlib-3.10.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:95b710fea129c76d30be72c3b38f330269363fbc6e570a5dd43580487380b5ff"}, + {file = "matplotlib-3.10.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cdbaf909887373c3e094b0318d7ff230b2ad9dcb64da7ade654182872ab2593"}, + {file = "matplotlib-3.10.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d907fddb39f923d011875452ff1eca29a9e7f21722b873e90db32e5d8ddff12e"}, + {file = "matplotlib-3.10.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:3b427392354d10975c1d0f4ee18aa5844640b512d5311ef32efd4dd7db106ede"}, + {file = "matplotlib-3.10.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5fd41b0ec7ee45cd960a8e71aea7c946a28a0b8a4dcee47d2856b2af051f334c"}, + {file = "matplotlib-3.10.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:81713dd0d103b379de4516b861d964b1d789a144103277769238c732229d7f03"}, + {file = "matplotlib-3.10.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:359f87baedb1f836ce307f0e850d12bb5f1936f70d035561f90d41d305fdacea"}, + {file = "matplotlib-3.10.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae80dc3a4add4665cf2faa90138384a7ffe2a4e37c58d83e115b54287c4f06ef"}, + {file = "matplotlib-3.10.0.tar.gz", hash = "sha256:b886d02a581b96704c9d1ffe55709e49b4d2d52709ccebc4be42db856e511278"}, +] + +[package.dependencies] +contourpy = ">=1.0.1" +cycler = ">=0.10" +fonttools = ">=4.22.0" +kiwisolver = ">=1.3.1" +numpy = ">=1.23" +packaging = ">=20.0" +pillow = ">=8" +pyparsing = ">=2.3.1" +python-dateutil = ">=2.7" + +[package.extras] +dev = ["meson-python (>=0.13.1,<0.17.0)", "pybind11 (>=2.13.2,!=2.13.3)", "setuptools (>=64)", "setuptools_scm (>=7)"] + [[package]] name = "matplotlib-inline" version = "0.1.7" description = "Inline Matplotlib backend for Jupyter" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca"}, {file = "matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90"}, @@ -1782,6 +2078,7 @@ version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." optional = false python-versions = ">=3.5" +groups = ["dev"] files = [ {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, @@ -1793,17 +2090,84 @@ version = "1.9.1" description = "Node.js virtual environment builder" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["dev"] files = [ {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, ] +[[package]] +name = "numpy" +version = "2.2.2" +description = "Fundamental package for array computing in Python" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "numpy-2.2.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7079129b64cb78bdc8d611d1fd7e8002c0a2565da6a47c4df8062349fee90e3e"}, + {file = "numpy-2.2.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2ec6c689c61df613b783aeb21f945c4cbe6c51c28cb70aae8430577ab39f163e"}, + {file = "numpy-2.2.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:40c7ff5da22cd391944a28c6a9c638a5eef77fcf71d6e3a79e1d9d9e82752715"}, + {file = "numpy-2.2.2-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:995f9e8181723852ca458e22de5d9b7d3ba4da3f11cc1cb113f093b271d7965a"}, + {file = "numpy-2.2.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b78ea78450fd96a498f50ee096f69c75379af5138f7881a51355ab0e11286c97"}, + {file = "numpy-2.2.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3fbe72d347fbc59f94124125e73fc4976a06927ebc503ec5afbfb35f193cd957"}, + {file = "numpy-2.2.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8e6da5cffbbe571f93588f562ed130ea63ee206d12851b60819512dd3e1ba50d"}, + {file = "numpy-2.2.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:09d6a2032faf25e8d0cadde7fd6145118ac55d2740132c1d845f98721b5ebcfd"}, + {file = "numpy-2.2.2-cp310-cp310-win32.whl", hash = "sha256:159ff6ee4c4a36a23fe01b7c3d07bd8c14cc433d9720f977fcd52c13c0098160"}, + {file = "numpy-2.2.2-cp310-cp310-win_amd64.whl", hash = "sha256:64bd6e1762cd7f0986a740fee4dff927b9ec2c5e4d9a28d056eb17d332158014"}, + {file = "numpy-2.2.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:642199e98af1bd2b6aeb8ecf726972d238c9877b0f6e8221ee5ab945ec8a2189"}, + {file = "numpy-2.2.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6d9fc9d812c81e6168b6d405bf00b8d6739a7f72ef22a9214c4241e0dc70b323"}, + {file = "numpy-2.2.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:c7d1fd447e33ee20c1f33f2c8e6634211124a9aabde3c617687d8b739aa69eac"}, + {file = "numpy-2.2.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:451e854cfae0febe723077bd0cf0a4302a5d84ff25f0bfece8f29206c7bed02e"}, + {file = "numpy-2.2.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd249bc894af67cbd8bad2c22e7cbcd46cf87ddfca1f1289d1e7e54868cc785c"}, + {file = "numpy-2.2.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02935e2c3c0c6cbe9c7955a8efa8908dd4221d7755644c59d1bba28b94fd334f"}, + {file = "numpy-2.2.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a972cec723e0563aa0823ee2ab1df0cb196ed0778f173b381c871a03719d4826"}, + {file = "numpy-2.2.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d6d6a0910c3b4368d89dde073e630882cdb266755565155bc33520283b2d9df8"}, + {file = "numpy-2.2.2-cp311-cp311-win32.whl", hash = "sha256:860fd59990c37c3ef913c3ae390b3929d005243acca1a86facb0773e2d8d9e50"}, + {file = "numpy-2.2.2-cp311-cp311-win_amd64.whl", hash = "sha256:da1eeb460ecce8d5b8608826595c777728cdf28ce7b5a5a8c8ac8d949beadcf2"}, + {file = "numpy-2.2.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ac9bea18d6d58a995fac1b2cb4488e17eceeac413af014b1dd26170b766d8467"}, + {file = "numpy-2.2.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:23ae9f0c2d889b7b2d88a3791f6c09e2ef827c2446f1c4a3e3e76328ee4afd9a"}, + {file = "numpy-2.2.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:3074634ea4d6df66be04f6728ee1d173cfded75d002c75fac79503a880bf3825"}, + {file = "numpy-2.2.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:8ec0636d3f7d68520afc6ac2dc4b8341ddb725039de042faf0e311599f54eb37"}, + {file = "numpy-2.2.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ffbb1acd69fdf8e89dd60ef6182ca90a743620957afb7066385a7bbe88dc748"}, + {file = "numpy-2.2.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0349b025e15ea9d05c3d63f9657707a4e1d471128a3b1d876c095f328f8ff7f0"}, + {file = "numpy-2.2.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:463247edcee4a5537841d5350bc87fe8e92d7dd0e8c71c995d2c6eecb8208278"}, + {file = "numpy-2.2.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9dd47ff0cb2a656ad69c38da850df3454da88ee9a6fde0ba79acceee0e79daba"}, + {file = "numpy-2.2.2-cp312-cp312-win32.whl", hash = "sha256:4525b88c11906d5ab1b0ec1f290996c0020dd318af8b49acaa46f198b1ffc283"}, + {file = "numpy-2.2.2-cp312-cp312-win_amd64.whl", hash = "sha256:5acea83b801e98541619af398cc0109ff48016955cc0818f478ee9ef1c5c3dcb"}, + {file = "numpy-2.2.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b208cfd4f5fe34e1535c08983a1a6803fdbc7a1e86cf13dd0c61de0b51a0aadc"}, + {file = "numpy-2.2.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d0bbe7dd86dca64854f4b6ce2ea5c60b51e36dfd597300057cf473d3615f2369"}, + {file = "numpy-2.2.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:22ea3bb552ade325530e72a0c557cdf2dea8914d3a5e1fecf58fa5dbcc6f43cd"}, + {file = "numpy-2.2.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:128c41c085cab8a85dc29e66ed88c05613dccf6bc28b3866cd16050a2f5448be"}, + {file = "numpy-2.2.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:250c16b277e3b809ac20d1f590716597481061b514223c7badb7a0f9993c7f84"}, + {file = "numpy-2.2.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0c8854b09bc4de7b041148d8550d3bd712b5c21ff6a8ed308085f190235d7ff"}, + {file = "numpy-2.2.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b6fb9c32a91ec32a689ec6410def76443e3c750e7cfc3fb2206b985ffb2b85f0"}, + {file = "numpy-2.2.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:57b4012e04cc12b78590a334907e01b3a85efb2107df2b8733ff1ed05fce71de"}, + {file = "numpy-2.2.2-cp313-cp313-win32.whl", hash = "sha256:4dbd80e453bd34bd003b16bd802fac70ad76bd463f81f0c518d1245b1c55e3d9"}, + {file = "numpy-2.2.2-cp313-cp313-win_amd64.whl", hash = "sha256:5a8c863ceacae696aff37d1fd636121f1a512117652e5dfb86031c8d84836369"}, + {file = "numpy-2.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:b3482cb7b3325faa5f6bc179649406058253d91ceda359c104dac0ad320e1391"}, + {file = "numpy-2.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:9491100aba630910489c1d0158034e1c9a6546f0b1340f716d522dc103788e39"}, + {file = "numpy-2.2.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:41184c416143defa34cc8eb9d070b0a5ba4f13a0fa96a709e20584638254b317"}, + {file = "numpy-2.2.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:7dca87ca328f5ea7dafc907c5ec100d187911f94825f8700caac0b3f4c384b49"}, + {file = "numpy-2.2.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bc61b307655d1a7f9f4b043628b9f2b721e80839914ede634e3d485913e1fb2"}, + {file = "numpy-2.2.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fad446ad0bc886855ddf5909cbf8cb5d0faa637aaa6277fb4b19ade134ab3c7"}, + {file = "numpy-2.2.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:149d1113ac15005652e8d0d3f6fd599360e1a708a4f98e43c9c77834a28238cb"}, + {file = "numpy-2.2.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:106397dbbb1896f99e044efc90360d098b3335060375c26aa89c0d8a97c5f648"}, + {file = "numpy-2.2.2-cp313-cp313t-win32.whl", hash = "sha256:0eec19f8af947a61e968d5429f0bd92fec46d92b0008d0a6685b40d6adf8a4f4"}, + {file = "numpy-2.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:97b974d3ba0fb4612b77ed35d7627490e8e3dff56ab41454d9e8b23448940576"}, + {file = "numpy-2.2.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b0531f0b0e07643eb089df4c509d30d72c9ef40defa53e41363eca8a8cc61495"}, + {file = "numpy-2.2.2-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:e9e82dcb3f2ebbc8cb5ce1102d5f1c5ed236bf8a11730fb45ba82e2841ec21df"}, + {file = "numpy-2.2.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0d4142eb40ca6f94539e4db929410f2a46052a0fe7a2c1c59f6179c39938d2a"}, + {file = "numpy-2.2.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:356ca982c188acbfa6af0d694284d8cf20e95b1c3d0aefa8929376fea9146f60"}, + {file = "numpy-2.2.2.tar.gz", hash = "sha256:ed6906f61834d687738d25988ae117683705636936cc605be0bb208b23df4d8f"}, +] + [[package]] name = "outcome" version = "1.3.0.post0" description = "Capture the outcome of Python function calls." optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "outcome-1.3.0.post0-py2.py3-none-any.whl", hash = "sha256:e771c5ce06d1415e356078d3bdd68523f284b4ce5419828922b6871e65eda82b"}, {file = "outcome-1.3.0.post0.tar.gz", hash = "sha256:9dcf02e65f2971b80047b377468e72a268e15c0af3cf1238e6ff14f7f91143b8"}, @@ -1818,17 +2182,102 @@ version = "24.2" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, ] +[[package]] +name = "pandas" +version = "2.2.3" +description = "Powerful data structures for data analysis, time series, and statistics" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "pandas-2.2.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1948ddde24197a0f7add2bdc4ca83bf2b1ef84a1bc8ccffd95eda17fd836ecb5"}, + {file = "pandas-2.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:381175499d3802cde0eabbaf6324cce0c4f5d52ca6f8c377c29ad442f50f6348"}, + {file = "pandas-2.2.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d9c45366def9a3dd85a6454c0e7908f2b3b8e9c138f5dc38fed7ce720d8453ed"}, + {file = "pandas-2.2.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86976a1c5b25ae3f8ccae3a5306e443569ee3c3faf444dfd0f41cda24667ad57"}, + {file = "pandas-2.2.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b8661b0238a69d7aafe156b7fa86c44b881387509653fdf857bebc5e4008ad42"}, + {file = "pandas-2.2.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:37e0aced3e8f539eccf2e099f65cdb9c8aa85109b0be6e93e2baff94264bdc6f"}, + {file = "pandas-2.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:56534ce0746a58afaf7942ba4863e0ef81c9c50d3f0ae93e9497d6a41a057645"}, + {file = "pandas-2.2.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:66108071e1b935240e74525006034333f98bcdb87ea116de573a6a0dccb6c039"}, + {file = "pandas-2.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7c2875855b0ff77b2a64a0365e24455d9990730d6431b9e0ee18ad8acee13dbd"}, + {file = "pandas-2.2.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd8d0c3be0515c12fed0bdbae072551c8b54b7192c7b1fda0ba56059a0179698"}, + {file = "pandas-2.2.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c124333816c3a9b03fbeef3a9f230ba9a737e9e5bb4060aa2107a86cc0a497fc"}, + {file = "pandas-2.2.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:63cc132e40a2e084cf01adf0775b15ac515ba905d7dcca47e9a251819c575ef3"}, + {file = "pandas-2.2.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:29401dbfa9ad77319367d36940cd8a0b3a11aba16063e39632d98b0e931ddf32"}, + {file = "pandas-2.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:3fc6873a41186404dad67245896a6e440baacc92f5b716ccd1bc9ed2995ab2c5"}, + {file = "pandas-2.2.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b1d432e8d08679a40e2a6d8b2f9770a5c21793a6f9f47fdd52c5ce1948a5a8a9"}, + {file = "pandas-2.2.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a5a1595fe639f5988ba6a8e5bc9649af3baf26df3998a0abe56c02609392e0a4"}, + {file = "pandas-2.2.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5de54125a92bb4d1c051c0659e6fcb75256bf799a732a87184e5ea503965bce3"}, + {file = "pandas-2.2.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fffb8ae78d8af97f849404f21411c95062db1496aeb3e56f146f0355c9989319"}, + {file = "pandas-2.2.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dfcb5ee8d4d50c06a51c2fffa6cff6272098ad6540aed1a76d15fb9318194d8"}, + {file = "pandas-2.2.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:062309c1b9ea12a50e8ce661145c6aab431b1e99530d3cd60640e255778bd43a"}, + {file = "pandas-2.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:59ef3764d0fe818125a5097d2ae867ca3fa64df032331b7e0917cf5d7bf66b13"}, + {file = "pandas-2.2.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f00d1345d84d8c86a63e476bb4955e46458b304b9575dcf71102b5c705320015"}, + {file = "pandas-2.2.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3508d914817e153ad359d7e069d752cdd736a247c322d932eb89e6bc84217f28"}, + {file = "pandas-2.2.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22a9d949bfc9a502d320aa04e5d02feab689d61da4e7764b62c30b991c42c5f0"}, + {file = "pandas-2.2.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3a255b2c19987fbbe62a9dfd6cff7ff2aa9ccab3fc75218fd4b7530f01efa24"}, + {file = "pandas-2.2.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:800250ecdadb6d9c78eae4990da62743b857b470883fa27f652db8bdde7f6659"}, + {file = "pandas-2.2.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6374c452ff3ec675a8f46fd9ab25c4ad0ba590b71cf0656f8b6daa5202bca3fb"}, + {file = "pandas-2.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:61c5ad4043f791b61dd4752191d9f07f0ae412515d59ba8f005832a532f8736d"}, + {file = "pandas-2.2.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3b71f27954685ee685317063bf13c7709a7ba74fc996b84fc6821c59b0f06468"}, + {file = "pandas-2.2.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:38cf8125c40dae9d5acc10fa66af8ea6fdf760b2714ee482ca691fc66e6fcb18"}, + {file = "pandas-2.2.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ba96630bc17c875161df3818780af30e43be9b166ce51c9a18c1feae342906c2"}, + {file = "pandas-2.2.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db71525a1538b30142094edb9adc10be3f3e176748cd7acc2240c2f2e5aa3a4"}, + {file = "pandas-2.2.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:15c0e1e02e93116177d29ff83e8b1619c93ddc9c49083f237d4312337a61165d"}, + {file = "pandas-2.2.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ad5b65698ab28ed8d7f18790a0dc58005c7629f227be9ecc1072aa74c0c1d43a"}, + {file = "pandas-2.2.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc6b93f9b966093cb0fd62ff1a7e4c09e6d546ad7c1de191767baffc57628f39"}, + {file = "pandas-2.2.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5dbca4c1acd72e8eeef4753eeca07de9b1db4f398669d5994086f788a5d7cc30"}, + {file = "pandas-2.2.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8cd6d7cc958a3910f934ea8dbdf17b2364827bb4dafc38ce6eef6bb3d65ff09c"}, + {file = "pandas-2.2.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99df71520d25fade9db7c1076ac94eb994f4d2673ef2aa2e86ee039b6746d20c"}, + {file = "pandas-2.2.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:31d0ced62d4ea3e231a9f228366919a5ea0b07440d9d4dac345376fd8e1477ea"}, + {file = "pandas-2.2.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7eee9e7cea6adf3e3d24e304ac6b8300646e2a5d1cd3a3c2abed9101b0846761"}, + {file = "pandas-2.2.3-cp39-cp39-win_amd64.whl", hash = "sha256:4850ba03528b6dd51d6c5d273c46f183f39a9baf3f0143e566b89450965b105e"}, + {file = "pandas-2.2.3.tar.gz", hash = "sha256:4f18ba62b61d7e192368b84517265a99b4d7ee8912f8708660fb4a366cc82667"}, +] + +[package.dependencies] +numpy = {version = ">=1.26.0", markers = "python_version >= \"3.12\""} +python-dateutil = ">=2.8.2" +pytz = ">=2020.1" +tzdata = ">=2022.7" + +[package.extras] +all = ["PyQt5 (>=5.15.9)", "SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)", "beautifulsoup4 (>=4.11.2)", "bottleneck (>=1.3.6)", "dataframe-api-compat (>=0.1.7)", "fastparquet (>=2022.12.0)", "fsspec (>=2022.11.0)", "gcsfs (>=2022.11.0)", "html5lib (>=1.1)", "hypothesis (>=6.46.1)", "jinja2 (>=3.1.2)", "lxml (>=4.9.2)", "matplotlib (>=3.6.3)", "numba (>=0.56.4)", "numexpr (>=2.8.4)", "odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "pandas-gbq (>=0.19.0)", "psycopg2 (>=2.9.6)", "pyarrow (>=10.0.1)", "pymysql (>=1.0.2)", "pyreadstat (>=1.2.0)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "qtpy (>=2.3.0)", "s3fs (>=2022.11.0)", "scipy (>=1.10.0)", "tables (>=3.8.0)", "tabulate (>=0.9.0)", "xarray (>=2022.12.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)", "zstandard (>=0.19.0)"] +aws = ["s3fs (>=2022.11.0)"] +clipboard = ["PyQt5 (>=5.15.9)", "qtpy (>=2.3.0)"] +compression = ["zstandard (>=0.19.0)"] +computation = ["scipy (>=1.10.0)", "xarray (>=2022.12.0)"] +consortium-standard = ["dataframe-api-compat (>=0.1.7)"] +excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)"] +feather = ["pyarrow (>=10.0.1)"] +fss = ["fsspec (>=2022.11.0)"] +gcp = ["gcsfs (>=2022.11.0)", "pandas-gbq (>=0.19.0)"] +hdf5 = ["tables (>=3.8.0)"] +html = ["beautifulsoup4 (>=4.11.2)", "html5lib (>=1.1)", "lxml (>=4.9.2)"] +mysql = ["SQLAlchemy (>=2.0.0)", "pymysql (>=1.0.2)"] +output-formatting = ["jinja2 (>=3.1.2)", "tabulate (>=0.9.0)"] +parquet = ["pyarrow (>=10.0.1)"] +performance = ["bottleneck (>=1.3.6)", "numba (>=0.56.4)", "numexpr (>=2.8.4)"] +plot = ["matplotlib (>=3.6.3)"] +postgresql = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "psycopg2 (>=2.9.6)"] +pyarrow = ["pyarrow (>=10.0.1)"] +spss = ["pyreadstat (>=1.2.0)"] +sql-other = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)"] +test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"] +xml = ["lxml (>=4.9.2)"] + [[package]] name = "parameterized" version = "0.9.0" description = "Parameterized testing with any Python test framework" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "parameterized-0.9.0-py2.py3-none-any.whl", hash = "sha256:4e0758e3d41bea3bbd05ec14fc2c24736723f243b28d702081aef438c9372b1b"}, {file = "parameterized-0.9.0.tar.gz", hash = "sha256:7fc905272cefa4f364c1a3429cbbe9c0f98b793988efb5bf90aac80f08db09b1"}, @@ -1843,6 +2292,7 @@ version = "3.5.0" description = "SSH2 protocol library" optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "paramiko-3.5.0-py3-none-any.whl", hash = "sha256:1fedf06b085359051cd7d0d270cebe19e755a8a921cc2ddbfa647fb0cd7d68f9"}, {file = "paramiko-3.5.0.tar.gz", hash = "sha256:ad11e540da4f55cedda52931f1a3f812a8238a7af7f62a60de538cd80bb28124"}, @@ -1864,6 +2314,7 @@ version = "0.8.4" description = "A Python Parser" optional = false python-versions = ">=3.6" +groups = ["main", "dev"] files = [ {file = "parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18"}, {file = "parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d"}, @@ -1879,6 +2330,7 @@ version = "0.12.1" description = "Utility library for gitignore style pattern matching of file paths." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, @@ -1890,6 +2342,8 @@ version = "4.9.0" description = "Pexpect allows easy control of interactive console applications." optional = false python-versions = "*" +groups = ["main", "dev"] +markers = "sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523"}, {file = "pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f"}, @@ -1900,104 +2354,102 @@ ptyprocess = ">=0.5" [[package]] name = "phonenumbers" -version = "8.13.52" +version = "8.13.53" description = "Python version of Google's common library for parsing, formatting, storing and validating international phone numbers." optional = false python-versions = "*" +groups = ["main"] files = [ - {file = "phonenumbers-8.13.52-py2.py3-none-any.whl", hash = "sha256:e803210038ece9d208b129e3023dc20e656a820d6bf6f1cb0471d4164f54bada"}, - {file = "phonenumbers-8.13.52.tar.gz", hash = "sha256:fdc371ea6a4da052beb1225de63963d5a2fddbbff2bb53e3a957f360e0185f80"}, + {file = "phonenumbers-8.13.53-py2.py3-none-any.whl", hash = "sha256:fa7b93e12b3ac9baa7b056061cfcbe4de406039f720bb539dc240bed9cc43da2"}, + {file = "phonenumbers-8.13.53.tar.gz", hash = "sha256:b7308f21837defa567b4f961925b6c652dd5148f3e0fadbd158a10758cc63d91"}, ] [[package]] name = "pillow" -version = "11.0.0" +version = "11.1.0" description = "Python Imaging Library (Fork)" optional = false python-versions = ">=3.9" -files = [ - {file = "pillow-11.0.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:6619654954dc4936fcff82db8eb6401d3159ec6be81e33c6000dfd76ae189947"}, - {file = "pillow-11.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b3c5ac4bed7519088103d9450a1107f76308ecf91d6dabc8a33a2fcfb18d0fba"}, - {file = "pillow-11.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a65149d8ada1055029fcb665452b2814fe7d7082fcb0c5bed6db851cb69b2086"}, - {file = "pillow-11.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88a58d8ac0cc0e7f3a014509f0455248a76629ca9b604eca7dc5927cc593c5e9"}, - {file = "pillow-11.0.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:c26845094b1af3c91852745ae78e3ea47abf3dbcd1cf962f16b9a5fbe3ee8488"}, - {file = "pillow-11.0.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:1a61b54f87ab5786b8479f81c4b11f4d61702830354520837f8cc791ebba0f5f"}, - {file = "pillow-11.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:674629ff60030d144b7bca2b8330225a9b11c482ed408813924619c6f302fdbb"}, - {file = "pillow-11.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:598b4e238f13276e0008299bd2482003f48158e2b11826862b1eb2ad7c768b97"}, - {file = "pillow-11.0.0-cp310-cp310-win32.whl", hash = "sha256:9a0f748eaa434a41fccf8e1ee7a3eed68af1b690e75328fd7a60af123c193b50"}, - {file = "pillow-11.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:a5629742881bcbc1f42e840af185fd4d83a5edeb96475a575f4da50d6ede337c"}, - {file = "pillow-11.0.0-cp310-cp310-win_arm64.whl", hash = "sha256:ee217c198f2e41f184f3869f3e485557296d505b5195c513b2bfe0062dc537f1"}, - {file = "pillow-11.0.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1c1d72714f429a521d8d2d018badc42414c3077eb187a59579f28e4270b4b0fc"}, - {file = "pillow-11.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:499c3a1b0d6fc8213519e193796eb1a86a1be4b1877d678b30f83fd979811d1a"}, - {file = "pillow-11.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c8b2351c85d855293a299038e1f89db92a2f35e8d2f783489c6f0b2b5f3fe8a3"}, - {file = "pillow-11.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f4dba50cfa56f910241eb7f883c20f1e7b1d8f7d91c750cd0b318bad443f4d5"}, - {file = "pillow-11.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:5ddbfd761ee00c12ee1be86c9c0683ecf5bb14c9772ddbd782085779a63dd55b"}, - {file = "pillow-11.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:45c566eb10b8967d71bf1ab8e4a525e5a93519e29ea071459ce517f6b903d7fa"}, - {file = "pillow-11.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b4fd7bd29610a83a8c9b564d457cf5bd92b4e11e79a4ee4716a63c959699b306"}, - {file = "pillow-11.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:cb929ca942d0ec4fac404cbf520ee6cac37bf35be479b970c4ffadf2b6a1cad9"}, - {file = "pillow-11.0.0-cp311-cp311-win32.whl", hash = "sha256:006bcdd307cc47ba43e924099a038cbf9591062e6c50e570819743f5607404f5"}, - {file = "pillow-11.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:52a2d8323a465f84faaba5236567d212c3668f2ab53e1c74c15583cf507a0291"}, - {file = "pillow-11.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:16095692a253047fe3ec028e951fa4221a1f3ed3d80c397e83541a3037ff67c9"}, - {file = "pillow-11.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d2c0a187a92a1cb5ef2c8ed5412dd8d4334272617f532d4ad4de31e0495bd923"}, - {file = "pillow-11.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:084a07ef0821cfe4858fe86652fffac8e187b6ae677e9906e192aafcc1b69903"}, - {file = "pillow-11.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8069c5179902dcdce0be9bfc8235347fdbac249d23bd90514b7a47a72d9fecf4"}, - {file = "pillow-11.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f02541ef64077f22bf4924f225c0fd1248c168f86e4b7abdedd87d6ebaceab0f"}, - {file = "pillow-11.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:fcb4621042ac4b7865c179bb972ed0da0218a076dc1820ffc48b1d74c1e37fe9"}, - {file = "pillow-11.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:00177a63030d612148e659b55ba99527803288cea7c75fb05766ab7981a8c1b7"}, - {file = "pillow-11.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8853a3bf12afddfdf15f57c4b02d7ded92c7a75a5d7331d19f4f9572a89c17e6"}, - {file = "pillow-11.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3107c66e43bda25359d5ef446f59c497de2b5ed4c7fdba0894f8d6cf3822dafc"}, - {file = "pillow-11.0.0-cp312-cp312-win32.whl", hash = "sha256:86510e3f5eca0ab87429dd77fafc04693195eec7fd6a137c389c3eeb4cfb77c6"}, - {file = "pillow-11.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:8ec4a89295cd6cd4d1058a5e6aec6bf51e0eaaf9714774e1bfac7cfc9051db47"}, - {file = "pillow-11.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:27a7860107500d813fcd203b4ea19b04babe79448268403172782754870dac25"}, - {file = "pillow-11.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:bcd1fb5bb7b07f64c15618c89efcc2cfa3e95f0e3bcdbaf4642509de1942a699"}, - {file = "pillow-11.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0e038b0745997c7dcaae350d35859c9715c71e92ffb7e0f4a8e8a16732150f38"}, - {file = "pillow-11.0.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ae08bd8ffc41aebf578c2af2f9d8749d91f448b3bfd41d7d9ff573d74f2a6b2"}, - {file = "pillow-11.0.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d69bfd8ec3219ae71bcde1f942b728903cad25fafe3100ba2258b973bd2bc1b2"}, - {file = "pillow-11.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:61b887f9ddba63ddf62fd02a3ba7add935d053b6dd7d58998c630e6dbade8527"}, - {file = "pillow-11.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:c6a660307ca9d4867caa8d9ca2c2658ab685de83792d1876274991adec7b93fa"}, - {file = "pillow-11.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:73e3a0200cdda995c7e43dd47436c1548f87a30bb27fb871f352a22ab8dcf45f"}, - {file = "pillow-11.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fba162b8872d30fea8c52b258a542c5dfd7b235fb5cb352240c8d63b414013eb"}, - {file = "pillow-11.0.0-cp313-cp313-win32.whl", hash = "sha256:f1b82c27e89fffc6da125d5eb0ca6e68017faf5efc078128cfaa42cf5cb38798"}, - {file = "pillow-11.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:8ba470552b48e5835f1d23ecb936bb7f71d206f9dfeee64245f30c3270b994de"}, - {file = "pillow-11.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:846e193e103b41e984ac921b335df59195356ce3f71dcfd155aa79c603873b84"}, - {file = "pillow-11.0.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4ad70c4214f67d7466bea6a08061eba35c01b1b89eaa098040a35272a8efb22b"}, - {file = "pillow-11.0.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:6ec0d5af64f2e3d64a165f490d96368bb5dea8b8f9ad04487f9ab60dc4bb6003"}, - {file = "pillow-11.0.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c809a70e43c7977c4a42aefd62f0131823ebf7dd73556fa5d5950f5b354087e2"}, - {file = "pillow-11.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:4b60c9520f7207aaf2e1d94de026682fc227806c6e1f55bba7606d1c94dd623a"}, - {file = "pillow-11.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:1e2688958a840c822279fda0086fec1fdab2f95bf2b717b66871c4ad9859d7e8"}, - {file = "pillow-11.0.0-cp313-cp313t-win32.whl", hash = "sha256:607bbe123c74e272e381a8d1957083a9463401f7bd01287f50521ecb05a313f8"}, - {file = "pillow-11.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5c39ed17edea3bc69c743a8dd3e9853b7509625c2462532e62baa0732163a904"}, - {file = "pillow-11.0.0-cp313-cp313t-win_arm64.whl", hash = "sha256:75acbbeb05b86bc53cbe7b7e6fe00fbcf82ad7c684b3ad82e3d711da9ba287d3"}, - {file = "pillow-11.0.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:2e46773dc9f35a1dd28bd6981332fd7f27bec001a918a72a79b4133cf5291dba"}, - {file = "pillow-11.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2679d2258b7f1192b378e2893a8a0a0ca472234d4c2c0e6bdd3380e8dfa21b6a"}, - {file = "pillow-11.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eda2616eb2313cbb3eebbe51f19362eb434b18e3bb599466a1ffa76a033fb916"}, - {file = "pillow-11.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20ec184af98a121fb2da42642dea8a29ec80fc3efbaefb86d8fdd2606619045d"}, - {file = "pillow-11.0.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:8594f42df584e5b4bb9281799698403f7af489fba84c34d53d1c4bfb71b7c4e7"}, - {file = "pillow-11.0.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:c12b5ae868897c7338519c03049a806af85b9b8c237b7d675b8c5e089e4a618e"}, - {file = "pillow-11.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:70fbbdacd1d271b77b7721fe3cdd2d537bbbd75d29e6300c672ec6bb38d9672f"}, - {file = "pillow-11.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5178952973e588b3f1360868847334e9e3bf49d19e169bbbdfaf8398002419ae"}, - {file = "pillow-11.0.0-cp39-cp39-win32.whl", hash = "sha256:8c676b587da5673d3c75bd67dd2a8cdfeb282ca38a30f37950511766b26858c4"}, - {file = "pillow-11.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:94f3e1780abb45062287b4614a5bc0874519c86a777d4a7ad34978e86428b8dd"}, - {file = "pillow-11.0.0-cp39-cp39-win_arm64.whl", hash = "sha256:290f2cc809f9da7d6d622550bbf4c1e57518212da51b6a30fe8e0a270a5b78bd"}, - {file = "pillow-11.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1187739620f2b365de756ce086fdb3604573337cc28a0d3ac4a01ab6b2d2a6d2"}, - {file = "pillow-11.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:fbbcb7b57dc9c794843e3d1258c0fbf0f48656d46ffe9e09b63bbd6e8cd5d0a2"}, - {file = "pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d203af30149ae339ad1b4f710d9844ed8796e97fda23ffbc4cc472968a47d0b"}, - {file = "pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21a0d3b115009ebb8ac3d2ebec5c2982cc693da935f4ab7bb5c8ebe2f47d36f2"}, - {file = "pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:73853108f56df97baf2bb8b522f3578221e56f646ba345a372c78326710d3830"}, - {file = "pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e58876c91f97b0952eb766123bfef372792ab3f4e3e1f1a2267834c2ab131734"}, - {file = "pillow-11.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:224aaa38177597bb179f3ec87eeefcce8e4f85e608025e9cfac60de237ba6316"}, - {file = "pillow-11.0.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:5bd2d3bdb846d757055910f0a59792d33b555800813c3b39ada1829c372ccb06"}, - {file = "pillow-11.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:375b8dd15a1f5d2feafff536d47e22f69625c1aa92f12b339ec0b2ca40263273"}, - {file = "pillow-11.0.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:daffdf51ee5db69a82dd127eabecce20729e21f7a3680cf7cbb23f0829189790"}, - {file = "pillow-11.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7326a1787e3c7b0429659e0a944725e1b03eeaa10edd945a86dead1913383944"}, - {file = "pillow-11.0.0.tar.gz", hash = "sha256:72bacbaf24ac003fea9bff9837d1eedb6088758d41e100c1552930151f677739"}, +groups = ["main"] +files = [ + {file = "pillow-11.1.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:e1abe69aca89514737465752b4bcaf8016de61b3be1397a8fc260ba33321b3a8"}, + {file = "pillow-11.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c640e5a06869c75994624551f45e5506e4256562ead981cce820d5ab39ae2192"}, + {file = "pillow-11.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a07dba04c5e22824816b2615ad7a7484432d7f540e6fa86af60d2de57b0fcee2"}, + {file = "pillow-11.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e267b0ed063341f3e60acd25c05200df4193e15a4a5807075cd71225a2386e26"}, + {file = "pillow-11.1.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:bd165131fd51697e22421d0e467997ad31621b74bfc0b75956608cb2906dda07"}, + {file = "pillow-11.1.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:abc56501c3fd148d60659aae0af6ddc149660469082859fa7b066a298bde9482"}, + {file = "pillow-11.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:54ce1c9a16a9561b6d6d8cb30089ab1e5eb66918cb47d457bd996ef34182922e"}, + {file = "pillow-11.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:73ddde795ee9b06257dac5ad42fcb07f3b9b813f8c1f7f870f402f4dc54b5269"}, + {file = "pillow-11.1.0-cp310-cp310-win32.whl", hash = "sha256:3a5fe20a7b66e8135d7fd617b13272626a28278d0e578c98720d9ba4b2439d49"}, + {file = "pillow-11.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:b6123aa4a59d75f06e9dd3dac5bf8bc9aa383121bb3dd9a7a612e05eabc9961a"}, + {file = "pillow-11.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:a76da0a31da6fcae4210aa94fd779c65c75786bc9af06289cd1c184451ef7a65"}, + {file = "pillow-11.1.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:e06695e0326d05b06833b40b7ef477e475d0b1ba3a6d27da1bb48c23209bf457"}, + {file = "pillow-11.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96f82000e12f23e4f29346e42702b6ed9a2f2fea34a740dd5ffffcc8c539eb35"}, + {file = "pillow-11.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3cd561ded2cf2bbae44d4605837221b987c216cff94f49dfeed63488bb228d2"}, + {file = "pillow-11.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f189805c8be5ca5add39e6f899e6ce2ed824e65fb45f3c28cb2841911da19070"}, + {file = "pillow-11.1.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:dd0052e9db3474df30433f83a71b9b23bd9e4ef1de13d92df21a52c0303b8ab6"}, + {file = "pillow-11.1.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:837060a8599b8f5d402e97197d4924f05a2e0d68756998345c829c33186217b1"}, + {file = "pillow-11.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:aa8dd43daa836b9a8128dbe7d923423e5ad86f50a7a14dc688194b7be5c0dea2"}, + {file = "pillow-11.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0a2f91f8a8b367e7a57c6e91cd25af510168091fb89ec5146003e424e1558a96"}, + {file = "pillow-11.1.0-cp311-cp311-win32.whl", hash = "sha256:c12fc111ef090845de2bb15009372175d76ac99969bdf31e2ce9b42e4b8cd88f"}, + {file = "pillow-11.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:fbd43429d0d7ed6533b25fc993861b8fd512c42d04514a0dd6337fb3ccf22761"}, + {file = "pillow-11.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:f7955ecf5609dee9442cbface754f2c6e541d9e6eda87fad7f7a989b0bdb9d71"}, + {file = "pillow-11.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2062ffb1d36544d42fcaa277b069c88b01bb7298f4efa06731a7fd6cc290b81a"}, + {file = "pillow-11.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a85b653980faad27e88b141348707ceeef8a1186f75ecc600c395dcac19f385b"}, + {file = "pillow-11.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9409c080586d1f683df3f184f20e36fb647f2e0bc3988094d4fd8c9f4eb1b3b3"}, + {file = "pillow-11.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7fdadc077553621911f27ce206ffcbec7d3f8d7b50e0da39f10997e8e2bb7f6a"}, + {file = "pillow-11.1.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:93a18841d09bcdd774dcdc308e4537e1f867b3dec059c131fde0327899734aa1"}, + {file = "pillow-11.1.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:9aa9aeddeed452b2f616ff5507459e7bab436916ccb10961c4a382cd3e03f47f"}, + {file = "pillow-11.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3cdcdb0b896e981678eee140d882b70092dac83ac1cdf6b3a60e2216a73f2b91"}, + {file = "pillow-11.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:36ba10b9cb413e7c7dfa3e189aba252deee0602c86c309799da5a74009ac7a1c"}, + {file = "pillow-11.1.0-cp312-cp312-win32.whl", hash = "sha256:cfd5cd998c2e36a862d0e27b2df63237e67273f2fc78f47445b14e73a810e7e6"}, + {file = "pillow-11.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:a697cd8ba0383bba3d2d3ada02b34ed268cb548b369943cd349007730c92bddf"}, + {file = "pillow-11.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:4dd43a78897793f60766563969442020e90eb7847463eca901e41ba186a7d4a5"}, + {file = "pillow-11.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ae98e14432d458fc3de11a77ccb3ae65ddce70f730e7c76140653048c71bfcbc"}, + {file = "pillow-11.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cc1331b6d5a6e144aeb5e626f4375f5b7ae9934ba620c0ac6b3e43d5e683a0f0"}, + {file = "pillow-11.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:758e9d4ef15d3560214cddbc97b8ef3ef86ce04d62ddac17ad39ba87e89bd3b1"}, + {file = "pillow-11.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b523466b1a31d0dcef7c5be1f20b942919b62fd6e9a9be199d035509cbefc0ec"}, + {file = "pillow-11.1.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:9044b5e4f7083f209c4e35aa5dd54b1dd5b112b108648f5c902ad586d4f945c5"}, + {file = "pillow-11.1.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:3764d53e09cdedd91bee65c2527815d315c6b90d7b8b79759cc48d7bf5d4f114"}, + {file = "pillow-11.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:31eba6bbdd27dde97b0174ddf0297d7a9c3a507a8a1480e1e60ef914fe23d352"}, + {file = "pillow-11.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b5d658fbd9f0d6eea113aea286b21d3cd4d3fd978157cbf2447a6035916506d3"}, + {file = "pillow-11.1.0-cp313-cp313-win32.whl", hash = "sha256:f86d3a7a9af5d826744fabf4afd15b9dfef44fe69a98541f666f66fbb8d3fef9"}, + {file = "pillow-11.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:593c5fd6be85da83656b93ffcccc2312d2d149d251e98588b14fbc288fd8909c"}, + {file = "pillow-11.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:11633d58b6ee5733bde153a8dafd25e505ea3d32e261accd388827ee987baf65"}, + {file = "pillow-11.1.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:70ca5ef3b3b1c4a0812b5c63c57c23b63e53bc38e758b37a951e5bc466449861"}, + {file = "pillow-11.1.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8000376f139d4d38d6851eb149b321a52bb8893a88dae8ee7d95840431977081"}, + {file = "pillow-11.1.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ee85f0696a17dd28fbcfceb59f9510aa71934b483d1f5601d1030c3c8304f3c"}, + {file = "pillow-11.1.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:dd0e081319328928531df7a0e63621caf67652c8464303fd102141b785ef9547"}, + {file = "pillow-11.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e63e4e5081de46517099dc30abe418122f54531a6ae2ebc8680bcd7096860eab"}, + {file = "pillow-11.1.0-cp313-cp313t-win32.whl", hash = "sha256:dda60aa465b861324e65a78c9f5cf0f4bc713e4309f83bc387be158b077963d9"}, + {file = "pillow-11.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ad5db5781c774ab9a9b2c4302bbf0c1014960a0a7be63278d13ae6fdf88126fe"}, + {file = "pillow-11.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:67cd427c68926108778a9005f2a04adbd5e67c442ed21d95389fe1d595458756"}, + {file = "pillow-11.1.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:bf902d7413c82a1bfa08b06a070876132a5ae6b2388e2712aab3a7cbc02205c6"}, + {file = "pillow-11.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c1eec9d950b6fe688edee07138993e54ee4ae634c51443cfb7c1e7613322718e"}, + {file = "pillow-11.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e275ee4cb11c262bd108ab2081f750db2a1c0b8c12c1897f27b160c8bd57bbc"}, + {file = "pillow-11.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4db853948ce4e718f2fc775b75c37ba2efb6aaea41a1a5fc57f0af59eee774b2"}, + {file = "pillow-11.1.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:ab8a209b8485d3db694fa97a896d96dd6533d63c22829043fd9de627060beade"}, + {file = "pillow-11.1.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:54251ef02a2309b5eec99d151ebf5c9904b77976c8abdcbce7891ed22df53884"}, + {file = "pillow-11.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5bb94705aea800051a743aa4874bb1397d4695fb0583ba5e425ee0328757f196"}, + {file = "pillow-11.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:89dbdb3e6e9594d512780a5a1c42801879628b38e3efc7038094430844e271d8"}, + {file = "pillow-11.1.0-cp39-cp39-win32.whl", hash = "sha256:e5449ca63da169a2e6068dd0e2fcc8d91f9558aba89ff6d02121ca8ab11e79e5"}, + {file = "pillow-11.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:3362c6ca227e65c54bf71a5f88b3d4565ff1bcbc63ae72c34b07bbb1cc59a43f"}, + {file = "pillow-11.1.0-cp39-cp39-win_arm64.whl", hash = "sha256:b20be51b37a75cc54c2c55def3fa2c65bb94ba859dde241cd0a4fd302de5ae0a"}, + {file = "pillow-11.1.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:8c730dc3a83e5ac137fbc92dfcfe1511ce3b2b5d7578315b63dbbb76f7f51d90"}, + {file = "pillow-11.1.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:7d33d2fae0e8b170b6a6c57400e077412240f6f5bb2a342cf1ee512a787942bb"}, + {file = "pillow-11.1.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a8d65b38173085f24bc07f8b6c505cbb7418009fa1a1fcb111b1f4961814a442"}, + {file = "pillow-11.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:015c6e863faa4779251436db398ae75051469f7c903b043a48f078e437656f83"}, + {file = "pillow-11.1.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d44ff19eea13ae4acdaaab0179fa68c0c6f2f45d66a4d8ec1eda7d6cecbcc15f"}, + {file = "pillow-11.1.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d3d8da4a631471dfaf94c10c85f5277b1f8e42ac42bade1ac67da4b4a7359b73"}, + {file = "pillow-11.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:4637b88343166249fe8aa94e7c4a62a180c4b3898283bb5d3d2fd5fe10d8e4e0"}, + {file = "pillow-11.1.0.tar.gz", hash = "sha256:368da70808b36d73b4b390a8ffac11069f8a5c85f29eff1f1b01bcf3ef5b2a20"}, ] [package.extras] docs = ["furo", "olefile", "sphinx (>=8.1)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"] fpx = ["olefile"] mic = ["olefile"] -tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] +tests = ["check-manifest", "coverage (>=7.4.2)", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout", "trove-classifiers (>=2024.10.12)"] typing = ["typing-extensions"] xmp = ["defusedxml"] @@ -2007,6 +2459,7 @@ version = "4.3.6" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, @@ -2023,6 +2476,7 @@ version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, @@ -2038,6 +2492,7 @@ version = "3.8.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "pre_commit-3.8.0-py2.py3-none-any.whl", hash = "sha256:9a90a53bf82fdd8778d58085faf8d83df56e40dfe18f45b19446e26bf1b3a63f"}, {file = "pre_commit-3.8.0.tar.gz", hash = "sha256:8bb6494d4a20423842e198980c9ecf9f96607a07ea29549e180eef9ae80fe7af"}, @@ -2052,13 +2507,14 @@ virtualenv = ">=20.10.0" [[package]] name = "prompt-toolkit" -version = "3.0.48" +version = "3.0.50" description = "Library for building powerful interactive command lines in Python" optional = false -python-versions = ">=3.7.0" +python-versions = ">=3.8.0" +groups = ["main", "dev"] files = [ - {file = "prompt_toolkit-3.0.48-py3-none-any.whl", hash = "sha256:f49a827f90062e411f1ce1f854f2aedb3c23353244f8108b89283587397ac10e"}, - {file = "prompt_toolkit-3.0.48.tar.gz", hash = "sha256:d6623ab0477a80df74e646bdbc93621143f5caf104206aa29294d53de1a03d90"}, + {file = "prompt_toolkit-3.0.50-py3-none-any.whl", hash = "sha256:9b6427eb19e479d98acff65196a307c555eb567989e6d88ebbb1b509d9779198"}, + {file = "prompt_toolkit-3.0.50.tar.gz", hash = "sha256:544748f3860a2623ca5cd6d2795e7a14f3d0e1c3c9728359013f79877fc89bab"}, ] [package.dependencies] @@ -2070,6 +2526,7 @@ version = "2.9.10" description = "psycopg2 - Python-PostgreSQL Database Adapter" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "psycopg2-2.9.10-cp310-cp310-win32.whl", hash = "sha256:5df2b672140f95adb453af93a7d669d7a7bf0a56bcd26f1502329166f4a61716"}, {file = "psycopg2-2.9.10-cp310-cp310-win_amd64.whl", hash = "sha256:c6f7b8561225f9e711a9c47087388a97fdc948211c10a4bccbf0ba68ab7b3b5a"}, @@ -2077,6 +2534,7 @@ files = [ {file = "psycopg2-2.9.10-cp311-cp311-win_amd64.whl", hash = "sha256:0435034157049f6846e95103bd8f5a668788dd913a7c30162ca9503fdf542cb4"}, {file = "psycopg2-2.9.10-cp312-cp312-win32.whl", hash = "sha256:65a63d7ab0e067e2cdb3cf266de39663203d38d6a8ed97f5ca0cb315c73fe067"}, {file = "psycopg2-2.9.10-cp312-cp312-win_amd64.whl", hash = "sha256:4a579d6243da40a7b3182e0430493dbd55950c493d8c68f4eec0b302f6bbf20e"}, + {file = "psycopg2-2.9.10-cp313-cp313-win_amd64.whl", hash = "sha256:91fd603a2155da8d0cfcdbf8ab24a2d54bca72795b90d2a3ed2b6da8d979dee2"}, {file = "psycopg2-2.9.10-cp39-cp39-win32.whl", hash = "sha256:9d5b3b94b79a844a986d029eee38998232451119ad653aea42bb9220a8c5066b"}, {file = "psycopg2-2.9.10-cp39-cp39-win_amd64.whl", hash = "sha256:88138c8dedcbfa96408023ea2b0c369eda40fe5d75002c0964c78f46f11fa442"}, {file = "psycopg2-2.9.10.tar.gz", hash = "sha256:12ec0b40b0273f95296233e8750441339298e6a572f7039da5b260e3c8b60e11"}, @@ -2088,6 +2546,8 @@ version = "0.7.0" description = "Run a subprocess in a pseudo terminal" optional = false python-versions = "*" +groups = ["main", "dev"] +markers = "sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, @@ -2099,6 +2559,7 @@ version = "0.2.3" description = "Safely evaluate AST nodes without side effects" optional = false python-versions = "*" +groups = ["main", "dev"] files = [ {file = "pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0"}, {file = "pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42"}, @@ -2113,6 +2574,7 @@ version = "0.6.1" description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629"}, {file = "pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034"}, @@ -2124,6 +2586,7 @@ version = "0.4.1" description = "A collection of ASN.1-based protocols modules" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "pyasn1_modules-0.4.1-py3-none-any.whl", hash = "sha256:49bfa96b45a292b711e986f222502c1c9a5e1f4e568fc30e2574a6c7d07838fd"}, {file = "pyasn1_modules-0.4.1.tar.gz", hash = "sha256:c28e2dbf9c06ad61c71a075c7e0f9fd0f1b0bb2d2ad4377f240d33ac2ab60a7c"}, @@ -2138,6 +2601,7 @@ version = "2.12.1" description = "Python style guide checker" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "pycodestyle-2.12.1-py2.py3-none-any.whl", hash = "sha256:46f0fb92069a7c28ab7bb558f05bfc0110dac69a0cd23c61ea0040283a9d78b3"}, {file = "pycodestyle-2.12.1.tar.gz", hash = "sha256:6838eae08bbce4f6accd5d5572075c63626a15ee3e6f842df996bf62f6d73521"}, @@ -2149,20 +2613,23 @@ version = "2.22" description = "C parser in Python" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, ] +markers = {dev = "os_name == \"nt\" and implementation_name != \"pypy\""} [[package]] name = "pydantic" -version = "2.10.4" +version = "2.10.6" description = "Data validation using Python type hints" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ - {file = "pydantic-2.10.4-py3-none-any.whl", hash = "sha256:597e135ea68be3a37552fb524bc7d0d66dcf93d395acd93a00682f1efcb8ee3d"}, - {file = "pydantic-2.10.4.tar.gz", hash = "sha256:82f12e9723da6de4fe2ba888b5971157b3be7ad914267dea8f05f82b28254f06"}, + {file = "pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584"}, + {file = "pydantic-2.10.6.tar.gz", hash = "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236"}, ] [package.dependencies] @@ -2180,6 +2647,7 @@ version = "2.27.2" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "pydantic_core-2.27.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa"}, {file = "pydantic_core-2.27.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c"}, @@ -2292,6 +2760,7 @@ version = "0.11.0" description = "A low-level PDF generator." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "pydyf-0.11.0-py3-none-any.whl", hash = "sha256:0aaf9e2ebbe786ec7a78ec3fbffa4cdcecde53fd6f563221d53c6bc1328848a3"}, {file = "pydyf-0.11.0.tar.gz", hash = "sha256:394dddf619cca9d0c55715e3c55ea121a9bf9cbc780cdc1201a2427917b86b64"}, @@ -2303,13 +2772,14 @@ test = ["pillow", "pytest", "ruff"] [[package]] name = "pygments" -version = "2.18.0" +version = "2.19.1" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ - {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, - {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, + {file = "pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c"}, + {file = "pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f"}, ] [package.extras] @@ -2321,6 +2791,7 @@ version = "1.5.0" description = "Python binding to the Networking and Cryptography (NaCl) library" optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "PyNaCl-1.5.0-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:401002a4aaa07c9414132aaed7f6836ff98f59277a234704ff66878c2ee4a0d1"}, {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:52cb72a79269189d4e0dc537556f4740f7f0a9ec41c1322598799b0bdad4ef92"}, @@ -2341,12 +2812,28 @@ cffi = ">=1.4.1" docs = ["sphinx (>=1.6.5)", "sphinx-rtd-theme"] tests = ["hypothesis (>=3.27.0)", "pytest (>=3.2.1,!=3.3.0)"] +[[package]] +name = "pyparsing" +version = "3.2.1" +description = "pyparsing module - Classes and methods to define and execute parsing grammars" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "pyparsing-3.2.1-py3-none-any.whl", hash = "sha256:506ff4f4386c4cec0590ec19e6302d3aedb992fdc02c761e90416f158dacf8e1"}, + {file = "pyparsing-3.2.1.tar.gz", hash = "sha256:61980854fd66de3a90028d679a954d5f2623e83144b5afe5ee86f43d762e5f0a"}, +] + +[package.extras] +diagrams = ["jinja2", "railroad-diagrams"] + [[package]] name = "pypdf" version = "4.3.1" description = "A pure-python PDF library capable of splitting, merging, cropping, and transforming PDF files" optional = false python-versions = ">=3.6" +groups = ["dev"] files = [ {file = "pypdf-4.3.1-py3-none-any.whl", hash = "sha256:64b31da97eda0771ef22edb1bfecd5deee4b72c3d1736b7df2689805076d6418"}, {file = "pypdf-4.3.1.tar.gz", hash = "sha256:b2f37fe9a3030aa97ca86067a56ba3f9d3565f9a791b305c7355d8392c30d91b"}, @@ -2361,13 +2848,14 @@ image = ["Pillow (>=8.0.0)"] [[package]] name = "pyphen" -version = "0.17.0" +version = "0.17.2" description = "Pure Python module to hyphenate text" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "pyphen-0.17.0-py3-none-any.whl", hash = "sha256:dad0b2e4aa80f6d70bf06711b2da36c47a756b933c1d0c4cbbab40f643a5958c"}, - {file = "pyphen-0.17.0.tar.gz", hash = "sha256:1d13acd1ce37a384d7612954ae6c7801bb4c5316da0e2b937b2127ba702a3da4"}, + {file = "pyphen-0.17.2-py3-none-any.whl", hash = "sha256:3a07fb017cb2341e1d9ff31b8634efb1ae4dc4b130468c7c39dd3d32e7c3affd"}, + {file = "pyphen-0.17.2.tar.gz", hash = "sha256:f60647a9c9b30ec6c59910097af82bc5dd2d36576b918e44148d8b07ef3b4aa3"}, ] [package.extras] @@ -2380,6 +2868,7 @@ version = "1.7.1" description = "A Python SOCKS client module. See https://github.com/Anorov/PySocks for more information." optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +groups = ["dev"] files = [ {file = "PySocks-1.7.1-py27-none-any.whl", hash = "sha256:08e69f092cc6dbe92a0fdd16eeb9b9ffbc13cadfe5ca4c7bd92ffb078b293299"}, {file = "PySocks-1.7.1-py3-none-any.whl", hash = "sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5"}, @@ -2392,6 +2881,7 @@ version = "8.3.4" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6"}, {file = "pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761"}, @@ -2412,6 +2902,7 @@ version = "5.0.0" description = "Pytest plugin for measuring coverage." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pytest-cov-5.0.0.tar.gz", hash = "sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857"}, {file = "pytest_cov-5.0.0-py3-none-any.whl", hash = "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652"}, @@ -2430,6 +2921,7 @@ version = "4.9.0" description = "A Django plugin for pytest." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pytest_django-4.9.0-py3-none-any.whl", hash = "sha256:1d83692cb39188682dbb419ff0393867e9904094a549a7d38a3154d5731b2b99"}, {file = "pytest_django-4.9.0.tar.gz", hash = "sha256:8bf7bc358c9ae6f6fc51b6cebb190fe20212196e6807121f11bd6a3b03428314"}, @@ -2448,6 +2940,7 @@ version = "1.0.0" description = "pytest-sugar is a plugin for pytest that changes the default look and feel of pytest (e.g. progressbar, show tests that fail instantly)." optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "pytest-sugar-1.0.0.tar.gz", hash = "sha256:6422e83258f5b0c04ce7c632176c7732cab5fdb909cb39cca5c9139f81276c0a"}, {file = "pytest_sugar-1.0.0-py3-none-any.whl", hash = "sha256:70ebcd8fc5795dc457ff8b69d266a4e2e8a74ae0c3edc749381c64b5246c8dfd"}, @@ -2467,6 +2960,7 @@ version = "0.15.1" description = "Create standard barcodes with Python. No external modules needed. (optional Pillow support included)." optional = false python-versions = "*" +groups = ["main"] files = [ {file = "python-barcode-0.15.1.tar.gz", hash = "sha256:3b1825fbdb11e597466dff4286b4ea9b1e86a57717b59e563ae679726fc854de"}, {file = "python_barcode-0.15.1-py3-none-any.whl", hash = "sha256:057636fba37369c22852410c8535b36adfbeb965ddfd4e5b6924455d692e0886"}, @@ -2481,6 +2975,7 @@ version = "2.9.0.post0" description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["main", "dev"] files = [ {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, @@ -2495,6 +2990,7 @@ version = "3.4.4" description = "Python modules for implementing LDAP clients" optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "python-ldap-3.4.4.tar.gz", hash = "sha256:7edb0accec4e037797705f3a05cbf36a9fde50d08c8f67f2aef99a2628fab828"}, ] @@ -2503,12 +2999,25 @@ files = [ pyasn1 = ">=0.3.7" pyasn1_modules = ">=0.1.5" +[[package]] +name = "pytz" +version = "2024.2" +description = "World timezone definitions, modern and historical" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725"}, + {file = "pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a"}, +] + [[package]] name = "pyyaml" version = "6.0.2" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, @@ -2571,6 +3080,7 @@ version = "5.2.1" description = "Python client for Redis database and key-value store" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "redis-5.2.1-py3-none-any.whl", hash = "sha256:ee7e1056b9aea0f04c6c2ed59452947f34c4940ee025f5dd83e6a6418b6989e4"}, {file = "redis-5.2.1.tar.gz", hash = "sha256:16f2e22dff21d5125e8481515e386711a34cbec50f0e44413dd7d9c060a54e0f"}, @@ -2582,13 +3092,14 @@ ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==23.2.1)", "requests (>=2.31.0)" [[package]] name = "referencing" -version = "0.35.1" +version = "0.36.2" description = "JSON Referencing + Python" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["dev"] files = [ - {file = "referencing-0.35.1-py3-none-any.whl", hash = "sha256:eda6d3234d62814d1c64e305c1331c9a3a6132da475ab6382eaa997b21ee75de"}, - {file = "referencing-0.35.1.tar.gz", hash = "sha256:25b42124a6c8b632a425174f24087783efb348a6f1e0008e63cd4466fedf703c"}, + {file = "referencing-0.36.2-py3-none-any.whl", hash = "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0"}, + {file = "referencing-0.36.2.tar.gz", hash = "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa"}, ] [package.dependencies] @@ -2601,6 +3112,7 @@ version = "2024.11.6" description = "Alternative regular expression module, to replace re." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "regex-2024.11.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ff590880083d60acc0433f9c3f713c51f7ac6ebb9adf889c79a261ecf541aa91"}, {file = "regex-2024.11.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:658f90550f38270639e83ce492f27d2c8d2cd63805c65a13a14d36ca126753f0"}, @@ -2704,6 +3216,7 @@ version = "2.32.3" description = "Python HTTP for Humans." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, @@ -2725,6 +3238,7 @@ version = "0.22.3" description = "Python bindings to Rust's persistent data structures (rpds)" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "rpds_py-0.22.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:6c7b99ca52c2c1752b544e310101b98a659b720b21db00e65edca34483259967"}, {file = "rpds_py-0.22.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:be2eb3f2495ba669d2a985f9b426c1797b7d48d6963899276d22f23e33d47e37"}, @@ -2833,13 +3347,14 @@ files = [ [[package]] name = "selenium" -version = "4.27.1" +version = "4.28.1" description = "Official Python bindings for Selenium WebDriver" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["dev"] files = [ - {file = "selenium-4.27.1-py3-none-any.whl", hash = "sha256:b89b1f62b5cfe8025868556fe82360d6b649d464f75d2655cb966c8f8447ea18"}, - {file = "selenium-4.27.1.tar.gz", hash = "sha256:5296c425a75ff1b44d0d5199042b36a6d1ef76c04fb775b97b40be739a9caae2"}, + {file = "selenium-4.28.1-py3-none-any.whl", hash = "sha256:4238847e45e24e4472cfcf3554427512c7aab9443396435b1623ef406fff1cc1"}, + {file = "selenium-4.28.1.tar.gz", hash = "sha256:0072d08670d7ec32db901bd0107695a330cecac9f196e3afb3fa8163026e022a"}, ] [package.dependencies] @@ -2856,6 +3371,7 @@ version = "1.17.0" description = "Python 2 and 3 compatibility utilities" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["main", "dev"] files = [ {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, @@ -2867,6 +3383,7 @@ version = "1.3.1" description = "Sniff out which async library your code is running under" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, @@ -2878,6 +3395,7 @@ version = "2.4.0" description = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0"}, {file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"}, @@ -2889,6 +3407,7 @@ version = "0.5.3" description = "A non-validating SQL parser." optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "sqlparse-0.5.3-py3-none-any.whl", hash = "sha256:cf2196ed3418f3ba5de6af7e82c694a9fbdbfecccdfc72e281548517081f16ca"}, {file = "sqlparse-0.5.3.tar.gz", hash = "sha256:09f67787f56a0b16ecdbde1bfc7f5d9c3371ca683cfeaa8e6ff60b4807ec9272"}, @@ -2904,6 +3423,7 @@ version = "0.6.3" description = "Extract data from python stack frames and tracebacks for informative displays" optional = false python-versions = "*" +groups = ["main", "dev"] files = [ {file = "stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695"}, {file = "stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9"}, @@ -2919,13 +3439,14 @@ tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] [[package]] name = "tablib" -version = "3.7.0" +version = "3.8.0" description = "Format agnostic tabular data library (XLS, JSON, YAML, CSV, etc.)" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "tablib-3.7.0-py3-none-any.whl", hash = "sha256:9a6930037cfe0f782377963ca3f2b1dae3fd4cdbf0883848f22f1447e7bb718b"}, - {file = "tablib-3.7.0.tar.gz", hash = "sha256:f9db84ed398df5109bd69c11d46613d16cc572fb9ad3213f10d95e2b5f12c18e"}, + {file = "tablib-3.8.0-py3-none-any.whl", hash = "sha256:35bdb9d4ec7052232f8803908f9c7a9c3c65807188b70618fa7a7d8ccd560b4d"}, + {file = "tablib-3.8.0.tar.gz", hash = "sha256:94d8bcdc65a715a0024a6d5b701a5f31e45bd159269e62c73731de79f048db2b"}, ] [package.extras] @@ -2943,6 +3464,7 @@ version = "2.5.0" description = "ANSI color formatting for output in terminal" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "termcolor-2.5.0-py3-none-any.whl", hash = "sha256:37b17b5fc1e604945c2642c872a3764b5d547a48009871aea3edd3afa180afb8"}, {file = "termcolor-2.5.0.tar.gz", hash = "sha256:998d8d27da6d48442e8e1f016119076b690d962507531df4890fcd2db2ef8a6f"}, @@ -2957,6 +3479,7 @@ version = "1.4.0" description = "A tiny CSS parser" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "tinycss2-1.4.0-py3-none-any.whl", hash = "sha256:3a49cf47b7675da0b15d0c6e1df8df4ebd96e9394bb905a5775adb0d884c5289"}, {file = "tinycss2-1.4.0.tar.gz", hash = "sha256:10c0972f6fc0fbee87c3edb76549357415e94548c1ae10ebccdea16fb404a9b7"}, @@ -2975,6 +3498,7 @@ version = "6.1.0" description = "A wrapper around the stdlib `tokenize` which roundtrips." optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "tokenize_rt-6.1.0-py2.py3-none-any.whl", hash = "sha256:d706141cdec4aa5f358945abe36b911b8cbdc844545da99e811250c0cee9b6fc"}, {file = "tokenize_rt-6.1.0.tar.gz", hash = "sha256:e8ee836616c0877ab7c7b54776d2fefcc3bde714449a206762425ae114b53c86"}, @@ -2986,6 +3510,7 @@ version = "4.67.1" description = "Fast, Extensible Progress Meter" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2"}, {file = "tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2"}, @@ -3007,6 +3532,7 @@ version = "5.14.3" description = "Traitlets Python configuration system" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f"}, {file = "traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7"}, @@ -3022,6 +3548,7 @@ version = "0.28.0" description = "A friendly Python library for async concurrency and I/O" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "trio-0.28.0-py3-none-any.whl", hash = "sha256:56d58977acc1635735a96581ec70513cc781b8b6decd299c487d3be2a721cd94"}, {file = "trio-0.28.0.tar.gz", hash = "sha256:4e547896fe9e8a5658e54e4c7c5fa1db748cbbbaa7c965e7d40505b928c73c05"}, @@ -3041,6 +3568,7 @@ version = "0.11.1" description = "WebSocket library for Trio" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "trio-websocket-0.11.1.tar.gz", hash = "sha256:18c11793647703c158b1f6e62de638acada927344d534e3c7628eedcb746839f"}, {file = "trio_websocket-0.11.1-py3-none-any.whl", hash = "sha256:520d046b0d030cf970b8b2b2e00c4c2245b3807853ecd44214acd33d74581638"}, @@ -3056,6 +3584,7 @@ version = "4.12.2" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, @@ -3063,14 +3592,16 @@ files = [ [[package]] name = "tzdata" -version = "2024.2" +version = "2025.1" description = "Provider of IANA time zone data" optional = false python-versions = ">=2" +groups = ["main", "dev"] files = [ - {file = "tzdata-2024.2-py2.py3-none-any.whl", hash = "sha256:a48093786cdcde33cad18c2555e8532f34422074448fbc874186f0abd79565cd"}, - {file = "tzdata-2024.2.tar.gz", hash = "sha256:7d85cc416e9382e69095b7bdf4afd9e3880418a2413feec7069d533d6b4e31cc"}, + {file = "tzdata-2025.1-py2.py3-none-any.whl", hash = "sha256:7e127113816800496f027041c570f50bcd464a020098a3b6b199517772303639"}, + {file = "tzdata-2025.1.tar.gz", hash = "sha256:24894909e88cdb28bd1636c6887801df64cb485bd593f2fd83ef29075a81d694"}, ] +markers = {dev = "sys_platform == \"win32\""} [[package]] name = "unidecode" @@ -3078,6 +3609,7 @@ version = "1.3.8" description = "ASCII transliterations of Unicode text" optional = false python-versions = ">=3.5" +groups = ["main"] files = [ {file = "Unidecode-1.3.8-py3-none-any.whl", hash = "sha256:d130a61ce6696f8148a3bd8fe779c99adeb4b870584eeb9526584e9aa091fd39"}, {file = "Unidecode-1.3.8.tar.gz", hash = "sha256:cfdb349d46ed3873ece4586b96aa75258726e2fa8ec21d6f00a591d98806c2f4"}, @@ -3089,6 +3621,7 @@ version = "4.1.1" description = "Implementation of RFC 6570 URI Templates" optional = false python-versions = ">=3.6" +groups = ["dev"] files = [ {file = "uritemplate-4.1.1-py2.py3-none-any.whl", hash = "sha256:830c08b8d99bdd312ea4ead05994a38e8936266f84b9a7878232db50b044e02e"}, {file = "uritemplate-4.1.1.tar.gz", hash = "sha256:4346edfc5c3b79f694bccd6d6099a322bbeb628dbf2cd86eea55a456ce5124f0"}, @@ -3100,6 +3633,7 @@ version = "2.3.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df"}, {file = "urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d"}, @@ -3120,6 +3654,7 @@ version = "5.1.0" description = "Python promises." optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "vine-5.1.0-py3-none-any.whl", hash = "sha256:40fdf3c48b2cfe1c38a49e9ae2da6fda88e4794c810050a728bd7413811fb1dc"}, {file = "vine-5.1.0.tar.gz", hash = "sha256:8b62e981d35c41049211cf62a0a1242d8c1ee9bd15bb196ce38aefd6799e61e0"}, @@ -3127,13 +3662,14 @@ files = [ [[package]] name = "virtualenv" -version = "20.28.0" +version = "20.29.1" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ - {file = "virtualenv-20.28.0-py3-none-any.whl", hash = "sha256:23eae1b4516ecd610481eda647f3a7c09aea295055337331bb4e6892ecce47b0"}, - {file = "virtualenv-20.28.0.tar.gz", hash = "sha256:2c9c3262bb8e7b87ea801d715fae4495e6032450c71d2309be9550e7364049aa"}, + {file = "virtualenv-20.29.1-py3-none-any.whl", hash = "sha256:4e4cb403c0b0da39e13b46b1b2476e505cb0046b25f242bee80f62bf990b2779"}, + {file = "virtualenv-20.29.1.tar.gz", hash = "sha256:b8b8970138d32fb606192cb97f6cd4bb644fa486be9308fb9b63f81091b5dc35"}, ] [package.dependencies] @@ -3151,6 +3687,7 @@ version = "0.2.13" description = "Measures the displayed width of unicode strings in a terminal" optional = false python-versions = "*" +groups = ["main", "dev"] files = [ {file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"}, {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"}, @@ -3162,6 +3699,7 @@ version = "62.3" description = "The Awesome Document Factory" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "weasyprint-62.3-py3-none-any.whl", hash = "sha256:d31048646ce15084e135b33e334a61f526aa68d2f679fcc109ed0e0f5edaed21"}, {file = "weasyprint-62.3.tar.gz", hash = "sha256:8d8680d732f7fa0fcbc587692a5a5cb095c3525627066918d6e203cbf42b7fcd"}, @@ -3187,6 +3725,7 @@ version = "0.5.1" description = "Character encoding aliases for legacy web content" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"}, {file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"}, @@ -3198,6 +3737,7 @@ version = "1.8.0" description = "WebSocket client for Python with low level API options" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "websocket_client-1.8.0-py3-none-any.whl", hash = "sha256:17b44cc997f5c498e809b22cdf2d9c7a9e71c02c8cc2b6c56e7c2d1239bfa526"}, {file = "websocket_client-1.8.0.tar.gz", hash = "sha256:3239df9f44da632f96012472805d40a23281a991027ce11d2f45a6f24ac4c3da"}, @@ -3214,6 +3754,7 @@ version = "3.1.3" description = "The comprehensive WSGI web application library." optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e"}, {file = "werkzeug-3.1.3.tar.gz", hash = "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746"}, @@ -3227,76 +3768,91 @@ watchdog = ["watchdog (>=2.3)"] [[package]] name = "wrapt" -version = "1.17.0" +version = "1.17.2" description = "Module for decorators, wrappers and monkey patching." optional = false python-versions = ">=3.8" -files = [ - {file = "wrapt-1.17.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2a0c23b8319848426f305f9cb0c98a6e32ee68a36264f45948ccf8e7d2b941f8"}, - {file = "wrapt-1.17.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1ca5f060e205f72bec57faae5bd817a1560fcfc4af03f414b08fa29106b7e2d"}, - {file = "wrapt-1.17.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e185ec6060e301a7e5f8461c86fb3640a7beb1a0f0208ffde7a65ec4074931df"}, - {file = "wrapt-1.17.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb90765dd91aed05b53cd7a87bd7f5c188fcd95960914bae0d32c5e7f899719d"}, - {file = "wrapt-1.17.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:879591c2b5ab0a7184258274c42a126b74a2c3d5a329df16d69f9cee07bba6ea"}, - {file = "wrapt-1.17.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:fce6fee67c318fdfb7f285c29a82d84782ae2579c0e1b385b7f36c6e8074fffb"}, - {file = "wrapt-1.17.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0698d3a86f68abc894d537887b9bbf84d29bcfbc759e23f4644be27acf6da301"}, - {file = "wrapt-1.17.0-cp310-cp310-win32.whl", hash = "sha256:69d093792dc34a9c4c8a70e4973a3361c7a7578e9cd86961b2bbf38ca71e4e22"}, - {file = "wrapt-1.17.0-cp310-cp310-win_amd64.whl", hash = "sha256:f28b29dc158ca5d6ac396c8e0a2ef45c4e97bb7e65522bfc04c989e6fe814575"}, - {file = "wrapt-1.17.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:74bf625b1b4caaa7bad51d9003f8b07a468a704e0644a700e936c357c17dd45a"}, - {file = "wrapt-1.17.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f2a28eb35cf99d5f5bd12f5dd44a0f41d206db226535b37b0c60e9da162c3ed"}, - {file = "wrapt-1.17.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:81b1289e99cf4bad07c23393ab447e5e96db0ab50974a280f7954b071d41b489"}, - {file = "wrapt-1.17.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f2939cd4a2a52ca32bc0b359015718472d7f6de870760342e7ba295be9ebaf9"}, - {file = "wrapt-1.17.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6a9653131bda68a1f029c52157fd81e11f07d485df55410401f745007bd6d339"}, - {file = "wrapt-1.17.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4e4b4385363de9052dac1a67bfb535c376f3d19c238b5f36bddc95efae15e12d"}, - {file = "wrapt-1.17.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bdf62d25234290db1837875d4dceb2151e4ea7f9fff2ed41c0fde23ed542eb5b"}, - {file = "wrapt-1.17.0-cp311-cp311-win32.whl", hash = "sha256:5d8fd17635b262448ab8f99230fe4dac991af1dabdbb92f7a70a6afac8a7e346"}, - {file = "wrapt-1.17.0-cp311-cp311-win_amd64.whl", hash = "sha256:92a3d214d5e53cb1db8b015f30d544bc9d3f7179a05feb8f16df713cecc2620a"}, - {file = "wrapt-1.17.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:89fc28495896097622c3fc238915c79365dd0ede02f9a82ce436b13bd0ab7569"}, - {file = "wrapt-1.17.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:875d240fdbdbe9e11f9831901fb8719da0bd4e6131f83aa9f69b96d18fae7504"}, - {file = "wrapt-1.17.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5ed16d95fd142e9c72b6c10b06514ad30e846a0d0917ab406186541fe68b451"}, - {file = "wrapt-1.17.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18b956061b8db634120b58f668592a772e87e2e78bc1f6a906cfcaa0cc7991c1"}, - {file = "wrapt-1.17.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:daba396199399ccabafbfc509037ac635a6bc18510ad1add8fd16d4739cdd106"}, - {file = "wrapt-1.17.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4d63f4d446e10ad19ed01188d6c1e1bb134cde8c18b0aa2acfd973d41fcc5ada"}, - {file = "wrapt-1.17.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8a5e7cc39a45fc430af1aefc4d77ee6bad72c5bcdb1322cfde852c15192b8bd4"}, - {file = "wrapt-1.17.0-cp312-cp312-win32.whl", hash = "sha256:0a0a1a1ec28b641f2a3a2c35cbe86c00051c04fffcfcc577ffcdd707df3f8635"}, - {file = "wrapt-1.17.0-cp312-cp312-win_amd64.whl", hash = "sha256:3c34f6896a01b84bab196f7119770fd8466c8ae3dfa73c59c0bb281e7b588ce7"}, - {file = "wrapt-1.17.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:714c12485aa52efbc0fc0ade1e9ab3a70343db82627f90f2ecbc898fdf0bb181"}, - {file = "wrapt-1.17.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da427d311782324a376cacb47c1a4adc43f99fd9d996ffc1b3e8529c4074d393"}, - {file = "wrapt-1.17.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba1739fb38441a27a676f4de4123d3e858e494fac05868b7a281c0a383c098f4"}, - {file = "wrapt-1.17.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e711fc1acc7468463bc084d1b68561e40d1eaa135d8c509a65dd534403d83d7b"}, - {file = "wrapt-1.17.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:140ea00c87fafc42739bd74a94a5a9003f8e72c27c47cd4f61d8e05e6dec8721"}, - {file = "wrapt-1.17.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:73a96fd11d2b2e77d623a7f26e004cc31f131a365add1ce1ce9a19e55a1eef90"}, - {file = "wrapt-1.17.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0b48554952f0f387984da81ccfa73b62e52817a4386d070c75e4db7d43a28c4a"}, - {file = "wrapt-1.17.0-cp313-cp313-win32.whl", hash = "sha256:498fec8da10e3e62edd1e7368f4b24aa362ac0ad931e678332d1b209aec93045"}, - {file = "wrapt-1.17.0-cp313-cp313-win_amd64.whl", hash = "sha256:fd136bb85f4568fffca995bd3c8d52080b1e5b225dbf1c2b17b66b4c5fa02838"}, - {file = "wrapt-1.17.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:17fcf043d0b4724858f25b8826c36e08f9fb2e475410bece0ec44a22d533da9b"}, - {file = "wrapt-1.17.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4a557d97f12813dc5e18dad9fa765ae44ddd56a672bb5de4825527c847d6379"}, - {file = "wrapt-1.17.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0229b247b0fc7dee0d36176cbb79dbaf2a9eb7ecc50ec3121f40ef443155fb1d"}, - {file = "wrapt-1.17.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8425cfce27b8b20c9b89d77fb50e368d8306a90bf2b6eef2cdf5cd5083adf83f"}, - {file = "wrapt-1.17.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9c900108df470060174108012de06d45f514aa4ec21a191e7ab42988ff42a86c"}, - {file = "wrapt-1.17.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:4e547b447073fc0dbfcbff15154c1be8823d10dab4ad401bdb1575e3fdedff1b"}, - {file = "wrapt-1.17.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:914f66f3b6fc7b915d46c1cc424bc2441841083de01b90f9e81109c9759e43ab"}, - {file = "wrapt-1.17.0-cp313-cp313t-win32.whl", hash = "sha256:a4192b45dff127c7d69b3bdfb4d3e47b64179a0b9900b6351859f3001397dabf"}, - {file = "wrapt-1.17.0-cp313-cp313t-win_amd64.whl", hash = "sha256:4f643df3d4419ea3f856c5c3f40fec1d65ea2e89ec812c83f7767c8730f9827a"}, - {file = "wrapt-1.17.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:69c40d4655e078ede067a7095544bcec5a963566e17503e75a3a3e0fe2803b13"}, - {file = "wrapt-1.17.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f495b6754358979379f84534f8dd7a43ff8cff2558dcdea4a148a6e713a758f"}, - {file = "wrapt-1.17.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:baa7ef4e0886a6f482e00d1d5bcd37c201b383f1d314643dfb0367169f94f04c"}, - {file = "wrapt-1.17.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8fc931382e56627ec4acb01e09ce66e5c03c384ca52606111cee50d931a342d"}, - {file = "wrapt-1.17.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:8f8909cdb9f1b237786c09a810e24ee5e15ef17019f7cecb207ce205b9b5fcce"}, - {file = "wrapt-1.17.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:ad47b095f0bdc5585bced35bd088cbfe4177236c7df9984b3cc46b391cc60627"}, - {file = "wrapt-1.17.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:948a9bd0fb2c5120457b07e59c8d7210cbc8703243225dbd78f4dfc13c8d2d1f"}, - {file = "wrapt-1.17.0-cp38-cp38-win32.whl", hash = "sha256:5ae271862b2142f4bc687bdbfcc942e2473a89999a54231aa1c2c676e28f29ea"}, - {file = "wrapt-1.17.0-cp38-cp38-win_amd64.whl", hash = "sha256:f335579a1b485c834849e9075191c9898e0731af45705c2ebf70e0cd5d58beed"}, - {file = "wrapt-1.17.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d751300b94e35b6016d4b1e7d0e7bbc3b5e1751e2405ef908316c2a9024008a1"}, - {file = "wrapt-1.17.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7264cbb4a18dc4acfd73b63e4bcfec9c9802614572025bdd44d0721983fc1d9c"}, - {file = "wrapt-1.17.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:33539c6f5b96cf0b1105a0ff4cf5db9332e773bb521cc804a90e58dc49b10578"}, - {file = "wrapt-1.17.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c30970bdee1cad6a8da2044febd824ef6dc4cc0b19e39af3085c763fdec7de33"}, - {file = "wrapt-1.17.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:bc7f729a72b16ee21795a943f85c6244971724819819a41ddbaeb691b2dd85ad"}, - {file = "wrapt-1.17.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:6ff02a91c4fc9b6a94e1c9c20f62ea06a7e375f42fe57587f004d1078ac86ca9"}, - {file = "wrapt-1.17.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2dfb7cff84e72e7bf975b06b4989477873dcf160b2fd89959c629535df53d4e0"}, - {file = "wrapt-1.17.0-cp39-cp39-win32.whl", hash = "sha256:2399408ac33ffd5b200480ee858baa58d77dd30e0dd0cab6a8a9547135f30a88"}, - {file = "wrapt-1.17.0-cp39-cp39-win_amd64.whl", hash = "sha256:4f763a29ee6a20c529496a20a7bcb16a73de27f5da6a843249c7047daf135977"}, - {file = "wrapt-1.17.0-py3-none-any.whl", hash = "sha256:d2c63b93548eda58abf5188e505ffed0229bf675f7c3090f8e36ad55b8cbc371"}, - {file = "wrapt-1.17.0.tar.gz", hash = "sha256:16187aa2317c731170a88ef35e8937ae0f533c402872c1ee5e6d079fcf320801"}, +groups = ["main"] +files = [ + {file = "wrapt-1.17.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3d57c572081fed831ad2d26fd430d565b76aa277ed1d30ff4d40670b1c0dd984"}, + {file = "wrapt-1.17.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b5e251054542ae57ac7f3fba5d10bfff615b6c2fb09abeb37d2f1463f841ae22"}, + {file = "wrapt-1.17.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:80dd7db6a7cb57ffbc279c4394246414ec99537ae81ffd702443335a61dbf3a7"}, + {file = "wrapt-1.17.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a6e821770cf99cc586d33833b2ff32faebdbe886bd6322395606cf55153246c"}, + {file = "wrapt-1.17.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b60fb58b90c6d63779cb0c0c54eeb38941bae3ecf7a73c764c52c88c2dcb9d72"}, + {file = "wrapt-1.17.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b870b5df5b71d8c3359d21be8f0d6c485fa0ebdb6477dda51a1ea54a9b558061"}, + {file = "wrapt-1.17.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4011d137b9955791f9084749cba9a367c68d50ab8d11d64c50ba1688c9b457f2"}, + {file = "wrapt-1.17.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:1473400e5b2733e58b396a04eb7f35f541e1fb976d0c0724d0223dd607e0f74c"}, + {file = "wrapt-1.17.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3cedbfa9c940fdad3e6e941db7138e26ce8aad38ab5fe9dcfadfed9db7a54e62"}, + {file = "wrapt-1.17.2-cp310-cp310-win32.whl", hash = "sha256:582530701bff1dec6779efa00c516496968edd851fba224fbd86e46cc6b73563"}, + {file = "wrapt-1.17.2-cp310-cp310-win_amd64.whl", hash = "sha256:58705da316756681ad3c9c73fd15499aa4d8c69f9fd38dc8a35e06c12468582f"}, + {file = "wrapt-1.17.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ff04ef6eec3eee8a5efef2401495967a916feaa353643defcc03fc74fe213b58"}, + {file = "wrapt-1.17.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4db983e7bca53819efdbd64590ee96c9213894272c776966ca6306b73e4affda"}, + {file = "wrapt-1.17.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9abc77a4ce4c6f2a3168ff34b1da9b0f311a8f1cfd694ec96b0603dff1c79438"}, + {file = "wrapt-1.17.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b929ac182f5ace000d459c59c2c9c33047e20e935f8e39371fa6e3b85d56f4a"}, + {file = "wrapt-1.17.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f09b286faeff3c750a879d336fb6d8713206fc97af3adc14def0cdd349df6000"}, + {file = "wrapt-1.17.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a7ed2d9d039bd41e889f6fb9364554052ca21ce823580f6a07c4ec245c1f5d6"}, + {file = "wrapt-1.17.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:129a150f5c445165ff941fc02ee27df65940fcb8a22a61828b1853c98763a64b"}, + {file = "wrapt-1.17.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1fb5699e4464afe5c7e65fa51d4f99e0b2eadcc176e4aa33600a3df7801d6662"}, + {file = "wrapt-1.17.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9a2bce789a5ea90e51a02dfcc39e31b7f1e662bc3317979aa7e5538e3a034f72"}, + {file = "wrapt-1.17.2-cp311-cp311-win32.whl", hash = "sha256:4afd5814270fdf6380616b321fd31435a462019d834f83c8611a0ce7484c7317"}, + {file = "wrapt-1.17.2-cp311-cp311-win_amd64.whl", hash = "sha256:acc130bc0375999da18e3d19e5a86403667ac0c4042a094fefb7eec8ebac7cf3"}, + {file = "wrapt-1.17.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d5e2439eecc762cd85e7bd37161d4714aa03a33c5ba884e26c81559817ca0925"}, + {file = "wrapt-1.17.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fc7cb4c1c744f8c05cd5f9438a3caa6ab94ce8344e952d7c45a8ed59dd88392"}, + {file = "wrapt-1.17.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8fdbdb757d5390f7c675e558fd3186d590973244fab0c5fe63d373ade3e99d40"}, + {file = "wrapt-1.17.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bb1d0dbf99411f3d871deb6faa9aabb9d4e744d67dcaaa05399af89d847a91d"}, + {file = "wrapt-1.17.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d18a4865f46b8579d44e4fe1e2bcbc6472ad83d98e22a26c963d46e4c125ef0b"}, + {file = "wrapt-1.17.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc570b5f14a79734437cb7b0500376b6b791153314986074486e0b0fa8d71d98"}, + {file = "wrapt-1.17.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6d9187b01bebc3875bac9b087948a2bccefe464a7d8f627cf6e48b1bbae30f82"}, + {file = "wrapt-1.17.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9e8659775f1adf02eb1e6f109751268e493c73716ca5761f8acb695e52a756ae"}, + {file = "wrapt-1.17.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e8b2816ebef96d83657b56306152a93909a83f23994f4b30ad4573b00bd11bb9"}, + {file = "wrapt-1.17.2-cp312-cp312-win32.whl", hash = "sha256:468090021f391fe0056ad3e807e3d9034e0fd01adcd3bdfba977b6fdf4213ea9"}, + {file = "wrapt-1.17.2-cp312-cp312-win_amd64.whl", hash = "sha256:ec89ed91f2fa8e3f52ae53cd3cf640d6feff92ba90d62236a81e4e563ac0e991"}, + {file = "wrapt-1.17.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6ed6ffac43aecfe6d86ec5b74b06a5be33d5bb9243d055141e8cabb12aa08125"}, + {file = "wrapt-1.17.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:35621ae4c00e056adb0009f8e86e28eb4a41a4bfa8f9bfa9fca7d343fe94f998"}, + {file = "wrapt-1.17.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a604bf7a053f8362d27eb9fefd2097f82600b856d5abe996d623babd067b1ab5"}, + {file = "wrapt-1.17.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cbabee4f083b6b4cd282f5b817a867cf0b1028c54d445b7ec7cfe6505057cf8"}, + {file = "wrapt-1.17.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49703ce2ddc220df165bd2962f8e03b84c89fee2d65e1c24a7defff6f988f4d6"}, + {file = "wrapt-1.17.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8112e52c5822fc4253f3901b676c55ddf288614dc7011634e2719718eaa187dc"}, + {file = "wrapt-1.17.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9fee687dce376205d9a494e9c121e27183b2a3df18037f89d69bd7b35bcf59e2"}, + {file = "wrapt-1.17.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:18983c537e04d11cf027fbb60a1e8dfd5190e2b60cc27bc0808e653e7b218d1b"}, + {file = "wrapt-1.17.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:703919b1633412ab54bcf920ab388735832fdcb9f9a00ae49387f0fe67dad504"}, + {file = "wrapt-1.17.2-cp313-cp313-win32.whl", hash = "sha256:abbb9e76177c35d4e8568e58650aa6926040d6a9f6f03435b7a522bf1c487f9a"}, + {file = "wrapt-1.17.2-cp313-cp313-win_amd64.whl", hash = "sha256:69606d7bb691b50a4240ce6b22ebb319c1cfb164e5f6569835058196e0f3a845"}, + {file = "wrapt-1.17.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:4a721d3c943dae44f8e243b380cb645a709ba5bd35d3ad27bc2ed947e9c68192"}, + {file = "wrapt-1.17.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:766d8bbefcb9e00c3ac3b000d9acc51f1b399513f44d77dfe0eb026ad7c9a19b"}, + {file = "wrapt-1.17.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e496a8ce2c256da1eb98bd15803a79bee00fc351f5dfb9ea82594a3f058309e0"}, + {file = "wrapt-1.17.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d615e4fe22f4ad3528448c193b218e077656ca9ccb22ce2cb20db730f8d306"}, + {file = "wrapt-1.17.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a5aaeff38654462bc4b09023918b7f21790efb807f54c000a39d41d69cf552cb"}, + {file = "wrapt-1.17.2-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a7d15bbd2bc99e92e39f49a04653062ee6085c0e18b3b7512a4f2fe91f2d681"}, + {file = "wrapt-1.17.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e3890b508a23299083e065f435a492b5435eba6e304a7114d2f919d400888cc6"}, + {file = "wrapt-1.17.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:8c8b293cd65ad716d13d8dd3624e42e5a19cc2a2f1acc74b30c2c13f15cb61a6"}, + {file = "wrapt-1.17.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c82b8785d98cdd9fed4cac84d765d234ed3251bd6afe34cb7ac523cb93e8b4f"}, + {file = "wrapt-1.17.2-cp313-cp313t-win32.whl", hash = "sha256:13e6afb7fe71fe7485a4550a8844cc9ffbe263c0f1a1eea569bc7091d4898555"}, + {file = "wrapt-1.17.2-cp313-cp313t-win_amd64.whl", hash = "sha256:eaf675418ed6b3b31c7a989fd007fa7c3be66ce14e5c3b27336383604c9da85c"}, + {file = "wrapt-1.17.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5c803c401ea1c1c18de70a06a6f79fcc9c5acfc79133e9869e730ad7f8ad8ef9"}, + {file = "wrapt-1.17.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f917c1180fdb8623c2b75a99192f4025e412597c50b2ac870f156de8fb101119"}, + {file = "wrapt-1.17.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ecc840861360ba9d176d413a5489b9a0aff6d6303d7e733e2c4623cfa26904a6"}, + {file = "wrapt-1.17.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb87745b2e6dc56361bfde481d5a378dc314b252a98d7dd19a651a3fa58f24a9"}, + {file = "wrapt-1.17.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58455b79ec2661c3600e65c0a716955adc2410f7383755d537584b0de41b1d8a"}, + {file = "wrapt-1.17.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4e42a40a5e164cbfdb7b386c966a588b1047558a990981ace551ed7e12ca9c2"}, + {file = "wrapt-1.17.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:91bd7d1773e64019f9288b7a5101f3ae50d3d8e6b1de7edee9c2ccc1d32f0c0a"}, + {file = "wrapt-1.17.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:bb90fb8bda722a1b9d48ac1e6c38f923ea757b3baf8ebd0c82e09c5c1a0e7a04"}, + {file = "wrapt-1.17.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:08e7ce672e35efa54c5024936e559469436f8b8096253404faeb54d2a878416f"}, + {file = "wrapt-1.17.2-cp38-cp38-win32.whl", hash = "sha256:410a92fefd2e0e10d26210e1dfb4a876ddaf8439ef60d6434f21ef8d87efc5b7"}, + {file = "wrapt-1.17.2-cp38-cp38-win_amd64.whl", hash = "sha256:95c658736ec15602da0ed73f312d410117723914a5c91a14ee4cdd72f1d790b3"}, + {file = "wrapt-1.17.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:99039fa9e6306880572915728d7f6c24a86ec57b0a83f6b2491e1d8ab0235b9a"}, + {file = "wrapt-1.17.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2696993ee1eebd20b8e4ee4356483c4cb696066ddc24bd70bcbb80fa56ff9061"}, + {file = "wrapt-1.17.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:612dff5db80beef9e649c6d803a8d50c409082f1fedc9dbcdfde2983b2025b82"}, + {file = "wrapt-1.17.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62c2caa1585c82b3f7a7ab56afef7b3602021d6da34fbc1cf234ff139fed3cd9"}, + {file = "wrapt-1.17.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c958bcfd59bacc2d0249dcfe575e71da54f9dcf4a8bdf89c4cb9a68a1170d73f"}, + {file = "wrapt-1.17.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc78a84e2dfbc27afe4b2bd7c80c8db9bca75cc5b85df52bfe634596a1da846b"}, + {file = "wrapt-1.17.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ba0f0eb61ef00ea10e00eb53a9129501f52385c44853dbd6c4ad3f403603083f"}, + {file = "wrapt-1.17.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1e1fe0e6ab7775fd842bc39e86f6dcfc4507ab0ffe206093e76d61cde37225c8"}, + {file = "wrapt-1.17.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c86563182421896d73858e08e1db93afdd2b947a70064b813d515d66549e15f9"}, + {file = "wrapt-1.17.2-cp39-cp39-win32.whl", hash = "sha256:f393cda562f79828f38a819f4788641ac7c4085f30f1ce1a68672baa686482bb"}, + {file = "wrapt-1.17.2-cp39-cp39-win_amd64.whl", hash = "sha256:36ccae62f64235cf8ddb682073a60519426fdd4725524ae38874adf72b5f2aeb"}, + {file = "wrapt-1.17.2-py3-none-any.whl", hash = "sha256:b18f2d1533a71f069c7f82d524a52599053d4c7166e9dd374ae2136b7f40f7c8"}, + {file = "wrapt-1.17.2.tar.gz", hash = "sha256:41388e9d4d1522446fe79d3213196bd9e3b301a336965b9e27ca2788ebd122f3"}, ] [[package]] @@ -3305,6 +3861,7 @@ version = "1.2.0" description = "WebSockets state-machine based protocol implementation" optional = false python-versions = ">=3.7.0" +groups = ["dev"] files = [ {file = "wsproto-1.2.0-py3-none-any.whl", hash = "sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736"}, {file = "wsproto-1.2.0.tar.gz", hash = "sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065"}, @@ -3319,6 +3876,7 @@ version = "0.2.3.post1" description = "Zopfli module for python" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "zopfli-0.2.3.post1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e0137dd64a493ba6a4be37405cfd6febe650a98cc1e9dca8f6b8c63b1db11b41"}, {file = "zopfli-0.2.3.post1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:aa588b21044f8a74e423d8c8a4c7fc9988501878aacced793467010039c50734"}, @@ -3397,6 +3955,6 @@ files = [ test = ["pytest"] [metadata] -lock-version = "2.0" +lock-version = "2.1" python-versions = "^3.13" -content-hash = "bb61e9f001eeff26863f043eb3ff0d782cb7a9e43d0070ec7a0013812c096b6f" +content-hash = "c0015072888a653ee5cec21f6bbfa332064a435728d1e7ad3a02c2b229dfee26" diff --git a/pyproject.toml b/pyproject.toml index ace530cdc..82dfeddd9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,6 +35,7 @@ djangorestframework = "^3.15.2" django-vite = "^3.0.5" django-cors-headers = "^4.6.0" parameterized = "^0.9.0" +distinctipy = {extras = ["extras"], version = "^1.3.4"} [tool.poetry.group.dev.dependencies] black = "^24.10.0" diff --git a/schema.yml b/schema.yml index bceb2818c..995bfd79d 100644 --- a/schema.yml +++ b/schema.yml @@ -3,22 +3,10 @@ info: title: '' version: 0.0.0 paths: - /statistics/number_of_abcd_members_at_date: + /statistics/available_colourblindness_types: get: - operationId: statistics_number_of_abcd_members_at_date_retrieve + operationId: statistics_available_colourblindness_types_retrieve description: Verify that the current user is authenticated. - parameters: - - in: query - name: at_date - schema: - type: string - format: date - required: true - - in: query - name: relative - schema: - type: boolean - required: true tags: - statistics security: @@ -28,23 +16,19 @@ paths: content: application/json: schema: - type: integer + type: array + items: + type: string description: '' - /statistics/number_of_active_members_at_date: + /statistics/available_datasets: get: - operationId: statistics_number_of_active_members_at_date_retrieve + operationId: statistics_available_datasets_list description: Verify that the current user is authenticated. parameters: - in: query - name: at_date + name: colourblindness schema: type: string - format: date - required: true - - in: query - name: relative - schema: - type: boolean required: true tags: - statistics @@ -55,24 +39,14 @@ paths: content: application/json: schema: - type: integer + type: array + items: + $ref: '#/components/schemas/Dataset' description: '' - /statistics/number_of_active_members_with_account_at_date: + /statistics/available_export_columns: get: - operationId: statistics_number_of_active_members_with_account_at_date_retrieve + operationId: statistics_available_export_columns_list description: Verify that the current user is authenticated. - parameters: - - in: query - name: at_date - schema: - type: string - format: date - required: true - - in: query - name: relative - schema: - type: boolean - required: true tags: - statistics security: @@ -82,11 +56,13 @@ paths: content: application/json: schema: - type: integer + type: array + items: + $ref: '#/components/schemas/Column' description: '' - /statistics/number_of_co_purchasers_at_date: + /statistics/export_dataset: get: - operationId: statistics_number_of_co_purchasers_at_date_retrieve + operationId: statistics_export_dataset_list description: Verify that the current user is authenticated. parameters: - in: query @@ -96,36 +72,16 @@ paths: format: date required: true - in: query - name: relative - schema: - type: boolean - required: true - tags: - - statistics - security: - - cookieAuth: [] - responses: - '200': - content: - application/json: - schema: - type: integer - description: '' - /statistics/number_of_created_resignations_in_same_month: - get: - operationId: statistics_number_of_created_resignations_in_same_month_retrieve - description: Verify that the current user is authenticated. - parameters: - - in: query - name: at_date + name: dataset schema: type: string - format: date required: true - in: query - name: relative + name: export_columns schema: - type: boolean + type: array + items: + type: string required: true tags: - statistics @@ -136,11 +92,13 @@ paths: content: application/json: schema: - type: integer + type: array + items: + $ref: '#/components/schemas/DatapointExport' description: '' - /statistics/number_of_exempted_members_at_date: + /statistics/graph_point: get: - operationId: statistics_number_of_exempted_members_at_date_retrieve + operationId: statistics_graph_point_retrieve description: Verify that the current user is authenticated. parameters: - in: query @@ -150,31 +108,9 @@ paths: format: date required: true - in: query - name: relative - schema: - type: boolean - required: true - tags: - - statistics - security: - - cookieAuth: [] - responses: - '200': - content: - application/json: - schema: - type: integer - description: '' - /statistics/number_of_exempted_members_that_work: - get: - operationId: statistics_number_of_exempted_members_that_work_retrieve - description: Verify that the current user is authenticated. - parameters: - - in: query - name: at_date + name: dataset schema: type: string - format: date required: true - in: query name: relative @@ -192,24 +128,18 @@ paths: schema: type: integer description: '' - /statistics/number_of_flying_members_at_date: + /welcomedesk/api/search: get: - operationId: statistics_number_of_flying_members_at_date_retrieve + operationId: welcomedesk_api_search_list description: Verify that the current user is authenticated. parameters: - in: query - name: at_date + name: search_input schema: type: string - format: date - required: true - - in: query - name: relative - schema: - type: boolean required: true tags: - - statistics + - welcomedesk security: - cookieAuth: [] responses: @@ -217,276 +147,101 @@ paths: content: application/json: schema: - type: integer + type: array + items: + $ref: '#/components/schemas/ShareOwnerForWelcomeDesk' description: '' - /statistics/number_of_frozen_members_at_date: - get: - operationId: statistics_number_of_frozen_members_at_date_retrieve - description: Verify that the current user is authenticated. - parameters: - - in: query - name: at_date - schema: +components: + schemas: + Column: + type: object + properties: + column_name: type: string - format: date - required: true - - in: query - name: relative - schema: - type: boolean - required: true - tags: - - statistics - security: - - cookieAuth: [] - responses: - '200': - content: - application/json: - schema: - type: integer - description: '' - /statistics/number_of_investing_members_at_date: - get: - operationId: statistics_number_of_investing_members_at_date_retrieve - description: Verify that the current user is authenticated. - parameters: - - in: query - name: at_date - schema: + required: + - column_name + DatapointExport: + type: object + properties: + member_number: + type: integer + display_name: type: string - format: date - required: true - - in: query - name: relative - schema: + is_company: type: boolean - required: true - tags: - - statistics - security: - - cookieAuth: [] - responses: - '200': - content: - application/json: - schema: - type: integer - description: '' - /statistics/number_of_long_term_frozen_members_at_date: - get: - operationId: statistics_number_of_long_term_frozen_members_at_date_retrieve - description: Verify that the current user is authenticated. - parameters: - - in: query - name: at_date - schema: + company_name: type: string - format: date - required: true - - in: query - name: relative - schema: - type: boolean - required: true - tags: - - statistics - security: - - cookieAuth: [] - responses: - '200': - content: - application/json: - schema: - type: integer - description: '' - /statistics/number_of_members_at_date: - get: - operationId: statistics_number_of_members_at_date_retrieve - description: Verify that the current user is authenticated. - parameters: - - in: query - name: at_date - schema: + first_name: type: string - format: date - required: true - - in: query - name: relative - schema: - type: boolean - required: true - tags: - - statistics - security: - - cookieAuth: [] - responses: - '200': - content: - application/json: - schema: - type: integer - description: '' - /statistics/number_of_paused_members_at_date: - get: - operationId: statistics_number_of_paused_members_at_date_retrieve - description: Verify that the current user is authenticated. - parameters: - - in: query - name: at_date - schema: + last_name: type: string - format: date - required: true - - in: query - name: relative - schema: - type: boolean - required: true - tags: - - statistics - security: - - cookieAuth: [] - responses: - '200': - content: - application/json: - schema: - type: integer - description: '' - /statistics/number_of_pending_resignations_at_date: - get: - operationId: statistics_number_of_pending_resignations_at_date_retrieve - description: Verify that the current user is authenticated. - parameters: - - in: query - name: at_date - schema: + usage_name: type: string - format: date - required: true - - in: query - name: relative - schema: - type: boolean - required: true - tags: - - statistics - security: - - cookieAuth: [] - responses: - '200': - content: - application/json: - schema: - type: integer - description: '' - /statistics/number_of_purchasing_members_at_date: - get: - operationId: statistics_number_of_purchasing_members_at_date_retrieve - description: Verify that the current user is authenticated. - parameters: - - in: query - name: at_date - schema: + pronouns: type: string - format: date - required: true - - in: query - name: relative - schema: + email: + type: string + phone_number: + type: string + birthdate: + type: string + street: + type: string + street_2: + type: string + postcode: + type: string + city: + type: string + country: + type: string + preferred_language: + type: string + is_investing: type: boolean - required: true - tags: - - statistics - security: - - cookieAuth: [] - responses: - '200': - content: - application/json: - schema: - type: integer - description: '' - /statistics/number_of_shift_partners_at_date: - get: - operationId: statistics_number_of_shift_partners_at_date_retrieve - description: Verify that the current user is authenticated. - parameters: - - in: query - name: at_date - schema: + ratenzahlung: + type: boolean + attended_welcome_session: + type: boolean + co_purchaser: type: string - format: date - required: true - - in: query - name: relative - schema: + allows_purchase_tracking: type: boolean - required: true - tags: - - statistics - security: - - cookieAuth: [] - responses: - '200': - content: - application/json: - schema: - type: integer - description: '' - /statistics/number_of_working_members_at_date: - get: - operationId: statistics_number_of_working_members_at_date_retrieve - description: Verify that the current user is authenticated. - parameters: - - in: query - name: at_date - schema: + shift_capabilities: + type: array + items: + type: string + shift_partner: + type: integer + shift_status: type: string - format: date - required: true - - in: query - name: relative - schema: + is_working: type: boolean - required: true - tags: - - statistics - security: - - cookieAuth: [] - responses: - '200': - content: - application/json: - schema: - type: integer - description: '' - /welcomedesk/api/search: - get: - operationId: welcomedesk_api_search_list - description: Verify that the current user is authenticated. - parameters: - - in: query - name: search_input - schema: + is_exempted: + type: boolean + is_paused: + type: boolean + can_shop: + type: boolean + Dataset: + type: object + properties: + id: type: string - required: true - tags: - - welcomedesk - security: - - cookieAuth: [] - responses: - '200': - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/ShareOwnerForWelcomeDesk' - description: '' -components: - schemas: + display_name: + type: string + description: + type: string + color: + type: string + point_style: + type: string + required: + - color + - description + - display_name + - id + - point_style ShareOwnerForWelcomeDesk: type: object properties: diff --git a/scripts/update_translation_files.sh b/scripts/update_translation_files.sh index 005b49ec9..ac764c7b9 100755 --- a/scripts/update_translation_files.sh +++ b/scripts/update_translation_files.sh @@ -4,5 +4,5 @@ docker compose run --rm -w /app/tapir web poetry run python ../manage.py makemessages --no-wrap -l de # translations from Javascript -docker compose exec vite npm run build +docker compose run --rm vite npm run build docker compose run --rm -w /app web poetry run python manage.py makemessages --no-wrap -l de -d djangojs \ No newline at end of file diff --git a/src/api-client/.openapi-generator/FILES b/src/api-client/.openapi-generator/FILES index de9368117..dd58759b9 100644 --- a/src/api-client/.openapi-generator/FILES +++ b/src/api-client/.openapi-generator/FILES @@ -2,6 +2,9 @@ apis/StatisticsApi.ts apis/WelcomedeskApi.ts apis/index.ts index.ts +models/Column.ts +models/DatapointExport.ts +models/Dataset.ts models/ShareOwnerForWelcomeDesk.ts models/index.ts runtime.ts diff --git a/src/api-client/apis/StatisticsApi.ts b/src/api-client/apis/StatisticsApi.ts index c9d214b4d..f8ef72ea0 100644 --- a/src/api-client/apis/StatisticsApi.ts +++ b/src/api-client/apis/StatisticsApi.ts @@ -14,89 +14,33 @@ import * as runtime from '../runtime'; - -export interface StatisticsNumberOfAbcdMembersAtDateRetrieveRequest { - atDate: Date; - relative: boolean; -} - -export interface StatisticsNumberOfActiveMembersAtDateRetrieveRequest { - atDate: Date; - relative: boolean; -} - -export interface StatisticsNumberOfActiveMembersWithAccountAtDateRetrieveRequest { - atDate: Date; - relative: boolean; -} - -export interface StatisticsNumberOfCoPurchasersAtDateRetrieveRequest { - atDate: Date; - relative: boolean; -} - -export interface StatisticsNumberOfCreatedResignationsInSameMonthRetrieveRequest { - atDate: Date; - relative: boolean; -} - -export interface StatisticsNumberOfExemptedMembersAtDateRetrieveRequest { - atDate: Date; - relative: boolean; -} - -export interface StatisticsNumberOfExemptedMembersThatWorkRetrieveRequest { - atDate: Date; - relative: boolean; -} - -export interface StatisticsNumberOfFlyingMembersAtDateRetrieveRequest { - atDate: Date; - relative: boolean; -} - -export interface StatisticsNumberOfFrozenMembersAtDateRetrieveRequest { - atDate: Date; - relative: boolean; -} - -export interface StatisticsNumberOfInvestingMembersAtDateRetrieveRequest { - atDate: Date; - relative: boolean; -} - -export interface StatisticsNumberOfLongTermFrozenMembersAtDateRetrieveRequest { - atDate: Date; - relative: boolean; -} - -export interface StatisticsNumberOfMembersAtDateRetrieveRequest { - atDate: Date; - relative: boolean; -} - -export interface StatisticsNumberOfPausedMembersAtDateRetrieveRequest { - atDate: Date; - relative: boolean; -} - -export interface StatisticsNumberOfPendingResignationsAtDateRetrieveRequest { - atDate: Date; - relative: boolean; -} - -export interface StatisticsNumberOfPurchasingMembersAtDateRetrieveRequest { - atDate: Date; - relative: boolean; +import type { + Column, + DatapointExport, + Dataset, +} from '../models/index'; +import { + ColumnFromJSON, + ColumnToJSON, + DatapointExportFromJSON, + DatapointExportToJSON, + DatasetFromJSON, + DatasetToJSON, +} from '../models/index'; + +export interface StatisticsAvailableDatasetsListRequest { + colourblindness: string; } -export interface StatisticsNumberOfShiftPartnersAtDateRetrieveRequest { +export interface StatisticsExportDatasetListRequest { atDate: Date; - relative: boolean; + dataset: string; + exportColumns: Array; } -export interface StatisticsNumberOfWorkingMembersAtDateRetrieveRequest { +export interface StatisticsGraphPointRetrieveRequest { atDate: Date; + dataset: string; relative: boolean; } @@ -108,226 +52,114 @@ export class StatisticsApi extends runtime.BaseAPI { /** * Verify that the current user is authenticated. */ - async statisticsNumberOfAbcdMembersAtDateRetrieveRaw(requestParameters: StatisticsNumberOfAbcdMembersAtDateRetrieveRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { - if (requestParameters['atDate'] == null) { - throw new runtime.RequiredError( - 'atDate', - 'Required parameter "atDate" was null or undefined when calling statisticsNumberOfAbcdMembersAtDateRetrieve().' - ); - } - - if (requestParameters['relative'] == null) { - throw new runtime.RequiredError( - 'relative', - 'Required parameter "relative" was null or undefined when calling statisticsNumberOfAbcdMembersAtDateRetrieve().' - ); - } - + async statisticsAvailableColourblindnessTypesRetrieveRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise>> { const queryParameters: any = {}; - if (requestParameters['atDate'] != null) { - queryParameters['at_date'] = (requestParameters['atDate'] as any).toISOString().substring(0,10); - } - - if (requestParameters['relative'] != null) { - queryParameters['relative'] = requestParameters['relative']; - } - const headerParameters: runtime.HTTPHeaders = {}; const response = await this.request({ - path: `/statistics/number_of_abcd_members_at_date`, + path: `/statistics/available_colourblindness_types`, method: 'GET', headers: headerParameters, query: queryParameters, }, initOverrides); - if (this.isJsonMime(response.headers.get('content-type'))) { - return new runtime.JSONApiResponse(response); - } else { - return new runtime.TextApiResponse(response) as any; - } + return new runtime.JSONApiResponse(response); } /** * Verify that the current user is authenticated. */ - async statisticsNumberOfAbcdMembersAtDateRetrieve(requestParameters: StatisticsNumberOfAbcdMembersAtDateRetrieveRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { - const response = await this.statisticsNumberOfAbcdMembersAtDateRetrieveRaw(requestParameters, initOverrides); + async statisticsAvailableColourblindnessTypesRetrieve(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { + const response = await this.statisticsAvailableColourblindnessTypesRetrieveRaw(initOverrides); return await response.value(); } /** * Verify that the current user is authenticated. */ - async statisticsNumberOfActiveMembersAtDateRetrieveRaw(requestParameters: StatisticsNumberOfActiveMembersAtDateRetrieveRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { - if (requestParameters['atDate'] == null) { - throw new runtime.RequiredError( - 'atDate', - 'Required parameter "atDate" was null or undefined when calling statisticsNumberOfActiveMembersAtDateRetrieve().' - ); - } - - if (requestParameters['relative'] == null) { + async statisticsAvailableDatasetsListRaw(requestParameters: StatisticsAvailableDatasetsListRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise>> { + if (requestParameters['colourblindness'] == null) { throw new runtime.RequiredError( - 'relative', - 'Required parameter "relative" was null or undefined when calling statisticsNumberOfActiveMembersAtDateRetrieve().' + 'colourblindness', + 'Required parameter "colourblindness" was null or undefined when calling statisticsAvailableDatasetsList().' ); } const queryParameters: any = {}; - if (requestParameters['atDate'] != null) { - queryParameters['at_date'] = (requestParameters['atDate'] as any).toISOString().substring(0,10); - } - - if (requestParameters['relative'] != null) { - queryParameters['relative'] = requestParameters['relative']; + if (requestParameters['colourblindness'] != null) { + queryParameters['colourblindness'] = requestParameters['colourblindness']; } const headerParameters: runtime.HTTPHeaders = {}; const response = await this.request({ - path: `/statistics/number_of_active_members_at_date`, + path: `/statistics/available_datasets`, method: 'GET', headers: headerParameters, query: queryParameters, }, initOverrides); - if (this.isJsonMime(response.headers.get('content-type'))) { - return new runtime.JSONApiResponse(response); - } else { - return new runtime.TextApiResponse(response) as any; - } + return new runtime.JSONApiResponse(response, (jsonValue) => jsonValue.map(DatasetFromJSON)); } /** * Verify that the current user is authenticated. */ - async statisticsNumberOfActiveMembersAtDateRetrieve(requestParameters: StatisticsNumberOfActiveMembersAtDateRetrieveRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { - const response = await this.statisticsNumberOfActiveMembersAtDateRetrieveRaw(requestParameters, initOverrides); + async statisticsAvailableDatasetsList(requestParameters: StatisticsAvailableDatasetsListRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { + const response = await this.statisticsAvailableDatasetsListRaw(requestParameters, initOverrides); return await response.value(); } /** * Verify that the current user is authenticated. */ - async statisticsNumberOfActiveMembersWithAccountAtDateRetrieveRaw(requestParameters: StatisticsNumberOfActiveMembersWithAccountAtDateRetrieveRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { - if (requestParameters['atDate'] == null) { - throw new runtime.RequiredError( - 'atDate', - 'Required parameter "atDate" was null or undefined when calling statisticsNumberOfActiveMembersWithAccountAtDateRetrieve().' - ); - } - - if (requestParameters['relative'] == null) { - throw new runtime.RequiredError( - 'relative', - 'Required parameter "relative" was null or undefined when calling statisticsNumberOfActiveMembersWithAccountAtDateRetrieve().' - ); - } - + async statisticsAvailableExportColumnsListRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise>> { const queryParameters: any = {}; - if (requestParameters['atDate'] != null) { - queryParameters['at_date'] = (requestParameters['atDate'] as any).toISOString().substring(0,10); - } - - if (requestParameters['relative'] != null) { - queryParameters['relative'] = requestParameters['relative']; - } - const headerParameters: runtime.HTTPHeaders = {}; const response = await this.request({ - path: `/statistics/number_of_active_members_with_account_at_date`, + path: `/statistics/available_export_columns`, method: 'GET', headers: headerParameters, query: queryParameters, }, initOverrides); - if (this.isJsonMime(response.headers.get('content-type'))) { - return new runtime.JSONApiResponse(response); - } else { - return new runtime.TextApiResponse(response) as any; - } + return new runtime.JSONApiResponse(response, (jsonValue) => jsonValue.map(ColumnFromJSON)); } /** * Verify that the current user is authenticated. */ - async statisticsNumberOfActiveMembersWithAccountAtDateRetrieve(requestParameters: StatisticsNumberOfActiveMembersWithAccountAtDateRetrieveRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { - const response = await this.statisticsNumberOfActiveMembersWithAccountAtDateRetrieveRaw(requestParameters, initOverrides); + async statisticsAvailableExportColumnsList(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { + const response = await this.statisticsAvailableExportColumnsListRaw(initOverrides); return await response.value(); } /** * Verify that the current user is authenticated. */ - async statisticsNumberOfCoPurchasersAtDateRetrieveRaw(requestParameters: StatisticsNumberOfCoPurchasersAtDateRetrieveRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { + async statisticsExportDatasetListRaw(requestParameters: StatisticsExportDatasetListRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise>> { if (requestParameters['atDate'] == null) { throw new runtime.RequiredError( 'atDate', - 'Required parameter "atDate" was null or undefined when calling statisticsNumberOfCoPurchasersAtDateRetrieve().' - ); - } - - if (requestParameters['relative'] == null) { - throw new runtime.RequiredError( - 'relative', - 'Required parameter "relative" was null or undefined when calling statisticsNumberOfCoPurchasersAtDateRetrieve().' + 'Required parameter "atDate" was null or undefined when calling statisticsExportDatasetList().' ); } - const queryParameters: any = {}; - - if (requestParameters['atDate'] != null) { - queryParameters['at_date'] = (requestParameters['atDate'] as any).toISOString().substring(0,10); - } - - if (requestParameters['relative'] != null) { - queryParameters['relative'] = requestParameters['relative']; - } - - const headerParameters: runtime.HTTPHeaders = {}; - - const response = await this.request({ - path: `/statistics/number_of_co_purchasers_at_date`, - method: 'GET', - headers: headerParameters, - query: queryParameters, - }, initOverrides); - - if (this.isJsonMime(response.headers.get('content-type'))) { - return new runtime.JSONApiResponse(response); - } else { - return new runtime.TextApiResponse(response) as any; - } - } - - /** - * Verify that the current user is authenticated. - */ - async statisticsNumberOfCoPurchasersAtDateRetrieve(requestParameters: StatisticsNumberOfCoPurchasersAtDateRetrieveRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { - const response = await this.statisticsNumberOfCoPurchasersAtDateRetrieveRaw(requestParameters, initOverrides); - return await response.value(); - } - - /** - * Verify that the current user is authenticated. - */ - async statisticsNumberOfCreatedResignationsInSameMonthRetrieveRaw(requestParameters: StatisticsNumberOfCreatedResignationsInSameMonthRetrieveRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { - if (requestParameters['atDate'] == null) { + if (requestParameters['dataset'] == null) { throw new runtime.RequiredError( - 'atDate', - 'Required parameter "atDate" was null or undefined when calling statisticsNumberOfCreatedResignationsInSameMonthRetrieve().' + 'dataset', + 'Required parameter "dataset" was null or undefined when calling statisticsExportDatasetList().' ); } - if (requestParameters['relative'] == null) { + if (requestParameters['exportColumns'] == null) { throw new runtime.RequiredError( - 'relative', - 'Required parameter "relative" was null or undefined when calling statisticsNumberOfCreatedResignationsInSameMonthRetrieve().' + 'exportColumns', + 'Required parameter "exportColumns" was null or undefined when calling statisticsExportDatasetList().' ); } @@ -337,101 +169,56 @@ export class StatisticsApi extends runtime.BaseAPI { queryParameters['at_date'] = (requestParameters['atDate'] as any).toISOString().substring(0,10); } - if (requestParameters['relative'] != null) { - queryParameters['relative'] = requestParameters['relative']; + if (requestParameters['dataset'] != null) { + queryParameters['dataset'] = requestParameters['dataset']; + } + + if (requestParameters['exportColumns'] != null) { + queryParameters['export_columns'] = requestParameters['exportColumns']; } const headerParameters: runtime.HTTPHeaders = {}; const response = await this.request({ - path: `/statistics/number_of_created_resignations_in_same_month`, + path: `/statistics/export_dataset`, method: 'GET', headers: headerParameters, query: queryParameters, }, initOverrides); - if (this.isJsonMime(response.headers.get('content-type'))) { - return new runtime.JSONApiResponse(response); - } else { - return new runtime.TextApiResponse(response) as any; - } + return new runtime.JSONApiResponse(response, (jsonValue) => jsonValue.map(DatapointExportFromJSON)); } /** * Verify that the current user is authenticated. */ - async statisticsNumberOfCreatedResignationsInSameMonthRetrieve(requestParameters: StatisticsNumberOfCreatedResignationsInSameMonthRetrieveRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { - const response = await this.statisticsNumberOfCreatedResignationsInSameMonthRetrieveRaw(requestParameters, initOverrides); + async statisticsExportDatasetList(requestParameters: StatisticsExportDatasetListRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { + const response = await this.statisticsExportDatasetListRaw(requestParameters, initOverrides); return await response.value(); } /** * Verify that the current user is authenticated. */ - async statisticsNumberOfExemptedMembersAtDateRetrieveRaw(requestParameters: StatisticsNumberOfExemptedMembersAtDateRetrieveRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { + async statisticsGraphPointRetrieveRaw(requestParameters: StatisticsGraphPointRetrieveRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { if (requestParameters['atDate'] == null) { throw new runtime.RequiredError( 'atDate', - 'Required parameter "atDate" was null or undefined when calling statisticsNumberOfExemptedMembersAtDateRetrieve().' - ); - } - - if (requestParameters['relative'] == null) { - throw new runtime.RequiredError( - 'relative', - 'Required parameter "relative" was null or undefined when calling statisticsNumberOfExemptedMembersAtDateRetrieve().' + 'Required parameter "atDate" was null or undefined when calling statisticsGraphPointRetrieve().' ); } - const queryParameters: any = {}; - - if (requestParameters['atDate'] != null) { - queryParameters['at_date'] = (requestParameters['atDate'] as any).toISOString().substring(0,10); - } - - if (requestParameters['relative'] != null) { - queryParameters['relative'] = requestParameters['relative']; - } - - const headerParameters: runtime.HTTPHeaders = {}; - - const response = await this.request({ - path: `/statistics/number_of_exempted_members_at_date`, - method: 'GET', - headers: headerParameters, - query: queryParameters, - }, initOverrides); - - if (this.isJsonMime(response.headers.get('content-type'))) { - return new runtime.JSONApiResponse(response); - } else { - return new runtime.TextApiResponse(response) as any; - } - } - - /** - * Verify that the current user is authenticated. - */ - async statisticsNumberOfExemptedMembersAtDateRetrieve(requestParameters: StatisticsNumberOfExemptedMembersAtDateRetrieveRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { - const response = await this.statisticsNumberOfExemptedMembersAtDateRetrieveRaw(requestParameters, initOverrides); - return await response.value(); - } - - /** - * Verify that the current user is authenticated. - */ - async statisticsNumberOfExemptedMembersThatWorkRetrieveRaw(requestParameters: StatisticsNumberOfExemptedMembersThatWorkRetrieveRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { - if (requestParameters['atDate'] == null) { + if (requestParameters['dataset'] == null) { throw new runtime.RequiredError( - 'atDate', - 'Required parameter "atDate" was null or undefined when calling statisticsNumberOfExemptedMembersThatWorkRetrieve().' + 'dataset', + 'Required parameter "dataset" was null or undefined when calling statisticsGraphPointRetrieve().' ); } if (requestParameters['relative'] == null) { throw new runtime.RequiredError( 'relative', - 'Required parameter "relative" was null or undefined when calling statisticsNumberOfExemptedMembersThatWorkRetrieve().' + 'Required parameter "relative" was null or undefined when calling statisticsGraphPointRetrieve().' ); } @@ -441,524 +228,8 @@ export class StatisticsApi extends runtime.BaseAPI { queryParameters['at_date'] = (requestParameters['atDate'] as any).toISOString().substring(0,10); } - if (requestParameters['relative'] != null) { - queryParameters['relative'] = requestParameters['relative']; - } - - const headerParameters: runtime.HTTPHeaders = {}; - - const response = await this.request({ - path: `/statistics/number_of_exempted_members_that_work`, - method: 'GET', - headers: headerParameters, - query: queryParameters, - }, initOverrides); - - if (this.isJsonMime(response.headers.get('content-type'))) { - return new runtime.JSONApiResponse(response); - } else { - return new runtime.TextApiResponse(response) as any; - } - } - - /** - * Verify that the current user is authenticated. - */ - async statisticsNumberOfExemptedMembersThatWorkRetrieve(requestParameters: StatisticsNumberOfExemptedMembersThatWorkRetrieveRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { - const response = await this.statisticsNumberOfExemptedMembersThatWorkRetrieveRaw(requestParameters, initOverrides); - return await response.value(); - } - - /** - * Verify that the current user is authenticated. - */ - async statisticsNumberOfFlyingMembersAtDateRetrieveRaw(requestParameters: StatisticsNumberOfFlyingMembersAtDateRetrieveRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { - if (requestParameters['atDate'] == null) { - throw new runtime.RequiredError( - 'atDate', - 'Required parameter "atDate" was null or undefined when calling statisticsNumberOfFlyingMembersAtDateRetrieve().' - ); - } - - if (requestParameters['relative'] == null) { - throw new runtime.RequiredError( - 'relative', - 'Required parameter "relative" was null or undefined when calling statisticsNumberOfFlyingMembersAtDateRetrieve().' - ); - } - - const queryParameters: any = {}; - - if (requestParameters['atDate'] != null) { - queryParameters['at_date'] = (requestParameters['atDate'] as any).toISOString().substring(0,10); - } - - if (requestParameters['relative'] != null) { - queryParameters['relative'] = requestParameters['relative']; - } - - const headerParameters: runtime.HTTPHeaders = {}; - - const response = await this.request({ - path: `/statistics/number_of_flying_members_at_date`, - method: 'GET', - headers: headerParameters, - query: queryParameters, - }, initOverrides); - - if (this.isJsonMime(response.headers.get('content-type'))) { - return new runtime.JSONApiResponse(response); - } else { - return new runtime.TextApiResponse(response) as any; - } - } - - /** - * Verify that the current user is authenticated. - */ - async statisticsNumberOfFlyingMembersAtDateRetrieve(requestParameters: StatisticsNumberOfFlyingMembersAtDateRetrieveRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { - const response = await this.statisticsNumberOfFlyingMembersAtDateRetrieveRaw(requestParameters, initOverrides); - return await response.value(); - } - - /** - * Verify that the current user is authenticated. - */ - async statisticsNumberOfFrozenMembersAtDateRetrieveRaw(requestParameters: StatisticsNumberOfFrozenMembersAtDateRetrieveRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { - if (requestParameters['atDate'] == null) { - throw new runtime.RequiredError( - 'atDate', - 'Required parameter "atDate" was null or undefined when calling statisticsNumberOfFrozenMembersAtDateRetrieve().' - ); - } - - if (requestParameters['relative'] == null) { - throw new runtime.RequiredError( - 'relative', - 'Required parameter "relative" was null or undefined when calling statisticsNumberOfFrozenMembersAtDateRetrieve().' - ); - } - - const queryParameters: any = {}; - - if (requestParameters['atDate'] != null) { - queryParameters['at_date'] = (requestParameters['atDate'] as any).toISOString().substring(0,10); - } - - if (requestParameters['relative'] != null) { - queryParameters['relative'] = requestParameters['relative']; - } - - const headerParameters: runtime.HTTPHeaders = {}; - - const response = await this.request({ - path: `/statistics/number_of_frozen_members_at_date`, - method: 'GET', - headers: headerParameters, - query: queryParameters, - }, initOverrides); - - if (this.isJsonMime(response.headers.get('content-type'))) { - return new runtime.JSONApiResponse(response); - } else { - return new runtime.TextApiResponse(response) as any; - } - } - - /** - * Verify that the current user is authenticated. - */ - async statisticsNumberOfFrozenMembersAtDateRetrieve(requestParameters: StatisticsNumberOfFrozenMembersAtDateRetrieveRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { - const response = await this.statisticsNumberOfFrozenMembersAtDateRetrieveRaw(requestParameters, initOverrides); - return await response.value(); - } - - /** - * Verify that the current user is authenticated. - */ - async statisticsNumberOfInvestingMembersAtDateRetrieveRaw(requestParameters: StatisticsNumberOfInvestingMembersAtDateRetrieveRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { - if (requestParameters['atDate'] == null) { - throw new runtime.RequiredError( - 'atDate', - 'Required parameter "atDate" was null or undefined when calling statisticsNumberOfInvestingMembersAtDateRetrieve().' - ); - } - - if (requestParameters['relative'] == null) { - throw new runtime.RequiredError( - 'relative', - 'Required parameter "relative" was null or undefined when calling statisticsNumberOfInvestingMembersAtDateRetrieve().' - ); - } - - const queryParameters: any = {}; - - if (requestParameters['atDate'] != null) { - queryParameters['at_date'] = (requestParameters['atDate'] as any).toISOString().substring(0,10); - } - - if (requestParameters['relative'] != null) { - queryParameters['relative'] = requestParameters['relative']; - } - - const headerParameters: runtime.HTTPHeaders = {}; - - const response = await this.request({ - path: `/statistics/number_of_investing_members_at_date`, - method: 'GET', - headers: headerParameters, - query: queryParameters, - }, initOverrides); - - if (this.isJsonMime(response.headers.get('content-type'))) { - return new runtime.JSONApiResponse(response); - } else { - return new runtime.TextApiResponse(response) as any; - } - } - - /** - * Verify that the current user is authenticated. - */ - async statisticsNumberOfInvestingMembersAtDateRetrieve(requestParameters: StatisticsNumberOfInvestingMembersAtDateRetrieveRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { - const response = await this.statisticsNumberOfInvestingMembersAtDateRetrieveRaw(requestParameters, initOverrides); - return await response.value(); - } - - /** - * Verify that the current user is authenticated. - */ - async statisticsNumberOfLongTermFrozenMembersAtDateRetrieveRaw(requestParameters: StatisticsNumberOfLongTermFrozenMembersAtDateRetrieveRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { - if (requestParameters['atDate'] == null) { - throw new runtime.RequiredError( - 'atDate', - 'Required parameter "atDate" was null or undefined when calling statisticsNumberOfLongTermFrozenMembersAtDateRetrieve().' - ); - } - - if (requestParameters['relative'] == null) { - throw new runtime.RequiredError( - 'relative', - 'Required parameter "relative" was null or undefined when calling statisticsNumberOfLongTermFrozenMembersAtDateRetrieve().' - ); - } - - const queryParameters: any = {}; - - if (requestParameters['atDate'] != null) { - queryParameters['at_date'] = (requestParameters['atDate'] as any).toISOString().substring(0,10); - } - - if (requestParameters['relative'] != null) { - queryParameters['relative'] = requestParameters['relative']; - } - - const headerParameters: runtime.HTTPHeaders = {}; - - const response = await this.request({ - path: `/statistics/number_of_long_term_frozen_members_at_date`, - method: 'GET', - headers: headerParameters, - query: queryParameters, - }, initOverrides); - - if (this.isJsonMime(response.headers.get('content-type'))) { - return new runtime.JSONApiResponse(response); - } else { - return new runtime.TextApiResponse(response) as any; - } - } - - /** - * Verify that the current user is authenticated. - */ - async statisticsNumberOfLongTermFrozenMembersAtDateRetrieve(requestParameters: StatisticsNumberOfLongTermFrozenMembersAtDateRetrieveRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { - const response = await this.statisticsNumberOfLongTermFrozenMembersAtDateRetrieveRaw(requestParameters, initOverrides); - return await response.value(); - } - - /** - * Verify that the current user is authenticated. - */ - async statisticsNumberOfMembersAtDateRetrieveRaw(requestParameters: StatisticsNumberOfMembersAtDateRetrieveRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { - if (requestParameters['atDate'] == null) { - throw new runtime.RequiredError( - 'atDate', - 'Required parameter "atDate" was null or undefined when calling statisticsNumberOfMembersAtDateRetrieve().' - ); - } - - if (requestParameters['relative'] == null) { - throw new runtime.RequiredError( - 'relative', - 'Required parameter "relative" was null or undefined when calling statisticsNumberOfMembersAtDateRetrieve().' - ); - } - - const queryParameters: any = {}; - - if (requestParameters['atDate'] != null) { - queryParameters['at_date'] = (requestParameters['atDate'] as any).toISOString().substring(0,10); - } - - if (requestParameters['relative'] != null) { - queryParameters['relative'] = requestParameters['relative']; - } - - const headerParameters: runtime.HTTPHeaders = {}; - - const response = await this.request({ - path: `/statistics/number_of_members_at_date`, - method: 'GET', - headers: headerParameters, - query: queryParameters, - }, initOverrides); - - if (this.isJsonMime(response.headers.get('content-type'))) { - return new runtime.JSONApiResponse(response); - } else { - return new runtime.TextApiResponse(response) as any; - } - } - - /** - * Verify that the current user is authenticated. - */ - async statisticsNumberOfMembersAtDateRetrieve(requestParameters: StatisticsNumberOfMembersAtDateRetrieveRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { - const response = await this.statisticsNumberOfMembersAtDateRetrieveRaw(requestParameters, initOverrides); - return await response.value(); - } - - /** - * Verify that the current user is authenticated. - */ - async statisticsNumberOfPausedMembersAtDateRetrieveRaw(requestParameters: StatisticsNumberOfPausedMembersAtDateRetrieveRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { - if (requestParameters['atDate'] == null) { - throw new runtime.RequiredError( - 'atDate', - 'Required parameter "atDate" was null or undefined when calling statisticsNumberOfPausedMembersAtDateRetrieve().' - ); - } - - if (requestParameters['relative'] == null) { - throw new runtime.RequiredError( - 'relative', - 'Required parameter "relative" was null or undefined when calling statisticsNumberOfPausedMembersAtDateRetrieve().' - ); - } - - const queryParameters: any = {}; - - if (requestParameters['atDate'] != null) { - queryParameters['at_date'] = (requestParameters['atDate'] as any).toISOString().substring(0,10); - } - - if (requestParameters['relative'] != null) { - queryParameters['relative'] = requestParameters['relative']; - } - - const headerParameters: runtime.HTTPHeaders = {}; - - const response = await this.request({ - path: `/statistics/number_of_paused_members_at_date`, - method: 'GET', - headers: headerParameters, - query: queryParameters, - }, initOverrides); - - if (this.isJsonMime(response.headers.get('content-type'))) { - return new runtime.JSONApiResponse(response); - } else { - return new runtime.TextApiResponse(response) as any; - } - } - - /** - * Verify that the current user is authenticated. - */ - async statisticsNumberOfPausedMembersAtDateRetrieve(requestParameters: StatisticsNumberOfPausedMembersAtDateRetrieveRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { - const response = await this.statisticsNumberOfPausedMembersAtDateRetrieveRaw(requestParameters, initOverrides); - return await response.value(); - } - - /** - * Verify that the current user is authenticated. - */ - async statisticsNumberOfPendingResignationsAtDateRetrieveRaw(requestParameters: StatisticsNumberOfPendingResignationsAtDateRetrieveRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { - if (requestParameters['atDate'] == null) { - throw new runtime.RequiredError( - 'atDate', - 'Required parameter "atDate" was null or undefined when calling statisticsNumberOfPendingResignationsAtDateRetrieve().' - ); - } - - if (requestParameters['relative'] == null) { - throw new runtime.RequiredError( - 'relative', - 'Required parameter "relative" was null or undefined when calling statisticsNumberOfPendingResignationsAtDateRetrieve().' - ); - } - - const queryParameters: any = {}; - - if (requestParameters['atDate'] != null) { - queryParameters['at_date'] = (requestParameters['atDate'] as any).toISOString().substring(0,10); - } - - if (requestParameters['relative'] != null) { - queryParameters['relative'] = requestParameters['relative']; - } - - const headerParameters: runtime.HTTPHeaders = {}; - - const response = await this.request({ - path: `/statistics/number_of_pending_resignations_at_date`, - method: 'GET', - headers: headerParameters, - query: queryParameters, - }, initOverrides); - - if (this.isJsonMime(response.headers.get('content-type'))) { - return new runtime.JSONApiResponse(response); - } else { - return new runtime.TextApiResponse(response) as any; - } - } - - /** - * Verify that the current user is authenticated. - */ - async statisticsNumberOfPendingResignationsAtDateRetrieve(requestParameters: StatisticsNumberOfPendingResignationsAtDateRetrieveRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { - const response = await this.statisticsNumberOfPendingResignationsAtDateRetrieveRaw(requestParameters, initOverrides); - return await response.value(); - } - - /** - * Verify that the current user is authenticated. - */ - async statisticsNumberOfPurchasingMembersAtDateRetrieveRaw(requestParameters: StatisticsNumberOfPurchasingMembersAtDateRetrieveRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { - if (requestParameters['atDate'] == null) { - throw new runtime.RequiredError( - 'atDate', - 'Required parameter "atDate" was null or undefined when calling statisticsNumberOfPurchasingMembersAtDateRetrieve().' - ); - } - - if (requestParameters['relative'] == null) { - throw new runtime.RequiredError( - 'relative', - 'Required parameter "relative" was null or undefined when calling statisticsNumberOfPurchasingMembersAtDateRetrieve().' - ); - } - - const queryParameters: any = {}; - - if (requestParameters['atDate'] != null) { - queryParameters['at_date'] = (requestParameters['atDate'] as any).toISOString().substring(0,10); - } - - if (requestParameters['relative'] != null) { - queryParameters['relative'] = requestParameters['relative']; - } - - const headerParameters: runtime.HTTPHeaders = {}; - - const response = await this.request({ - path: `/statistics/number_of_purchasing_members_at_date`, - method: 'GET', - headers: headerParameters, - query: queryParameters, - }, initOverrides); - - if (this.isJsonMime(response.headers.get('content-type'))) { - return new runtime.JSONApiResponse(response); - } else { - return new runtime.TextApiResponse(response) as any; - } - } - - /** - * Verify that the current user is authenticated. - */ - async statisticsNumberOfPurchasingMembersAtDateRetrieve(requestParameters: StatisticsNumberOfPurchasingMembersAtDateRetrieveRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { - const response = await this.statisticsNumberOfPurchasingMembersAtDateRetrieveRaw(requestParameters, initOverrides); - return await response.value(); - } - - /** - * Verify that the current user is authenticated. - */ - async statisticsNumberOfShiftPartnersAtDateRetrieveRaw(requestParameters: StatisticsNumberOfShiftPartnersAtDateRetrieveRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { - if (requestParameters['atDate'] == null) { - throw new runtime.RequiredError( - 'atDate', - 'Required parameter "atDate" was null or undefined when calling statisticsNumberOfShiftPartnersAtDateRetrieve().' - ); - } - - if (requestParameters['relative'] == null) { - throw new runtime.RequiredError( - 'relative', - 'Required parameter "relative" was null or undefined when calling statisticsNumberOfShiftPartnersAtDateRetrieve().' - ); - } - - const queryParameters: any = {}; - - if (requestParameters['atDate'] != null) { - queryParameters['at_date'] = (requestParameters['atDate'] as any).toISOString().substring(0,10); - } - - if (requestParameters['relative'] != null) { - queryParameters['relative'] = requestParameters['relative']; - } - - const headerParameters: runtime.HTTPHeaders = {}; - - const response = await this.request({ - path: `/statistics/number_of_shift_partners_at_date`, - method: 'GET', - headers: headerParameters, - query: queryParameters, - }, initOverrides); - - if (this.isJsonMime(response.headers.get('content-type'))) { - return new runtime.JSONApiResponse(response); - } else { - return new runtime.TextApiResponse(response) as any; - } - } - - /** - * Verify that the current user is authenticated. - */ - async statisticsNumberOfShiftPartnersAtDateRetrieve(requestParameters: StatisticsNumberOfShiftPartnersAtDateRetrieveRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { - const response = await this.statisticsNumberOfShiftPartnersAtDateRetrieveRaw(requestParameters, initOverrides); - return await response.value(); - } - - /** - * Verify that the current user is authenticated. - */ - async statisticsNumberOfWorkingMembersAtDateRetrieveRaw(requestParameters: StatisticsNumberOfWorkingMembersAtDateRetrieveRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { - if (requestParameters['atDate'] == null) { - throw new runtime.RequiredError( - 'atDate', - 'Required parameter "atDate" was null or undefined when calling statisticsNumberOfWorkingMembersAtDateRetrieve().' - ); - } - - if (requestParameters['relative'] == null) { - throw new runtime.RequiredError( - 'relative', - 'Required parameter "relative" was null or undefined when calling statisticsNumberOfWorkingMembersAtDateRetrieve().' - ); - } - - const queryParameters: any = {}; - - if (requestParameters['atDate'] != null) { - queryParameters['at_date'] = (requestParameters['atDate'] as any).toISOString().substring(0,10); + if (requestParameters['dataset'] != null) { + queryParameters['dataset'] = requestParameters['dataset']; } if (requestParameters['relative'] != null) { @@ -968,7 +239,7 @@ export class StatisticsApi extends runtime.BaseAPI { const headerParameters: runtime.HTTPHeaders = {}; const response = await this.request({ - path: `/statistics/number_of_working_members_at_date`, + path: `/statistics/graph_point`, method: 'GET', headers: headerParameters, query: queryParameters, @@ -984,8 +255,8 @@ export class StatisticsApi extends runtime.BaseAPI { /** * Verify that the current user is authenticated. */ - async statisticsNumberOfWorkingMembersAtDateRetrieve(requestParameters: StatisticsNumberOfWorkingMembersAtDateRetrieveRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { - const response = await this.statisticsNumberOfWorkingMembersAtDateRetrieveRaw(requestParameters, initOverrides); + async statisticsGraphPointRetrieve(requestParameters: StatisticsGraphPointRetrieveRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { + const response = await this.statisticsGraphPointRetrieveRaw(requestParameters, initOverrides); return await response.value(); } diff --git a/src/api-client/models/Column.ts b/src/api-client/models/Column.ts new file mode 100644 index 000000000..df5759772 --- /dev/null +++ b/src/api-client/models/Column.ts @@ -0,0 +1,66 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 0.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { mapValues } from '../runtime'; +/** + * + * @export + * @interface Column + */ +export interface Column { + /** + * + * @type {string} + * @memberof Column + */ + columnName: string; +} + +/** + * Check if a given object implements the Column interface. + */ +export function instanceOfColumn(value: object): value is Column { + if (!('columnName' in value) || value['columnName'] === undefined) return false; + return true; +} + +export function ColumnFromJSON(json: any): Column { + return ColumnFromJSONTyped(json, false); +} + +export function ColumnFromJSONTyped(json: any, ignoreDiscriminator: boolean): Column { + if (json == null) { + return json; + } + return { + + 'columnName': json['column_name'], + }; +} + + export function ColumnToJSON(json: any): Column { + return ColumnToJSONTyped(json, false); + } + + export function ColumnToJSONTyped(value?: Column | null, ignoreDiscriminator: boolean = false): any { + if (value == null) { + return value; + } + + return { + + 'column_name': value['columnName'], + }; +} + diff --git a/src/api-client/models/DatapointExport.ts b/src/api-client/models/DatapointExport.ts new file mode 100644 index 000000000..b8df33ca6 --- /dev/null +++ b/src/api-client/models/DatapointExport.ts @@ -0,0 +1,289 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 0.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { mapValues } from '../runtime'; +/** + * + * @export + * @interface DatapointExport + */ +export interface DatapointExport { + /** + * + * @type {number} + * @memberof DatapointExport + */ + memberNumber?: number; + /** + * + * @type {string} + * @memberof DatapointExport + */ + displayName?: string; + /** + * + * @type {boolean} + * @memberof DatapointExport + */ + isCompany?: boolean; + /** + * + * @type {string} + * @memberof DatapointExport + */ + companyName?: string; + /** + * + * @type {string} + * @memberof DatapointExport + */ + firstName?: string; + /** + * + * @type {string} + * @memberof DatapointExport + */ + lastName?: string; + /** + * + * @type {string} + * @memberof DatapointExport + */ + usageName?: string; + /** + * + * @type {string} + * @memberof DatapointExport + */ + pronouns?: string; + /** + * + * @type {string} + * @memberof DatapointExport + */ + email?: string; + /** + * + * @type {string} + * @memberof DatapointExport + */ + phoneNumber?: string; + /** + * + * @type {string} + * @memberof DatapointExport + */ + birthdate?: string; + /** + * + * @type {string} + * @memberof DatapointExport + */ + street?: string; + /** + * + * @type {string} + * @memberof DatapointExport + */ + street2?: string; + /** + * + * @type {string} + * @memberof DatapointExport + */ + postcode?: string; + /** + * + * @type {string} + * @memberof DatapointExport + */ + city?: string; + /** + * + * @type {string} + * @memberof DatapointExport + */ + country?: string; + /** + * + * @type {string} + * @memberof DatapointExport + */ + preferredLanguage?: string; + /** + * + * @type {boolean} + * @memberof DatapointExport + */ + isInvesting?: boolean; + /** + * + * @type {boolean} + * @memberof DatapointExport + */ + ratenzahlung?: boolean; + /** + * + * @type {boolean} + * @memberof DatapointExport + */ + attendedWelcomeSession?: boolean; + /** + * + * @type {string} + * @memberof DatapointExport + */ + coPurchaser?: string; + /** + * + * @type {boolean} + * @memberof DatapointExport + */ + allowsPurchaseTracking?: boolean; + /** + * + * @type {Array} + * @memberof DatapointExport + */ + shiftCapabilities?: Array; + /** + * + * @type {number} + * @memberof DatapointExport + */ + shiftPartner?: number; + /** + * + * @type {string} + * @memberof DatapointExport + */ + shiftStatus?: string; + /** + * + * @type {boolean} + * @memberof DatapointExport + */ + isWorking?: boolean; + /** + * + * @type {boolean} + * @memberof DatapointExport + */ + isExempted?: boolean; + /** + * + * @type {boolean} + * @memberof DatapointExport + */ + isPaused?: boolean; + /** + * + * @type {boolean} + * @memberof DatapointExport + */ + canShop?: boolean; +} + +/** + * Check if a given object implements the DatapointExport interface. + */ +export function instanceOfDatapointExport(value: object): value is DatapointExport { + return true; +} + +export function DatapointExportFromJSON(json: any): DatapointExport { + return DatapointExportFromJSONTyped(json, false); +} + +export function DatapointExportFromJSONTyped(json: any, ignoreDiscriminator: boolean): DatapointExport { + if (json == null) { + return json; + } + return { + + 'memberNumber': json['member_number'] == null ? undefined : json['member_number'], + 'displayName': json['display_name'] == null ? undefined : json['display_name'], + 'isCompany': json['is_company'] == null ? undefined : json['is_company'], + 'companyName': json['company_name'] == null ? undefined : json['company_name'], + 'firstName': json['first_name'] == null ? undefined : json['first_name'], + 'lastName': json['last_name'] == null ? undefined : json['last_name'], + 'usageName': json['usage_name'] == null ? undefined : json['usage_name'], + 'pronouns': json['pronouns'] == null ? undefined : json['pronouns'], + 'email': json['email'] == null ? undefined : json['email'], + 'phoneNumber': json['phone_number'] == null ? undefined : json['phone_number'], + 'birthdate': json['birthdate'] == null ? undefined : json['birthdate'], + 'street': json['street'] == null ? undefined : json['street'], + 'street2': json['street_2'] == null ? undefined : json['street_2'], + 'postcode': json['postcode'] == null ? undefined : json['postcode'], + 'city': json['city'] == null ? undefined : json['city'], + 'country': json['country'] == null ? undefined : json['country'], + 'preferredLanguage': json['preferred_language'] == null ? undefined : json['preferred_language'], + 'isInvesting': json['is_investing'] == null ? undefined : json['is_investing'], + 'ratenzahlung': json['ratenzahlung'] == null ? undefined : json['ratenzahlung'], + 'attendedWelcomeSession': json['attended_welcome_session'] == null ? undefined : json['attended_welcome_session'], + 'coPurchaser': json['co_purchaser'] == null ? undefined : json['co_purchaser'], + 'allowsPurchaseTracking': json['allows_purchase_tracking'] == null ? undefined : json['allows_purchase_tracking'], + 'shiftCapabilities': json['shift_capabilities'] == null ? undefined : json['shift_capabilities'], + 'shiftPartner': json['shift_partner'] == null ? undefined : json['shift_partner'], + 'shiftStatus': json['shift_status'] == null ? undefined : json['shift_status'], + 'isWorking': json['is_working'] == null ? undefined : json['is_working'], + 'isExempted': json['is_exempted'] == null ? undefined : json['is_exempted'], + 'isPaused': json['is_paused'] == null ? undefined : json['is_paused'], + 'canShop': json['can_shop'] == null ? undefined : json['can_shop'], + }; +} + + export function DatapointExportToJSON(json: any): DatapointExport { + return DatapointExportToJSONTyped(json, false); + } + + export function DatapointExportToJSONTyped(value?: DatapointExport | null, ignoreDiscriminator: boolean = false): any { + if (value == null) { + return value; + } + + return { + + 'member_number': value['memberNumber'], + 'display_name': value['displayName'], + 'is_company': value['isCompany'], + 'company_name': value['companyName'], + 'first_name': value['firstName'], + 'last_name': value['lastName'], + 'usage_name': value['usageName'], + 'pronouns': value['pronouns'], + 'email': value['email'], + 'phone_number': value['phoneNumber'], + 'birthdate': value['birthdate'], + 'street': value['street'], + 'street_2': value['street2'], + 'postcode': value['postcode'], + 'city': value['city'], + 'country': value['country'], + 'preferred_language': value['preferredLanguage'], + 'is_investing': value['isInvesting'], + 'ratenzahlung': value['ratenzahlung'], + 'attended_welcome_session': value['attendedWelcomeSession'], + 'co_purchaser': value['coPurchaser'], + 'allows_purchase_tracking': value['allowsPurchaseTracking'], + 'shift_capabilities': value['shiftCapabilities'], + 'shift_partner': value['shiftPartner'], + 'shift_status': value['shiftStatus'], + 'is_working': value['isWorking'], + 'is_exempted': value['isExempted'], + 'is_paused': value['isPaused'], + 'can_shop': value['canShop'], + }; +} + diff --git a/src/api-client/models/Dataset.ts b/src/api-client/models/Dataset.ts new file mode 100644 index 000000000..6195270d7 --- /dev/null +++ b/src/api-client/models/Dataset.ts @@ -0,0 +1,102 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 0.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { mapValues } from '../runtime'; +/** + * + * @export + * @interface Dataset + */ +export interface Dataset { + /** + * + * @type {string} + * @memberof Dataset + */ + id: string; + /** + * + * @type {string} + * @memberof Dataset + */ + displayName: string; + /** + * + * @type {string} + * @memberof Dataset + */ + description: string; + /** + * + * @type {string} + * @memberof Dataset + */ + color: string; + /** + * + * @type {string} + * @memberof Dataset + */ + pointStyle: string; +} + +/** + * Check if a given object implements the Dataset interface. + */ +export function instanceOfDataset(value: object): value is Dataset { + if (!('id' in value) || value['id'] === undefined) return false; + if (!('displayName' in value) || value['displayName'] === undefined) return false; + if (!('description' in value) || value['description'] === undefined) return false; + if (!('color' in value) || value['color'] === undefined) return false; + if (!('pointStyle' in value) || value['pointStyle'] === undefined) return false; + return true; +} + +export function DatasetFromJSON(json: any): Dataset { + return DatasetFromJSONTyped(json, false); +} + +export function DatasetFromJSONTyped(json: any, ignoreDiscriminator: boolean): Dataset { + if (json == null) { + return json; + } + return { + + 'id': json['id'], + 'displayName': json['display_name'], + 'description': json['description'], + 'color': json['color'], + 'pointStyle': json['point_style'], + }; +} + + export function DatasetToJSON(json: any): Dataset { + return DatasetToJSONTyped(json, false); + } + + export function DatasetToJSONTyped(value?: Dataset | null, ignoreDiscriminator: boolean = false): any { + if (value == null) { + return value; + } + + return { + + 'id': value['id'], + 'display_name': value['displayName'], + 'description': value['description'], + 'color': value['color'], + 'point_style': value['pointStyle'], + }; +} + diff --git a/src/api-client/models/index.ts b/src/api-client/models/index.ts index e79e7846e..9cf10b64d 100644 --- a/src/api-client/models/index.ts +++ b/src/api-client/models/index.ts @@ -1,3 +1,6 @@ /* tslint:disable */ /* eslint-disable */ +export * from './Column'; +export * from './DatapointExport'; +export * from './Dataset'; export * from './ShareOwnerForWelcomeDesk'; diff --git a/src/statistics/FancyExportCard.tsx b/src/statistics/FancyExportCard.tsx new file mode 100644 index 000000000..8784f6f47 --- /dev/null +++ b/src/statistics/FancyExportCard.tsx @@ -0,0 +1,314 @@ +import React, { useEffect, useState } from "react"; +import { Badge, Card, Col, Form, Row, Spinner, Table } from "react-bootstrap"; +import { useApi } from "../hooks/useApi.ts"; +import { DatapointExport, Dataset, StatisticsApi } from "../api-client"; +import { getDateInputValue } from "./utils.tsx"; +import TapirButton from "../components/TapirButton.tsx"; +import { Copy, Download } from "react-bootstrap-icons"; + +declare let gettext: (english_text: string) => string; + +const FancyExportCard: React.FC = () => { + const api = useApi(StatisticsApi); + const [availableColumnsError, setAvailableColumnsError] = useState(""); + const [availableColumns, setAvailableColumns] = useState([]); + const [availableColumnsLoading, setAvailableColumnsLoading] = useState(false); + const [date, setDate] = useState(new Date()); + const [rows, setRows] = useState([]); + const [availableDatasetsError, setAvailableDatasetsError] = useState(""); + const [availableDatasets, setAvailableDatasets] = useState([]); + const [availableDatasetsLoading, setAvailableDatasetsLoading] = + useState(false); + const [selectedDataset, setSelectedDataset] = useState(); + const [selectedColumns, setSelectedColumns] = useState>( + new Set(), + ); + const [exportDownloading, setExportDownloading] = useState(false); + const [downloadExportError, setDownloadExportError] = useState(""); + + useEffect(() => { + setAvailableColumnsLoading(true); + api + .statisticsAvailableExportColumnsList() + .then((columns) => { + setAvailableColumns(columns.map((column) => column.columnName)); + }) + .catch(setAvailableColumnsError) + .finally(() => { + setAvailableColumnsLoading(false); + }); + }, []); + + useEffect(() => { + setAvailableDatasetsLoading(true); + + api + .statisticsAvailableDatasetsList({ colourblindness: "" }) + .then(setAvailableDatasets) + .catch(setAvailableDatasetsError) + .finally(() => { + setAvailableDatasetsLoading(false); + }); + }, []); + + function snakeCaseToCamelCase(input: string): string { + return input + .split("_") + .reduce( + (output, word, i) => + i === 0 + ? word.toLowerCase() + : `${output}${word.charAt(0).toUpperCase()}${word + .substring(1) + .toLowerCase()}`, + "", + ); + } + + function addExportColumnToSelection(columnName: string) { + const newSelectedColumnsSet = new Set(selectedColumns); + newSelectedColumnsSet.add(columnName); + setSelectedColumns(newSelectedColumnsSet); + setRows([]); + } + + function removeExportColumnFromSelection(columnName: string) { + const newSelectedColumnsSet = new Set(selectedColumns); + newSelectedColumnsSet.delete(columnName); + setSelectedColumns(newSelectedColumnsSet); + setRows([]); + } + + function downloadExport() { + if (!selectedDataset) { + alert("You must first select which dataset you want to export"); + return; + } + + setExportDownloading(true); + api + .statisticsExportDatasetList({ + exportColumns: Array.from(selectedColumns), + dataset: selectedDataset.id, + atDate: date, + }) + .then(setRows) + .catch(setDownloadExportError) + .finally(() => setExportDownloading(false)); + } + + function copyExportToClipboard() { + let text = Array.from(selectedColumns).join(","); + text += "\n" + rows.map((row) => buildRowExport(row)).join("\n"); + navigator.clipboard.writeText(text).then(() => { + alert("Copied"); + }); + } + + function buildRowExport(row: DatapointExport): string { + return Array.from(selectedColumns) + .map((columnName) => + buildColumnExport( + row[snakeCaseToCamelCase(columnName) as keyof DatapointExport], + ), + ) + .join(","); + } + + function buildColumnExport( + columnValue: string | number | boolean | string[] | undefined, + ): string { + if (typeof columnValue === "boolean") { + return columnValue ? "True" : "False"; + } + + if (Array.isArray(columnValue)) { + return columnValue.join(" - "); + } + + return columnValue ? columnValue.toString() : "N/A"; + } + + return ( + <> + + + + +
{gettext("Fancy export")}
+
+ +
+
+ {availableDatasetsError ? ( +
{availableDatasetsError}
+ ) : availableDatasetsLoading ? ( + + ) : ( + + {gettext("Source dataset")} + { + for (const dataset of availableDatasets) { + if (dataset.id == event.target.value) { + setSelectedDataset(dataset); + setRows([]); + return; + } + } + setSelectedDataset(undefined); + }} + > + + {availableDatasets.map((dataset) => ( + + ))} + + {selectedDataset && ( + {selectedDataset.description} + )} + + )} + + {gettext("Date")} + { + setDate(new Date(event.target.value)); + }} + /> + + {gettext( + "The date is only relevant for the following fields: shift_status, is_working, is_exempted, is_paused, can_shop. " + + "For all other fields, the value as it is now is exported, not the value as it was at the given date.", + )} + + +
+
+ {availableColumnsError ? ( +
{availableColumnsError}
+ ) : availableColumnsLoading ? ( + + ) : ( + + + {gettext("Add columns to the export")} + + { + addExportColumnToSelection(event.target.value); + }} + > + + {availableColumns + .filter((column) => !selectedColumns.has(column)) + .map((column) => ( + + ))} + + + {gettext("Click on a selected column to deselect it.")} + + + )} +
+
+ {Array.from(selectedColumns).map((column) => ( + removeExportColumnFromSelection(column)} + style={{ cursor: "pointer" }} + key={column} + > + {column} + + ))} +
+
+ { + downloadExport(); + }} + disabled={!selectedDataset} + loading={exportDownloading} + /> + 0 + ? gettext("Copy export to clipboard") + : gettext("Build the export to copy it") + } + icon={Copy} + onClick={() => { + copyExportToClipboard(); + }} + disabled={rows.length === 0} + /> +
+
+ {downloadExportError ? ( + downloadExportError + ) : rows.length === 0 ? ( +
{gettext("Waiting for build")}
+ ) : ( + + + + {Array.from(selectedColumns).map((columnName) => ( + + ))} + + + + {rows.map((row, index) => ( + + {Array.from(selectedColumns).map((columnName) => ( + + ))} + + ))} + +
{columnName}
+ {buildColumnExport( + row[ + snakeCaseToCamelCase( + columnName, + ) as keyof DatapointExport + ], + )} +
+ )} +
+
+
+
+ +
+ + ); +}; + +export default FancyExportCard; diff --git a/src/statistics/FancyGraphCard.tsx b/src/statistics/FancyGraphCard.tsx index 32cda7758..658b5b48f 100644 --- a/src/statistics/FancyGraphCard.tsx +++ b/src/statistics/FancyGraphCard.tsx @@ -1,10 +1,11 @@ -import React, { useEffect, useRef, useState } from "react"; +import React, { MutableRefObject, useEffect, useRef, useState } from "react"; import { Alert, Card, Col, Row, Spinner } from "react-bootstrap"; import { BarController, BarElement, CategoryScale, Chart as ChartJS, + ChartType, Colors, Legend, LinearScale, @@ -14,15 +15,15 @@ import { Tooltip, } from "chart.js"; import { useApi } from "../hooks/useApi.ts"; -import { FetchError, StatisticsApi } from "../api-client"; +import { Dataset, FetchError, StatisticsApi } from "../api-client"; import { Chart } from "react-chartjs-2"; -import { datasets } from "./datasets.tsx"; import { formatDate } from "../utils/formatDate.ts"; import DatasetPickerCard from "./components/DatasetPickerCard.tsx"; import { getFirstOfMonth } from "./utils.tsx"; import TapirButton from "../components/TapirButton.tsx"; import { Download } from "react-bootstrap-icons"; import DateRangePicker from "./components/DateRangePicker.tsx"; +import ColourblindnessTypePicker from "./components/ColourblindnessTypePicker.tsx"; declare let gettext: (english_text: string) => string; @@ -50,17 +51,42 @@ const FancyGraphCard: React.FC = () => { const [error, setError] = useState(""); const [dateFrom, setDateFrom] = useState(new Date()); const [dateTo, setDateTo] = useState(getFirstOfMonth(new Date())); - const [enabledDatasets, setEnabledDatasets] = useState>( - new Set(), + const [enabledNotRelativeDatasets, setEnabledNotRelativeDatasets] = useState< + Set + >(new Set()); + const enabledDatasetsNotRelativeRef = useRef>( + new Set(), ); - const enabledDatasetsRef = useRef>(new Set()); + const [enabledRelativeDatasets, setEnabledRelativeDatasets] = useState< + Set + >(new Set()); + const enabledRelativeDatasetsRef = useRef>(new Set()); const [graphData, setGraphData] = useState({}); const [graphLabels, setGraphLabels] = useState([]); const [dates, setDates] = useState([]); const [fetching, setFetching] = useState(false); const [datapickerExpanded, setDatapickerExpanded] = useState(false); - const cachedData = useRef({}); + const cachedDataNotRelative = useRef({}); + const cachedDataRelative = useRef({}); const api = useApi(StatisticsApi); + const [availableDatasetsLoading, setAvailableDatasetsLoading] = + useState(false); + const [availableDatasets, setAvailableDatasets] = useState([]); + const [availableDatasetsError, setAvailableDatasetsError] = useState(""); + const [selectedColourblindnessType, setSelectedColourblindnessType] = + useState(""); + + useEffect(() => { + setAvailableDatasetsLoading(true); + + api + .statisticsAvailableDatasetsList({ + colourblindness: selectedColourblindnessType, + }) + .then(setAvailableDatasets) + .catch(setAvailableDatasetsError) + .finally(() => setAvailableDatasetsLoading(false)); + }, [selectedColourblindnessType]); useEffect(() => { const dateFromOnPageLoad = new Date(); @@ -69,21 +95,27 @@ const FancyGraphCard: React.FC = () => { }, []); useEffect(() => { - fillCachedData(); + fillCachedData(enabledDatasetsNotRelativeRef, cachedDataNotRelative); + fillCachedData(enabledRelativeDatasetsRef, cachedDataRelative); buildAndSetGraphData(); fetchData(); - }, [dates, enabledDatasets]); + }, [dates, enabledNotRelativeDatasets, enabledRelativeDatasets]); - function fillCachedData() { - for (const datasetId of enabledDatasetsRef.current) { - if (!Object.keys(cachedData.current).includes(datasetId)) { - cachedData.current[datasetId] = {}; + function fillCachedData( + datasetsRef: MutableRefObject>, + cachedDataRef: MutableRefObject, + ) { + for (const dataset of datasetsRef.current) { + if (!Object.keys(cachedDataRef.current).includes(dataset.id)) { + cachedDataRef.current[dataset.id] = {}; } for (const date of dates) { if ( - !Object.keys(cachedData.current[datasetId]).includes(formatDate(date)) + !Object.keys(cachedDataRef.current[dataset.id]).includes( + formatDate(date), + ) ) { - cachedData.current[datasetId][formatDate(date)] = null; + cachedDataRef.current[dataset.id][formatDate(date)] = null; } } } @@ -91,9 +123,14 @@ const FancyGraphCard: React.FC = () => { function buildAndSetGraphData() { const newGraphData: GraphData = {}; - for (const datasetId of enabledDatasetsRef.current) { - newGraphData[datasetId] = dates.map( - (date) => cachedData.current[datasetId][formatDate(date)], + for (const dataset of enabledDatasetsNotRelativeRef.current) { + newGraphData[dataset.id] = dates.map( + (date) => cachedDataNotRelative.current[dataset.id][formatDate(date)], + ); + } + for (const dataset of enabledRelativeDatasetsRef.current) { + newGraphData[dataset.id + "_relative"] = dates.map( + (date) => cachedDataRelative.current[dataset.id][formatDate(date)], ); } setGraphData(newGraphData); @@ -108,7 +145,7 @@ const FancyGraphCard: React.FC = () => { setError(""); setFetching(true); - const [datasetId, dateString] = nextDataToFetch; + const [datasetId, dateString, relative] = nextDataToFetch; const date = new Date(); const [day, month, year] = dateString.split("."); @@ -118,10 +155,16 @@ const FancyGraphCard: React.FC = () => { date.setHours(12); date.setMinutes(0); - datasets[datasetId].apiCall - .call(api, { atDate: date, relative: datasets[datasetId].relative }) + api + .statisticsGraphPointRetrieve({ + atDate: date, + relative: relative, + dataset: datasetId, + }) .then((value: number) => { - cachedData.current[datasetId][dateString] = value; + (relative ? cachedDataRelative : cachedDataNotRelative).current[ + datasetId + ][dateString] = value; buildAndSetGraphData(); setFetching(false); fetchData(); @@ -133,11 +176,21 @@ const FancyGraphCard: React.FC = () => { }); } - function getNextDataToFetch() { - for (const datasetId of enabledDatasetsRef.current) { - for (const date of Object.keys(cachedData.current[datasetId])) { - if (cachedData.current[datasetId][date] === null) { - return [datasetId, date]; + function getNextDataToFetch(): [string, string, boolean] | null { + for (const dataset of enabledDatasetsNotRelativeRef.current) { + for (const date of Object.keys( + cachedDataNotRelative.current[dataset.id], + )) { + if (cachedDataNotRelative.current[dataset.id][date] === null) { + return [dataset.id, date, false]; + } + } + } + + for (const dataset of enabledRelativeDatasetsRef.current) { + for (const date of Object.keys(cachedDataRelative.current[dataset.id])) { + if (cachedDataRelative.current[dataset.id][date] === null) { + return [dataset.id, date, true]; } } } @@ -148,14 +201,16 @@ const FancyGraphCard: React.FC = () => { const data = { labels: graphLabels, datasets: Object.entries(graphData).map(([datasetId, data]) => { - const dataset = datasets[datasetId]; + const dataset = availableDatasets.find( + (dataset) => dataset.id === datasetId.replace("_relative", ""), + ); return { - label: dataset.display_name, - type: dataset.chart_type, + label: dataset!.displayName, + type: (datasetId.endsWith("_relative") ? "bar" : "line") as ChartType, data: data, - borderColor: dataset.color, - backgroundColor: dataset.color, - pointStyle: dataset.pointStyle, + borderColor: dataset!.color, + backgroundColor: dataset!.color, + pointStyle: dataset!.pointStyle, radius: 5, }; }), @@ -218,10 +273,15 @@ const FancyGraphCard: React.FC = () => { @@ -241,6 +301,11 @@ const FancyGraphCard: React.FC = () => { setDates={setDates} setGraphLabels={setGraphLabels} /> + string; + +interface ColourblindnessTypePickerProps { + setSelectedColourblindnessType: (colourblindnessType: string) => void; +} + +const ColourblindnessTypePicker: React.FC = ({ + setSelectedColourblindnessType, +}) => { + const api = useApi(StatisticsApi); + const [availableTypes, setAvailableTypes] = useState([]); + + useEffect(() => { + api + .statisticsAvailableColourblindnessTypesRetrieve() + .then(setAvailableTypes); + }, []); + + return ( + + + { + setSelectedColourblindnessType(event.target.value); + }} + > + + {availableTypes.map((type) => ( + + ))} + + + + ); +}; + +export default ColourblindnessTypePicker; diff --git a/src/statistics/components/DatasetPickerCard.tsx b/src/statistics/components/DatasetPickerCard.tsx index 1ae4a77f9..edbba0fb3 100644 --- a/src/statistics/components/DatasetPickerCard.tsx +++ b/src/statistics/components/DatasetPickerCard.tsx @@ -1,26 +1,36 @@ import React, { MutableRefObject } from "react"; -import { Card, Form, Table } from "react-bootstrap"; -import { datasets } from "../datasets.tsx"; +import { Card, Form, Spinner, Table } from "react-bootstrap"; import TapirButton from "../../components/TapirButton.tsx"; import { ArrowsCollapseVertical, ArrowsExpandVertical, } from "react-bootstrap-icons"; +import { Dataset } from "../../api-client"; declare let gettext: (english_text: string) => string; interface DatasetPickerCardProps { - enabledDatasetsRef: MutableRefObject>; - setEnabledDatasets: (set: Set) => void; + enabledDatasetsRef: MutableRefObject>; + setEnabledDatasets: (set: Set) => void; + enabledRelativeDatasetsRef: MutableRefObject>; + setEnabledRelativeDatasets: (set: Set) => void; isExpanded: boolean; setIsExpanded: (isExpanded: boolean) => void; + availableDatasets: Dataset[]; + availableDatasetsLoading: boolean; + availableDatasetsError: string; } const DatasetPickerCard: React.FC = ({ enabledDatasetsRef, setEnabledDatasets, + enabledRelativeDatasetsRef, + setEnabledRelativeDatasets, isExpanded, setIsExpanded, + availableDatasets, + availableDatasetsLoading, + availableDatasetsError, }) => { return ( @@ -35,68 +45,75 @@ const DatasetPickerCard: React.FC = ({ /> - - - - - - - - {isExpanded && } - - - - {Object.entries(datasets).map(([datasetId, dataset]) => { - if (dataset.relative) { - return null; - } - const datasetRelativeId = datasetId + "_relative"; - return ( - - - - - - {isExpanded && ( - + + {isExpanded && ( + + )} + + ); + })} + +
DataColorAbsoluteRelativeDescription
{dataset.display_name} - ■ - - { - if (e.target.checked) { - enabledDatasetsRef.current.add(datasetId); - } else { - enabledDatasetsRef.current.delete(datasetId); - } - setEnabledDatasets(new Set(enabledDatasetsRef.current)); - }} - /> - - { - if (e.target.checked) { - enabledDatasetsRef.current.add(datasetRelativeId); - } else { - enabledDatasetsRef.current.delete(datasetRelativeId); - } - setEnabledDatasets(new Set(enabledDatasetsRef.current)); - }} - /> - -
{dataset.description}
+ {availableDatasetsError ? ( +
{availableDatasetsError}
+ ) : availableDatasetsLoading ? ( + + ) : ( + + + + + + + + {isExpanded && } + + + + {availableDatasets.map((dataset) => { + return ( + + + - )} - - ); - })} - -
DataColorAbsoluteRelativeDescription
{dataset.displayName} + ■
+
+ { + if (e.target.checked) { + enabledDatasetsRef.current.add(dataset); + } else { + enabledDatasetsRef.current.delete(dataset); + } + setEnabledDatasets( + new Set(enabledDatasetsRef.current), + ); + }} + /> + + { + if (e.target.checked) { + enabledRelativeDatasetsRef.current.add(dataset); + } else { + enabledRelativeDatasetsRef.current.delete(dataset); + } + setEnabledRelativeDatasets( + new Set(enabledRelativeDatasetsRef.current), + ); + }} + /> + +
{dataset.description}
+
+ )}
); diff --git a/src/statistics/components/DateRangePicker.tsx b/src/statistics/components/DateRangePicker.tsx index 42144e9b9..19b18778f 100644 --- a/src/statistics/components/DateRangePicker.tsx +++ b/src/statistics/components/DateRangePicker.tsx @@ -1,6 +1,10 @@ import React, { useEffect, useState } from "react"; import { FloatingLabel, Form } from "react-bootstrap"; -import { getFirstOfMonth, getLastOfMonth } from "../utils.tsx"; +import { + getDateInputValue, + getFirstOfMonth, + getLastOfMonth, +} from "../utils.tsx"; import { formatDate } from "../../utils/formatDate.ts"; declare let gettext: (english_text: string) => string; @@ -71,13 +75,6 @@ const DateRangePicker: React.FC = ({ return dateAdapter(date); } - function getDateInputValue(date: Date) { - if (isNaN(date.getTime())) { - return undefined; - } - return date.toISOString().substring(0, 10); - } - return ( <> diff --git a/src/statistics/datasets.tsx b/src/statistics/datasets.tsx deleted file mode 100644 index c227ac35b..000000000 --- a/src/statistics/datasets.tsx +++ /dev/null @@ -1,237 +0,0 @@ -import {InitOverrideFunction, StatisticsApi} from "../api-client"; -import {ChartType} from "chart.js"; -import {useApi} from "../hooks/useApi.ts"; - -declare let gettext: (english_text: string) => string; - -const datasetNumberOfMembers = "number_of_members"; -const datasetNumberOfActiveMembers = "number_of_active_members"; -const datasetNumberOfActiveMembersWithAccount = "number_of_active_members_with_account"; -const datasetNumberOfInvestingMembers = "number_of_investing_members"; -const datasetNumberOfPausedMembers = "number_of_paused_members"; -const datasetNumberOfWorkingMembers = "number_of_working_members"; -const datasetNumberOfPurchasingMembers = "number_of_purchasing_members"; -const datasetNumberOfFrozenMembers = "number_of_frozen_members"; -const datasetNumberOfLongTermFrozenMembers = - "number_of_long_term_frozen_members"; -const datasetNumberOfShiftPartners = "number_of_shift_partners"; -const datasetNumberOfCoPurchasers = "number_of_co_purchasers"; -const datasetNumberOfFlyingMembers = "number_of_flying_members"; -const datasetNumberOfAbcdMembers = "number_of_abcd_members"; -const datasetNumberOfPendingResignations = "number_of_pending_resignations"; -const datasetNumberOfCreatedResignations = "number_of_created_resignations"; -const datasetNumberOfExemptedMembers = "number_of_exempted_members"; -const datasetNumberOfExemptedMembersThatWork = - "number_of_exempted_members_that_work"; - -// Colors from https://mokole.com/palette.html -// or https://lospec.com/palette-list/syz15 - -// Point styles from https://www.chartjs.org/docs/latest/configuration/elements.html#info - -interface Dataset { - apiCall: ( - requestParameters: { atDate: Date; relative: boolean }, - initOverrides?: RequestInit | InitOverrideFunction, - ) => any; - display_name: string; - description?: string; - chart_type: ChartType; - relative: boolean; - color: string; - pointStyle: string; -} - -const api = useApi(StatisticsApi); - -export const datasets: { [key: string]: Dataset } = { - [datasetNumberOfMembers]: { - display_name: gettext("Total members"), - description: gettext( - "Ignoring status: investing and paused members are included", - ), - apiCall: api.statisticsNumberOfMembersAtDateRetrieve, - chart_type: "line", - relative: false, - color: "#0e0c19", - pointStyle: "circle", - }, - [datasetNumberOfActiveMembers]: { - display_name: gettext("Active members"), - description: gettext( - "Active in the sense of their membership: paused and investing members are not active, but frozen members are active", - ), - apiCall: api.statisticsNumberOfActiveMembersAtDateRetrieve, - chart_type: "line", - relative: false, - color: "#9d1f2f", - pointStyle: "cross", - }, - [datasetNumberOfActiveMembersWithAccount]: { - display_name: gettext("Active members with Tapir account"), - description: gettext( - "Same as active members, but also had an account at the given date. Some members declare themselves active when joining the coop but never come to activate their account.", - ), - apiCall: api.statisticsNumberOfActiveMembersWithAccountAtDateRetrieve, - chart_type: "line", - relative: false, - color: "#bd3f4f", - pointStyle: "circle", - }, - [datasetNumberOfInvestingMembers]: { - display_name: gettext("Investing members"), - apiCall: api.statisticsNumberOfInvestingMembersAtDateRetrieve, - chart_type: "line", - relative: false, - color: "#f28c8c", - pointStyle: "crossRot", - }, - [datasetNumberOfPausedMembers]: { - display_name: gettext("Paused members"), - apiCall: api.statisticsNumberOfPausedMembersAtDateRetrieve, - chart_type: "line", - relative: false, - color: "#b05621", - pointStyle: "dash", - }, - [datasetNumberOfWorkingMembers]: { - display_name: gettext("Working members"), - apiCall: api.statisticsNumberOfWorkingMembersAtDateRetrieve, - chart_type: "line", - relative: false, - color: "#c9ad23", - pointStyle: "line", - }, - [datasetNumberOfPurchasingMembers]: { - display_name: gettext("Purchasing members"), - description: gettext( - "Members who are allowed to shop. To be allowed to shop, a member must be active (see the description for \"Active members\"), have a Tapir account, and not be frozen.", - ), - apiCall: api.statisticsNumberOfPurchasingMembersAtDateRetrieve, - chart_type: "line", - relative: false, - color: "#d8e1a9", - pointStyle: "rect", - }, - [datasetNumberOfFrozenMembers]: { - display_name: gettext("Frozen members"), - description: gettext( - "Counted out of 'active' members: paused and investing members not counted.", - ), - apiCall: api.statisticsNumberOfFrozenMembersAtDateRetrieve, - chart_type: "line", - relative: false, - color: "#52b466", - pointStyle: "rectRounded", - }, - [datasetNumberOfLongTermFrozenMembers]: { - display_name: gettext("Long-term frozen members"), - description: gettext( - "Members that are frozen since more than 180 days (roughly 6 month). Long-term frozen members are included in the \"Frozen members\" dataset", - ), - apiCall: api.statisticsNumberOfLongTermFrozenMembersAtDateRetrieve, - chart_type: "line", - relative: false, - color: "#0e4f38", - pointStyle: "rectRot", - }, - [datasetNumberOfShiftPartners]: { - display_name: gettext("Shift partners"), - description: gettext( - "Counted out of working members only: a frozen member with a shift partner is not counted", - ), - apiCall: api.statisticsNumberOfShiftPartnersAtDateRetrieve, - chart_type: "line", - relative: false, - color: "#68062f", - pointStyle: "star", - }, - [datasetNumberOfCoPurchasers]: { - display_name: gettext("Co-purchasers"), - description: gettext( - "Only members who can shop are counted: members that have a co-purchaser but are not allowed to shop are not counted", - ), - apiCall: api.statisticsNumberOfCoPurchasersAtDateRetrieve, - chart_type: "line", - relative: false, - color: "#8d5a88", - pointStyle: "triangle", - }, - [datasetNumberOfFlyingMembers]: { - display_name: gettext("Flying members"), - description: gettext( - "Only members who work are counted: members that are exempted, paused, frozen... are not counted", - ), - apiCall: api.statisticsNumberOfFlyingMembersAtDateRetrieve, - chart_type: "line", - relative: false, - color: "#126f7e", - pointStyle: "circle", - }, - [datasetNumberOfAbcdMembers]: { - display_name: gettext("ABCD members"), - description: gettext( - "Only members who work are counted: members that are exempted, paused, frozen... are not counted", - ), - apiCall: api.statisticsNumberOfAbcdMembersAtDateRetrieve, - chart_type: "line", - relative: false, - color: "#53269a", - pointStyle: "cross", - }, - [datasetNumberOfPendingResignations]: { - display_name: gettext("Pending resignations"), - description: gettext( - "Members who want to get their money back and are waiting for the 3 year term", - ), - apiCall: api.statisticsNumberOfPendingResignationsAtDateRetrieve, - chart_type: "line", - relative: false, - color: "#221452", - pointStyle: "crossRot", - }, - [datasetNumberOfCreatedResignations]: { - display_name: gettext("Created resignations"), - description: gettext( - "Regardless of whether the member gifts their share or get their money back, this is relative to when the resignation is created.", - ), - apiCall: api.statisticsNumberOfCreatedResignationsInSameMonthRetrieve, - chart_type: "line", - relative: false, - color: "#122f70", - pointStyle: "dash", - }, - [datasetNumberOfExemptedMembers]: { - display_name: gettext("Exempted members"), - description: gettext( - "Counting only members that would work if they were not exempted: frozen and investing members with an exemption are not counted. ", - ), - apiCall: api.statisticsNumberOfExemptedMembersAtDateRetrieve, - chart_type: "line", - relative: false, - color: "#223421", - pointStyle: "dash", - }, - [datasetNumberOfExemptedMembersThatWork]: { - display_name: gettext("Exempted members that work"), - description: gettext( - "Counting all exempted members (ignoring if they are frozen or investing) that actually did a shift in the past 60 days. Just registering to the shift doesn't count, the attendance must be confirmed. ", - ), - apiCall: api.statisticsNumberOfExemptedMembersThatWorkRetrieve, - chart_type: "line", - relative: false, - color: "#cc33cc", - pointStyle: "dash", - }, -}; - -for (const [datasetId, dataset] of Object.entries(datasets)) { - datasets[datasetId + "_relative"] = { - display_name: dataset.display_name + " (relative)", - apiCall: dataset.apiCall, - chart_type: "bar", - relative: true, - color: dataset.color, - pointStyle: dataset.pointStyle, - }; -} diff --git a/src/statistics/fancy_export_entry.tsx b/src/statistics/fancy_export_entry.tsx new file mode 100644 index 000000000..9b4ae435a --- /dev/null +++ b/src/statistics/fancy_export_entry.tsx @@ -0,0 +1,10 @@ +import {createRoot} from "react-dom/client"; +import FancyExportCard from "./FancyExportCard.tsx"; + +const domNode = document.getElementById("fancy_export"); +if (domNode) { + const root = createRoot(domNode); + root.render(); +} else { + console.error("Failed to render fancy export from React"); +} diff --git a/src/statistics/utils.tsx b/src/statistics/utils.tsx index 02fcc07a7..21eb840f3 100644 --- a/src/statistics/utils.tsx +++ b/src/statistics/utils.tsx @@ -6,3 +6,10 @@ export function getFirstOfMonth(date: Date) { export function getLastOfMonth(date: Date) { return new Date(date.getFullYear(), date.getMonth() + 1, 0); } + +export function getDateInputValue(date: Date) { + if (isNaN(date.getTime())) { + return undefined; + } + return date.toISOString().substring(0, 10); +} diff --git a/tapir/statistics/apps.py b/tapir/statistics/apps.py index 57eb99b82..e6de0ead6 100644 --- a/tapir/statistics/apps.py +++ b/tapir/statistics/apps.py @@ -20,3 +20,86 @@ def ready(self): url=reverse("statistics:main_statistics"), ordering=1, ) + + self.register_data_providers() + + @classmethod + def register_data_providers(cls): + from tapir.statistics.services.data_providers.data_provider_abcd_members import ( + DataProviderAbcdMembers, + ) + from tapir.statistics.services.data_providers.data_provider_active_members import ( + DataProviderActiveMembers, + ) + from tapir.statistics.services.data_providers.data_provider_active_members_with_account import ( + DataProviderActiveMembersWithAccount, + ) + from tapir.statistics.services.data_providers.data_provider_co_purchasers import ( + DataProviderCoPurchasers, + ) + from tapir.statistics.services.data_providers.data_provider_exempted_members import ( + DataProviderExemptedMembers, + ) + from tapir.statistics.services.data_providers.data_provider_exempted_members_that_work import ( + DataProviderExemptedMembersThatWork, + ) + from tapir.statistics.services.data_providers.data_provider_flying_members import ( + DataProviderFlyingMembers, + ) + from tapir.statistics.services.data_providers.data_provider_frozen_members import ( + DataProviderFrozenMembers, + ) + from tapir.statistics.services.data_providers.data_provider_frozen_members_long_term import ( + DataProviderFrozenMembersLongTerm, + ) + from tapir.statistics.services.data_providers.data_provider_investing_members import ( + DataProviderInvestingMembers, + ) + from tapir.statistics.services.data_providers.data_provider_paused_members import ( + DataProviderPausedMembers, + ) + from tapir.statistics.services.data_providers.data_provider_purchasing_members import ( + DataProviderPurchasingMembers, + ) + from tapir.statistics.services.data_providers.data_provider_resignations_created import ( + DataProviderResignationsCreated, + ) + from tapir.statistics.services.data_providers.data_provider_resignations_pending import ( + DataProviderResignationsPending, + ) + from tapir.statistics.services.data_providers.data_provider_shift_partners import ( + DataProviderShiftPartners, + ) + from tapir.statistics.services.data_providers.data_provider_total_members import ( + DataProviderTotalMembers, + ) + from tapir.statistics.services.data_providers.data_provider_working_members import ( + DataProviderWorkingMembers, + ) + + data_providers = [ + DataProviderAbcdMembers, + DataProviderActiveMembers, + DataProviderActiveMembersWithAccount, + DataProviderCoPurchasers, + DataProviderExemptedMembers, + DataProviderExemptedMembersThatWork, + DataProviderFlyingMembers, + DataProviderFrozenMembers, + DataProviderFrozenMembersLongTerm, + DataProviderInvestingMembers, + DataProviderPausedMembers, + DataProviderPurchasingMembers, + DataProviderResignationsCreated, + DataProviderResignationsPending, + DataProviderShiftPartners, + DataProviderTotalMembers, + DataProviderWorkingMembers, + ] + + from tapir.statistics.services.data_providers.base_data_provider import ( + BaseDataProvider, + ) + + for data_provider in data_providers: + BaseDataProvider.register_data_provider(data_provider) diff --git a/tapir/statistics/migrations/0006_remove_fancygraphcache_view_name_and_more.py b/tapir/statistics/migrations/0006_remove_fancygraphcache_view_name_and_more.py new file mode 100644 index 000000000..4e54eb3f2 --- /dev/null +++ b/tapir/statistics/migrations/0006_remove_fancygraphcache_view_name_and_more.py @@ -0,0 +1,23 @@ +# Generated by Django 5.1.5 on 2025-01-30 17:47 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("statistics", "0005_merge_20241209_2116"), + ] + + operations = [ + migrations.RemoveField( + model_name="fancygraphcache", + name="view_name", + ), + migrations.AddField( + model_name="fancygraphcache", + name="data_provider_name_name", + field=models.CharField(default="unused", max_length=500), + preserve_default=False, + ), + ] diff --git a/tapir/statistics/migrations/0007_rename_data_provider_name_name_fancygraphcache_data_provider_name.py b/tapir/statistics/migrations/0007_rename_data_provider_name_name_fancygraphcache_data_provider_name.py new file mode 100644 index 000000000..47f2067a2 --- /dev/null +++ b/tapir/statistics/migrations/0007_rename_data_provider_name_name_fancygraphcache_data_provider_name.py @@ -0,0 +1,18 @@ +# Generated by Django 5.1.5 on 2025-01-30 18:48 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("statistics", "0006_remove_fancygraphcache_view_name_and_more"), + ] + + operations = [ + migrations.RenameField( + model_name="fancygraphcache", + old_name="data_provider_name_name", + new_name="data_provider_name", + ), + ] diff --git a/tapir/statistics/models.py b/tapir/statistics/models.py index 7c8fb2e25..8d257c2cc 100644 --- a/tapir/statistics/models.py +++ b/tapir/statistics/models.py @@ -33,10 +33,13 @@ class PurchaseBasket(models.Model): class FancyGraphCache(models.Model): - view_name = models.CharField(max_length=255) + data_provider_name = models.CharField(max_length=500) date = models.DateField() value = models.IntegerField() + def __str__(self): + return f"{self.data_provider_name} - {self.date} - {self.value}" + class CreditAccount(models.Model): source_file = models.ForeignKey(ProcessedCreditFiles, on_delete=models.CASCADE) diff --git a/tapir/statistics/serializers.py b/tapir/statistics/serializers.py new file mode 100644 index 000000000..37a1cdb8c --- /dev/null +++ b/tapir/statistics/serializers.py @@ -0,0 +1,47 @@ +from rest_framework import serializers + + +class DatasetSerializer(serializers.Serializer): + id = serializers.CharField() + display_name = serializers.CharField() + description = serializers.CharField() + color = serializers.CharField() + point_style = serializers.CharField() + + +class DatapointExportSerializer(serializers.Serializer): + member_number = serializers.IntegerField(required=False) + display_name = serializers.CharField(required=False) + is_company = serializers.BooleanField(required=False) + company_name = serializers.CharField(required=False) + first_name = serializers.CharField(required=False) + last_name = serializers.CharField(required=False) + usage_name = serializers.CharField(required=False) + pronouns = serializers.CharField(required=False) + email = serializers.CharField(required=False) + phone_number = serializers.CharField(required=False) + birthdate = serializers.CharField(required=False) + street = serializers.CharField(required=False) + street_2 = serializers.CharField(required=False) + postcode = serializers.CharField(required=False) + city = serializers.CharField(required=False) + country = serializers.CharField(required=False) + preferred_language = serializers.CharField(required=False) + is_investing = serializers.BooleanField(required=False) + ratenzahlung = serializers.BooleanField(required=False) + attended_welcome_session = serializers.BooleanField(required=False) + co_purchaser = serializers.CharField(required=False) + allows_purchase_tracking = serializers.BooleanField(required=False) + shift_capabilities = serializers.ListField( + required=False, child=serializers.CharField() + ) + shift_partner = serializers.IntegerField(required=False) + shift_status = serializers.CharField(required=False) + is_working = serializers.BooleanField(required=False) + is_exempted = serializers.BooleanField(required=False) + is_paused = serializers.BooleanField(required=False) + can_shop = serializers.BooleanField(required=False) + + +class ColumnSerializer(serializers.Serializer): + column_name = serializers.CharField() diff --git a/tapir/statistics/tests/fancy_graph/__init__.py b/tapir/statistics/services/__init__.py similarity index 100% rename from tapir/statistics/tests/fancy_graph/__init__.py rename to tapir/statistics/services/__init__.py diff --git a/tapir/statistics/views/fancy_graph/__init__.py b/tapir/statistics/services/data_providers/__init__.py similarity index 100% rename from tapir/statistics/views/fancy_graph/__init__.py rename to tapir/statistics/services/data_providers/__init__.py diff --git a/tapir/statistics/services/data_providers/base_data_provider.py b/tapir/statistics/services/data_providers/base_data_provider.py new file mode 100644 index 000000000..a89dc95f8 --- /dev/null +++ b/tapir/statistics/services/data_providers/base_data_provider.py @@ -0,0 +1,38 @@ +from __future__ import annotations + +import datetime +from abc import ABC, abstractmethod +from typing import Dict, Type + +from django.db.models import QuerySet + +from tapir.coop.models import ShareOwner + +data_providers: Dict[str, Type[BaseDataProvider]] = {} + + +class BaseDataProvider(ABC): + @staticmethod + def raise_not_implemented(): + raise NotImplementedError( + "Children of BaseDataProvider must implement this method" + ) + + @classmethod + @abstractmethod + def get_display_name(cls): + cls.raise_not_implemented() + + @classmethod + @abstractmethod + def get_description(cls): + cls.raise_not_implemented() + + @classmethod + @abstractmethod + def get_queryset(cls, reference_time: datetime.datetime) -> QuerySet[ShareOwner]: + cls.raise_not_implemented() + + @staticmethod + def register_data_provider(data_provider: Type[BaseDataProvider]): + data_providers[data_provider.__name__] = data_provider diff --git a/tapir/statistics/views/fancy_graph/number_of_abcd_members_view.py b/tapir/statistics/services/data_providers/data_provider_abcd_members.py similarity index 54% rename from tapir/statistics/views/fancy_graph/number_of_abcd_members_view.py rename to tapir/statistics/services/data_providers/data_provider_abcd_members.py index 4b5c4a54f..773ca3720 100644 --- a/tapir/statistics/views/fancy_graph/number_of_abcd_members_view.py +++ b/tapir/statistics/services/data_providers/data_provider_abcd_members.py @@ -1,20 +1,30 @@ import datetime -from django.db.models import Q +from django.db.models import Q, QuerySet +from django.utils.translation import gettext_lazy as _ +from tapir.coop.models import ShareOwner from tapir.shifts.models import ShiftAttendanceMode, ShiftUserData from tapir.shifts.services.shift_attendance_mode_service import ( ShiftAttendanceModeService, ) from tapir.shifts.services.shift_expectation_service import ShiftExpectationService -from tapir.statistics.views.fancy_graph.base_view import ( - DatapointView, -) +from tapir.statistics.services.data_providers.base_data_provider import BaseDataProvider + +class DataProviderAbcdMembers(BaseDataProvider): + @classmethod + def get_display_name(cls): + return _("ABCD members") -class NumberOfAbcdMembersAtDateView(DatapointView): - def calculate_datapoint(self, reference_time: datetime.datetime) -> int: + @classmethod + def get_description(cls): + return _( + "Only members who work are counted: members that are exempted, paused, frozen... are not counted" + ) + @classmethod + def get_queryset(cls, reference_time: datetime.datetime) -> QuerySet[ShareOwner]: working_members = ( ShiftExpectationService.annotate_shift_user_data_queryset_with_working_status_at_datetime( ShiftUserData.objects.all(), reference_time @@ -31,10 +41,9 @@ def calculate_datapoint(self, reference_time: datetime.datetime) -> int: ) abcd_members_ids = list(abcd_members.values_list("id", flat=True)) - return ( - ShiftUserData.objects.filter( - Q(id__in=working_members_ids) & Q(id__in=abcd_members_ids) - ) - .distinct() - .count() + abcd_shift_user_datas = ShiftUserData.objects.filter( + Q(id__in=working_members_ids) & Q(id__in=abcd_members_ids) + ).distinct() + return ShareOwner.objects.filter( + user__shift_user_data__in=abcd_shift_user_datas ) diff --git a/tapir/statistics/services/data_providers/data_provider_active_members.py b/tapir/statistics/services/data_providers/data_provider_active_members.py new file mode 100644 index 000000000..46534cb3b --- /dev/null +++ b/tapir/statistics/services/data_providers/data_provider_active_members.py @@ -0,0 +1,24 @@ +import datetime + +from django.db.models import QuerySet +from django.utils.translation import gettext_lazy as _ + +from tapir.coop.models import ShareOwner, MemberStatus +from tapir.statistics.services.data_providers.base_data_provider import BaseDataProvider + + +class DataProviderActiveMembers(BaseDataProvider): + @classmethod + def get_display_name(cls): + return _("Active members") + + @classmethod + def get_description(cls): + return _( + "Active in the sense of their membership: paused and investing members are not active, but frozen members are active" + ) + + @classmethod + def get_queryset(cls, reference_time: datetime.datetime) -> QuerySet[ShareOwner]: + reference_date = reference_time.date() + return ShareOwner.objects.with_status(MemberStatus.ACTIVE, reference_date) diff --git a/tapir/statistics/services/data_providers/data_provider_active_members_with_account.py b/tapir/statistics/services/data_providers/data_provider_active_members_with_account.py new file mode 100644 index 000000000..58dfbbf4d --- /dev/null +++ b/tapir/statistics/services/data_providers/data_provider_active_members_with_account.py @@ -0,0 +1,31 @@ +import datetime + +from django.db.models import QuerySet +from django.utils.translation import gettext_lazy as _ + +from tapir.accounts.models import TapirUser +from tapir.coop.models import ShareOwner, MemberStatus +from tapir.statistics.services.data_providers.base_data_provider import BaseDataProvider + + +class DataProviderActiveMembersWithAccount(BaseDataProvider): + @classmethod + def get_display_name(cls): + return _("Active members with Tapir account") + + @classmethod + def get_description(cls): + return _( + "Same as active members, but also had an account at the given date. Some members declare themselves active when joining the coop but never come to activate their account." + ) + + @classmethod + def get_queryset(cls, reference_time: datetime.datetime) -> QuerySet[ShareOwner]: + reference_date = reference_time.date() + active_members = ShareOwner.objects.with_status( + MemberStatus.ACTIVE, reference_date + ).distinct() + active_members_with_account = TapirUser.objects.filter( + share_owner__in=active_members, date_joined__lte=reference_date + ) + return ShareOwner.objects.filter(user__in=active_members_with_account) diff --git a/tapir/statistics/views/fancy_graph/number_of_co_purchasers_view.py b/tapir/statistics/services/data_providers/data_provider_co_purchasers.py similarity index 58% rename from tapir/statistics/views/fancy_graph/number_of_co_purchasers_view.py rename to tapir/statistics/services/data_providers/data_provider_co_purchasers.py index 3f8602b66..8050416d4 100644 --- a/tapir/statistics/views/fancy_graph/number_of_co_purchasers_view.py +++ b/tapir/statistics/services/data_providers/data_provider_co_purchasers.py @@ -1,6 +1,7 @@ import datetime -from django.db.models import Q +from django.db.models import QuerySet, Q +from django.utils.translation import gettext_lazy as _ from tapir.accounts.models import TapirUser from tapir.accounts.services.co_purchaser_history_service import ( @@ -8,11 +9,22 @@ ) from tapir.coop.models import ShareOwner from tapir.coop.services.member_can_shop_service import MemberCanShopService -from tapir.statistics.views.fancy_graph.base_view import DatapointView +from tapir.statistics.services.data_providers.base_data_provider import BaseDataProvider -class NumberOfCoPurchasersAtDateView(DatapointView): - def calculate_datapoint(self, reference_time: datetime.datetime) -> int: +class DataProviderCoPurchasers(BaseDataProvider): + @classmethod + def get_display_name(cls): + return _("Co-purchasers") + + @classmethod + def get_description(cls): + return _( + "Only members who can shop are counted: members that have a co-purchaser but are not allowed to shop are not counted" + ) + + @classmethod + def get_queryset(cls, reference_time: datetime.datetime) -> QuerySet[ShareOwner]: share_owners_that_can_shop = MemberCanShopService.annotate_share_owner_queryset_with_shopping_status_at_datetime( ShareOwner.objects.all(), reference_time ).filter( @@ -31,11 +43,7 @@ def calculate_datapoint(self, reference_time: datetime.datetime) -> int: tapir_users_with_co_purchasers.values_list("id", flat=True) ) - return ( - ShareOwner.objects.filter( - Q(id__in=share_owners_that_can_shop_ids) - & Q(user__id__in=tapir_users_with_co_purchasers_ids) - ) - .distinct() - .count() + return ShareOwner.objects.filter( + Q(id__in=share_owners_that_can_shop_ids) + & Q(user__id__in=tapir_users_with_co_purchasers_ids) ) diff --git a/tapir/statistics/views/fancy_graph/number_of_exempted_members_view.py b/tapir/statistics/services/data_providers/data_provider_exempted_members.py similarity index 70% rename from tapir/statistics/views/fancy_graph/number_of_exempted_members_view.py rename to tapir/statistics/services/data_providers/data_provider_exempted_members.py index 753e6e1f4..910503159 100644 --- a/tapir/statistics/views/fancy_graph/number_of_exempted_members_view.py +++ b/tapir/statistics/services/data_providers/data_provider_exempted_members.py @@ -1,17 +1,29 @@ import datetime -from django.db.models import Q +from django.db.models import QuerySet, Q +from django.utils.translation import gettext_lazy as _ from tapir.coop.models import ShareOwner, MemberStatus from tapir.shifts.models import ShiftExemption from tapir.shifts.services.frozen_status_history_service import ( FrozenStatusHistoryService, ) -from tapir.statistics.views.fancy_graph.base_view import DatapointView +from tapir.statistics.services.data_providers.base_data_provider import BaseDataProvider -class NumberOfExemptedMembersAtDateView(DatapointView): - def calculate_datapoint(self, reference_time: datetime.datetime) -> int: +class DataProviderExemptedMembers(BaseDataProvider): + @classmethod + def get_display_name(cls): + return _("Exempted members") + + @classmethod + def get_description(cls): + return _( + "Counting only members that would work if they were not exempted: frozen and investing members with an exemption are not counted." + ) + + @classmethod + def get_queryset(cls, reference_time: datetime.datetime) -> QuerySet[ShareOwner]: reference_date = reference_time.date() active_members = ShareOwner.objects.with_status( @@ -48,4 +60,4 @@ def calculate_datapoint(self, reference_time: datetime.datetime) -> int: ]: all_criteria &= Q(id__in=id_list) - return ShareOwner.objects.filter(all_criteria).count() + return ShareOwner.objects.filter(all_criteria) diff --git a/tapir/statistics/services/data_providers/data_provider_exempted_members_that_work.py b/tapir/statistics/services/data_providers/data_provider_exempted_members_that_work.py new file mode 100644 index 000000000..25fe2ebbb --- /dev/null +++ b/tapir/statistics/services/data_providers/data_provider_exempted_members_that_work.py @@ -0,0 +1,51 @@ +import datetime + +from django.db.models import QuerySet, Q +from django.utils.translation import gettext_lazy as _ + +from tapir.coop.models import ShareOwner +from tapir.shifts.models import ShiftAttendance, ShiftExemption +from tapir.statistics.services.data_providers.base_data_provider import BaseDataProvider + + +class DataProviderExemptedMembersThatWork(BaseDataProvider): + @classmethod + def get_display_name(cls): + return _("Exempted members that work") + + @classmethod + def get_description(cls): + return _( + "Counting all exempted members (ignoring if they are frozen or investing) that actually did a shift in the past 60 days. Just registering to the shift doesn't count, the attendance must be confirmed." + ) + + @classmethod + def get_queryset(cls, reference_time: datetime.datetime) -> QuerySet[ShareOwner]: + reference_date = reference_time.date() + + exemptions = ShiftExemption.objects.active_temporal(reference_date) + members_exempted = ShareOwner.objects.filter( + user__shift_user_data__shift_exemptions__in=exemptions + ).distinct() + members_exempted_ids = list(members_exempted.values_list("id", flat=True)) + + members_that_did_a_shift_ids = cls.get_ids_of_members_that_did_a_shift_lately( + reference_time + ) + + all_criteria = Q() + for id_list in [members_exempted_ids, members_that_did_a_shift_ids]: + all_criteria &= Q(id__in=id_list) + + return ShareOwner.objects.filter(all_criteria) + + @staticmethod + def get_ids_of_members_that_did_a_shift_lately(reference_time): + return list( + ShiftAttendance.objects.filter( + state=ShiftAttendance.State.DONE, + slot__shift__start_time__gte=reference_time + - datetime.timedelta(days=60), + slot__shift__start_time__lte=reference_time, + ).values_list("user__share_owner__id", flat=True) + ) diff --git a/tapir/statistics/views/fancy_graph/number_of_flying_members_view.py b/tapir/statistics/services/data_providers/data_provider_flying_members.py similarity index 55% rename from tapir/statistics/views/fancy_graph/number_of_flying_members_view.py rename to tapir/statistics/services/data_providers/data_provider_flying_members.py index dcdbe2dab..4a0fbcf79 100644 --- a/tapir/statistics/views/fancy_graph/number_of_flying_members_view.py +++ b/tapir/statistics/services/data_providers/data_provider_flying_members.py @@ -1,19 +1,31 @@ import datetime -from django.db.models import Q +from django.db.models import QuerySet, Q +from django.utils.translation import gettext_lazy as _ -from tapir.shifts.models import ShiftAttendanceMode, ShiftUserData +from tapir.coop.models import ShareOwner +from tapir.shifts.models import ShiftUserData, ShiftAttendanceMode from tapir.shifts.services.shift_attendance_mode_service import ( ShiftAttendanceModeService, ) from tapir.shifts.services.shift_expectation_service import ShiftExpectationService -from tapir.statistics.views.fancy_graph.base_view import ( - DatapointView, +from tapir.statistics.services.data_providers.base_data_provider import BaseDataProvider +from tapir.statistics.services.data_providers.data_provider_abcd_members import ( + DataProviderAbcdMembers, ) -class NumberOfFlyingMembersAtDateView(DatapointView): - def calculate_datapoint(self, reference_time: datetime.datetime) -> int: +class DataProviderFlyingMembers(BaseDataProvider): + @classmethod + def get_display_name(cls): + return _("Flying members") + + @classmethod + def get_description(cls): + return DataProviderAbcdMembers.get_description() + + @classmethod + def get_queryset(cls, reference_time: datetime.datetime) -> QuerySet[ShareOwner]: shift_user_datas = ShiftUserData.objects.all() shift_user_datas_working = ( @@ -36,11 +48,7 @@ def calculate_datapoint(self, reference_time: datetime.datetime) -> int: shift_user_datas_flying.values_list("id", flat=True) ) - return ( - ShiftUserData.objects.filter( - Q(id__in=shift_user_datas_working_ids) - & Q(id__in=shift_user_datas_flying_ids) - ) - .distinct() - .count() + return ShareOwner.objects.filter( + Q(user__shift_user_data__id__in=shift_user_datas_working_ids) + & Q(user__shift_user_data__id__in=shift_user_datas_flying_ids) ) diff --git a/tapir/statistics/services/data_providers/data_provider_frozen_members.py b/tapir/statistics/services/data_providers/data_provider_frozen_members.py new file mode 100644 index 000000000..858fa968d --- /dev/null +++ b/tapir/statistics/services/data_providers/data_provider_frozen_members.py @@ -0,0 +1,36 @@ +import datetime + +from django.db.models import QuerySet +from django.utils.translation import gettext_lazy as _ + +from tapir.coop.models import ShareOwner, MemberStatus +from tapir.shifts.services.frozen_status_history_service import ( + FrozenStatusHistoryService, +) +from tapir.statistics.services.data_providers.base_data_provider import BaseDataProvider + + +class DataProviderFrozenMembers(BaseDataProvider): + @classmethod + def get_display_name(cls): + return _("Frozen members") + + @classmethod + def get_description(cls): + return _( + "Counted out of 'active' members: paused and investing members not counted." + ) + + @classmethod + def get_queryset(cls, reference_time: datetime.datetime) -> QuerySet[ShareOwner]: + share_owners = ShareOwner.objects.with_status( + MemberStatus.ACTIVE, reference_time + ) + + share_owners = FrozenStatusHistoryService.annotate_share_owner_queryset_with_is_frozen_at_datetime( + share_owners, reference_time + ) + + return share_owners.filter( + **{FrozenStatusHistoryService.ANNOTATION_IS_FROZEN_AT_DATE: True} + ) diff --git a/tapir/statistics/services/data_providers/data_provider_frozen_members_long_term.py b/tapir/statistics/services/data_providers/data_provider_frozen_members_long_term.py new file mode 100644 index 000000000..b551b84dc --- /dev/null +++ b/tapir/statistics/services/data_providers/data_provider_frozen_members_long_term.py @@ -0,0 +1,52 @@ +import datetime + +from django.db.models import QuerySet +from django.utils.translation import gettext_lazy as _ + +from tapir.coop.models import ShareOwner +from tapir.shifts.models import UpdateShiftUserDataLogEntry +from tapir.statistics.services.data_providers.base_data_provider import BaseDataProvider +from tapir.statistics.services.data_providers.data_provider_frozen_members import ( + DataProviderFrozenMembers, +) + + +class DataProviderFrozenMembersLongTerm(BaseDataProvider): + @classmethod + def get_display_name(cls): + return _("Long-term frozen members") + + @classmethod + def get_description(cls): + return _( + 'Members that are frozen since more than 180 days (roughly 6 month). Long-term frozen members are included in the "Frozen members" dataset' + ) + + @classmethod + def get_queryset(cls, reference_time: datetime.datetime) -> QuerySet[ShareOwner]: + share_owners_frozen = DataProviderFrozenMembers.get_queryset(reference_time) + tapir_user_frozen_ids = list( + share_owners_frozen.values_list("user__id", flat=True) + ) + + long_term_frozen_ids = [] + for tapir_user_id in tapir_user_frozen_ids: + status_change_log_entry = ( + UpdateShiftUserDataLogEntry.objects.filter( + user__id=tapir_user_id, + created_date__lte=reference_time, + new_values__is_frozen="True", + ) + .order_by("-created_date") + .first() + ) + + if not status_change_log_entry: + # could not find any log entry, we assume the member is frozen long-term + long_term_frozen_ids.append(tapir_user_id) + continue + + if (reference_time - status_change_log_entry.created_date).days > 30 * 6: + long_term_frozen_ids.append(tapir_user_id) + + return ShareOwner.objects.filter(user__id__in=long_term_frozen_ids) diff --git a/tapir/statistics/services/data_providers/data_provider_investing_members.py b/tapir/statistics/services/data_providers/data_provider_investing_members.py new file mode 100644 index 000000000..ed2503767 --- /dev/null +++ b/tapir/statistics/services/data_providers/data_provider_investing_members.py @@ -0,0 +1,23 @@ +import datetime + +from django.db.models import QuerySet +from django.utils.translation import gettext_lazy as _ + +from tapir.coop.models import ShareOwner, MemberStatus +from tapir.statistics.services.data_providers.base_data_provider import BaseDataProvider + + +class DataProviderInvestingMembers(BaseDataProvider): + @classmethod + def get_display_name(cls): + return _("Investing members") + + @classmethod + def get_description(cls): + return "" + + @classmethod + def get_queryset(cls, reference_time: datetime.datetime) -> QuerySet[ShareOwner]: + reference_date = reference_time.date() + + return ShareOwner.objects.with_status(MemberStatus.INVESTING, reference_date) diff --git a/tapir/statistics/services/data_providers/data_provider_paused_members.py b/tapir/statistics/services/data_providers/data_provider_paused_members.py new file mode 100644 index 000000000..5a0b687e7 --- /dev/null +++ b/tapir/statistics/services/data_providers/data_provider_paused_members.py @@ -0,0 +1,22 @@ +import datetime + +from django.db.models import QuerySet +from django.utils.translation import gettext_lazy as _ + +from tapir.coop.models import ShareOwner, MemberStatus +from tapir.statistics.services.data_providers.base_data_provider import BaseDataProvider + + +class DataProviderPausedMembers(BaseDataProvider): + @classmethod + def get_display_name(cls): + return _("Paused members") + + @classmethod + def get_description(cls): + return "" + + @classmethod + def get_queryset(cls, reference_time: datetime.datetime) -> QuerySet[ShareOwner]: + reference_date = reference_time.date() + return ShareOwner.objects.with_status(MemberStatus.PAUSED, reference_date) diff --git a/tapir/statistics/services/data_providers/data_provider_purchasing_members.py b/tapir/statistics/services/data_providers/data_provider_purchasing_members.py new file mode 100644 index 000000000..774c34866 --- /dev/null +++ b/tapir/statistics/services/data_providers/data_provider_purchasing_members.py @@ -0,0 +1,27 @@ +import datetime + +from django.db.models import QuerySet +from django.utils.translation import gettext_lazy as _ + +from tapir.coop.models import ShareOwner +from tapir.coop.services.member_can_shop_service import MemberCanShopService +from tapir.statistics.services.data_providers.base_data_provider import BaseDataProvider + + +class DataProviderPurchasingMembers(BaseDataProvider): + @classmethod + def get_display_name(cls): + return _("Purchasing members") + + @classmethod + def get_description(cls): + return _( + 'Members who are allowed to shop. To be allowed to shop, a member must be active (see the description for "Active members"), have a Tapir account, and not be frozen.' + ) + + @classmethod + def get_queryset(cls, reference_time: datetime.datetime) -> QuerySet[ShareOwner]: + share_owners = MemberCanShopService.annotate_share_owner_queryset_with_shopping_status_at_datetime( + ShareOwner.objects.all(), reference_time + ) + return share_owners.filter(**{MemberCanShopService.ANNOTATION_CAN_SHOP: True}) diff --git a/tapir/statistics/services/data_providers/data_provider_resignations_created.py b/tapir/statistics/services/data_providers/data_provider_resignations_created.py new file mode 100644 index 000000000..e17b493ba --- /dev/null +++ b/tapir/statistics/services/data_providers/data_provider_resignations_created.py @@ -0,0 +1,29 @@ +import datetime + +from django.db.models import QuerySet +from django.utils.translation import gettext_lazy as _ + +from tapir.coop.models import ShareOwner, MembershipResignation +from tapir.statistics.services.data_providers.base_data_provider import BaseDataProvider + + +class DataProviderResignationsCreated(BaseDataProvider): + @classmethod + def get_display_name(cls): + return _("Created resignations") + + @classmethod + def get_description(cls): + return _( + "Regardless of whether the member gifts their share or get their money back, this is relative to when the resignation is created." + ) + + @classmethod + def get_queryset(cls, reference_time: datetime.datetime) -> QuerySet[ShareOwner]: + reference_date = reference_time.date() + + resignations = MembershipResignation.objects.filter( + cancellation_date__year=reference_date.year, + cancellation_date__month=reference_date.month, + ).distinct() + return ShareOwner.objects.filter(share_owner__in=resignations) diff --git a/tapir/statistics/services/data_providers/data_provider_resignations_pending.py b/tapir/statistics/services/data_providers/data_provider_resignations_pending.py new file mode 100644 index 000000000..ae00e72ea --- /dev/null +++ b/tapir/statistics/services/data_providers/data_provider_resignations_pending.py @@ -0,0 +1,28 @@ +import datetime + +from django.db.models import QuerySet +from django.utils.translation import gettext_lazy as _ + +from tapir.coop.models import ShareOwner, MembershipResignation +from tapir.statistics.services.data_providers.base_data_provider import BaseDataProvider + + +class DataProviderResignationsPending(BaseDataProvider): + @classmethod + def get_display_name(cls): + return _("Pending resignations") + + @classmethod + def get_description(cls): + return _( + "Members who want to get their money back and are waiting for the 3 year term" + ) + + @classmethod + def get_queryset(cls, reference_time: datetime.datetime) -> QuerySet[ShareOwner]: + reference_date = reference_time.date() + + pending_resignations = MembershipResignation.objects.filter( + cancellation_date__lte=reference_date, pay_out_day__gte=reference_date + ) + return ShareOwner.objects.filter(share_owner__in=pending_resignations) diff --git a/tapir/statistics/views/fancy_graph/number_of_shift_partners_view.py b/tapir/statistics/services/data_providers/data_provider_shift_partners.py similarity index 53% rename from tapir/statistics/views/fancy_graph/number_of_shift_partners_view.py rename to tapir/statistics/services/data_providers/data_provider_shift_partners.py index efb2b61c0..39a10a817 100644 --- a/tapir/statistics/views/fancy_graph/number_of_shift_partners_view.py +++ b/tapir/statistics/services/data_providers/data_provider_shift_partners.py @@ -1,17 +1,30 @@ -from django.db.models import Q +import datetime +from django.db.models import QuerySet, Q +from django.utils.translation import gettext_lazy as _ + +from tapir.coop.models import ShareOwner from tapir.shifts.models import ShiftUserData from tapir.shifts.services.shift_expectation_service import ShiftExpectationService from tapir.shifts.services.shift_partner_history_service import ( ShiftPartnerHistoryService, ) -from tapir.statistics.views.fancy_graph.base_view import ( - DatapointView, -) +from tapir.statistics.services.data_providers.base_data_provider import BaseDataProvider -class NumberOfShiftPartnersAtDateView(DatapointView): - def calculate_datapoint(self, reference_time) -> int: +class DataProviderShiftPartners(BaseDataProvider): + @classmethod + def get_display_name(cls): + return _("Shift partners") + + @classmethod + def get_description(cls): + return _( + "Counted out of working members only: a frozen member with a shift partner is not counted" + ) + + @classmethod + def get_queryset(cls, reference_time: datetime.datetime) -> QuerySet[ShareOwner]: shift_user_datas_working = ( ShiftExpectationService.annotate_shift_user_data_queryset_with_working_status_at_datetime( ShiftUserData.objects.all(), reference_time @@ -30,11 +43,10 @@ def calculate_datapoint(self, reference_time) -> int: shift_user_datas_with_shift_partners.values_list("id", flat=True) ) - return ( - ShiftUserData.objects.filter( - Q(id__in=shift_user_datas_working_ids) - & Q(id__in=shift_user_datas_with_shift_partners_ids) - ) - .distinct() - .count() + shift_user_datas_with_shift_partners = ShiftUserData.objects.filter( + Q(id__in=shift_user_datas_working_ids) + & Q(id__in=shift_user_datas_with_shift_partners_ids) + ) + return ShareOwner.objects.filter( + user__shift_user_data__in=shift_user_datas_with_shift_partners ) diff --git a/tapir/statistics/services/data_providers/data_provider_total_members.py b/tapir/statistics/services/data_providers/data_provider_total_members.py new file mode 100644 index 000000000..3af647170 --- /dev/null +++ b/tapir/statistics/services/data_providers/data_provider_total_members.py @@ -0,0 +1,33 @@ +import datetime + +from django.db.models import QuerySet +from django.utils.translation import gettext_lazy as _ + +from tapir.coop.models import ShareOwner, MemberStatus +from tapir.statistics.services.data_providers.base_data_provider import BaseDataProvider + + +class DataProviderTotalMembers(BaseDataProvider): + @classmethod + def get_display_name(cls): + return _("Total members") + + @classmethod + def get_description(cls): + return _("Ignoring status: investing and paused members are included") + + @classmethod + def get_queryset(cls, reference_time: datetime.datetime) -> QuerySet[ShareOwner]: + reference_date = reference_time.date() + share_owner_ids = set() + for member_status in [ + MemberStatus.ACTIVE, + MemberStatus.PAUSED, + MemberStatus.INVESTING, + ]: + share_owner_ids.update( + ShareOwner.objects.with_status(member_status, reference_date) + .distinct() + .values_list("id", flat=True) + ) + return ShareOwner.objects.filter(id__in=share_owner_ids) diff --git a/tapir/statistics/services/data_providers/data_provider_working_members.py b/tapir/statistics/services/data_providers/data_provider_working_members.py new file mode 100644 index 000000000..517622686 --- /dev/null +++ b/tapir/statistics/services/data_providers/data_provider_working_members.py @@ -0,0 +1,31 @@ +import datetime + +from django.db.models import QuerySet +from django.utils.translation import gettext_lazy as _ + +from tapir.coop.models import ShareOwner +from tapir.shifts.models import ShiftUserData +from tapir.shifts.services.shift_expectation_service import ShiftExpectationService +from tapir.statistics.services.data_providers.base_data_provider import BaseDataProvider + + +class DataProviderWorkingMembers(BaseDataProvider): + @classmethod + def get_display_name(cls): + return _("Working members") + + @classmethod + def get_description(cls): + return "" + + @classmethod + def get_queryset(cls, reference_time: datetime.datetime) -> QuerySet[ShareOwner]: + queryset = ShiftExpectationService.annotate_shift_user_data_queryset_with_working_status_at_datetime( + ShiftUserData.objects.all(), reference_time + ) + + queryset = queryset.filter( + **{ShiftExpectationService.ANNOTATION_IS_WORKING_AT_DATE: True} + ) + + return ShareOwner.objects.filter(user__shift_user_data__in=queryset) diff --git a/tapir/statistics/services/dataset_export_column_builder.py b/tapir/statistics/services/dataset_export_column_builder.py new file mode 100644 index 000000000..f8dc350c4 --- /dev/null +++ b/tapir/statistics/services/dataset_export_column_builder.py @@ -0,0 +1,182 @@ +import datetime + +from tapir.coop.models import ShareOwner +from tapir.coop.services.member_can_shop_service import MemberCanShopService +from tapir.coop.services.membership_pause_service import MembershipPauseService +from tapir.shifts.models import ShiftUserData +from tapir.shifts.services.shift_attendance_mode_service import ( + ShiftAttendanceModeService, +) +from tapir.shifts.services.shift_exemption_service import ShiftExemptionService +from tapir.shifts.services.shift_expectation_service import ShiftExpectationService +from tapir.utils.user_utils import UserUtils + + +class DatasetExportColumnBuilder: + @staticmethod + def build_column_member_number(share_owner: ShareOwner, **_): + return share_owner.id + + @staticmethod + def build_column_display_name(share_owner: ShareOwner, **_): + return UserUtils.build_display_name( + share_owner, UserUtils.DISPLAY_NAME_TYPE_FULL + ) + + @staticmethod + def build_column_is_company(share_owner: ShareOwner, **_): + return share_owner.is_company + + @staticmethod + def build_column_company_name(share_owner: ShareOwner, **_): + return share_owner.company_name + + @staticmethod + def build_column_first_name(share_owner: ShareOwner, **_): + return share_owner.get_info().first_name + + @staticmethod + def build_column_last_name(share_owner: ShareOwner, **_): + return share_owner.get_info().last_name + + @staticmethod + def build_column_usage_name(share_owner: ShareOwner, **_): + return share_owner.get_info().usage_name + + @staticmethod + def build_column_pronouns(share_owner: ShareOwner, **_): + return share_owner.get_info().pronouns + + @staticmethod + def build_column_email(share_owner: ShareOwner, **_): + return share_owner.get_info().email + + @staticmethod + def build_column_phone_number(share_owner: ShareOwner, **_): + return share_owner.get_info().phone_number + + @staticmethod + def build_column_birthdate(share_owner: ShareOwner, **_): + return share_owner.get_info().birthdate + + @staticmethod + def build_column_street(share_owner: ShareOwner, **_): + return share_owner.get_info().street + + @staticmethod + def build_column_street_2(share_owner: ShareOwner, **_): + return share_owner.get_info().street_2 + + @staticmethod + def build_column_postcode(share_owner: ShareOwner, **_): + return share_owner.get_info().postcode + + @staticmethod + def build_column_city(share_owner: ShareOwner, **_): + return share_owner.get_info().city + + @staticmethod + def build_column_country(share_owner: ShareOwner, **_): + return share_owner.get_info().country + + @staticmethod + def build_column_preferred_language(share_owner: ShareOwner, **_): + return share_owner.get_info().preferred_language + + @staticmethod + def build_column_is_investing(share_owner: ShareOwner, **_): + return share_owner.is_investing + + @staticmethod + def build_column_ratenzahlung(share_owner: ShareOwner, **_): + return share_owner.ratenzahlung + + @staticmethod + def build_column_attended_welcome_session(share_owner: ShareOwner, **_): + return share_owner.attended_welcome_session + + @staticmethod + def build_column_co_purchaser(share_owner: ShareOwner, **_): + tapir_user = getattr(share_owner, "user", None) + if not tapir_user: + return "" + return share_owner.user.co_purchaser + + @staticmethod + def build_column_allows_purchase_tracking(share_owner: ShareOwner, **_): + tapir_user = getattr(share_owner, "user", None) + if not tapir_user: + return False + return share_owner.user.allows_purchase_tracking + + @staticmethod + def build_column_shift_capabilities(share_owner: ShareOwner, **_): + tapir_user = getattr(share_owner, "user", None) + if not tapir_user: + return "" + return share_owner.user.shift_user_data.capabilities + + @staticmethod + def build_column_shift_partner(share_owner: ShareOwner, **_): + tapir_user = getattr(share_owner, "user", None) + if not tapir_user: + return "" + return UserUtils.build_display_name( + share_owner.user.shift_user_data.shift_partner.user, + UserUtils.DISPLAY_NAME_TYPE_FULL, + ) + + @staticmethod + def build_column_shift_status( + share_owner: ShareOwner, reference_time: datetime.datetime + ): + tapir_user = getattr(share_owner, "user", None) + if ( + not tapir_user + or not ShiftExpectationService.is_member_expected_to_do_shifts( + share_owner.user.shift_user_data, reference_time + ) + ): + return "not working" + return ShiftAttendanceModeService.get_attendance_mode( + share_owner.user.shift_user_data, reference_time + ) + + @staticmethod + def build_column_is_working( + share_owner: ShareOwner, reference_time: datetime.datetime + ): + tapir_user = getattr(share_owner, "user", None) + if not tapir_user: + return False + return ShiftExpectationService.is_member_expected_to_do_shifts( + share_owner.user.shift_user_data, reference_time + ) + + @staticmethod + def build_column_is_exempted( + share_owner: ShareOwner, reference_time: datetime.datetime + ): + tapir_user = getattr(share_owner, "user", None) + if not tapir_user: + return False + + queryset = ShiftExemptionService.annotate_shift_user_data_queryset_with_has_exemption_at_date( + ShiftUserData.objects.filter(id=share_owner.user.shift_user_data.id), + reference_time, + ) + return getattr( + queryset.first(), ShiftExemptionService.ANNOTATION_HAS_EXEMPTION_AT_DATE + ) + + @staticmethod + def build_column_is_paused( + share_owner: ShareOwner, reference_time: datetime.datetime + ): + return MembershipPauseService.has_active_pause(share_owner, reference_time) + + @staticmethod + def build_column_can_shop( + share_owner: ShareOwner, reference_time: datetime.datetime + ): + return MemberCanShopService.can_shop(share_owner, reference_time) diff --git a/tapir/statistics/templates/statistics/fancy_export.html b/tapir/statistics/templates/statistics/fancy_export.html new file mode 100644 index 000000000..3e1f08835 --- /dev/null +++ b/tapir/statistics/templates/statistics/fancy_export.html @@ -0,0 +1,15 @@ +{% extends "core/base.html" %} +{% load django_vite %} +{% load statistics %} +{% load core %} +{% load django_bootstrap5 %} +{% load static %} +{% load i18n %} +{% load utils %} +{% block title %} + {% translate 'Fancy Export' %} +{% endblock title %} +{% block content %} + {% vite_asset 'statistics/fancy_export_entry.tsx' %} +
Loading...
+{% endblock content %} diff --git a/tapir/statistics/tests/data_providers/__init__.py b/tapir/statistics/tests/data_providers/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tapir/statistics/tests/fancy_graph/test_number_of_abcd_members_view.py b/tapir/statistics/tests/data_providers/test_data_provider_abcd_members.py similarity index 59% rename from tapir/statistics/tests/fancy_graph/test_number_of_abcd_members_view.py rename to tapir/statistics/tests/data_providers/test_data_provider_abcd_members.py index 10e29c1e0..501113da0 100644 --- a/tapir/statistics/tests/fancy_graph/test_number_of_abcd_members_view.py +++ b/tapir/statistics/tests/data_providers/test_data_provider_abcd_members.py @@ -4,8 +4,8 @@ from tapir.coop.models import ShareOwner from tapir.shifts.models import CreateShiftAttendanceTemplateLogEntry -from tapir.statistics.views.fancy_graph.number_of_abcd_members_view import ( - NumberOfAbcdMembersAtDateView, +from tapir.statistics.services.data_providers.data_provider_abcd_members import ( + DataProviderAbcdMembers, ) from tapir.utils.tests_utils import ( TapirFactoryTestBase, @@ -15,7 +15,7 @@ ) -class TestNumberOfAbcdMembersView(TapirFactoryTestBase): +class TestDataProviderAbcdMembers(TapirFactoryTestBase): NOW = datetime.datetime(year=2023, month=4, day=1, hour=12) REFERENCE_TIME = timezone.make_aware( datetime.datetime(year=2022, month=6, day=15, hour=12) @@ -25,36 +25,31 @@ def setUp(self) -> None: super().setUp() self.NOW = mock_timezone_now(self, self.NOW) - def test_calculateDatapoint_memberIsAbcdButIsNotWorking_notCounted(self): + def test_getQueryset_memberIsAbcdButIsNotWorking_notIncluded(self): tapir_user = create_member_that_is_working(self, self.REFERENCE_TIME) ShareOwner.objects.update(is_investing=True) create_attendance_template_log_entry_in_the_past( CreateShiftAttendanceTemplateLogEntry, tapir_user, self.REFERENCE_TIME ) - result = NumberOfAbcdMembersAtDateView().calculate_datapoint( - self.REFERENCE_TIME - ) + queryset = DataProviderAbcdMembers.get_queryset(self.REFERENCE_TIME) - self.assertEqual(0, result) + self.assertEqual(0, queryset.count()) - def test_calculateDatapoint_memberIsWorkingButIsNotAbcd_notCounted(self): + def test_calculateDatapoint_memberIsWorkingButIsNotAbcd_notIncluded(self): create_member_that_is_working(self, self.REFERENCE_TIME) - result = NumberOfAbcdMembersAtDateView().calculate_datapoint( - self.REFERENCE_TIME - ) + queryset = DataProviderAbcdMembers.get_queryset(self.REFERENCE_TIME) - self.assertEqual(0, result) + self.assertEqual(0, queryset.count()) - def test_calculateDatapoint_memberIsWorkingAndAbcd_counted(self): + def test_calculateDatapoint_memberIsWorkingAndAbcd_included(self): tapir_user = create_member_that_is_working(self, self.REFERENCE_TIME) create_attendance_template_log_entry_in_the_past( CreateShiftAttendanceTemplateLogEntry, tapir_user, self.REFERENCE_TIME ) - result = NumberOfAbcdMembersAtDateView().calculate_datapoint( - self.REFERENCE_TIME - ) + queryset = DataProviderAbcdMembers.get_queryset(self.REFERENCE_TIME) - self.assertEqual(1, result) + self.assertEqual(1, queryset.count()) + self.assertIn(tapir_user.share_owner, queryset) diff --git a/tapir/statistics/tests/fancy_graph/test_number_of_active_members_view.py b/tapir/statistics/tests/data_providers/test_data_provider_active_members.py similarity index 51% rename from tapir/statistics/tests/fancy_graph/test_number_of_active_members_view.py rename to tapir/statistics/tests/data_providers/test_data_provider_active_members.py index 1598d3cf2..525fb2dca 100644 --- a/tapir/statistics/tests/fancy_graph/test_number_of_active_members_view.py +++ b/tapir/statistics/tests/data_providers/test_data_provider_active_members.py @@ -4,8 +4,8 @@ from tapir.coop.models import ShareOwnership from tapir.coop.tests.factories import ShareOwnerFactory -from tapir.statistics.views.fancy_graph.number_of_active_members_view import ( - NumberOfActiveMembersAtDateView, +from tapir.statistics.services.data_providers.data_provider_active_members import ( + DataProviderActiveMembers, ) from tapir.utils.tests_utils import ( TapirFactoryTestBase, @@ -13,7 +13,7 @@ ) -class TestNumberOfActiveMembersView(TapirFactoryTestBase): +class TestDataProviderActiveMembers(TapirFactoryTestBase): NOW = datetime.datetime(year=2023, month=4, day=1, hour=12) REFERENCE_TIME = timezone.make_aware( datetime.datetime(year=2022, month=6, day=15, hour=12) @@ -23,23 +23,20 @@ def setUp(self) -> None: super().setUp() self.NOW = mock_timezone_now(self, self.NOW) - def test_calculateDatapoint_memberIsNotActive_notCounted(self): + def test_getQueryset_memberIsNotActive_notIncluded(self): ShareOwnerFactory.create(nb_shares=0) - result = NumberOfActiveMembersAtDateView().calculate_datapoint( - self.REFERENCE_TIME - ) + queryset = DataProviderActiveMembers.get_queryset(self.REFERENCE_TIME) - self.assertEqual(0, result) + self.assertEqual(0, queryset.count()) - def test_calculateDatapoint_memberIsActive_counted(self): - ShareOwnerFactory.create(nb_shares=1, is_investing=False) + def test_getQueryset_memberIsActive_included(self): + share_owner = ShareOwnerFactory.create(nb_shares=1, is_investing=False) ShareOwnership.objects.update( start_date=self.REFERENCE_TIME.date() - datetime.timedelta(days=1) ) - result = NumberOfActiveMembersAtDateView().calculate_datapoint( - self.REFERENCE_TIME - ) + queryset = DataProviderActiveMembers.get_queryset(self.REFERENCE_TIME) - self.assertEqual(1, result) + self.assertEqual(1, queryset.count()) + self.assertIn(share_owner, queryset) diff --git a/tapir/statistics/tests/fancy_graph/test_number_of_active_members_with_account_view.py b/tapir/statistics/tests/data_providers/test_data_provider_active_members_with_account.py similarity index 59% rename from tapir/statistics/tests/fancy_graph/test_number_of_active_members_with_account_view.py rename to tapir/statistics/tests/data_providers/test_data_provider_active_members_with_account.py index 4f937d97a..3933b34da 100644 --- a/tapir/statistics/tests/fancy_graph/test_number_of_active_members_with_account_view.py +++ b/tapir/statistics/tests/data_providers/test_data_provider_active_members_with_account.py @@ -5,8 +5,8 @@ from tapir.accounts.tests.factories.factories import TapirUserFactory from tapir.coop.models import ShareOwnership from tapir.coop.tests.factories import ShareOwnerFactory -from tapir.statistics.views.fancy_graph.number_of_active_members_with_account_view import ( - NumberOfActiveMembersWithAccountAtDateView, +from tapir.statistics.services.data_providers.data_provider_active_members_with_account import ( + DataProviderActiveMembersWithAccount, ) from tapir.utils.tests_utils import ( TapirFactoryTestBase, @@ -14,7 +14,7 @@ ) -class TestNumberOfActiveMembersView(TapirFactoryTestBase): +class TestDataProviderActiveMembersWithAccount(TapirFactoryTestBase): NOW = datetime.datetime(year=2023, month=7, day=2, hour=18) REFERENCE_TIME = timezone.make_aware( datetime.datetime(year=2022, month=4, day=8, hour=10) @@ -28,7 +28,7 @@ def create_test_user(self, is_investing=False, date_joined=None): if date_joined is None: date_joined = self.REFERENCE_TIME - datetime.timedelta(days=1) - TapirUserFactory.create( + tapir_user = TapirUserFactory.create( share_owner__nb_shares=1, share_owner__is_investing=is_investing, date_joined=date_joined, @@ -36,46 +36,48 @@ def create_test_user(self, is_investing=False, date_joined=None): ShareOwnership.objects.update( start_date=self.REFERENCE_TIME.date() - datetime.timedelta(days=1) ) + return tapir_user - def test_calculateDatapoint_memberIsNotActive_notCounted(self): + def test_getQueryset_memberIsNotActive_notIncluded(self): self.create_test_user(is_investing=True) - result = NumberOfActiveMembersWithAccountAtDateView().calculate_datapoint( + queryset = DataProviderActiveMembersWithAccount.get_queryset( self.REFERENCE_TIME ) - self.assertEqual(0, result) + self.assertEqual(0, queryset.count()) - def test_calculateDatapoint_memberHasNoAccount_notCounted(self): + def test_getQueryset_memberHasNoAccount_notIncluded(self): ShareOwnerFactory.create(nb_shares=1, is_investing=False) ShareOwnership.objects.update( start_date=self.REFERENCE_TIME.date() - datetime.timedelta(days=1) ) - result = NumberOfActiveMembersWithAccountAtDateView().calculate_datapoint( + queryset = DataProviderActiveMembersWithAccount.get_queryset( self.REFERENCE_TIME ) - self.assertEqual(0, result) + self.assertEqual(0, queryset.count()) - def test_calculateDatapoint_memberCreatedAccountAfterDate_notCounted(self): + def test_getQueryset_memberCreatedAccountAfterDate_notIncluded(self): self.create_test_user( date_joined=self.REFERENCE_TIME + datetime.timedelta(days=1) ) - result = NumberOfActiveMembersWithAccountAtDateView().calculate_datapoint( + queryset = DataProviderActiveMembersWithAccount.get_queryset( self.REFERENCE_TIME ) - self.assertEqual(0, result) + self.assertEqual(0, queryset.count()) - def test_calculateDatapoint_memberCreatedAccountBeforeDate_counted(self): - self.create_test_user( + def test_getQueryset_memberCreatedAccountBeforeDate_included(self): + tapir_user = self.create_test_user( date_joined=self.REFERENCE_TIME - datetime.timedelta(days=1) ) - result = NumberOfActiveMembersWithAccountAtDateView().calculate_datapoint( + queryset = DataProviderActiveMembersWithAccount.get_queryset( self.REFERENCE_TIME ) - self.assertEqual(1, result) + self.assertEqual(1, queryset.count()) + self.assertIn(tapir_user.share_owner, queryset) diff --git a/tapir/statistics/tests/fancy_graph/test_number_of_co_purchasers_view.py b/tapir/statistics/tests/data_providers/test_data_provider_co_purchasers.py similarity index 50% rename from tapir/statistics/tests/fancy_graph/test_number_of_co_purchasers_view.py rename to tapir/statistics/tests/data_providers/test_data_provider_co_purchasers.py index c006faca7..18d053e18 100644 --- a/tapir/statistics/tests/fancy_graph/test_number_of_co_purchasers_view.py +++ b/tapir/statistics/tests/data_providers/test_data_provider_co_purchasers.py @@ -4,8 +4,8 @@ from tapir.accounts.models import TapirUser from tapir.coop.models import ShareOwner -from tapir.statistics.views.fancy_graph.number_of_co_purchasers_view import ( - NumberOfCoPurchasersAtDateView, +from tapir.statistics.services.data_providers.data_provider_co_purchasers import ( + DataProviderCoPurchasers, ) from tapir.utils.tests_utils import ( TapirFactoryTestBase, @@ -14,7 +14,7 @@ ) -class TestNumberOfCoPurchasersView(TapirFactoryTestBase): +class TestDataProviderCoPurchasers(TapirFactoryTestBase): NOW = datetime.datetime(year=2023, month=4, day=1, hour=12) REFERENCE_TIME = timezone.make_aware( datetime.datetime(year=2022, month=6, day=15, hour=12) @@ -24,35 +24,30 @@ def setUp(self) -> None: super().setUp() self.NOW = mock_timezone_now(self, self.NOW) - def test_calculateDatapoint_memberHasCoPurchaserButCannotShop_notCounted(self): + def test_getQueryset_memberHasCoPurchaserButCannotShop_notIncluded(self): create_member_that_can_shop(self, self.REFERENCE_TIME) ShareOwner.objects.update(is_investing=True) TapirUser.objects.update(co_purchaser="A test co-purchaser") - result = NumberOfCoPurchasersAtDateView().calculate_datapoint( - self.REFERENCE_TIME - ) + queryset = DataProviderCoPurchasers.get_queryset(self.REFERENCE_TIME) - self.assertEqual(0, result) + self.assertEqual(0, queryset.count()) - def test_calculateDatapoint_memberCanShopButDoesntHaveACoPurchaser_notCounted( + def test_getQueryset_memberCanShopButDoesntHaveACoPurchaser_notIncluded( self, ): create_member_that_can_shop(self, self.REFERENCE_TIME) TapirUser.objects.update(co_purchaser="") - result = NumberOfCoPurchasersAtDateView().calculate_datapoint( - self.REFERENCE_TIME - ) + queryset = DataProviderCoPurchasers.get_queryset(self.REFERENCE_TIME) - self.assertEqual(0, result) + self.assertEqual(0, queryset.count()) - def test_calculateDatapoint_memberIsWorkingAndHasCoPurchaser_counted(self): - create_member_that_can_shop(self, self.REFERENCE_TIME) + def test_getQueryset_memberIsWorkingAndHasCoPurchaser_included(self): + tapir_user = create_member_that_can_shop(self, self.REFERENCE_TIME) TapirUser.objects.update(co_purchaser="A test co-purchaser") - result = NumberOfCoPurchasersAtDateView().calculate_datapoint( - self.REFERENCE_TIME - ) + queryset = DataProviderCoPurchasers.get_queryset(self.REFERENCE_TIME) - self.assertEqual(1, result) + self.assertEqual(1, queryset.count()) + self.assertIn(tapir_user.share_owner, queryset) diff --git a/tapir/statistics/tests/fancy_graph/test_number_of_exempted_members_view.py b/tapir/statistics/tests/data_providers/test_data_provider_exempted_members.py similarity index 56% rename from tapir/statistics/tests/fancy_graph/test_number_of_exempted_members_view.py rename to tapir/statistics/tests/data_providers/test_data_provider_exempted_members.py index 39b838eff..fcb7d74e7 100644 --- a/tapir/statistics/tests/fancy_graph/test_number_of_exempted_members_view.py +++ b/tapir/statistics/tests/data_providers/test_data_provider_exempted_members.py @@ -5,8 +5,8 @@ from tapir.accounts.models import TapirUser from tapir.coop.models import ShareOwner from tapir.shifts.models import ShiftExemption, ShiftUserData -from tapir.statistics.views.fancy_graph.number_of_exempted_members_view import ( - NumberOfExemptedMembersAtDateView, +from tapir.statistics.services.data_providers.data_provider_exempted_members import ( + DataProviderExemptedMembers, ) from tapir.utils.tests_utils import ( TapirFactoryTestBase, @@ -15,7 +15,7 @@ ) -class TestNumberOfExemptedMembersAtDateView(TapirFactoryTestBase): +class TestDataProviderExemptedMembersAtDate(TapirFactoryTestBase): NOW = datetime.datetime(year=2022, month=7, day=1, hour=12) REFERENCE_TIME = timezone.make_aware( datetime.datetime(year=2023, month=8, day=15, hour=18) @@ -32,49 +32,45 @@ def create_member_where_the_only_reason_for_not_working_is_an_exemption(self): end_date=self.REFERENCE_TIME.date() + datetime.timedelta(days=1), shift_user_data=tapir_user.shift_user_data, ) + return tapir_user - def test_calculateDatapoint_exemptedMemberThatWouldWorkOtherwise_counted(self): - self.create_member_where_the_only_reason_for_not_working_is_an_exemption() - - result = NumberOfExemptedMembersAtDateView().calculate_datapoint( - self.REFERENCE_TIME + def test_getQueryset_exemptedMemberThatWouldWorkOtherwise_included(self): + tapir_user = ( + self.create_member_where_the_only_reason_for_not_working_is_an_exemption() ) - self.assertEqual(1, result) + queryset = DataProviderExemptedMembers.get_queryset(self.REFERENCE_TIME) - def test_calculateDatapoint_memberHasExemptionButIsNotActive_notCounted(self): + self.assertEqual(1, queryset.count()) + self.assertIn(tapir_user.share_owner, queryset) + + def test_getQueryset_memberHasExemptionButIsNotActive_notIncluded(self): self.create_member_where_the_only_reason_for_not_working_is_an_exemption() ShareOwner.objects.update(is_investing=True) - result = NumberOfExemptedMembersAtDateView().calculate_datapoint( - self.REFERENCE_TIME - ) + queryset = DataProviderExemptedMembers.get_queryset(self.REFERENCE_TIME) - self.assertEqual(0, result) + self.assertEqual(0, queryset.count()) - def test_calculateDatapoint_memberHasExemptionButIsFrozen_notCounted(self): + def test_getQueryset_memberHasExemptionButIsFrozen_notIncluded(self): self.create_member_where_the_only_reason_for_not_working_is_an_exemption() ShiftUserData.objects.update(is_frozen=True) - result = NumberOfExemptedMembersAtDateView().calculate_datapoint( - self.REFERENCE_TIME - ) + queryset = DataProviderExemptedMembers.get_queryset(self.REFERENCE_TIME) - self.assertEqual(0, result) + self.assertEqual(0, queryset.count()) - def test_calculateDatapoint_memberHasExemptionButJoinedAfterDate_notCounted(self): + def test_getQueryset_memberHasExemptionButJoinedAfterDate_notIncluded(self): self.create_member_where_the_only_reason_for_not_working_is_an_exemption() TapirUser.objects.update( date_joined=self.REFERENCE_TIME + datetime.timedelta(days=1) ) - result = NumberOfExemptedMembersAtDateView().calculate_datapoint( - self.REFERENCE_TIME - ) + queryset = DataProviderExemptedMembers.get_queryset(self.REFERENCE_TIME) - self.assertEqual(0, result) + self.assertEqual(0, queryset.count()) - def test_calculateDatapoint_memberHasExemptionThatIsNotActiveAtGivenDate_notCounted( + def test_getQueryset_memberHasExemptionThatIsNotActiveAtGivenDate_notIncluded( self, ): self.create_member_where_the_only_reason_for_not_working_is_an_exemption() @@ -83,8 +79,6 @@ def test_calculateDatapoint_memberHasExemptionThatIsNotActiveAtGivenDate_notCoun end_date=self.REFERENCE_TIME.date() + datetime.timedelta(days=2), ) - result = NumberOfExemptedMembersAtDateView().calculate_datapoint( - self.REFERENCE_TIME - ) + queryset = DataProviderExemptedMembers.get_queryset(self.REFERENCE_TIME) - self.assertEqual(0, result) + self.assertEqual(0, queryset.count()) diff --git a/tapir/statistics/tests/fancy_graph/test_number_of_exempted_members_that_work.py b/tapir/statistics/tests/data_providers/test_data_provider_exempted_members_that_work.py similarity index 68% rename from tapir/statistics/tests/fancy_graph/test_number_of_exempted_members_that_work.py rename to tapir/statistics/tests/data_providers/test_data_provider_exempted_members_that_work.py index 6b0c4ed02..0d7b3a8d0 100644 --- a/tapir/statistics/tests/fancy_graph/test_number_of_exempted_members_that_work.py +++ b/tapir/statistics/tests/data_providers/test_data_provider_exempted_members_that_work.py @@ -4,8 +4,8 @@ from tapir.shifts.models import ShiftAttendance, ShiftExemption from tapir.shifts.tests.factories import ShiftFactory -from tapir.statistics.views.fancy_graph.number_of_exempted_members_that_work_view import ( - NumberOfExemptedMembersThatWorkView, +from tapir.statistics.services.data_providers.data_provider_exempted_members_that_work import ( + DataProviderExemptedMembersThatWork, ) from tapir.utils.tests_utils import ( TapirFactoryTestBase, @@ -14,7 +14,7 @@ ) -class TestNumberOfExemptedMembersAtDateView(TapirFactoryTestBase): +class TestDataProviderExemptedMembers(TapirFactoryTestBase): NOW = datetime.datetime(year=2022, month=7, day=1, hour=12) REFERENCE_TIME = timezone.make_aware( datetime.datetime(year=2021, month=9, day=15, hour=10) @@ -24,7 +24,7 @@ def setUp(self) -> None: super().setUp() self.NOW = mock_timezone_now(self, self.NOW) - def test_calculateDatapoint_memberDidAShiftButIsNotExempted_notCounted(self): + def test_getQueryset_memberDidAShiftButIsNotExempted_notIncluded(self): tapir_user = create_member_that_is_working(self, self.REFERENCE_TIME) shift = ShiftFactory.create( start_time=self.REFERENCE_TIME - datetime.timedelta(days=1) @@ -33,13 +33,11 @@ def test_calculateDatapoint_memberDidAShiftButIsNotExempted_notCounted(self): state=ShiftAttendance.State.DONE, user=tapir_user, slot=shift.slots.first() ) - result = NumberOfExemptedMembersThatWorkView().calculate_datapoint( - self.REFERENCE_TIME - ) + queryset = DataProviderExemptedMembersThatWork.get_queryset(self.REFERENCE_TIME) - self.assertEqual(0, result) + self.assertEqual(0, queryset.count()) - def test_calculateDatapoint_memberDidAShiftAndIsExempted_counted(self): + def test_getQueryset_memberDidAShiftAndIsExempted_included(self): tapir_user = create_member_that_is_working(self, self.REFERENCE_TIME) shift = ShiftFactory.create( start_time=self.REFERENCE_TIME - datetime.timedelta(days=1) @@ -52,13 +50,12 @@ def test_calculateDatapoint_memberDidAShiftAndIsExempted_counted(self): start_date=self.REFERENCE_TIME - datetime.timedelta(days=1), ) - result = NumberOfExemptedMembersThatWorkView().calculate_datapoint( - self.REFERENCE_TIME - ) + queryset = DataProviderExemptedMembersThatWork.get_queryset(self.REFERENCE_TIME) - self.assertEqual(1, result) + self.assertEqual(1, queryset.count()) + self.assertIn(tapir_user.share_owner, queryset) - def test_calculateDatapoint_memberDidAShiftAndIsExemptedButTheShiftIsTooFarAway_counted( + def test_getQueryset_memberDidAShiftAndIsExemptedButTheShiftIsTooFarAway_notIncluded( self, ): tapir_user = create_member_that_is_working(self, self.REFERENCE_TIME) @@ -73,8 +70,6 @@ def test_calculateDatapoint_memberDidAShiftAndIsExemptedButTheShiftIsTooFarAway_ start_date=self.REFERENCE_TIME - datetime.timedelta(days=1), ) - result = NumberOfExemptedMembersThatWorkView().calculate_datapoint( - self.REFERENCE_TIME - ) + queryset = DataProviderExemptedMembersThatWork.get_queryset(self.REFERENCE_TIME) - self.assertEqual(0, result) + self.assertEqual(0, queryset.count()) diff --git a/tapir/statistics/tests/fancy_graph/test_number_of_flying_members_view.py b/tapir/statistics/tests/data_providers/test_data_provider_flying_members.py similarity index 52% rename from tapir/statistics/tests/fancy_graph/test_number_of_flying_members_view.py rename to tapir/statistics/tests/data_providers/test_data_provider_flying_members.py index 605d3700d..ee38e2942 100644 --- a/tapir/statistics/tests/fancy_graph/test_number_of_flying_members_view.py +++ b/tapir/statistics/tests/data_providers/test_data_provider_flying_members.py @@ -4,8 +4,8 @@ from tapir.accounts.tests.factories.factories import TapirUserFactory from tapir.shifts.models import CreateShiftAttendanceTemplateLogEntry -from tapir.statistics.views.fancy_graph.number_of_flying_members_view import ( - NumberOfFlyingMembersAtDateView, +from tapir.statistics.services.data_providers.data_provider_flying_members import ( + DataProviderFlyingMembers, ) from tapir.utils.tests_utils import ( TapirFactoryTestBase, @@ -15,7 +15,7 @@ ) -class TestNumberOfFlyingMembersView(TapirFactoryTestBase): +class TestDataProviderFlyingMembers(TapirFactoryTestBase): NOW = datetime.datetime(year=2023, month=4, day=1, hour=12) REFERENCE_TIME = timezone.make_aware( datetime.datetime(year=2022, month=6, day=15, hour=12) @@ -25,34 +25,29 @@ def setUp(self) -> None: super().setUp() self.NOW = mock_timezone_now(self, self.NOW) - def test_calculateDatapoint_memberIsFlyingButIsNotWorking_notCounted(self): + def test_getQueryset_memberIsFlyingButIsNotWorking_notIncluded(self): TapirUserFactory.create( date_joined=self.REFERENCE_TIME + datetime.timedelta(days=1) ) - result = NumberOfFlyingMembersAtDateView().calculate_datapoint( - self.REFERENCE_TIME - ) + queryset = DataProviderFlyingMembers.get_queryset(self.REFERENCE_TIME) - self.assertEqual(0, result) + self.assertEqual(0, queryset.count()) - def test_calculateDatapoint_memberIsWorkingButIsNotFlying_notCounted(self): + def test_getQueryset_memberIsWorkingButIsNotFlying_notIncluded(self): tapir_user = create_member_that_is_working(self, self.REFERENCE_TIME) create_attendance_template_log_entry_in_the_past( CreateShiftAttendanceTemplateLogEntry, tapir_user, self.REFERENCE_TIME ) - result = NumberOfFlyingMembersAtDateView().calculate_datapoint( - self.REFERENCE_TIME - ) + queryset = DataProviderFlyingMembers.get_queryset(self.REFERENCE_TIME) - self.assertEqual(0, result) + self.assertEqual(0, queryset.count()) - def test_calculateDatapoint_memberIsWorkingAndFlying_counted(self): - create_member_that_is_working(self, self.REFERENCE_TIME) + def test_getQueryset_memberIsWorkingAndFlying_included(self): + tapir_user = create_member_that_is_working(self, self.REFERENCE_TIME) - result = NumberOfFlyingMembersAtDateView().calculate_datapoint( - self.REFERENCE_TIME - ) + queryset = DataProviderFlyingMembers.get_queryset(self.REFERENCE_TIME) - self.assertEqual(1, result) + self.assertEqual(1, queryset.count()) + self.assertIn(tapir_user.share_owner, queryset) diff --git a/tapir/statistics/tests/fancy_graph/test_number_of_frozen_members_view.py b/tapir/statistics/tests/data_providers/test_data_provider_frozen_members.py similarity index 54% rename from tapir/statistics/tests/fancy_graph/test_number_of_frozen_members_view.py rename to tapir/statistics/tests/data_providers/test_data_provider_frozen_members.py index fa594c44d..ec0ce0764 100644 --- a/tapir/statistics/tests/fancy_graph/test_number_of_frozen_members_view.py +++ b/tapir/statistics/tests/data_providers/test_data_provider_frozen_members.py @@ -4,8 +4,8 @@ from tapir.accounts.tests.factories.factories import TapirUserFactory from tapir.shifts.models import ShiftUserData -from tapir.statistics.views.fancy_graph.number_of_frozen_members_view import ( - NumberOfFrozenMembersAtDateView, +from tapir.statistics.services.data_providers.data_provider_frozen_members import ( + DataProviderFrozenMembers, ) from tapir.utils.tests_utils import ( TapirFactoryTestBase, @@ -13,7 +13,7 @@ ) -class TestNumberOfFrozenMembersView(TapirFactoryTestBase): +class TestDataProviderFrozenMembers(TapirFactoryTestBase): NOW = datetime.datetime(year=2023, month=4, day=1, hour=12) REFERENCE_TIME = timezone.make_aware( datetime.datetime(year=2022, month=6, day=15, hour=12) @@ -23,38 +23,33 @@ def setUp(self) -> None: super().setUp() self.NOW = mock_timezone_now(self, self.NOW) - def test_calculateDatapoint_memberIsFrozenButIsNotActive_notCounted(self): + def test_getQueryset_memberIsFrozenButIsNotActive_notIncluded(self): TapirUserFactory.create(share_owner__is_investing=True) ShiftUserData.objects.update(is_frozen=True) - result = NumberOfFrozenMembersAtDateView().calculate_datapoint( - self.REFERENCE_TIME - ) + queryset = DataProviderFrozenMembers.get_queryset(self.REFERENCE_TIME) - self.assertEqual(0, result) + self.assertEqual(0, queryset.count()) - def test_calculateDatapoint_memberIsActiveButIsNotFrozen_notCounted(self): + def test_getQueryset_memberIsActiveButIsNotFrozen_notIncluded(self): TapirUserFactory.create( date_joined=self.REFERENCE_TIME - datetime.timedelta(days=1), share_owner__is_investing=False, ) ShiftUserData.objects.update(is_frozen=False) - result = NumberOfFrozenMembersAtDateView().calculate_datapoint( - self.REFERENCE_TIME - ) + queryset = DataProviderFrozenMembers.get_queryset(self.REFERENCE_TIME) - self.assertEqual(0, result) + self.assertEqual(0, queryset.count()) - def test_calculateDatapoint_memberIsActiveAndFrozen_counted(self): - TapirUserFactory.create( + def test_getQueryset_memberIsActiveAndFrozen_included(self): + tapir_user = TapirUserFactory.create( date_joined=self.REFERENCE_TIME - datetime.timedelta(days=1), share_owner__is_investing=False, ) ShiftUserData.objects.update(is_frozen=True) - result = NumberOfFrozenMembersAtDateView().calculate_datapoint( - self.REFERENCE_TIME - ) + queryset = DataProviderFrozenMembers.get_queryset(self.REFERENCE_TIME) - self.assertEqual(1, result) + self.assertEqual(1, queryset.count()) + self.assertIn(tapir_user.share_owner, queryset) diff --git a/tapir/statistics/tests/fancy_graph/test_number_of_long_term_frozen_members_view.py b/tapir/statistics/tests/data_providers/test_data_provider_frozen_members_long_term.py similarity index 67% rename from tapir/statistics/tests/fancy_graph/test_number_of_long_term_frozen_members_view.py rename to tapir/statistics/tests/data_providers/test_data_provider_frozen_members_long_term.py index 33482d5da..fbe3a5fc4 100644 --- a/tapir/statistics/tests/fancy_graph/test_number_of_long_term_frozen_members_view.py +++ b/tapir/statistics/tests/data_providers/test_data_provider_frozen_members_long_term.py @@ -5,8 +5,8 @@ from tapir.accounts.tests.factories.factories import TapirUserFactory from tapir.coop.models import ShareOwnership from tapir.shifts.models import ShiftUserData, UpdateShiftUserDataLogEntry -from tapir.statistics.views.fancy_graph.number_of_long_term_frozen_members_view import ( - NumberOfLongTermFrozenMembersAtDateView, +from tapir.statistics.services.data_providers.data_provider_frozen_members_long_term import ( + DataProviderFrozenMembersLongTerm, ) from tapir.utils.tests_utils import ( TapirFactoryTestBase, @@ -14,7 +14,7 @@ ) -class TestNumberOfLongTermFrozenMembersView(TapirFactoryTestBase): +class TestDataProviderLongTermFrozenMembers(TapirFactoryTestBase): NOW = datetime.datetime(year=2023, month=4, day=1, hour=12) REFERENCE_TIME = timezone.make_aware( datetime.datetime(year=2022, month=6, day=15, hour=12) @@ -24,19 +24,17 @@ def setUp(self) -> None: super().setUp() self.NOW = mock_timezone_now(self, self.NOW) - def test_calculateDatapoint_memberIsNotFrozen_notCounted(self): + def test_getQueryset_memberIsNotFrozen_notIncluded(self): TapirUserFactory.create( date_joined=self.REFERENCE_TIME + datetime.timedelta(days=1) ) ShiftUserData.objects.update(is_frozen=False) - result = NumberOfLongTermFrozenMembersAtDateView().calculate_datapoint( - self.REFERENCE_TIME - ) + queryset = DataProviderFrozenMembersLongTerm.get_queryset(self.REFERENCE_TIME) - self.assertEqual(0, result) + self.assertEqual(0, queryset.count()) - def test_calculateDatapoint_memberIsFrozenSinceNotLongEnough_notCounted(self): + def test_getQueryset_memberIsFrozenSinceNotLongEnough_notIncluded(self): tapir_user = TapirUserFactory.create( date_joined=self.REFERENCE_TIME - datetime.timedelta(days=1), share_owner__is_investing=False, @@ -54,13 +52,11 @@ def test_calculateDatapoint_memberIsFrozenSinceNotLongEnough_notCounted(self): log_entry.created_date = self.REFERENCE_TIME - datetime.timedelta(days=150) log_entry.save() - result = NumberOfLongTermFrozenMembersAtDateView().calculate_datapoint( - self.REFERENCE_TIME - ) + queryset = DataProviderFrozenMembersLongTerm.get_queryset(self.REFERENCE_TIME) - self.assertEqual(0, result) + self.assertEqual(0, queryset.count()) - def test_calculateDatapoint_memberIsFrozenSinceLongEnough_counted(self): + def test_getQueryset_memberIsFrozenSinceLongEnough_included(self): tapir_user = TapirUserFactory.create( date_joined=self.REFERENCE_TIME + datetime.timedelta(days=1), share_owner__is_investing=False, @@ -78,14 +74,13 @@ def test_calculateDatapoint_memberIsFrozenSinceLongEnough_counted(self): log_entry.created_date = self.REFERENCE_TIME - datetime.timedelta(days=190) log_entry.save() - result = NumberOfLongTermFrozenMembersAtDateView().calculate_datapoint( - self.REFERENCE_TIME - ) + queryset = DataProviderFrozenMembersLongTerm.get_queryset(self.REFERENCE_TIME) - self.assertEqual(1, result) + self.assertEqual(1, queryset.count()) + self.assertIn(tapir_user.share_owner, queryset) - def test_calculateDatapoint_memberIsFrozenAndHasNoLogs_counted(self): - TapirUserFactory.create( + def test_getQueryset_memberIsFrozenAndHasNoLogs_included(self): + tapir_user = TapirUserFactory.create( date_joined=self.REFERENCE_TIME + datetime.timedelta(days=1), share_owner__is_investing=False, ) @@ -94,8 +89,7 @@ def test_calculateDatapoint_memberIsFrozenAndHasNoLogs_counted(self): ) ShiftUserData.objects.update(is_frozen=True) - result = NumberOfLongTermFrozenMembersAtDateView().calculate_datapoint( - self.REFERENCE_TIME - ) + queryset = DataProviderFrozenMembersLongTerm.get_queryset(self.REFERENCE_TIME) - self.assertEqual(1, result) + self.assertEqual(1, queryset.count()) + self.assertIn(tapir_user.share_owner, queryset) diff --git a/tapir/statistics/tests/fancy_graph/test_number_of_investing_members_view.py b/tapir/statistics/tests/data_providers/test_data_provider_investing_members.py similarity index 51% rename from tapir/statistics/tests/fancy_graph/test_number_of_investing_members_view.py rename to tapir/statistics/tests/data_providers/test_data_provider_investing_members.py index 60dc5f606..18d9a1faf 100644 --- a/tapir/statistics/tests/fancy_graph/test_number_of_investing_members_view.py +++ b/tapir/statistics/tests/data_providers/test_data_provider_investing_members.py @@ -4,8 +4,8 @@ from tapir.coop.models import ShareOwnership from tapir.coop.tests.factories import ShareOwnerFactory -from tapir.statistics.views.fancy_graph.number_of_investing_members_view import ( - NumberOfInvestingMembersAtDateView, +from tapir.statistics.services.data_providers.data_provider_investing_members import ( + DataProviderInvestingMembers, ) from tapir.utils.tests_utils import ( TapirFactoryTestBase, @@ -13,7 +13,7 @@ ) -class TestNumberOfInvestingMembersView(TapirFactoryTestBase): +class TestDataProviderInvestingMembers(TapirFactoryTestBase): NOW = datetime.datetime(year=2023, month=4, day=1, hour=12) REFERENCE_TIME = timezone.make_aware( datetime.datetime(year=2022, month=6, day=15, hour=12) @@ -23,23 +23,20 @@ def setUp(self) -> None: super().setUp() self.NOW = mock_timezone_now(self, self.NOW) - def test_calculateDatapoint_memberIsNotInvesting_notCounted(self): + def test_getQueryset_memberIsNotInvesting_notIncluded(self): ShareOwnerFactory.create(is_investing=False) - result = NumberOfInvestingMembersAtDateView().calculate_datapoint( - self.REFERENCE_TIME - ) + queryset = DataProviderInvestingMembers.get_queryset(self.REFERENCE_TIME) - self.assertEqual(0, result) + self.assertEqual(0, queryset.count()) - def test_calculateDatapoint_memberIsInvesting_counted(self): - ShareOwnerFactory.create(is_investing=True) + def test_getQueryset_memberIsInvesting_included(self): + share_owner = ShareOwnerFactory.create(is_investing=True) ShareOwnership.objects.update( start_date=self.REFERENCE_TIME.date() - datetime.timedelta(days=1) ) - result = NumberOfInvestingMembersAtDateView().calculate_datapoint( - self.REFERENCE_TIME - ) + queryset = DataProviderInvestingMembers.get_queryset(self.REFERENCE_TIME) - self.assertEqual(1, result) + self.assertEqual(1, queryset.count()) + self.assertIn(share_owner, queryset) diff --git a/tapir/statistics/tests/fancy_graph/test_number_of_members_view.py b/tapir/statistics/tests/data_providers/test_data_provider_members.py similarity index 59% rename from tapir/statistics/tests/fancy_graph/test_number_of_members_view.py rename to tapir/statistics/tests/data_providers/test_data_provider_members.py index 6ef0133f9..9a81bdb2f 100644 --- a/tapir/statistics/tests/fancy_graph/test_number_of_members_view.py +++ b/tapir/statistics/tests/data_providers/test_data_provider_members.py @@ -4,8 +4,8 @@ from tapir.coop.models import MemberStatus, MembershipPause from tapir.coop.tests.factories import ShareOwnerFactory -from tapir.statistics.views.fancy_graph.number_of_members_view import ( - NumberOfMembersAtDateView, +from tapir.statistics.services.data_providers.data_provider_total_members import ( + DataProviderTotalMembers, ) from tapir.utils.tests_utils import ( TapirFactoryTestBase, @@ -13,7 +13,7 @@ ) -class TestNumberOfActiveMembersView(TapirFactoryTestBase): +class TestDataProviderActiveMembers(TapirFactoryTestBase): NOW = datetime.datetime(year=2023, month=4, day=1, hour=12) REFERENCE_TIME = timezone.make_aware( datetime.datetime(year=2022, month=6, day=15, hour=12) @@ -23,28 +23,29 @@ def setUp(self) -> None: super().setUp() self.NOW = mock_timezone_now(self, self.NOW) - def test_calculateDatapoint_memberStatusSold_notCounted(self): + def test_getQueryset_memberStatusSold_notIncluded(self): member_sold = ShareOwnerFactory.create(nb_shares=0) self.assertEqual( MemberStatus.SOLD, member_sold.get_member_status(self.REFERENCE_TIME) ) - result = NumberOfMembersAtDateView().calculate_datapoint(self.REFERENCE_TIME) + queryset = DataProviderTotalMembers.get_queryset(self.REFERENCE_TIME) - self.assertEqual(0, result) + self.assertEqual(0, queryset.count()) - def test_calculateDatapoint_memberStatusInvesting_counted(self): + def test_getQueryset_memberStatusInvesting_included(self): member_investing = ShareOwnerFactory.create(is_investing=True) self.assertEqual( MemberStatus.INVESTING, member_investing.get_member_status(self.REFERENCE_TIME), ) - result = NumberOfMembersAtDateView().calculate_datapoint(self.REFERENCE_TIME) + queryset = DataProviderTotalMembers.get_queryset(self.REFERENCE_TIME) - self.assertEqual(1, result) + self.assertEqual(1, queryset.count()) + self.assertIn(member_investing, queryset) - def test_calculateDatapoint_memberStatusPaused_counted(self): + def test_getQueryset_memberStatusPaused_included(self): member_paused = ShareOwnerFactory.create(is_investing=False) MembershipPause.objects.create( share_owner=member_paused, @@ -56,17 +57,19 @@ def test_calculateDatapoint_memberStatusPaused_counted(self): member_paused.get_member_status(self.REFERENCE_TIME), ) - result = NumberOfMembersAtDateView().calculate_datapoint(self.REFERENCE_TIME) + queryset = DataProviderTotalMembers.get_queryset(self.REFERENCE_TIME) - self.assertEqual(1, result) + self.assertEqual(1, queryset.count()) + self.assertIn(member_paused, queryset) - def test_calculateDatapoint_memberStatusActive_counted(self): + def test_getQueryset_memberStatusActive_included(self): member_active = ShareOwnerFactory.create(is_investing=False) self.assertEqual( MemberStatus.ACTIVE, member_active.get_member_status(self.REFERENCE_TIME), ) - result = NumberOfMembersAtDateView().calculate_datapoint(self.REFERENCE_TIME) + queryset = DataProviderTotalMembers.get_queryset(self.REFERENCE_TIME) - self.assertEqual(1, result) + self.assertEqual(1, queryset.count()) + self.assertIn(member_active, queryset) diff --git a/tapir/statistics/tests/fancy_graph/test_number_of_paused_members_view.py b/tapir/statistics/tests/data_providers/test_data_provider_paused_members.py similarity index 57% rename from tapir/statistics/tests/fancy_graph/test_number_of_paused_members_view.py rename to tapir/statistics/tests/data_providers/test_data_provider_paused_members.py index 6e80ac43c..4344ce812 100644 --- a/tapir/statistics/tests/fancy_graph/test_number_of_paused_members_view.py +++ b/tapir/statistics/tests/data_providers/test_data_provider_paused_members.py @@ -4,8 +4,8 @@ from tapir.coop.models import MembershipPause from tapir.coop.tests.factories import ShareOwnerFactory -from tapir.statistics.views.fancy_graph.number_of_paused_members_view import ( - NumberOfPausedMembersAtDateView, +from tapir.statistics.services.data_providers.data_provider_paused_members import ( + DataProviderPausedMembers, ) from tapir.utils.tests_utils import ( TapirFactoryTestBase, @@ -13,7 +13,7 @@ ) -class TestNumberOfPausedMembersView(TapirFactoryTestBase): +class TestDataProviderPausedMembers(TapirFactoryTestBase): NOW = datetime.datetime(year=2023, month=4, day=1, hour=12) REFERENCE_TIME = timezone.make_aware( datetime.datetime(year=2022, month=6, day=15, hour=12) @@ -23,16 +23,14 @@ def setUp(self) -> None: super().setUp() self.NOW = mock_timezone_now(self, self.NOW) - def test_calculateDatapoint_memberIsNotPaused_notCounted(self): + def test_getQueryset_memberIsNotPaused_notIncluded(self): ShareOwnerFactory.create() - result = NumberOfPausedMembersAtDateView().calculate_datapoint( - self.REFERENCE_TIME - ) + queryset = DataProviderPausedMembers.get_queryset(self.REFERENCE_TIME) - self.assertEqual(0, result) + self.assertEqual(0, queryset.count()) - def test_calculateDatapoint_memberIsPaused_counted(self): + def test_getQueryset_memberIsPaused_included(self): share_owner = ShareOwnerFactory.create(is_investing=False) MembershipPause.objects.create( share_owner=share_owner, @@ -40,8 +38,7 @@ def test_calculateDatapoint_memberIsPaused_counted(self): start_date=self.REFERENCE_TIME.date(), ) - result = NumberOfPausedMembersAtDateView().calculate_datapoint( - self.REFERENCE_TIME - ) + queryset = DataProviderPausedMembers.get_queryset(self.REFERENCE_TIME) - self.assertEqual(1, result) + self.assertEqual(1, queryset.count()) + self.assertIn(share_owner, queryset) diff --git a/tapir/statistics/tests/data_providers/test_data_provider_purchasing_members.py b/tapir/statistics/tests/data_providers/test_data_provider_purchasing_members.py new file mode 100644 index 000000000..e9714a679 --- /dev/null +++ b/tapir/statistics/tests/data_providers/test_data_provider_purchasing_members.py @@ -0,0 +1,40 @@ +import datetime + +from django.utils import timezone + +from tapir.coop.models import ShareOwner +from tapir.statistics.services.data_providers.data_provider_purchasing_members import ( + DataProviderPurchasingMembers, +) +from tapir.utils.tests_utils import ( + TapirFactoryTestBase, + mock_timezone_now, + create_member_that_can_shop, +) + + +class TestDataProviderPurchasingMembers(TapirFactoryTestBase): + NOW = datetime.datetime(year=2023, month=4, day=1, hour=12) + REFERENCE_TIME = timezone.make_aware( + datetime.datetime(year=2022, month=6, day=15, hour=12) + ) + + def setUp(self) -> None: + super().setUp() + self.NOW = mock_timezone_now(self, self.NOW) + + def test_getQueryset_memberCantShop_notIncluded(self): + create_member_that_can_shop(self, self.REFERENCE_TIME) + ShareOwner.objects.update(is_investing=True) + + queryset = DataProviderPurchasingMembers.get_queryset(self.REFERENCE_TIME) + + self.assertEqual(0, queryset.count()) + + def test_getQueryset_memberCanShop_included(self): + tapir_user = create_member_that_can_shop(self, self.REFERENCE_TIME) + + queryset = DataProviderPurchasingMembers.get_queryset(self.REFERENCE_TIME) + + self.assertEqual(1, queryset.count()) + self.assertIn(tapir_user.share_owner, queryset) diff --git a/tapir/statistics/tests/fancy_graph/test_number_of_created_resignations_view.py b/tapir/statistics/tests/data_providers/test_data_provider_resignations_created.py similarity index 61% rename from tapir/statistics/tests/fancy_graph/test_number_of_created_resignations_view.py rename to tapir/statistics/tests/data_providers/test_data_provider_resignations_created.py index 234ac15dd..be786ea74 100644 --- a/tapir/statistics/tests/fancy_graph/test_number_of_created_resignations_view.py +++ b/tapir/statistics/tests/data_providers/test_data_provider_resignations_created.py @@ -3,8 +3,8 @@ from django.utils import timezone from tapir.coop.tests.factories import MembershipResignationFactory -from tapir.statistics.views.fancy_graph.number_of_created_resignations_view import ( - NumberOfCreatedResignationsInSameMonthView, +from tapir.statistics.services.data_providers.data_provider_resignations_created import ( + DataProviderResignationsCreated, ) from tapir.utils.tests_utils import ( TapirFactoryTestBase, @@ -12,7 +12,7 @@ ) -class TestNumberOfCreatedResignationsInSameMonthView(TapirFactoryTestBase): +class TestDataProviderCreatedResignationsInSameMonth(TapirFactoryTestBase): NOW = datetime.datetime(year=2023, month=4, day=1, hour=12) REFERENCE_TIME = timezone.make_aware( datetime.datetime(year=2022, month=6, day=15, hour=12) @@ -22,19 +22,18 @@ def setUp(self) -> None: super().setUp() self.NOW = mock_timezone_now(self, self.NOW) - def test_calculateDatapoint_default_countsOnlyRelevantResignations(self): + def test_getQueryset_default_includesOnlyRelevantResignations(self): MembershipResignationFactory.create( cancellation_date=datetime.date(year=2022, month=5, day=1) ) - MembershipResignationFactory.create( + relevant_resignation = MembershipResignationFactory.create( cancellation_date=datetime.date(year=2022, month=6, day=30) ) MembershipResignationFactory.create( cancellation_date=datetime.date(year=2023, month=6, day=30) ) - result = NumberOfCreatedResignationsInSameMonthView().calculate_datapoint( - self.REFERENCE_TIME - ) + queryset = DataProviderResignationsCreated().get_queryset(self.REFERENCE_TIME) - self.assertEqual(1, result) + self.assertEqual(1, queryset.count()) + self.assertIn(relevant_resignation.share_owner, queryset) diff --git a/tapir/statistics/tests/fancy_graph/test_number_of_pending_resignations_view.py b/tapir/statistics/tests/data_providers/test_data_provider_resignations_pending.py similarity index 53% rename from tapir/statistics/tests/fancy_graph/test_number_of_pending_resignations_view.py rename to tapir/statistics/tests/data_providers/test_data_provider_resignations_pending.py index 0d5101f87..d341e186e 100644 --- a/tapir/statistics/tests/fancy_graph/test_number_of_pending_resignations_view.py +++ b/tapir/statistics/tests/data_providers/test_data_provider_resignations_pending.py @@ -3,8 +3,8 @@ from django.utils import timezone from tapir.coop.tests.factories import MembershipResignationFactory -from tapir.statistics.views.fancy_graph.number_of_pending_resignations_view import ( - NumberOfPendingResignationsAtDateView, +from tapir.statistics.services.data_providers.data_provider_resignations_pending import ( + DataProviderResignationsPending, ) from tapir.utils.tests_utils import ( TapirFactoryTestBase, @@ -12,7 +12,7 @@ ) -class TestNumberOfPendingResignationsView(TapirFactoryTestBase): +class TestDataProviderPendingResignations(TapirFactoryTestBase): NOW = datetime.datetime(year=2023, month=4, day=1, hour=12) REFERENCE_TIME = timezone.make_aware( datetime.datetime(year=2022, month=6, day=15, hour=12) @@ -22,38 +22,33 @@ def setUp(self) -> None: super().setUp() self.NOW = mock_timezone_now(self, self.NOW) - def test_calculateDatapoint_resignationIsPayedOut_notCounted(self): + def test_getQueryset_resignationIsPayedOut_notIncluded(self): MembershipResignationFactory.create( cancellation_date=datetime.date(year=2022, month=5, day=1), pay_out_day=datetime.date(year=2022, month=6, day=14), ) - result = NumberOfPendingResignationsAtDateView().calculate_datapoint( - self.REFERENCE_TIME - ) + queryset = DataProviderResignationsPending.get_queryset(self.REFERENCE_TIME) - self.assertEqual(0, result) + self.assertEqual(0, queryset.count()) - def test_calculateDatapoint_resignationIsPending_counted(self): - MembershipResignationFactory.create( + def test_getQueryset_resignationIsPending_included(self): + resignation = MembershipResignationFactory.create( cancellation_date=datetime.date(year=2022, month=5, day=1), pay_out_day=datetime.date(year=2022, month=6, day=16), ) - result = NumberOfPendingResignationsAtDateView().calculate_datapoint( - self.REFERENCE_TIME - ) + queryset = DataProviderResignationsPending.get_queryset(self.REFERENCE_TIME) - self.assertEqual(1, result) + self.assertEqual(1, queryset.count()) + self.assertIn(resignation.share_owner, queryset) - def test_calculateDatapoint_resignationIsInTheFuture_counted(self): + def test_getQueryset_resignationIsInTheFuture_included(self): MembershipResignationFactory.create( cancellation_date=datetime.date(year=2022, month=6, day=16), pay_out_day=datetime.date(year=2028, month=6, day=14), ) - result = NumberOfPendingResignationsAtDateView().calculate_datapoint( - self.REFERENCE_TIME - ) + queryset = DataProviderResignationsPending.get_queryset(self.REFERENCE_TIME) - self.assertEqual(0, result) + self.assertEqual(0, queryset.count()) diff --git a/tapir/statistics/tests/fancy_graph/test_number_of_shift_partners_view.py b/tapir/statistics/tests/data_providers/test_data_provider_shift_partners.py similarity index 61% rename from tapir/statistics/tests/fancy_graph/test_number_of_shift_partners_view.py rename to tapir/statistics/tests/data_providers/test_data_provider_shift_partners.py index afba0c8a0..dbdee34f9 100644 --- a/tapir/statistics/tests/fancy_graph/test_number_of_shift_partners_view.py +++ b/tapir/statistics/tests/data_providers/test_data_provider_shift_partners.py @@ -2,8 +2,8 @@ from django.utils import timezone -from tapir.statistics.views.fancy_graph.number_of_shift_partners_view import ( - NumberOfShiftPartnersAtDateView, +from tapir.statistics.services.data_providers.data_provider_shift_partners import ( + DataProviderShiftPartners, ) from tapir.utils.tests_utils import ( TapirFactoryTestBase, @@ -12,7 +12,7 @@ ) -class TestNumberOfShiftPartnersView(TapirFactoryTestBase): +class TestDataProviderShiftPartners(TapirFactoryTestBase): NOW = datetime.datetime(year=2023, month=4, day=1, hour=12) REFERENCE_TIME = timezone.make_aware( datetime.datetime(year=2022, month=6, day=15, hour=12) @@ -22,7 +22,7 @@ def setUp(self) -> None: super().setUp() self.NOW = mock_timezone_now(self, self.NOW) - def test_calculateDatapoint_memberHasPartnerButIsNotWorking_notCounted(self): + def test_getQueryset_memberHasPartnerButIsNotWorking_notIncluded(self): member_with_partner = create_member_that_is_working(self, self.REFERENCE_TIME) member_that_is_partner_of = create_member_that_is_working( self, self.REFERENCE_TIME @@ -35,22 +35,18 @@ def test_calculateDatapoint_memberHasPartnerButIsNotWorking_notCounted(self): member_with_partner.share_owner.is_investing = True member_with_partner.share_owner.save() - result = NumberOfShiftPartnersAtDateView().calculate_datapoint( - self.REFERENCE_TIME - ) + queryset = DataProviderShiftPartners.get_queryset(self.REFERENCE_TIME) - self.assertEqual(0, result) + self.assertEqual(0, queryset.count()) - def test_calculateDatapoint_memberIsWorkingButHasNoPartner_notCounted(self): + def test_getQueryset_memberIsWorkingButHasNoPartner_notIncluded(self): create_member_that_is_working(self, self.REFERENCE_TIME) - result = NumberOfShiftPartnersAtDateView().calculate_datapoint( - self.REFERENCE_TIME - ) + queryset = DataProviderShiftPartners.get_queryset(self.REFERENCE_TIME) - self.assertEqual(0, result) + self.assertEqual(0, queryset.count()) - def test_calculateDatapoint_memberIsWorkingAndHasAPartner_counted(self): + def test_getQueryset_memberIsWorkingAndHasAPartner_included(self): member_with_partner = create_member_that_is_working(self, self.REFERENCE_TIME) member_that_is_partner_of = create_member_that_is_working( self, self.REFERENCE_TIME @@ -60,8 +56,7 @@ def test_calculateDatapoint_memberIsWorkingAndHasAPartner_counted(self): ) member_with_partner.shift_user_data.save() - result = NumberOfShiftPartnersAtDateView().calculate_datapoint( - self.REFERENCE_TIME - ) + queryset = DataProviderShiftPartners.get_queryset(self.REFERENCE_TIME) - self.assertEqual(1, result) + self.assertEqual(1, queryset.count()) + self.assertIn(member_with_partner.share_owner, queryset) diff --git a/tapir/statistics/tests/data_providers/test_data_provider_working_members.py b/tapir/statistics/tests/data_providers/test_data_provider_working_members.py new file mode 100644 index 000000000..6ecc97852 --- /dev/null +++ b/tapir/statistics/tests/data_providers/test_data_provider_working_members.py @@ -0,0 +1,40 @@ +import datetime + +from django.utils import timezone + +from tapir.coop.models import ShareOwner +from tapir.statistics.services.data_providers.data_provider_working_members import ( + DataProviderWorkingMembers, +) +from tapir.utils.tests_utils import ( + TapirFactoryTestBase, + mock_timezone_now, + create_member_that_is_working, +) + + +class TestDataProviderWorkingMembers(TapirFactoryTestBase): + NOW = datetime.datetime(year=2023, month=4, day=1, hour=12) + REFERENCE_TIME = timezone.make_aware( + datetime.datetime(year=2022, month=6, day=15, hour=12) + ) + + def setUp(self) -> None: + super().setUp() + self.NOW = mock_timezone_now(self, self.NOW) + + def test_getQueryset_memberIsWorking_included(self): + tapir_user = create_member_that_is_working(self, self.REFERENCE_TIME) + + queryset = DataProviderWorkingMembers.get_queryset(self.REFERENCE_TIME) + + self.assertEqual(1, queryset.count()) + self.assertIn(tapir_user.share_owner, queryset) + + def test_getQueryset_memberIsNotWorking_notIncluded(self): + create_member_that_is_working(self, self.REFERENCE_TIME) + ShareOwner.objects.update(is_investing=True) + + queryset = DataProviderWorkingMembers.get_queryset(self.REFERENCE_TIME) + + self.assertEqual(0, queryset.count()) diff --git a/tapir/statistics/tests/fancy_graph/test_number_of_purchasing_members_view.py b/tapir/statistics/tests/fancy_graph/test_number_of_purchasing_members_view.py deleted file mode 100644 index 215edc641..000000000 --- a/tapir/statistics/tests/fancy_graph/test_number_of_purchasing_members_view.py +++ /dev/null @@ -1,43 +0,0 @@ -import datetime - -from django.utils import timezone - -from tapir.coop.models import ShareOwner -from tapir.statistics.views.fancy_graph.number_of_purchasing_members_view import ( - NumberOfPurchasingMembersAtDateView, -) -from tapir.utils.tests_utils import ( - TapirFactoryTestBase, - mock_timezone_now, - create_member_that_can_shop, -) - - -class TestNumberOfPurchasingMembersView(TapirFactoryTestBase): - NOW = datetime.datetime(year=2023, month=4, day=1, hour=12) - REFERENCE_TIME = timezone.make_aware( - datetime.datetime(year=2022, month=6, day=15, hour=12) - ) - - def setUp(self) -> None: - super().setUp() - self.NOW = mock_timezone_now(self, self.NOW) - - def test_calculateDatapoint_memberCantShop_notCounted(self): - create_member_that_can_shop(self, self.REFERENCE_TIME) - ShareOwner.objects.update(is_investing=True) - - result = NumberOfPurchasingMembersAtDateView().calculate_datapoint( - self.REFERENCE_TIME - ) - - self.assertEqual(0, result) - - def test_calculateDatapoint_memberCanShop_counted(self): - create_member_that_can_shop(self, self.REFERENCE_TIME) - - result = NumberOfPurchasingMembersAtDateView().calculate_datapoint( - self.REFERENCE_TIME - ) - - self.assertEqual(1, result) diff --git a/tapir/statistics/tests/fancy_graph/test_number_of_working_members_view.py b/tapir/statistics/tests/fancy_graph/test_number_of_working_members_view.py deleted file mode 100644 index ede21ad04..000000000 --- a/tapir/statistics/tests/fancy_graph/test_number_of_working_members_view.py +++ /dev/null @@ -1,43 +0,0 @@ -import datetime - -from django.utils import timezone - -from tapir.coop.models import ShareOwner -from tapir.statistics.views.fancy_graph.number_of_working_members_view import ( - NumberOfWorkingMembersAtDateView, -) -from tapir.utils.tests_utils import ( - TapirFactoryTestBase, - mock_timezone_now, - create_member_that_is_working, -) - - -class TestNumberOfWorkingMembersView(TapirFactoryTestBase): - NOW = datetime.datetime(year=2023, month=4, day=1, hour=12) - REFERENCE_TIME = timezone.make_aware( - datetime.datetime(year=2022, month=6, day=15, hour=12) - ) - - def setUp(self) -> None: - super().setUp() - self.NOW = mock_timezone_now(self, self.NOW) - - def test_calculateDatapoint_memberIsWorking_counted(self): - create_member_that_is_working(self, self.REFERENCE_TIME) - - result = NumberOfWorkingMembersAtDateView().calculate_datapoint( - self.REFERENCE_TIME - ) - - self.assertEqual(1, result) - - def test_calculateDatapoint_memberIsNotWorking_notCounted(self): - create_member_that_is_working(self, self.REFERENCE_TIME) - ShareOwner.objects.update(is_investing=True) - - result = NumberOfWorkingMembersAtDateView().calculate_datapoint( - self.REFERENCE_TIME - ) - - self.assertEqual(0, result) diff --git a/tapir/statistics/tests/tests_available_datasets_view.py b/tapir/statistics/tests/tests_available_datasets_view.py new file mode 100644 index 000000000..068db4888 --- /dev/null +++ b/tapir/statistics/tests/tests_available_datasets_view.py @@ -0,0 +1,34 @@ +from django.urls import reverse + +from tapir.utils.tests_utils import TapirFactoryTestBase + + +class TestAvailableDatasetsView(TapirFactoryTestBase): + + def test_availableDatasetsView_default_alwaysReturnsTheSameColors(self): + self.login_as_member_office_user() + + response = self.client.get(reverse("statistics:available_datasets")) + colors_first_request = [dataset["color"] for dataset in response.json()] + + response = self.client.get(reverse("statistics:available_datasets")) + colors_second_request = [dataset["color"] for dataset in response.json()] + + self.assertEqual(colors_first_request, colors_second_request) + + def test_availableDatasetsView_withColourblindnessParameter_returnsDifferentColorsAsWithoutColourblindness( + self, + ): + self.login_as_member_office_user() + + response = self.client.get(reverse("statistics:available_datasets")) + colors_without_colourblindness = [ + dataset["color"] for dataset in response.json() + ] + + response = self.client.get( + reverse("statistics:available_datasets") + "?colourblindness=Protanopia" + ) + colors_with_colourblindness = [dataset["color"] for dataset in response.json()] + + self.assertNotEqual(colors_without_colourblindness, colors_with_colourblindness) diff --git a/tapir/statistics/tests/tests_datapoint_view.py b/tapir/statistics/tests/tests_datapoint_view.py deleted file mode 100644 index 799aa3235..000000000 --- a/tapir/statistics/tests/tests_datapoint_view.py +++ /dev/null @@ -1,100 +0,0 @@ -import datetime -from unittest.mock import patch, Mock - -from django.test import RequestFactory - -from tapir.accounts.tests.factories.factories import TapirUserFactory -from tapir.statistics.models import FancyGraphCache -from tapir.statistics.views.fancy_graph.base_view import DatapointView -from tapir.utils.tests_utils import TapirFactoryTestBase, mock_timezone_now - - -class DummyDatapointView(DatapointView): - VALUES = { - datetime.date(year=2023, month=4, day=5): 10, - datetime.date(year=2023, month=4, day=1): 8, - datetime.date(year=2023, month=3, day=1): 5, - } - - def calculate_datapoint(self, reference_time: datetime.datetime) -> int: - return self.VALUES[reference_time.date()] - - -class TestDatapointView(TapirFactoryTestBase): - NOW = datetime.datetime(year=2023, month=4, day=1, hour=12) - - def setUp(self) -> None: - super().setUp() - self.NOW = mock_timezone_now(self, self.NOW) - - @patch.object(DummyDatapointView, "calculate_datapoint") - def test_getDatapoint_noCache_callsCalculate(self, mock_calculate_datapoint: Mock): - date = self.NOW - datetime.timedelta(days=5) - mock_calculate_datapoint.return_value = 2 - - result = DummyDatapointView().get_datapoint(date) - - mock_calculate_datapoint.assert_called_once_with(date) - self.assertEqual(2, result) - - @patch.object(DummyDatapointView, "calculate_datapoint") - def test_getDatapoint_hasCache_returnsCachedValue( - self, mock_calculate_datapoint: Mock - ): - date = self.NOW - datetime.timedelta(days=5) - mock_calculate_datapoint.return_value = 2 - view_name = f"{DummyDatapointView.__module__}.{DummyDatapointView.__name__}" - FancyGraphCache.objects.create(view_name=view_name, date=date.date(), value=5) - - result = DummyDatapointView().get_datapoint(date) - - mock_calculate_datapoint.assert_not_called() - self.assertEqual(5, result) - - @patch.object(DummyDatapointView, "calculate_datapoint") - def test_getDatapoint_askingForTodaysData_doesntUseCachedValue( - self, mock_calculate_datapoint: Mock - ): - mock_calculate_datapoint.return_value = 2 - view_name = f"{DummyDatapointView.__module__}.{DummyDatapointView.__name__}" - FancyGraphCache.objects.create( - view_name=view_name, date=self.NOW.date(), value=5 - ) - - result = DummyDatapointView().get_datapoint(self.NOW) - - mock_calculate_datapoint.assert_called_once_with(self.NOW) - self.assertEqual(2, result) - - def test_viewGet_notRelative_returnsDatapoint(self): - request_factory = RequestFactory() - request = request_factory.get( - "", query_params={"relative": "false", "at_date": "2023-4-1"} - ) - request.user = TapirUserFactory.create(is_in_member_office=True) - - response = DummyDatapointView.as_view()(request) - - self.assertEqual(8, response.data) - - def test_viewGet_relativeFromToday_returnsDiffFromTodayToStartOfMonth(self): - request_factory = RequestFactory() - request = request_factory.get( - "", query_params={"relative": "true", "at_date": "2023-4-5"} - ) - request.user = TapirUserFactory.create(is_in_member_office=True) - - response = DummyDatapointView.as_view()(request) - - self.assertEqual(2, response.data) - - def test_viewGet_relativeFromFirstOfMonth_returnsDiffFromLastMonth(self): - request_factory = RequestFactory() - request = request_factory.get( - "", query_params={"relative": "true", "at_date": "2023-4-1"} - ) - request.user = TapirUserFactory.create(is_in_member_office=True) - - response = DummyDatapointView.as_view()(request) - - self.assertEqual(3, response.data) diff --git a/tapir/statistics/tests/tests_dataset_export_column_builder.py b/tapir/statistics/tests/tests_dataset_export_column_builder.py new file mode 100644 index 000000000..5ddf10682 --- /dev/null +++ b/tapir/statistics/tests/tests_dataset_export_column_builder.py @@ -0,0 +1,341 @@ +import datetime + +from django.utils import timezone + +from tapir.accounts.tests.factories.factories import TapirUserFactory +from tapir.coop.models import MembershipPause +from tapir.coop.tests.factories import ShareOwnerFactory +from tapir.shifts.models import ShiftUserCapability, ShiftExemption, ShiftAttendanceMode +from tapir.statistics.services.dataset_export_column_builder import ( + DatasetExportColumnBuilder, +) +from tapir.utils.tests_utils import ( + TapirFactoryTestBase, + mock_timezone_now, + create_member_that_is_working, + create_member_that_can_shop, +) + + +class TestDatasetExportColumnBuilder(TapirFactoryTestBase): + NOW = datetime.datetime(year=2026, month=7, day=5, hour=17) + REFERENCE_TIME = datetime.datetime(year=2025, month=2, day=8, hour=15) + + def setUp(self) -> None: + super().setUp() + self.NOW = mock_timezone_now(self, self.NOW) + self.REFERENCE_TIME = timezone.make_aware(self.REFERENCE_TIME) + + def test_buildColumnMemberNumber_default_returnsMemberNumber(self): + share_owner = ShareOwnerFactory.create() + + result = DatasetExportColumnBuilder.build_column_member_number(share_owner) + + self.assertEqual(share_owner.id, result) + + def test_buildColumnDisplayName_default_returnsDisplayName(self): + share_owner = ShareOwnerFactory.create( + first_name="Bruce", last_name="Wayne", usage_name="Batman" + ) + + result = DatasetExportColumnBuilder.build_column_display_name(share_owner) + + self.assertEqual(f"Batman Wayne #{share_owner.id}", result) + + def test_buildColumnIsCompany_default_returnsIsCompany(self): + share_owner = ShareOwnerFactory.create(is_company=True) + + result = DatasetExportColumnBuilder.build_column_is_company(share_owner) + + self.assertTrue(result) + + def test_buildColumnFirstName_default_returnsFirstName(self): + share_owner = ShareOwnerFactory.create(first_name="Bruce", usage_name="Batman") + + result = DatasetExportColumnBuilder.build_column_first_name(share_owner) + + self.assertEqual("Bruce", result) + + def test_buildColumnLastName_default_returnsFirstName(self): + share_owner = ShareOwnerFactory.create(last_name="Wayne") + + result = DatasetExportColumnBuilder.build_column_last_name(share_owner) + + self.assertEqual("Wayne", result) + + def test_buildColumnUsageName_default_returnsUsageName(self): + share_owner = ShareOwnerFactory.create(first_name="Bruce", usage_name="Batman") + + result = DatasetExportColumnBuilder.build_column_usage_name(share_owner) + + self.assertEqual("Batman", result) + + def test_buildColumnPronouns_default_returnsPronouns(self): + share_owner = ShareOwnerFactory.create(pronouns="test pronouns") + + result = DatasetExportColumnBuilder.build_column_pronouns(share_owner) + + self.assertEqual("test pronouns", result) + + def test_buildColumnEmail_default_returnsEmail(self): + share_owner = ShareOwnerFactory.create(email="test email") + + result = DatasetExportColumnBuilder.build_column_email(share_owner) + + self.assertEqual("test email", result) + + def test_buildColumnPhoneNumber_default_returnsPhoneNumber(self): + share_owner = ShareOwnerFactory.create(phone_number="+4917612345678") + + result = DatasetExportColumnBuilder.build_column_phone_number(share_owner) + + self.assertEqual("+4917612345678", result) + + def test_buildColumnBirthdate_default_returnsBirthdate(self): + birthdate = datetime.date(day=22, month=12, year=1990) + share_owner = ShareOwnerFactory.create(birthdate=birthdate) + + result = DatasetExportColumnBuilder.build_column_birthdate(share_owner) + + self.assertEqual(birthdate, result) + + def test_buildColumnStreet_default_returnsStreet(self): + share_owner = ShareOwnerFactory.create(street="test street") + + result = DatasetExportColumnBuilder.build_column_street(share_owner) + + self.assertEqual("test street", result) + + def test_buildColumnStreet2_default_returnsStreet2(self): + share_owner = ShareOwnerFactory.create(street_2="test street 2") + + result = DatasetExportColumnBuilder.build_column_street_2(share_owner) + + self.assertEqual("test street 2", result) + + def test_buildColumnPostcode_default_returnsPostcode(self): + share_owner = ShareOwnerFactory.create(postcode="13347") + + result = DatasetExportColumnBuilder.build_column_postcode(share_owner) + + self.assertEqual("13347", result) + + def test_buildColumnCity_default_returnsCity(self): + share_owner = ShareOwnerFactory.create(city="test city") + + result = DatasetExportColumnBuilder.build_column_city(share_owner) + + self.assertEqual("test city", result) + + def test_buildColumnCountry_default_returnsCountry(self): + share_owner = ShareOwnerFactory.create(country="FR") + + result = DatasetExportColumnBuilder.build_column_country(share_owner) + + self.assertEqual("FR", result) + + def test_buildColumnPreferredLanguage_default_returnsPreferredLanguage(self): + share_owner = ShareOwnerFactory.create(preferred_language="DE") + + result = DatasetExportColumnBuilder.build_column_preferred_language(share_owner) + + self.assertEqual("DE", result) + + def test_buildColumnIsInvesting_default_returnsIsInvesting(self): + share_owner = ShareOwnerFactory.create(is_investing=True) + + result = DatasetExportColumnBuilder.build_column_is_investing(share_owner) + + self.assertTrue(result) + + def test_buildColumnRatenzahlung_default_returnsRatenzahlung(self): + share_owner = ShareOwnerFactory.create(ratenzahlung=True) + + result = DatasetExportColumnBuilder.build_column_ratenzahlung(share_owner) + + self.assertTrue(result) + + def test_buildColumnAttendedWelcomeSessions_default_returnsAttendedWelcomeSessions( + self, + ): + share_owner = ShareOwnerFactory.create(attended_welcome_session=True) + + result = DatasetExportColumnBuilder.build_column_attended_welcome_session( + share_owner + ) + + self.assertTrue(result) + + def test_buildColumnCoPurchaser_default_returnsCoPurchaser(self): + share_owner = TapirUserFactory.create( + co_purchaser="test co purchaser" + ).share_owner + + result = DatasetExportColumnBuilder.build_column_co_purchaser(share_owner) + + self.assertEqual("test co purchaser", result) + + def test_buildColumnCoPurchaser_noAccount_returnsEmptyString(self): + share_owner = ShareOwnerFactory.create() + + result = DatasetExportColumnBuilder.build_column_co_purchaser(share_owner) + + self.assertEqual("", result) + + def test_buildColumnAllowsPurchaseTracking_default_returnsAllowsPurchaseTracking( + self, + ): + share_owner = TapirUserFactory.create(allows_purchase_tracking=True).share_owner + + result = DatasetExportColumnBuilder.build_column_allows_purchase_tracking( + share_owner + ) + + self.assertTrue(result) + + def test_buildColumnAllowsPurchaseTracking_noAccount_returnsFalse(self): + share_owner = ShareOwnerFactory.create() + + result = DatasetExportColumnBuilder.build_column_allows_purchase_tracking( + share_owner + ) + + self.assertFalse(result) + + def test_buildColumnShiftCapabilities_default_returnsCapabilities(self): + share_owner = TapirUserFactory.create( + shift_capabilities=[ShiftUserCapability.BREAD_DELIVERY] + ).share_owner + + result = DatasetExportColumnBuilder.build_column_shift_capabilities(share_owner) + + self.assertEqual([ShiftUserCapability.BREAD_DELIVERY], result) + + def test_buildColumnShiftCapabilities_noAccount_returnsEmptyString(self): + share_owner = ShareOwnerFactory.create() + + result = DatasetExportColumnBuilder.build_column_shift_capabilities(share_owner) + + self.assertEqual("", result) + + def test_buildColumnShiftPartner_default_returnsShiftpartner(self): + share_owner = TapirUserFactory.create().share_owner + partner = TapirUserFactory.create( + first_name="Frida", last_name="Kahlo", usage_name="" + ) + share_owner.user.shift_user_data.shift_partner = partner.shift_user_data + share_owner.user.shift_user_data.save() + + result = DatasetExportColumnBuilder.build_column_shift_partner(share_owner) + + self.assertEqual(f"Frida Kahlo #{partner.get_member_number()}", result) + + def test_buildColumnShiftPartner_noAccount_returnsEmptyString(self): + share_owner = ShareOwnerFactory.create() + + result = DatasetExportColumnBuilder.build_column_shift_partner(share_owner) + + self.assertEqual("", result) + + def test_buildColumnShiftStatus_noAccount_returnsNotWorking(self): + share_owner = ShareOwnerFactory.create() + + result = DatasetExportColumnBuilder.build_column_shift_status( + share_owner, self.REFERENCE_TIME + ) + + self.assertEqual("not working", result) + + def test_buildColumnShiftStatus_memberIsExempted_returnsNotWorking(self): + share_owner = create_member_that_is_working(self, self.NOW).share_owner + ShiftExemption.objects.create( + shift_user_data=share_owner.user.shift_user_data, + description="test description", + start_date=self.REFERENCE_TIME - datetime.timedelta(days=1), + end_date=self.REFERENCE_TIME + datetime.timedelta(days=1), + ) + + result = DatasetExportColumnBuilder.build_column_shift_status( + share_owner, self.REFERENCE_TIME + ) + + self.assertEqual("not working", result) + + def test_buildColumnShiftStatus_default_returnsStatus(self): + share_owner = create_member_that_is_working( + self, self.REFERENCE_TIME + ).share_owner + + result = DatasetExportColumnBuilder.build_column_shift_status( + share_owner, self.REFERENCE_TIME + ) + + self.assertEqual(ShiftAttendanceMode.FLYING, result) + + def test_buildColumnIsWorking_noAccount_returnsFalse(self): + share_owner = ShareOwnerFactory.create() + + result = DatasetExportColumnBuilder.build_column_is_working( + share_owner, self.REFERENCE_TIME + ) + + self.assertFalse(result) + + def test_buildColumnIsWorking_default_returnsIsWorking(self): + share_owner = create_member_that_is_working( + self, self.REFERENCE_TIME + ).share_owner + + result = DatasetExportColumnBuilder.build_column_is_working( + share_owner, self.REFERENCE_TIME + ) + + self.assertTrue(result) + + def test_buildColumnIsExempted_noAccount_returnsFalse(self): + share_owner = ShareOwnerFactory.create() + + result = DatasetExportColumnBuilder.build_column_is_exempted( + share_owner, self.REFERENCE_TIME + ) + + self.assertFalse(result) + + def test_buildColumnIsExempted_default_returnsIsExempted(self): + share_owner = TapirUserFactory.create().share_owner + ShiftExemption.objects.create( + shift_user_data=share_owner.user.shift_user_data, + description="test description", + start_date=self.REFERENCE_TIME - datetime.timedelta(days=1), + end_date=self.REFERENCE_TIME + datetime.timedelta(days=1), + ) + + result = DatasetExportColumnBuilder.build_column_is_exempted( + share_owner, self.REFERENCE_TIME + ) + + self.assertTrue(result) + + def test_buildColumnIsPaused_default_returnsIsPaused(self): + share_owner = TapirUserFactory.create().share_owner + MembershipPause.objects.create( + share_owner=share_owner, + description="test description", + start_date=self.REFERENCE_TIME - datetime.timedelta(days=1), + end_date=self.REFERENCE_TIME + datetime.timedelta(days=1), + ) + + result = DatasetExportColumnBuilder.build_column_is_paused( + share_owner, self.REFERENCE_TIME + ) + + self.assertTrue(result) + + def test_buildColumnCanShop_default_returnsCanShop(self): + share_owner = create_member_that_can_shop(self, self.REFERENCE_TIME).share_owner + + result = DatasetExportColumnBuilder.build_column_can_shop( + share_owner, self.REFERENCE_TIME + ) + + self.assertTrue(result) diff --git a/tapir/statistics/tests/tests_dataset_export_view.py b/tapir/statistics/tests/tests_dataset_export_view.py new file mode 100644 index 000000000..98762c68d --- /dev/null +++ b/tapir/statistics/tests/tests_dataset_export_view.py @@ -0,0 +1,31 @@ +import datetime + +from django.urls import reverse + +from tapir.coop.tests.factories import ShareOwnerFactory +from tapir.statistics.services.data_providers.base_data_provider import BaseDataProvider +from tapir.statistics.tests.tests_dataset_graph_point_view import DummyDataProvider +from tapir.utils.tests_utils import TapirFactoryTestBase, mock_timezone_now + + +class TestDatasetExportView(TapirFactoryTestBase): + NOW = datetime.datetime(year=2023, month=4, day=1, hour=12) + + def setUp(self) -> None: + super().setUp() + self.NOW = mock_timezone_now(self, self.NOW) + for i in range(10): + ShareOwnerFactory.create(first_name=f"first_name_{i}") + + BaseDataProvider.register_data_provider(DummyDataProvider) + + def test_dataSetExportView_default_exportsRequiredColumns(self): + self.login_as_member_office_user() + + response = self.client.get( + reverse("statistics:export_dataset") + + "?at_date=2023-03-01&export_columns=first_name&dataset=DummyDataProvider" + ) + + expected_result = [{"first_name": f"first_name_{i}"} for i in range(5)] + self.assertEqual(expected_result, response.json()) diff --git a/tapir/statistics/tests/tests_dataset_graph_point_view.py b/tapir/statistics/tests/tests_dataset_graph_point_view.py new file mode 100644 index 000000000..db33e3b45 --- /dev/null +++ b/tapir/statistics/tests/tests_dataset_graph_point_view.py @@ -0,0 +1,144 @@ +import datetime +from unittest.mock import patch, Mock + +from django.db.models import QuerySet +from django.test import RequestFactory + +from tapir.accounts.tests.factories.factories import TapirUserFactory +from tapir.coop.models import ShareOwner +from tapir.coop.tests.factories import ShareOwnerFactory +from tapir.statistics.models import FancyGraphCache +from tapir.statistics.services.data_providers.base_data_provider import BaseDataProvider +from tapir.statistics.views.dataset_graph_point_view import DatasetGraphPointView +from tapir.utils.tests_utils import TapirFactoryTestBase, mock_timezone_now + + +class DummyDataProvider(BaseDataProvider): + VALUES = { + datetime.date(year=2023, month=4, day=5): 10, + datetime.date(year=2023, month=4, day=1): 8, + datetime.date(year=2023, month=3, day=1): 5, + } + + @classmethod + def get_display_name(cls): + return "Test display name" + + @classmethod + def get_description(cls): + return "Test description" + + @classmethod + def get_queryset(cls, reference_time: datetime.datetime) -> QuerySet[ShareOwner]: + if reference_time.date() == datetime.date(year=2023, month=4, day=1): + return ShareOwner.objects.filter( + id__in=ShareOwner.objects.all()[:8].values_list("id", flat=True) + ) + if reference_time.date() == datetime.date(year=2023, month=3, day=1): + return ShareOwner.objects.filter( + id__in=ShareOwner.objects.all()[:5].values_list("id", flat=True) + ) + return ShareOwner.objects.filter( + id__in=ShareOwner.objects.all()[:10].values_list("id", flat=True) + ) + + +class TestDatasetGraphPointView(TapirFactoryTestBase): + NOW = datetime.datetime(year=2023, month=4, day=1, hour=12) + + def setUp(self) -> None: + super().setUp() + self.NOW = mock_timezone_now(self, self.NOW) + for _ in range(10): + ShareOwnerFactory.create() + + BaseDataProvider.register_data_provider(DummyDataProvider) + + @patch.object(DatasetGraphPointView, "calculate_datapoint") + def test_getDatapoint_noCache_callsCalculate(self, mock_calculate_datapoint: Mock): + date = self.NOW - datetime.timedelta(days=5) + mock_calculate_datapoint.return_value = 2 + + result = DatasetGraphPointView().get_datapoint(DummyDataProvider, date) + + mock_calculate_datapoint.assert_called_once_with(DummyDataProvider, date) + self.assertEqual(2, result) + + @patch.object(DatasetGraphPointView, "calculate_datapoint") + def test_getDatapoint_hasCache_returnsCachedValue( + self, mock_calculate_datapoint: Mock + ): + date = self.NOW - datetime.timedelta(days=5) + mock_calculate_datapoint.return_value = 2 + view_name = f"{DummyDataProvider.__module__}.{DummyDataProvider.__name__}" + FancyGraphCache.objects.create( + data_provider_name=view_name, date=date.date(), value=5 + ) + + result = DatasetGraphPointView().get_datapoint(DummyDataProvider, date) + + mock_calculate_datapoint.assert_not_called() + self.assertEqual(5, result) + + @patch.object(DatasetGraphPointView, "calculate_datapoint") + def test_getDatapoint_askingForTodaysData_doesntUseCachedValue( + self, mock_calculate_datapoint: Mock + ): + mock_calculate_datapoint.return_value = 2 + view_name = f"{DummyDataProvider.__module__}.{DummyDataProvider.__name__}" + FancyGraphCache.objects.create( + data_provider_name=view_name, date=self.NOW.date(), value=5 + ) + + result = DatasetGraphPointView().get_datapoint(DummyDataProvider, self.NOW) + + mock_calculate_datapoint.assert_called_once_with(DummyDataProvider, self.NOW) + self.assertEqual(2, result) + + def test_viewGet_notRelative_returnsDatapoint(self): + request_factory = RequestFactory() + request = request_factory.get( + "", + query_params={ + "relative": "false", + "at_date": "2023-4-1", + "dataset": "DummyDataProvider", + }, + ) + request.user = TapirUserFactory.create(is_in_member_office=True) + + response = DatasetGraphPointView.as_view()(request) + + self.assertEqual(8, response.data) + + def test_viewGet_relativeFromToday_returnsDiffFromTodayToStartOfMonth(self): + request_factory = RequestFactory() + request = request_factory.get( + "", + query_params={ + "relative": "true", + "at_date": "2023-4-5", + "dataset": "DummyDataProvider", + }, + ) + request.user = TapirUserFactory.create(is_in_member_office=True) + + response = DatasetGraphPointView.as_view()(request) + + self.assertEqual(2, response.data) + + def test_viewGet_relativeFromFirstOfMonth_returnsDiffFromLastMonth(self): + request_factory = RequestFactory() + request = request_factory.get( + "", + query_params={ + "relative": "true", + "at_date": "2023-4-1", + "dataset": "DummyDataProvider", + }, + ) + request.user = TapirUserFactory.create(is_in_member_office=True) + + response = DatasetGraphPointView.as_view()(request) + + self.assertEqual(3, response.data) diff --git a/tapir/statistics/urls.py b/tapir/statistics/urls.py index 6db541221..0f894274d 100644 --- a/tapir/statistics/urls.py +++ b/tapir/statistics/urls.py @@ -1,25 +1,15 @@ from django.urls import path -import tapir.statistics.views.fancy_graph.base_view -import tapir.statistics.views.fancy_graph.number_of_abcd_members_view -import tapir.statistics.views.fancy_graph.number_of_active_members_view -import tapir.statistics.views.fancy_graph.number_of_active_members_with_account_view -import tapir.statistics.views.fancy_graph.number_of_co_purchasers_view -import tapir.statistics.views.fancy_graph.number_of_created_resignations_view -import tapir.statistics.views.fancy_graph.number_of_exempted_members_that_work_view -import tapir.statistics.views.fancy_graph.number_of_exempted_members_view -import tapir.statistics.views.fancy_graph.number_of_flying_members_view -import tapir.statistics.views.fancy_graph.number_of_frozen_members_view -import tapir.statistics.views.fancy_graph.number_of_investing_members_view -import tapir.statistics.views.fancy_graph.number_of_long_term_frozen_members_view -import tapir.statistics.views.fancy_graph.number_of_members_view -import tapir.statistics.views.fancy_graph.number_of_paused_members_view -import tapir.statistics.views.fancy_graph.number_of_pending_resignations_view -import tapir.statistics.views.fancy_graph.number_of_purchasing_members_view -import tapir.statistics.views.fancy_graph.number_of_shift_partners_view -import tapir.statistics.views.fancy_graph.number_of_working_members_view from tapir.statistics import views -from tapir.statistics.views import fancy_graph +from tapir.statistics.views.available_colourblindness_types_view import ( + AvailableColourblindnessTypes, +) +from tapir.statistics.views.available_columns_view import AvailableColumnsView +from tapir.statistics.views.available_datasets_view import AvailableDatasetsView +from tapir.statistics.views.dataset_export_view import DatasetExportView +from tapir.statistics.views.dataset_graph_point_view import DatasetGraphPointView +from tapir.statistics.views.fancy_export_view import FancyExportView +from tapir.statistics.views.fancy_graph_view import FancyGraphView app_name = "statistics" urlpatterns = [ @@ -65,92 +55,37 @@ ), path( "fancy_graph", - fancy_graph.base_view.FancyGraphView.as_view(), + FancyGraphView.as_view(), name="fancy_graph", ), path( - "number_of_members_at_date", - fancy_graph.number_of_members_view.NumberOfMembersAtDateView.as_view(), - name="number_of_members_at_date", + "fancy_export", + FancyExportView.as_view(), + name="fancy_export", ), path( - "number_of_active_members_at_date", - fancy_graph.number_of_active_members_view.NumberOfActiveMembersAtDateView.as_view(), - name="number_of_active_members_at_date", + "available_export_columns", + AvailableColumnsView.as_view(), + name="available_export_columns", ), path( - "number_of_active_members_with_account_at_date", - fancy_graph.number_of_active_members_with_account_view.NumberOfActiveMembersWithAccountAtDateView.as_view(), - name="number_of_active_members_with_account_at_date", + "available_datasets", + AvailableDatasetsView.as_view(), + name="available_datasets", ), path( - "number_of_working_members_at_date", - fancy_graph.number_of_working_members_view.NumberOfWorkingMembersAtDateView.as_view(), - name="number_of_working_members_at_date", + "export_dataset", + DatasetExportView.as_view(), + name="export_dataset", ), path( - "number_of_purchasing_members_at_date", - fancy_graph.number_of_purchasing_members_view.NumberOfPurchasingMembersAtDateView.as_view(), - name="number_of_purchasing_members_at_date", + "graph_point", + DatasetGraphPointView.as_view(), + name="graph_point", ), path( - "number_of_frozen_members_at_date", - fancy_graph.number_of_frozen_members_view.NumberOfFrozenMembersAtDateView.as_view(), - name="number_of_frozen_members_at_date", - ), - path( - "number_of_long_term_frozen_members_at_date", - fancy_graph.number_of_long_term_frozen_members_view.NumberOfLongTermFrozenMembersAtDateView.as_view(), - name="number_of_long_term_frozen_members_at_date", - ), - path( - "number_of_shift_partners_at_date", - fancy_graph.number_of_shift_partners_view.NumberOfShiftPartnersAtDateView.as_view(), - name="number_of_shift_partners_at_date", - ), - path( - "number_of_co_purchasers_at_date", - fancy_graph.number_of_co_purchasers_view.NumberOfCoPurchasersAtDateView.as_view(), - name="number_of_co_purchasers_at_date", - ), - path( - "number_of_flying_members_at_date", - fancy_graph.number_of_flying_members_view.NumberOfFlyingMembersAtDateView.as_view(), - name="number_of_flying_members_at_date", - ), - path( - "number_of_abcd_members_at_date", - fancy_graph.number_of_abcd_members_view.NumberOfAbcdMembersAtDateView.as_view(), - name="number_of_abcd_members_at_date", - ), - path( - "number_of_investing_members_at_date", - fancy_graph.number_of_investing_members_view.NumberOfInvestingMembersAtDateView.as_view(), - name="number_of_investing_members_at_date", - ), - path( - "number_of_paused_members_at_date", - fancy_graph.number_of_paused_members_view.NumberOfPausedMembersAtDateView.as_view(), - name="number_of_paused_members_at_date", - ), - path( - "number_of_pending_resignations_at_date", - fancy_graph.number_of_pending_resignations_view.NumberOfPendingResignationsAtDateView.as_view(), - name="number_of_pending_resignations_at_date", - ), - path( - "number_of_created_resignations_in_same_month", - fancy_graph.number_of_created_resignations_view.NumberOfCreatedResignationsInSameMonthView.as_view(), - name="number_of_created_resignations_in_same_month", - ), - path( - "number_of_exempted_members_at_date", - fancy_graph.number_of_exempted_members_view.NumberOfExemptedMembersAtDateView.as_view(), - name="number_of_exempted_members_at_date", - ), - path( - "number_of_exempted_members_that_work", - fancy_graph.number_of_exempted_members_that_work_view.NumberOfExemptedMembersThatWorkView.as_view(), - name="number_of_exempted_members_that_work", + "available_colourblindness_types", + AvailableColourblindnessTypes.as_view(), + name="available_colourblindness_types", ), ] diff --git a/tapir/statistics/views/available_colourblindness_types_view.py b/tapir/statistics/views/available_colourblindness_types_view.py new file mode 100644 index 000000000..182f46a83 --- /dev/null +++ b/tapir/statistics/views/available_colourblindness_types_view.py @@ -0,0 +1,36 @@ +from typing import List + +from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin +from drf_spectacular.utils import extend_schema +from rest_framework import status +from rest_framework.response import Response +from rest_framework.views import APIView + +from tapir.settings import PERMISSION_COOP_MANAGE + + +class AvailableColourblindnessTypes( + LoginRequiredMixin, PermissionRequiredMixin, APIView +): + permission_required = PERMISSION_COOP_MANAGE + + # From https://distinctipy.readthedocs.io/en/latest/api.html#module-distinctipy.colorblind + COLOURBLINDNESS_TYPES = [ + "Protanopia", + "Protanomaly", + "Deuteranopia", + "Deuteranomaly", + "Tritanopia", + "Tritanomaly", + "Achromatopsia", + "Achromatomaly", + ] + + @extend_schema( + responses={200: List[str]}, + ) + def get(self, request): + return Response( + self.COLOURBLINDNESS_TYPES, + status=status.HTTP_200_OK, + ) diff --git a/tapir/statistics/views/available_columns_view.py b/tapir/statistics/views/available_columns_view.py new file mode 100644 index 000000000..477451352 --- /dev/null +++ b/tapir/statistics/views/available_columns_view.py @@ -0,0 +1,26 @@ +from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin +from drf_spectacular.utils import extend_schema +from rest_framework import status +from rest_framework.response import Response +from rest_framework.views import APIView + +from tapir.settings import PERMISSION_COOP_MANAGE +from tapir.statistics.serializers import ColumnSerializer, DatapointExportSerializer + + +class AvailableColumnsView(LoginRequiredMixin, PermissionRequiredMixin, APIView): + permission_required = PERMISSION_COOP_MANAGE + + @extend_schema( + responses={200: ColumnSerializer(many=True)}, + ) + def get(self, request): + objects = [ + {"column_name": column_name} + for column_name in DatapointExportSerializer().get_fields().keys() + ] + + return Response( + ColumnSerializer(objects, many=True).data, + status=status.HTTP_200_OK, + ) diff --git a/tapir/statistics/views/available_datasets_view.py b/tapir/statistics/views/available_datasets_view.py new file mode 100644 index 000000000..24aad5328 --- /dev/null +++ b/tapir/statistics/views/available_datasets_view.py @@ -0,0 +1,58 @@ +from distinctipy import distinctipy +from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin +from drf_spectacular.utils import extend_schema, OpenApiParameter +from rest_framework import status +from rest_framework.response import Response +from rest_framework.views import APIView + +from tapir.settings import PERMISSION_COOP_MANAGE +from tapir.statistics.serializers import DatasetSerializer +from tapir.statistics.services.data_providers.base_data_provider import data_providers + + +class AvailableDatasetsView(LoginRequiredMixin, PermissionRequiredMixin, APIView): + permission_required = PERMISSION_COOP_MANAGE + + # Point styles from https://www.chartjs.org/docs/latest/configuration/elements.html#info + POINT_STYLES = [ + "circle", + "cross", + "crossRot", + "dash", + "line", + "rect", + "rectRounded", + "rectRot", + "star", + "triangle", + ] + + @extend_schema( + responses={200: DatasetSerializer(many=True)}, + parameters=[ + OpenApiParameter(name="colourblindness", required=True, type=str), + ], + ) + def get(self, request): + datasets = [ + { + "id": provider_id, + "display_name": provider.get_display_name(), + "description": provider.get_description(), + } + for provider_id, provider in data_providers.items() + ] + + colors = distinctipy.get_colors( + len(datasets), + rng=123456, + colorblind_type=request.query_params.get("colourblindness"), + ) + for index, dataset in enumerate(datasets): + dataset["color"] = distinctipy.get_hex(colors[index]) + dataset["point_style"] = self.POINT_STYLES[index % len(self.POINT_STYLES)] + + return Response( + DatasetSerializer(datasets, many=True).data, + status=status.HTTP_200_OK, + ) diff --git a/tapir/statistics/views/dataset_export_view.py b/tapir/statistics/views/dataset_export_view.py new file mode 100644 index 000000000..d18bc3283 --- /dev/null +++ b/tapir/statistics/views/dataset_export_view.py @@ -0,0 +1,83 @@ +import datetime +from typing import List + +from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin +from django.utils import timezone +from drf_spectacular.utils import extend_schema, OpenApiParameter +from rest_framework import status +from rest_framework.response import Response +from rest_framework.views import APIView + +from tapir.coop.models import ShareOwner +from tapir.settings import PERMISSION_COOP_MANAGE +from tapir.statistics.serializers import DatapointExportSerializer +from tapir.statistics.services.data_providers.base_data_provider import data_providers +from tapir.statistics.services.dataset_export_column_builder import ( + DatasetExportColumnBuilder, +) + + +class DatasetExportView(LoginRequiredMixin, PermissionRequiredMixin, APIView): + permission_required = PERMISSION_COOP_MANAGE + + @extend_schema( + responses={200: DatapointExportSerializer(many=True)}, + parameters=[ + OpenApiParameter(name="at_date", required=True, type=datetime.date), + OpenApiParameter(name="export_columns", required=True, type=str, many=True), + OpenApiParameter(name="dataset", required=True, type=str), + ], + ) + def get(self, request): + reference_time = self.get_reference_time(request) + export_columns = request.query_params.getlist("export_columns") + + queryset = ( + data_providers[request.query_params.get("dataset")] + .get_queryset(reference_time) + .order_by("id") + ) + + return Response( + DatapointExportSerializer( + self.build_serializer_data(queryset, export_columns, reference_time), + many=True, + ).data, + status=status.HTTP_200_OK, + ) + + def build_serializer_data( + self, queryset, export_columns: List[str], reference_time: datetime.datetime + ): + return [ + self.build_single_entry_data(share_owner, export_columns, reference_time) + for share_owner in queryset + ] + + def build_single_entry_data( + self, + share_owner: ShareOwner, + export_columns: List[str], + reference_time: datetime.datetime, + ): + return { + column_name: self.build_column_data( + share_owner, column_name, reference_time + ) + for column_name in export_columns + } + + @staticmethod + def build_column_data( + share_owner: ShareOwner, column_name: str, reference_time: datetime.datetime + ): + function_name = f"build_column_{column_name}" + return getattr(DatasetExportColumnBuilder, function_name)( + share_owner=share_owner, reference_time=reference_time + ) + + @staticmethod + def get_reference_time(request): + at_date = request.query_params.get("at_date") + reference_time = datetime.datetime.strptime(at_date, "%Y-%m-%d") + return timezone.make_aware(reference_time) diff --git a/tapir/statistics/views/fancy_graph/base_view.py b/tapir/statistics/views/dataset_graph_point_view.py similarity index 62% rename from tapir/statistics/views/fancy_graph/base_view.py rename to tapir/statistics/views/dataset_graph_point_view.py index b8cf26394..ef2da940c 100644 --- a/tapir/statistics/views/fancy_graph/base_view.py +++ b/tapir/statistics/views/dataset_graph_point_view.py @@ -1,9 +1,9 @@ import datetime -from abc import ABC, abstractmethod +from abc import ABC +from typing import Type from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin from django.utils import timezone -from django.views import generic from drf_spectacular.utils import extend_schema, OpenApiParameter from rest_framework import status from rest_framework.response import Response @@ -11,41 +11,39 @@ from tapir.settings import PERMISSION_COOP_MANAGE from tapir.statistics.models import FancyGraphCache +from tapir.statistics.services.data_providers.base_data_provider import ( + data_providers, + BaseDataProvider, +) -class FancyGraphView(LoginRequiredMixin, PermissionRequiredMixin, generic.TemplateView): +class DatasetGraphPointView(LoginRequiredMixin, PermissionRequiredMixin, APIView, ABC): permission_required = PERMISSION_COOP_MANAGE - template_name = "statistics/fancy_graph.html" - def get_context_data(self, **kwargs): - context_data = super().get_context_data(**kwargs) - - return context_data - - -class DatapointView(LoginRequiredMixin, PermissionRequiredMixin, APIView, ABC): - permission_required = PERMISSION_COOP_MANAGE - - @abstractmethod - def calculate_datapoint(self, reference_time: datetime.datetime) -> int: - pass - - def get_datapoint(self, reference_time: datetime.datetime): + @staticmethod + def calculate_datapoint( + data_provider: Type[BaseDataProvider], reference_time: datetime.datetime + ) -> int: + return data_provider.get_queryset(reference_time).distinct().count() + + def get_datapoint( + self, data_provider: Type[BaseDataProvider], reference_time: datetime.datetime + ): reference_date = reference_time.date() - view_name = f"{self.__class__.__module__}.{self.__class__.__name__}" + data_provider_name = f"{data_provider.__module__}.{data_provider.__name__}" if reference_date < timezone.now().date(): # Only use the cache for dates in the past: # someone may make changes and check the results on the graph on the same day. cached_value = FancyGraphCache.objects.filter( - view_name=view_name, date=reference_date + data_provider_name=data_provider_name, date=reference_date ).first() if cached_value: return cached_value.value - value = self.calculate_datapoint(reference_time) + value = self.calculate_datapoint(data_provider, reference_time) FancyGraphCache.objects.create( - view_name=view_name, date=reference_date, value=value + data_provider_name=data_provider_name, date=reference_date, value=value ) return value @@ -65,19 +63,24 @@ def transfer_attributes(source, target, attributes): parameters=[ OpenApiParameter(name="at_date", required=True, type=datetime.date), OpenApiParameter(name="relative", required=True, type=bool), + OpenApiParameter(name="dataset", required=True, type=str), ], ) def get(self, request): reference_time = self.get_reference_time(request) relative = request.query_params.get("relative") == "true" - result = self.get_datapoint(reference_time) + data_provider = data_providers[request.query_params.get("dataset")] + + result = self.get_datapoint(data_provider, reference_time) if relative: previous_datapoint_time = ( reference_time - datetime.timedelta(days=1) ).replace(day=1) - previous_datapoint = self.get_datapoint(previous_datapoint_time) + previous_datapoint = self.get_datapoint( + data_provider, previous_datapoint_time + ) result = result - previous_datapoint return Response( diff --git a/tapir/statistics/views/fancy_export_view.py b/tapir/statistics/views/fancy_export_view.py new file mode 100644 index 000000000..b492eeb70 --- /dev/null +++ b/tapir/statistics/views/fancy_export_view.py @@ -0,0 +1,16 @@ +from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin +from django.views import generic + +from tapir.settings import PERMISSION_COOP_MANAGE + + +class FancyExportView( + LoginRequiredMixin, PermissionRequiredMixin, generic.TemplateView +): + permission_required = PERMISSION_COOP_MANAGE + template_name = "statistics/fancy_export.html" + + def get_context_data(self, **kwargs): + context_data = super().get_context_data(**kwargs) + + return context_data diff --git a/tapir/statistics/views/fancy_graph/number_of_active_members_view.py b/tapir/statistics/views/fancy_graph/number_of_active_members_view.py deleted file mode 100644 index 640421415..000000000 --- a/tapir/statistics/views/fancy_graph/number_of_active_members_view.py +++ /dev/null @@ -1,14 +0,0 @@ -import datetime - -from tapir.coop.models import ShareOwner, MemberStatus -from tapir.statistics.views.fancy_graph.base_view import DatapointView - - -class NumberOfActiveMembersAtDateView(DatapointView): - def calculate_datapoint(self, reference_time: datetime.datetime) -> int: - reference_date = reference_time.date() - return ( - ShareOwner.objects.with_status(MemberStatus.ACTIVE, reference_date) - .distinct() - .count() - ) diff --git a/tapir/statistics/views/fancy_graph/number_of_active_members_with_account_view.py b/tapir/statistics/views/fancy_graph/number_of_active_members_with_account_view.py deleted file mode 100644 index 807c1ea81..000000000 --- a/tapir/statistics/views/fancy_graph/number_of_active_members_with_account_view.py +++ /dev/null @@ -1,17 +0,0 @@ -import datetime - -from tapir.accounts.models import TapirUser -from tapir.coop.models import ShareOwner, MemberStatus -from tapir.statistics.views.fancy_graph.base_view import DatapointView - - -class NumberOfActiveMembersWithAccountAtDateView(DatapointView): - def calculate_datapoint(self, reference_time: datetime.datetime) -> int: - reference_date = reference_time.date() - active_members = ShareOwner.objects.with_status( - MemberStatus.ACTIVE, reference_date - ).distinct() - active_members_with_account = TapirUser.objects.filter( - share_owner__in=active_members, date_joined__lte=reference_date - ) - return active_members_with_account.count() diff --git a/tapir/statistics/views/fancy_graph/number_of_created_resignations_view.py b/tapir/statistics/views/fancy_graph/number_of_created_resignations_view.py deleted file mode 100644 index 891d2b6b9..000000000 --- a/tapir/statistics/views/fancy_graph/number_of_created_resignations_view.py +++ /dev/null @@ -1,18 +0,0 @@ -import datetime - -from tapir.coop.models import MembershipResignation -from tapir.statistics.views.fancy_graph.base_view import DatapointView - - -class NumberOfCreatedResignationsInSameMonthView(DatapointView): - def calculate_datapoint(self, reference_time: datetime.datetime) -> int: - reference_date = reference_time.date() - - return ( - MembershipResignation.objects.filter( - cancellation_date__year=reference_date.year, - cancellation_date__month=reference_date.month, - ) - .distinct() - .count() - ) diff --git a/tapir/statistics/views/fancy_graph/number_of_exempted_members_that_work_view.py b/tapir/statistics/views/fancy_graph/number_of_exempted_members_that_work_view.py deleted file mode 100644 index a8ed7084a..000000000 --- a/tapir/statistics/views/fancy_graph/number_of_exempted_members_that_work_view.py +++ /dev/null @@ -1,41 +0,0 @@ -import datetime - -from django.db.models import Q - -from tapir.coop.models import ShareOwner -from tapir.shifts.models import ShiftExemption, ShiftAttendance -from tapir.statistics.views.fancy_graph.base_view import DatapointView - - -class NumberOfExemptedMembersThatWorkView(DatapointView): - def calculate_datapoint(self, reference_time: datetime.datetime) -> int: - reference_date = reference_time.date() - - exemptions = ShiftExemption.objects.active_temporal(reference_date) - members_exempted = ShareOwner.objects.filter( - user__shift_user_data__shift_exemptions__in=exemptions - ).distinct() - members_exempted_ids = list(members_exempted.values_list("id", flat=True)) - - members_that_did_a_shift_ids = self.get_ids_of_members_that_did_a_shift_lately( - reference_time - ) - - all_criteria = Q() - for id_list in [members_exempted_ids, members_that_did_a_shift_ids]: - all_criteria &= Q(id__in=id_list) - - return ShareOwner.objects.filter(all_criteria).count() - - @staticmethod - def get_ids_of_members_that_did_a_shift_lately(reference_time): - return list( - ShiftAttendance.objects.filter( - state=ShiftAttendance.State.DONE, - slot__shift__start_time__gte=reference_time - - datetime.timedelta(days=60), - slot__shift__start_time__lte=reference_time, - ) - .values_list("user__share_owner__id", flat=True) - .distinct() - ) diff --git a/tapir/statistics/views/fancy_graph/number_of_frozen_members_view.py b/tapir/statistics/views/fancy_graph/number_of_frozen_members_view.py deleted file mode 100644 index 77e8be0aa..000000000 --- a/tapir/statistics/views/fancy_graph/number_of_frozen_members_view.py +++ /dev/null @@ -1,29 +0,0 @@ -import datetime - -from tapir.coop.models import ShareOwner, MemberStatus -from tapir.settings import PERMISSION_COOP_MANAGE -from tapir.shifts.services.frozen_status_history_service import ( - FrozenStatusHistoryService, -) -from tapir.statistics.views.fancy_graph.base_view import DatapointView - - -class NumberOfFrozenMembersAtDateView(DatapointView): - permission_required = PERMISSION_COOP_MANAGE - - def calculate_datapoint(self, reference_time: datetime.datetime) -> int: - return self.get_members_frozen_at_datetime(reference_time).count() - - @staticmethod - def get_members_frozen_at_datetime(reference_time): - share_owners = ShareOwner.objects.with_status( - MemberStatus.ACTIVE, reference_time - ) - - share_owners = FrozenStatusHistoryService.annotate_share_owner_queryset_with_is_frozen_at_datetime( - share_owners, reference_time - ) - - return share_owners.filter( - **{FrozenStatusHistoryService.ANNOTATION_IS_FROZEN_AT_DATE: True} - ).distinct() diff --git a/tapir/statistics/views/fancy_graph/number_of_investing_members_view.py b/tapir/statistics/views/fancy_graph/number_of_investing_members_view.py deleted file mode 100644 index 371ad30ca..000000000 --- a/tapir/statistics/views/fancy_graph/number_of_investing_members_view.py +++ /dev/null @@ -1,15 +0,0 @@ -import datetime - -from tapir.coop.models import ShareOwner, MemberStatus -from tapir.statistics.views.fancy_graph.base_view import DatapointView - - -class NumberOfInvestingMembersAtDateView(DatapointView): - def calculate_datapoint(self, reference_time: datetime.datetime) -> int: - reference_date = reference_time.date() - - return ( - ShareOwner.objects.with_status(MemberStatus.INVESTING, reference_date) - .distinct() - .count() - ) diff --git a/tapir/statistics/views/fancy_graph/number_of_long_term_frozen_members_view.py b/tapir/statistics/views/fancy_graph/number_of_long_term_frozen_members_view.py deleted file mode 100644 index 64936d026..000000000 --- a/tapir/statistics/views/fancy_graph/number_of_long_term_frozen_members_view.py +++ /dev/null @@ -1,44 +0,0 @@ -import datetime - -from tapir.settings import PERMISSION_COOP_MANAGE -from tapir.shifts.models import UpdateShiftUserDataLogEntry -from tapir.statistics.views.fancy_graph.base_view import DatapointView -from tapir.statistics.views.fancy_graph.number_of_frozen_members_view import ( - NumberOfFrozenMembersAtDateView, -) - - -class NumberOfLongTermFrozenMembersAtDateView(DatapointView): - permission_required = PERMISSION_COOP_MANAGE - - def calculate_datapoint(self, reference_time: datetime.datetime) -> int: - share_owners_frozen = ( - NumberOfFrozenMembersAtDateView.get_members_frozen_at_datetime( - reference_time - ) - ) - tapir_user_frozen_ids = list( - share_owners_frozen.values_list("user__id", flat=True) - ) - - count = 0 - for tapir_user_id in tapir_user_frozen_ids: - status_change_log_entry = ( - UpdateShiftUserDataLogEntry.objects.filter( - user__id=tapir_user_id, - created_date__lte=reference_time, - new_values__is_frozen="True", - ) - .order_by("-created_date") - .first() - ) - - if not status_change_log_entry: - # could not find any log entry, we assume the member is frozen long-term - count += 1 - continue - - if (reference_time - status_change_log_entry.created_date).days > 30 * 6: - count += 1 - - return count diff --git a/tapir/statistics/views/fancy_graph/number_of_members_view.py b/tapir/statistics/views/fancy_graph/number_of_members_view.py deleted file mode 100644 index 5c3c6cfaf..000000000 --- a/tapir/statistics/views/fancy_graph/number_of_members_view.py +++ /dev/null @@ -1,21 +0,0 @@ -import datetime - -from tapir.coop.models import MemberStatus, ShareOwner -from tapir.statistics.views.fancy_graph.base_view import DatapointView - - -class NumberOfMembersAtDateView(DatapointView): - def calculate_datapoint(self, reference_time: datetime.datetime) -> int: - reference_date = reference_time.date() - total_count = 0 - for member_status in [ - MemberStatus.ACTIVE, - MemberStatus.PAUSED, - MemberStatus.INVESTING, - ]: - total_count += ( - ShareOwner.objects.with_status(member_status, reference_date) - .distinct() - .count() - ) - return total_count diff --git a/tapir/statistics/views/fancy_graph/number_of_paused_members_view.py b/tapir/statistics/views/fancy_graph/number_of_paused_members_view.py deleted file mode 100644 index 8ba7142cd..000000000 --- a/tapir/statistics/views/fancy_graph/number_of_paused_members_view.py +++ /dev/null @@ -1,14 +0,0 @@ -import datetime - -from tapir.coop.models import ShareOwner, MemberStatus -from tapir.statistics.views.fancy_graph.base_view import DatapointView - - -class NumberOfPausedMembersAtDateView(DatapointView): - def calculate_datapoint(self, reference_time: datetime.datetime) -> int: - reference_date = reference_time.date() - return ( - ShareOwner.objects.with_status(MemberStatus.PAUSED, reference_date) - .distinct() - .count() - ) diff --git a/tapir/statistics/views/fancy_graph/number_of_pending_resignations_view.py b/tapir/statistics/views/fancy_graph/number_of_pending_resignations_view.py deleted file mode 100644 index 1a79b00b5..000000000 --- a/tapir/statistics/views/fancy_graph/number_of_pending_resignations_view.py +++ /dev/null @@ -1,17 +0,0 @@ -import datetime - -from tapir.coop.models import MembershipResignation -from tapir.statistics.views.fancy_graph.base_view import DatapointView - - -class NumberOfPendingResignationsAtDateView(DatapointView): - def calculate_datapoint(self, reference_time: datetime.datetime) -> int: - reference_date = reference_time.date() - - return ( - MembershipResignation.objects.filter( - cancellation_date__lte=reference_date, pay_out_day__gte=reference_date - ) - .distinct() - .count() - ) diff --git a/tapir/statistics/views/fancy_graph/number_of_purchasing_members_view.py b/tapir/statistics/views/fancy_graph/number_of_purchasing_members_view.py deleted file mode 100644 index 648b2c81f..000000000 --- a/tapir/statistics/views/fancy_graph/number_of_purchasing_members_view.py +++ /dev/null @@ -1,15 +0,0 @@ -from tapir.coop.models import ShareOwner -from tapir.coop.services.member_can_shop_service import MemberCanShopService -from tapir.statistics.views.fancy_graph.base_view import DatapointView - - -class NumberOfPurchasingMembersAtDateView(DatapointView): - def calculate_datapoint(self, reference_time) -> int: - share_owners = MemberCanShopService.annotate_share_owner_queryset_with_shopping_status_at_datetime( - ShareOwner.objects.all(), reference_time - ) - return ( - share_owners.filter(**{MemberCanShopService.ANNOTATION_CAN_SHOP: True}) - .distinct() - .count() - ) diff --git a/tapir/statistics/views/fancy_graph/number_of_working_members_view.py b/tapir/statistics/views/fancy_graph/number_of_working_members_view.py deleted file mode 100644 index 36dcb3897..000000000 --- a/tapir/statistics/views/fancy_graph/number_of_working_members_view.py +++ /dev/null @@ -1,18 +0,0 @@ -import datetime - -from tapir.shifts.models import ShiftUserData -from tapir.shifts.services.shift_expectation_service import ShiftExpectationService -from tapir.statistics.views.fancy_graph.base_view import DatapointView - - -class NumberOfWorkingMembersAtDateView(DatapointView): - def calculate_datapoint(self, reference_time: datetime.datetime) -> int: - queryset = ShiftExpectationService.annotate_shift_user_data_queryset_with_working_status_at_datetime( - ShiftUserData.objects.all(), reference_time - ) - - queryset = queryset.filter( - **{ShiftExpectationService.ANNOTATION_IS_WORKING_AT_DATE: True} - ) - - return queryset.count() diff --git a/tapir/statistics/views/fancy_graph_view.py b/tapir/statistics/views/fancy_graph_view.py new file mode 100644 index 000000000..8d21583ae --- /dev/null +++ b/tapir/statistics/views/fancy_graph_view.py @@ -0,0 +1,14 @@ +from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin +from django.views import generic + +from tapir.settings import PERMISSION_COOP_MANAGE + + +class FancyGraphView(LoginRequiredMixin, PermissionRequiredMixin, generic.TemplateView): + permission_required = PERMISSION_COOP_MANAGE + template_name = "statistics/fancy_graph.html" + + def get_context_data(self, **kwargs): + context_data = super().get_context_data(**kwargs) + + return context_data diff --git a/tapir/translations/locale/de/LC_MESSAGES/django.po b/tapir/translations/locale/de/LC_MESSAGES/django.po index b4bfe45fc..f0bb823a0 100644 --- a/tapir/translations/locale/de/LC_MESSAGES/django.po +++ b/tapir/translations/locale/de/LC_MESSAGES/django.po @@ -2,12 +2,12 @@ # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. -# +# msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-01-27 11:59+0100\n" +"POT-Creation-Date: 2025-01-31 11:52+0100\n" "PO-Revision-Date: 2024-12-17 14:15+0100\n" "Last-Translator: \n" "Language-Team: \n" @@ -4520,6 +4520,160 @@ msgstr "" msgid "Statistics" msgstr "Statistiken" +#: statistics/services/data_providers/data_provider_abcd_members.py:18 +#, fuzzy +#| msgid "All members" +msgid "ABCD members" +msgstr "Alle Mitglieder" + +#: statistics/services/data_providers/data_provider_abcd_members.py:23 +msgid "Only members who work are counted: members that are exempted, paused, frozen... are not counted" +msgstr "" + +#: statistics/services/data_providers/data_provider_active_members.py:13 +msgid "Active members" +msgstr "Aktive Mitglieder" + +#: statistics/services/data_providers/data_provider_active_members.py:18 +msgid "Active in the sense of their membership: paused and investing members are not active, but frozen members are active" +msgstr "" + +#: statistics/services/data_providers/data_provider_active_members_with_account.py:14 +#, fuzzy +#| msgid "Active Members with a Tapir Account" +msgid "Active members with Tapir account" +msgstr "Aktive Mitglieder mit Tapir-Konto" + +#: statistics/services/data_providers/data_provider_active_members_with_account.py:19 +msgid "Same as active members, but also had an account at the given date. Some members declare themselves active when joining the coop but never come to activate their account." +msgstr "" + +#: statistics/services/data_providers/data_provider_co_purchasers.py:18 +#: statistics/templates/statistics/main_statistics.html:139 +msgid "Co-purchasers" +msgstr "Miteinkäufer*innen" + +#: statistics/services/data_providers/data_provider_co_purchasers.py:23 +msgid "Only members who can shop are counted: members that have a co-purchaser but are not allowed to shop are not counted" +msgstr "" + +#: statistics/services/data_providers/data_provider_exempted_members.py:17 +#, fuzzy +#| msgid "Credited member" +msgid "Exempted members" +msgstr "Empfangendes Mitglied" + +#: statistics/services/data_providers/data_provider_exempted_members.py:22 +msgid "Counting only members that would work if they were not exempted: frozen and investing members with an exemption are not counted." +msgstr "" + +#: statistics/services/data_providers/data_provider_exempted_members_that_work.py:14 +msgid "Exempted members that work" +msgstr "" + +#: statistics/services/data_providers/data_provider_exempted_members_that_work.py:19 +msgid "Counting all exempted members (ignoring if they are frozen or investing) that actually did a shift in the past 60 days. Just registering to the shift doesn't count, the attendance must be confirmed." +msgstr "" + +#: statistics/services/data_providers/data_provider_flying_members.py:21 +#, fuzzy +#| msgid "Paying member" +msgid "Flying members" +msgstr "Zahlendes Mitglied" + +#: statistics/services/data_providers/data_provider_frozen_members.py:16 +#: statistics/templates/statistics/main_statistics.html:118 +#: statistics/templates/statistics/main_statistics.html:129 +#: statistics/views/main_view.py:392 +msgid "Frozen members" +msgstr "Eingefrorene Mitglieder" + +#: statistics/services/data_providers/data_provider_frozen_members.py:21 +msgid "Counted out of 'active' members: paused and investing members not counted." +msgstr "" + +#: statistics/services/data_providers/data_provider_frozen_members_long_term.py:17 +#, fuzzy +#| msgid "Frozen members" +msgid "Long-term frozen members" +msgstr "Eingefrorene Mitglieder" + +#: statistics/services/data_providers/data_provider_frozen_members_long_term.py:22 +msgid "Members that are frozen since more than 180 days (roughly 6 month). Long-term frozen members are included in the \"Frozen members\" dataset" +msgstr "" + +#: statistics/services/data_providers/data_provider_investing_members.py:13 +#, fuzzy +#| msgid "Investing member" +msgid "Investing members" +msgstr "Investierendes Mitglied" + +#: statistics/services/data_providers/data_provider_paused_members.py:13 +#, fuzzy +#| msgid "Credited member" +msgid "Paused members" +msgstr "Empfangendes Mitglied" + +#: statistics/services/data_providers/data_provider_purchasing_members.py:14 +#: statistics/views/main_view.py:392 +msgid "Purchasing members" +msgstr "Einkaufsberechtigten Mitglieder*innen" + +#: statistics/services/data_providers/data_provider_purchasing_members.py:19 +msgid "Members who are allowed to shop. To be allowed to shop, a member must be active (see the description for \"Active members\"), have a Tapir account, and not be frozen." +msgstr "" + +#: statistics/services/data_providers/data_provider_resignations_created.py:13 +#, fuzzy +#| msgid "Membership confirmation" +msgid "Created resignations" +msgstr "Mitgliedsbestätigung" + +#: statistics/services/data_providers/data_provider_resignations_created.py:18 +msgid "Regardless of whether the member gifts their share or get their money back, this is relative to when the resignation is created." +msgstr "" + +#: statistics/services/data_providers/data_provider_resignations_pending.py:13 +#, fuzzy +#| msgid "Membership confirmation" +msgid "Pending resignations" +msgstr "Mitgliedsbestätigung" + +#: statistics/services/data_providers/data_provider_resignations_pending.py:18 +msgid "Members who want to get their money back and are waiting for the 3 year term" +msgstr "" + +#: statistics/services/data_providers/data_provider_shift_partners.py:18 +#, fuzzy +#| msgid "Shift reminder" +msgid "Shift partners" +msgstr "Schicht-Erinnerung" + +#: statistics/services/data_providers/data_provider_shift_partners.py:23 +msgid "Counted out of working members only: a frozen member with a shift partner is not counted" +msgstr "" + +#: statistics/services/data_providers/data_provider_total_members.py:13 +#, fuzzy +#| msgid "Not a member" +msgid "Total members" +msgstr "Kein Mitlied" + +#: statistics/services/data_providers/data_provider_total_members.py:17 +msgid "Ignoring status: investing and paused members are included" +msgstr "" + +#: statistics/services/data_providers/data_provider_working_members.py:15 +#: statistics/templates/statistics/main_statistics.html:88 +msgid "Working members" +msgstr "Mitarbeitende Mitglieder" + +#: statistics/templates/statistics/fancy_export.html:10 +#, fuzzy +#| msgid "Export" +msgid "Fancy Export" +msgstr "Exportieren" + #: statistics/templates/statistics/fancy_graph.html:10 msgid "Fancy graph" msgstr "" @@ -4605,10 +4759,6 @@ msgstr "" msgid "Current" msgstr "Aktuelle" -#: statistics/templates/statistics/main_statistics.html:88 -msgid "Working members" -msgstr "Mitarbeitende Mitglieder" - #: statistics/templates/statistics/main_statistics.html:92 #, python-format msgid "" @@ -4624,12 +4774,6 @@ msgstr "" "\n" " " -#: statistics/templates/statistics/main_statistics.html:118 -#: statistics/templates/statistics/main_statistics.html:129 -#: statistics/views/main_view.py:392 -msgid "Frozen members" -msgstr "Eingefrorene Mitglieder" - #: statistics/templates/statistics/main_statistics.html:122 msgid "" "\n" @@ -4643,10 +4787,6 @@ msgstr "" "\n" " " -#: statistics/templates/statistics/main_statistics.html:139 -msgid "Co-purchasers" -msgstr "Miteinkäufer*innen" - #: statistics/templates/statistics/main_statistics.html:143 msgid "" "\n" @@ -4736,10 +4876,6 @@ msgstr "Entwicklung der Gesamtausgaben pro Monat" msgid "Total spends per month" msgstr "Gesamtausgaben pro Monat" -#: statistics/views/main_view.py:392 -msgid "Purchasing members" -msgstr "Einkaufsberechtigten Mitglieder*innen" - #: statistics/views/main_view.py:410 msgid "Percentage of members with a co-purchaser relative to the number of active members" msgstr "Prozentualer Anteil der Mitglieder mit einem Miterwerber im Verhältnis zur Zahl der aktiven Mitglieder" @@ -5840,11 +5976,6 @@ msgstr "%(name)s hat an dem Willkommenstreffen noch nicht teilgenommen. Stelle s #~ msgid "Shift system" #~ msgstr "Schichten" -#, fuzzy -#~| msgid "Credited member" -#~ msgid "Resigned members" -#~ msgstr "Empfangendes Mitglied" - #, fuzzy #~| msgid "List of all emails" #~ msgid "List of resigned members" @@ -6152,9 +6283,6 @@ msgstr "%(name)s hat an dem Willkommenstreffen noch nicht teilgenommen. Stelle s #~ msgid "Required number of working members to reach break-even" #~ msgstr "Erforderliche Anzahl von mitarbeitende Mitgliedern*innen, um den Break-Even zu erreichen" -#~ msgid "Active members" -#~ msgstr "Aktive Mitglieder" - #, python-format #~ msgid "" #~ "\n" diff --git a/tapir/translations/locale/de/LC_MESSAGES/djangojs.po b/tapir/translations/locale/de/LC_MESSAGES/djangojs.po index 8b9d6609e..53eff401a 100644 --- a/tapir/translations/locale/de/LC_MESSAGES/djangojs.po +++ b/tapir/translations/locale/de/LC_MESSAGES/djangojs.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-12-05 12:50+0100\n" +"POT-Creation-Date: 2025-01-31 12:35+0100\n" "PO-Revision-Date: 2024-11-05 11:14+0100\n" "Last-Translator: \n" "Language-Team: \n" @@ -19,62 +19,106 @@ msgstr "" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Poedit 3.5\n" -#: dist/assets/welcome_desk-CNolcy1C.js:1 +#: dist/assets/fancy_export-DavLwnOd.js:3 +msgid "Fancy export" +msgstr "" + +#: dist/assets/fancy_export-DavLwnOd.js:3 +msgid "Source dataset" +msgstr "" + +#: dist/assets/fancy_export-DavLwnOd.js:3 +msgid "Date" +msgstr "" + +#: dist/assets/fancy_export-DavLwnOd.js:3 +msgid "The date is only relevant for the following fields: shift_status, is_working, is_exempted, is_paused, can_shop. For all other fields, the value as it is now is exported, not the value as it was at the given date." +msgstr "" + +#: dist/assets/fancy_export-DavLwnOd.js:3 +msgid "Add columns to the export" +msgstr "" + +#: dist/assets/fancy_export-DavLwnOd.js:3 +msgid "Click on a selected column to deselect it." +msgstr "" + +#: dist/assets/fancy_export-DavLwnOd.js:3 +msgid "Build export for " +msgstr "" + +#: dist/assets/fancy_export-DavLwnOd.js:3 +msgid "Pick a source dataset" +msgstr "" + +#: dist/assets/fancy_export-DavLwnOd.js:3 +msgid "Copy export to clipboard" +msgstr "" + +#: dist/assets/fancy_export-DavLwnOd.js:3 +msgid "Build the export to copy it" +msgstr "" + +#: dist/assets/fancy_export-DavLwnOd.js:3 +msgid "Waiting for build" +msgstr "" + +#: dist/assets/welcome_desk-ehnOadaX.js:1 msgid "Use the search field on the top right." msgstr "Verwende das Suchfeld oben rechts." -#: dist/assets/welcome_desk-CNolcy1C.js:1 +#: dist/assets/welcome_desk-ehnOadaX.js:1 msgid "Name" msgstr "Name" -#: dist/assets/welcome_desk-CNolcy1C.js:1 +#: dist/assets/welcome_desk-ehnOadaX.js:1 msgid "Can shop" msgstr "Kann einkaufen" -#: dist/assets/welcome_desk-CNolcy1C.js:1 +#: dist/assets/welcome_desk-ehnOadaX.js:1 msgid "Yes" msgstr "Ja" -#: dist/assets/welcome_desk-CNolcy1C.js:1 +#: dist/assets/welcome_desk-ehnOadaX.js:1 msgid "No" msgstr "Nein" -#: dist/assets/welcome_desk-CNolcy1C.js:1 +#: dist/assets/welcome_desk-ehnOadaX.js:1 msgid "Member details" msgstr "Mitgliedsdetails" -#: dist/assets/welcome_desk-CNolcy1C.js:1 +#: dist/assets/welcome_desk-ehnOadaX.js:1 msgid "Member" msgstr "Mitgleid" -#: dist/assets/welcome_desk-CNolcy1C.js:1 +#: dist/assets/welcome_desk-ehnOadaX.js:1 msgid "yes" msgstr "ja" -#: dist/assets/welcome_desk-CNolcy1C.js:1 +#: dist/assets/welcome_desk-ehnOadaX.js:1 msgid "no" msgstr "nein" -#: dist/assets/welcome_desk-CNolcy1C.js:1 +#: dist/assets/welcome_desk-ehnOadaX.js:1 msgid "Why this member cannot shop: " msgstr "Warum kann dieses Mitglied nicht einkaufen: " -#: dist/assets/welcome_desk-CNolcy1C.js:1 +#: dist/assets/welcome_desk-ehnOadaX.js:1 msgid "Co-purchaser: " msgstr "Miteinkäufer*in: " -#: dist/assets/welcome_desk-CNolcy1C.js:1 +#: dist/assets/welcome_desk-ehnOadaX.js:1 msgid "None" msgstr "Keine" -#: dist/assets/welcome_desk-CNolcy1C.js:1 +#: dist/assets/welcome_desk-ehnOadaX.js:1 msgid "Whoops! Something went wrong. Please try again. If it keeps happening, please write in the #tapir channel on Slack with your current search: " msgstr "Hoppla! Es ist was schiefgegangen. Bitte versuche es nochmal. Wenn das wieder passiert, schreib bitte in der #tapir-Kanal bei Slack mit deine aktuelle Suche: " -#: dist/assets/welcome_desk-CNolcy1C.js:1 +#: dist/assets/welcome_desk-ehnOadaX.js:1 msgid "Welcome Desk" msgstr "Welcome Desk" -#: dist/assets/welcome_desk-CNolcy1C.js:1 +#: dist/assets/welcome_desk-ehnOadaX.js:1 msgid "Name or member ID" msgstr "Name oder Mitgliedsnummer" diff --git a/test.csv b/test.csv new file mode 100644 index 000000000..b487791dc --- /dev/null +++ b/test.csv @@ -0,0 +1,333 @@ +member_number,shift_capabilities +1, +2, +3, +4,cashier +5, +6, +8,cashier +9, +10, +11, +12, +13,cashier +15,cashier +16, +17,shift_coordinator +18, +19,cashier +20, +22, +23,shift_coordinator +24,cashier +25,shift_coordinator +26,cashier +27,cashier +29, +30, +31, +32, +33,cashier +34, +36, +37, +38,cashier +39,cashier +41,cashier +43,shift_coordinator - cashier +44,shift_coordinator +45,cashier +46,shift_coordinator +47, +48,cashier +50, +51, +52, +53,cashier +54,cashier +55,cashier +57, +58, +59, +61,shift_coordinator - cashier +62,cashier +64,shift_coordinator - cashier +65,shift_coordinator - cashier +66, +67, +68, +69, +71, +72, +73,cashier +74, +75, +76, +78,cashier +79,cashier +81, +82, +83, +85,cashier +86, +87, +88, +89,cashier +90,cashier +92, +93, +94, +95, +96, +97,shift_coordinator - cashier +99, +100,cashier +101, +102,cashier +103, +104, +106,cashier +107, +108, +109, +110, +111, +113,shift_coordinator - cashier +114, +115,cashier +116, +117, +118,cashier +121,cashier +122,cashier +123,cashier +124, +125,cashier +127,cashier +128,cashier +129, +130,shift_coordinator +131, +132, +134, +135, +136, +137, +138,shift_coordinator +139, +141, +142, +143, +144, +145,cashier +146, +148, +149,shift_coordinator +150,shift_coordinator +151, +152, +153, +155, +156, +157,shift_coordinator +158, +159,cashier +162, +163, +164, +165, +166, +167,shift_coordinator - cashier +169, +170,shift_coordinator +171, +172, +173, +174, +176,cashier +177, +178, +179,shift_coordinator - cashier +181,cashier +183,cashier +184, +185,cashier +186,cashier +187, +188, +190, +191, +192, +193,shift_coordinator +194, +195, +197, +198, +199, +201,shift_coordinator +202,cashier +204,cashier +205, +206,shift_coordinator +207, +208,shift_coordinator - cashier +209, +211,cashier +212, +213,cashier +214, +215, +216,cashier +218, +219,shift_coordinator - cashier +220, +221, +222, +223, +225,shift_coordinator - cashier +226,cashier +227, +228, +229, +230,cashier +232,cashier +233, +234, +235, +236,cashier +237,shift_coordinator +239,cashier +241,cashier +242, +243, +244, +246,shift_coordinator - cashier +247, +248, +249, +250, +251, +253,cashier +254, +255,cashier +256, +257, +258,cashier +260, +261, +262, +263, +264,cashier +265, +267, +268,shift_coordinator +269, +270, +271, +272,cashier +274, +275, +276, +277, +278, +279,cashier +281,cashier +282, +283, +284, +285, +286, +288,cashier +289, +290,shift_coordinator +291, +292, +293, +295,cashier +296, +297,cashier +298, +299, +300,shift_coordinator +302, +303,cashier +304,cashier +305, +306, +307,shift_coordinator +309,cashier +310, +311, +312, +313, +314, +316, +317,shift_coordinator - cashier +318, +319, +321,shift_coordinator - cashier +323,shift_coordinator +324,cashier +325,cashier +326, +327, +328, +330, +331, +332,cashier +333, +334,shift_coordinator +335,cashier +337,shift_coordinator +338, +339, +340, +341,shift_coordinator - cashier +342,cashier +344, +345, +346, +347,shift_coordinator +348, +349,cashier +351, +352, +353,cashier +354, +355, +356, +358, +359, +361,shift_coordinator +362,cashier +363,cashier +365, +366, +367,shift_coordinator +368, +369,cashier +370,cashier +372, +373,cashier +374, +375, +376, +377, +379, +380, +381, +382, +383, +384,cashier +386, +387,cashier +388, +389,cashier +390, +391, +393, +394, +395, +396, +397, +398,cashier \ No newline at end of file diff --git a/vite.config.ts b/vite.config.ts index e0ad5f242..ba9cdf915 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -20,6 +20,7 @@ export default defineConfig(() => { input: { welcome_desk: join(INPUT_DIR, "/welcome_desk/welcome_desk_entry.tsx"), fancy_graph: join(INPUT_DIR, "/statistics/fancy_graph_entry.tsx"), + fancy_export: join(INPUT_DIR, "/statistics/fancy_export_entry.tsx"), }, }, },